diff options
Diffstat (limited to 'src/gui/widgets')
154 files changed, 97713 insertions, 0 deletions
diff --git a/src/gui/widgets/qabstractbutton.cpp b/src/gui/widgets/qabstractbutton.cpp new file mode 100644 index 0000000..330a7f8 --- /dev/null +++ b/src/gui/widgets/qabstractbutton.cpp @@ -0,0 +1,1468 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractbutton.h" +#include "qabstractitemview.h" +#include "qbuttongroup.h" +#include "qabstractbutton_p.h" +#include "qevent.h" +#include "qpainter.h" +#include "qapplication.h" +#include "qstyle.h" +#include "qaction.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif + +QT_BEGIN_NAMESPACE + +#define AUTO_REPEAT_DELAY 300 +#define AUTO_REPEAT_INTERVAL 100 + +extern bool qt_tab_all_widgets; + +/*! + \class QAbstractButton + + \brief The QAbstractButton class is the abstract base class of + button widgets, providing functionality common to buttons. + + \ingroup abstractwidgets + + This class implements an \e abstract button. + Subclasses of this class handle user actions, and specify how the button + is drawn. + + QAbstractButton provides support for both push buttons and checkable + (toggle) buttons. Checkable buttons are implemented in the QRadioButton + and QCheckBox classes. Push buttons are implemented in the + QPushButton and QToolButton classes; these also provide toggle + behavior if required. + + Any button can display a label containing text and an icon. setText() + sets the text; setIcon() sets the icon. If a button is disabled, its label + is changed to give the button a "disabled" appearance. + + If the button is a text button with a string containing an + ampersand ('&'), QAbstractButton automatically creates a shortcut + key. For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qabstractbutton.cpp 0 + + The \key Alt+C shortcut is assigned to the button, i.e., when the + user presses \key Alt+C the button will call animateClick(). See + the \l {QShortcut#mnemonic}{QShortcut} documentation for details + (to display an actual ampersand, use '&&'). + + You can also set a custom shortcut key using the setShortcut() + function. This is useful mostly for buttons that do not have any + text, because they have no automatic shortcut. + + \snippet doc/src/snippets/code/src_gui_widgets_qabstractbutton.cpp 1 + + All of the buttons provided by Qt (QPushButton, QToolButton, + QCheckBox, and QRadioButton) can display both \l text and \l{icon}{icons}. + + A button can be made the default button in a dialog are provided by + QPushButton::setDefault() and QPushButton::setAutoDefault(). + + QAbstractButton provides most of the states used for buttons: + + \list + + \o isDown() indicates whether the button is \e pressed down. + + \o isChecked() indicates whether the button is \e checked. Only + checkable buttons can be checked and unchecked (see below). + + \o isEnabled() indicates whether the button can be pressed by the + user. + + \o setAutoRepeat() sets whether the button will auto-repeat if the + user holds it down. \l autoRepeatDelay and \l autoRepeatInterval + define how auto-repetition is done. + + \o setCheckable() sets whether the button is a toggle button or not. + + \endlist + + The difference between isDown() and isChecked() is as follows. + When the user clicks a toggle button to check it, the button is first + \e pressed then released into the \e checked state. When the user + clicks it again (to uncheck it), the button moves first to the + \e pressed state, then to the \e unchecked state (isChecked() and + isDown() are both false). + + QAbstractButton provides four signals: + + \list 1 + + \o pressed() is emitted when the left mouse button is pressed while + the mouse cursor is inside the button. + + \o released() is emitted when the left mouse button is released. + + \o clicked() is emitted when the button is first pressed and then + released, when the shortcut key is typed, or when click() or + animateClick() is called. + + \o toggled() is emitted when the state of a toggle button changes. + + \endlist + + To subclass QAbstractButton, you must reimplement at least + paintEvent() to draw the button's outline and its text or pixmap. It + is generally advisable to reimplement sizeHint() as well, and + sometimes hitButton() (to determine whether a button press is within + the button). For buttons with more than two states (like tri-state + buttons), you will also have to reimplement checkStateSet() and + nextCheckState(). + + \sa QButtonGroup +*/ + +QAbstractButtonPrivate::QAbstractButtonPrivate(QSizePolicy::ControlType type) + : +#ifndef QT_NO_SHORTCUT + shortcutId(0), +#endif + checkable(false), checked(false), autoRepeat(false), autoExclusive(false), + down(false), blockRefresh(false), +#ifndef QT_NO_BUTTONGROUP + group(0), +#endif + autoRepeatDelay(AUTO_REPEAT_DELAY), + autoRepeatInterval(AUTO_REPEAT_INTERVAL), + controlType(type) +{} + +#ifndef QT_NO_BUTTONGROUP + +class QButtonGroupPrivate: public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QButtonGroup) + +public: + QButtonGroupPrivate():exclusive(true){} + QList<QAbstractButton *> buttonList; + QPointer<QAbstractButton> checkedButton; + void detectCheckedButton(); + void notifyChecked(QAbstractButton *button); + bool exclusive; + QMap<QAbstractButton*, int> mapping; +}; + +QButtonGroup::QButtonGroup(QObject *parent) + : QObject(*new QButtonGroupPrivate, parent) +{ +} + +QButtonGroup::~QButtonGroup() +{ + Q_D(QButtonGroup); + for (int i = 0; i < d->buttonList.count(); ++i) + d->buttonList.at(i)->d_func()->group = 0; +} + + +bool QButtonGroup::exclusive() const +{ + Q_D(const QButtonGroup); + return d->exclusive; +} + +void QButtonGroup::setExclusive(bool exclusive) +{ + Q_D(QButtonGroup); + d->exclusive = exclusive; +} + +/*! + Adds the given \a button to the end of the group's internal list of buttons. + + \sa removeButton() +*/ +void QButtonGroup::addButton(QAbstractButton *button) +{ + addButton(button, -1); +} + +void QButtonGroup::addButton(QAbstractButton *button, int id) +{ + Q_D(QButtonGroup); + if (QButtonGroup *previous = button->d_func()->group) + previous->removeButton(button); + button->d_func()->group = this; + d->buttonList.append(button); + if (id != -1) + d->mapping[button] = id; + if (d->exclusive && button->isChecked()) + button->d_func()->notifyChecked(); +} + +void QButtonGroup::removeButton(QAbstractButton *button) +{ + Q_D(QButtonGroup); + if (d->checkedButton == button) { + d->detectCheckedButton(); + } + if (button->d_func()->group == this) { + button->d_func()->group = 0; + d->buttonList.removeAll(button); + d->mapping.remove(button); + } +} + +QList<QAbstractButton*> QButtonGroup::buttons() const +{ + Q_D(const QButtonGroup); + return d->buttonList; +} + +QAbstractButton *QButtonGroup::checkedButton() const +{ + Q_D(const QButtonGroup); + return d->checkedButton; +} + +QAbstractButton *QButtonGroup::button(int id) const +{ + Q_D(const QButtonGroup); + return d->mapping.key(id); +} + +void QButtonGroup::setId(QAbstractButton *button, int id) +{ + Q_D(QButtonGroup); + if (button && id != -1) + d->mapping[button] = id; +} + +int QButtonGroup::id(QAbstractButton *button) const +{ + Q_D(const QButtonGroup); + return d->mapping.value(button, -1); +} + +int QButtonGroup::checkedId() const +{ + Q_D(const QButtonGroup); + return d->mapping.value(d->checkedButton, -1); +} + +// detect a checked button other than the current one +void QButtonGroupPrivate::detectCheckedButton() +{ + QAbstractButton *previous = checkedButton; + checkedButton = 0; + if (exclusive) + return; + for (int i = 0; i < buttonList.count(); i++) { + if (buttonList.at(i) != previous && buttonList.at(i)->isChecked()) { + checkedButton = buttonList.at(i); + return; + } + } +} + +#endif // QT_NO_BUTTONGROUP + +QList<QAbstractButton *>QAbstractButtonPrivate::queryButtonList() const +{ +#ifndef QT_NO_BUTTONGROUP + if (group) + return group->d_func()->buttonList; +#endif + + Q_Q(const QAbstractButton); + QList<QAbstractButton*>candidates; + if (q->parentWidget()) { + candidates = qFindChildren<QAbstractButton *>(q->parentWidget()); + if (autoExclusive) { + for (int i = candidates.count() - 1; i >= 0; --i) { + QAbstractButton *candidate = candidates.at(i); + if (!candidate->autoExclusive() +#ifndef QT_NO_BUTTONGROUP + || candidate->group() +#endif + ) + candidates.removeAt(i); + } + } + } + return candidates; +} + +QAbstractButton *QAbstractButtonPrivate::queryCheckedButton() const +{ +#ifndef QT_NO_BUTTONGROUP + if (group) + return group->d_func()->checkedButton; +#endif + + Q_Q(const QAbstractButton); + QList<QAbstractButton *> buttonList = queryButtonList(); + if (!autoExclusive || buttonList.count() == 1) // no group + return 0; + + for (int i = 0; i < buttonList.count(); ++i) { + QAbstractButton *b = buttonList.at(i); + if (b->d_func()->checked && b != q) + return b; + } + return checked ? const_cast<QAbstractButton *>(q) : 0; +} + +void QAbstractButtonPrivate::notifyChecked() +{ +#ifndef QT_NO_BUTTONGROUP + Q_Q(QAbstractButton); + if (group) { + QAbstractButton *previous = group->d_func()->checkedButton; + group->d_func()->checkedButton = q; + if (group->d_func()->exclusive && previous && previous != q) + previous->nextCheckState(); + } else +#endif + if (autoExclusive) { + if (QAbstractButton *b = queryCheckedButton()) + b->setChecked(false); + } +} + +void QAbstractButtonPrivate::moveFocus(int key) +{ + QList<QAbstractButton *> buttonList = queryButtonList();; +#ifndef QT_NO_BUTTONGROUP + bool exclusive = group ? group->d_func()->exclusive : autoExclusive; +#else + bool exclusive = autoExclusive; +#endif + QWidget *f = qApp->focusWidget(); + QAbstractButton *fb = qobject_cast<QAbstractButton *>(f); + if (!fb || !buttonList.contains(fb)) + return; + + QAbstractButton *candidate = 0; + int bestScore = -1; + QRect target = f->rect().translated(f->mapToGlobal(QPoint(0,0))); + QPoint goal = target.center(); + uint focus_flag = qt_tab_all_widgets ? Qt::TabFocus : Qt::StrongFocus; + + for (int i = 0; i < buttonList.count(); ++i) { + QAbstractButton *button = buttonList.at(i); + if (button != f && button->window() == f->window() && button->isEnabled() && !button->isHidden() && + (autoExclusive || (button->focusPolicy() & focus_flag) == focus_flag)) { + QRect buttonRect = button->rect().translated(button->mapToGlobal(QPoint(0,0))); + QPoint p = buttonRect.center(); + + //Priority to widgets that overlap on the same coordinate. + //In that case, the distance in the direction will be used as significant score, + //take also in account orthogonal distance in case two widget are in the same distance. + int score; + if ((buttonRect.x() < target.right() && target.x() < buttonRect.right()) + && (key == Qt::Key_Up || key == Qt::Key_Down)) { + //one item's is at the vertical of the other + score = (qAbs(p.y() - goal.y()) << 16) + qAbs(p.x() - goal.x()); + } else if ((buttonRect.y() < target.bottom() && target.y() < buttonRect.bottom()) + && (key == Qt::Key_Left || key == Qt::Key_Right) ) { + //one item's is at the horizontal of the other + score = (qAbs(p.x() - goal.x()) << 16) + qAbs(p.y() - goal.y()); + } else { + score = (1 << 30) + (p.y() - goal.y()) * (p.y() - goal.y()) + (p.x() - goal.x()) * (p.x() - goal.x()); + } + + if (score > bestScore && candidate) + continue; + + switch(key) { + case Qt::Key_Up: + if (p.y() < goal.y()) { + candidate = button; + bestScore = score; + } + break; + case Qt::Key_Down: + if (p.y() > goal.y()) { + candidate = button; + bestScore = score; + } + break; + case Qt::Key_Left: + if (p.x() < goal.x()) { + candidate = button; + bestScore = score; + } + break; + case Qt::Key_Right: + if (p.x() > goal.x()) { + candidate = button; + bestScore = score; + } + break; + } + } + } + + if (exclusive +#ifdef QT_KEYPAD_NAVIGATION + && !QApplication::keypadNavigationEnabled() +#endif + && candidate + && fb->d_func()->checked + && candidate->d_func()->checkable) + candidate->click(); + + if (candidate) { + if (key == Qt::Key_Up || key == Qt::Key_Left) + candidate->setFocus(Qt::BacktabFocusReason); + else + candidate->setFocus(Qt::TabFocusReason); + } +} + +void QAbstractButtonPrivate::fixFocusPolicy() +{ + Q_Q(QAbstractButton); +#ifndef QT_NO_BUTTONGROUP + if (!group && !autoExclusive) +#else + if (!autoExclusive) +#endif + return; + + QList<QAbstractButton *> buttonList = queryButtonList(); + for (int i = 0; i < buttonList.count(); ++i) { + QAbstractButton *b = buttonList.at(i); + if (!b->isCheckable()) + continue; + b->setFocusPolicy((Qt::FocusPolicy) ((b == q || !q->isCheckable()) + ? (b->focusPolicy() | Qt::TabFocus) + : (b->focusPolicy() & ~Qt::TabFocus))); + } +} + +void QAbstractButtonPrivate::init() +{ + Q_Q(QAbstractButton); + + q->setFocusPolicy(Qt::FocusPolicy(q->style()->styleHint(QStyle::SH_Button_FocusPolicy))); + q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, controlType)); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + q->setForegroundRole(QPalette::ButtonText); + q->setBackgroundRole(QPalette::Button); +} + +void QAbstractButtonPrivate::refresh() +{ + Q_Q(QAbstractButton); + + if (blockRefresh) + return; + q->update(); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(q, 0, QAccessible::StateChanged); +#endif +} + +void QAbstractButtonPrivate::click() +{ + Q_Q(QAbstractButton); + + down = false; + blockRefresh = true; + bool changeState = true; + if (checked && queryCheckedButton() == q) { + // the checked button of an exclusive or autoexclusive group cannot be unchecked +#ifndef QT_NO_BUTTONGROUP + if (group ? group->d_func()->exclusive : autoExclusive) +#else + if (autoExclusive) +#endif + changeState = false; + } + + QPointer<QAbstractButton> guard(q); + if (changeState) { + q->nextCheckState(); + if (!guard) + return; + } + blockRefresh = false; + refresh(); + q->repaint(); //flush paint event before invoking potentially expensive operation + QApplication::flush(); + if (guard) + emitReleased(); + if (guard) + emitClicked(); +} + +void QAbstractButtonPrivate::emitClicked() +{ + Q_Q(QAbstractButton); + QPointer<QAbstractButton> guard(q); + emit q->clicked(checked); +#ifndef QT_NO_BUTTONGROUP + if (guard && group) { + emit group->buttonClicked(group->id(q)); + if (guard && group) + emit group->buttonClicked(q); + } +#endif +} + +void QAbstractButtonPrivate::emitPressed() +{ + Q_Q(QAbstractButton); + QPointer<QAbstractButton> guard(q); + emit q->pressed(); +#ifndef QT_NO_BUTTONGROUP + if (guard && group) { + emit group->buttonPressed(group->id(q)); + if (guard && group) + emit group->buttonPressed(q); + } +#endif +} + +void QAbstractButtonPrivate::emitReleased() +{ + Q_Q(QAbstractButton); + QPointer<QAbstractButton> guard(q); + emit q->released(); +#ifndef QT_NO_BUTTONGROUP + if (guard && group) { + emit group->buttonReleased(group->id(q)); + if (guard && group) + emit group->buttonReleased(q); + } +#endif +} + +/*! + Constructs an abstract button with a \a parent. +*/ +QAbstractButton::QAbstractButton(QWidget *parent) + : QWidget(*new QAbstractButtonPrivate, parent, 0) +{ + Q_D(QAbstractButton); + d->init(); +} + +/*! + Destroys the button. + */ + QAbstractButton::~QAbstractButton() +{ +#ifndef QT_NO_BUTTONGROUP + Q_D(QAbstractButton); + if (d->group) + d->group->removeButton(this); +#endif +} + + +/*! \internal + */ +QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent) + : QWidget(dd, parent, 0) +{ + Q_D(QAbstractButton); + d->init(); +} + +/*! +\property QAbstractButton::text +\brief the text shown on the button + +If the button has no text, the text() function will return a an empty +string. + +If the text contains an ampersand character ('&'), a shortcut is +automatically created for it. The character that follows the '&' will +be used as the shortcut key. Any previous shortcut will be +overwritten, or cleared if no shortcut is defined by the text. See the +\l {QShortcut#mnemonic}{QShortcut} documentation for details (to +display an actual ampersand, use '&&'). + +There is no default text. +*/ + +void QAbstractButton::setText(const QString &text) +{ + Q_D(QAbstractButton); + if (d->text == text) + return; + d->text = text; +#ifndef QT_NO_SHORTCUT + QKeySequence newMnemonic = QKeySequence::mnemonic(text); + setShortcut(newMnemonic); +#endif + d->sizeHint = QSize(); + update(); + updateGeometry(); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::NameChanged); +#endif +} + +QString QAbstractButton::text() const +{ + Q_D(const QAbstractButton); + return d->text; +} + + +/*! + \property QAbstractButton::icon + \brief the icon shown on the button + + The icon's default size is defined by the GUI style, but can be + adjusted by setting the \l iconSize property. +*/ +void QAbstractButton::setIcon(const QIcon &icon) +{ + Q_D(QAbstractButton); + d->icon = icon; + d->sizeHint = QSize(); + update(); + updateGeometry(); +} + +QIcon QAbstractButton::icon() const +{ + Q_D(const QAbstractButton); + return d->icon; +} + +#ifndef QT_NO_SHORTCUT +/*! +\property QAbstractButton::shortcut +\brief the mnemonic associated with the button +*/ + +void QAbstractButton::setShortcut(const QKeySequence &key) +{ + Q_D(QAbstractButton); + if (d->shortcutId != 0) + releaseShortcut(d->shortcutId); + d->shortcut = key; + d->shortcutId = grabShortcut(key); +} + +QKeySequence QAbstractButton::shortcut() const +{ + Q_D(const QAbstractButton); + return d->shortcut; +} +#endif // QT_NO_SHORTCUT + +/*! +\property QAbstractButton::checkable +\brief whether the button is checkable + +By default, the button is not checkable. + +\sa checked +*/ +void QAbstractButton::setCheckable(bool checkable) +{ + Q_D(QAbstractButton); + if (d->checkable == checkable) + return; + + d->checkable = checkable; + d->checked = false; +} + +bool QAbstractButton::isCheckable() const +{ + Q_D(const QAbstractButton); + return d->checkable; +} + +/*! +\property QAbstractButton::checked +\brief whether the button is checked + +Only checkable buttons can be checked. By default, the button is unchecked. + +\sa checkable +*/ +void QAbstractButton::setChecked(bool checked) +{ + Q_D(QAbstractButton); + if (!d->checkable || d->checked == checked) { + if (!d->blockRefresh) + checkStateSet(); + return; + } + + if (!checked && d->queryCheckedButton() == this) { + // the checked button of an exclusive or autoexclusive group cannot be unchecked +#ifndef QT_NO_BUTTONGROUP + if (d->group ? d->group->d_func()->exclusive : d->autoExclusive) + return; + if (d->group) + d->group->d_func()->detectCheckedButton(); +#else + if (d->autoExclusive) + return; +#endif + } + + QPointer<QAbstractButton> guard(this); + + d->checked = checked; + if (!d->blockRefresh) + checkStateSet(); + d->refresh(); + + if (guard && checked) + d->notifyChecked(); + if (guard) + emit toggled(checked); +} + +bool QAbstractButton::isChecked() const +{ + Q_D(const QAbstractButton); + return d->checked; +} + +/*! + \property QAbstractButton::down + \brief whether the button is pressed down + + If this property is true, the button is pressed down. The signals + pressed() and clicked() are not emitted if you set this property + to true. The default is false. +*/ + +void QAbstractButton::setDown(bool down) +{ + Q_D(QAbstractButton); + if (d->down == down) + return; + d->down = down; + d->refresh(); + if (d->autoRepeat && d->down) + d->repeatTimer.start(d->autoRepeatDelay, this); + else + d->repeatTimer.stop(); +} + +bool QAbstractButton::isDown() const +{ + Q_D(const QAbstractButton); + return d->down; +} + +/*! +\property QAbstractButton::autoRepeat +\brief whether autoRepeat is enabled + +If autoRepeat is enabled, then the pressed(), released(), and clicked() signals are emitted at +regular intervals when the button is down. autoRepeat is off by default. +The initial delay and the repetition interval are defined in milliseconds by \l +autoRepeatDelay and \l autoRepeatInterval. + +Note: If a button is pressed down by a shortcut key, then auto-repeat is enabled and timed by the +system and not by this class. The pressed(), released(), and clicked() signals will be emitted +like in the normal case. +*/ + +void QAbstractButton::setAutoRepeat(bool autoRepeat) +{ + Q_D(QAbstractButton); + if (d->autoRepeat == autoRepeat) + return; + d->autoRepeat = autoRepeat; + if (d->autoRepeat && d->down) + d->repeatTimer.start(d->autoRepeatDelay, this); + else + d->repeatTimer.stop(); +} + +bool QAbstractButton::autoRepeat() const +{ + Q_D(const QAbstractButton); + return d->autoRepeat; +} + +/*! + \property QAbstractButton::autoRepeatDelay + \brief the initial delay of auto-repetition + \since 4.2 + + If \l autoRepeat is enabled, then autoRepeatDelay defines the initial + delay in milliseconds before auto-repetition kicks in. + + \sa autoRepeat, autoRepeatInterval +*/ + +void QAbstractButton::setAutoRepeatDelay(int autoRepeatDelay) +{ + Q_D(QAbstractButton); + d->autoRepeatDelay = autoRepeatDelay; +} + +int QAbstractButton::autoRepeatDelay() const +{ + Q_D(const QAbstractButton); + return d->autoRepeatDelay; +} + +/*! + \property QAbstractButton::autoRepeatInterval + \brief the interval of auto-repetition + \since 4.2 + + If \l autoRepeat is enabled, then autoRepeatInterval defines the + length of the auto-repetition interval in millisecons. + + \sa autoRepeat, autoRepeatDelay +*/ + +void QAbstractButton::setAutoRepeatInterval(int autoRepeatInterval) +{ + Q_D(QAbstractButton); + d->autoRepeatInterval = autoRepeatInterval; +} + +int QAbstractButton::autoRepeatInterval() const +{ + Q_D(const QAbstractButton); + return d->autoRepeatInterval; +} + + + +/*! +\property QAbstractButton::autoExclusive +\brief whether auto-exclusivity is enabled + +If auto-exclusivity is enabled, checkable buttons that belong to the +same parent widget behave as if they were part of the same +exclusive button group. In an exclusive button group, only one button +can be checked at any time; checking another button automatically +unchecks the previously checked one. + +The property has no effect on buttons that belong to a button +group. + +autoExclusive is off by default, except for radio buttons. + +\sa QRadioButton +*/ +void QAbstractButton::setAutoExclusive(bool autoExclusive) +{ + Q_D(QAbstractButton); + d->autoExclusive = autoExclusive; +} + +bool QAbstractButton::autoExclusive() const +{ + Q_D(const QAbstractButton); + return d->autoExclusive; +} + +#ifndef QT_NO_BUTTONGROUP +/*! + Returns the group that this button belongs to. + + If the button is not a member of any QButtonGroup, this function + returns 0. + + \sa QButtonGroup +*/ +QButtonGroup *QAbstractButton::group() const +{ + Q_D(const QAbstractButton); + return d->group; +} +#endif // QT_NO_BUTTONGROUP + +/*! +Performs an animated click: the button is pressed immediately, and +released \a msec milliseconds later (the default is 100 ms). + +Calling this function again before the button was released will reset +the release timer. + +All signals associated with a click are emitted as appropriate. + +This function does nothing if the button is \link setEnabled() +disabled. \endlink + +\sa click() +*/ +void QAbstractButton::animateClick(int msec) +{ + if (!isEnabled()) + return; + Q_D(QAbstractButton); + if (d->checkable && focusPolicy() & Qt::ClickFocus) + setFocus(); + setDown(true); + repaint(); //flush paint event before invoking potentially expensive operation + QApplication::flush(); + if (!d->animateTimer.isActive()) + d->emitPressed(); + d->animateTimer.start(msec, this); +} + +/*! +Performs a click. + +All the usual signals associated with a click are emitted as +appropriate. If the button is checkable, the state of the button is +toggled. + +This function does nothing if the button is \link setEnabled() +disabled. \endlink + +\sa animateClick() + */ +void QAbstractButton::click() +{ + if (!isEnabled()) + return; + Q_D(QAbstractButton); + QPointer<QAbstractButton> guard(this); + d->down = true; + d->emitPressed(); + if (guard) { + d->down = false; + nextCheckState(); + if (guard) + d->emitReleased(); + if (guard) + d->emitClicked(); + } +} + +/*! \fn void QAbstractButton::toggle() + + Toggles the state of a checkable button. + + \sa checked +*/ +void QAbstractButton::toggle() +{ + Q_D(QAbstractButton); + setChecked(!d->checked); +} + + +/*! This virtual handler is called when setChecked() was called, +unless it was called from within nextCheckState(). It allows +subclasses to reset their intermediate button states. + +\sa nextCheckState() + */ +void QAbstractButton::checkStateSet() +{ +} + +/*! This virtual handler is called when a button is clicked. The +default implementation calls setChecked(!isChecked()) if the button +isCheckable(). It allows subclasses to implement intermediate button +states. + +\sa checkStateSet() +*/ +void QAbstractButton::nextCheckState() +{ + if (isCheckable()) + setChecked(!isChecked()); +} + +/*! +Returns true if \a pos is inside the clickable button rectangle; +otherwise returns false. + +By default, the clickable area is the entire widget. Subclasses +may reimplement this function to provide support for clickable +areas of different shapes and sizes. +*/ +bool QAbstractButton::hitButton(const QPoint &pos) const +{ + return rect().contains(pos); +} + +/*! \reimp */ +bool QAbstractButton::event(QEvent *e) +{ + // as opposed to other widgets, disabled buttons accept mouse + // events. This avoids surprising click-through scenarios + if (!isEnabled()) { + switch(e->type()) { + case QEvent::TabletPress: + case QEvent::TabletRelease: + case QEvent::TabletMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::HoverMove: + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::ContextMenu: +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: +#endif + return true; + default: + break; + } + } + +#ifndef QT_NO_SHORTCUT + if (e->type() == QEvent::Shortcut) { + Q_D(QAbstractButton); + QShortcutEvent *se = static_cast<QShortcutEvent *>(e); + if (d->shortcutId != se->shortcutId()) + return false; + if (!se->isAmbiguous()) { + if (!d->animateTimer.isActive()) + animateClick(); + } else { + if (focusPolicy() != Qt::NoFocus) + setFocus(Qt::ShortcutFocusReason); + window()->setAttribute(Qt::WA_KeyboardFocusChange); + } + return true; + } +#endif + return QWidget::event(e); +} + +/*! \reimp */ +void QAbstractButton::mousePressEvent(QMouseEvent *e) +{ + Q_D(QAbstractButton); + if (e->button() != Qt::LeftButton) { + e->ignore(); + return; + } + if (hitButton(e->pos())) { + setDown(true); + repaint(); //flush paint event before invoking potentially expensive operation + QApplication::flush(); + d->emitPressed(); + e->accept(); + } else { + e->ignore(); + } +} + +/*! \reimp */ +void QAbstractButton::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QAbstractButton); + if (e->button() != Qt::LeftButton) { + e->ignore(); + return; + } + + if (!d->down) { + e->ignore(); + return; + } + + if (hitButton(e->pos())) { + d->repeatTimer.stop(); + d->click(); + e->accept(); + } else { + setDown(false); + e->ignore(); + } +} + +/*! \reimp */ +void QAbstractButton::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QAbstractButton); + if (!(e->buttons() & Qt::LeftButton)) { + e->ignore(); + return; + } + + if (hitButton(e->pos()) != d->down) { + setDown(!d->down); + repaint(); //flush paint event before invoking potentially expensive operation + QApplication::flush(); + if (d->down) + d->emitPressed(); + else + d->emitReleased(); + e->accept(); + } else if (!hitButton(e->pos())) { + e->ignore(); + } +} + +/*! \reimp */ +void QAbstractButton::keyPressEvent(QKeyEvent *e) +{ + Q_D(QAbstractButton); + bool next = true; + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + e->ignore(); + break; + case Qt::Key_Select: + case Qt::Key_Space: + if (!e->isAutoRepeat()) { + setDown(true); + repaint(); //flush paint event before invoking potentially expensive operation + QApplication::flush(); + d->emitPressed(); + } + break; + case Qt::Key_Up: + case Qt::Key_Left: + next = false; + // fall through + case Qt::Key_Right: + case Qt::Key_Down: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right)) { + e->ignore(); + return; + } +#endif + QWidget *pw; + if (d->autoExclusive +#ifndef QT_NO_BUTTONGROUP + || d->group +#endif +#ifndef QT_NO_ITEMVIEWS + || ((pw = parentWidget()) && qobject_cast<QAbstractItemView *>(pw->parentWidget())) +#endif + ) { + // ### Using qobject_cast to check if the parent is a viewport of + // QAbstractItemView is a crude hack, and should be revisited and + // cleaned up when fixing task 194373. It's here to ensure that we + // keep compatibility outside QAbstractItemView. + d->moveFocus(e->key()); + if (hasFocus()) // nothing happend, propagate + e->ignore(); + } else { + focusNextPrevChild(next); + } + break; + case Qt::Key_Escape: + if (d->down) { + setDown(false); + repaint(); //flush paint event before invoking potentially expensive operation + QApplication::flush(); + d->emitReleased(); + break; + } + // fall through + default: + e->ignore(); + } +} + +/*! \reimp */ +void QAbstractButton::keyReleaseEvent(QKeyEvent *e) +{ + Q_D(QAbstractButton); + + if (!e->isAutoRepeat()) + d->repeatTimer.stop(); + + switch (e->key()) { + case Qt::Key_Select: + case Qt::Key_Space: + if (!e->isAutoRepeat() && d->down) + d->click(); + break; + default: + e->ignore(); + } +} + +/*!\reimp + */ +void QAbstractButton::timerEvent(QTimerEvent *e) +{ + Q_D(QAbstractButton); + if (e->timerId() == d->repeatTimer.timerId()) { + d->repeatTimer.start(d->autoRepeatInterval, this); + if (d->down) { + QPointer<QAbstractButton> guard(this); + d->emitReleased(); + if (guard) + d->emitClicked(); + if (guard) + d->emitPressed(); + } + } else if (e->timerId() == d->animateTimer.timerId()) { + d->animateTimer.stop(); + d->click(); + } +} + +#if defined(Q_OS_WINCE) && !defined(QT_NO_CONTEXTMENU) +/*! \reimp */ +void QAbstractButton::contextMenuEvent(QContextMenuEvent *e) +{ + e->ignore(); + setDown(false); +} +#endif + +/*! \reimp */ +void QAbstractButton::focusInEvent(QFocusEvent *e) +{ + Q_D(QAbstractButton); +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled()) +#endif + d->fixFocusPolicy(); + QWidget::focusInEvent(e); +} + +/*! \reimp */ +void QAbstractButton::focusOutEvent(QFocusEvent *e) +{ + Q_D(QAbstractButton); + if (e->reason() != Qt::PopupFocusReason) + d->down = false; + QWidget::focusOutEvent(e); +} + +/*! \reimp */ +void QAbstractButton::changeEvent(QEvent *e) +{ + Q_D(QAbstractButton); + switch (e->type()) { + case QEvent::EnabledChange: + if (!isEnabled()) + setDown(false); + break; + default: + d->sizeHint = QSize(); + break; + } + QWidget::changeEvent(e); +} + +/*! + \fn void QAbstractButton::paintEvent(QPaintEvent *e) + \reimp +*/ + +/*! + \fn void QAbstractButton::pressed() + + This signal is emitted when the button is pressed down. + + \sa released(), clicked() +*/ + +/*! + \fn void QAbstractButton::released() + + This signal is emitted when the button is released. + + \sa pressed(), clicked(), toggled() +*/ + +/*! +\fn void QAbstractButton::clicked(bool checked) + +This signal is emitted when the button is activated (i.e. pressed down +then released while the mouse cursor is inside the button), when the +shortcut key is typed, or when click() or animateClick() is called. +Notably, this signal is \e not emitted if you call setDown(), +setChecked() or toggle(). + +If the button is checkable, \a checked is true if the button is +checked, or false if the button is unchecked. + +\sa pressed(), released(), toggled() +*/ + +/*! +\fn void QAbstractButton::toggled(bool checked) + +This signal is emitted whenever a checkable button changes its state. +\a checked is true if the button is checked, or false if the button is +unchecked. + +This may be the result of a user action, click() slot activation, +or because setChecked() was called. + +The states of buttons in exclusive button groups are updated before this +signal is emitted. This means that slots can act on either the "off" +signal or the "on" signal emitted by the buttons in the group whose +states have changed. + +For example, a slot that reacts to signals emitted by newly checked +buttons but which ignores signals from buttons that have been unchecked +can be implemented using the following pattern: + +\snippet doc/src/snippets/code/src_gui_widgets_qabstractbutton.cpp 2 + +Button groups can be created using the QButtonGroup class, and +updates to the button states monitored with the +\l{QButtonGroup::buttonClicked()} signal. + +\sa checked, clicked() +*/ + +/*! + \property QAbstractButton::iconSize + \brief the icon size used for this button. + + The default size is defined by the GUI style. This is a maximum + size for the icons. Smaller icons will not be scaled up. +*/ + +QSize QAbstractButton::iconSize() const +{ + Q_D(const QAbstractButton); + if (d->iconSize.isValid()) + return d->iconSize; + int e = style()->pixelMetric(QStyle::PM_ButtonIconSize, 0, this); + return QSize(e, e); +} + +void QAbstractButton::setIconSize(const QSize &size) +{ + Q_D(QAbstractButton); + if (d->iconSize == size) + return; + + d->iconSize = size; + d->sizeHint = QSize(); + updateGeometry(); + if (isVisible()) { + update(); + } +} + + +#ifdef QT3_SUPPORT +/*! + Use icon() instead. +*/ +QIcon *QAbstractButton::iconSet() const +{ + Q_D(const QAbstractButton); + if (!d->icon.isNull()) + return const_cast<QIcon *>(&d->icon); + return 0; +} + +/*! + Use QAbstractButton(QWidget *) instead. + + Call setObjectName() if you want to specify an object name, and + setParent() if you want to set the window flags. +*/ +QAbstractButton::QAbstractButton(QWidget *parent, const char *name, Qt::WindowFlags f) + : QWidget(*new QAbstractButtonPrivate, parent, f) +{ + Q_D(QAbstractButton); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! \fn bool QAbstractButton::isOn() const + + Use isChecked() instead. +*/ + +/*! + \fn QPixmap *QAbstractButton::pixmap() const + + This compatibility function always returns 0. + + Use icon() instead. +*/ + +/*! \fn void QAbstractButton::setPixmap(const QPixmap &p) + + Use setIcon() instead. +*/ + +/*! \fn void QAbstractButton::setIconSet(const QIcon &icon) + + Use setIcon() instead. +*/ + +/*! \fn void QAbstractButton::setOn(bool b) + + Use setChecked() instead. +*/ + +/*! \fn bool QAbstractButton::isToggleButton() const + + Use isCheckable() instead. +*/ + +/*! + \fn void QAbstractButton::setToggleButton(bool b) + + Use setCheckable() instead. +*/ + +/*! \fn void QAbstractButton::setAccel(const QKeySequence &key) + + Use setShortcut() instead. +*/ + +/*! \fn QKeySequence QAbstractButton::accel() const + + Use shortcut() instead. +*/ +#endif + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qabstractbutton.h b/src/gui/widgets/qabstractbutton.h new file mode 100644 index 0000000..6503a56 --- /dev/null +++ b/src/gui/widgets/qabstractbutton.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTBUTTON_H +#define QABSTRACTBUTTON_H + +#include <QtGui/qicon.h> +#include <QtGui/qkeysequence.h> +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QButtonGroup; +class QAbstractButtonPrivate; + +class Q_GUI_EXPORT QAbstractButton : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) +#ifndef QT_NO_SHORTCUT + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) +#endif + Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable) + Q_PROPERTY(bool checked READ isChecked WRITE setChecked DESIGNABLE isCheckable NOTIFY toggled USER true) + Q_PROPERTY(bool autoRepeat READ autoRepeat WRITE setAutoRepeat) + Q_PROPERTY(bool autoExclusive READ autoExclusive WRITE setAutoExclusive) + Q_PROPERTY(int autoRepeatDelay READ autoRepeatDelay WRITE setAutoRepeatDelay) + Q_PROPERTY(int autoRepeatInterval READ autoRepeatInterval WRITE setAutoRepeatInterval) + Q_PROPERTY(bool down READ isDown WRITE setDown DESIGNABLE false) + +public: + explicit QAbstractButton(QWidget* parent=0); + ~QAbstractButton(); + + void setText(const QString &text); + QString text() const; + + void setIcon(const QIcon &icon); + QIcon icon() const; + + QSize iconSize() const; + +#ifndef QT_NO_SHORTCUT + void setShortcut(const QKeySequence &key); + QKeySequence shortcut() const; +#endif + + void setCheckable(bool); + bool isCheckable() const; + + bool isChecked() const; + + void setDown(bool); + bool isDown() const; + + void setAutoRepeat(bool); + bool autoRepeat() const; + + void setAutoRepeatDelay(int); + int autoRepeatDelay() const; + + void setAutoRepeatInterval(int); + int autoRepeatInterval() const; + + void setAutoExclusive(bool); + bool autoExclusive() const; + +#ifndef QT_NO_BUTTONGROUP + QButtonGroup *group() const; +#endif + +public Q_SLOTS: + void setIconSize(const QSize &size); + void animateClick(int msec = 100); + void click(); + void toggle(); + void setChecked(bool); + +Q_SIGNALS: + void pressed(); + void released(); + void clicked(bool checked = false); + void toggled(bool checked); + +protected: + virtual void paintEvent(QPaintEvent *e) = 0; + virtual bool hitButton(const QPoint &pos) const; + virtual void checkStateSet(); + virtual void nextCheckState(); + + bool event(QEvent *e); + void keyPressEvent(QKeyEvent *e); + void keyReleaseEvent(QKeyEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void focusInEvent(QFocusEvent *e); + void focusOutEvent(QFocusEvent *e); + void changeEvent(QEvent *e); + void timerEvent(QTimerEvent *e); +#ifdef Q_OS_WINCE + void contextMenuEvent(QContextMenuEvent *e); +#endif + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QAbstractButton(QWidget *parent, const char *name, Qt::WindowFlags f=0); + inline QT3_SUPPORT bool isOn() const { return isChecked(); } + inline QT3_SUPPORT const QPixmap *pixmap() const { return 0; } // help styles compile + inline QT3_SUPPORT void setPixmap( const QPixmap &p ) { + setIcon(QIcon(p)); + setIconSize(p.size()); + } + QT3_SUPPORT QIcon *iconSet() const; + inline QT3_SUPPORT void setIconSet(const QIcon &icon) { setIcon(icon); } + inline QT3_SUPPORT bool isToggleButton() const { return isCheckable(); } + inline QT3_SUPPORT void setToggleButton(bool b) { setCheckable(b); } + inline QT3_SUPPORT void setAccel(const QKeySequence &key) { setShortcut(key); } + inline QT3_SUPPORT QKeySequence accel() const { return shortcut(); } + +public Q_SLOTS: + inline QT_MOC_COMPAT void setOn(bool b) { setChecked(b); } +#endif + +protected: + QAbstractButton(QAbstractButtonPrivate &dd, QWidget* parent = 0); + +private: + Q_DECLARE_PRIVATE(QAbstractButton) + Q_DISABLE_COPY(QAbstractButton) + friend class QButtonGroup; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTBUTTON_H diff --git a/src/gui/widgets/qabstractbutton_p.h b/src/gui/widgets/qabstractbutton_p.h new file mode 100644 index 0000000..d250952 --- /dev/null +++ b/src/gui/widgets/qabstractbutton_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTBUTTON_P_H +#define QABSTRACTBUTTON_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 "QtCore/qbasictimer.h" +#include "private/qwidget_p.h" + +QT_BEGIN_NAMESPACE + +class QAbstractButtonPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QAbstractButton) +public: + QAbstractButtonPrivate(QSizePolicy::ControlType type = QSizePolicy::DefaultType); + + QString text; + QIcon icon; + QSize iconSize; +#ifndef QT_NO_SHORTCUT + QKeySequence shortcut; + int shortcutId; +#endif + uint checkable :1; + uint checked :1; + uint autoRepeat :1; + uint autoExclusive :1; + uint down :1; + uint blockRefresh :1; + +#ifndef QT_NO_BUTTONGROUP + QButtonGroup* group; +#endif + QBasicTimer repeatTimer; + QBasicTimer animateTimer; + + int autoRepeatDelay, autoRepeatInterval; + + QSizePolicy::ControlType controlType; + mutable QSize sizeHint; + + void init(); + void click(); + void refresh(); + + QList<QAbstractButton *>queryButtonList() const; + QAbstractButton *queryCheckedButton() const; + void notifyChecked(); + void moveFocus(int key); + void fixFocusPolicy(); + + void emitPressed(); + void emitReleased(); + void emitClicked(); +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTBUTTON_P_H diff --git a/src/gui/widgets/qabstractscrollarea.cpp b/src/gui/widgets/qabstractscrollarea.cpp new file mode 100644 index 0000000..9886969 --- /dev/null +++ b/src/gui/widgets/qabstractscrollarea.cpp @@ -0,0 +1,1303 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractscrollarea.h" + +#ifndef QT_NO_SCROLLAREA + +#include "qscrollbar.h" +#include "qapplication.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qevent.h" +#include "qdebug.h" +#include "qboxlayout.h" +#include "qpainter.h" + +#include "qabstractscrollarea_p.h" +#include <qwidget.h> + +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractScrollArea + \brief The QAbstractScrollArea widget provides a scrolling area with + on-demand scroll bars. + + \ingroup abstractwidgets + + QAbstractScrollArea is a low-level abstraction of a scrolling + area. The area provides a central widget called the viewport, in + which the contents of the area is to be scrolled (i.e, the + visible parts of the contents are rendered in the viewport). + + Next to the viewport is a vertical scroll bar, and below is a + horizontal scroll bar. When all of the area contents fits in the + viewport, each scroll bar can be either visible or hidden + depending on the scroll bar's Qt::ScrollBarPolicy. When a scroll + bar is hidden, the viewport expands in order to cover all + available space. When a scroll bar becomes visible again, the + viewport shrinks in order to make room for the scroll bar. + + It is possible to reserve a margin area around the viewport, see + setViewportMargins(). The feature is mostly used to place a + QHeaderView widget above or beside the scrolling area. Subclasses + of QAbstractScrollArea should implement margins. + + When inheriting QAbstractScrollArea, you need to do the + following: + + \list + \o Control the scroll bars by setting their + range, value, page step, and tracking their + movements. + \o Draw the contents of the area in the viewport according + to the values of the scroll bars. + \o Handle events received by the viewport in + viewportEvent() - notably resize events. + \o Use \c{viewport->update()} to update the contents of the + viewport instead of \l{QWidget::update()}{update()} + as all painting operations take place on the viewport. + \endlist + + With a scroll bar policy of Qt::ScrollBarAsNeeded (the default), + QAbstractScrollArea shows scroll bars when they provide a non-zero + scrolling range, and hides them otherwise. + + The scroll bars and viewport should be updated whenever the viewport + receives a resize event or the size of the contents changes. + The viewport also needs to be updated when the scroll bars + values change. The initial values of the scroll bars are often + set when the area receives new contents. + + We give a simple example, in which we have implemented a scroll area + that can scroll any QWidget. We make the widget a child of the + viewport; this way, we do not have to calculate which part of + the widget to draw but can simply move the widget with + QWidget::move(). When the area contents or the viewport size + changes, we do the following: + + \snippet doc/src/snippets/myscrollarea.cpp 1 + + When the scroll bars change value, we need to update the widget + position, i.e., find the part of the widget that is to be drawn in + the viewport: + + \snippet doc/src/snippets/myscrollarea.cpp 0 + + In order to track scroll bar movements, reimplement the virtual + function scrollContentsBy(). In order to fine-tune scrolling + behavior, connect to a scroll bar's + QAbstractSlider::actionTriggered() signal and adjust the \l + QAbstractSlider::sliderPosition as you wish. + + For convenience, QAbstractScrollArea makes all viewport events + available in the virtual viewportEvent() handler. QWidget's + specialized handlers are remapped to viewport events in the cases + where this makes sense. The remapped specialized handlers are: + paintEvent(), mousePressEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), mouseMoveEvent(), wheelEvent(), + dragEnterEvent(), dragMoveEvent(), dragLeaveEvent(), dropEvent(), + contextMenuEvent(), and resizeEvent(). + + QScrollArea, which inherits QAbstractScrollArea, provides smooth + scrolling for any QWidget (i.e., the widget is scrolled pixel by + pixel). You only need to subclass QAbstractScrollArea if you need + more specialized behavior. This is, for instance, true if the + entire contents of the area is not suitable for being drawn on a + QWidget or if you do not want smooth scrolling. + + \sa QScrollArea +*/ + +QAbstractScrollAreaPrivate::QAbstractScrollAreaPrivate() + :hbar(0), vbar(0), vbarpolicy(Qt::ScrollBarAsNeeded), hbarpolicy(Qt::ScrollBarAsNeeded), + viewport(0), cornerWidget(0), left(0), top(0), right(0), bottom(0), + xoffset(0), yoffset(0), viewportFilter(0) +{ +} + +QAbstractScrollAreaScrollBarContainer::QAbstractScrollAreaScrollBarContainer(Qt::Orientation orientation, QWidget *parent) + :QWidget(parent), scrollBar(new QScrollBar(orientation, this)), + layout(new QBoxLayout(orientation == Qt::Horizontal ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom)), + orientation(orientation) +{ + setLayout(layout); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(scrollBar); +} + +/*! \internal + Adds a widget to the scroll bar container. +*/ +void QAbstractScrollAreaScrollBarContainer::addWidget(QWidget *widget, LogicalPosition position) +{ + QSizePolicy policy = widget->sizePolicy(); + if (orientation == Qt::Vertical) + policy.setHorizontalPolicy(QSizePolicy::Ignored); + else + policy.setVerticalPolicy(QSizePolicy::Ignored); + widget->setSizePolicy(policy); + widget->setParent(this); + + const int insertIndex = (position & LogicalLeft) ? 0 : scrollBarLayoutIndex() + 1; + layout->insertWidget(insertIndex, widget); +} + +/*! \internal + Retuns a list of scroll bar widgets for the given position. The scroll bar + itself is not returned. +*/ +QWidgetList QAbstractScrollAreaScrollBarContainer::widgets(LogicalPosition position) +{ + QWidgetList list; + const int scrollBarIndex = scrollBarLayoutIndex(); + if (position == LogicalLeft) { + for (int i = 0; i < scrollBarIndex; ++i) + list.append(layout->itemAt(i)->widget()); + } else if (position == LogicalRight) { + const int layoutItemCount = layout->count(); + for (int i = scrollBarIndex + 1; i < layoutItemCount; ++i) + list.append(layout->itemAt(i)->widget()); + } + return list; +} + +/*! \internal + Returns the layout index for the scroll bar. This needs to be + recalculated by a linear search for each use, since items in + the layout can be removed at any time (i.e. when a widget is + deleted or re-parented). +*/ +int QAbstractScrollAreaScrollBarContainer::scrollBarLayoutIndex() const +{ + const int layoutItemCount = layout->count(); + for (int i = 0; i < layoutItemCount; ++i) { + if (qobject_cast<QScrollBar *>(layout->itemAt(i)->widget())) + return i; + } + return -1; +} + +/*! \internal +*/ +void QAbstractScrollAreaPrivate::replaceScrollBar(QScrollBar *scrollBar, + Qt::Orientation orientation) +{ + Q_Q(QAbstractScrollArea); + + QAbstractScrollAreaScrollBarContainer *container = scrollBarContainers[orientation]; + bool horizontal = (orientation == Qt::Horizontal); + QScrollBar *oldBar = horizontal ? hbar : vbar; + if (horizontal) + hbar = scrollBar; + else + vbar = scrollBar; + scrollBar->setParent(container); + container->scrollBar = scrollBar; + container->layout->removeWidget(oldBar); + container->layout->insertWidget(0, scrollBar); + scrollBar->setVisible(oldBar->isVisibleTo(container)); + scrollBar->setInvertedAppearance(oldBar->invertedAppearance()); + scrollBar->setInvertedControls(oldBar->invertedControls()); + scrollBar->setRange(oldBar->minimum(), oldBar->maximum()); + scrollBar->setOrientation(oldBar->orientation()); + scrollBar->setPageStep(oldBar->pageStep()); + scrollBar->setSingleStep(oldBar->singleStep()); + scrollBar->setSliderDown(oldBar->isSliderDown()); + scrollBar->setSliderPosition(oldBar->sliderPosition()); + scrollBar->setTracking(oldBar->hasTracking()); + scrollBar->setValue(oldBar->value()); + delete oldBar; + + QObject::connect(scrollBar, SIGNAL(valueChanged(int)), + q, horizontal ? SLOT(_q_hslide(int)) : SLOT(_q_vslide(int))); + QObject::connect(scrollBar, SIGNAL(rangeChanged(int,int)), + q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection); +} + +void QAbstractScrollAreaPrivate::init() +{ + Q_Q(QAbstractScrollArea); + viewport = new QWidget(q); + viewport->setObjectName(QLatin1String("qt_scrollarea_viewport")); + viewport->setBackgroundRole(QPalette::Base); + viewport->setAutoFillBackground(true); + scrollBarContainers[Qt::Horizontal] = new QAbstractScrollAreaScrollBarContainer(Qt::Horizontal, q); + scrollBarContainers[Qt::Horizontal]->setObjectName(QLatin1String("qt_scrollarea_hcontainer")); + hbar = scrollBarContainers[Qt::Horizontal]->scrollBar; + hbar->setRange(0,0); + scrollBarContainers[Qt::Horizontal]->setVisible(false); + QObject::connect(hbar, SIGNAL(valueChanged(int)), q, SLOT(_q_hslide(int))); + QObject::connect(hbar, SIGNAL(rangeChanged(int,int)), q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection); + scrollBarContainers[Qt::Vertical] = new QAbstractScrollAreaScrollBarContainer(Qt::Vertical, q); + scrollBarContainers[Qt::Vertical]->setObjectName(QLatin1String("qt_scrollarea_vcontainer")); + vbar = scrollBarContainers[Qt::Vertical]->scrollBar; + vbar->setRange(0,0); + scrollBarContainers[Qt::Vertical]->setVisible(false); + QObject::connect(vbar, SIGNAL(valueChanged(int)), q, SLOT(_q_vslide(int))); + QObject::connect(vbar, SIGNAL(rangeChanged(int,int)), q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection); + viewportFilter = new QAbstractScrollAreaFilter(this); + viewport->installEventFilter(viewportFilter); + viewport->setFocusProxy(q); + q->setFocusPolicy(Qt::WheelFocus); + q->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layoutChildren(); +} + +void QAbstractScrollAreaPrivate::layoutChildren() +{ + Q_Q(QAbstractScrollArea); + bool needh = (hbarpolicy == Qt::ScrollBarAlwaysOn + || (hbarpolicy == Qt::ScrollBarAsNeeded && hbar->minimum() < hbar->maximum())); + + bool needv = (vbarpolicy == Qt::ScrollBarAlwaysOn + || (vbarpolicy == Qt::ScrollBarAsNeeded && vbar->minimum() < vbar->maximum())); + +#ifdef Q_WS_MAC + QWidget * const window = q->window(); + + // Use small scroll bars for tool windows, to match the native size grip. + bool hbarIsSmall = hbar->testAttribute(Qt::WA_MacSmallSize); + bool vbarIsSmall = vbar->testAttribute(Qt::WA_MacSmallSize); + const Qt::WindowType windowType = window->windowType(); + if (windowType == Qt::Tool) { + if (!hbarIsSmall) { + hbar->setAttribute(Qt::WA_MacMiniSize, false); + hbar->setAttribute(Qt::WA_MacNormalSize, false); + hbar->setAttribute(Qt::WA_MacSmallSize, true); + } + if (!vbarIsSmall) { + vbar->setAttribute(Qt::WA_MacMiniSize, false); + vbar->setAttribute(Qt::WA_MacNormalSize, false); + vbar->setAttribute(Qt::WA_MacSmallSize, true); + } + } else { + if (hbarIsSmall) { + hbar->setAttribute(Qt::WA_MacMiniSize, false); + hbar->setAttribute(Qt::WA_MacNormalSize, false); + hbar->setAttribute(Qt::WA_MacSmallSize, false); + } + if (vbarIsSmall) { + vbar->setAttribute(Qt::WA_MacMiniSize, false); + vbar->setAttribute(Qt::WA_MacNormalSize, false); + vbar->setAttribute(Qt::WA_MacSmallSize, false); + } + } +#endif + + const int hsbExt = hbar->sizeHint().height(); + const int vsbExt = vbar->sizeHint().width(); + const QPoint extPoint(vsbExt, hsbExt); + const QSize extSize(vsbExt, hsbExt); + + const QRect widgetRect = q->rect(); + QStyleOption opt(0); + opt.init(q); + + const bool hasCornerWidget = (cornerWidget != 0); + +// If the scroll bars are at the very right and bottom of the window we +// move their positions to be aligned with the size grip. +#ifdef Q_WS_MAC + // Check if a native sizegrip is present. + bool hasMacReverseSizeGrip = false; + bool hasMacSizeGrip = false; + bool nativeGripPresent = false; + if (q->testAttribute(Qt::WA_WState_Created)) + nativeGripPresent = qt_mac_checkForNativeSizeGrip(q); + + if (nativeGripPresent) { + // Look for a native size grip at the visual window bottom right and at the + // absolute window bottom right. In reverse mode, the native size grip does not + // swich side, so we need to check if it is on the "wrong side". + const QPoint scrollAreaBottomRight = q->mapTo(window, widgetRect.bottomRight() - QPoint(frameWidth, frameWidth)); + const QPoint windowBottomRight = window->rect().bottomRight(); + const QPoint visualWindowBottomRight = QStyle::visualPos(opt.direction, opt.rect, windowBottomRight); + const QPoint offset = windowBottomRight - scrollAreaBottomRight; + const QPoint visualOffset = visualWindowBottomRight - scrollAreaBottomRight; + hasMacSizeGrip = (visualOffset.manhattanLength() < vsbExt); + hasMacReverseSizeGrip = (hasMacSizeGrip == false && (offset.manhattanLength() < hsbExt)); + } +#endif + + QPoint cornerOffset(needv ? vsbExt : 0, needh ? hsbExt : 0); + QRect controlsRect; + QRect viewportRect; + + // In FrameOnlyAroundContents mode the frame is drawn between the controls and + // the viewport, else the frame rect is equal to the widget rect. + if ((frameStyle != QFrame::NoFrame) && + q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, q)) { + controlsRect = widgetRect; + const int extra = q->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing); + const QPoint cornerExtra(needv ? extra : 0, needh ? extra : 0); + QRect frameRect = widgetRect; + frameRect.adjust(0, 0, -cornerOffset.x() - cornerExtra.x(), -cornerOffset.y() - cornerExtra.y()); + q->setFrameRect(QStyle::visualRect(opt.direction, opt.rect, frameRect)); + // The frame rect needs to be in logical coords, however we need to flip + // the contentsRect back before passing it on to the viewportRect + // since the viewportRect has its logical coords calculated later. + viewportRect = QStyle::visualRect(opt.direction, opt.rect, q->contentsRect()); + } else { + q->setFrameRect(QStyle::visualRect(opt.direction, opt.rect, widgetRect)); + controlsRect = q->contentsRect(); + viewportRect = QRect(controlsRect.topLeft(), controlsRect.bottomRight() - cornerOffset); + } + + // If we have a corner widget and are only showing one scroll bar, we need to move it + // to make room for the corner widget. + if (hasCornerWidget && (needv || needh)) + cornerOffset = extPoint; + +#ifdef Q_WS_MAC + // Also move the scroll bars if they are covered by the native Mac size grip. + if (hasMacSizeGrip) + cornerOffset = extPoint; +#endif + + // The corner point is where the scroll bar rects, the corner widget rect and the + // viewport rect meets. + const QPoint cornerPoint(controlsRect.bottomRight() + QPoint(1, 1) - cornerOffset); + + // Some styles paints the corner if both scorllbars are showing and there is + // no corner widget. Also, on the Mac we paint if there is a native + // (transparent) sizegrip in the area where a corner widget would be. + if ((needv && needh && hasCornerWidget == false) + || ((needv || needh) +#ifdef Q_WS_MAC + && hasMacSizeGrip +#endif + ) + ) { + cornerPaintingRect = QStyle::visualRect(opt.direction, opt.rect, QRect(cornerPoint, extSize)); + } else { + cornerPaintingRect = QRect(); + } + +#ifdef Q_WS_MAC + if (hasMacReverseSizeGrip) + reverseCornerPaintingRect = QRect(controlsRect.bottomRight() + QPoint(1, 1) - extPoint, extSize); + else + reverseCornerPaintingRect = QRect(); +#endif + + if (needh) { + QRect horizontalScrollBarRect(QPoint(controlsRect.left(), cornerPoint.y()), QPoint(cornerPoint.x() - 1, controlsRect.bottom())); +#ifdef Q_WS_MAC + if (hasMacReverseSizeGrip) + horizontalScrollBarRect.adjust(vsbExt, 0, 0, 0); +#endif + scrollBarContainers[Qt::Horizontal]->setGeometry(QStyle::visualRect(opt.direction, opt.rect, horizontalScrollBarRect)); + scrollBarContainers[Qt::Horizontal]->raise(); + } + + if (needv) { + const QRect verticalScrollBarRect (QPoint(cornerPoint.x(), controlsRect.top()), QPoint(controlsRect.right(), cornerPoint.y() - 1)); + scrollBarContainers[Qt::Vertical]->setGeometry(QStyle::visualRect(opt.direction, opt.rect, verticalScrollBarRect)); + scrollBarContainers[Qt::Vertical]->raise(); + } + + if (cornerWidget) { + const QRect cornerWidgetRect(cornerPoint, controlsRect.bottomRight()); + cornerWidget->setGeometry(QStyle::visualRect(opt.direction, opt.rect, cornerWidgetRect)); + } + + scrollBarContainers[Qt::Horizontal]->setVisible(needh); + scrollBarContainers[Qt::Vertical]->setVisible(needv); + + if (q->isRightToLeft()) + viewportRect.adjust(right, top, -left, -bottom); + else + viewportRect.adjust(left, top, -right, -bottom); + + viewport->setGeometry(QStyle::visualRect(opt.direction, opt.rect, viewportRect)); // resize the viewport last +} + +// ### Fix for 4.4, talk to Bjoern E or Girish. +void QAbstractScrollAreaPrivate::scrollBarPolicyChanged(Qt::Orientation, Qt::ScrollBarPolicy) {} + +/*! + \internal + + Creates a new QAbstractScrollAreaPrivate, \a dd with the given \a parent. +*/ +QAbstractScrollArea::QAbstractScrollArea(QAbstractScrollAreaPrivate &dd, QWidget *parent) + :QFrame(dd, parent) +{ + Q_D(QAbstractScrollArea); + d->init(); +} + +/*! + Constructs a viewport. + + The \a parent arguments is sent to the QWidget constructor. +*/ +QAbstractScrollArea::QAbstractScrollArea(QWidget *parent) + :QFrame(*new QAbstractScrollAreaPrivate, parent) +{ + Q_D(QAbstractScrollArea); + d->init(); +} + + +/*! + Destroys the viewport. + */ +QAbstractScrollArea::~QAbstractScrollArea() +{ + Q_D(QAbstractScrollArea); + delete d->viewportFilter; +} + + +/*! + \since 4.2 + Sets the viewport to be the given \a widget. + The QAbstractScrollArea will take ownership of the given \a widget. + + If \a widget is 0, QAbstractScrollArea will assign a new QWidget instance + for the viewport. + + \sa viewport() +*/ +void QAbstractScrollArea::setViewport(QWidget *widget) +{ + Q_D(QAbstractScrollArea); + if (widget != d->viewport) { + QWidget *oldViewport = d->viewport; + if (!widget) + widget = new QWidget; + d->viewport = widget; + d->viewport->setParent(this); + d->viewport->setFocusProxy(this); + d->viewport->installEventFilter(d->viewportFilter); + d->layoutChildren(); + if (isVisible()) + d->viewport->show(); + QMetaObject::invokeMethod(this, "setupViewport", Q_ARG(QWidget *, widget)); + delete oldViewport; + } +} + +/*! + Returns the viewport widget. + + Use the QScrollArea::widget() function to retrieve the contents of + the viewport widget. + + \sa QScrollArea::widget() +*/ +QWidget *QAbstractScrollArea::viewport() const +{ + Q_D(const QAbstractScrollArea); + return d->viewport; +} + + +/*! +Returns the size of the viewport as if the scroll bars had no valid +scrolling range. +*/ +// ### still thinking about the name +QSize QAbstractScrollArea::maximumViewportSize() const +{ + Q_D(const QAbstractScrollArea); + int hsbExt = d->hbar->sizeHint().height(); + int vsbExt = d->vbar->sizeHint().width(); + + int f = 2 * d->frameWidth; + QSize max = size() - QSize(f + d->left + d->right, f + d->top + d->bottom); + if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) + max.rwidth() -= vsbExt; + if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) + max.rheight() -= hsbExt; + return max; +} + +/*! + \property QAbstractScrollArea::verticalScrollBarPolicy + \brief the policy for the vertical scroll bar + + The default policy is Qt::ScrollBarAsNeeded. + + \sa horizontalScrollBarPolicy +*/ + +Qt::ScrollBarPolicy QAbstractScrollArea::verticalScrollBarPolicy() const +{ + Q_D(const QAbstractScrollArea); + return d->vbarpolicy; +} + +void QAbstractScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) +{ + Q_D(QAbstractScrollArea); + const Qt::ScrollBarPolicy oldPolicy = d->vbarpolicy; + d->vbarpolicy = policy; + if (isVisible()) + d->layoutChildren(); + if (oldPolicy != d->vbarpolicy) + d->scrollBarPolicyChanged(Qt::Vertical, d->vbarpolicy); +} + + +/*! + Returns the vertical scroll bar. + + \sa verticalScrollBarPolicy, horizontalScrollBar() + */ +QScrollBar *QAbstractScrollArea::verticalScrollBar() const +{ + Q_D(const QAbstractScrollArea); + return d->vbar; +} + +/*! + \since 4.2 + Replaces the existing vertical scroll bar with \a scrollBar, and sets all + the former scroll bar's slider properties on the new scroll bar. The former + scroll bar is then deleted. + + QAbstractScrollArea already provides vertical and horizontal scroll bars by + default. You can call this function to replace the default vertical + scroll bar with your own custom scroll bar. + + \sa verticalScrollBar(), setHorizontalScrollBar() +*/ +void QAbstractScrollArea::setVerticalScrollBar(QScrollBar *scrollBar) +{ + Q_D(QAbstractScrollArea); + if (!scrollBar) { + qWarning("QAbstractScrollArea::setVerticalScrollBar: Cannot set a null scroll bar"); + return; + } + + d->replaceScrollBar(scrollBar, Qt::Vertical); +} + +/*! + \property QAbstractScrollArea::horizontalScrollBarPolicy + \brief the policy for the horizontal scroll bar + + The default policy is Qt::ScrollBarAsNeeded. + + \sa verticalScrollBarPolicy +*/ + +Qt::ScrollBarPolicy QAbstractScrollArea::horizontalScrollBarPolicy() const +{ + Q_D(const QAbstractScrollArea); + return d->hbarpolicy; +} + +void QAbstractScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) +{ + Q_D(QAbstractScrollArea); + const Qt::ScrollBarPolicy oldPolicy = d->hbarpolicy; + d->hbarpolicy = policy; + if (isVisible()) + d->layoutChildren(); + if (oldPolicy != d->hbarpolicy) + d->scrollBarPolicyChanged(Qt::Horizontal, d->hbarpolicy); +} + +/*! + Returns the horizontal scroll bar. + + \sa horizontalScrollBarPolicy, verticalScrollBar() + */ +QScrollBar *QAbstractScrollArea::horizontalScrollBar() const +{ + Q_D(const QAbstractScrollArea); + return d->hbar; +} + +/*! + \since 4.2 + + Replaces the existing horizontal scroll bar with \a scrollBar, and sets all + the former scroll bar's slider properties on the new scroll bar. The former + scroll bar is then deleted. + + QAbstractScrollArea already provides horizontal and vertical scroll bars by + default. You can call this function to replace the default horizontal + scroll bar with your own custom scroll bar. + + \sa horizontalScrollBar(), setVerticalScrollBar() +*/ +void QAbstractScrollArea::setHorizontalScrollBar(QScrollBar *scrollBar) +{ + Q_D(QAbstractScrollArea); + if (!scrollBar) { + qWarning("QAbstractScrollArea::setHorizontalScrollBar: Cannot set a null scroll bar"); + return; + } + + d->replaceScrollBar(scrollBar, Qt::Horizontal); +} + +/*! + \since 4.2 + + Returns the widget in the corner between the two scroll bars. + + By default, no corner widget is present. +*/ +QWidget *QAbstractScrollArea::cornerWidget() const +{ + Q_D(const QAbstractScrollArea); + return d->cornerWidget; +} + +/*! + \since 4.2 + + Sets the widget in the corner between the two scroll bars to be + \a widget. + + You will probably also want to set at least one of the scroll bar + modes to \c AlwaysOn. + + Passing 0 shows no widget in the corner. + + Any previous corner widget is hidden. + + You may call setCornerWidget() with the same widget at different + times. + + All widgets set here will be deleted by the scroll area when it is + destroyed unless you separately reparent the widget after setting + some other corner widget (or 0). + + Any \e newly set widget should have no current parent. + + By default, no corner widget is present. + + \sa horizontalScrollBarPolicy, horizontalScrollBarPolicy +*/ +void QAbstractScrollArea::setCornerWidget(QWidget *widget) +{ + Q_D(QAbstractScrollArea); + QWidget* oldWidget = d->cornerWidget; + if (oldWidget != widget) { + if (oldWidget) + oldWidget->hide(); + d->cornerWidget = widget; + + if (widget && widget->parentWidget() != this) + widget->setParent(this); + + d->layoutChildren(); + if (widget) + widget->show(); + } else { + d->cornerWidget = widget; + d->layoutChildren(); + } +} + +/*! + \since 4.2 + Adds \a widget as a scroll bar widget in the location specified + by \a alignment. + + Scroll bar widgets are shown next to the horizontal or vertical + scroll bar, and can be placed on either side of it. If you want + the scroll bar widgets to be always visible, set the + scrollBarPolicy for the corresponding scroll bar to \c AlwaysOn. + + \a alignment must be one of Qt::Alignleft and Qt::AlignRight, + which maps to the horizontal scroll bar, or Qt::AlignTop and + Qt::AlignBottom, which maps to the vertical scroll bar. + + A scroll bar widget can be removed by either re-parenting the + widget or deleting it. It's also possible to hide a widget with + QWidget::hide() + + The scroll bar widget will be resized to fit the scroll bar + geometry for the current style. The following describes the case + for scroll bar widgets on the horizontal scroll bar: + + The height of the widget will be set to match the height of the + scroll bar. To control the width of the widget, use + QWidget::setMinimumWidth and QWidget::setMaximumWidth, or + implement QWidget::sizeHint() and set a horizontal size policy. + If you want a square widget, call + QStyle::pixelMetric(QStyle::PM_ScrollBarExtent) and set the + width to this value. + + \sa scrollBarWidgets() +*/ +void QAbstractScrollArea::addScrollBarWidget(QWidget *widget, Qt::Alignment alignment) +{ + Q_D(QAbstractScrollArea); + + if (widget == 0) + return; + + const Qt::Orientation scrollBarOrientation + = ((alignment & Qt::AlignLeft) || (alignment & Qt::AlignRight)) ? Qt::Horizontal : Qt::Vertical; + const QAbstractScrollAreaScrollBarContainer::LogicalPosition position + = ((alignment & Qt::AlignRight) || (alignment & Qt::AlignBottom)) + ? QAbstractScrollAreaScrollBarContainer::LogicalRight : QAbstractScrollAreaScrollBarContainer::LogicalLeft; + d->scrollBarContainers[scrollBarOrientation]->addWidget(widget, position); + d->layoutChildren(); + if (isHidden() == false) + widget->show(); +} + +/*! + \since 4.2 + Returns a list of the currently set scroll bar widgets. \a alignment + can be any combination of the four location flags. + + \sa addScrollBarWidget() +*/ +QWidgetList QAbstractScrollArea::scrollBarWidgets(Qt::Alignment alignment) +{ + Q_D(QAbstractScrollArea); + + QWidgetList list; + + if (alignment & Qt::AlignLeft) + list += d->scrollBarContainers[Qt::Horizontal]->widgets(QAbstractScrollAreaScrollBarContainer::LogicalLeft); + if (alignment & Qt::AlignRight) + list += d->scrollBarContainers[Qt::Horizontal]->widgets(QAbstractScrollAreaScrollBarContainer::LogicalRight); + if (alignment & Qt::AlignTop) + list += d->scrollBarContainers[Qt::Vertical]->widgets(QAbstractScrollAreaScrollBarContainer::LogicalLeft); + if (alignment & Qt::AlignBottom) + list += d->scrollBarContainers[Qt::Vertical]->widgets(QAbstractScrollAreaScrollBarContainer::LogicalRight); + + return list; +} + +/*! + Sets the margins around the scrolling area to \a left, \a top, \a + right and \a bottom. This is useful for applications such as + spreadsheets with "locked" rows and columns. The marginal space is + is left blank; put widgets in the unused area. + + Note that this function is frequently called by QTreeView and + QTableView, so margins must be implemented by QAbstractScrollArea + subclasses. Also, if the subclasses are to be used in item views, + they should not call this function. + + By default all margins are zero. + +*/ +void QAbstractScrollArea::setViewportMargins(int left, int top, int right, int bottom) +{ + Q_D(QAbstractScrollArea); + d->left = left; + d->top = top; + d->right = right; + d->bottom = bottom; + d->layoutChildren(); +} + +/*! + \fn bool QAbstractScrollArea::event(QEvent *event) + + \reimp + + This is the main event handler for the QAbstractScrollArea widget (\e not + the scrolling area viewport()). The specified \a event is a general event + object that may need to be cast to the appropriate class depending on its + type. + + \sa QEvent::type() +*/ +bool QAbstractScrollArea::event(QEvent *e) +{ + Q_D(QAbstractScrollArea); + switch (e->type()) { + case QEvent::AcceptDropsChange: + // There was a chance that with accessibility client we get an + // event before the viewport was created. + // Also, in some cases we might get here from QWidget::event() virtual function which is (indirectly) called + // from the viewport constructor at the time when the d->viewport is not yet initialized even without any + // accessibility client. See qabstractscrollarea autotest for a test case. + if (d->viewport) + d->viewport->setAcceptDrops(acceptDrops()); + break; + case QEvent::MouseTrackingChange: + d->viewport->setMouseTracking(hasMouseTracking()); + break; + case QEvent::Resize: + d->layoutChildren(); + break; + case QEvent::Paint: + if (d->cornerPaintingRect.isValid()) { + QStyleOption option; + option.rect = d->cornerPaintingRect; + QPainter p(this); + style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &option, &p, this); + } +#ifdef Q_WS_MAC + if (d->reverseCornerPaintingRect.isValid()) { + QStyleOption option; + option.rect = d->reverseCornerPaintingRect; + QPainter p(this); + style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &option, &p, this); + } +#endif + QFrame::paintEvent((QPaintEvent*)e); + break; +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: + if (static_cast<QContextMenuEvent *>(e)->reason() == QContextMenuEvent::Keyboard) + return QFrame::event(e); + e->ignore(); + break; +#endif // QT_NO_CONTEXTMENU + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::Wheel: +#ifndef QT_NO_DRAGANDDROP + case QEvent::Drop: + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::DragLeave: +#endif + return false; + case QEvent::StyleChange: + case QEvent::LayoutDirectionChange: + case QEvent::ApplicationLayoutDirectionChange: + d->layoutChildren(); + // fall through + default: + return QFrame::event(e); + } + return true; +} + +/*! + \fn bool QAbstractScrollArea::viewportEvent(QEvent *event) + + The main event handler for the scrolling area (the viewport() widget). + It handles the \a event specified, and can be called by subclasses to + provide reasonable default behavior. + + Returns true to indicate to the event system that the event has been + handled, and needs no further processing; otherwise returns false to + indicate that the event should be propagated further. + + You can reimplement this function in a subclass, but we recommend + using one of the specialized event handlers instead. + + Specialised handlers for viewport events are: paintEvent(), + mousePressEvent(), mouseReleaseEvent(), mouseDoubleClickEvent(), + mouseMoveEvent(), wheelEvent(), dragEnterEvent(), dragMoveEvent(), + dragLeaveEvent(), dropEvent(), contextMenuEvent(), and + resizeEvent(). +*/ +bool QAbstractScrollArea::viewportEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::Resize: + case QEvent::Paint: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::ContextMenu: +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: +#endif +#ifndef QT_NO_DRAGANDDROP + case QEvent::Drop: + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::DragLeave: +#endif + return QFrame::event(e); + case QEvent::LayoutRequest: + return event(e); + default: + break; + } + return false; // let the viewport widget handle the event +} + +/*! + \fn void QAbstractScrollArea::resizeEvent(QResizeEvent *event) + + This event handler can be reimplemented in a subclass to receive + resize events (passed in \a event), for the viewport() widget. + + When resizeEvent() is called, the viewport already has its new + geometry: Its new size is accessible through the + QResizeEvent::size() function, and the old size through + QResizeEvent::oldSize(). + + \sa QWidget::resizeEvent() + */ +void QAbstractScrollArea::resizeEvent(QResizeEvent *) +{ +} + +/*! + \fn void QAbstractScrollArea::paintEvent(QPaintEvent *event) + + This event handler can be reimplemented in a subclass to receive + paint events (passed in \a event), for the viewport() widget. + + \note If you open a painter, make sure to open it on the viewport(). + + \sa QWidget::paintEvent() +*/ +void QAbstractScrollArea::paintEvent(QPaintEvent*) +{ +} + +/*! + This event handler can be reimplemented in a subclass to receive + mouse press events for the viewport() widget. The event is passed + in \a e. + + \sa QWidget::mousePressEvent() +*/ +void QAbstractScrollArea::mousePressEvent(QMouseEvent *e) +{ + e->ignore(); +} + +/*! + This event handler can be reimplemented in a subclass to receive + mouse release events for the viewport() widget. The event is + passed in \a e. + + \sa QWidget::mouseReleaseEvent() +*/ +void QAbstractScrollArea::mouseReleaseEvent(QMouseEvent *e) +{ + e->ignore(); +} + +/*! + This event handler can be reimplemented in a subclass to receive + mouse double click events for the viewport() widget. The event is + passed in \a e. + + \sa QWidget::mouseDoubleClickEvent() +*/ +void QAbstractScrollArea::mouseDoubleClickEvent(QMouseEvent *e) +{ + e->ignore(); +} + +/*! + This event handler can be reimplemented in a subclass to receive + mouse move events for the viewport() widget. The event is passed + in \a e. + + \sa QWidget::mouseMoveEvent() +*/ +void QAbstractScrollArea::mouseMoveEvent(QMouseEvent *e) +{ + e->ignore(); +} + +/*! + This event handler can be reimplemented in a subclass to receive + wheel events for the viewport() widget. The event is passed in \a + e. + + \sa QWidget::wheelEvent() +*/ +#ifndef QT_NO_WHEELEVENT +void QAbstractScrollArea::wheelEvent(QWheelEvent *e) +{ + Q_D(QAbstractScrollArea); + if (static_cast<QWheelEvent*>(e)->orientation() == Qt::Horizontal) + QApplication::sendEvent(d->hbar, e); + else + QApplication::sendEvent(d->vbar, e); +} +#endif + +#ifndef QT_NO_CONTEXTMENU +/*! + This event handler can be reimplemented in a subclass to receive + context menu events for the viewport() widget. The event is passed + in \a e. + + \sa QWidget::contextMenuEvent() +*/ +void QAbstractScrollArea::contextMenuEvent(QContextMenuEvent *e) +{ + e->ignore(); +} +#endif // QT_NO_CONTEXTMENU + +/*! + This function is called with key event \a e when key presses + occur. It handles PageUp, PageDown, Up, Down, Left, and Right, and + ignores all other key presses. +*/ +void QAbstractScrollArea::keyPressEvent(QKeyEvent * e) +{ + Q_D(QAbstractScrollArea); + if (false){ +#ifndef QT_NO_SHORTCUT + } else if (e == QKeySequence::MoveToPreviousPage) { + d->vbar->triggerAction(QScrollBar::SliderPageStepSub); + } else if (e == QKeySequence::MoveToNextPage) { + d->vbar->triggerAction(QScrollBar::SliderPageStepAdd); +#endif + } else { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + e->ignore(); + return; + } +#endif + switch (e->key()) { + case Qt::Key_Up: + d->vbar->triggerAction(QScrollBar::SliderSingleStepSub); + break; + case Qt::Key_Down: + d->vbar->triggerAction(QScrollBar::SliderSingleStepAdd); + break; + case Qt::Key_Left: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && hasEditFocus() + && (!d->hbar->isVisible() || d->hbar->value() == d->hbar->minimum())) { + //if we aren't using the hbar or we are already at the leftmost point ignore + e->ignore(); + return; + } +#endif + d->hbar->triggerAction( + layoutDirection() == Qt::LeftToRight + ? QScrollBar::SliderSingleStepSub : QScrollBar::SliderSingleStepAdd); + break; + case Qt::Key_Right: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && hasEditFocus() + && (!d->hbar->isVisible() || d->hbar->value() == d->hbar->maximum())) { + //if we aren't using the hbar or we are already at the rightmost point ignore + e->ignore(); + return; + } +#endif + d->hbar->triggerAction( + layoutDirection() == Qt::LeftToRight + ? QScrollBar::SliderSingleStepAdd : QScrollBar::SliderSingleStepSub); + break; + default: + e->ignore(); + return; + } + } + e->accept(); +} + + +#ifndef QT_NO_DRAGANDDROP +/*! + \fn void QAbstractScrollArea::dragEnterEvent(QDragEnterEvent *event) + + This event handler can be reimplemented in a subclass to receive + drag enter events (passed in \a event), for the viewport() widget. + + \sa QWidget::dragEnterEvent() +*/ +void QAbstractScrollArea::dragEnterEvent(QDragEnterEvent *) +{ +} + +/*! + \fn void QAbstractScrollArea::dragMoveEvent(QDragMoveEvent *event) + + This event handler can be reimplemented in a subclass to receive + drag move events (passed in \a event), for the viewport() widget. + + \sa QWidget::dragMoveEvent() +*/ +void QAbstractScrollArea::dragMoveEvent(QDragMoveEvent *) +{ +} + +/*! + \fn void QAbstractScrollArea::dragLeaveEvent(QDragLeaveEvent *event) + + This event handler can be reimplemented in a subclass to receive + drag leave events (passed in \a event), for the viewport() widget. + + \sa QWidget::dragLeaveEvent() +*/ +void QAbstractScrollArea::dragLeaveEvent(QDragLeaveEvent *) +{ +} + +/*! + \fn void QAbstractScrollArea::dropEvent(QDropEvent *event) + + This event handler can be reimplemented in a subclass to receive + drop events (passed in \a event), for the viewport() widget. + + \sa QWidget::dropEvent() +*/ +void QAbstractScrollArea::dropEvent(QDropEvent *) +{ +} + + +#endif + +/*! + This virtual handler is called when the scroll bars are moved by + \a dx, \a dy, and consequently the viewport's contents should be + scrolled accordingly. + + The default implementation simply calls update() on the entire + viewport(), subclasses can reimplement this handler for + optimization purposes, or - like QScrollArea - to move a contents + widget. The parameters \a dx and \a dy are there for convenience, + so that the class knows how much should be scrolled (useful + e.g. when doing pixel-shifts). You may just as well ignore these + values and scroll directly to the position the scroll bars + indicate. + + Calling this function in order to scroll programmatically is an + error, use the scroll bars instead (e.g. by calling + QScrollBar::setValue() directly). +*/ +void QAbstractScrollArea::scrollContentsBy(int, int) +{ + viewport()->update(); +} + +void QAbstractScrollAreaPrivate::_q_hslide(int x) +{ + Q_Q(QAbstractScrollArea); + int dx = xoffset - x; + xoffset = x; + q->scrollContentsBy(dx, 0); +} + +void QAbstractScrollAreaPrivate::_q_vslide(int y) +{ + Q_Q(QAbstractScrollArea); + int dy = yoffset - y; + yoffset = y; + q->scrollContentsBy(0, dy); +} + +void QAbstractScrollAreaPrivate::_q_showOrHideScrollBars() +{ + layoutChildren(); +} + +QPoint QAbstractScrollAreaPrivate::contentsOffset() const +{ + Q_Q(const QAbstractScrollArea); + QPoint offset; + if (vbar->isVisible()) + offset.setY(vbar->value()); + if (hbar->isVisible()) { + if (q->isRightToLeft()) + offset.setX(hbar->maximum() - hbar->value()); + else + offset.setX(hbar->value()); + } + return offset; +} + +/*! + \reimp + +*/ +QSize QAbstractScrollArea::minimumSizeHint() const +{ + Q_D(const QAbstractScrollArea); + int hsbExt = d->hbar->sizeHint().height(); + int vsbExt = d->vbar->sizeHint().width(); + int extra = 2 * d->frameWidth; + return QSize(d->scrollBarContainers[Qt::Horizontal]->sizeHint().width() + vsbExt + extra, + d->scrollBarContainers[Qt::Vertical]->sizeHint().height() + hsbExt + extra); +} + +/*! + \reimp +*/ +QSize QAbstractScrollArea::sizeHint() const +{ + return QSize(256, 192); +#if 0 + Q_D(const QAbstractScrollArea); + int h = qMax(10, fontMetrics().height()); + int f = 2 * d->frameWidth; + return QSize((6 * h) + f, (4 * h) + f); +#endif +} + +/*! + This slot is called by QAbstractScrollArea after setViewport(\a + viewport) has been called. Reimplement this function in a + subclass of QAbstractScrollArea to initialize the new \a viewport + before it is used. + + \sa setViewport() +*/ +void QAbstractScrollArea::setupViewport(QWidget *viewport) +{ + Q_UNUSED(viewport); +} + +QT_END_NAMESPACE + +#include "moc_qabstractscrollarea.cpp" +#include "moc_qabstractscrollarea_p.cpp" + +#endif // QT_NO_SCROLLAREA diff --git a/src/gui/widgets/qabstractscrollarea.h b/src/gui/widgets/qabstractscrollarea.h new file mode 100644 index 0000000..eac32f3 --- /dev/null +++ b/src/gui/widgets/qabstractscrollarea.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSCROLLAREA_H +#define QABSTRACTSCROLLAREA_H + +#include <QtGui/qframe.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SCROLLAREA + +class QScrollBar; +class QAbstractScrollAreaPrivate; + +class Q_GUI_EXPORT QAbstractScrollArea : public QFrame +{ + Q_OBJECT + Q_PROPERTY(Qt::ScrollBarPolicy verticalScrollBarPolicy READ verticalScrollBarPolicy WRITE setVerticalScrollBarPolicy) + Q_PROPERTY(Qt::ScrollBarPolicy horizontalScrollBarPolicy READ horizontalScrollBarPolicy WRITE setHorizontalScrollBarPolicy) + +public: + explicit QAbstractScrollArea(QWidget* parent=0); + ~QAbstractScrollArea(); + + Qt::ScrollBarPolicy verticalScrollBarPolicy() const; + void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy); + QScrollBar *verticalScrollBar() const; + void setVerticalScrollBar(QScrollBar *scrollbar); + + Qt::ScrollBarPolicy horizontalScrollBarPolicy() const; + void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy); + QScrollBar *horizontalScrollBar() const; + void setHorizontalScrollBar(QScrollBar *scrollbar); + + QWidget *cornerWidget() const; + void setCornerWidget(QWidget *widget); + + void addScrollBarWidget(QWidget *widget, Qt::Alignment alignment); + QWidgetList scrollBarWidgets(Qt::Alignment alignment); + + QWidget *viewport() const; + void setViewport(QWidget *widget); + QSize maximumViewportSize() const; + + QSize minimumSizeHint() const; + + QSize sizeHint() const; + +protected Q_SLOTS: + void setupViewport(QWidget *viewport); + +protected: + QAbstractScrollArea(QAbstractScrollAreaPrivate &dd, QWidget *parent = 0); + void setViewportMargins(int left, int top, int right, int bottom); + + bool event(QEvent *); + virtual bool viewportEvent(QEvent *); + + void resizeEvent(QResizeEvent *); + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mouseDoubleClickEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *); +#endif +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *); +#endif +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *); + void dragMoveEvent(QDragMoveEvent *); + void dragLeaveEvent(QDragLeaveEvent *); + void dropEvent(QDropEvent *); +#endif + + void keyPressEvent(QKeyEvent *); + + virtual void scrollContentsBy(int dx, int dy); + +private: + Q_DECLARE_PRIVATE(QAbstractScrollArea) + Q_DISABLE_COPY(QAbstractScrollArea) + Q_PRIVATE_SLOT(d_func(), void _q_hslide(int)) + Q_PRIVATE_SLOT(d_func(), void _q_vslide(int)) + Q_PRIVATE_SLOT(d_func(), void _q_showOrHideScrollBars()) + + friend class QStyleSheetStyle; +}; + +#endif // QT_NO_SCROLLAREA + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTSCROLLAREA_H diff --git a/src/gui/widgets/qabstractscrollarea_p.h b/src/gui/widgets/qabstractscrollarea_p.h new file mode 100644 index 0000000..e4c47e9 --- /dev/null +++ b/src/gui/widgets/qabstractscrollarea_p.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSCROLLAREA_P_H +#define QABSTRACTSCROLLAREA_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/qframe_p.h" +#include "qabstractscrollarea.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SCROLLAREA + +class QScrollBar; +class QAbstractScrollAreaScrollBarContainer; +class Q_GUI_EXPORT QAbstractScrollAreaPrivate: public QFramePrivate +{ + Q_DECLARE_PUBLIC(QAbstractScrollArea) + +public: + QAbstractScrollAreaPrivate(); + + void replaceScrollBar(QScrollBar *scrollBar, Qt::Orientation orientation); + + QAbstractScrollAreaScrollBarContainer *scrollBarContainers[Qt::Vertical + 1]; + QScrollBar *hbar, *vbar; + Qt::ScrollBarPolicy vbarpolicy, hbarpolicy; + + QWidget *viewport; + QWidget *cornerWidget; + QRect cornerPaintingRect; +#ifdef Q_WS_MAC + QRect reverseCornerPaintingRect; +#endif + int left, top, right, bottom; // viewport margin + + int xoffset, yoffset; + + void init(); + void layoutChildren(); + // ### Fix for 4.4, talk to Bjoern E or Girish. + virtual void scrollBarPolicyChanged(Qt::Orientation, Qt::ScrollBarPolicy); + + void _q_hslide(int); + void _q_vslide(int); + void _q_showOrHideScrollBars(); + + virtual QPoint contentsOffset() const; + + inline bool viewportEvent(QEvent *event) + { return q_func()->viewportEvent(event); } + QObject *viewportFilter; +}; + +class QAbstractScrollAreaFilter : public QObject +{ + Q_OBJECT +public: + QAbstractScrollAreaFilter(QAbstractScrollAreaPrivate *p) : d(p) + { setObjectName(QLatin1String("qt_abstractscrollarea_filter")); } + bool eventFilter(QObject *o, QEvent *e) + { return (o == d->viewport ? d->viewportEvent(e) : false); } +private: + QAbstractScrollAreaPrivate *d; +}; + +class QBoxLayout; +class QAbstractScrollAreaScrollBarContainer : public QWidget +{ +public: + enum LogicalPosition { LogicalLeft = 1, LogicalRight = 2 }; + + QAbstractScrollAreaScrollBarContainer(Qt::Orientation orientation, QWidget *parent); + void addWidget(QWidget *widget, LogicalPosition position); + QWidgetList widgets(LogicalPosition position); + void removeWidget(QWidget *widget); + + QScrollBar *scrollBar; + QBoxLayout *layout; +private: + int scrollBarLayoutIndex() const; + + Qt::Orientation orientation; +}; + +#endif // QT_NO_SCROLLAREA + +QT_END_NAMESPACE + +#endif // QABSTRACTSCROLLAREA_P_H diff --git a/src/gui/widgets/qabstractslider.cpp b/src/gui/widgets/qabstractslider.cpp new file mode 100644 index 0000000..8028fdc --- /dev/null +++ b/src/gui/widgets/qabstractslider.cpp @@ -0,0 +1,914 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qapplication.h> +#include "qabstractslider.h" +#include "qevent.h" +#include "qabstractslider_p.h" +#include "qdebug.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#include <limits.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractSlider + \brief The QAbstractSlider class provides an integer value within a range. + + \ingroup abstractwidgets + + The class is designed as a common super class for widgets like + QScrollBar, QSlider and QDial. + + Here are the main properties of the class: + + \list 1 + + \i \l value: The bounded integer that QAbstractSlider maintains. + + \i \l minimum: The lowest possible value. + + \i \l maximum: The highest possible value. + + \i \l singleStep: The smaller of two natural steps that an + abstract sliders provides and typically corresponds to the user + pressing an arrow key. + + \i \l pageStep: The larger of two natural steps that an abstract + slider provides and typically corresponds to the user pressing + PageUp or PageDown. + + \i \l tracking: Whether slider tracking is enabled. + + \i \l sliderPosition: The current position of the slider. If \l + tracking is enabled (the default), this is identical to \l value. + + \endlist + + Unity (1) may be viewed as a third step size. setValue() lets you + set the current value to any integer in the allowed range, not + just minimum() + \e n * singleStep() for integer values of \e n. + Some widgets may allow the user to set any value at all; others + may just provide multiples of singleStep() or pageStep(). + + QAbstractSlider emits a comprehensive set of signals: + + \table + \header \i Signal \i Emitted when + \row \i \l valueChanged() + \i the value has changed. The \l tracking + determines whether this signal is emitted during user + interaction. + \row \i \l sliderPressed() + \i the user starts to drag the slider. + \row \i \l sliderMoved() + \i the user drags the slider. + \row \i \l sliderReleased() + \i the user releases the slider. + \row \i \l actionTriggered() + \i a slider action was triggerd. + \row \i \l rangeChanged() + \i a the range has changed. + \endtable + + QAbstractSlider provides a virtual sliderChange() function that is + well suited for updating the on-screen representation of + sliders. By calling triggerAction(), subclasses trigger slider + actions. Two helper functions QStyle::sliderPositionFromValue() and + QStyle::sliderValueFromPosition() help subclasses and styles to map + screen coordinates to logical range values. + + \sa QAbstractSpinBox, QSlider, QDial, QScrollBar, {Sliders Example} +*/ + +/*! + \enum QAbstractSlider::SliderAction + + \value SliderNoAction + \value SliderSingleStepAdd + \value SliderSingleStepSub + \value SliderPageStepAdd + \value SliderPageStepSub + \value SliderToMinimum + \value SliderToMaximum + \value SliderMove + +*/ + +/*! + \fn void QAbstractSlider::valueChanged(int value) + + This signal is emitted when the slider value has changed, with the + new slider \a value as argument. +*/ + +/*! + \fn void QAbstractSlider::sliderPressed() + + This signal is emitted when the user presses the slider with the + mouse, or programmatically when setSliderDown(true) is called. + + \sa sliderReleased(), sliderMoved(), isSliderDown() +*/ + +/*! + \fn void QAbstractSlider::sliderMoved(int value) + + This signal is emitted when sliderDown is true and the slider moves. This + usually happens when the user is dragging the slider. The \a value + is the new slider position. + + This signal is emitted even when tracking is turned off. + + \sa setTracking(), valueChanged(), isSliderDown(), + sliderPressed(), sliderReleased() +*/ + +/*! + \fn void QAbstractSlider::sliderReleased() + + This signal is emitted when the user releases the slider with the + mouse, or programmatically when setSliderDown(false) is called. + + \sa sliderPressed() sliderMoved() sliderDown +*/ + +/*! + \fn void QAbstractSlider::rangeChanged(int min, int max) + + This signal is emitted when the slider range has changed, with \a + min being the new minimum, and \a max being the new maximum. + + \sa minimum, maximum +*/ + +/*! + \fn void QAbstractSlider::actionTriggered(int action) + + This signal is emitted when the slider action \a action is + triggered. Actions are \l SliderSingleStepAdd, \l + SliderSingleStepSub, \l SliderPageStepAdd, \l SliderPageStepSub, + \l SliderToMinimum, \l SliderToMaximum, and \l SliderMove. + + When the signal is emitted, the \l sliderPosition has been + adjusted according to the action, but the \l value has not yet + been propagated (meaning the valueChanged() signal was not yet + emitted), and the visual display has not been updated. In slots + connected to this signal you can thus safely adjust any action by + calling setSliderPosition() yourself, based on both the action and + the slider's value. + + \sa triggerAction() +*/ + +/*! + \enum QAbstractSlider::SliderChange + + \value SliderRangeChange + \value SliderOrientationChange + \value SliderStepsChange + \value SliderValueChange +*/ + +QAbstractSliderPrivate::QAbstractSliderPrivate() + : minimum(0), maximum(99), singleStep(1), pageStep(10), + value(0), position(0), pressValue(-1), tracking(true), blocktracking(false), pressed(false), + invertedAppearance(false), invertedControls(false), + orientation(Qt::Horizontal), repeatAction(QAbstractSlider::SliderNoAction) +{ +} + +QAbstractSliderPrivate::~QAbstractSliderPrivate() +{ +} + +/*! + Sets the slider's minimum to \a min and its maximum to \a max. + + If \a max is smaller than \a min, \a min becomes the only legal + value. + + \sa minimum maximum +*/ +void QAbstractSlider::setRange(int min, int max) +{ + Q_D(QAbstractSlider); + int oldMin = d->minimum; + int oldMax = d->maximum; + d->minimum = min; + d->maximum = qMax(min, max); + if (oldMin != d->minimum || oldMax != d->maximum) { + sliderChange(SliderRangeChange); + emit rangeChanged(d->minimum, d->maximum); + setValue(d->value); // re-bound + } +} + + +void QAbstractSliderPrivate::setSteps(int single, int page) +{ + Q_Q(QAbstractSlider); + singleStep = qAbs(single); + pageStep = qAbs(page); + q->sliderChange(QAbstractSlider::SliderStepsChange); +} + +/*! + Constructs an abstract slider. + + The \a parent arguments is sent to the QWidget constructor. + + The \l minimum defaults to 0, the \l maximum to 99, with a \l + singleStep size of 1 and a \l pageStep size of 10, and an initial + \l value of 0. +*/ +QAbstractSlider::QAbstractSlider(QWidget *parent) + :QWidget(*new QAbstractSliderPrivate, parent, 0) +{ +} + +/*! \internal */ +QAbstractSlider::QAbstractSlider(QAbstractSliderPrivate &dd, QWidget *parent) + :QWidget(dd, parent, 0) +{ +} + +/*! + Destroys the slider. +*/ +QAbstractSlider::~QAbstractSlider() +{ +} + +/*! + \property QAbstractSlider::orientation + \brief the orientation of the slider + + The orientation must be \l Qt::Vertical (the default) or \l + Qt::Horizontal. +*/ +void QAbstractSlider::setOrientation(Qt::Orientation orientation) +{ + Q_D(QAbstractSlider); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + if (!testAttribute(Qt::WA_WState_OwnSizePolicy)) { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy(sp); + setAttribute(Qt::WA_WState_OwnSizePolicy, false); + } + update(); + updateGeometry(); +} + +Qt::Orientation QAbstractSlider::orientation() const +{ + Q_D(const QAbstractSlider); + return d->orientation; +} + + +/*! + \property QAbstractSlider::minimum + \brief the sliders's minimum value + + When setting this property, the \l maximum is adjusted if + necessary to ensure that the range remains valid. Also the + slider's current value is adjusted to be within the new range. + +*/ + +void QAbstractSlider::setMinimum(int min) +{ + Q_D(QAbstractSlider); + setRange(min, qMax(d->maximum, min)); +} + +int QAbstractSlider::minimum() const +{ + Q_D(const QAbstractSlider); + return d->minimum; +} + + +/*! + \property QAbstractSlider::maximum + \brief the slider's maximum value + + When setting this property, the \l minimum is adjusted if + necessary to ensure that the range remains valid. Also the + slider's current value is adjusted to be within the new range. + + +*/ + +void QAbstractSlider::setMaximum(int max) +{ + Q_D(QAbstractSlider); + setRange(qMin(d->minimum, max), max); +} + +int QAbstractSlider::maximum() const +{ + Q_D(const QAbstractSlider); + return d->maximum; +} + + + +/*! + \property QAbstractSlider::singleStep + \brief the single step. + + The smaller of two natural steps that an + abstract sliders provides and typically corresponds to the user + pressing an arrow key. + + \sa pageStep +*/ + +void QAbstractSlider::setSingleStep(int step) +{ + Q_D(QAbstractSlider); + if (step != d->singleStep) + d->setSteps(step, d->pageStep); +} + +int QAbstractSlider::singleStep() const +{ + Q_D(const QAbstractSlider); + return d->singleStep; +} + + +/*! + \property QAbstractSlider::pageStep + \brief the page step. + + The larger of two natural steps that an abstract slider provides + and typically corresponds to the user pressing PageUp or PageDown. + + \sa singleStep +*/ + +void QAbstractSlider::setPageStep(int step) +{ + Q_D(QAbstractSlider); + if (step != d->pageStep) + d->setSteps(d->singleStep, step); +} + +int QAbstractSlider::pageStep() const +{ + Q_D(const QAbstractSlider); + return d->pageStep; +} + +/*! + \property QAbstractSlider::tracking + \brief whether slider tracking is enabled + + If tracking is enabled (the default), the slider emits the + valueChanged() signal while the slider is being dragged. If + tracking is disabled, the slider emits the valueChanged() signal + only when the user releases the slider. + + \sa sliderDown +*/ +void QAbstractSlider::setTracking(bool enable) +{ + Q_D(QAbstractSlider); + d->tracking = enable; +} + +bool QAbstractSlider::hasTracking() const +{ + Q_D(const QAbstractSlider); + return d->tracking; +} + + +/*! + \property QAbstractSlider::sliderDown + \brief whether the slider is pressed down. + + The property is set by subclasses in order to let the abstract + slider know whether or not \l tracking has any effect. + + Changing the slider down property emits the sliderPressed() and + sliderReleased() signals. + +*/ +void QAbstractSlider::setSliderDown(bool down) +{ + Q_D(QAbstractSlider); + bool doEmit = d->pressed != down; + + d->pressed = down; + + if (doEmit) { + if (down) + emit sliderPressed(); + else + emit sliderReleased(); + } + + if (!down && d->position != d->value) + triggerAction(SliderMove); +} + +bool QAbstractSlider::isSliderDown() const +{ + Q_D(const QAbstractSlider); + return d->pressed; +} + + +/*! + \property QAbstractSlider::sliderPosition + \brief the current slider position + + If \l tracking is enabled (the default), this is identical to \l value. +*/ +void QAbstractSlider::setSliderPosition(int position) +{ + Q_D(QAbstractSlider); + position = d->bound(position); + if (position == d->position) + return; + d->position = position; + if (!d->tracking) + update(); + if (d->pressed) + emit sliderMoved(position); + if (d->tracking && !d->blocktracking) + triggerAction(SliderMove); +} + +int QAbstractSlider::sliderPosition() const +{ + Q_D(const QAbstractSlider); + return d->position; +} + + +/*! + \property QAbstractSlider::value + \brief the slider's current value + + The slider forces the value to be within the legal range: \l + minimum <= \c value <= \l maximum. + + Changing the value also changes the \l sliderPosition. +*/ + + +int QAbstractSlider::value() const +{ + Q_D(const QAbstractSlider); + return d->value; +} + +void QAbstractSlider::setValue(int value) +{ + Q_D(QAbstractSlider); + value = d->bound(value); + if (d->value == value && d->position == value) + return; + d->value = value; + if (d->position != value) { + d->position = value; + if (d->pressed) + emit sliderMoved((d->position = value)); + } +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::ValueChanged); +#endif + sliderChange(SliderValueChange); + emit valueChanged(value); +} + +/*! + \property QAbstractSlider::invertedAppearance + \brief whether or not a slider shows its values inverted. + + If this property is false (the default), the minimum and maximum will + be shown in its classic position for the inherited widget. If the + value is true, the minimum and maximum appear at their opposite location. + + Note: This property makes most sense for sliders and dials. For + scroll bars, the visual effect of the scroll bar subcontrols depends on + whether or not the styles understand inverted appearance; most styles + ignore this property for scroll bars. +*/ + +bool QAbstractSlider::invertedAppearance() const +{ + Q_D(const QAbstractSlider); + return d->invertedAppearance; +} + +void QAbstractSlider::setInvertedAppearance(bool invert) +{ + Q_D(QAbstractSlider); + d->invertedAppearance = invert; + update(); +} + + +/*! + \property QAbstractSlider::invertedControls + \brief whether or not the slider inverts its wheel and key events. + + If this property is false, scrolling the mouse wheel "up" and using keys + like page up will increase the slider's value towards its maximum. Otherwise + pressing page up will move value towards the slider's minimum. +*/ + + +bool QAbstractSlider::invertedControls() const +{ + Q_D(const QAbstractSlider); + return d->invertedControls; +} + +void QAbstractSlider::setInvertedControls(bool invert) +{ + Q_D(QAbstractSlider); + d->invertedControls = invert; +} + +/*! Triggers a slider \a action. Possible actions are \l + SliderSingleStepAdd, \l SliderSingleStepSub, \l SliderPageStepAdd, + \l SliderPageStepSub, \l SliderToMinimum, \l SliderToMaximum, and \l + SliderMove. + + \sa actionTriggered() + */ +void QAbstractSlider::triggerAction(SliderAction action) +{ + Q_D(QAbstractSlider); + d->blocktracking = true; + switch (action) { + case SliderSingleStepAdd: + setSliderPosition(d->overflowSafeAdd(d->singleStep)); + break; + case SliderSingleStepSub: + setSliderPosition(d->overflowSafeAdd(-d->singleStep)); + break; + case SliderPageStepAdd: + setSliderPosition(d->overflowSafeAdd(d->pageStep)); + break; + case SliderPageStepSub: + setSliderPosition(d->overflowSafeAdd(-d->pageStep)); + break; + case SliderToMinimum: + setSliderPosition(d->minimum); + break; + case SliderToMaximum: + setSliderPosition(d->maximum); + break; + case SliderMove: + case SliderNoAction: + break; + }; + emit actionTriggered(action); + d->blocktracking = false; + setValue(d->position); +} + +/*! Sets action \a action to be triggered repetitively in intervals +of \a repeatTime, after an initial delay of \a thresholdTime. + +\sa triggerAction() repeatAction() + */ +void QAbstractSlider::setRepeatAction(SliderAction action, int thresholdTime, int repeatTime) +{ + Q_D(QAbstractSlider); + if ((d->repeatAction = action) == SliderNoAction) { + d->repeatActionTimer.stop(); + } else { + d->repeatActionTime = repeatTime; + d->repeatActionTimer.start(thresholdTime, this); + } +} + +/*! + Returns the current repeat action. + \sa setRepeatAction() + */ +QAbstractSlider::SliderAction QAbstractSlider::repeatAction() const +{ + Q_D(const QAbstractSlider); + return d->repeatAction; +} + +/*!\reimp + */ +void QAbstractSlider::timerEvent(QTimerEvent *e) +{ + Q_D(QAbstractSlider); + if (e->timerId() == d->repeatActionTimer.timerId()) { + if (d->repeatActionTime) { // was threshold time, use repeat time next time + d->repeatActionTimer.start(d->repeatActionTime, this); + d->repeatActionTime = 0; + } + if (d->repeatAction == SliderPageStepAdd) + d->setAdjustedSliderPosition(d->overflowSafeAdd(d->pageStep)); + else if (d->repeatAction == SliderPageStepSub) + d->setAdjustedSliderPosition(d->overflowSafeAdd(-d->pageStep)); + else + triggerAction(d->repeatAction); + } +} + +/*! + Reimplement this virtual function to track slider changes such as + \l SliderRangeChange, \l SliderOrientationChange, \l + SliderStepsChange, or \l SliderValueChange. The default + implementation only updates the display and ignores the \a change + parameter. + */ +void QAbstractSlider::sliderChange(SliderChange) +{ + update(); +} + + +/*! + \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QAbstractSlider::wheelEvent(QWheelEvent * e) +{ + Q_D(QAbstractSlider); + e->ignore(); + if (e->orientation() != d->orientation && !rect().contains(e->pos())) + return; + + static qreal offset = 0; + static QAbstractSlider *offset_owner = 0; + if (offset_owner != this){ + offset_owner = this; + offset = 0; + } + + // On Mac/Cocoa, always scroll one step. The mouse wheel acceleration + // is higher than on other systems, so this works well in practice. +#ifdef QT_MAC_USE_COCOA + int step = 1; +#else + int step = qMin(QApplication::wheelScrollLines() * d->singleStep, d->pageStep); +#endif + if ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::ShiftModifier)) + step = d->pageStep; + int currentOffset = qRound(qreal(e->delta()) * step / 120); + if (currentOffset == 0) + currentOffset = (e->delta() < 0 ? -1 : 1); + offset += currentOffset; + + if (d->invertedControls) + offset = -offset; + + int prevValue = d->value; + d->position = d->overflowSafeAdd(int(offset)); // value will be updated by triggerAction() + + triggerAction(SliderMove); + if (prevValue == d->value) { + offset = 0; + } else { + offset -= int(offset); + e->accept(); + } +} +#endif +/*! + \reimp +*/ +void QAbstractSlider::keyPressEvent(QKeyEvent *ev) +{ + Q_D(QAbstractSlider); + SliderAction action = SliderNoAction; + switch (ev->key()) { +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) + setEditFocus(!hasEditFocus()); + else + ev->ignore(); + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled() && hasEditFocus()) { + setValue(d->origValue); + setEditFocus(false); + } else + ev->ignore(); + break; +#endif + + // It seems we need to use invertedAppearance for Left and right, otherwise, things look weird. + case Qt::Key_Left: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + ev->ignore(); + return; + } + if (QApplication::keypadNavigationEnabled() && d->orientation == Qt::Vertical) + action = d->invertedControls ? SliderSingleStepSub : SliderSingleStepAdd; + else +#endif + if (isRightToLeft()) + action = d->invertedAppearance ? SliderSingleStepSub : SliderSingleStepAdd; + else + action = !d->invertedAppearance ? SliderSingleStepSub : SliderSingleStepAdd; + break; + case Qt::Key_Right: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + ev->ignore(); + return; + } + if (QApplication::keypadNavigationEnabled() && d->orientation == Qt::Vertical) + action = d->invertedControls ? SliderSingleStepAdd : SliderSingleStepSub; + else +#endif + if (isRightToLeft()) + action = d->invertedAppearance ? SliderSingleStepAdd : SliderSingleStepSub; + else + action = !d->invertedAppearance ? SliderSingleStepAdd : SliderSingleStepSub; + break; + case Qt::Key_Up: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + ev->ignore(); + break; + } +#endif + action = d->invertedControls ? SliderSingleStepSub : SliderSingleStepAdd; + break; + case Qt::Key_Down: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + ev->ignore(); + break; + } +#endif + action = d->invertedControls ? SliderSingleStepAdd : SliderSingleStepSub; + break; + case Qt::Key_PageUp: + action = d->invertedControls ? SliderPageStepSub : SliderPageStepAdd; + break; + case Qt::Key_PageDown: + action = d->invertedControls ? SliderPageStepAdd : SliderPageStepSub; + break; + case Qt::Key_Home: + action = SliderToMinimum; + break; + case Qt::Key_End: + action = SliderToMaximum; + break; + default: + ev->ignore(); + break; + } + if (action) + triggerAction(action); +} + +/*! + \reimp +*/ +void QAbstractSlider::changeEvent(QEvent *ev) +{ + Q_D(QAbstractSlider); + switch (ev->type()) { + case QEvent::EnabledChange: + if (!isEnabled()) { + d->repeatActionTimer.stop(); + setSliderDown(false); + } + // fall through... + default: + QWidget::changeEvent(ev); + } +} + +/*! + \reimp +*/ +bool QAbstractSlider::event(QEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + Q_D(QAbstractSlider); + switch (e->type()) { + case QEvent::FocusIn: + d->origValue = d->value; + break; + default: + break; + } +#endif + + return QWidget::event(e); +} + +/*! \fn int QAbstractSlider::minValue() const + + Use minimum() instead. +*/ + +/*! \fn int QAbstractSlider::maxValue() const + + Use maximum() instead. +*/ + +/*! \fn int QAbstractSlider::lineStep() const + + Use singleStep() instead. +*/ + +/*! \fn void QAbstractSlider::setMinValue(int v) + + Use setMinimum() instead. +*/ + +/*! \fn void QAbstractSlider::setMaxValue(int v) + + Use setMaximum() instead. +*/ + +/*! \fn void QAbstractSlider::setLineStep(int v) + + Use setSingleStep() instead. +*/ + +/*! \fn void QAbstractSlider::addPage() + + Use triggerAction(QAbstractSlider::SliderPageStepAdd) instead. +*/ + +/*! \fn void QAbstractSlider::subtractPage() + + Use triggerAction(QAbstractSlider::SliderPageStepSub) instead. +*/ + +/*! \fn void QAbstractSlider::addLine() + + Use triggerAction(QAbstractSlider::SliderSingleStepAdd) instead. +*/ + +/*! \fn void QAbstractSlider::subtractLine() + + Use triggerAction(QAbstractSlider::SliderSingleStepSub) instead. +*/ + +/*! \fn void QAbstractSlider::setSteps(int single, int page) + + Use setSingleStep(\a single) followed by setPageStep(\a page) + instead. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qabstractslider.h b/src/gui/widgets/qabstractslider.h new file mode 100644 index 0000000..e94d047 --- /dev/null +++ b/src/gui/widgets/qabstractslider.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSLIDER_H +#define QABSTRACTSLIDER_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAbstractSliderPrivate; + +class Q_GUI_EXPORT QAbstractSlider : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int maximum READ maximum WRITE setMaximum) + Q_PROPERTY(int singleStep READ singleStep WRITE setSingleStep) + Q_PROPERTY(int pageStep READ pageStep WRITE setPageStep) + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged USER true) + Q_PROPERTY(int sliderPosition READ sliderPosition WRITE setSliderPosition NOTIFY sliderMoved) + Q_PROPERTY(bool tracking READ hasTracking WRITE setTracking) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(bool invertedAppearance READ invertedAppearance WRITE setInvertedAppearance) + Q_PROPERTY(bool invertedControls READ invertedControls WRITE setInvertedControls) + Q_PROPERTY(bool sliderDown READ isSliderDown WRITE setSliderDown DESIGNABLE false) + +public: + explicit QAbstractSlider(QWidget *parent=0); + ~QAbstractSlider(); + + Qt::Orientation orientation() const; + + void setMinimum(int); + int minimum() const; + + void setMaximum(int); + int maximum() const; + + void setRange(int min, int max); + + void setSingleStep(int); + int singleStep() const; + + void setPageStep(int); + int pageStep() const; + + void setTracking(bool enable); + bool hasTracking() const; + + void setSliderDown(bool); + bool isSliderDown() const; + + void setSliderPosition(int); + int sliderPosition() const; + + void setInvertedAppearance(bool); + bool invertedAppearance() const; + + void setInvertedControls(bool); + bool invertedControls() const; + + enum SliderAction { + SliderNoAction, + SliderSingleStepAdd, + SliderSingleStepSub, + SliderPageStepAdd, + SliderPageStepSub, + SliderToMinimum, + SliderToMaximum, + SliderMove + }; + + int value() const; + + void triggerAction(SliderAction action); + +public Q_SLOTS: + void setValue(int); + void setOrientation(Qt::Orientation); + +Q_SIGNALS: + void valueChanged(int value); + + void sliderPressed(); + void sliderMoved(int position); + void sliderReleased(); + + void rangeChanged(int min, int max); + + void actionTriggered(int action); + +protected: + bool event(QEvent *e); + + void setRepeatAction(SliderAction action, int thresholdTime = 500, int repeatTime = 50); + SliderAction repeatAction() const; + + enum SliderChange { + SliderRangeChange, + SliderOrientationChange, + SliderStepsChange, + SliderValueChange + }; + virtual void sliderChange(SliderChange change); + + void keyPressEvent(QKeyEvent *ev); + void timerEvent(QTimerEvent *); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *e); +#endif + void changeEvent(QEvent *e); + +#ifdef QT3_SUPPORT +public: + inline QT3_SUPPORT int minValue() const { return minimum(); } + inline QT3_SUPPORT int maxValue() const { return maximum(); } + inline QT3_SUPPORT int lineStep() const { return singleStep(); } + inline QT3_SUPPORT void setMinValue(int v) { setMinimum(v); } + inline QT3_SUPPORT void setMaxValue(int v) { setMaximum(v); } + inline QT3_SUPPORT void setLineStep(int v) { setSingleStep(v); } + inline QT3_SUPPORT void setSteps(int single, int page) { setSingleStep(single); setPageStep(page); } + inline QT3_SUPPORT void addPage() { triggerAction(SliderPageStepAdd); } + inline QT3_SUPPORT void subtractPage() { triggerAction(SliderPageStepSub); } + inline QT3_SUPPORT void addLine() { triggerAction(SliderSingleStepAdd); } + inline QT3_SUPPORT void subtractLine() { triggerAction(SliderSingleStepSub); } +#endif + +protected: + QAbstractSlider(QAbstractSliderPrivate &dd, QWidget *parent=0); + +private: + Q_DISABLE_COPY(QAbstractSlider) + Q_DECLARE_PRIVATE(QAbstractSlider) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTSLIDER_H diff --git a/src/gui/widgets/qabstractslider_p.h b/src/gui/widgets/qabstractslider_p.h new file mode 100644 index 0000000..6438d30 --- /dev/null +++ b/src/gui/widgets/qabstractslider_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSLIDER_P_H +#define QABSTRACTSLIDER_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 "QtCore/qbasictimer.h" +#include "private/qwidget_p.h" +#include "qstyle.h" + +QT_BEGIN_NAMESPACE + +class QAbstractSliderPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QAbstractSlider) +public: + QAbstractSliderPrivate(); + ~QAbstractSliderPrivate(); + + void setSteps(int single, int page); + + int minimum, maximum, singleStep, pageStep, value, position, pressValue; + uint tracking : 1; + uint blocktracking :1; + uint pressed : 1; + uint invertedAppearance : 1; + uint invertedControls : 1; + Qt::Orientation orientation; + + QBasicTimer repeatActionTimer; + int repeatActionTime; + QAbstractSlider::SliderAction repeatAction; + +#ifdef QT_KEYPAD_NAVIGATION + int origValue; +#endif + + inline int bound(int val) const { return qMax(minimum, qMin(maximum, val)); } + inline int overflowSafeAdd(int add) const + { + int newValue = value + add; + if (add > 0 && newValue < value) + newValue = maximum; + else if (add < 0 && newValue > value) + newValue = minimum; + return newValue; + } + inline void setAdjustedSliderPosition(int position) + { + Q_Q(QAbstractSlider); + if (q->style()->styleHint(QStyle::SH_Slider_StopMouseOverSlider, 0, q)) { + if ((position > pressValue - 2 * pageStep) && (position < pressValue + 2 * pageStep)) { + repeatAction = QAbstractSlider::SliderNoAction; + q->setSliderPosition(pressValue); + return; + } + } + q->triggerAction(repeatAction); + } +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSLIDER_P_H diff --git a/src/gui/widgets/qabstractspinbox.cpp b/src/gui/widgets/qabstractspinbox.cpp new file mode 100644 index 0000000..347f89a --- /dev/null +++ b/src/gui/widgets/qabstractspinbox.cpp @@ -0,0 +1,2049 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> +#include <private/qabstractspinbox_p.h> +#include <private/qdatetime_p.h> +#include <private/qlineedit_p.h> +#include <qabstractspinbox.h> + +#ifndef QT_NO_SPINBOX + +#include <qapplication.h> +#include <qclipboard.h> +#include <qdatetime.h> +#include <qdatetimeedit.h> +#include <qevent.h> +#include <qmenu.h> +#include <qpainter.h> +#include <qpalette.h> +#include <qstylepainter.h> +#include <qdebug.h> + +#if defined(Q_WS_X11) +#include <limits.h> +#endif + +//#define QABSTRACTSPINBOX_QSBDEBUG +#ifdef QABSTRACTSPINBOX_QSBDEBUG +# define QASBDEBUG qDebug +#else +# define QASBDEBUG if (false) qDebug +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractSpinBox + \brief The QAbstractSpinBox class provides a spinbox and a line edit to + display values. + + \ingroup abstractwidgets + + The class is designed as a common super class for widgets like + QSpinBox, QDoubleSpinBox and QDateTimeEdit + + Here are the main properties of the class: + + \list 1 + + \i \l text: The text that is displayed in the QAbstractSpinBox. + + \i \l alignment: The alignment of the text in the QAbstractSpinBox. + + \i \l wrapping: Whether the QAbstractSpinBox wraps from the + minimum value to the maximum value and vica versa. + + \endlist + + QAbstractSpinBox provides a virtual stepBy() function that is + called whenever the user triggers a step. This function takes an + integer value to signify how many steps were taken. E.g. Pressing + Qt::Key_Down will trigger a call to stepBy(-1). + + QAbstractSpinBox also provide a virtual function stepEnabled() to + determine whether stepping up/down is allowed at any point. This + function returns a bitset of StepEnabled. + + \sa QAbstractSlider, QSpinBox, QDoubleSpinBox, QDateTimeEdit, + {Spin Boxes Example} +*/ + +/*! + \enum QAbstractSpinBox::StepEnabledFlag + + \value StepNone + \value StepUpEnabled + \value StepDownEnabled +*/ + +/*! + \fn void QAbstractSpinBox::editingFinished() + + This signal is emitted editing is finished. This happens when the + spinbox loses focus and when enter is pressed. +*/ + +/*! + Constructs an abstract spinbox with the given \a parent with default + \l wrapping, and \l alignment properties. +*/ + +QAbstractSpinBox::QAbstractSpinBox(QWidget *parent) + : QWidget(*new QAbstractSpinBoxPrivate, parent, 0) +{ + Q_D(QAbstractSpinBox); + d->init(); +} + +/*! + \internal +*/ +QAbstractSpinBox::QAbstractSpinBox(QAbstractSpinBoxPrivate &dd, QWidget *parent) + : QWidget(dd, parent, 0) +{ + Q_D(QAbstractSpinBox); + d->init(); +} + +/*! + Called when the QAbstractSpinBox is destroyed. +*/ + +QAbstractSpinBox::~QAbstractSpinBox() +{ +} + +/*! + \enum QAbstractSpinBox::ButtonSymbols + + This enum type describes the symbols that can be displayed on the buttons + in a spin box. + + \inlineimage qspinbox-updown.png + \inlineimage qspinbox-plusminus.png + + \value UpDownArrows Little arrows in the classic style. + \value PlusMinus \bold{+} and \bold{-} symbols. + \value NoButtons Don't display buttons. + + \sa QAbstractSpinBox::buttonSymbols +*/ + +/*! + \property QAbstractSpinBox::buttonSymbols + + \brief the current button symbol mode + + The possible values can be either \c UpDownArrows or \c PlusMinus. + The default is \c UpDownArrows. + + Note that some styles might render PlusMinus and UpDownArrows + identically. + + \sa ButtonSymbols +*/ + +QAbstractSpinBox::ButtonSymbols QAbstractSpinBox::buttonSymbols() const +{ + Q_D(const QAbstractSpinBox); + return d->buttonSymbols; +} + +void QAbstractSpinBox::setButtonSymbols(ButtonSymbols buttonSymbols) +{ + Q_D(QAbstractSpinBox); + if (d->buttonSymbols != buttonSymbols) { + d->buttonSymbols = buttonSymbols; + update(); + } +} + +/*! + \property QAbstractSpinBox::text + + \brief the spin box's text, including any prefix and suffix + + There is no default text. +*/ + +QString QAbstractSpinBox::text() const +{ + return lineEdit()->displayText(); +} + + +/*! + \property QAbstractSpinBox::specialValueText + \brief the special-value text + + If set, the spin box will display this text instead of a numeric + value whenever the current value is equal to minimum(). Typical use + is to indicate that this choice has a special (default) meaning. + + For example, if your spin box allows the user to choose a scale factor + (or zoom level) for displaying an image, and your application is able + to automatically choose one that will enable the image to fit completely + within the display window, you can set up the spin box like this: + + \snippet examples/widgets/spinboxes/window.cpp 3 + + The user will then be able to choose a scale from 1% to 1000% + or select "Auto" to leave it up to the application to choose. Your code + must then interpret the spin box value of 0 as a request from the user + to scale the image to fit inside the window. + + All values are displayed with the prefix and suffix (if set), \e + except for the special value, which only shows the special value + text. This special text is passed in the QSpinBox::valueChanged() + signal that passes a QString. + + To turn off the special-value text display, call this function + with an empty string. The default is no special-value text, i.e. + the numeric value is shown as usual. + + If no special-value text is set, specialValueText() returns an + empty string. +*/ + +QString QAbstractSpinBox::specialValueText() const +{ + Q_D(const QAbstractSpinBox); + return d->specialValueText; +} + +void QAbstractSpinBox::setSpecialValueText(const QString &specialValueText) +{ + Q_D(QAbstractSpinBox); + + d->specialValueText = specialValueText; + d->cachedSizeHint = QSize(); // minimumSizeHint doesn't care about specialValueText + d->clearCache(); + d->updateEdit(); +} + +/*! + \property QAbstractSpinBox::wrapping + + \brief whether the spin box is circular. + + If wrapping is true stepping up from maximum() value will take you + to the minimum() value and vica versa. Wrapping only make sense if + you have minimum() and maximum() values set. + + \snippet doc/src/snippets/code/src_gui_widgets_qabstractspinbox.cpp 0 + + \sa QSpinBox::minimum(), QSpinBox::maximum() +*/ + +bool QAbstractSpinBox::wrapping() const +{ + Q_D(const QAbstractSpinBox); + return d->wrapping; +} + +void QAbstractSpinBox::setWrapping(bool wrapping) +{ + Q_D(QAbstractSpinBox); + d->wrapping = wrapping; +} + + +/*! + \property QAbstractSpinBox::readOnly + \brief whether the spin box is read only. + + In read-only mode, the user can still copy the text to the + clipboard, or drag and drop the text; + but cannot edit it. + + The QLineEdit in the QAbstractSpinBox does not show a cursor in + read-only mode. + + \sa QLineEdit::readOnly +*/ + +bool QAbstractSpinBox::isReadOnly() const +{ + Q_D(const QAbstractSpinBox); + return d->readOnly; +} + +void QAbstractSpinBox::setReadOnly(bool enable) +{ + Q_D(QAbstractSpinBox); + d->readOnly = enable; + d->edit->setReadOnly(enable); + update(); +} + +/*! + \property QAbstractSpinBox::keyboardTracking + \brief whether keyboard tracking is enabled for the spinbox. + \since 4.3 + + If keyboard tracking is enabled (the default), the spinbox + emits the valueChanged() signal while the new value is being + entered from the keyboard. + + E.g. when the user enters the value 600 by typing 6, 0, and 0, + the spinbox emits 3 signals with the values 6, 60, and 600 + respectively. + + If keyboard tracking is disabled, the spinbox doesn't emit the + valueChanged() signal while typing. It emits the signal later, + when the return key is pressed, when keyboard focus is lost, or + when other spinbox functionality is used, e.g. pressing an arrow + key. +*/ + +bool QAbstractSpinBox::keyboardTracking() const +{ + Q_D(const QAbstractSpinBox); + return d->keyboardTracking; +} + +void QAbstractSpinBox::setKeyboardTracking(bool enable) +{ + Q_D(QAbstractSpinBox); + d->keyboardTracking = enable; +} + +/*! + \property QAbstractSpinBox::frame + \brief whether the spin box draws itself with a frame + + If enabled (the default) the spin box draws itself inside a frame, + otherwise the spin box draws itself without any frame. +*/ + +bool QAbstractSpinBox::hasFrame() const +{ + Q_D(const QAbstractSpinBox); + return d->frame; +} + + +void QAbstractSpinBox::setFrame(bool enable) +{ + Q_D(QAbstractSpinBox); + d->frame = enable; + update(); + d->updateEditFieldGeometry(); +} + +/*! + \property QAbstractSpinBox::accelerated + \brief whether the spin box will accelerate the frequency of the steps when + pressing the step Up/Down buttons. + \since 4.2 + + If enabled the spin box will increase/decrease the value faster + the longer you hold the button down. +*/ + +void QAbstractSpinBox::setAccelerated(bool accelerate) +{ + Q_D(QAbstractSpinBox); + d->accelerate = accelerate; + +} +bool QAbstractSpinBox::isAccelerated() const +{ + Q_D(const QAbstractSpinBox); + return d->accelerate; +} + +/*! + \enum QAbstractSpinBox::CorrectionMode + + This enum type describes the mode the spinbox will use to correct + an \l{QValidator::}{Intermediate} value if editing finishes. + + \value CorrectToPreviousValue The spinbox will revert to the last + valid value. + + \value CorrectToNearestValue The spinbox will revert to the nearest + valid value. + + \sa correctionMode +*/ + +/*! + \property QAbstractSpinBox::correctionMode + \brief the mode to correct an \l{QValidator::}{Intermediate} + value if editing finishes + \since 4.2 + + The default mode is QAbstractSpinBox::CorrectToPreviousValue. + + \sa acceptableInput, validate(), fixup() +*/ +void QAbstractSpinBox::setCorrectionMode(CorrectionMode correctionMode) +{ + Q_D(QAbstractSpinBox); + d->correctionMode = correctionMode; + +} +QAbstractSpinBox::CorrectionMode QAbstractSpinBox::correctionMode() const +{ + Q_D(const QAbstractSpinBox); + return d->correctionMode; +} + + +/*! + \property QAbstractSpinBox::acceptableInput + \brief whether the input satisfies the current validation + \since 4.2 + + \sa validate(), fixup(), correctionMode +*/ + +bool QAbstractSpinBox::hasAcceptableInput() const +{ + Q_D(const QAbstractSpinBox); + return d->edit->hasAcceptableInput(); +} + +/*! + \property QAbstractSpinBox::alignment + \brief the alignment of the spin box + + Possible Values are Qt::AlignLeft, Qt::AlignRight, and Qt::AlignHCenter. + + By default, the alignment is Qt::AlignLeft + + Attempting to set the alignment to an illegal flag combination + does nothing. + + \sa Qt::Alignment +*/ + +Qt::Alignment QAbstractSpinBox::alignment() const +{ + Q_D(const QAbstractSpinBox); + + return (Qt::Alignment)d->edit->alignment(); +} + +void QAbstractSpinBox::setAlignment(Qt::Alignment flag) +{ + Q_D(QAbstractSpinBox); + + d->edit->setAlignment(flag); +} + +/*! + Selects all the text in the spinbox except the prefix and suffix. +*/ + +void QAbstractSpinBox::selectAll() +{ + Q_D(QAbstractSpinBox); + + + if (!d->specialValue()) { + const int tmp = d->edit->displayText().size() - d->suffix.size(); + d->edit->setSelection(tmp, -(tmp - d->prefix.size())); + } else { + d->edit->selectAll(); + } +} + +/*! + Clears the lineedit of all text but prefix and suffix. +*/ + +void QAbstractSpinBox::clear() +{ + Q_D(QAbstractSpinBox); + + d->edit->setText(d->prefix + d->suffix); + d->edit->setCursorPosition(d->prefix.size()); + d->cleared = true; +} + +/*! + Virtual function that determines whether stepping up and down is + legal at any given time. + + The up arrow will be painted as disabled unless (stepEnabled() & + StepUpEnabled) != 0. + + The default implementation will return (StepUpEnabled| + StepDownEnabled) if wrapping is turned on. Else it will return + StepDownEnabled if value is > minimum() or'ed with StepUpEnabled if + value < maximum(). + + If you subclass QAbstractSpinBox you will need to reimplement this function. + + \sa QSpinBox::minimum(), QSpinBox::maximum(), wrapping() +*/ + + +QAbstractSpinBox::StepEnabled QAbstractSpinBox::stepEnabled() const +{ + Q_D(const QAbstractSpinBox); + if (d->readOnly || d->type == QVariant::Invalid) + return StepNone; + if (d->wrapping) + return StepEnabled(StepUpEnabled | StepDownEnabled); + StepEnabled ret = StepNone; + if (d->variantCompare(d->value, d->maximum) < 0) { + ret |= StepUpEnabled; + } + if (d->variantCompare(d->value, d->minimum) > 0) { + ret |= StepDownEnabled; + } + return ret; +} + +/*! + This virtual function is called by the QAbstractSpinBox to + determine whether \a input is valid. The \a pos parameter indicates + the position in the string. Reimplemented in the various + subclasses. +*/ + +QValidator::State QAbstractSpinBox::validate(QString & /* input */, int & /* pos */) const +{ + return QValidator::Acceptable; +} + +/*! + This virtual function is called by the QAbstractSpinBox if the + \a input is not validated to QValidator::Acceptable when Return is + pressed or interpretText() is called. It will try to change the + text so it is valid. Reimplemented in the various subclasses. +*/ + +void QAbstractSpinBox::fixup(QString & /* input */) const +{ +} + +/*! + Steps up by one linestep + Calling this slot is analogous to calling stepBy(1); + \sa stepBy(), stepDown() +*/ + +void QAbstractSpinBox::stepUp() +{ + stepBy(1); +} + +/*! + Steps down by one linestep + Calling this slot is analogous to calling stepBy(-1); + \sa stepBy(), stepUp() +*/ + +void QAbstractSpinBox::stepDown() +{ + stepBy(-1); +} +/*! + Virtual function that is called whenever the user triggers a step. + The \a steps parameter indicates how many steps were taken, e.g. + Pressing Qt::Key_Down will trigger a call to stepBy(-1), + whereas pressing Qt::Key_Prior will trigger a call to + stepBy(10). + + If you subclass QAbstractSpinBox you must reimplement this + function. Note that this function is called even if the resulting + value will be outside the bounds of minimum and maximum. It's this + function's job to handle these situations. +*/ + +void QAbstractSpinBox::stepBy(int steps) +{ + Q_D(QAbstractSpinBox); + + const QVariant old = d->value; + QString tmp = d->edit->displayText(); + int cursorPos = d->edit->cursorPosition(); + bool dontstep = false; + EmitPolicy e = EmitIfChanged; + if (d->pendingEmit) { + dontstep = validate(tmp, cursorPos) != QValidator::Acceptable; + d->cleared = false; + d->interpret(NeverEmit); + if (d->value != old) + e = AlwaysEmit; + } + if (!dontstep) { + d->setValue(d->bound(d->value + (d->singleStep * steps), old, steps), e); + } else if (e == AlwaysEmit) { + d->emitSignals(e, old); + } + selectAll(); +} + +/*! + This function returns a pointer to the line edit of the spin box. +*/ + +QLineEdit *QAbstractSpinBox::lineEdit() const +{ + Q_D(const QAbstractSpinBox); + + return d->edit; +} + + +/*! + \fn void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit) + + Sets the line edit of the spinbox to be \a lineEdit instead of the + current line edit widget. \a lineEdit can not be 0. + + QAbstractSpinBox takes ownership of the new lineEdit + + If QLineEdit::validator() for the \a lineEdit returns 0, the internal + validator of the spinbox will be set on the line edit. +*/ + +void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit) +{ + Q_D(QAbstractSpinBox); + + if (!lineEdit) { + Q_ASSERT(lineEdit); + return; + } + delete d->edit; + d->edit = lineEdit; + if (!d->edit->validator()) + d->edit->setValidator(d->validator); + + if (d->edit->parent() != this) + d->edit->setParent(this); + + d->edit->setFrame(false); + d->edit->setAttribute(Qt::WA_InputMethodEnabled, false); + d->edit->setFocusProxy(this); + d->edit->setAcceptDrops(false); + + if (d->type != QVariant::Invalid) { + connect(d->edit, SIGNAL(textChanged(QString)), + this, SLOT(_q_editorTextChanged(QString))); + connect(d->edit, SIGNAL(cursorPositionChanged(int,int)), + this, SLOT(_q_editorCursorPositionChanged(int,int))); + } + d->updateEditFieldGeometry(); + d->edit->setContextMenuPolicy(Qt::NoContextMenu); + + if (isVisible()) + d->edit->show(); + if (isVisible()) + d->updateEdit(); +} + + +/*! + This function interprets the text of the spin box. If the value + has changed since last interpretation it will emit signals. +*/ + +void QAbstractSpinBox::interpretText() +{ + Q_D(QAbstractSpinBox); + d->interpret(EmitIfChanged); +} + +/*! + \reimp +*/ + +bool QAbstractSpinBox::event(QEvent *event) +{ + Q_D(QAbstractSpinBox); + switch (event->type()) { + case QEvent::FontChange: + case QEvent::StyleChange: + d->cachedSizeHint = d->cachedMinimumSizeHint = QSize(); + break; + case QEvent::ApplicationLayoutDirectionChange: + case QEvent::LayoutDirectionChange: + d->updateEditFieldGeometry(); + break; + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) + d->updateHoverControl(he->pos()); + break; + case QEvent::ShortcutOverride: + if (d->edit->event(event)) + return true; + break; +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + case QEvent::LeaveEditFocus: + if (QApplication::keypadNavigationEnabled()) { + const bool b = d->edit->event(event); + d->edit->setSelection(d->edit->displayText().size() - d->suffix.size(),0); + if (event->type() == QEvent::LeaveEditFocus) + emit editingFinished(); + if (b) + return true; + } + break; +#endif + case QEvent::InputMethod: + return d->edit->event(event); + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::showEvent(QShowEvent *) +{ + Q_D(QAbstractSpinBox); + d->reset(); + + if (d->ignoreUpdateEdit) { + d->ignoreUpdateEdit = false; + } else { + d->updateEdit(); + } +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::changeEvent(QEvent *event) +{ + Q_D(QAbstractSpinBox); + + switch (event->type()) { + case QEvent::StyleChange: + d->spinClickTimerInterval = style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, 0, this); + d->spinClickThresholdTimerInterval = + style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, 0, this); + d->reset(); + d->updateEditFieldGeometry(); + break; + case QEvent::EnabledChange: + if (!isEnabled()) { + d->reset(); + } + break; + case QEvent::ActivationChange: + if (!isActiveWindow()){ + d->reset(); + if (d->pendingEmit) // pendingEmit can be true even if it hasn't changed. + d->interpret(EmitIfChanged); // E.g. 10 to 10.0 + } + break; + default: + break; + } + QWidget::changeEvent(event); +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::resizeEvent(QResizeEvent *event) +{ + Q_D(QAbstractSpinBox); + QWidget::resizeEvent(event); + + d->updateEditFieldGeometry(); + update(); +} + +/*! + \reimp +*/ + +QSize QAbstractSpinBox::sizeHint() const +{ + Q_D(const QAbstractSpinBox); + if (d->cachedSizeHint.isEmpty()) { + ensurePolished(); + + const QFontMetrics fm(fontMetrics()); + int h = d->edit->sizeHint().height(); + int w = 0; + QString s; + s = d->prefix + d->textFromValue(d->minimum) + d->suffix + QLatin1Char(' '); + s.truncate(18); + w = qMax(w, fm.width(s)); + s = d->prefix + d->textFromValue(d->maximum) + d->suffix + QLatin1Char(' '); + s.truncate(18); + w = qMax(w, fm.width(s)); + if (d->specialValueText.size()) { + s = d->specialValueText; + w = qMax(w, fm.width(s)); + } + w += 2; // cursor blinking space + + QStyleOptionSpinBox opt; + initStyleOption(&opt); + QSize hint(w, h); + QSize extra(35, 6); + opt.rect.setSize(hint + extra); + extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, this).size(); + // get closer to final result by repeating the calculation + opt.rect.setSize(hint + extra); + extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, this).size(); + hint += extra; + + opt.rect = rect(); + d->cachedSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this) + .expandedTo(QApplication::globalStrut()); + } + return d->cachedSizeHint; +} + +/*! + \reimp +*/ + +QSize QAbstractSpinBox::minimumSizeHint() const +{ + Q_D(const QAbstractSpinBox); + if (d->cachedMinimumSizeHint.isEmpty()) { + ensurePolished(); + + const QFontMetrics fm(fontMetrics()); + int h = d->edit->minimumSizeHint().height(); + int w = fm.width(QLatin1String("1000")); + w += 2; // cursor blinking space + + QStyleOptionSpinBox opt; + initStyleOption(&opt); + QSize hint(w, h); + QSize extra(35, 6); + opt.rect.setSize(hint + extra); + extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, this).size(); + // get closer to final result by repeating the calculation + opt.rect.setSize(hint + extra); + extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, this).size(); + hint += extra; + + opt.rect = rect(); + + d->cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this) + .expandedTo(QApplication::globalStrut()); + } + return d->cachedMinimumSizeHint; +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::paintEvent(QPaintEvent *) +{ + QStyleOptionSpinBox opt; + initStyleOption(&opt); + QStylePainter p(this); + p.drawComplexControl(QStyle::CC_SpinBox, opt); +} + +/*! + \reimp + + This function handles keyboard input. + + The following keys are handled specifically: + \table + \row \i Enter/Return + \i This will reinterpret the text and emit a signal even if the value has not changed + since last time a signal was emitted. + \row \i Up + \i This will invoke stepBy(1) + \row \i Down + \i This will invoke stepBy(-1) + \row \i Page up + \i This will invoke stepBy(10) + \row \i Page down + \i This will invoke stepBy(-10) + \endtable +*/ + + +void QAbstractSpinBox::keyPressEvent(QKeyEvent *event) +{ + Q_D(QAbstractSpinBox); + + if (!event->text().isEmpty() && d->edit->cursorPosition() < d->prefix.size()) + d->edit->setCursorPosition(d->prefix.size()); + + int steps = 1; + switch (event->key()) { + case Qt::Key_PageUp: + case Qt::Key_PageDown: + steps *= 10; + case Qt::Key_Up: + case Qt::Key_Down: { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + // Reserve up/down for nav - use left/right for edit. + if (!hasEditFocus() && (event->key() == Qt::Key_Up + || event->key() == Qt::Key_Down)) { + event->ignore(); + return; + } + } +#endif + event->accept(); + const bool up = (event->key() == Qt::Key_PageUp || event->key() == Qt::Key_Up); + if (!(stepEnabled() & (up ? StepUpEnabled : StepDownEnabled))) + return; + if (!up) + steps *= -1; + if (style()->styleHint(QStyle::SH_SpinBox_AnimateButton, 0, this)) { + d->buttonState = (Keyboard | (up ? Up : Down)); + } + stepBy(steps); + return; + } +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Left: + case Qt::Key_Right: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + event->ignore(); + return; + } + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + event->ignore(); + return; + } + break; +#endif + case Qt::Key_Enter: + case Qt::Key_Return: + d->edit->d_func()->modifiedState = d->edit->d_func()->undoState = 0; + d->interpret(d->keyboardTracking ? AlwaysEmit : EmitIfChanged); + selectAll(); + event->ignore(); + emit editingFinished(); + return; + +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + // Toggles between left/right moving cursor and inc/dec. + setEditFocus(!hasEditFocus()); + } + return; +#endif + +#ifdef Q_WS_X11 // only X11 + case Qt::Key_U: + if (event->modifiers() & Qt::ControlModifier) { + event->accept(); + if (!isReadOnly()) + clear(); + return; + } + break; +#endif + + case Qt::Key_End: + case Qt::Key_Home: + if (event->modifiers() & Qt::ShiftModifier) { + int currentPos = d->edit->cursorPosition(); + const QString text = d->edit->displayText(); + if (event->key() == Qt::Key_End) { + if ((currentPos == 0 && !d->prefix.isEmpty()) || text.size() - d->suffix.size() <= currentPos) { + break; // let lineedit handle this + } else { + d->edit->setSelection(currentPos, text.size() - d->suffix.size() - currentPos); + } + } else { + if ((currentPos == text.size() && !d->suffix.isEmpty()) || currentPos <= d->prefix.size()) { + break; // let lineedit handle this + } else { + d->edit->setSelection(currentPos, d->prefix.size() - currentPos); + } + } + event->accept(); + return; + } + break; + + default: +#ifndef QT_NO_SHORTCUT + if (event == QKeySequence::SelectAll) { + selectAll(); + event->accept(); + return; + } +#endif + break; + } + + d->edit->event(event); + if (!isVisible()) + d->ignoreUpdateEdit = true; +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QAbstractSpinBox); + + if (d->buttonState & Keyboard && !event->isAutoRepeat() + && style()->styleHint(QStyle::SH_SpinBox_AnimateButton, 0, this)) { + d->reset(); + } else { + d->edit->event(event); + } +} + +/*! + \reimp +*/ + +#ifndef QT_NO_WHEELEVENT +void QAbstractSpinBox::wheelEvent(QWheelEvent *event) +{ + const int steps = (event->delta() > 0 ? 1 : -1); + if (stepEnabled() & (steps > 0 ? StepUpEnabled : StepDownEnabled)) + stepBy(event->modifiers() & Qt::ControlModifier ? steps * 10 : steps); + event->accept(); +} +#endif + + +/*! + \reimp +*/ +void QAbstractSpinBox::focusInEvent(QFocusEvent *event) +{ + Q_D(QAbstractSpinBox); + + d->edit->event(event); + if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason) { + selectAll(); + } + QWidget::focusInEvent(event); +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::focusOutEvent(QFocusEvent *event) +{ + Q_D(QAbstractSpinBox); + + if (d->pendingEmit) + d->interpret(EmitIfChanged); + + d->reset(); + d->edit->event(event); + d->updateEdit(); + QWidget::focusOutEvent(event); + +#ifdef QT_KEYPAD_NAVIGATION + // editingFinished() is already emitted on LeaveEditFocus + if (!QApplication::keypadNavigationEnabled()) +#endif + emit editingFinished(); +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::closeEvent(QCloseEvent *event) +{ + Q_D(QAbstractSpinBox); + + d->reset(); + if (d->pendingEmit) + d->interpret(EmitIfChanged); + QWidget::closeEvent(event); +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::hideEvent(QHideEvent *event) +{ + Q_D(QAbstractSpinBox); + d->reset(); + if (d->pendingEmit) + d->interpret(EmitIfChanged); + QWidget::hideEvent(event); +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::timerEvent(QTimerEvent *event) +{ + Q_D(QAbstractSpinBox); + + bool doStep = false; + if (event->timerId() == d->spinClickThresholdTimerId) { + killTimer(d->spinClickThresholdTimerId); + d->spinClickThresholdTimerId = -1; + d->spinClickTimerId = startTimer(d->spinClickTimerInterval); + doStep = true; + } else if (event->timerId() == d->spinClickTimerId) { + if (d->accelerate) { + d->acceleration = d->acceleration + (int)(d->spinClickTimerInterval * 0.05); + if (d->spinClickTimerInterval - d->acceleration >= 10) { + killTimer(d->spinClickTimerId); + d->spinClickTimerId = startTimer(d->spinClickTimerInterval - d->acceleration); + } + } + doStep = true; + } + + if (doStep) { + const StepEnabled st = stepEnabled(); + if (d->buttonState & Up) { + if (!(st & StepUpEnabled)) { + d->reset(); + } else { + stepBy(1); + } + } else if (d->buttonState & Down) { + if (!(st & StepDownEnabled)) { + d->reset(); + } else { + stepBy(-1); + } + } + return; + } + QWidget::timerEvent(event); + return; +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::contextMenuEvent(QContextMenuEvent *event) +{ +#ifdef QT_NO_CONTEXTMENU + Q_UNUSED(event); +#else + Q_D(QAbstractSpinBox); + + d->reset(); + QPointer<QMenu> menu = d->edit->createStandardContextMenu(); + + QAction *selAll = new QAction(tr("&Select All"), menu); + menu->insertAction(d->edit->d_func()->selectAllAction, + selAll); + menu->removeAction(d->edit->d_func()->selectAllAction); + menu->addSeparator(); + const uint se = stepEnabled(); + QAction *up = menu->addAction(tr("&Step up")); + up->setEnabled(se & StepUpEnabled); + QAction *down = menu->addAction(tr("Step &down")); + down->setEnabled(se & StepDownEnabled); + menu->addSeparator(); + + const QPointer<QAbstractSpinBox> that = this; + const QPoint pos = (event->reason() == QContextMenuEvent::Mouse) + ? event->globalPos() : mapToGlobal(QPoint(event->pos().x(), 0)) + QPoint(width() / 2, height() / 2); + const QAction *action = menu->exec(pos); + delete static_cast<QMenu *>(menu); + if (that && action) { + if (action == up) { + stepBy(1); + } else if (action == down) { + stepBy(-1); + } else if (action == selAll) { + selectAll(); + } + } + event->accept(); +#endif // QT_NO_CONTEXTMENU +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QAbstractSpinBox); + + d->updateHoverControl(event->pos()); + + // If we have a timer ID, update the state + if (d->spinClickTimerId != -1 && d->buttonSymbols != NoButtons) { + const StepEnabled se = stepEnabled(); + if ((se & StepUpEnabled) && d->hoverControl == QStyle::SC_SpinBoxUp) + d->updateState(true); + else if ((se & StepDownEnabled) && d->hoverControl == QStyle::SC_SpinBoxDown) + d->updateState(false); + else + d->reset(); + event->accept(); + } +} + +/*! + \reimp +*/ + +void QAbstractSpinBox::mousePressEvent(QMouseEvent *event) +{ + Q_D(QAbstractSpinBox); + + if (event->button() != Qt::LeftButton || d->buttonState != None) { + return; + } + + d->updateHoverControl(event->pos()); + event->accept(); + + const StepEnabled se = (d->buttonSymbols == NoButtons) ? StepEnabled(StepNone) : stepEnabled(); + if ((se & StepUpEnabled) && d->hoverControl == QStyle::SC_SpinBoxUp) { + d->updateState(true); + } else if ((se & StepDownEnabled) && d->hoverControl == QStyle::SC_SpinBoxDown) { + d->updateState(false); + } else { + event->ignore(); + } +} + +/*! + \reimp +*/ +void QAbstractSpinBox::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QAbstractSpinBox); + + if ((d->buttonState & Mouse) != 0) + d->reset(); + event->accept(); +} + +// --- QAbstractSpinBoxPrivate --- + +/*! + \internal + Constructs a QAbstractSpinBoxPrivate object +*/ + +QAbstractSpinBoxPrivate::QAbstractSpinBoxPrivate() + : edit(0), type(QVariant::Invalid), spinClickTimerId(-1), + spinClickTimerInterval(100), spinClickThresholdTimerId(-1), spinClickThresholdTimerInterval(-1), + buttonState(None), cachedText(QLatin1String("\x01")), cachedState(QValidator::Invalid), + pendingEmit(false), readOnly(false), wrapping(false), + ignoreCursorPositionChanged(false), frame(true), accelerate(false), keyboardTracking(true), + cleared(false), ignoreUpdateEdit(false), correctionMode(QAbstractSpinBox::CorrectToPreviousValue), + acceleration(0), hoverControl(QStyle::SC_None), buttonSymbols(QAbstractSpinBox::UpDownArrows), validator(0) +{ +} + +/* + \internal + Called when the QAbstractSpinBoxPrivate is destroyed +*/ +QAbstractSpinBoxPrivate::~QAbstractSpinBoxPrivate() +{ +} + +/*! + \internal + Updates the old and new hover control. Does nothing if the hover + control has not changed. +*/ +bool QAbstractSpinBoxPrivate::updateHoverControl(const QPoint &pos) +{ + Q_Q(QAbstractSpinBox); + QRect lastHoverRect = hoverRect; + QStyle::SubControl lastHoverControl = hoverControl; + bool doesHover = q->testAttribute(Qt::WA_Hover); + if (lastHoverControl != newHoverControl(pos) && doesHover) { + q->update(lastHoverRect); + q->update(hoverRect); + return true; + } + return !doesHover; +} + +/*! + \internal + Returns the hover control at \a pos. + This will update the hoverRect and hoverControl. +*/ +QStyle::SubControl QAbstractSpinBoxPrivate::newHoverControl(const QPoint &pos) +{ + Q_Q(QAbstractSpinBox); + + QStyleOptionSpinBox opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + hoverControl = q->style()->hitTestComplexControl(QStyle::CC_SpinBox, &opt, pos, q); + hoverRect = q->style()->subControlRect(QStyle::CC_SpinBox, &opt, hoverControl, q); + return hoverControl; +} + +/*! + \internal + Strips any prefix/suffix from \a text. +*/ + +QString QAbstractSpinBoxPrivate::stripped(const QString &t, int *pos) const +{ + QString text = t; + if (specialValueText.size() == 0 || text != specialValueText) { + int from = 0; + int size = text.size(); + bool changed = false; + if (prefix.size() && text.startsWith(prefix)) { + from += prefix.size(); + size -= from; + changed = true; + } + if (suffix.size() && text.endsWith(suffix)) { + size -= suffix.size(); + changed = true; + } + if (changed) + text = text.mid(from, size); + } + + const int s = text.size(); + text = text.trimmed(); + if (pos) + (*pos) -= (s - text.size()); + return text; + +} + +void QAbstractSpinBoxPrivate::updateEditFieldGeometry() +{ + Q_Q(QAbstractSpinBox); + QStyleOptionSpinBox opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_SpinBoxEditField; + edit->setGeometry(q->style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, q)); +} +/*! + \internal + Returns true if a specialValueText has been set and the current value is minimum. +*/ + +bool QAbstractSpinBoxPrivate::specialValue() const +{ + return (value == minimum && !specialValueText.isEmpty()); +} + +/*! + \internal Virtual function that emits signals when the value + changes. Reimplemented in the different subclasses. +*/ + +void QAbstractSpinBoxPrivate::emitSignals(EmitPolicy, const QVariant &) +{ +} + +/*! + \internal + + Slot connected to the line edit's textChanged(const QString &) + signal. +*/ + +void QAbstractSpinBoxPrivate::_q_editorTextChanged(const QString &t) +{ + Q_Q(QAbstractSpinBox); + + if (keyboardTracking) { + QString tmp = t; + int pos = edit->cursorPosition(); + QValidator::State state = q->validate(tmp, pos); + if (state == QValidator::Acceptable) { + const QVariant v = valueFromText(tmp); + setValue(v, EmitIfChanged, tmp != t); + pendingEmit = false; + } else { + pendingEmit = true; + } + } else { + pendingEmit = true; + } +} + +/*! + \internal + + Virtual slot connected to the line edit's + cursorPositionChanged(int, int) signal. Will move the cursor to a + valid position if the new one is invalid. E.g. inside the prefix. + Reimplemented in Q[Date|Time|DateTime]EditPrivate to account for + the different sections etc. +*/ + +void QAbstractSpinBoxPrivate::_q_editorCursorPositionChanged(int oldpos, int newpos) +{ + if (!edit->hasSelectedText() && !ignoreCursorPositionChanged && !specialValue()) { + ignoreCursorPositionChanged = true; + + bool allowSelection = true; + int pos = -1; + if (newpos < prefix.size() && newpos != 0) { + if (oldpos == 0) { + allowSelection = false; + pos = prefix.size(); + } else { + pos = oldpos; + } + } else if (newpos > edit->text().size() - suffix.size() + && newpos != edit->text().size()) { + if (oldpos == edit->text().size()) { + pos = edit->text().size() - suffix.size(); + allowSelection = false; + } else { + pos = edit->text().size(); + } + } + if (pos != -1) { + const int selSize = edit->selectionStart() >= 0 && allowSelection + ? (edit->selectedText().size() + * (newpos < pos ? -1 : 1)) - newpos + pos + : 0; + + const bool wasBlocked = edit->blockSignals(true); + if (selSize != 0) { + edit->setSelection(pos - selSize, selSize); + } else { + edit->setCursorPosition(pos); + } + edit->blockSignals(wasBlocked); + } + ignoreCursorPositionChanged = false; + } +} + +/*! + \internal + + Initialises the QAbstractSpinBoxPrivate object. +*/ + +void QAbstractSpinBoxPrivate::init() +{ + Q_Q(QAbstractSpinBox); + + q->setLineEdit(new QLineEdit(q)); + edit->setObjectName(QLatin1String("qt_spinbox_lineedit")); + validator = new QSpinBoxValidator(q, this); + edit->setValidator(validator); + + QStyleOptionSpinBox opt; + q->initStyleOption(&opt); + spinClickTimerInterval = q->style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, &opt, q); + spinClickThresholdTimerInterval = q->style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, &opt, q); + q->setFocusPolicy(Qt::WheelFocus); + q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::SpinBox)); + q->setAttribute(Qt::WA_InputMethodEnabled); + + q->setAttribute(Qt::WA_MacShowFocusRect); +} + +/*! + \internal + + Resets the state of the spinbox. E.g. the state is set to + (Keyboard|Up) if Key up is currently pressed. +*/ + +void QAbstractSpinBoxPrivate::reset() +{ + Q_Q(QAbstractSpinBox); + + buttonState = None; + if (q) { + if (spinClickTimerId != -1) + q->killTimer(spinClickTimerId); + if (spinClickThresholdTimerId != -1) + q->killTimer(spinClickThresholdTimerId); + spinClickTimerId = spinClickThresholdTimerId = -1; + acceleration = 0; + q->update(); + } +} + +/*! + \internal + + Updates the state of the spinbox. +*/ + +void QAbstractSpinBoxPrivate::updateState(bool up) +{ + Q_Q(QAbstractSpinBox); + if ((up && (buttonState & Up)) || (!up && (buttonState & Down))) + return; + reset(); + if (q && (q->stepEnabled() & (up ? QAbstractSpinBox::StepUpEnabled + : QAbstractSpinBox::StepDownEnabled))) { + spinClickThresholdTimerId = q->startTimer(spinClickThresholdTimerInterval); + buttonState = (up ? (Mouse | Up) : (Mouse | Down)); + q->stepBy(up ? 1 : -1); + } +} + + +/*! + Initialize \a option with the values from this QSpinBox. This method + is useful for subclasses when they need a QStyleOptionSpinBox, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QAbstractSpinBox::initStyleOption(QStyleOptionSpinBox *option) const +{ + if (!option) + return; + + Q_D(const QAbstractSpinBox); + option->initFrom(this); + option->activeSubControls = QStyle::SC_None; + option->buttonSymbols = d->buttonSymbols; + option->subControls = QStyle::SC_SpinBoxFrame; + if (d->buttonSymbols != QAbstractSpinBox::NoButtons) { + option->subControls |= QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; + if (d->buttonState & Up) { + option->activeSubControls = QStyle::SC_SpinBoxUp; + } else if (d->buttonState & Down) { + option->activeSubControls = QStyle::SC_SpinBoxDown; + } + } + + if (d->buttonState) { + option->state |= QStyle::State_Sunken; + } else { + option->activeSubControls = d->hoverControl; + } + + option->stepEnabled = style()->styleHint(QStyle::SH_SpinControls_DisableOnBounds) + ? stepEnabled() + : (QAbstractSpinBox::StepDownEnabled|QAbstractSpinBox::StepUpEnabled); + + option->frame = d->frame; +} + +/*! + \internal + + Bounds \a val to be within minimum and maximum. Also tries to be + clever about setting it at min and max depending on what it was + and what direction it was changed etc. +*/ + +QVariant QAbstractSpinBoxPrivate::bound(const QVariant &val, const QVariant &old, int steps) const +{ + QVariant v = val; + if (!wrapping || steps == 0 || old.isNull()) { + if (variantCompare(v, minimum) < 0) { + v = wrapping ? maximum : minimum; + } + if (variantCompare(v, maximum) > 0) { + v = wrapping ? minimum : maximum; + } + } else { + const bool wasMin = old == minimum; + const bool wasMax = old == maximum; + const int oldcmp = variantCompare(v, old); + const int maxcmp = variantCompare(v, maximum); + const int mincmp = variantCompare(v, minimum); + const bool wrapped = (oldcmp > 0 && steps < 0) || (oldcmp < 0 && steps > 0); + if (maxcmp > 0) { + v = ((wasMax && !wrapped && steps > 0) || (steps < 0 && !wasMin && wrapped)) + ? minimum : maximum; + } else if (wrapped && (maxcmp > 0 || mincmp < 0)) { + v = ((wasMax && steps > 0) || (!wasMin && steps < 0)) ? minimum : maximum; + } else if (mincmp < 0) { + v = (!wasMax && !wasMin ? minimum : maximum); + } + } + + return v; +} + +/*! + \internal + + Sets the value of the spin box to \a val. Depending on the value + of \a ep it will also emit signals. +*/ + +void QAbstractSpinBoxPrivate::setValue(const QVariant &val, EmitPolicy ep, + bool doUpdate) +{ + Q_Q(QAbstractSpinBox); + const QVariant old = value; + value = bound(val); + pendingEmit = false; + cleared = false; + if (doUpdate) { + updateEdit(); + } + q->update(); + + if (ep == AlwaysEmit || (ep == EmitIfChanged && old != value)) { + emitSignals(ep, old); + } +} + +/*! + \internal + + Updates the line edit to reflect the current value of the spin box. +*/ + +void QAbstractSpinBoxPrivate::updateEdit() +{ + Q_Q(QAbstractSpinBox); + if (type == QVariant::Invalid) + return; + const QString newText = specialValue() ? specialValueText : prefix + textFromValue(value) + suffix; + if (newText == edit->displayText() || cleared) + return; + + const bool empty = edit->text().isEmpty(); + int cursor = edit->cursorPosition(); + int selsize = edit->selectedText().size(); + const bool sb = edit->blockSignals(true); + edit->setText(newText); + + if (!specialValue()) { + cursor = qBound(prefix.size(), cursor, edit->displayText().size() - suffix.size()); + + if (selsize > 0) { + edit->setSelection(cursor, selsize); + } else { + edit->setCursorPosition(empty ? prefix.size() : cursor); + } + } + edit->blockSignals(sb); + q->update(); +} + +/*! + \internal + + Convenience function to set min/max values. +*/ + +void QAbstractSpinBoxPrivate::setRange(const QVariant &min, const QVariant &max) +{ + clearCache(); + minimum = min; + maximum = (variantCompare(min, max) < 0 ? max : min); + cachedSizeHint = QSize(); // minimumSizeHint doesn't care about min/max + + reset(); + if (!(bound(value) == value)) { + setValue(bound(value), EmitIfChanged); + } else if (value == minimum && !specialValueText.isEmpty()) { + updateEdit(); + } +} + +/*! + \internal + + Convenience function to get a variant of the right type. +*/ + +QVariant QAbstractSpinBoxPrivate::getZeroVariant() const +{ + QVariant ret; + switch (type) { + case QVariant::Int: ret = QVariant((int)0); break; + case QVariant::Double: ret = QVariant((double)0.0); break; + default: break; + } + return ret; +} + +/*! + \internal + + Virtual method called that calls the public textFromValue() + functions in the subclasses. Needed to change signature from + QVariant to int/double/QDateTime etc. Used when needing to display + a value textually. + + This method is reimeplemented in the various subclasses. +*/ + +QString QAbstractSpinBoxPrivate::textFromValue(const QVariant &) const +{ + return QString(); +} + +/*! + \internal + + Virtual method called that calls the public valueFromText() + functions in the subclasses. Needed to change signature from + QVariant to int/double/QDateTime etc. Used when needing to + interpret a string as another type. + + This method is reimeplemented in the various subclasses. +*/ + +QVariant QAbstractSpinBoxPrivate::valueFromText(const QString &) const +{ + return QVariant(); +} +/*! + \internal + + Interprets text and emits signals. Called when the spinbox needs + to interpret the text on the lineedit. +*/ + +void QAbstractSpinBoxPrivate::interpret(EmitPolicy ep) +{ + Q_Q(QAbstractSpinBox); + if (type == QVariant::Invalid || cleared) + return; + + QVariant v = getZeroVariant(); + bool doInterpret = true; + QString tmp = edit->displayText(); + int pos = edit->cursorPosition(); + const int oldpos = pos; + + if (q->validate(tmp, pos) != QValidator::Acceptable) { + const QString copy = tmp; + q->fixup(tmp); + QASBDEBUG() << "QAbstractSpinBoxPrivate::interpret() text '" + << edit->displayText() + << "' >> '" << copy << "'" + << "' >> '" << tmp << "'"; + + doInterpret = tmp != copy && (q->validate(tmp, pos) == QValidator::Acceptable); + if (!doInterpret) { + v = (correctionMode == QAbstractSpinBox::CorrectToNearestValue + ? variantBound(minimum, v, maximum) : value); + } + } + if (doInterpret) { + v = valueFromText(tmp); + } + clearCache(); + setValue(v, ep, true); + if (oldpos != pos) + edit->setCursorPosition(pos); +} + +void QAbstractSpinBoxPrivate::clearCache() const +{ + cachedText.clear(); + cachedValue.clear(); + cachedState = QValidator::Acceptable; +} + + +// --- QSpinBoxValidator --- + +/*! + \internal + Constructs a QSpinBoxValidator object +*/ + +QSpinBoxValidator::QSpinBoxValidator(QAbstractSpinBox *qp, QAbstractSpinBoxPrivate *dp) + : QValidator(qp), qptr(qp), dptr(dp) +{ + setObjectName(QLatin1String("qt_spinboxvalidator")); +} + +/*! + \internal + + Checks for specialValueText, prefix, suffix and calls + the virtual QAbstractSpinBox::validate function. +*/ + +QValidator::State QSpinBoxValidator::validate(QString &input, int &pos) const +{ + if (dptr->specialValueText.size() > 0 && input == dptr->specialValueText) + return QValidator::Acceptable; + + if (!dptr->prefix.isEmpty() && !input.startsWith(dptr->prefix)) + input.prepend(dptr->prefix); + + if (!dptr->suffix.isEmpty() && !input.endsWith(dptr->suffix)) + input.append(dptr->suffix); + + return qptr->validate(input, pos); +} +/*! + \internal + Calls the virtual QAbstractSpinBox::fixup function. +*/ + +void QSpinBoxValidator::fixup(QString &input) const +{ + qptr->fixup(input); +} + +// --- global --- + +/*! + \internal + Adds two variants together and returns the result. +*/ + +QVariant operator+(const QVariant &arg1, const QVariant &arg2) +{ + QVariant ret; + if (arg1.type() != arg2.type()) + qWarning("QAbstractSpinBox: Internal error: Different types (%s vs %s) (%s:%d)", + arg1.typeName(), arg2.typeName(), __FILE__, __LINE__); + switch (arg1.type()) { + case QVariant::Int: ret = QVariant(arg1.toInt() + arg2.toInt()); break; + case QVariant::Double: ret = QVariant(arg1.toDouble() + arg2.toDouble()); break; + case QVariant::DateTime: { + QDateTime a2 = arg2.toDateTime(); + QDateTime a1 = arg1.toDateTime().addDays(QDATETIMEEDIT_DATETIME_MIN.daysTo(a2)); + a1.setTime(a1.time().addMSecs(QTime().msecsTo(a2.time()))); + ret = QVariant(a1); + } + default: break; + } + return ret; +} + + +/*! + \internal + Subtracts two variants and returns the result. +*/ + +QVariant operator-(const QVariant &arg1, const QVariant &arg2) +{ + QVariant ret; + if (arg1.type() != arg2.type()) + qWarning("QAbstractSpinBox: Internal error: Different types (%s vs %s) (%s:%d)", + arg1.typeName(), arg2.typeName(), __FILE__, __LINE__); + switch (arg1.type()) { + case QVariant::Int: ret = QVariant(arg1.toInt() - arg2.toInt()); break; + case QVariant::Double: ret = QVariant(arg1.toDouble() - arg2.toDouble()); break; + case QVariant::DateTime: { + QDateTime a1 = arg1.toDateTime(); + QDateTime a2 = arg2.toDateTime(); + int days = a2.daysTo(a1); + int secs = a2.secsTo(a1); + int msecs = qMax(0, a1.time().msec() - a2.time().msec()); + if (days < 0 || secs < 0 || msecs < 0) { + ret = arg1; + } else { + QDateTime dt = a2.addDays(days).addSecs(secs); + if (msecs > 0) + dt.setTime(dt.time().addMSecs(msecs)); + ret = QVariant(dt); + } + } + default: break; + } + return ret; +} + +/*! + \internal + Multiplies \a arg1 by \a multiplier and returns the result. +*/ + +QVariant operator*(const QVariant &arg1, double multiplier) +{ + QVariant ret; + + switch (arg1.type()) { + case QVariant::Int: ret = QVariant((int)(arg1.toInt() * multiplier)); break; + case QVariant::Double: ret = QVariant(arg1.toDouble() * multiplier); break; + case QVariant::DateTime: { + double days = QDATETIMEEDIT_DATE_MIN.daysTo(arg1.toDateTime().date()) * multiplier; + int daysInt = (int)days; + days -= daysInt; + long msecs = (long)((QDATETIMEEDIT_TIME_MIN.msecsTo(arg1.toDateTime().time()) * multiplier) + + (days * (24 * 3600 * 1000))); + ret = QDateTime(QDate().addDays(int(days)), QTime().addMSecs(msecs)); + break; + } + default: ret = arg1; break; + } + + return ret; +} + + + +double operator/(const QVariant &arg1, const QVariant &arg2) +{ + double a1 = 0; + double a2 = 0; + + switch (arg1.type()) { + case QVariant::Int: + a1 = (double)arg1.toInt(); + a2 = (double)arg2.toInt(); + break; + case QVariant::Double: + a1 = arg1.toDouble(); + a2 = arg2.toDouble(); + break; + case QVariant::DateTime: + a1 = QDATETIMEEDIT_DATE_MIN.daysTo(arg1.toDate()); + a2 = QDATETIMEEDIT_DATE_MIN.daysTo(arg2.toDate()); + a1 += (double)QDATETIMEEDIT_TIME_MIN.msecsTo(arg1.toDateTime().time()) / (long)(3600 * 24 * 1000); + a2 += (double)QDATETIMEEDIT_TIME_MIN.msecsTo(arg2.toDateTime().time()) / (long)(3600 * 24 * 1000); + default: break; + } + + return (a1 != 0 && a2 != 0) ? (a1 / a2) : 0.0; +} + +int QAbstractSpinBoxPrivate::variantCompare(const QVariant &arg1, const QVariant &arg2) +{ + switch (arg2.type()) { + case QVariant::Date: + Q_ASSERT_X(arg1.type() == QVariant::Date, "QAbstractSpinBoxPrivate::variantCompare", + qPrintable(QString::fromAscii("Internal error 1 (%1)"). + arg(QString::fromAscii(arg1.typeName())))); + if (arg1.toDate() == arg2.toDate()) { + return 0; + } else if (arg1.toDate() < arg2.toDate()) { + return -1; + } else { + return 1; + } + case QVariant::Time: + Q_ASSERT_X(arg1.type() == QVariant::Time, "QAbstractSpinBoxPrivate::variantCompare", + qPrintable(QString::fromAscii("Internal error 2 (%1)"). + arg(QString::fromAscii(arg1.typeName())))); + if (arg1.toTime() == arg2.toTime()) { + return 0; + } else if (arg1.toTime() < arg2.toTime()) { + return -1; + } else { + return 1; + } + + + case QVariant::DateTime: + if (arg1.toDateTime() == arg2.toDateTime()) { + return 0; + } else if (arg1.toDateTime() < arg2.toDateTime()) { + return -1; + } else { + return 1; + } + case QVariant::Int: + if (arg1.toInt() == arg2.toInt()) { + return 0; + } else if (arg1.toInt() < arg2.toInt()) { + return -1; + } else { + return 1; + } + case QVariant::Double: + if (arg1.toDouble() == arg2.toDouble()) { + return 0; + } else if (arg1.toDouble() < arg2.toDouble()) { + return -1; + } else { + return 1; + } + case QVariant::Invalid: + if (arg2.type() == QVariant::Invalid) + return 0; + default: + Q_ASSERT_X(0, "QAbstractSpinBoxPrivate::variantCompare", + qPrintable(QString::fromAscii("Internal error 3 (%1 %2)"). + arg(QString::fromAscii(arg1.typeName())). + arg(QString::fromAscii(arg2.typeName())))); + } + return -2; +} + +QVariant QAbstractSpinBoxPrivate::variantBound(const QVariant &min, + const QVariant &value, + const QVariant &max) +{ + Q_ASSERT(variantCompare(min, max) <= 0); + if (variantCompare(min, value) < 0) { + const int compMax = variantCompare(value, max); + return (compMax < 0 ? value : max); + } else { + return min; + } +} + + +QT_END_NAMESPACE + +#include "moc_qabstractspinbox.cpp" + +#endif // QT_NO_SPINBOX diff --git a/src/gui/widgets/qabstractspinbox.h b/src/gui/widgets/qabstractspinbox.h new file mode 100644 index 0000000..4e7fc3f --- /dev/null +++ b/src/gui/widgets/qabstractspinbox.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSPINBOX_H +#define QABSTRACTSPINBOX_H + +#include <QtGui/qwidget.h> +#include <QtGui/qvalidator.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SPINBOX + +class QLineEdit; + +class QAbstractSpinBoxPrivate; +class QStyleOptionSpinBox; + +class Q_GUI_EXPORT QAbstractSpinBox : public QWidget +{ + Q_OBJECT + + Q_ENUMS(ButtonSymbols) + Q_ENUMS(CorrectionMode) + Q_PROPERTY(bool wrapping READ wrapping WRITE setWrapping) + Q_PROPERTY(bool frame READ hasFrame WRITE setFrame) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(ButtonSymbols buttonSymbols READ buttonSymbols WRITE setButtonSymbols) + Q_PROPERTY(QString specialValueText READ specialValueText WRITE setSpecialValueText) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(bool accelerated READ isAccelerated WRITE setAccelerated) + Q_PROPERTY(CorrectionMode correctionMode READ correctionMode WRITE setCorrectionMode) + Q_PROPERTY(bool acceptableInput READ hasAcceptableInput) + Q_PROPERTY(bool keyboardTracking READ keyboardTracking WRITE setKeyboardTracking) +public: + explicit QAbstractSpinBox(QWidget *parent = 0); + ~QAbstractSpinBox(); + + enum StepEnabledFlag { StepNone = 0x00, StepUpEnabled = 0x01, + StepDownEnabled = 0x02 }; + Q_DECLARE_FLAGS(StepEnabled, StepEnabledFlag) + + enum ButtonSymbols { UpDownArrows, PlusMinus, NoButtons }; + + ButtonSymbols buttonSymbols() const; + void setButtonSymbols(ButtonSymbols bs); + + enum CorrectionMode { CorrectToPreviousValue, CorrectToNearestValue }; + + void setCorrectionMode(CorrectionMode cm); + CorrectionMode correctionMode() const; + + bool hasAcceptableInput() const; + QString text() const; + + QString specialValueText() const; + void setSpecialValueText(const QString &txt); + + bool wrapping() const; + void setWrapping(bool w); + + void setReadOnly(bool r); + bool isReadOnly() const; + + void setKeyboardTracking(bool kt); + bool keyboardTracking() const; + + void setAlignment(Qt::Alignment flag); + Qt::Alignment alignment() const; + + void setFrame(bool); + bool hasFrame() const; + + void setAccelerated(bool on); + bool isAccelerated() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + void interpretText(); + bool event(QEvent *event); + + virtual QValidator::State validate(QString &input, int &pos) const; + virtual void fixup(QString &input) const; + + virtual void stepBy(int steps); +public Q_SLOTS: + void stepUp(); + void stepDown(); + void selectAll(); + virtual void clear(); +protected: + void resizeEvent(QResizeEvent *event); + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + void wheelEvent(QWheelEvent *event); + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + void contextMenuEvent(QContextMenuEvent *event); + void changeEvent(QEvent *event); + void closeEvent(QCloseEvent *event); + void hideEvent(QHideEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void timerEvent(QTimerEvent *event); + void paintEvent(QPaintEvent *event); + void showEvent(QShowEvent *event); + void initStyleOption(QStyleOptionSpinBox *option) const; + + QLineEdit *lineEdit() const; + void setLineEdit(QLineEdit *edit); + + virtual StepEnabled stepEnabled() const; +Q_SIGNALS: + void editingFinished(); +protected: + QAbstractSpinBox(QAbstractSpinBoxPrivate &dd, QWidget *parent = 0); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_editorTextChanged(const QString &)) + Q_PRIVATE_SLOT(d_func(), void _q_editorCursorPositionChanged(int, int)) + + Q_DECLARE_PRIVATE(QAbstractSpinBox) + Q_DISABLE_COPY(QAbstractSpinBox) +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractSpinBox::StepEnabled) + +#endif // QT_NO_SPINBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTSPINBOX_H diff --git a/src/gui/widgets/qabstractspinbox_p.h b/src/gui/widgets/qabstractspinbox_p.h new file mode 100644 index 0000000..5f7f896 --- /dev/null +++ b/src/gui/widgets/qabstractspinbox_p.h @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSPINBOX_P_H +#define QABSTRACTSPINBOX_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 "QtGui/qabstractspinbox.h" + +#ifndef QT_NO_SPINBOX + +#include "QtGui/qlineedit.h" +#include "QtGui/qstyleoption.h" +#include "QtGui/qvalidator.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qvariant.h" +#include "private/qwidget_p.h" +#include "private/qdatetime_p.h" + +QT_BEGIN_NAMESPACE + +QVariant operator+(const QVariant &arg1, const QVariant &arg2); +QVariant operator-(const QVariant &arg1, const QVariant &arg2); +QVariant operator*(const QVariant &arg1, double multiplier); +double operator/(const QVariant &arg1, const QVariant &arg2); + +enum EmitPolicy { + EmitIfChanged, + AlwaysEmit, + NeverEmit +}; + +enum Button { + None = 0x000, + Keyboard = 0x001, + Mouse = 0x002, + Wheel = 0x004, + ButtonMask = 0x008, + Up = 0x010, + Down = 0x020, + DirectionMask = 0x040 +}; +class QSpinBoxValidator; +class QAbstractSpinBoxPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QAbstractSpinBox) +public: + QAbstractSpinBoxPrivate(); + ~QAbstractSpinBoxPrivate(); + + void init(); + void reset(); + void updateState(bool up); + QString stripped(const QString &text, int *pos = 0) const; + bool specialValue() const; + virtual QVariant getZeroVariant() const; + virtual void setRange(const QVariant &min, const QVariant &max); + void setValue(const QVariant &val, EmitPolicy ep, bool updateEdit = true); + virtual QVariant bound(const QVariant &val, const QVariant &old = QVariant(), int steps = 0) const; + virtual void updateEdit(); + + virtual void emitSignals(EmitPolicy ep, const QVariant &old); + virtual void interpret(EmitPolicy ep); + virtual QString textFromValue(const QVariant &n) const; + virtual QVariant valueFromText(const QString &input) const; + + void _q_editorTextChanged(const QString &); + virtual void _q_editorCursorPositionChanged(int oldpos, int newpos); + + virtual QStyle::SubControl newHoverControl(const QPoint &pos); + bool updateHoverControl(const QPoint &pos); + + virtual void clearCache() const; + virtual void updateEditFieldGeometry(); + + static int variantCompare(const QVariant &arg1, const QVariant &arg2); + static QVariant variantBound(const QVariant &min, const QVariant &value, const QVariant &max); + + QLineEdit *edit; + QString prefix, suffix, specialValueText; + QVariant value, minimum, maximum, singleStep; + QVariant::Type type; + int spinClickTimerId, spinClickTimerInterval, spinClickThresholdTimerId, spinClickThresholdTimerInterval; + uint buttonState; + mutable QString cachedText; + mutable QVariant cachedValue; + mutable QValidator::State cachedState; + mutable QSize cachedSizeHint, cachedMinimumSizeHint; + uint pendingEmit : 1; + uint spindownEnabled : 1; + uint spinupEnabled : 1; + uint readOnly : 1; + uint wrapping : 1; + uint ignoreCursorPositionChanged : 1; + uint frame : 1; + uint accelerate : 1; + uint keyboardTracking : 1; + uint cleared : 1; + uint ignoreUpdateEdit : 1; + QAbstractSpinBox::CorrectionMode correctionMode; + int acceleration; + QStyle::SubControl hoverControl; + QRect hoverRect; + QAbstractSpinBox::ButtonSymbols buttonSymbols; + QSpinBoxValidator *validator; +}; + +class QSpinBoxValidator : public QValidator +{ +public: + QSpinBoxValidator(QAbstractSpinBox *qptr, QAbstractSpinBoxPrivate *dptr); + QValidator::State validate(QString &input, int &) const; + void fixup(QString &) const; +private: + QAbstractSpinBox *qptr; + QAbstractSpinBoxPrivate *dptr; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SPINBOX + +#endif // QABSTRACTSPINBOX_P_H diff --git a/src/gui/widgets/qbuttongroup.cpp b/src/gui/widgets/qbuttongroup.cpp new file mode 100644 index 0000000..06bcf1e --- /dev/null +++ b/src/gui/widgets/qbuttongroup.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +/*! + \class QButtonGroup + \brief The QButtonGroup class provides a container to organize groups of + button widgets. + + \ingroup organizers + \ingroup geomanagement + \ingroup appearance + \mainclass + + QButtonGroup provides an abstract container into which button widgets can + be placed. It does not provide a visual representation of this container + (see QGroupBox for a container widget), but instead manages the states of + each of the buttons in the group. + + An \l {QButtonGroup::exclusive} {exclusive} button group switches + off all checkable (toggle) buttons except the one that was + clicked. By default, a button group is exclusive. The buttons in a + button group are usually checkable QPushButton's, \l{QCheckBox}es + (normally for non-exclusive button groups), or \l{QRadioButton}s. + If you create an exclusive button group, you should ensure that + one of the buttons in the group is initially checked; otherwise, + the group will initially be in a state where no buttons are + checked. + + A button is added to the group with addButton(). It can be removed + from the group with removeButton(). If the group is exclusive, the + currently checked button is available as checkedButton(). If a + button is clicked the buttonClicked() signal is emitted. For a + checkable button in an exclusive group this means that the button + was checked. The list of buttons in the group is returned by + buttons(). + + In addition, QButtonGroup can map between integers and buttons. + You can assign an integer id to a button with setId(), and + retrieve it with id(). The id of the currently checked button is + available with checkedId(), and there is an overloaded signal + buttonClicked() which emits the id of the button. The id \c {-1} + is reserved by QButtonGroup to mean "no such button". The purpose + of the mapping mechanism is to simplify the representation of enum + values in a user interface. + + \sa QGroupBox QPushButton, QCheckBox, QRadioButton +*/ + +/*! + \fn QButtonGroup::QButtonGroup(QObject *parent) + + Constructs a new, empty button group with the given \a parent. + + \sa addButton() setExclusive() +*/ + +/*! + \fn QButtonGroup::~QButtonGroup() + + Destroys the button group. +*/ + +/*! + \property QButtonGroup::exclusive + \brief whether the button group is exclusive + + If this property is true then only one button in the group can be checked + at any given time. The user can click on any button to check it, and that + button will replace the existing one as the checked button in the group. + + In an exclusive group, the user cannot uncheck the currently checked button + by clicking on it; instead, another button in the group must be clicked + to set the new checked button for that group. + + By default, this property is true. +*/ + +/*! + \fn void QButtonGroup::buttonClicked(QAbstractButton *button) + + This signal is emitted when the given \a button is clicked. A + button is clicked when it is first pressed and then released, when + its shortcut key is typed, or programmatically when + QAbstractButton::click() or QAbstractButton::animateClick() is + called. + + + \sa checkedButton(), QAbstractButton::clicked() +*/ + +/*! + \fn void QButtonGroup::buttonClicked(int id) + + This signal is emitted when a button with the given \a id is + clicked. + + \sa checkedButton(), QAbstractButton::clicked() +*/ + +/*! + \fn void QButtonGroup::buttonPressed(QAbstractButton *button) + \since 4.2 + + This signal is emitted when the given \a button is pressed down. + + \sa QAbstractButton::pressed() +*/ + +/*! + \fn void QButtonGroup::buttonPressed(int id) + \since 4.2 + + This signal is emitted when a button with the given \a id is + pressed down. + + \sa QAbstractButton::pressed() +*/ + +/*! + \fn void QButtonGroup::buttonReleased(QAbstractButton *button) + \since 4.2 + + This signal is emitted when the given \a button is released. + + \sa QAbstractButton::released() +*/ + +/*! + \fn void QButtonGroup::buttonReleased(int id) + \since 4.2 + + This signal is emitted when a button with the given \a id is + released. + + \sa QAbstractButton::released() +*/ + +/*! + \fn void QButtonGroup::addButton(QAbstractButton *button, int id = -1); + + Adds the given \a button to the button group, with the given \a + id. If \a id is -1 (the default), an id will be assigned to the + button by this QButtonGroup. + + \sa removeButton() buttons() +*/ + +/*! + \fn void QButtonGroup::removeButton(QAbstractButton *button); + + Removes the given \a button from the button group. + + \sa addButton() buttons() +*/ + +/*! + \fn QList<QAbstractButton*> QButtonGroup::buttons() const + + Returns the list of this groups's buttons. This may be empty. + + \sa addButton(), removeButton() +*/ + +/*! + \fn QAbstractButton *QButtonGroup::checkedButton() const; + + Returns the button group's checked button, or 0 if no buttons are + checked. + + \sa buttonClicked() +*/ + +/*! + \fn QAbstractButton *QButtonGroup::button(int id) const; + \since 4.1 + + Returns the button with the specified \a id, or 0 if no such button + exists. +*/ + +/*! + \fn void QButtonGroup::setId(QAbstractButton *button, int id) + \since 4.1 + + Sets the \a id for the specified \a button. Note that \a id can + not be -1. + + \sa id() +*/ + +/*! + \fn int QButtonGroup::id(QAbstractButton *button) const; + \since 4.1 + + Returns the id for the specified \a button, or -1 if no such button + exists. + + + \sa setId() +*/ + +/*! + \fn int QButtonGroup::checkedId() const; + \since 4.1 + + Returns the id of the checkedButton(), or -1 if no button is checked. + + \sa setId() +*/ + + +/*! \fn void QButtonGroup::insert(QAbstractButton *b) + + Use addButton() instead. +*/ + +/*! \fn void QButtonGroup::remove(QAbstractButton *b) + + Use removeButton() instead. +*/ diff --git a/src/gui/widgets/qbuttongroup.h b/src/gui/widgets/qbuttongroup.h new file mode 100644 index 0000000..e0b01ee --- /dev/null +++ b/src/gui/widgets/qbuttongroup.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBUTTONGROUP_H +#define QBUTTONGROUP_H + +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_BUTTONGROUP + +class QAbstractButton; +class QAbstractButtonPrivate; +class QButtonGroupPrivate; + +class Q_GUI_EXPORT QButtonGroup : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool exclusive READ exclusive WRITE setExclusive) +public: + explicit QButtonGroup(QObject *parent = 0); + ~QButtonGroup(); + + void setExclusive(bool); + bool exclusive() const; + + void addButton(QAbstractButton *); + void addButton(QAbstractButton *, int id); + void removeButton(QAbstractButton *); + + QList<QAbstractButton*> buttons() const; + + QAbstractButton * checkedButton() const; + // no setter on purpose! + + QAbstractButton *button(int id) const; + void setId(QAbstractButton *button, int id); + int id(QAbstractButton *button) const; + int checkedId() const; + +Q_SIGNALS: + void buttonClicked(QAbstractButton *); + void buttonClicked(int); + void buttonPressed(QAbstractButton *); + void buttonPressed(int); + void buttonReleased(QAbstractButton *); + void buttonReleased(int); + +#ifdef QT3_SUPPORT +public: + inline QT3_SUPPORT void insert(QAbstractButton *b) { addButton(b); } + inline QT3_SUPPORT void remove(QAbstractButton *b) { removeButton(b); } +#endif + +private: + Q_DISABLE_COPY(QButtonGroup) + Q_DECLARE_PRIVATE(QButtonGroup) + friend class QAbstractButton; + friend class QAbstractButtonPrivate; +}; + +#endif // QT_NO_BUTTONGROUP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QBUTTONGROUP_H diff --git a/src/gui/widgets/qcalendartextnavigator_p.h b/src/gui/widgets/qcalendartextnavigator_p.h new file mode 100644 index 0000000..0aaeffc --- /dev/null +++ b/src/gui/widgets/qcalendartextnavigator_p.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCALENDARTEXTNAVIGATOR_P_H +#define QCALENDARTEXTNAVIGATOR_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 <QtCore/qobject.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qbasictimer.h> + +#ifndef QT_NO_CALENDARWIDGET + +QT_BEGIN_NAMESPACE + +class QLabel; +class QCalendarDateValidator; +class QFrame; + +class QCalendarTextNavigator: public QObject +{ + Q_OBJECT +public: + QCalendarTextNavigator(QObject *parent = 0) + : QObject(parent), m_dateText(0), m_dateFrame(0), m_dateValidator(0), m_widget(0), m_editDelay(1500), m_date(QDate::currentDate()) { } + + QWidget *widget() const; + void setWidget(QWidget *widget); + + int dateEditAcceptDelay() const; + void setDateEditAcceptDelay(int delay); + + QDate date() const; + void setDate(const QDate &date); + + bool eventFilter(QObject *o, QEvent *e); + void timerEvent(QTimerEvent *e); + +signals: + void dateChanged(const QDate &date); + void editingFinished(); + +private: + void applyDate(); + void updateDateLabel(); + void createDateLabel(); + void removeDateLabel(); + + QLabel *m_dateText; + QFrame *m_dateFrame; + QBasicTimer m_acceptTimer; + QCalendarDateValidator *m_dateValidator; + QWidget *m_widget; + int m_editDelay; + + QDate m_date; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_CALENDARWIDGET + +#endif + diff --git a/src/gui/widgets/qcalendarwidget.cpp b/src/gui/widgets/qcalendarwidget.cpp new file mode 100644 index 0000000..92c12a5 --- /dev/null +++ b/src/gui/widgets/qcalendarwidget.cpp @@ -0,0 +1,3091 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcalendarwidget.h" + +#ifndef QT_NO_CALENDARWIDGET + +#include <qabstractitemmodel.h> +#include <qitemdelegate.h> +#include <qdatetime.h> +#include <qtableview.h> +#include <qlayout.h> +#include <qevent.h> +#include <qtextformat.h> +#include <qheaderview.h> +#include <private/qwidget_p.h> +#include <qpushbutton.h> +#include <qtoolbutton.h> +#include <qlabel.h> +#include <qspinbox.h> +#include <qmenu.h> +#include <qapplication.h> +#include <qbasictimer.h> +#include <qstylepainter.h> +#include <private/qcalendartextnavigator_p.h> + +QT_BEGIN_NAMESPACE + +enum { + RowCount = 6, + ColumnCount = 7, + HeaderColumn = 0, + HeaderRow = 0, + MinimumDayOffset = 1 +}; + +class QCalendarDateSectionValidator +{ +public: + + enum Section { + NextSection, + ThisSection, + PrevSection + }; + + QCalendarDateSectionValidator() {} + virtual ~QCalendarDateSectionValidator() {} + virtual Section handleKey(int key) = 0; + virtual QDate applyToDate(const QDate &date) const = 0; + virtual void setDate(const QDate &date) = 0; + virtual QString text() const = 0; + virtual QString text(const QDate &date, int repeat) const = 0; + + QLocale m_locale; + +protected: + QString highlightString(const QString &str, int pos) const; +private: +}; + +QString QCalendarDateSectionValidator::highlightString(const QString &str, int pos) const +{ + if (pos == 0) + return QLatin1String("<b>") + str + QLatin1String("</b>"); + int startPos = str.length() - pos; + return str.mid(0, startPos) + QLatin1String("<b>") + str.mid(startPos, pos) + QLatin1String("</b>"); + +} + +class QCalendarDayValidator : public QCalendarDateSectionValidator +{ + +public: + QCalendarDayValidator(); + virtual Section handleKey(int key); + virtual QDate applyToDate(const QDate &date) const; + virtual void setDate(const QDate &date); + virtual QString text() const; + virtual QString text(const QDate &date, int repeat) const; +private: + int m_pos; + int m_day; + int m_oldDay; +}; + +QCalendarDayValidator::QCalendarDayValidator() + : QCalendarDateSectionValidator(), m_pos(0), m_day(1), m_oldDay(1) +{ +} + +QCalendarDateSectionValidator::Section QCalendarDayValidator::handleKey(int key) +{ + if (key == Qt::Key_Right || key == Qt::Key_Left) { + m_pos = 0; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Up) { + m_pos = 0; + ++m_day; + if (m_day > 31) + m_day = 1; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Down) { + m_pos = 0; + --m_day; + if (m_day < 1) + m_day = 31; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) { + --m_pos; + if (m_pos < 0) + m_pos = 1; + + if (m_pos == 0) + m_day = m_oldDay; + else + m_day = m_day / 10; + //m_day = m_oldDay / 10 * 10 + m_day / 10; + + if (m_pos == 0) + return QCalendarDateSectionValidator::PrevSection; + return QCalendarDateSectionValidator::ThisSection; + } + if (key < Qt::Key_0 || key > Qt::Key_9) + return QCalendarDateSectionValidator::ThisSection; + int pressedKey = key - Qt::Key_0; + if (m_pos == 0) + m_day = pressedKey; + else + m_day = m_day % 10 * 10 + pressedKey; + if (m_day > 31) + m_day = 31; + ++m_pos; + if (m_pos > 1) { + m_pos = 0; + return QCalendarDateSectionValidator::NextSection; + } + return QCalendarDateSectionValidator::ThisSection; +} + +QDate QCalendarDayValidator::applyToDate(const QDate &date) const +{ + int day = m_day; + if (day < 1) + day = 1; + else if (day > 31) + day = 31; + if (day > date.daysInMonth()) + day = date.daysInMonth(); + return QDate(date.year(), date.month(), day); +} + +void QCalendarDayValidator::setDate(const QDate &date) +{ + m_day = m_oldDay = date.day(); + m_pos = 0; +} + +QString QCalendarDayValidator::text() const +{ + QString str; + if (m_day / 10 == 0) + str += QLatin1String("0"); + str += QString::number(m_day); + return highlightString(str, m_pos); +} + +QString QCalendarDayValidator::text(const QDate &date, int repeat) const +{ + if (repeat <= 1) { + return QString::number(date.day()); + } else if (repeat == 2) { + QString str; + if (date.day() / 10 == 0) + str += QLatin1String("0"); + return str + QString::number(date.day()); + } else if (repeat == 3) { + return m_locale.dayName(date.dayOfWeek(), QLocale::ShortFormat); + } else if (repeat >= 4) { + return m_locale.dayName(date.dayOfWeek(), QLocale::LongFormat); + } + return QString(); +} + +////////////////////////////////// + +class QCalendarMonthValidator : public QCalendarDateSectionValidator +{ + +public: + QCalendarMonthValidator(); + virtual Section handleKey(int key); + virtual QDate applyToDate(const QDate &date) const; + virtual void setDate(const QDate &date); + virtual QString text() const; + virtual QString text(const QDate &date, int repeat) const; +private: + int m_pos; + int m_month; + int m_oldMonth; +}; + +QCalendarMonthValidator::QCalendarMonthValidator() + : QCalendarDateSectionValidator(), m_pos(0), m_month(1), m_oldMonth(1) +{ +} + +QCalendarDateSectionValidator::Section QCalendarMonthValidator::handleKey(int key) +{ + if (key == Qt::Key_Right || key == Qt::Key_Left) { + m_pos = 0; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Up) { + m_pos = 0; + ++m_month; + if (m_month > 12) + m_month = 1; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Down) { + m_pos = 0; + --m_month; + if (m_month < 1) + m_month = 12; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) { + --m_pos; + if (m_pos < 0) + m_pos = 1; + + if (m_pos == 0) + m_month = m_oldMonth; + else + m_month = m_month / 10; + //m_month = m_oldMonth / 10 * 10 + m_month / 10; + + if (m_pos == 0) + return QCalendarDateSectionValidator::PrevSection; + return QCalendarDateSectionValidator::ThisSection; + } + if (key < Qt::Key_0 || key > Qt::Key_9) + return QCalendarDateSectionValidator::ThisSection; + int pressedKey = key - Qt::Key_0; + if (m_pos == 0) + m_month = pressedKey; + else + m_month = m_month % 10 * 10 + pressedKey; + if (m_month > 12) + m_month = 12; + ++m_pos; + if (m_pos > 1) { + m_pos = 0; + return QCalendarDateSectionValidator::NextSection; + } + return QCalendarDateSectionValidator::ThisSection; +} + +QDate QCalendarMonthValidator::applyToDate(const QDate &date) const +{ + int month = m_month; + if (month < 1) + month = 1; + else if (month > 12) + month = 12; + QDate newDate(date.year(), m_month, 1); + int day = date.day(); + if (day > newDate.daysInMonth()) + day = newDate.daysInMonth(); + return QDate(date.year(), month, day); +} + +void QCalendarMonthValidator::setDate(const QDate &date) +{ + m_month = m_oldMonth = date.month(); + m_pos = 0; +} + +QString QCalendarMonthValidator::text() const +{ + QString str; + if (m_month / 10 == 0) + str += QLatin1String("0"); + str += QString::number(m_month); + return highlightString(str, m_pos); +} + +QString QCalendarMonthValidator::text(const QDate &date, int repeat) const +{ + if (repeat <= 1) { + return QString::number(date.month()); + } else if (repeat == 2) { + QString str; + if (date.month() / 10 == 0) + str += QLatin1String("0"); + return str + QString::number(date.month()); + } else if (repeat == 3) { + return m_locale.standaloneMonthName(date.month(), QLocale::ShortFormat); + } else if (repeat >= 4) { + return m_locale.standaloneMonthName(date.month(), QLocale::LongFormat); + } + return QString(); +} + +////////////////////////////////// + +class QCalendarYearValidator : public QCalendarDateSectionValidator +{ + +public: + QCalendarYearValidator(); + virtual Section handleKey(int key); + virtual QDate applyToDate(const QDate &date) const; + virtual void setDate(const QDate &date); + virtual QString text() const; + virtual QString text(const QDate &date, int repeat) const; +private: + int pow10(int n); + int m_pos; + int m_year; + int m_oldYear; +}; + +QCalendarYearValidator::QCalendarYearValidator() + : QCalendarDateSectionValidator(), m_pos(0), m_year(2000), m_oldYear(2000) +{ +} + +int QCalendarYearValidator::pow10(int n) +{ + int power = 1; + for (int i = 0; i < n; i++) + power *= 10; + return power; +} + +QCalendarDateSectionValidator::Section QCalendarYearValidator::handleKey(int key) +{ + if (key == Qt::Key_Right || key == Qt::Key_Left) { + m_pos = 0; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Up) { + m_pos = 0; + ++m_year; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Down) { + m_pos = 0; + --m_year; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) { + --m_pos; + if (m_pos < 0) + m_pos = 3; + + int pow = pow10(m_pos); + m_year = m_oldYear / pow * pow + m_year % (pow * 10) / 10; + + if (m_pos == 0) + return QCalendarDateSectionValidator::PrevSection; + return QCalendarDateSectionValidator::ThisSection; + } + if (key < Qt::Key_0 || key > Qt::Key_9) + return QCalendarDateSectionValidator::ThisSection; + int pressedKey = key - Qt::Key_0; + int pow = pow10(m_pos); + m_year = m_year / (pow * 10) * (pow * 10) + m_year % pow * 10 + pressedKey; + ++m_pos; + if (m_pos > 3) { + m_pos = 0; + return QCalendarDateSectionValidator::NextSection; + } + return QCalendarDateSectionValidator::ThisSection; +} + +QDate QCalendarYearValidator::applyToDate(const QDate &date) const +{ + int year = m_year; + if (year < 1) + year = 1; + QDate newDate(year, date.month(), 1); + int day = date.day(); + if (day > newDate.daysInMonth()) + day = newDate.daysInMonth(); + return QDate(year, date.month(), day); +} + +void QCalendarYearValidator::setDate(const QDate &date) +{ + m_year = m_oldYear = date.year(); + m_pos = 0; +} + +QString QCalendarYearValidator::text() const +{ + QString str; + int pow = 10; + for (int i = 0; i < 3; i++) { + if (m_year / pow == 0) + str += QLatin1String("0"); + pow *= 10; + } + str += QString::number(m_year); + return highlightString(str, m_pos); +} + +QString QCalendarYearValidator::text(const QDate &date, int repeat) const +{ + if (repeat < 4) { + QString str; + int year = date.year() % 100; + if (year / 10 == 0) + str = QLatin1String("0"); + return str + QString::number(year); + } + return QString::number(date.year()); +} + +/////////////////////////////////// + +class QCalendarDateValidator +{ +public: + QCalendarDateValidator(); + ~QCalendarDateValidator(); + + void handleKeyEvent(QKeyEvent *keyEvent); + QString currentText() const; + QDate currentDate() const { return m_currentDate; } + void setFormat(const QString &format); + void setInitialDate(const QDate &date); + + void setLocale(const QLocale &locale); + +private: + + struct SectionToken { + SectionToken(QCalendarDateSectionValidator *val, int rep) : validator(val), repeat(rep) {} + QCalendarDateSectionValidator *validator; + int repeat; + }; + + void toNextToken(); + void toPreviousToken(); + void applyToDate(); + + int countRepeat(const QString &str, int index) const; + void clear(); + + QStringList m_separators; + QList<SectionToken *> m_tokens; + QCalendarDateSectionValidator *m_yearValidator; + QCalendarDateSectionValidator *m_monthValidator; + QCalendarDateSectionValidator *m_dayValidator; + + SectionToken *m_currentToken; + + QDate m_initialDate; + QDate m_currentDate; + + QCalendarDateSectionValidator::Section m_lastSectionMove; +}; + +QCalendarDateValidator::QCalendarDateValidator() + : m_currentToken(0), m_lastSectionMove(QCalendarDateSectionValidator::ThisSection) +{ + m_initialDate = m_currentDate = QDate::currentDate(); + m_yearValidator = new QCalendarYearValidator(); + m_monthValidator = new QCalendarMonthValidator(); + m_dayValidator = new QCalendarDayValidator(); +} + +void QCalendarDateValidator::setLocale(const QLocale &locale) +{ + m_yearValidator->m_locale = locale; + m_monthValidator->m_locale = locale; + m_dayValidator->m_locale = locale; +} + +QCalendarDateValidator::~QCalendarDateValidator() +{ + delete m_yearValidator; + delete m_monthValidator; + delete m_dayValidator; + clear(); +} + +// from qdatetime.cpp +int QCalendarDateValidator::countRepeat(const QString &str, int index) const +{ + Q_ASSERT(index >= 0 && index < str.size()); + int count = 1; + const QChar ch = str.at(index); + while (index + count < str.size() && str.at(index + count) == ch) + ++count; + return count; +} + +void QCalendarDateValidator::setInitialDate(const QDate &date) +{ + m_yearValidator->setDate(date); + m_monthValidator->setDate(date); + m_dayValidator->setDate(date); + m_initialDate = date; + m_currentDate = date; + m_lastSectionMove = QCalendarDateSectionValidator::ThisSection; +} + +QString QCalendarDateValidator::currentText() const +{ + QString str; + QStringListIterator itSep(m_separators); + QListIterator<SectionToken *> itTok(m_tokens); + while (itSep.hasNext()) { + str += itSep.next(); + if (itTok.hasNext()) { + SectionToken *token = itTok.next(); + QCalendarDateSectionValidator *validator = token->validator; + if (m_currentToken == token) + str += validator->text(); + else + str += validator->text(m_currentDate, token->repeat); + } + } + return str; +} + +void QCalendarDateValidator::clear() +{ + QListIterator<SectionToken *> it(m_tokens); + while (it.hasNext()) + delete it.next(); + + m_tokens.clear(); + m_separators.clear(); + + m_currentToken = 0; +} + +void QCalendarDateValidator::setFormat(const QString &format) +{ + clear(); + + int pos = 0; + const QLatin1String quote("'"); + bool quoting = false; + QString separator; + while (pos < format.size()) { + QString mid = format.mid(pos); + int offset = 1; + + if (mid.startsWith(quote)) { + quoting = !quoting; + } else { + const QChar nextChar = format.at(pos); + if (quoting) { + separator += nextChar; + } else { + SectionToken *token = 0; + if (nextChar == QLatin1Char('d')) { + offset = qMin(4, countRepeat(format, pos)); + token = new SectionToken(m_dayValidator, offset); + } else if (nextChar == QLatin1Char('M')) { + offset = qMin(4, countRepeat(format, pos)); + token = new SectionToken(m_monthValidator, offset); + } else if (nextChar == QLatin1Char('y')) { + offset = qMin(4, countRepeat(format, pos)); + token = new SectionToken(m_yearValidator, offset); + } else { + separator += nextChar; + } + if (token) { + m_tokens.append(token); + m_separators.append(separator); + separator = QString(); + if (!m_currentToken) + m_currentToken = token; + + } + } + } + pos += offset; + } + m_separators += separator; +} + +void QCalendarDateValidator::applyToDate() +{ + m_currentDate = m_yearValidator->applyToDate(m_currentDate); + m_currentDate = m_monthValidator->applyToDate(m_currentDate); + m_currentDate = m_dayValidator->applyToDate(m_currentDate); +} + +void QCalendarDateValidator::toNextToken() +{ + const int idx = m_tokens.indexOf(m_currentToken); + if (idx == -1) + return; + if (idx + 1 >= m_tokens.count()) + m_currentToken = m_tokens.first(); + else + m_currentToken = m_tokens.at(idx + 1); +} + +void QCalendarDateValidator::toPreviousToken() +{ + const int idx = m_tokens.indexOf(m_currentToken); + if (idx == -1) + return; + if (idx - 1 < 0) + m_currentToken = m_tokens.last(); + else + m_currentToken = m_tokens.at(idx - 1); +} + +void QCalendarDateValidator::handleKeyEvent(QKeyEvent *keyEvent) +{ + if (!m_currentToken) + return; + + int key = keyEvent->key(); + if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection) { + if (key == Qt::Key_Back || key == Qt::Key_Backspace) + toPreviousToken(); + } + if (key == Qt::Key_Right) + toNextToken(); + else if (key == Qt::Key_Left) + toPreviousToken(); + + m_lastSectionMove = m_currentToken->validator->handleKey(key); + + applyToDate(); + if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection) + toNextToken(); + else if (m_lastSectionMove == QCalendarDateSectionValidator::PrevSection) + toPreviousToken(); +} + +QWidget *QCalendarTextNavigator::widget() const +{ + return m_widget; +} + +void QCalendarTextNavigator::setWidget(QWidget *widget) +{ + m_widget = widget; +} + +QDate QCalendarTextNavigator::date() const +{ + return m_date; +} + +void QCalendarTextNavigator::setDate(const QDate &date) +{ + m_date = date; +} + +void QCalendarTextNavigator::updateDateLabel() +{ + if (!m_widget) + return; + + m_acceptTimer.start(m_editDelay, this); + + m_dateText->setText(m_dateValidator->currentText()); + + QSize s = m_dateFrame->sizeHint(); + QRect r = m_widget->geometry(); // later, just the table section + QRect newRect((r.width() - s.width()) / 2, (r.height() - s.height()) / 2, s.width(), s.height()); + m_dateFrame->setGeometry(newRect); + // need to set palette after geometry update as phonestyle sets transparency + // effect in move event. + QPalette p = m_dateFrame->palette(); + p.setBrush(QPalette::Window, m_dateFrame->window()->palette().brush(QPalette::Window)); + m_dateFrame->setPalette(p); + + m_dateFrame->raise(); + m_dateFrame->show(); +} + +void QCalendarTextNavigator::applyDate() +{ + QDate date = m_dateValidator->currentDate(); + if (m_date == date) + return; + + m_date = date; + emit dateChanged(date); +} + +void QCalendarTextNavigator::createDateLabel() +{ + if (m_dateFrame) + return; + m_dateFrame = new QFrame(m_widget); + QVBoxLayout *vl = new QVBoxLayout; + m_dateText = new QLabel; + vl->addWidget(m_dateText); + m_dateFrame->setLayout(vl); + m_dateFrame->setFrameShadow(QFrame::Plain); + m_dateFrame->setFrameShape(QFrame::Box); + m_dateValidator = new QCalendarDateValidator(); + m_dateValidator->setLocale(m_widget->locale()); + m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat)); + m_dateValidator->setInitialDate(m_date); + + m_dateFrame->setAutoFillBackground(true); + m_dateFrame->setBackgroundRole(QPalette::Window); +} + +void QCalendarTextNavigator::removeDateLabel() +{ + if (!m_dateFrame) + return; + m_acceptTimer.stop(); + m_dateFrame->hide(); + m_dateFrame->deleteLater(); + delete m_dateValidator; + m_dateFrame = 0; + m_dateText = 0; + m_dateValidator = 0; +} + +bool QCalendarTextNavigator::eventFilter(QObject *o, QEvent *e) +{ + if (m_widget) { + if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { + QKeyEvent* ke = (QKeyEvent*)e; + if ((ke->text().length() > 0 && ke->text()[0].isPrint()) || m_dateFrame) { + if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Select) { + applyDate(); + emit editingFinished(); + removeDateLabel(); + } else if (ke->key() == Qt::Key_Escape) { + removeDateLabel(); + } else if (e->type() == QEvent::KeyPress) { + createDateLabel(); + m_dateValidator->handleKeyEvent(ke); + updateDateLabel(); + } + ke->accept(); + return true; + } + // If we are navigating let the user finish his date in old locate. + // If we change our mind and want it to update immediately simply uncomment below + /* + } else if (e->type() == QEvent::LocaleChange) { + if (m_dateValidator) { + m_dateValidator->setLocale(m_widget->locale()); + m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat)); + updateDateLabel(); + } + */ + } + } + return QObject::eventFilter(o,e); +} + +void QCalendarTextNavigator::timerEvent(QTimerEvent *e) +{ + if (e->timerId() == m_acceptTimer.timerId()) { + applyDate(); + removeDateLabel(); + } +} + +int QCalendarTextNavigator::dateEditAcceptDelay() const +{ + return m_editDelay; +} + +void QCalendarTextNavigator::setDateEditAcceptDelay(int delay) +{ + m_editDelay = delay; +} + +class QCalendarView; + +class QCalendarModel : public QAbstractTableModel +{ + Q_OBJECT +public: + QCalendarModel(QObject *parent = 0); + + int rowCount(const QModelIndex &) const + { return RowCount + m_firstRow; } + int columnCount(const QModelIndex &) const + { return ColumnCount + m_firstColumn; } + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + beginInsertRows(parent, row, row + count - 1); + endInsertRows(); + return true; + } + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) + { + beginInsertColumns(parent, column, column + count - 1); + endInsertColumns(); + return true; + } + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + beginRemoveRows(parent, row, row + count - 1); + endRemoveRows(); + return true; + } + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) + { + beginRemoveColumns(parent, column, column + count - 1); + endRemoveColumns(); + return true; + } + + void showMonth(int year, int month); + void setDate(const QDate &d); + + void setMinimumDate(const QDate &date); + void setMaximumDate(const QDate &date); + + void setRange(const QDate &min, const QDate &max); + + void setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format); + + void setFirstColumnDay(Qt::DayOfWeek dayOfWeek); + Qt::DayOfWeek firstColumnDay() const; + + bool weekNumbersShown() const; + void setWeekNumbersShown(bool show); + + QTextCharFormat formatForCell(int row, int col) const; + Qt::DayOfWeek dayOfWeekForColumn(int section) const; + int columnForDayOfWeek(Qt::DayOfWeek day) const; + QDate dateForCell(int row, int column) const; + void cellForDate(const QDate &date, int *row, int *column) const; + QString dayName(Qt::DayOfWeek day) const; + + void setView(QCalendarView *view) + { m_view = view; } + + void internalUpdate(); + QDate referenceDate() const; + int columnForFirstOfMonth(const QDate &date) const; + + int m_firstColumn; + int m_firstRow; + QDate m_date; + QDate m_minimumDate; + QDate m_maximumDate; + int m_shownYear; + int m_shownMonth; + Qt::DayOfWeek m_firstDay; + QCalendarWidget::HorizontalHeaderFormat m_horizontalHeaderFormat; + bool m_weekNumbersShown; + QMap<Qt::DayOfWeek, QTextCharFormat> m_dayFormats; + QMap<QDate, QTextCharFormat> m_dateFormats; + QTextCharFormat m_headerFormat; + QCalendarView *m_view; +}; + +class QCalendarView : public QTableView +{ + Q_OBJECT +public: + QCalendarView(QWidget *parent = 0); + + void internalUpdate() { updateGeometries(); } + void setReadOnly(bool enable); + virtual void keyboardSearch(const QString & search) { Q_UNUSED(search) } + +signals: + void showDate(const QDate &date); + void changeDate(const QDate &date, bool changeMonth); + void clicked(const QDate &date); + void editingFinished(); +protected: + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + void mouseDoubleClickEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *event); +#endif + void keyPressEvent(QKeyEvent *event); + bool event(QEvent *event); + + QDate handleMouseEvent(QMouseEvent *event); +public: + bool readOnly; +private: + bool validDateClicked; +#ifdef QT_KEYPAD_NAVIGATION + QDate origDate; +#endif +}; + +QCalendarModel::QCalendarModel(QObject *parent) + : QAbstractTableModel(parent) +{ + m_date = QDate::currentDate(); + m_minimumDate = QDate::fromJulianDay(1); + m_maximumDate = QDate(7999, 12, 31); + m_shownYear = m_date.year(); + m_shownMonth = m_date.month(); + m_firstDay = Qt::Sunday; + m_horizontalHeaderFormat = QCalendarWidget::ShortDayNames; + m_weekNumbersShown = true; + m_firstColumn = 1; + m_firstRow = 1; + m_view = 0; +} + +Qt::DayOfWeek QCalendarModel::dayOfWeekForColumn(int column) const +{ + int col = column - m_firstColumn; + if (col < 0 || col > 6) + return Qt::Sunday; + int day = m_firstDay + col; + if (day > 7) + day -= 7; + return Qt::DayOfWeek(day); +} + +int QCalendarModel::columnForDayOfWeek(Qt::DayOfWeek day) const +{ + if (day < 1 || day > 7) + return -1; + int column = (int)day - (int)m_firstDay; + if (column < 0) + column += 7; + return column + m_firstColumn; +} + +/* +This simple algorithm tries to generate a valid date from the month shown. +Some months don't contain a first day (e.g. Jan of -4713 year, +so QDate (-4713, 1, 1) would be invalid). In that case we try to generate +another valid date for that month. Later, returned date's day is the number of cells +calendar widget will reserve for days before referenceDate. (E.g. if returned date's +day is 16, that day will be placed in 3rd or 4th row, not in the 1st or 2nd row). +Depending on referenceData we can change behaviour of Oct 1582. If referenceDate is 1st +of Oct we render 1 Oct in 1st or 2nd row. If referenceDate is 17 of Oct we show always 16 +dates before 17 of Oct, and since this month contains the hole 5-14 Oct, the first of Oct +will be rendered in 2nd or 3rd row, showing more dates from previous month. +*/ +QDate QCalendarModel::referenceDate() const +{ + int refDay = 1; + while (refDay <= 31) { + QDate refDate(m_shownYear, m_shownMonth, refDay); + if (refDate.isValid()) + return refDate; + refDay += 1; + } + return QDate(); +} + +int QCalendarModel::columnForFirstOfMonth(const QDate &date) const +{ + return (columnForDayOfWeek(static_cast<Qt::DayOfWeek>(date.dayOfWeek())) - (date.day() % 7) + 8) % 7; +} + +QDate QCalendarModel::dateForCell(int row, int column) const +{ + if (row < m_firstRow || row > m_firstRow + RowCount - 1 || + column < m_firstColumn || column > m_firstColumn + ColumnCount - 1) + return QDate(); + const QDate refDate = referenceDate(); + if (!refDate.isValid()) + return QDate(); + + const int columnForFirstOfShownMonth = columnForFirstOfMonth(refDate); + if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset) + row -= 1; + + const int requestedDay = 7 * (row - m_firstRow) + column - columnForFirstOfShownMonth - refDate.day() + 1; + return refDate.addDays(requestedDay); +} + +void QCalendarModel::cellForDate(const QDate &date, int *row, int *column) const +{ + if (!row && !column) + return; + + if (row) + *row = -1; + if (column) + *column = -1; + + const QDate refDate = referenceDate(); + if (!refDate.isValid()) + return; + + const int columnForFirstOfShownMonth = columnForFirstOfMonth(refDate); + const int requestedPosition = refDate.daysTo(date) - m_firstColumn + columnForFirstOfShownMonth + refDate.day() - 1; + + int c = requestedPosition % 7; + int r = requestedPosition / 7; + if (c < 0) { + c += 7; + r -= 1; + } + + if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset) + r += 1; + + if (r < 0 || r > RowCount - 1 || c < 0 || c > ColumnCount - 1) + return; + + if (row) + *row = r + m_firstRow; + if (column) + *column = c + m_firstColumn; +} + +QString QCalendarModel::dayName(Qt::DayOfWeek day) const +{ + switch (m_horizontalHeaderFormat) { + case QCalendarWidget::SingleLetterDayNames: { + QString standaloneDayName = m_view->locale().standaloneDayName(day, QLocale::NarrowFormat); + if (standaloneDayName == m_view->locale().dayName(day, QLocale::NarrowFormat)) + return standaloneDayName.left(1); + return standaloneDayName; + } + case QCalendarWidget::ShortDayNames: + return m_view->locale().dayName(day, QLocale::ShortFormat); + case QCalendarWidget::LongDayNames: + return m_view->locale().dayName(day, QLocale::LongFormat); + default: + break; + } + return QString(); +} + +QTextCharFormat QCalendarModel::formatForCell(int row, int col) const +{ + QPalette pal; + QPalette::ColorGroup cg = QPalette::Active; + if (m_view) { + pal = m_view->palette(); + if (!m_view->isEnabled()) + cg = QPalette::Disabled; + else if (!m_view->isActiveWindow()) + cg = QPalette::Inactive; + } + + QTextCharFormat format; + format.setFont(m_view->font()); + bool header = (m_weekNumbersShown && col == HeaderColumn) + || (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow); + format.setBackground(pal.brush(cg, header ? QPalette::AlternateBase : QPalette::Base)); + format.setForeground(pal.brush(cg, QPalette::Text)); + if (header) { + format.merge(m_headerFormat); + } + + if (col >= m_firstColumn && col < m_firstColumn + ColumnCount) { + Qt::DayOfWeek dayOfWeek = dayOfWeekForColumn(col); + if (m_dayFormats.contains(dayOfWeek)) + format.merge(m_dayFormats.value(dayOfWeek)); + } + + if (!header) { + QDate date = dateForCell(row, col); + format.merge(m_dateFormats.value(date)); + if(date < m_minimumDate || date > m_maximumDate) + format.setBackground(pal.brush(cg, QPalette::Window)); + if (m_shownMonth != date.month()) + format.setForeground(pal.brush(QPalette::Disabled, QPalette::Text)); + } + return format; +} + +QVariant QCalendarModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::TextAlignmentRole) + return (int) Qt::AlignCenter; + + int row = index.row(); + int column = index.column(); + + if(role == Qt::DisplayRole) { + if (m_weekNumbersShown && column == HeaderColumn + && row >= m_firstRow && row < m_firstRow + RowCount) { + QDate date = dateForCell(row, columnForDayOfWeek(Qt::Monday)); + if (date.isValid()) + return date.weekNumber(); + } + if (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow + && column >= m_firstColumn && column < m_firstColumn + ColumnCount) + return dayName(dayOfWeekForColumn(column)); + QDate date = dateForCell(row, column); + if (date.isValid()) + return date.day(); + return QString(); + } + + QTextCharFormat fmt = formatForCell(row, column); + if (role == Qt::BackgroundColorRole) + return fmt.background().color(); + if (role == Qt::TextColorRole) + return fmt.foreground().color(); + if (role == Qt::FontRole) + return fmt.font(); + if (role == Qt::ToolTipRole) + return fmt.toolTip(); + return QVariant(); +} + +Qt::ItemFlags QCalendarModel::flags(const QModelIndex &index) const +{ + QDate date = dateForCell(index.row(), index.column()); + if (!date.isValid()) + return QAbstractTableModel::flags(index); + if (date < m_minimumDate) + return 0; + if (date > m_maximumDate) + return 0; + return QAbstractTableModel::flags(index); +} + +void QCalendarModel::setDate(const QDate &d) +{ + m_date = d; + if (m_date < m_minimumDate) + m_date = m_minimumDate; + else if (m_date > m_maximumDate) + m_date = m_maximumDate; +} + +void QCalendarModel::showMonth(int year, int month) +{ + if (m_shownYear == year && m_shownMonth == month) + return; + + m_shownYear = year; + m_shownMonth = month; + + internalUpdate(); +} + +void QCalendarModel::setMinimumDate(const QDate &d) +{ + if (!d.isValid() || d == m_minimumDate) + return; + + m_minimumDate = d; + if (m_maximumDate < m_minimumDate) + m_maximumDate = m_minimumDate; + if (m_date < m_minimumDate) + m_date = m_minimumDate; + internalUpdate(); +} + +void QCalendarModel::setMaximumDate(const QDate &d) +{ + if (!d.isValid() || d == m_maximumDate) + return; + + m_maximumDate = d; + if (m_minimumDate > m_maximumDate) + m_minimumDate = m_maximumDate; + if (m_date > m_maximumDate) + m_date = m_maximumDate; + internalUpdate(); +} + +void QCalendarModel::setRange(const QDate &min, const QDate &max) +{ + m_minimumDate = min; + m_maximumDate = max; + if (m_minimumDate > m_maximumDate) + qSwap(m_minimumDate, m_maximumDate); + if (m_date < m_minimumDate) + m_date = m_minimumDate; + if (m_date > m_maximumDate) + m_date = m_maximumDate; + internalUpdate(); +} + +void QCalendarModel::internalUpdate() +{ + QModelIndex begin = index(0, 0); + QModelIndex end = index(m_firstRow + RowCount - 1, m_firstColumn + ColumnCount - 1); + emit dataChanged(begin, end); + emit headerDataChanged(Qt::Vertical, 0, m_firstRow + RowCount - 1); + emit headerDataChanged(Qt::Horizontal, 0, m_firstColumn + ColumnCount - 1); +} + +void QCalendarModel::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format) +{ + if (m_horizontalHeaderFormat == format) + return; + + int oldFormat = m_horizontalHeaderFormat; + m_horizontalHeaderFormat = format; + if (oldFormat == QCalendarWidget::NoHorizontalHeader) { + m_firstRow = 1; + insertRow(0); + } else if (m_horizontalHeaderFormat == QCalendarWidget::NoHorizontalHeader) { + m_firstRow = 0; + removeRow(0); + } + internalUpdate(); +} + +void QCalendarModel::setFirstColumnDay(Qt::DayOfWeek dayOfWeek) +{ + if (m_firstDay == dayOfWeek) + return; + + m_firstDay = dayOfWeek; + internalUpdate(); +} + +Qt::DayOfWeek QCalendarModel::firstColumnDay() const +{ + return m_firstDay; +} + +bool QCalendarModel::weekNumbersShown() const +{ + return m_weekNumbersShown; +} + +void QCalendarModel::setWeekNumbersShown(bool show) +{ + if (m_weekNumbersShown == show) + return; + + m_weekNumbersShown = show; + if (show) { + m_firstColumn = 1; + insertColumn(0); + } else { + m_firstColumn = 0; + removeColumn(0); + } + internalUpdate(); +} + +QCalendarView::QCalendarView(QWidget *parent) + : QTableView(parent), + readOnly(false), + validDateClicked(false) +{ + setTabKeyNavigation(false); + setShowGrid(false); + verticalHeader()->setVisible(false); + horizontalHeader()->setVisible(false); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +QModelIndex QCalendarView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) + return QTableView::moveCursor(cursorAction, modifiers); + + if (readOnly) + return currentIndex(); + + QModelIndex index = currentIndex(); + QDate currentDate = static_cast<QCalendarModel*>(model())->dateForCell(index.row(), index.column()); + switch (cursorAction) { + case QAbstractItemView::MoveUp: + currentDate = currentDate.addDays(-7); + break; + case QAbstractItemView::MoveDown: + currentDate = currentDate.addDays(7); + break; + case QAbstractItemView::MoveLeft: + currentDate = currentDate.addDays(isRightToLeft() ? 1 : -1); + break; + case QAbstractItemView::MoveRight: + currentDate = currentDate.addDays(isRightToLeft() ? -1 : 1); + break; + case QAbstractItemView::MoveHome: + currentDate = QDate(currentDate.year(), currentDate.month(), 1); + break; + case QAbstractItemView::MoveEnd: + currentDate = QDate(currentDate.year(), currentDate.month(), currentDate.daysInMonth()); + break; + case QAbstractItemView::MovePageUp: + currentDate = currentDate.addMonths(-1); + break; + case QAbstractItemView::MovePageDown: + currentDate = currentDate.addMonths(1); + break; + case QAbstractItemView::MoveNext: + case QAbstractItemView::MovePrevious: + return currentIndex(); + default: + break; + } + emit changeDate(currentDate, true); + return currentIndex(); +} + +void QCalendarView::keyPressEvent(QKeyEvent *event) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (event->key() == Qt::Key_Select) { + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus()) { + setEditFocus(true); + return; + } + } + } else if (event->key() == Qt::Key_Back) { + if (QApplication::keypadNavigationEnabled() && hasEditFocus()) { + if (qobject_cast<QCalendarModel *>(model())) { + emit changeDate(origDate, true); //changes selection back to origDate, but doesn't activate + setEditFocus(false); + return; + } + } + } +#endif + + if (!readOnly) { + switch (event->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Select: + emit editingFinished(); + return; + default: + break; + } + } + QTableView::keyPressEvent(event); +} + +#ifndef QT_NO_WHEELEVENT +void QCalendarView::wheelEvent(QWheelEvent *event) +{ + const int numDegrees = event->delta() / 8; + const int numSteps = numDegrees / 15; + const QModelIndex index = currentIndex(); + QDate currentDate = static_cast<QCalendarModel*>(model())->dateForCell(index.row(), index.column()); + currentDate = currentDate.addMonths(-numSteps); + emit showDate(currentDate); +} +#endif + +bool QCalendarView::event(QEvent *event) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (event->type() == QEvent::FocusIn) { + if (QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model())) { + origDate = calendarModel->m_date; + } + } +#endif + + return QTableView::event(event); +} + +QDate QCalendarView::handleMouseEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) + return QDate(); + + QPoint pos = event->pos(); + QModelIndex index = indexAt(pos); + QDate date = calendarModel->dateForCell(index.row(), index.column()); + if (date.isValid() && date >= calendarModel->m_minimumDate + && date <= calendarModel->m_maximumDate) { + return date; + } + return QDate(); +} + +void QCalendarView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mouseDoubleClickEvent(event); + return; + } + + if (readOnly) + return; + + QDate date = handleMouseEvent(event); + validDateClicked = false; + if (date == calendarModel->m_date && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { + emit editingFinished(); + } +} + +void QCalendarView::mousePressEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mousePressEvent(event); + return; + } + + if (readOnly) + return; + + if (event->button() != Qt::LeftButton) + return; + + QDate date = handleMouseEvent(event); + if (date.isValid()) { + validDateClicked = true; + int row = -1, col = -1; + static_cast<QCalendarModel *>(model())->cellForDate(date, &row, &col); + if (row != -1 && col != -1) { + selectionModel()->setCurrentIndex(model()->index(row, col), QItemSelectionModel::NoUpdate); + } + } else { + validDateClicked = false; + event->ignore(); + } +} + +void QCalendarView::mouseMoveEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mouseMoveEvent(event); + return; + } + + if (readOnly) + return; + + if (validDateClicked) { + QDate date = handleMouseEvent(event); + if (date.isValid()) { + int row = -1, col = -1; + static_cast<QCalendarModel *>(model())->cellForDate(date, &row, &col); + if (row != -1 && col != -1) { + selectionModel()->setCurrentIndex(model()->index(row, col), QItemSelectionModel::NoUpdate); + } + } + } else { + event->ignore(); + } +} + +void QCalendarView::mouseReleaseEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mouseReleaseEvent(event); + return; + } + + if (event->button() != Qt::LeftButton) + return; + + if (readOnly) + return; + + if (validDateClicked) { + QDate date = handleMouseEvent(event); + if (date.isValid()) { + emit changeDate(date, true); + emit clicked(date); + if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) + emit editingFinished(); + } + validDateClicked = false; + } else { + event->ignore(); + } +} + +class QCalendarDelegate : public QItemDelegate +{ + Q_OBJECT +public: + QCalendarDelegate(QCalendarWidgetPrivate *w, QObject *parent = 0) + : QItemDelegate(parent), calendarWidgetPrivate(w) + { } + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const; + +private: + QCalendarWidgetPrivate *calendarWidgetPrivate; + mutable QStyleOptionViewItemV4 storedOption; +}; + +//Private tool button class +class QCalToolButton: public QToolButton +{ +public: + QCalToolButton(QWidget * parent) + : QToolButton(parent) + { } +protected: + void paintEvent(QPaintEvent *e) + { + Q_UNUSED(e) + +#ifndef Q_WS_MAC + QStyleOptionToolButton opt; + initStyleOption(&opt); + + if (opt.state & QStyle::State_MouseOver || isDown()) { + //act as normal button + setPalette(QPalette()); + } else { + //set the highlight color for button text + QPalette toolPalette = palette(); + toolPalette.setColor(QPalette::ButtonText, toolPalette.color(QPalette::HighlightedText)); + setPalette(toolPalette); + } +#endif + QToolButton::paintEvent(e); + } +}; + +class QPrevNextCalButton : public QToolButton +{ + Q_OBJECT +public: + QPrevNextCalButton(QWidget *parent) : QToolButton(parent) {} +protected: + void paintEvent(QPaintEvent *) { + QStylePainter painter(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.state &= ~QStyle::State_HasFocus; + painter.drawComplexControl(QStyle::CC_ToolButton, opt); + } +}; + +class QCalendarWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QCalendarWidget) +public: + QCalendarWidgetPrivate(); + + void showMonth(int year, int month); + void update(); + void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const; + + void _q_slotShowDate(const QDate &date); + void _q_slotChangeDate(const QDate &date); + void _q_slotChangeDate(const QDate &date, bool changeMonth); + void _q_editingFinished(); + void _q_monthChanged(QAction*); + void _q_prevMonthClicked(); + void _q_nextMonthClicked(); + void _q_yearEditingFinished(); + void _q_yearClicked(); + + void createNavigationBar(QWidget *widget); + void updateButtonIcons(); + void updateMonthMenu(); + void updateMonthMenuNames(); + void updateNavigationBar(); + void updateCurrentPage(const QDate &newDate); + inline QDate getCurrentDate(); + void setNavigatorEnabled(bool enable); + + QCalendarModel *m_model; + QCalendarView *m_view; + QCalendarDelegate *m_delegate; + QItemSelectionModel *m_selection; + QCalendarTextNavigator *m_navigator; + bool m_dateEditEnabled; + + QToolButton *nextMonth; + QToolButton *prevMonth; + QCalToolButton *monthButton; + QMenu *monthMenu; + QMap<int, QAction *> monthToAction; + QCalToolButton *yearButton; + QSpinBox *yearEdit; + QWidget *navBarBackground; + QSpacerItem *spaceHolder; + + bool navBarVisible; + mutable QSize cachedSizeHint; + Qt::FocusPolicy oldFocusPolicy; +}; + +void QCalendarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QDate date = calendarWidgetPrivate->m_model->dateForCell(index.row(), index.column()); + if (date.isValid()) { + storedOption = option; + QRect rect = option.rect; + calendarWidgetPrivate->paintCell(painter, rect, date); + } else { + QItemDelegate::paint(painter, option, index); + } +} + +void QCalendarDelegate::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + storedOption.rect = rect; + int row = -1; + int col = -1; + calendarWidgetPrivate->m_model->cellForDate(date, &row, &col); + QModelIndex idx = calendarWidgetPrivate->m_model->index(row, col); + QItemDelegate::paint(painter, storedOption, idx); +} + +QCalendarWidgetPrivate::QCalendarWidgetPrivate() + : QWidgetPrivate() +{ + m_model = 0; + m_view = 0; + m_delegate = 0; + m_selection = 0; + m_navigator = 0; + m_dateEditEnabled = false; + navBarVisible = true; + oldFocusPolicy = Qt::StrongFocus; +} + +void QCalendarWidgetPrivate::setNavigatorEnabled(bool enable) +{ + Q_Q(QCalendarWidget); + + bool navigatorEnabled = (m_navigator->widget() != 0); + if (enable == navigatorEnabled) + return; + + if (enable) { + m_navigator->setWidget(q); + q->connect(m_navigator, SIGNAL(dateChanged(QDate)), + q, SLOT(_q_slotChangeDate(QDate))); + q->connect(m_navigator, SIGNAL(editingFinished()), + q, SLOT(_q_editingFinished())); + m_view->installEventFilter(m_navigator); + } else { + m_navigator->setWidget(0); + q->disconnect(m_navigator, SIGNAL(dateChanged(QDate)), + q, SLOT(_q_slotChangeDate(QDate))); + q->disconnect(m_navigator, SIGNAL(editingFinished()), + q, SLOT(_q_editingFinished())); + m_view->removeEventFilter(m_navigator); + } +} + +void QCalendarWidgetPrivate::createNavigationBar(QWidget *widget) +{ + Q_Q(QCalendarWidget); + navBarBackground = new QWidget(widget); + navBarBackground->setObjectName(QLatin1String("qt_calendar_navigationbar")); + navBarBackground->setAutoFillBackground(true); + navBarBackground->setBackgroundRole(QPalette::Highlight); + + prevMonth = new QPrevNextCalButton(navBarBackground); + nextMonth = new QPrevNextCalButton(navBarBackground); + prevMonth->setAutoRaise(true); + nextMonth->setAutoRaise(true); + prevMonth->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + nextMonth->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + nextMonth->setAutoRaise(true); + updateButtonIcons(); + prevMonth->setAutoRepeat(true); + nextMonth->setAutoRepeat(true); + + monthButton = new QCalToolButton(navBarBackground); + monthButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + monthButton->setAutoRaise(true); + monthButton->setPopupMode(QToolButton::InstantPopup); + monthMenu = new QMenu(monthButton); + for (int i = 1; i <= 12; i++) { + QString monthName(q->locale().standaloneMonthName(i, QLocale::LongFormat)); + QAction *act = monthMenu->addAction(monthName); + act->setData(i); + monthToAction[i] = act; + } + monthButton->setMenu(monthMenu); + yearButton = new QCalToolButton(navBarBackground); + yearButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + yearButton->setAutoRaise(true); + yearEdit = new QSpinBox(navBarBackground); + + QFont font = q->font(); + font.setBold(true); + monthButton->setFont(font); + yearButton->setFont(font); + yearEdit->setFrame(false); + yearEdit->setMinimum(m_model->m_minimumDate.year()); + yearEdit->setMaximum(m_model->m_maximumDate.year()); + yearEdit->hide(); + spaceHolder = new QSpacerItem(0,0); + + QHBoxLayout *headerLayout = new QHBoxLayout; + headerLayout->setMargin(0); + headerLayout->setSpacing(0); + headerLayout->addWidget(prevMonth); + headerLayout->insertStretch(headerLayout->count()); + headerLayout->addWidget(monthButton); + headerLayout->addItem(spaceHolder); + headerLayout->addWidget(yearButton); + headerLayout->insertStretch(headerLayout->count()); + headerLayout->addWidget(nextMonth); + navBarBackground->setLayout(headerLayout); + + yearEdit->setFocusPolicy(Qt::StrongFocus); + prevMonth->setFocusPolicy(Qt::NoFocus); + nextMonth->setFocusPolicy(Qt::NoFocus); + yearButton->setFocusPolicy(Qt::NoFocus); + monthButton->setFocusPolicy(Qt::NoFocus); + + //set names for the header controls. + prevMonth->setObjectName(QLatin1String("qt_calendar_prevmonth")); + nextMonth->setObjectName(QLatin1String("qt_calendar_nextmonth")); + monthButton->setObjectName(QLatin1String("qt_calendar_monthbutton")); + yearButton->setObjectName(QLatin1String("qt_calendar_yearbutton")); + yearEdit->setObjectName(QLatin1String("qt_calendar_yearedit")); + + updateMonthMenu(); + showMonth(m_model->m_date.year(), m_model->m_date.month()); +} + +void QCalendarWidgetPrivate::updateButtonIcons() +{ + Q_Q(QCalendarWidget); + prevMonth->setIcon(q->style()->standardIcon(q->isRightToLeft() ? QStyle::SP_ArrowRight : QStyle::SP_ArrowLeft, 0, q)); + nextMonth->setIcon(q->style()->standardIcon(q->isRightToLeft() ? QStyle::SP_ArrowLeft : QStyle::SP_ArrowRight, 0, q)); +} + +void QCalendarWidgetPrivate::updateMonthMenu() +{ + int beg = 1, end = 12; + bool prevEnabled = true; + bool nextEnabled = true; + if (m_model->m_shownYear == m_model->m_minimumDate.year()) { + beg = m_model->m_minimumDate.month(); + if (m_model->m_shownMonth == m_model->m_minimumDate.month()) + prevEnabled = false; + } + if (m_model->m_shownYear == m_model->m_maximumDate.year()) { + end = m_model->m_maximumDate.month(); + if (m_model->m_shownMonth == m_model->m_maximumDate.month()) + nextEnabled = false; + } + prevMonth->setEnabled(prevEnabled); + nextMonth->setEnabled(nextEnabled); + for (int i = 1; i <= 12; i++) { + bool monthEnabled = true; + if (i < beg || i > end) + monthEnabled = false; + monthToAction[i]->setEnabled(monthEnabled); + } +} + +void QCalendarWidgetPrivate::updateMonthMenuNames() +{ + Q_Q(QCalendarWidget); + + for (int i = 1; i <= 12; i++) { + QString monthName(q->locale().standaloneMonthName(i, QLocale::LongFormat)); + monthToAction[i]->setText(monthName); + } +} + +void QCalendarWidgetPrivate::updateCurrentPage(const QDate &date) +{ + Q_Q(QCalendarWidget); + + QDate newDate = date; + QDate minDate = q->minimumDate(); + QDate maxDate = q->maximumDate(); + if (minDate.isValid()&& minDate.daysTo(newDate) < 0) + newDate = minDate; + if (maxDate.isValid()&& maxDate.daysTo(newDate) > 0) + newDate = maxDate; + showMonth(newDate.year(), newDate.month()); + int row = -1, col = -1; + m_model->cellForDate(newDate, &row, &col); + if (row != -1 && col != -1) + { + m_view->selectionModel()->setCurrentIndex(m_model->index(row, col), + QItemSelectionModel::NoUpdate); + } +} + +void QCalendarWidgetPrivate::_q_monthChanged(QAction *act) +{ + monthButton->setText(act->text()); + QDate currentDate = getCurrentDate(); + QDate newDate = currentDate.addMonths(act->data().toInt()-currentDate.month()); + updateCurrentPage(newDate); +} + +QDate QCalendarWidgetPrivate::getCurrentDate() +{ + QModelIndex index = m_view->currentIndex(); + return m_model->dateForCell(index.row(), index.column()); +} + +void QCalendarWidgetPrivate::_q_prevMonthClicked() +{ + QDate currentDate = getCurrentDate().addMonths(-1); + updateCurrentPage(currentDate); +} + +void QCalendarWidgetPrivate::_q_nextMonthClicked() +{ + QDate currentDate = getCurrentDate().addMonths(1); + updateCurrentPage(currentDate); +} + +void QCalendarWidgetPrivate::_q_yearEditingFinished() +{ + Q_Q(QCalendarWidget); + yearButton->setText(yearEdit->text()); + yearEdit->hide(); + q->setFocusPolicy(oldFocusPolicy); + qApp->removeEventFilter(q); + spaceHolder->changeSize(0, 0); + yearButton->show(); + QDate currentDate = getCurrentDate(); + currentDate = currentDate.addYears(yearEdit->text().toInt() - currentDate.year()); + updateCurrentPage(currentDate); +} + +void QCalendarWidgetPrivate::_q_yearClicked() +{ + Q_Q(QCalendarWidget); + //show the spinbox on top of the button + yearEdit->setGeometry(yearButton->x(), yearButton->y(), + yearEdit->sizeHint().width(), yearButton->height()); + spaceHolder->changeSize(yearButton->width(), 0); + yearButton->hide(); + oldFocusPolicy = q->focusPolicy(); + q->setFocusPolicy(Qt::NoFocus); + yearEdit->show(); + qApp->installEventFilter(q); + yearEdit->raise(); + yearEdit->selectAll(); + yearEdit->setFocus(Qt::MouseFocusReason); +} + +void QCalendarWidgetPrivate::showMonth(int year, int month) +{ + if (m_model->m_shownYear == year && m_model->m_shownMonth == month) + return; + Q_Q(QCalendarWidget); + m_model->showMonth(year, month); + updateNavigationBar(); + emit q->currentPageChanged(year, month); + m_view->internalUpdate(); + cachedSizeHint = QSize(); + update(); + updateMonthMenu(); +} + +void QCalendarWidgetPrivate::updateNavigationBar() +{ + Q_Q(QCalendarWidget); + + QString monthName = q->locale().standaloneMonthName(m_model->m_shownMonth, QLocale::LongFormat); + + monthButton->setText(monthName); + yearButton->setText(QString::number(m_model->m_shownYear)); + yearEdit->setValue(m_model->m_shownYear); +} + +void QCalendarWidgetPrivate::update() +{ + QDate currentDate = m_model->m_date; + int row, column; + m_model->cellForDate(currentDate, &row, &column); + QModelIndex idx; + m_selection->clear(); + if (row != -1 && column != -1) { + idx = m_model->index(row, column); + m_selection->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent); + } +} + +void QCalendarWidgetPrivate::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + Q_Q(const QCalendarWidget); + q->paintCell(painter, rect, date); +} + +void QCalendarWidgetPrivate::_q_slotShowDate(const QDate &date) +{ + updateCurrentPage(date); +} + +void QCalendarWidgetPrivate::_q_slotChangeDate(const QDate &date) +{ + _q_slotChangeDate(date, true); +} + +void QCalendarWidgetPrivate::_q_slotChangeDate(const QDate &date, bool changeMonth) +{ + QDate oldDate = m_model->m_date; + m_model->setDate(date); + QDate newDate = m_model->m_date; + if (changeMonth) + showMonth(newDate.year(), newDate.month()); + if (oldDate != newDate) { + update(); + Q_Q(QCalendarWidget); + m_navigator->setDate(newDate); + emit q->selectionChanged(); + } +} + +void QCalendarWidgetPrivate::_q_editingFinished() +{ + Q_Q(QCalendarWidget); + emit q->activated(m_model->m_date); +} + +/*! + \class QCalendarWidget + \brief The QCalendarWidget class provides a monthly based + calendar widget allowing the user to select a date. + \since 4.2 + \mainclass + \ingroup advanced + + \image cleanlooks-calendarwidget.png + + The widget is initialized with the current month and year, but + QCalendarWidget provides several public slots to change the year + and month that is shown. The currently displayed month and year + can be retrieved using the currentPageMonth() and currentPageYear() + functions, respectively. + + By default, today's date is selected, and the user can select a + date using both mouse and keyboard. The currently selected date + can be retrieved using the selectedDate() function. It is + possible to constrain the user selection to a given date range by + setting the minimumDate and maximumDate properties. + Alternatively, both properties can be set in one go using the + setDateRange() convenience slot. Set the \l selectionMode + property to NoSelection to prohibit the user from selecting at + all. Note that a date also can be selected programmatically using + the setSelectedDate() slot. + + A newly created calendar widget uses abbreviated day names, and + both Saturdays and Sundays are marked in red. The calendar grid is + not visible. The week numbers are displayed, and the first column + day is Sunday. + + The notation of the days can be altered to a single letter + abbreviations ("M" for "Monday") by setting the + horizontalHeaderFormat property to + QCalendarWidget::SingleLetterDayNames. Setting the same property + to QCalendarWidget::LongDayNames makes the header display the + complete day names. The week numbers can be removed by setting + the verticalHeaderFormat property to + QCalendarWidget::NoVerticalHeader. The calendar grid can be + turned on by setting the gridVisible property to true using the + setGridVisible() function: + + \table + \row \o + \image qcalendarwidget-grid.png + \row \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 0 + \endtable + + Finally, the day in the first column can be altered using the + setFirstDayOfWeek() function. + + The QCalendarWidget class also provides three signals, + selectionChanged(), activated() and currentPageChanged() making it + possible to respond to user interaction. + + The rendering of the headers, weekdays or single days can be + largely customized by setting QTextCharFormat's for some special + weekday, a special date or for the rendering of the headers. + + Only a subset of the properties in QTextCharFormat are used by the + calendar widget. Currently, the foreground, background and font + properties are used to determine the rendering of individual cells + in the widget. + + \sa QDate, QDateEdit, QTextCharFormat +*/ + +/*! + \enum QCalendarWidget::SelectionMode + + This enum describes the types of selection offered to the user for + selecting dates in the calendar. + + \value NoSelection Dates cannot be selected. + \value SingleSelection Single dates can be selected. + + \sa selectionMode +*/ + +/*! + Constructs a calendar widget with the given \a parent. + + The widget is initialized with the current month and year, and the + currently selected date is today. + + \sa setCurrentPage() +*/ +QCalendarWidget::QCalendarWidget(QWidget *parent) + : QWidget(*new QCalendarWidgetPrivate, parent, 0) +{ + Q_D(QCalendarWidget); + + setAutoFillBackground(true); + setBackgroundRole(QPalette::Window); + + QVBoxLayout *layoutV = new QVBoxLayout(this); + layoutV->setMargin(0); + d->m_model = new QCalendarModel(this); + QTextCharFormat fmt; + fmt.setForeground(QBrush(Qt::red)); + d->m_model->m_dayFormats.insert(Qt::Saturday, fmt); + d->m_model->m_dayFormats.insert(Qt::Sunday, fmt); + d->m_view = new QCalendarView(this); + d->m_view->setObjectName(QLatin1String("qt_calendar_calendarview")); + d->m_view->setModel(d->m_model); + d->m_model->setView(d->m_view); + d->m_view->setSelectionBehavior(QAbstractItemView::SelectItems); + d->m_view->setSelectionMode(QAbstractItemView::SingleSelection); + d->m_view->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + d->m_view->horizontalHeader()->setClickable(false); + d->m_view->verticalHeader()->setResizeMode(QHeaderView::Stretch); + d->m_view->verticalHeader()->setClickable(false); + d->m_selection = d->m_view->selectionModel(); + d->createNavigationBar(this); + d->m_view->setFrameStyle(QFrame::NoFrame); + d->m_delegate = new QCalendarDelegate(d, this); + d->m_view->setItemDelegate(d->m_delegate); + d->update(); + d->updateNavigationBar(); + setFocusPolicy(Qt::StrongFocus); + setFocusProxy(d->m_view); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + connect(d->m_view, SIGNAL(showDate(QDate)), + this, SLOT(_q_slotShowDate(QDate))); + connect(d->m_view, SIGNAL(changeDate(QDate,bool)), + this, SLOT(_q_slotChangeDate(QDate,bool))); + connect(d->m_view, SIGNAL(clicked(QDate)), + this, SIGNAL(clicked(QDate))); + connect(d->m_view, SIGNAL(editingFinished()), + this, SLOT(_q_editingFinished())); + + connect(d->prevMonth, SIGNAL(clicked(bool)), + this, SLOT(_q_prevMonthClicked())); + connect(d->nextMonth, SIGNAL(clicked(bool)), + this, SLOT(_q_nextMonthClicked())); + connect(d->yearButton, SIGNAL(clicked(bool)), + this, SLOT(_q_yearClicked())); + connect(d->monthMenu, SIGNAL(triggered(QAction*)), + this, SLOT(_q_monthChanged(QAction*))); + connect(d->yearEdit, SIGNAL(editingFinished()), + this, SLOT(_q_yearEditingFinished())); + + layoutV->setMargin(0); + layoutV->setSpacing(0); + layoutV->addWidget(d->navBarBackground); + layoutV->addWidget(d->m_view); + + d->m_navigator = new QCalendarTextNavigator(this); + setDateEditEnabled(true); +} + +/*! + Destroys the calendar widget. +*/ +QCalendarWidget::~QCalendarWidget() +{ +} + +/*! + \reimp +*/ +QSize QCalendarWidget::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \reimp +*/ +QSize QCalendarWidget::minimumSizeHint() const +{ + Q_D(const QCalendarWidget); + if (d->cachedSizeHint.isValid()) + return d->cachedSizeHint; + + ensurePolished(); + + int w = 0; + int h = 0; + + int end = 53; + int rows = 7; + int cols = 8; + int startRow = 0; + int startCol = 0; + + const int marginH = (style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1) * 2; + + if (horizontalHeaderFormat() == QCalendarWidget::NoHorizontalHeader) { + rows = 6; + startRow = 1; + } else { + for (int i = 1; i <= 7; i++) { + QFontMetrics fm(d->m_model->formatForCell(0, i).font()); + w = qMax(w, fm.width(d->m_model->dayName(d->m_model->dayOfWeekForColumn(i))) + marginH); + h = qMax(h, fm.height()); + } + } + + if (verticalHeaderFormat() == QCalendarWidget::NoVerticalHeader) { + cols = 7; + startCol = 1; + } else { + for (int i = 1; i <= 6; i++) { + QFontMetrics fm(d->m_model->formatForCell(i, 0).font()); + for (int j = 1; j < end; j++) + w = qMax(w, fm.width(QString::number(j)) + marginH); + h = qMax(h, fm.height()); + } + } + + QFontMetrics fm(d->m_model->formatForCell(1, 1).font()); + for (int i = 1; i <= end; i++) { + w = qMax(w, fm.width(QString::number(i)) + marginH); + h = qMax(h, fm.height()); + } + + if (d->m_view->showGrid()) { + // hardcoded in tableview + w += 1; + h += 1; + } + + w += 1; // default column span + + h = qMax(h, d->m_view->verticalHeader()->minimumSectionSize()); + w = qMax(w, d->m_view->horizontalHeader()->minimumSectionSize()); + + //add the size of the header. + QSize headerSize(0, 0); + if (d->navBarVisible) { + int headerH = d->navBarBackground->sizeHint().height(); + int headerW = 0; + + headerW += d->prevMonth->sizeHint().width(); + headerW += d->nextMonth->sizeHint().width(); + + QFontMetrics fm = d->monthButton->fontMetrics(); + int monthW = 0; + for (int i = 1; i < 12; i++) { + QString monthName = locale().standaloneMonthName(i, QLocale::LongFormat); + monthW = qMax(monthW, fm.boundingRect(monthName).width()); + } + const int buttonDecoMargin = d->monthButton->sizeHint().width() - fm.boundingRect(d->monthButton->text()).width(); + headerW += monthW + buttonDecoMargin; + + fm = d->yearButton->fontMetrics(); + headerW += fm.boundingRect(QLatin1String("5555")).width() + buttonDecoMargin; + + headerSize = QSize(headerW, headerH); + } + w *= cols; + w = qMax(headerSize.width(), w); + h = (h * rows) + headerSize.height(); + d->cachedSizeHint = QSize(w, h); + return d->cachedSizeHint; +} + +/*! + Paints the cell specified by the given \a date, using the given \a painter and \a rect. +*/ + +void QCalendarWidget::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + Q_D(const QCalendarWidget); + d->m_delegate->paintCell(painter, rect, date); +} + +/*! + \property QCalendarWidget::selectedDate + \brief the currently selected date. + + The selected date must be within the date range specified by the + minimumDate and maximumDate properties. By default, the selected + date is the current date. + + \sa setDateRange() +*/ + +QDate QCalendarWidget::selectedDate() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_date; +} + +void QCalendarWidget::setSelectedDate(const QDate &date) +{ + Q_D(QCalendarWidget); + if (d->m_model->m_date == date && date == d->getCurrentDate()) + return; + + if (!date.isValid()) + return; + + d->m_model->setDate(date); + d->update(); + QDate newDate = d->m_model->m_date; + d->showMonth(newDate.year(), newDate.month()); + emit selectionChanged(); +} + +/*! + Returns the year of the currently displayed month. Months are + numbered from 1 to 12. + + \sa monthShown(), setCurrentPage() +*/ + +int QCalendarWidget::yearShown() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_shownYear; +} + +/*! + Returns the currently displayed month. Months are numbered from 1 to + 12. + + \sa yearShown(), setCurrentPage() +*/ + +int QCalendarWidget::monthShown() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_shownMonth; +} + +/*! + Displays the given \a month of the given \a year without changing + the selected date. Use the setSelectedDate() function to alter the + selected date. + + The currently displayed month and year can be retrieved using the + currentPageMonth() and currentPageYear() functions respectively. + + \sa yearShown(), monthShown(), showPreviousMonth(), showNextMonth(), + showPreviousYear(), showNextYear() +*/ + +void QCalendarWidget::setCurrentPage(int year, int month) +{ + Q_D(QCalendarWidget); + d->showMonth(year, month); +} + +/*! + Shows the next month relative to the currently displayed + month. Note that the selected date is not changed. + + \sa showPreviousMonth(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showNextMonth() +{ + int year = yearShown(); + int month = monthShown(); + if (month == 12) { + ++year; + month = 1; + } else { + ++month; + } + setCurrentPage(year, month); +} + +/*! + Shows the previous month relative to the currently displayed + month. Note that the selected date is not changed. + + \sa showNextMonth(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showPreviousMonth() +{ + int year = yearShown(); + int month = monthShown(); + if (month == 1) { + --year; + month = 12; + } else { + --month; + } + setCurrentPage(year, month); +} + +/*! + Shows the currently displayed month in the \e next year relative + to the currently displayed year. Note that the selected date is + not changed. + + \sa showPreviousYear(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showNextYear() +{ + int year = yearShown(); + int month = monthShown(); + ++year; + setCurrentPage(year, month); +} + +/*! + Shows the currently displayed month in the \e previous year + relative to the currently displayed year. Note that the selected + date is not changed. + + \sa showNextYear(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showPreviousYear() +{ + int year = yearShown(); + int month = monthShown(); + --year; + setCurrentPage(year, month); +} + +/*! + Shows the month of the selected date. + + \sa selectedDate(), setCurrentPage() +*/ +void QCalendarWidget::showSelectedDate() +{ + QDate currentDate = selectedDate(); + setCurrentPage(currentDate.year(), currentDate.month()); +} + +/*! + Shows the month of the today's date. + + \sa selectedDate(), setCurrentPage() +*/ +void QCalendarWidget::showToday() +{ + QDate currentDate = QDate::currentDate(); + setCurrentPage(currentDate.year(), currentDate.month()); +} + +/*! + \property QCalendarWidget::minimumDate + \brief the minimum date of the currently specified date range. + + The user will not be able to select a date that is before the + currently set minimum date. + + \table + \row + \o \image qcalendarwidget-minimum.png + \row + \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 1 + \endtable + + By default, the minimum date is the earliest date that the QDate + class can handle. + + When setting a minimum date, the maximumDate and selectedDate + properties are adjusted if the selection range becomes invalid. If + the provided date is not a valid QDate object, the + setMinimumDate() function does nothing. + + \sa setDateRange() +*/ + +QDate QCalendarWidget::minimumDate() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_minimumDate; +} + +void QCalendarWidget::setMinimumDate(const QDate &date) +{ + Q_D(QCalendarWidget); + if (!date.isValid() || d->m_model->m_minimumDate == date) + return; + + QDate oldDate = d->m_model->m_date; + d->m_model->setMinimumDate(date); + d->yearEdit->setMinimum(d->m_model->m_minimumDate.year()); + d->updateMonthMenu(); + QDate newDate = d->m_model->m_date; + if (oldDate != newDate) { + d->update(); + d->showMonth(newDate.year(), newDate.month()); + d->m_navigator->setDate(newDate); + emit selectionChanged(); + } +} + +/*! + \property QCalendarWidget::maximumDate + \brief the maximum date of the currently specified date range. + + The user will not be able to select a date which is after the + currently set maximum date. + + \table + \row + \o \image qcalendarwidget-maximum.png + \row + \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 2 + \endtable + + By default, the maximum date is the last day the QDate class can + handle. + + When setting a maximum date, the minimumDate and selectedDate + properties are adjusted if the selection range becomes invalid. If + the provided date is not a valid QDate object, the + setMaximumDate() function does nothing. + + \sa setDateRange() +*/ + +QDate QCalendarWidget::maximumDate() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_maximumDate; +} + +void QCalendarWidget::setMaximumDate(const QDate &date) +{ + Q_D(QCalendarWidget); + if (!date.isValid() || d->m_model->m_maximumDate == date) + return; + + QDate oldDate = d->m_model->m_date; + d->m_model->setMaximumDate(date); + d->yearEdit->setMaximum(d->m_model->m_maximumDate.year()); + d->updateMonthMenu(); + QDate newDate = d->m_model->m_date; + if (oldDate != newDate) { + d->update(); + d->showMonth(newDate.year(), newDate.month()); + d->m_navigator->setDate(newDate); + emit selectionChanged(); + } +} + +/*! + Defines a date range by setting the minimumDate and maximumDate + properties. + + The date range restricts the user selection, i.e. the user can + only select dates within the specified date range. Note that + + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 3 + + is analogous to + + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 4 + + If either the \a min or \a max parameters are not valid QDate + objects, this function does nothing. + + \sa setMinimumDate(), setMaximumDate() +*/ + +void QCalendarWidget::setDateRange(const QDate &min, const QDate &max) +{ + Q_D(QCalendarWidget); + if (d->m_model->m_minimumDate == min && d->m_model->m_maximumDate == max) + return; + if (!min.isValid() || !max.isValid()) + return; + + QDate minimum = min; + QDate maximum = max; + if (min > max) { + minimum = max; + maximum = min; + } + + QDate oldDate = d->m_model->m_date; + d->m_model->setRange(min, max); + d->yearEdit->setMinimum(d->m_model->m_minimumDate.year()); + d->yearEdit->setMaximum(d->m_model->m_maximumDate.year()); + d->updateMonthMenu(); + QDate newDate = d->m_model->m_date; + if (oldDate != newDate) { + d->update(); + d->showMonth(newDate.year(), newDate.month()); + d->m_navigator->setDate(newDate); + emit selectionChanged(); + } +} + + +/*! \enum QCalendarWidget::HorizontalHeaderFormat + + This enum type defines the various formats the horizontal header can display. + + \value SingleLetterDayNames The header displays a single letter abbreviation for day names (e.g. M for Monday). + \value ShortDayNames The header displays a short abbreviation for day names (e.g. Mon for Monday). + \value LongDayNames The header displays complete day names (e.g. Monday). + \value NoHorizontalHeader The header is hidden. + + \sa horizontalHeaderFormat(), VerticalHeaderFormat +*/ + +/*! + \property QCalendarWidget::horizontalHeaderFormat + \brief the format of the horizontal header. + + The default value is QCalendarWidget::ShortDayNames. +*/ + +void QCalendarWidget::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format) +{ + Q_D(QCalendarWidget); + if (d->m_model->m_horizontalHeaderFormat == format) + return; + + d->m_model->setHorizontalHeaderFormat(format); + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +QCalendarWidget::HorizontalHeaderFormat QCalendarWidget::horizontalHeaderFormat() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_horizontalHeaderFormat; +} + + +/*! + \enum QCalendarWidget::VerticalHeaderFormat + + This enum type defines the various formats the vertical header can display. + + \value ISOWeekNumbers The header displays ISO week numbers as described by \l QDate::weekNumber(). + \value NoVerticalHeader The header is hidden. + + \sa verticalHeaderFormat(), HorizontalHeaderFormat +*/ + +/*! + \property QCalendarWidget::verticalHeaderFormat + \brief the format of the vertical header. + + The default value is QCalendarWidget::ISOWeekNumber. +*/ + +QCalendarWidget::VerticalHeaderFormat QCalendarWidget::verticalHeaderFormat() const +{ + Q_D(const QCalendarWidget); + bool shown = d->m_model->weekNumbersShown(); + if (shown) + return QCalendarWidget::ISOWeekNumbers; + return QCalendarWidget::NoVerticalHeader; +} + +void QCalendarWidget::setVerticalHeaderFormat(QCalendarWidget::VerticalHeaderFormat format) +{ + Q_D(QCalendarWidget); + bool show = false; + if (format == QCalendarWidget::ISOWeekNumbers) + show = true; + if (d->m_model->weekNumbersShown() == show) + return; + d->m_model->setWeekNumbersShown(show); + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + \property QCalendarWidget::gridVisible + \brief whether the table grid is displayed. + + \table + \row + \o \inlineimage qcalendarwidget-grid.png + \row + \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 5 + \endtable + + The default value is false. +*/ + +bool QCalendarWidget::isGridVisible() const +{ + Q_D(const QCalendarWidget); + return d->m_view->showGrid(); +} + +void QCalendarWidget::setGridVisible(bool show) +{ + Q_D(QCalendarWidget); + d->m_view->setShowGrid(show); + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + \property QCalendarWidget::selectionMode + \brief the type of selection the user can make in the calendar + + When this property is set to SingleSelection, the user can select a date + within the minimum and maximum allowed dates, using either the mouse or + the keyboard. + + When the property is set to NoSelection, the user will be unable to select + dates, but they can still be selected programmatically. Note that the date + that is selected when the property is set to NoSelection will still be + the selected date of the calendar. + + The default value is SingleSelection. +*/ + +QCalendarWidget::SelectionMode QCalendarWidget::selectionMode() const +{ + Q_D(const QCalendarWidget); + return d->m_view->readOnly ? QCalendarWidget::NoSelection : QCalendarWidget::SingleSelection; +} + +void QCalendarWidget::setSelectionMode(SelectionMode mode) +{ + Q_D(QCalendarWidget); + d->m_view->readOnly = (mode == QCalendarWidget::NoSelection); + d->setNavigatorEnabled(isDateEditEnabled() && (selectionMode() != QCalendarWidget::NoSelection)); + d->update(); +} + +/*! + \property QCalendarWidget::firstDayOfWeek + \brief a value identifying the day displayed in the first column. + + By default, the day displayed in the first column is Sunday +*/ + +void QCalendarWidget::setFirstDayOfWeek(Qt::DayOfWeek dayOfWeek) +{ + Q_D(QCalendarWidget); + if ((Qt::DayOfWeek)d->m_model->firstColumnDay() == dayOfWeek) + return; + + d->m_model->setFirstColumnDay(dayOfWeek); + d->update(); +} + +Qt::DayOfWeek QCalendarWidget::firstDayOfWeek() const +{ + Q_D(const QCalendarWidget); + return (Qt::DayOfWeek)d->m_model->firstColumnDay(); +} + +/*! + Returns the text char format for rendering the header. +*/ +QTextCharFormat QCalendarWidget::headerTextFormat() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_headerFormat; +} + +/*! + Sets the text char format for rendering the header to \a format. + If you also set a weekday text format, this format's foreground and + background color will take precedence over the header's format. + The other formatting information will still be decided by + the header's format. +*/ +void QCalendarWidget::setHeaderTextFormat(const QTextCharFormat &format) +{ + Q_D(QCalendarWidget); + d->m_model->m_headerFormat = format; + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + Returns the text char format for rendering of day in the week \a dayOfWeek. + + \sa headerTextFormat() +*/ +QTextCharFormat QCalendarWidget::weekdayTextFormat(Qt::DayOfWeek dayOfWeek) const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_dayFormats.value(dayOfWeek); +} + +/*! + Sets the text char format for rendering of day in the week \a dayOfWeek to \a format. + The format will take precedence over the header format in case of foreground + and background color. Other text formatting information is taken from the headers format. + + \sa setHeaderTextFormat() +*/ +void QCalendarWidget::setWeekdayTextFormat(Qt::DayOfWeek dayOfWeek, const QTextCharFormat &format) +{ + Q_D(QCalendarWidget); + d->m_model->m_dayFormats[dayOfWeek] = format; + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + Returns a QMap from QDate to QTextCharFormat showing all dates + that use a special format that alters their rendering. +*/ +QMap<QDate, QTextCharFormat> QCalendarWidget::dateTextFormat() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_dateFormats; +} + +/*! + Returns a QTextCharFormat for \a date. The char format can be be + empty if the date is not renderd specially. +*/ +QTextCharFormat QCalendarWidget::dateTextFormat(const QDate &date) const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_dateFormats.value(date); +} + +/*! + Sets the format used to render the given \a date to that specified by \a format. + + If \a date is null, all date formats are cleared. +*/ +void QCalendarWidget::setDateTextFormat(const QDate &date, const QTextCharFormat &format) +{ + Q_D(QCalendarWidget); + if ( date.isNull() && !format.isValid() ) + d->m_model->m_dateFormats.clear(); + else + d->m_model->m_dateFormats[date] = format; + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + \property QCalendarWidget::dateEditEnabled + \brief whether the date edit popup is enabled + \since 4.3 + + If this property is enabled, pressing a non-modifier key will cause a + date edit to popup if the calendar widget has focus, allowing the user + to specify a date in the form specified by the current locale. + + By default, this property is enabled. + + The date edit is simpler in appearance than QDateEdit, but allows the + user to navigate between fields using the left and right cursor keys, + increment and decrement individual fields using the up and down cursor + keys, and enter values directly using the number keys. + + \sa QCalendarWidget::dateEditAcceptDelay +*/ +bool QCalendarWidget::isDateEditEnabled() const +{ + Q_D(const QCalendarWidget); + return d->m_dateEditEnabled; +} + +void QCalendarWidget::setDateEditEnabled(bool enable) +{ + Q_D(QCalendarWidget); + if (isDateEditEnabled() == enable) + return; + + d->m_dateEditEnabled = enable; + + d->setNavigatorEnabled(enable && (selectionMode() != QCalendarWidget::NoSelection)); +} + +/*! + \property QCalendarWidget::dateEditAcceptDelay + \brief the time an inactive date edit is shown before its contents are accepted + \since 4.3 + + If the calendar widget's \l{dateEditEnabled}{date edit is enabled}, this + property specifies the amount of time (in millseconds) that the date edit + remains open after the most recent user input. Once this time has elapsed, + the date specified in the date edit is accepted and the popup is closed. + + By default, the delay is defined to be 1500 milliseconds (1.5 seconds). +*/ +int QCalendarWidget::dateEditAcceptDelay() const +{ + Q_D(const QCalendarWidget); + return d->m_navigator->dateEditAcceptDelay(); +} + +void QCalendarWidget::setDateEditAcceptDelay(int delay) +{ + Q_D(QCalendarWidget); + d->m_navigator->setDateEditAcceptDelay(delay); +} + +/*! + \since 4.4 + + Updates the cell specified by the given \a date unless updates + are disabled or the cell is hidden. + + \sa updateCells(), yearShown(), monthShown() +*/ +void QCalendarWidget::updateCell(const QDate &date) +{ + if (!date.isValid()) { + qWarning("QCalendarWidget::updateCell: Invalid date"); + return; + } + + if (!isVisible()) + return; + + Q_D(QCalendarWidget); + int row, column; + d->m_model->cellForDate(date, &row, &column); + if (row == -1 || column == -1) + return; + + QModelIndex modelIndex = d->m_model->index(row, column); + if (!modelIndex.isValid()) + return; + + d->m_view->viewport()->update(d->m_view->visualRect(modelIndex)); +} + +/*! + \since 4.4 + + Updates all visible cells unless updates are disabled. + + \sa updateCell() +*/ +void QCalendarWidget::updateCells() +{ + Q_D(QCalendarWidget); + if (isVisible()) + d->m_view->viewport()->update(); +} + +/*! + \fn void QCalendarWidget::selectionChanged() + + This signal is emitted when the currently selected date is + changed. + + The currently selected date can be changed by the user using the + mouse or keyboard, or by the programmer using setSelectedDate(). + + \sa selectedDate() +*/ + +/*! + \fn void QCalendarWidget::currentPageChanged(int year, int month) + + This signal is emitted when the currently shown month is changed. + The new \a year and \a month are passed as parameters. + + \sa setCurrentPage() +*/ + +/*! + \fn void QCalendarWidget::activated(const QDate &date) + + This signal is emitted whenever the user presses the Return or + Enter key or double-clicks a \a date in the calendar + widget. +*/ + +/*! + \fn void QCalendarWidget::clicked(const QDate &date) + + This signal is emitted when a mouse button is clicked. The date + the mouse was clicked on is specified by \a date. The signal is + only emitted when clicked on a valid date, e.g., dates are not + outside the minimumDate() and maximumDate(). If the selection mode + is NoSelection, this signal will not be emitted. + +*/ + +/*! + \property QCalendarWidget::headerVisible + \brief whether the navigation bar is shown or not + + \obsolete + + Use navigationBarVisible() instead. + + By default, this property is true. +*/ + +/*! + \obsolete + + Use setNavigationBarVisible() instead. +*/ +bool QCalendarWidget::isHeaderVisible() const +{ + Q_D(const QCalendarWidget); + return d->navBarVisible; +} + +/*! + \obsolete + + Use setNavigationBarVisible() instead. + +*/ +void QCalendarWidget::setHeaderVisible(bool visible) +{ + setNavigationBarVisible(visible); +} + +/*! + \property QCalendarWidget::navigationBarVisible + \brief whether the navigation bar is shown or not + + \since 4.3 + + When this property is true (the default), the next month, + previous month, month selection, year selection controls are + shown on top. + + When the property is set to false, these controls are hidden. +*/ + +void QCalendarWidget::setNavigationBarVisible(bool visible) +{ + Q_D(QCalendarWidget); + d->navBarVisible = visible; + d->cachedSizeHint = QSize(); + d->navBarBackground->setVisible(visible); + updateGeometry(); +} + +/*! + \reimp +*/ +bool QCalendarWidget::event(QEvent *event) +{ + Q_D(QCalendarWidget); + switch (event->type()) { + case QEvent::LayoutDirectionChange: + d->updateButtonIcons(); + case QEvent::LocaleChange: + d->cachedSizeHint = QSize(); + d->updateMonthMenuNames(); + d->updateNavigationBar(); + d->m_view->updateGeometry(); + break; + case QEvent::FontChange: + case QEvent::ApplicationFontChange: + d->cachedSizeHint = QSize(); + d->m_view->updateGeometry(); + break; + case QEvent::StyleChange: + d->cachedSizeHint = QSize(); + d->m_view->updateGeometry(); + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +bool QCalendarWidget::eventFilter(QObject *watched, QEvent *event) +{ + Q_D(QCalendarWidget); + if (event->type() == QEvent::MouseButtonPress && d->yearEdit->hasFocus() && !QRect(d->yearEdit->mapToGlobal(QPoint(0, 0)), d->yearEdit->size()).contains(static_cast<QMouseEvent *>(event)->globalPos())) { + event->accept(); + d->_q_yearEditingFinished(); + setFocus(); + return true; + } + return QWidget::eventFilter(watched, event); +} + +/*! + \reimp +*/ +void QCalendarWidget::mousePressEvent(QMouseEvent *event) +{ + setAttribute(Qt::WA_NoMouseReplay); + QWidget::mousePressEvent(event); + setFocus(); +} + +/*! + \reimp +*/ +void QCalendarWidget::resizeEvent(QResizeEvent * event) +{ + Q_D(QCalendarWidget); + + // XXX Should really use a QWidgetStack for yearEdit and yearButton, + // XXX here we hide the year edit when the layout is likely to break + // XXX the manual positioning of the yearEdit over the yearButton. + if(d->yearEdit->isVisible() && event->size().width() != event->oldSize().width()) + d->_q_yearEditingFinished(); + + QWidget::resizeEvent(event); +} + +/*! + \reimp +*/ +void QCalendarWidget::keyPressEvent(QKeyEvent * event) +{ + Q_D(QCalendarWidget); + if(d->yearEdit->isVisible()&& event->key() == Qt::Key_Escape) + { + d->yearEdit->setValue(yearShown()); + d->_q_yearEditingFinished(); + return; + } + QWidget::keyPressEvent(event); +} + +QT_END_NAMESPACE + +#include "qcalendarwidget.moc" +#include "moc_qcalendarwidget.cpp" + +#endif //QT_NO_CALENDARWIDGET diff --git a/src/gui/widgets/qcalendarwidget.h b/src/gui/widgets/qcalendarwidget.h new file mode 100644 index 0000000..05c2344 --- /dev/null +++ b/src/gui/widgets/qcalendarwidget.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCALENDARWIDGET_H +#define QCALENDARWIDGET_H + +#include <QtGui/qwidget.h> +#include <QtCore/qdatetime.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_CALENDARWIDGET + +class QDate; +class QTextCharFormat; +class QCalendarWidgetPrivate; + +class Q_GUI_EXPORT QCalendarWidget : public QWidget +{ + Q_OBJECT + Q_ENUMS(Qt::DayOfWeek) + Q_ENUMS(HorizontalHeaderFormat) + Q_ENUMS(VerticalHeaderFormat) + Q_ENUMS(SelectionMode) + Q_PROPERTY(QDate selectedDate READ selectedDate WRITE setSelectedDate) + Q_PROPERTY(QDate minimumDate READ minimumDate WRITE setMinimumDate) + Q_PROPERTY(QDate maximumDate READ maximumDate WRITE setMaximumDate) + Q_PROPERTY(Qt::DayOfWeek firstDayOfWeek READ firstDayOfWeek WRITE setFirstDayOfWeek) + Q_PROPERTY(bool gridVisible READ isGridVisible WRITE setGridVisible) + Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode) + Q_PROPERTY(HorizontalHeaderFormat horizontalHeaderFormat READ horizontalHeaderFormat WRITE setHorizontalHeaderFormat) + Q_PROPERTY(VerticalHeaderFormat verticalHeaderFormat READ verticalHeaderFormat WRITE setVerticalHeaderFormat) + Q_PROPERTY(bool headerVisible READ isHeaderVisible WRITE setHeaderVisible STORED false DESIGNABLE false) // obsolete + Q_PROPERTY(bool navigationBarVisible READ isNavigationBarVisible WRITE setNavigationBarVisible) + Q_PROPERTY(bool dateEditEnabled READ isDateEditEnabled WRITE setDateEditEnabled) + Q_PROPERTY(int dateEditAcceptDelay READ dateEditAcceptDelay WRITE setDateEditAcceptDelay) + +public: + enum HorizontalHeaderFormat { + NoHorizontalHeader, + SingleLetterDayNames, + ShortDayNames, + LongDayNames + }; + + enum VerticalHeaderFormat { + NoVerticalHeader, + ISOWeekNumbers + }; + + enum SelectionMode { + NoSelection, + SingleSelection + }; + + explicit QCalendarWidget(QWidget *parent = 0); + ~QCalendarWidget(); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + QDate selectedDate() const; + + int yearShown() const; + int monthShown() const; + + QDate minimumDate() const; + void setMinimumDate(const QDate &date); + + QDate maximumDate() const; + void setMaximumDate(const QDate &date); + + Qt::DayOfWeek firstDayOfWeek() const; + void setFirstDayOfWeek(Qt::DayOfWeek dayOfWeek); + + // ### Qt 5: eliminate these two + bool isHeaderVisible() const; + void setHeaderVisible(bool show); + + inline bool isNavigationBarVisible() const { return isHeaderVisible(); } + + bool isGridVisible() const; + + SelectionMode selectionMode() const; + void setSelectionMode(SelectionMode mode); + + HorizontalHeaderFormat horizontalHeaderFormat() const; + void setHorizontalHeaderFormat(HorizontalHeaderFormat format); + + VerticalHeaderFormat verticalHeaderFormat() const; + void setVerticalHeaderFormat(VerticalHeaderFormat format); + + QTextCharFormat headerTextFormat() const; + void setHeaderTextFormat(const QTextCharFormat &format); + + QTextCharFormat weekdayTextFormat(Qt::DayOfWeek dayOfWeek) const; + void setWeekdayTextFormat(Qt::DayOfWeek dayOfWeek, const QTextCharFormat &format); + + QMap<QDate, QTextCharFormat> dateTextFormat() const; + QTextCharFormat dateTextFormat(const QDate &date) const; + void setDateTextFormat(const QDate &date, const QTextCharFormat &format); + + bool isDateEditEnabled() const; + void setDateEditEnabled(bool enable); + + int dateEditAcceptDelay() const; + void setDateEditAcceptDelay(int delay); + +protected: + bool event(QEvent *event); + bool eventFilter(QObject *watched, QEvent *event); + void mousePressEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent * event); + void keyPressEvent(QKeyEvent * event); + + virtual void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const; + void updateCell(const QDate &date); + void updateCells(); + +public Q_SLOTS: + void setSelectedDate(const QDate &date); + void setDateRange(const QDate &min, const QDate &max); + void setCurrentPage(int year, int month); + void setGridVisible(bool show); + void setNavigationBarVisible(bool visible); + void showNextMonth(); + void showPreviousMonth(); + void showNextYear(); + void showPreviousYear(); + void showSelectedDate(); + void showToday(); + +Q_SIGNALS: + void selectionChanged(); + void clicked(const QDate &date); + void activated(const QDate &date); + void currentPageChanged(int year, int month); + +private: + Q_DECLARE_PRIVATE(QCalendarWidget) + Q_DISABLE_COPY(QCalendarWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_slotShowDate(const QDate &date)) + Q_PRIVATE_SLOT(d_func(), void _q_slotChangeDate(const QDate &date)) + Q_PRIVATE_SLOT(d_func(), void _q_slotChangeDate(const QDate &date, bool changeMonth)) + Q_PRIVATE_SLOT(d_func(), void _q_editingFinished()) + Q_PRIVATE_SLOT(d_func(), void _q_prevMonthClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_nextMonthClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_yearEditingFinished()) + Q_PRIVATE_SLOT(d_func(), void _q_yearClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_monthChanged(QAction *act)) + +}; + +#endif // QT_NO_CALENDARWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCALENDARWIDGET_H + diff --git a/src/gui/widgets/qcheckbox.cpp b/src/gui/widgets/qcheckbox.cpp new file mode 100644 index 0000000..1998f9f --- /dev/null +++ b/src/gui/widgets/qcheckbox.cpp @@ -0,0 +1,425 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcheckbox.h" +#include "qapplication.h" +#include "qbitmap.h" +#include "qicon.h" +#include "qstylepainter.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qevent.h" + +#include "private/qabstractbutton_p.h" + +QT_BEGIN_NAMESPACE + +class QCheckBoxPrivate : public QAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QCheckBox) +public: + QCheckBoxPrivate() + : QAbstractButtonPrivate(QSizePolicy::CheckBox), tristate(false), noChange(false), + hovering(true), publishedState(Qt::Unchecked) {} + + uint tristate : 1; + uint noChange : 1; + uint hovering : 1; + uint publishedState : 2; + + void init(); +}; + +/*! + \class QCheckBox + \brief The QCheckBox widget provides a checkbox with a text label. + + \ingroup basicwidgets + \mainclass + + A QCheckBox is an option button that can be switched on (checked) + or off (unchecked). Checkboxes are typically used to represent + features in an application that can be enabled or disabled without + affecting others, but different types of behavior can be + implemented. + + A QButtonGroup can be used to group check buttons visually. + + Whenever a checkbox is checked or cleared it emits the signal + stateChanged(). Connect to this signal if you want to trigger an + action each time the checkbox changes state. You can use + isChecked() to query whether or not a checkbox is checked. + + In addition to the usual checked and unchecked states, QCheckBox + optionally provides a third state to indicate "no change". This + is useful whenever you need to give the user the option of neither + checking nor unchecking a checkbox. If you need this third state, + enable it with setTristate(), and use checkState() to query the current + toggle state. + + Just like QPushButton, a checkbox displays text, and optionally a + small icon. The icon is set with setIcon(). The text can be set in + the constructor or with setText(). A shortcut key can be specified + by preceding the preferred character with an ampersand. For + example: + + \snippet doc/src/snippets/code/src_gui_widgets_qcheckbox.cpp 0 + + In this example the shortcut is \e{Alt+A}. See the \l + {QShortcut#mnemonic}{QShortcut} documentation for details (to + display an actual ampersand, use '&&'). + + Important inherited functions: text(), setText(), text(), + pixmap(), setPixmap(), accel(), setAccel(), isToggleButton(), + setDown(), isDown(), isOn(), checkState(), autoRepeat(), + isExclusiveToggle(), group(), setAutoRepeat(), toggle(), + pressed(), released(), clicked(), toggled(), checkState(), and + stateChanged(). + + \table 100% + \row \o \inlineimage macintosh-checkbox.png Screenshot of a Macintosh style checkbox + \o A checkbox shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage windows-checkbox.png Screenshot of a Windows XP style checkbox + \o A checkbox shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage plastique-checkbox.png Screenshot of a Plastique style checkbox + \o A checkbox shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QAbstractButton, QRadioButton, {fowler}{GUI Design Handbook: Check Box} +*/ + +/*! + \enum QCheckBox::ToggleState + \compat + + \value Off Use Qt::Unchecked instead. + \value NoChange Use Qt::PartiallyChecked instead. + \value On Use Qt::Checked instead. +*/ + +/*! + \fn void QCheckBox::stateChanged(int state) + + This signal is emitted whenever the check box's state changes, + i.e. whenever the user checks or unchecks it. + + \a state contains the check box's new Qt::CheckState. +*/ + +/*! + \property QCheckBox::tristate + \brief whether the checkbox is a tri-state checkbox + + The default is false; i.e. the checkbox has only two states. +*/ + +void QCheckBoxPrivate::init() +{ + Q_Q(QCheckBox); + q->setCheckable(true); + q->setMouseTracking(true); + q->setForegroundRole(QPalette::WindowText); + setLayoutItemMargins(QStyle::SE_CheckBoxLayoutItem); +} + +/*! + Initialize \a option with the values from this QCheckBox. This method is useful + for subclasses when they need a QStyleOptionButton, but don't want to fill + in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QCheckBox::initStyleOption(QStyleOptionButton *option) const +{ + if (!option) + return; + Q_D(const QCheckBox); + option->initFrom(this); + if (d->down) + option->state |= QStyle::State_Sunken; + if (d->tristate && d->noChange) + option->state |= QStyle::State_NoChange; + else + option->state |= d->checked ? QStyle::State_On : QStyle::State_Off; + if (testAttribute(Qt::WA_Hover) && underMouse()) { + if (d->hovering) + option->state |= QStyle::State_MouseOver; + else + option->state &= ~QStyle::State_MouseOver; + } + option->text = d->text; + option->icon = d->icon; + option->iconSize = iconSize(); +} + +/*! + Constructs a checkbox with the given \a parent, but with no text. + + The \a parent argument is passed on to the QAbstractButton constructor. +*/ + +QCheckBox::QCheckBox(QWidget *parent) + : QAbstractButton (*new QCheckBoxPrivate, parent) +{ + Q_D(QCheckBox); + d->init(); +} + +/*! + Constructs a checkbox with the given \a parent and \a text. + + The \a parent argument is passed on to the QAbstractButton constructor. +*/ + +QCheckBox::QCheckBox(const QString &text, QWidget *parent) + : QAbstractButton (*new QCheckBoxPrivate, parent) +{ + Q_D(QCheckBox); + d->init(); + setText(text); +} + +void QCheckBox::setTristate(bool y) +{ + Q_D(QCheckBox); + d->tristate = y; +} + +bool QCheckBox::isTristate() const +{ + Q_D(const QCheckBox); + return d->tristate; +} + + +/*! + Returns the check box's check state. + If you do not need tristate support, you can also + use \l QAbstractButton::isChecked() which returns + a boolean. + + \sa setCheckState() Qt::CheckState +*/ +Qt::CheckState QCheckBox::checkState() const +{ + Q_D(const QCheckBox); + if (d->tristate && d->noChange) + return Qt::PartiallyChecked; + return d->checked ? Qt::Checked : Qt::Unchecked; +} + +/*! + Sets the check box's check state to \a state. + If you do not need tristate support, you can also + use \l QAbstractButton::setChecked() which takes + a boolean. + + \sa checkState() Qt::CheckState +*/ +void QCheckBox::setCheckState(Qt::CheckState state) +{ + Q_D(QCheckBox); + if (state == Qt::PartiallyChecked) { + d->tristate = true; + d->noChange = true; + } else { + d->noChange = false; + } + d->blockRefresh = true; + setChecked(state != Qt::Unchecked); + d->blockRefresh = false; + d->refresh(); + if ((uint)state != d->publishedState) { + d->publishedState = state; + emit stateChanged(state); + } +} + + +/*!\reimp +*/ +QSize QCheckBox::sizeHint() const +{ + Q_D(const QCheckBox); + if (d->sizeHint.isValid()) + return d->sizeHint; + ensurePolished(); + QFontMetrics fm = fontMetrics(); + QStyleOptionButton opt; + initStyleOption(&opt); + QSize sz = style()->itemTextRect(fm, QRect(0, 0, 1, 1), Qt::TextShowMnemonic, false, + text()).size(); + if (!opt.icon.isNull()) + sz = QSize(sz.width() + opt.iconSize.width() + 4, qMax(sz.height(), opt.iconSize.height())); + d->sizeHint = (style()->sizeFromContents(QStyle::CT_CheckBox, &opt, sz, this) + .expandedTo(QApplication::globalStrut())); + return d->sizeHint; +} + +/*!\reimp +*/ +void QCheckBox::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionButton opt; + initStyleOption(&opt); + p.drawControl(QStyle::CE_CheckBox, opt); +} + +/*! + \reimp +*/ +void QCheckBox::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QCheckBox); + if (testAttribute(Qt::WA_Hover)) { + bool hit = false; + if (underMouse()) + hit = hitButton(e->pos()); + + if (hit != d->hovering) { + update(rect()); + d->hovering = hit; + } + } + + QAbstractButton::mouseMoveEvent(e); +} + + +/*!\reimp*/ +bool QCheckBox::hitButton(const QPoint &pos) const +{ + QStyleOptionButton opt; + initStyleOption(&opt); + return style()->subElementRect(QStyle::SE_CheckBoxClickRect, &opt, this).contains(pos); +} + +/*!\reimp*/ +void QCheckBox::checkStateSet() +{ + Q_D(QCheckBox); + d->noChange = false; + Qt::CheckState state = checkState(); + if ((uint)state != d->publishedState) { + d->publishedState = state; + emit stateChanged(state); + } +} + +/*!\reimp*/ +void QCheckBox::nextCheckState() +{ + Q_D(QCheckBox); + if (d->tristate) + setCheckState((Qt::CheckState)((checkState() + 1) % 3)); + else { + QAbstractButton::nextCheckState(); + QCheckBox::checkStateSet(); + } +} + +/*! + \reimp +*/ +bool QCheckBox::event(QEvent *e) +{ + Q_D(QCheckBox); + if (e->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || e->type() == QEvent::MacSizeChange +#endif + ) + d->setLayoutItemMargins(QStyle::SE_CheckBoxLayoutItem); + return QAbstractButton::event(e); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QCheckBox::QCheckBox(QWidget *parent, const char* name) + : QAbstractButton (*new QCheckBoxPrivate, parent) +{ + Q_D(QCheckBox); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QCheckBox::QCheckBox(const QString &text, QWidget *parent, const char* name) + : QAbstractButton (*new QCheckBoxPrivate, parent) +{ + Q_D(QCheckBox); + setObjectName(QString::fromAscii(name)); + d->init(); + setText(text); +} + +#endif + + +/*! + \fn void QCheckBox::setNoChange() + \compat + + Use setCheckState() instead. +*/ + +/*! + \fn void QCheckBox::setState(ToggleState state) + \compat + + Use setCheckState() instead. +*/ + +/*! + \fn QCheckBox::ToggleState QCheckBox::state() const + \compat + + Use checkState() instead. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qcheckbox.h b/src/gui/widgets/qcheckbox.h new file mode 100644 index 0000000..fcab7a7 --- /dev/null +++ b/src/gui/widgets/qcheckbox.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCHECKBOX_H +#define QCHECKBOX_H + +#include <QtGui/qabstractbutton.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QCheckBoxPrivate; +class QStyleOptionButton; + +class Q_GUI_EXPORT QCheckBox : public QAbstractButton +{ + Q_OBJECT + + Q_PROPERTY(bool tristate READ isTristate WRITE setTristate) + +public: + explicit QCheckBox(QWidget *parent=0); + explicit QCheckBox(const QString &text, QWidget *parent=0); + + + QSize sizeHint() const; + + void setTristate(bool y = true); + bool isTristate() const; + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + +Q_SIGNALS: + void stateChanged(int); + +protected: + bool event(QEvent *e); + bool hitButton(const QPoint &pos) const; + void checkStateSet(); + void nextCheckState(); + void paintEvent(QPaintEvent *); + void mouseMoveEvent(QMouseEvent *); + void initStyleOption(QStyleOptionButton *option) const; + +#ifdef QT3_SUPPORT +public: + enum ToggleState { + Off = Qt::Unchecked, + NoChange = Qt::PartiallyChecked, + On = Qt::Checked + }; + inline QT3_SUPPORT ToggleState state() const + { return static_cast<QCheckBox::ToggleState>(static_cast<int>(checkState())); } + inline QT3_SUPPORT void setState(ToggleState state) + { setCheckState(static_cast<Qt::CheckState>(static_cast<int>(state))); } + inline QT3_SUPPORT void setNoChange() + { setCheckState(Qt::PartiallyChecked); } + QT3_SUPPORT_CONSTRUCTOR QCheckBox(QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QCheckBox(const QString &text, QWidget *parent, const char* name); +#endif + +private: + Q_DECLARE_PRIVATE(QCheckBox) + Q_DISABLE_COPY(QCheckBox) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCHECKBOX_H diff --git a/src/gui/widgets/qcocoamenu_mac.mm b/src/gui/widgets/qcocoamenu_mac.mm new file mode 100644 index 0000000..c5977e4 --- /dev/null +++ b/src/gui/widgets/qcocoamenu_mac.mm @@ -0,0 +1,187 @@ +/**************************************************************************** + ** + ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + ** + ** This file is part of the QtGui module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + ** + ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + ** + ****************************************************************************/ + +#include "qmacdefines_mac.h" +#include "qapplication.h" +#ifdef QT_MAC_USE_COCOA +#import <private/qcocoamenu_mac_p.h> +#import <private/qcocoamenuloader_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qapplication_p.h> + +#include <QtGui/QMenu> + +QT_FORWARD_DECLARE_CLASS(QAction) +QT_FORWARD_DECLARE_CLASS(QWidget) +QT_FORWARD_DECLARE_CLASS(QApplication) + +@implementation QT_MANGLE_NAMESPACE(QCocoaMenu) + +- (id)initWithQMenu:(QMenu*)menu +{ + self = [super init]; + if (self) { + qmenu = menu; + [self setAutoenablesItems:NO]; + [self setDelegate:self]; + } + return self; +} + +- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item; +{ + Q_UNUSED(menu); + + if (!item) { + // ### According to the docs everything will be highlighted. Not sure what we should do in + // Qt, so just return. + return; + } + + if (QAction *action = reinterpret_cast<QAction *>([item tag])) + action->activate(QAction::Hover); +} + +- (void)menuWillOpen:(NSMenu*)menu; +{ + while (QWidget *popup + = QApplication::activePopupWidget()) + popup->close(); + qt_mac_emit_menuSignals(((QT_MANGLE_NAMESPACE(QCocoaMenu) *)menu)->qmenu, true); +} + +- (void)menuWillClose:(NSMenu*)menu; +{ + qt_mac_emit_menuSignals(((QT_MANGLE_NAMESPACE(QCocoaMenu) *)menu)->qmenu, false); +} + +- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier + whichItem:(NSMenuItem**)outItem +{ + for (NSMenuItem *item in [menu itemArray]) { + if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) + continue; + if ([item hasSubmenu]) { + if ([self hasShortcut:[item submenu] + forKey:key + forModifiers:modifier whichItem:outItem]) { + if (outItem) + *outItem = item; + return YES; + } + } + NSString *menuKey = [item keyEquivalent]; + if (menuKey && NSOrderedSame == [menuKey compare:key] + && (modifier == [item keyEquivalentModifierMask])) { + if (outItem) + *outItem = item; + return YES; + } + } + if (outItem) + *outItem = 0; + return NO; +} + +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action +{ + // Check if the menu actually has a keysequence defined for this key event. + // If it does, then we will first send the key sequence to the QWidget that has focus + // since (in Qt's eyes) it needs to a chance at the key event first. If the widget + // accepts the key event, we then return YES, but set the target and action to be nil, + // which means that the action should not be triggered. In every other case we return + // NO, which means that Cocoa can do as it pleases (i.e., fire the menu action). + NSMenuItem *whichItem; + if ([self hasShortcut:menu + forKey:[event characters] + forModifiers:([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) + whichItem:&whichItem]) { + QWidget *widget = 0; + QAction *qaction = 0; + if (whichItem && [whichItem tag]) { + qaction = reinterpret_cast<QAction *>([whichItem tag]); + } + if (qApp->activePopupWidget()) + widget = (qApp->activePopupWidget()->focusWidget() ? + qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget()); + else if (QApplicationPrivate::focus_widget) + widget = QApplicationPrivate::focus_widget; + if (qaction && widget) { + int key = qaction->shortcut(); + QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)), + Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)); + accel_ev.ignore(); + qt_sendSpontaneousEvent(widget, &accel_ev); + if (accel_ev.isAccepted()) { + *target = nil; + *action = nil; + return YES; + } + } + } + return NO; +} + +@end + +QT_BEGIN_NAMESPACE +extern int qt_mac_menus_open_count; // qmenu_mac.mm + +void qt_mac_emit_menuSignals(QMenu *menu, bool show) +{ + if (!menu) + return; + int delta; + if (show) { + emit menu->aboutToShow(); + delta = 1; + } else { + emit menu->aboutToHide(); + delta = -1; + } + qt_mac_menus_open_count += delta; +} +QT_END_NAMESPACE + +#endif diff --git a/src/gui/widgets/qcocoamenu_mac_p.h b/src/gui/widgets/qcocoamenu_mac_p.h new file mode 100644 index 0000000..1372f09 --- /dev/null +++ b/src/gui/widgets/qcocoamenu_mac_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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 "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import <Cocoa/Cocoa.h> + +QT_FORWARD_DECLARE_CLASS(QMenu) + +@interface QT_MANGLE_NAMESPACE(QCocoaMenu) : NSMenu +{ + QMenu *qmenu; +} +- (id)initWithQMenu:(QMenu*)menu; +- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item; +- (void)menuWillOpen:(NSMenu*)menu; +- (void)menuWillClose:(NSMenu*)menu; +- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier + whichItem:(NSMenuItem**)outItem; +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action; +@end +#endif + diff --git a/src/gui/widgets/qcocoatoolbardelegate_mac.mm b/src/gui/widgets/qcocoatoolbardelegate_mac.mm new file mode 100644 index 0000000..a0ccaf3 --- /dev/null +++ b/src/gui/widgets/qcocoatoolbardelegate_mac.mm @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import <private/qcocoatoolbardelegate_mac_p.h> +#ifdef QT_MAC_USE_COCOA +#include <private/qmainwindowlayout_p.h> +#include <private/qt_mac_p.h> +#include <private/qcocoaview_mac_p.h> +#include <private/qwidget_p.h> +#include <qtoolbar.h> +#include <qlayout.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE +extern QWidgetPrivate *qt_widget_private(QWidget *widget); +QT_END_NAMESPACE + +QT_FORWARD_DECLARE_CLASS(QMainWindowLayout); +QT_FORWARD_DECLARE_CLASS(QToolBar); +QT_FORWARD_DECLARE_CLASS(QCFString); + +@implementation QCocoaToolBarDelegate + +- (id)initWithMainWindowLayout:(QMainWindowLayout *)layout +{ + self = [super init]; + if (self) { + mainWindowLayout = layout; + } + return self; +} + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar +{ + Q_UNUSED(toolbar); + return [NSArray arrayWithObject:@"com.trolltech.qt.nstoolbar-qtoolbar"]; +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar +{ + return [self toolbarAllowedItemIdentifiers:toolbar]; +} + +- (void)toolbarDidRemoveItem:(NSNotification *)notification +{ + NSToolbarItem *item = [[notification userInfo] valueForKey:@"item"]; + mainWindowLayout->unifiedToolbarHash.remove(item); + for (int i = 0; i < mainWindowLayout->toolbarItemsCopy.size(); ++i) { + if (mainWindowLayout->toolbarItemsCopy.at(i) == item) { + // I know about it, so release it. + mainWindowLayout->toolbarItemsCopy.removeAt(i); + mainWindowLayout->qtoolbarsInUnifiedToolbarList.removeAt(i); + [item release]; + break; + } + } +} + +- (NSToolbarItem *)toolbar:(NSToolbar *)nstoolbar itemForItemIdentifier:(NSString *)itemIdentifier + willBeInsertedIntoToolbar:(BOOL)flag +{ + Q_UNUSED(flag); + Q_UNUSED(nstoolbar); + QToolBar *tb = mainWindowLayout->cocoaItemIDToToolbarHash.value(QCFString::toQString(CFStringRef(itemIdentifier))); + NSToolbarItem *item = nil; + if (tb) { + item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier]; + mainWindowLayout->unifiedToolbarHash.insert(item, tb); + } + return item; +} + +- (void)toolbarWillAddItem:(NSNotification *)notification +{ + NSToolbarItem *item = [[notification userInfo] valueForKey:@"item"]; + QToolBar *tb = mainWindowLayout->cocoaItemIDToToolbarHash.value(QCFString::toQString(CFStringRef([item itemIdentifier]))); + if (!tb) + return; // I can't really do anything about this. + [item retain]; + [item setView:QT_PREPEND_NAMESPACE(qt_mac_nativeview_for)(tb)]; + + NSArray *items = [[qt_mac_window_for(mainWindowLayout->layoutState.mainWindow->window()) toolbar] items]; + int someIndex = 0; + bool foundItem = false; + for (NSToolbarItem *i in items) { + if (i == item) { + foundItem = true; + break; + } + ++someIndex; + } + mainWindowLayout->toolbarItemsCopy.insert(someIndex, item); + + // This is synchronization code that was needed in Carbon, but may not be needed anymore here. + QToolBar *toolbar = mainWindowLayout->unifiedToolbarHash.value(item); + if (toolbar) { + int toolbarIndex = mainWindowLayout->qtoolbarsInUnifiedToolbarList.indexOf(toolbar); + if (someIndex != toolbarIndex) { + // Dang, we must be out of sync, rebuild it from the "toolbarItemsCopy" + mainWindowLayout->qtoolbarsInUnifiedToolbarList.clear(); + for (int i = 0; i < mainWindowLayout->toolbarItemsCopy.size(); ++i) { + // This will either append the correct toolbar or an + // null toolbar. This is fine because this list + // is really only kept to make sure that things are but in the right order. + mainWindowLayout->qtoolbarsInUnifiedToolbarList.append( + mainWindowLayout->unifiedToolbarHash.value(mainWindowLayout-> + toolbarItemsCopy.at(i))); + } + } + toolbar->update(); + } +} + +@end +#endif // QT_MAC_USE_COCOA diff --git a/src/gui/widgets/qcocoatoolbardelegate_mac_p.h b/src/gui/widgets/qcocoatoolbardelegate_mac_p.h new file mode 100644 index 0000000..a32e936 --- /dev/null +++ b/src/gui/widgets/qcocoatoolbardelegate_mac_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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 "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import <Cocoa/Cocoa.h> + +QT_BEGIN_NAMESPACE +class QMainWindowLayout; +class QToolBar; +QT_END_NAMESPACE + +@class NSToolbarItem; + +@interface QCocoaToolBarDelegate : NSObject { + QT_PREPEND_NAMESPACE(QMainWindowLayout) *mainWindowLayout; + NSToolbarItem *toolbarItem; +} + +- (id)initWithMainWindowLayout:(QT_PREPEND_NAMESPACE(QMainWindowLayout) *)layout; +@end +#endif diff --git a/src/gui/widgets/qcombobox.cpp b/src/gui/widgets/qcombobox.cpp new file mode 100644 index 0000000..75b8b59 --- /dev/null +++ b/src/gui/widgets/qcombobox.cpp @@ -0,0 +1,3184 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcombobox.h" + +#ifndef QT_NO_COMBOBOX +#include <qstylepainter.h> +#include <qlineedit.h> +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qlistview.h> +#include <qtableview.h> +#include <qitemdelegate.h> +#include <qmap.h> +#include <qmenu.h> +#include <qevent.h> +#include <qlayout.h> +#include <qscrollbar.h> +#include <qtreeview.h> +#include <qheaderview.h> +#ifndef QT_NO_IM +#include "qinputcontext.h" +#endif +#include <private/qcombobox_p.h> +#include <private/qabstractitemmodel_p.h> +#include <qdebug.h> + +#ifdef Q_WS_X11 +#include <private/qt_x11_p.h> +#endif +#if defined(Q_WS_MAC) && !defined(QT_NO_EFFECTS) && !defined(QT_NO_STYLE_MAC) +#include <private/qcore_mac_p.h> +#include <QMacStyle> +#include <private/qt_cocoa_helpers_mac_p.h> +#endif +#ifndef QT_NO_EFFECTS +# include <private/qeffects_p.h> +#endif +QT_BEGIN_NAMESPACE + +extern QHash<QByteArray, QFont> *qt_app_fonts_hash(); + +QComboBoxPrivate::QComboBoxPrivate() + : QWidgetPrivate(), + model(0), + lineEdit(0), + container(0), + insertPolicy(QComboBox::InsertAtBottom), + sizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow), + minimumContentsLength(0), + shownOnce(false), + autoCompletion(true), + duplicatesEnabled(false), + frame(true), + maxVisibleItems(10), + maxCount(INT_MAX), + modelColumn(0), + inserting(false), + arrowState(QStyle::State_None), + hoverControl(QStyle::SC_None), + autoCompletionCaseSensitivity(Qt::CaseInsensitive), + indexBeforeChange(-1) +#ifndef QT_NO_COMPLETER + , completer(0) +#endif +{ +} + +QStyleOptionMenuItem QComboMenuDelegate::getStyleOption(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionMenuItem menuOption; + menuOption.palette = QComboBoxPrivate::viewContainerPalette(mCombo).resolve(QApplication::palette("QMenu")); + menuOption.state = QStyle::State_None; + if (mCombo->window()->isActiveWindow()) + menuOption.state = QStyle::State_Active; + if ((option.state & QStyle::State_Enabled) && (index.model()->flags(index) & Qt::ItemIsEnabled)) + menuOption.state |= QStyle::State_Enabled; + else + menuOption.palette.setCurrentColorGroup(QPalette::Disabled); + if (option.state & QStyle::State_Selected) + menuOption.state |= QStyle::State_Selected; + menuOption.checkType = QStyleOptionMenuItem::NonExclusive; + menuOption.checked = mCombo->currentIndex() == index.row(); + if (QComboBoxDelegate::isSeparator(index)) + menuOption.menuItemType = QStyleOptionMenuItem::Separator; + else + menuOption.menuItemType = QStyleOptionMenuItem::Normal; + + QVariant variant = index.model()->data(index, Qt::DecorationRole); + switch (variant.type()) { + case QVariant::Icon: + menuOption.icon = qvariant_cast<QIcon>(variant); + break; + case QVariant::Color: { + static QPixmap pixmap(option.decorationSize); + pixmap.fill(qvariant_cast<QColor>(variant)); + menuOption.icon = pixmap; + break; } + default: + menuOption.icon = qvariant_cast<QPixmap>(variant); + break; + } + + menuOption.text = index.model()->data(index, Qt::DisplayRole).toString() + .replace(QLatin1Char('&'), QLatin1String("&&")); + menuOption.tabWidth = 0; + menuOption.maxIconWidth = option.decorationSize.width() + 4; + menuOption.menuRect = option.rect; + menuOption.rect = option.rect; + + // Make sure fonts set on the combo box also overrides the font for the popup menu. + if (mCombo->testAttribute(Qt::WA_SetFont) || mCombo->testAttribute(Qt::WA_MacSmallSize) + || mCombo->testAttribute(Qt::WA_MacMiniSize)) + menuOption.font = mCombo->font(); + else + menuOption.font = qt_app_fonts_hash()->value("QComboMenuItem", mCombo->font()); + + menuOption.fontMetrics = QFontMetrics(menuOption.font); + + return menuOption; +} + +#ifdef QT_KEYPAD_NAVIGATION +void QComboBoxPrivate::_q_completerActivated() +{ + Q_Q(QComboBox); + if ( QApplication::keypadNavigationEnabled() + && q->isEditable() + && q->completer() + && q->completer()->completionMode() == QCompleter::UnfilteredPopupCompletion ) { + q->setEditFocus(false); + } +} +#endif + +void QComboBoxPrivate::updateArrow(QStyle::StateFlag state) +{ + Q_Q(QComboBox); + if (arrowState == state) + return; + arrowState = state; + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + q->update(q->style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow, q)); +} + +void QComboBoxPrivate::_q_modelReset() +{ + Q_Q(QComboBox); + if (lineEdit) { + lineEdit->setText(QString()); + updateLineEditGeometry(); + } + q->update(); +} + +void QComboBoxPrivate::_q_modelDestroyed() +{ + model = QAbstractItemModelPrivate::staticEmptyModel(); +} + + +//Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't +QRect QComboBoxPrivate::popupGeometry(int screen) const +{ +#ifdef Q_WS_WIN + return QApplication::desktop()->screenGeometry(screen); +#elif defined Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE) + return QApplication::desktop()->screenGeometry(screen); + else + return QApplication::desktop()->availableGeometry(screen); +#else + return QApplication::desktop()->availableGeometry(screen); +#endif +} + +bool QComboBoxPrivate::updateHoverControl(const QPoint &pos) +{ + + Q_Q(QComboBox); + QRect lastHoverRect = hoverRect; + QStyle::SubControl lastHoverControl = hoverControl; + bool doesHover = q->testAttribute(Qt::WA_Hover); + if (lastHoverControl != newHoverControl(pos) && doesHover) { + q->update(lastHoverRect); + q->update(hoverRect); + return true; + } + return !doesHover; +} + +QStyle::SubControl QComboBoxPrivate::newHoverControl(const QPoint &pos) +{ + Q_Q(QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, pos, q); + hoverRect = (hoverControl != QStyle::SC_None) + ? q->style()->subControlRect(QStyle::CC_ComboBox, &opt, hoverControl, q) + : QRect(); + return hoverControl; +} + +/* + Computes a size hint based on the maximum width + for the items in the combobox. +*/ +int QComboBoxPrivate::computeWidthHint() const +{ + Q_Q(const QComboBox); + + int width = 0; + const int count = q->count(); + const int iconWidth = q->iconSize().width() + 4; + const QFontMetrics &fontMetrics = q->fontMetrics(); + + for (int i = 0; i < count; ++i) { + const int textWidth = fontMetrics.width(q->itemText(i)); + if (q->itemIcon(i).isNull()) + width = (qMax(width, textWidth)); + else + width = (qMax(width, textWidth + iconWidth)); + } + + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + QSize tmp(width, 0); + tmp = q->style()->sizeFromContents(QStyle::CT_ComboBox, &opt, tmp, q); + return tmp.width(); +} + +QSize QComboBoxPrivate::recomputeSizeHint(QSize &sh) const +{ + Q_Q(const QComboBox); + if (!sh.isValid()) { + bool hasIcon = sizeAdjustPolicy == QComboBox::AdjustToMinimumContentsLengthWithIcon ? true : false; + int count = q->count(); + QSize iconSize = q->iconSize(); + const QFontMetrics &fm = q->fontMetrics(); + + // text width + if (&sh == &sizeHint || minimumContentsLength == 0) { + switch (sizeAdjustPolicy) { + case QComboBox::AdjustToContents: + case QComboBox::AdjustToContentsOnFirstShow: + if (count == 0) { + sh.rwidth() = 7 * fm.width(QLatin1Char('x')); + } else { + for (int i = 0; i < count; ++i) { + if (!q->itemIcon(i).isNull()) { + hasIcon = true; + sh.setWidth(qMax(sh.width(), fm.boundingRect(q->itemText(i)).width() + iconSize.width() + 4)); + } else { + sh.setWidth(qMax(sh.width(), fm.boundingRect(q->itemText(i)).width())); + } + } + } + break; + case QComboBox::AdjustToMinimumContentsLength: + for (int i = 0; i < count && !hasIcon; ++i) + hasIcon = !q->itemIcon(i).isNull(); + default: + ; + } + } else { + for (int i = 0; i < count && !hasIcon; ++i) + hasIcon = !q->itemIcon(i).isNull(); + } + if (minimumContentsLength > 0) + sh.setWidth(qMax(sh.width(), minimumContentsLength * fm.width(QLatin1Char('X')) + (hasIcon ? iconSize.width() + 4 : 0))); + + + // height + sh.setHeight(qMax(fm.lineSpacing(), 14) + 2); + if (hasIcon) { + sh.setHeight(qMax(sh.height(), iconSize.height() + 2)); + } + + // add style and strut values + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + sh = q->style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, q); + } + return sh.expandedTo(QApplication::globalStrut()); +} + +void QComboBoxPrivate::adjustComboBoxSize() +{ + viewContainer()->adjustSizeTimer.start(20, container); +} + +void QComboBoxPrivate::updateLayoutDirection() +{ + Q_Q(const QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + Qt::LayoutDirection dir = Qt::LayoutDirection( + q->style()->styleHint(QStyle::SH_ComboBox_LayoutDirection, &opt, q)); + if (lineEdit) + lineEdit->setLayoutDirection(dir); + if (container) + container->setLayoutDirection(dir); +} + + +void QComboBoxPrivateContainer::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() == adjustSizeTimer.timerId()) { + adjustSizeTimer.stop(); + if (combo->sizeAdjustPolicy() == QComboBox::AdjustToContents) { + combo->adjustSize(); + combo->update(); + } + } +} + +void QComboBoxPrivateContainer::resizeEvent(QResizeEvent *e) +{ + QStyleOptionComboBox opt = comboStyleOption(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)) { + QStyleOption myOpt; + myOpt.initFrom(this); + QStyleHintReturnMask mask; + if (combo->style()->styleHint(QStyle::SH_Menu_Mask, &myOpt, this, &mask)) { + setMask(mask.region); + } + } else { + clearMask(); + } + QFrame::resizeEvent(e); +} + +void QComboBoxPrivateContainer::leaveEvent(QEvent *) +{ +// On Mac using the Mac style we want to clear the selection +// when the mouse moves outside the popup. +#ifdef Q_WS_MAC + QStyleOptionComboBox opt = comboStyleOption(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)) + view->clearSelection(); +#endif +} + +QComboBoxPrivateContainer::QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent) + : QFrame(parent, Qt::Popup), combo(parent), view(0), top(0), bottom(0) +{ + // we need the combobox and itemview + Q_ASSERT(parent); + Q_ASSERT(itemView); + + setAttribute(Qt::WA_WindowPropagation); + setAttribute(Qt::WA_X11NetWmWindowTypeCombo); + + // setup container + blockMouseReleaseTimer.setSingleShot(true); + + // we need a vertical layout + QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setSpacing(0); + layout->setMargin(0); + + // set item view + setItemView(itemView); + + // add scroller arrows if style needs them + QStyleOptionComboBox opt = comboStyleOption(); + const bool usePopup = combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo); + if (usePopup) { + top = new QComboBoxPrivateScroller(QAbstractSlider::SliderSingleStepSub, this); + bottom = new QComboBoxPrivateScroller(QAbstractSlider::SliderSingleStepAdd, this); + top->hide(); + bottom->hide(); + } else { + setLineWidth(1); + } + + setFrameStyle(combo->style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, combo)); + + if (top) { + layout->insertWidget(0, top); + connect(top, SIGNAL(doScroll(int)), this, SLOT(scrollItemView(int))); + } + if (bottom) { + layout->addWidget(bottom); + connect(bottom, SIGNAL(doScroll(int)), this, SLOT(scrollItemView(int))); + } + + // Some styles (Mac) have a margin at the top and bottom of the popup. + layout->insertSpacing(0, 0); + layout->addSpacing(0); + updateTopBottomMargin(); +} + +void QComboBoxPrivateContainer::scrollItemView(int action) +{ +#ifndef QT_NO_SCROLLBAR + if (view->verticalScrollBar()) + view->verticalScrollBar()->triggerAction(static_cast<QAbstractSlider::SliderAction>(action)); +#endif +} + +/* + Hides or shows the scrollers when we emulate a popupmenu +*/ +void QComboBoxPrivateContainer::updateScrollers() +{ +#ifndef QT_NO_SCROLLBAR + if (!top || !bottom) + return; + + if (isVisible() == false) + return; + + QStyleOptionComboBox opt = comboStyleOption(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo) && + view->verticalScrollBar()->minimum() < view->verticalScrollBar()->maximum()) { + + bool needTop = view->verticalScrollBar()->value() + > (view->verticalScrollBar()->minimum() + spacing()); + bool needBottom = view->verticalScrollBar()->value() + < (view->verticalScrollBar()->maximum() - spacing()*2); + if (needTop) + top->show(); + else + top->hide(); + if (needBottom) + bottom->show(); + else + bottom->hide(); + } else { + top->hide(); + bottom->hide(); + } +#endif // QT_NO_SCROLLBAR +} + +/* + Cleans up when the view is destroyed. +*/ +void QComboBoxPrivateContainer::viewDestroyed() +{ + view = 0; + setItemView(new QComboBoxListView()); +} + +/* + Sets currentIndex on entered if the LeftButton is not pressed. This + means that if mouseTracking(...) is on, we setCurrentIndex and select + even when LeftButton is not pressed. +*/ +void QComboBoxPrivateContainer::setCurrentIndex(const QModelIndex &index) +{ + if (QComboBoxDelegate::isSeparator(index)) + return; + view->setCurrentIndex(index); +} + +/* + Returns the item view used for the combobox popup. +*/ +QAbstractItemView *QComboBoxPrivateContainer::itemView() const +{ + return view; +} + +/*! + Sets the item view to be used for the combobox popup. +*/ +void QComboBoxPrivateContainer::setItemView(QAbstractItemView *itemView) +{ + Q_ASSERT(itemView); + + // clean up old one + if (view) { + view->removeEventFilter(this); + view->viewport()->removeEventFilter(this); +#ifndef QT_NO_SCROLLBAR + disconnect(view->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(updateScrollers())); + disconnect(view->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), + this, SLOT(updateScrollers())); +#endif + disconnect(view, SIGNAL(entered(QModelIndex)), + this, SLOT(setCurrentIndex(QModelIndex))); + disconnect(view, SIGNAL(destroyed()), + this, SLOT(viewDestroyed())); + delete view; + view = 0; + } + + // setup the item view + view = itemView; + view->setParent(this); + view->setAttribute(Qt::WA_MacShowFocusRect, false); + qobject_cast<QBoxLayout*>(layout())->insertWidget(top ? 2 : 0, view); + view->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + view->installEventFilter(this); + view->viewport()->installEventFilter(this); + view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QStyleOptionComboBox opt = comboStyleOption(); + const bool usePopup = combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo); +#ifndef QT_NO_SCROLLBAR + if (usePopup) + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +#endif + if (combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking, &opt, combo) || + usePopup) { + view->setMouseTracking(true); + } + view->setSelectionMode(QAbstractItemView::SingleSelection); + view->setFrameStyle(QFrame::NoFrame); + view->setLineWidth(0); + view->setEditTriggers(QAbstractItemView::NoEditTriggers); +#ifndef QT_NO_SCROLLBAR + connect(view->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(updateScrollers())); + connect(view->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), + this, SLOT(updateScrollers())); +#endif + connect(view, SIGNAL(entered(QModelIndex)), + this, SLOT(setCurrentIndex(QModelIndex))); + connect(view, SIGNAL(destroyed()), + this, SLOT(viewDestroyed())); +} + +/*! + Returns the spacing between the items in the view. +*/ +int QComboBoxPrivateContainer::spacing() const +{ + QListView *lview = qobject_cast<QListView*>(view); + if (lview) + return lview->spacing(); +#ifndef QT_NO_TABLEVIEW + QTableView *tview = qobject_cast<QTableView*>(view); + if (tview) + return tview->showGrid() ? 1 : 0; +#endif + return 0; +} + +void QComboBoxPrivateContainer::updateTopBottomMargin() +{ + if (!layout() || layout()->count() < 1) + return; + + QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(layout()); + if (!boxLayout) + return; + + const QStyleOptionComboBox opt = comboStyleOption(); + const bool usePopup = combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo); + const int margin = usePopup ? combo->style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, combo) : 0; + + QSpacerItem *topSpacer = boxLayout->itemAt(0)->spacerItem(); + if (topSpacer) + topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); + + QSpacerItem *bottomSpacer = boxLayout->itemAt(boxLayout->count() - 1)->spacerItem(); + if (bottomSpacer && bottomSpacer != topSpacer) + bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); + + boxLayout->invalidate(); +} + +void QComboBoxPrivateContainer::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::StyleChange) { + QStyleOptionComboBox opt = comboStyleOption(); + view->setMouseTracking(combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking, &opt, combo) || + combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)); + setFrameStyle(combo->style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, combo)); + } + QWidget::changeEvent(e); +} + + +bool QComboBoxPrivateContainer::eventFilter(QObject *o, QEvent *e) +{ + switch (e->type()) { + case QEvent::KeyPress: + case QEvent::ShortcutOverride: + switch (static_cast<QKeyEvent*>(e)->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: +#endif + if (view->currentIndex().isValid() && (view->currentIndex().flags() & Qt::ItemIsEnabled) ) { + combo->hidePopup(); + emit itemSelected(view->currentIndex()); + } + return true; + case Qt::Key_Down: + if (!(static_cast<QKeyEvent*>(e)->modifiers() & Qt::AltModifier)) + break; + // fall through + case Qt::Key_F4: + case Qt::Key_Escape: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Back: +#endif + combo->hidePopup(); + return true; + default: + break; + } + break; + case QEvent::MouseMove: { + if (isVisible()) { + QMouseEvent *m = static_cast<QMouseEvent *>(e); + QWidget *widget = static_cast<QWidget *>(o); + QPoint vector = widget->mapToGlobal(m->pos()) - initialClickPosition; + if (vector.manhattanLength() > 9 && blockMouseReleaseTimer.isActive()) + blockMouseReleaseTimer.stop(); + } + break; + } + case QEvent::MouseButtonRelease: { + QMouseEvent *m = static_cast<QMouseEvent *>(e); + if (isVisible() && view->rect().contains(m->pos()) && view->currentIndex().isValid() + && !blockMouseReleaseTimer.isActive() + && (view->currentIndex().flags() & Qt::ItemIsEnabled) + && (view->currentIndex().flags() & Qt::ItemIsSelectable)) { + combo->hidePopup(); + emit itemSelected(view->currentIndex()); + return true; + } + break; + } + default: + break; + } + return QFrame::eventFilter(o, e); +} + +void QComboBoxPrivateContainer::showEvent(QShowEvent *) +{ + combo->update(); +} + +void QComboBoxPrivateContainer::hideEvent(QHideEvent *) +{ + emit resetButton(); + combo->update(); +} + +void QComboBoxPrivateContainer::mousePressEvent(QMouseEvent *e) +{ + + QStyleOptionComboBox opt = comboStyleOption(); + opt.subControls = QStyle::SC_All; + opt.activeSubControls = QStyle::SC_ComboBoxArrow; + QStyle::SubControl sc = combo->style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, + combo->mapFromGlobal(e->globalPos()), + combo); + if ((combo->isEditable() && sc == QStyle::SC_ComboBoxArrow) + || (!combo->isEditable() && sc != QStyle::SC_None)) + setAttribute(Qt::WA_NoMouseReplay); + combo->hidePopup(); +} + +void QComboBoxPrivateContainer::mouseReleaseEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + if (!blockMouseReleaseTimer.isActive()){ + combo->hidePopup(); + emit resetButton(); + } +} + +QStyleOptionComboBox QComboBoxPrivateContainer::comboStyleOption() const +{ + // ### This should use QComboBox's initStyleOption(), but it's protected + // perhaps, we could cheat by having the QCombo private instead? + QStyleOptionComboBox opt; + opt.initFrom(combo); + opt.subControls = QStyle::SC_All; + opt.activeSubControls = QStyle::SC_None; + opt.editable = combo->isEditable(); + return opt; +} + +/*! + \enum QComboBox::InsertPolicy + + This enum specifies what the QComboBox should do when a new string is + entered by the user. + + \value NoInsert The string will not be inserted into the combobox. + \value InsertAtTop The string will be inserted as the first item in the combobox. + \value InsertAtCurrent The current item will be \e replaced by the string. + \value InsertAtBottom The string will be inserted after the last item in the combobox. + \value InsertAfterCurrent The string is inserted after the current item in the combobox. + \value InsertBeforeCurrent The string is inserted before the current item in the combobox. + \value InsertAlphabetically The string is inserted in the alphabetic order in the combobox. + \omitvalue NoInsertion + \omitvalue AtTop + \omitvalue AtCurrent + \omitvalue AtBottom + \omitvalue AfterCurrent + \omitvalue BeforeCurrent +*/ + +/*! + \enum QComboBox::SizeAdjustPolicy + + This enum specifies how the size hint of the QComboBox should + adjust when new content is added or content changes. + + \value AdjustToContents The combobox will always adjust to the contents + \value AdjustToContentsOnFirstShow The combobox will adjust to its contents the first time it is shown. + \value AdjustToMinimumContentsLength Use AdjustToContents or AdjustToContentsOnFirstShow instead. + \value AdjustToMinimumContentsLengthWithIcon The combobox will adjust to \l minimumContentsLength plus space for an icon. For performance reasons use this policy on large models. +*/ + +/*! + \fn void QComboBox::activated(int index) + + This signal is sent when the user chooses an item in the combobox. + The item's \a index is passed. Note that this signal is sent even + when the choice is not changed. If you need to know when the + choice actually changes, use signal currentIndexChanged(). + +*/ + +/*! + \fn void QComboBox::activated(const QString &text) + + This signal is sent when the user chooses an item in the combobox. + The item's \a text is passed. Note that this signal is sent even + when the choice is not changed. If you need to know when the + choice actually changes, use signal currentIndexChanged(). + +*/ + +/*! + \fn void QComboBox::highlighted(int index) + + This signal is sent when an item in the combobox popup list is + highlighted by the user. The item's \a index is passed. +*/ + +/*! + \fn void QComboBox::highlighted(const QString &text) + + This signal is sent when an item in the combobox popup list is + highlighted by the user. The item's \a text is passed. +*/ + +/*! + \fn void QComboBox::currentIndexChanged(int index) + \since 4.1 + + This signal is sent whenever the currentIndex in the combobox + changes either through user interaction or programmatically. The + item's \a index is passed or -1 if the combobox becomes empty or the + currentIndex was reset. +*/ + +/*! + \fn void QComboBox::currentIndexChanged(const QString &text) + \since 4.1 + + This signal is sent whenever the currentIndex in the combobox + changes either through user interaction or programmatically. The + item's \a text is passed. +*/ + +/*! + Constructs a combobox with the given \a parent, using the default + model QStandardItemModel. +*/ +QComboBox::QComboBox(QWidget *parent) + : QWidget(*new QComboBoxPrivate(), parent, 0) +{ + Q_D(QComboBox); + d->init(); +} + +/*! + \internal +*/ +QComboBox::QComboBox(QComboBoxPrivate &dd, QWidget *parent) + : QWidget(dd, parent, 0) +{ + Q_D(QComboBox); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QComboBox::QComboBox(QWidget *parent, const char *name) + : QWidget(*new QComboBoxPrivate(), parent, 0) +{ + Q_D(QComboBox); + d->init(); + setObjectName(QString::fromAscii(name)); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QComboBox::QComboBox(bool rw, QWidget *parent, const char *name) + : QWidget(*new QComboBoxPrivate(), parent, 0) +{ + Q_D(QComboBox); + d->init(); + setEditable(rw); + setObjectName(QString::fromAscii(name)); +} + +#endif //QT3_SUPPORT + +/*! + \class QComboBox + \brief The QComboBox widget is a combined button and popup list. + + \ingroup basicwidgets + \mainclass + + A QComboBox provides a means of presenting a list of options to the user + in a way that takes up the minimum amount of screen space. + + A combobox is a selection widget that displays the current item, + and can pop up a list of selectable items. A combobox may be editable, + allowing the user to modify each item in the list. + + Comboboxes can contain pixmaps as well as strings; the + insertItem() and setItemText() functions are suitably overloaded. + For editable comboboxes, the function clearEditText() is provided, + to clear the displayed string without changing the combobox's + contents. + + There are two signals emitted if the current item of a combobox + changes, currentIndexChanged() and activated(). + currentIndexChanged() is always emitted regardless if the change + was done programmatically or by user interaction, while + activated() is only emitted when the change is caused by user + interaction. The highlighted() signal is emitted when the user + highlights an item in the combobox popup list. All three signals + exist in two versions, one with a QString argument and one with an + \c int argument. If the user selectes or highlights a pixmap, only + the \c int signals are emitted. Whenever the text of an editable + combobox is changed the editTextChanged() signal is emitted. + + When the user enters a new string in an editable combobox, the + widget may or may not insert it, and it can insert it in several + locations. The default policy is is \l AtBottom but you can change + this using setInsertPolicy(). + + It is possible to constrain the input to an editable combobox + using QValidator; see setValidator(). By default, any input is + accepted. + + A combobox can be populated using the insert functions, + insertItem() and insertItems() for example. Items can be + changed with setItemText(). An item can be removed with + removeItem() and all items can be removed with clear(). The text + of the current item is returned by currentText(), and the text of + a numbered item is returned with text(). The current item can be + set with setCurrentIndex(). The number of items in the combobox is + returned by count(); the maximum number of items can be set with + setMaxCount(). You can allow editing using setEditable(). For + editable comboboxes you can set auto-completion using + setCompleter() and whether or not the user can add duplicates + is set with setDuplicatesEnabled(). + + QComboBox uses the \l{Model/View Programming}{model/view + framework} for its popup list and to store its items. By default + a QStandardItemModel stores the items and a QListView subclass + displays the popuplist. You can access the model and view directly + (with model() and view()), but QComboBox also provides functions + to set and get item data (e.g., setItemData() and itemText()). You + can also set a new model and view (with setModel() and setView()). + For the text and icon in the combobox label, the data in the model + that has the Qt::DisplayRole and Qt::DecorationRole is used. + + \image qstyle-comboboxes.png Comboboxes in the different built-in styles. + + \sa QLineEdit, QSpinBox, QRadioButton, QButtonGroup, + {fowler}{GUI Design Handbook: Combo Box, Drop-Down List Box} +*/ + +void QComboBoxPrivate::init() +{ + Q_Q(QComboBox); + q->setFocusPolicy(Qt::WheelFocus); + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed, + QSizePolicy::ComboBox)); + setLayoutItemMargins(QStyle::SE_ComboBoxLayoutItem); + q->setModel(new QStandardItemModel(0, 1, q)); + q->setAttribute(Qt::WA_InputMethodEnabled); +} + +QComboBoxPrivateContainer* QComboBoxPrivate::viewContainer() +{ + if (container) + return container; + + Q_Q(QComboBox); + container = new QComboBoxPrivateContainer(new QComboBoxListView(q), q); + container->itemView()->setModel(model); + container->itemView()->setTextElideMode(Qt::ElideMiddle); + updateDelegate(); + updateLayoutDirection(); + QObject::connect(container, SIGNAL(itemSelected(QModelIndex)), + q, SLOT(_q_itemSelected(QModelIndex))); + QObject::connect(container->itemView()->selectionModel(), + SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitHighlighted(QModelIndex))); + QObject::connect(container, SIGNAL(resetButton()), q, SLOT(_q_resetButton())); + return container; +} + + +void QComboBoxPrivate::_q_resetButton() +{ + updateArrow(QStyle::State_None); +} + +void QComboBoxPrivate::_q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_Q(QComboBox); + if (inserting || topLeft.parent() != root) + return; + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } + + if (currentIndex.row() >= topLeft.row() && currentIndex.row() <= bottomRight.row()) { + if (lineEdit) { + lineEdit->setText(q->itemText(currentIndex.row())); + updateLineEditGeometry(); + } + q->update(); + } +} + +void QComboBoxPrivate::_q_rowsAboutToBeInserted(const QModelIndex & parent, + int /*start*/, int /*end*/) +{ + if (parent != root) + return; + indexBeforeChange = currentIndex.row(); +} + +void QComboBoxPrivate::_q_rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_Q(QComboBox); + if (inserting || parent != root) + return; + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } + + // set current index if combo was previously empty + if (start == 0 && (end - start + 1) == q->count() && !currentIndex.isValid()) { + q->setCurrentIndex(0); + // need to emit changed if model updated index "silently" + } else if (currentIndex.row() != indexBeforeChange) { + q->update(); + _q_emitCurrentIndexChanged(currentIndex); + } +} + +void QComboBoxPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int /*start*/, int /*end*/) +{ + if (parent != root) + return; + + indexBeforeChange = currentIndex.row(); +} + +void QComboBoxPrivate::_q_rowsRemoved(const QModelIndex &parent, int /*start*/, int /*end*/) +{ + Q_Q(QComboBox); + if (parent != root) + return; + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } + + // model has changed the currentIndex + if (currentIndex.row() != indexBeforeChange) { + if (!currentIndex.isValid() && q->count()) { + q->setCurrentIndex(qMin(q->count() - 1, qMax(indexBeforeChange, 0))); + return; + } + if (lineEdit) { + lineEdit->setText(q->itemText(currentIndex.row())); + updateLineEditGeometry(); + } + q->update(); + _q_emitCurrentIndexChanged(currentIndex); + } +} + + +/*! + Initialize \a option with the values from this QComboBox. This method + is useful for subclasses when they need a QStyleOptionComboBox, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QComboBox::initStyleOption(QStyleOptionComboBox *option) const +{ + if (!option) + return; + + Q_D(const QComboBox); + option->initFrom(this); + option->editable = isEditable(); + option->frame = d->frame; + if (hasFocus() && !option->editable) + option->state |= QStyle::State_Selected; + option->subControls = QStyle::SC_All; + if (d->arrowState == QStyle::State_Sunken) { + option->activeSubControls = QStyle::SC_ComboBoxArrow; + option->state |= d->arrowState; + } else { + option->activeSubControls = d->hoverControl; + } + if (d->currentIndex.isValid()) { + option->currentText = currentText(); + option->currentIcon = d->itemIcon(d->currentIndex); + } + option->iconSize = iconSize(); + if (d->container && d->container->isVisible()) + option->state |= QStyle::State_On; +} + +void QComboBoxPrivate::updateLineEditGeometry() +{ + if (!lineEdit) + return; + + Q_Q(QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + QRect editRect = q->style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, q); + if (!q->itemIcon(q->currentIndex()).isNull()) { + QRect comboRect(editRect); + editRect.setWidth(editRect.width() - q->iconSize().width() - 4); + editRect = QStyle::alignedRect(q->layoutDirection(), Qt::AlignRight, + editRect.size(), comboRect); + } + lineEdit->setGeometry(editRect); +} + +void QComboBoxPrivate::_q_returnPressed() +{ + Q_Q(QComboBox); + if (lineEdit && !lineEdit->text().isEmpty()) { + if (q->count() >= maxCount && !(this->insertPolicy == QComboBox::InsertAtCurrent)) + return; + lineEdit->deselect(); + lineEdit->end(false); + QString text = lineEdit->text(); + // check for duplicates (if not enabled) and quit + int index = -1; + if (!duplicatesEnabled) { + // Base how duplicates are determined on the autocompletion case sensitivity + Qt::MatchFlags flags = Qt::MatchFixedString; +#ifndef QT_NO_COMPLETER + if (!lineEdit->completer() || lineEdit->completer()->caseSensitivity() == Qt::CaseSensitive) +#endif + flags |= Qt::MatchCaseSensitive; + index = q->findText(text, flags); + if (index != -1) { + q->setCurrentIndex(index); + emitActivated(currentIndex); + return; + } + } + switch (insertPolicy) { + case QComboBox::InsertAtTop: + index = 0; + break; + case QComboBox::InsertAtBottom: + index = q->count(); + break; + case QComboBox::InsertAtCurrent: + case QComboBox::InsertAfterCurrent: + case QComboBox::InsertBeforeCurrent: + if (!q->count() || !currentIndex.isValid()) + index = 0; + else if (insertPolicy == QComboBox::InsertAtCurrent) + q->setItemText(q->currentIndex(), text); + else if (insertPolicy == QComboBox::InsertAfterCurrent) + index = q->currentIndex() + 1; + else if (insertPolicy == QComboBox::InsertBeforeCurrent) + index = q->currentIndex(); + break; + case QComboBox::InsertAlphabetically: + index = 0; + for (int i=0; i< q->count(); i++, index++ ) { + if (text.toLower() < q->itemText(i).toLower()) + break; + } + break; + case QComboBox::NoInsert: + default: + break; + } + if (index >= 0) { + q->insertItem(index, text); + q->setCurrentIndex(index); + emitActivated(currentIndex); + } + } +} + +void QComboBoxPrivate::_q_itemSelected(const QModelIndex &item) +{ + Q_Q(QComboBox); + if (item != currentIndex) { + setCurrentIndex(item); + } else if (lineEdit) { + lineEdit->selectAll(); + lineEdit->setText(q->itemText(currentIndex.row())); + } + emitActivated(currentIndex); +} + +void QComboBoxPrivate::emitActivated(const QModelIndex &index) +{ + Q_Q(QComboBox); + if (!index.isValid()) + return; + QString text(itemText(index)); + emit q->activated(index.row()); + emit q->activated(text); +} + +void QComboBoxPrivate::_q_emitHighlighted(const QModelIndex &index) +{ + Q_Q(QComboBox); + if (!index.isValid()) + return; + QString text(itemText(index)); + emit q->highlighted(index.row()); + emit q->highlighted(text); +} + +void QComboBoxPrivate::_q_emitCurrentIndexChanged(const QModelIndex &index) +{ + Q_Q(QComboBox); + emit q->currentIndexChanged(index.row()); + emit q->currentIndexChanged(itemText(index)); +} + +QString QComboBoxPrivate::itemText(const QModelIndex &index) const +{ + return index.isValid() ? model->data(index, itemRole()).toString() : QString(); +} + +int QComboBoxPrivate::itemRole() const +{ + return q_func()->isEditable() ? Qt::EditRole : Qt::DisplayRole; +} + +/*! + Destroys the combobox. +*/ +QComboBox::~QComboBox() +{ + // ### check delegateparent and delete delegate if us? + Q_D(QComboBox); + + disconnect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); +} + +/*! + \property QComboBox::maxVisibleItems + \brief the maximum allowed size on screen of the combo box, measured in items + + By default, this property has a value of 10. + + \note This property is ignored for non-editable comboboxes in Mac style. +*/ +int QComboBox::maxVisibleItems() const +{ + Q_D(const QComboBox); + return d->maxVisibleItems; +} + +void QComboBox::setMaxVisibleItems(int maxItems) +{ + Q_D(QComboBox); + if (maxItems < 0) { + qWarning("QComboBox::setMaxVisibleItems: " + "Invalid max visible items (%d) must be >= 0", maxItems); + return; + } + d->maxVisibleItems = maxItems; +} + +/*! + \property QComboBox::count + \brief the number of items in the combobox + + By default, for an empty combo box, this property has a value of 0. +*/ +int QComboBox::count() const +{ + Q_D(const QComboBox); + return d->model->rowCount(d->root); +} + +/*! + \property QComboBox::maxCount + \brief the maximum number of items allowed in the combobox + + \note If you set the maximum number to be less then the current + amount of items in the combobox, the extra items will be + truncated. This also applies if you have set an external model on + the combobox. + + By default, this property's value is derived from the highest + signed integer available (typically 2147483647). +*/ +void QComboBox::setMaxCount(int max) +{ + Q_D(QComboBox); + if (max < 0) { + qWarning("QComboBox::setMaxCount: Invalid count (%d) must be >= 0", max); + return; + } + + if (max < count()) + d->model->removeRows(max, count() - max, d->root); + + d->maxCount = max; +} + +int QComboBox::maxCount() const +{ + Q_D(const QComboBox); + return d->maxCount; +} + +#ifndef QT_NO_COMPLETER + +/*! + \property QComboBox::autoCompletion + \brief whether the combobox provides auto-completion for editable items + \since 4.1 + \obsolete + + Use setCompleter() instead. + + By default, this property is true. + + \sa editable +*/ + +/*! + \obsolete + + Use setCompleter() instead. +*/ +bool QComboBox::autoCompletion() const +{ + Q_D(const QComboBox); + return d->autoCompletion; +} + +/*! + \obsolete + + Use setCompleter() instead. +*/ +void QComboBox::setAutoCompletion(bool enable) +{ + Q_D(QComboBox); + +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !enable && isEditable()) + qWarning("QComboBox::setAutoCompletion: auto completion is mandatory when combo box editable"); +#endif + + d->autoCompletion = enable; + if (!d->lineEdit) + return; + if (enable) { + if (d->lineEdit->completer()) + return; + d->completer = new QCompleter(d->model, d->lineEdit); + d->completer->setCaseSensitivity(d->autoCompletionCaseSensitivity); + d->completer->setCompletionMode(QCompleter::InlineCompletion); + d->completer->setCompletionColumn(d->modelColumn); + d->lineEdit->setCompleter(d->completer); + d->completer->setWidget(this); + } else { + d->lineEdit->setCompleter(0); + } +} + +/*! + \property QComboBox::autoCompletionCaseSensitivity + \brief whether string comparisons are case-sensitive or case-insensitive for auto-completion + \obsolete + + By default, this property is Qt::CaseInsensitive. + + Use setCompleter() instead. Case sensitivity of the auto completion can be + changed using QCompleter::setCaseSensitivity(). + + \sa autoCompletion +*/ + +/*! + \obsolete + + Use setCompleter() and QCompleter::setCaseSensitivity() instead. +*/ +Qt::CaseSensitivity QComboBox::autoCompletionCaseSensitivity() const +{ + Q_D(const QComboBox); + return d->autoCompletionCaseSensitivity; +} + +/*! + \obsolete + + Use setCompleter() and QCompleter::setCaseSensitivity() instead. +*/ +void QComboBox::setAutoCompletionCaseSensitivity(Qt::CaseSensitivity sensitivity) +{ + Q_D(QComboBox); + d->autoCompletionCaseSensitivity = sensitivity; + if (d->lineEdit && d->lineEdit->completer()) + d->lineEdit->completer()->setCaseSensitivity(sensitivity); +} + +#endif // QT_NO_COMPLETER + +/*! + \property QComboBox::duplicatesEnabled + \brief whether the user can enter duplicate items into the combobox + + Note that it is always possible to programmatically insert duplicate items into the + combobox. + + By default, this property is false (duplicates are not allowed). +*/ +bool QComboBox::duplicatesEnabled() const +{ + Q_D(const QComboBox); + return d->duplicatesEnabled; +} + +void QComboBox::setDuplicatesEnabled(bool enable) +{ + Q_D(QComboBox); + d->duplicatesEnabled = enable; +} + +/*! \fn int QComboBox::findText(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly|Qt::MatchCaseSensitive) const + + Returns the index of the item containing the given \a text; otherwise + returns -1. + + The \a flags specify how the items in the combobox are searched. +*/ + +/*! + Returns the index of the item containing the given \a data for the + given \a role; otherwise returns -1. + + The \a flags specify how the items in the combobox are searched. +*/ +int QComboBox::findData(const QVariant &data, int role, Qt::MatchFlags flags) const +{ + Q_D(const QComboBox); + QModelIndexList result; + QModelIndex start = d->model->index(0, d->modelColumn, d->root); + result = d->model->match(start, role, data, 1, flags); + if (result.isEmpty()) + return -1; + return result.first().row(); +} + +/*! + \property QComboBox::insertPolicy + \brief the policy used to determine where user-inserted items should + appear in the combobox + + The default value is \l AtBottom, indicating that new items will appear + at the bottom of the list of items. + + \sa InsertPolicy +*/ + +QComboBox::InsertPolicy QComboBox::insertPolicy() const +{ + Q_D(const QComboBox); + return d->insertPolicy; +} + +void QComboBox::setInsertPolicy(InsertPolicy policy) +{ + Q_D(QComboBox); + d->insertPolicy = policy; +} + +/*! + \property QComboBox::sizeAdjustPolicy + \brief the policy describing how the size of the combobox changes + when the content changes + + The default value is \l AdjustToContentsOnFirstShow. + + \sa SizeAdjustPolicy +*/ + +QComboBox::SizeAdjustPolicy QComboBox::sizeAdjustPolicy() const +{ + Q_D(const QComboBox); + return d->sizeAdjustPolicy; +} + +void QComboBox::setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy policy) +{ + Q_D(QComboBox); + if (policy == d->sizeAdjustPolicy) + return; + + d->sizeAdjustPolicy = policy; + d->sizeHint = QSize(); + d->adjustComboBoxSize(); + updateGeometry(); +} + +/*! + \property QComboBox::minimumContentsLength + \brief the minimum number of characters that should fit into the combobox. + + The default value is 0. + + If this property is set to a positive value, the + minimumSizeHint() and sizeHint() take it into account. + + \sa sizeAdjustPolicy +*/ +int QComboBox::minimumContentsLength() const +{ + Q_D(const QComboBox); + return d->minimumContentsLength; +} + +void QComboBox::setMinimumContentsLength(int characters) +{ + Q_D(QComboBox); + if (characters == d->minimumContentsLength || characters < 0) + return; + + d->minimumContentsLength = characters; + + if (d->sizeAdjustPolicy == AdjustToContents + || d->sizeAdjustPolicy == AdjustToMinimumContentsLength + || d->sizeAdjustPolicy == AdjustToMinimumContentsLengthWithIcon) { + d->sizeHint = QSize(); + d->adjustComboBoxSize(); + updateGeometry(); + } +} + +/*! + \property QComboBox::iconSize + \brief the size of the icons shown in the combobox. + + Unless explicitly set this returns the default value of the + current style. This size is the maximum size that icons can have; + icons of smaller size are not scaled up. +*/ + +QSize QComboBox::iconSize() const +{ + Q_D(const QComboBox); + if (d->iconSize.isValid()) + return d->iconSize; + + int iconWidth = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + return QSize(iconWidth, iconWidth); +} + +void QComboBox::setIconSize(const QSize &size) +{ + Q_D(QComboBox); + if (size == d->iconSize) + return; + + view()->setIconSize(size); + d->iconSize = size; + d->sizeHint = QSize(); + updateGeometry(); +} + +/*! + \property QComboBox::editable + \brief whether the combo box can be edited by the user + + By default, this property is false. +*/ +bool QComboBox::isEditable() const +{ + Q_D(const QComboBox); + return d->lineEdit != 0; +} + +void QComboBoxPrivate::updateDelegate() +{ + Q_Q(QComboBox); + QStyleOptionComboBox opt; + q->initStyleOption(&opt); + if (q->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, q)) + q->setItemDelegate(new QComboMenuDelegate(q->view(), q)); + else + q->setItemDelegate(new QComboBoxDelegate(q->view(), q)); +} + +QIcon QComboBoxPrivate::itemIcon(const QModelIndex &index) const +{ + QVariant decoration = model->data(index, Qt::DecorationRole); + if (decoration.type() == QVariant::Pixmap) + return QIcon(qvariant_cast<QPixmap>(decoration)); + else + return qvariant_cast<QIcon>(decoration); +} + +void QComboBox::setEditable(bool editable) +{ + Q_D(QComboBox); + if (isEditable() == editable) + return; + + d->updateDelegate(); + + QStyleOptionComboBox opt; + initStyleOption(&opt); + if (editable) { + if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) { + d->viewContainer()->updateScrollers(); + view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } + QLineEdit *le = new QLineEdit(this); + setLineEdit(le); + } else { + if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) { + d->viewContainer()->updateScrollers(); + view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + setAttribute(Qt::WA_InputMethodEnabled, false); + d->lineEdit->hide(); + d->lineEdit->deleteLater(); + d->lineEdit = 0; + } + + d->viewContainer()->updateTopBottomMargin(); + if (!testAttribute(Qt::WA_Resized)) + adjustSize(); +} + +/*! + Sets the line \a edit to use instead of the current line edit widget. + + The combo box takes ownership of the line edit. +*/ +void QComboBox::setLineEdit(QLineEdit *edit) +{ + Q_D(QComboBox); + if (!edit) { + qWarning("QComboBox::setLineEdit: cannot set a 0 line edit"); + return; + } + + if (edit == d->lineEdit) + return; + + edit->setText(currentText()); + delete d->lineEdit; + + d->lineEdit = edit; + if (d->lineEdit->parent() != this) + d->lineEdit->setParent(this); + connect(d->lineEdit, SIGNAL(returnPressed()), this, SLOT(_q_returnPressed())); + connect(d->lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(editTextChanged(QString))); +#ifdef QT3_SUPPORT + connect(d->lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString))); +#endif + d->lineEdit->setFrame(false); + d->lineEdit->setContextMenuPolicy(Qt::NoContextMenu); + d->lineEdit->setFocusProxy(this); + d->lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); +#ifndef QT_NO_COMPLETER + setAutoCompletion(d->autoCompletion); +#endif + +#ifdef QT_KEYPAD_NAVIGATION +#ifndef QT_NO_COMPLETER + if (QApplication::keypadNavigationEnabled()) { + // Editable combo boxes will have a completer that is set to UnfilteredPopupCompletion. + // This means that when the user enters edit mode they are immediately presented with a + // list of possible completions. + setAutoCompletion(true); + if (d->completer) { + d->completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); + connect(d->completer, SIGNAL(activated(QModelIndex)), this, SLOT(_q_completerActivated())); + } + } +#endif +#endif + + setAttribute(Qt::WA_InputMethodEnabled); + d->updateLayoutDirection(); + d->updateLineEditGeometry(); + if (isVisible()) + d->lineEdit->show(); + + update(); +} + +/*! + Returns the line edit used to edit items in the combobox, or 0 if there + is no line edit. + + Only editable combo boxes have a line edit. +*/ +QLineEdit *QComboBox::lineEdit() const +{ + Q_D(const QComboBox); + return d->lineEdit; +} + +#ifndef QT_NO_VALIDATOR +/*! + \fn void QComboBox::setValidator(const QValidator *validator) + + Sets the \a validator to use instead of the current validator. +*/ + +void QComboBox::setValidator(const QValidator *v) +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->setValidator(v); +} + +/*! + Returns the validator that is used to constrain text input for the + combobox. + + \sa editable +*/ +const QValidator *QComboBox::validator() const +{ + Q_D(const QComboBox); + return d->lineEdit ? d->lineEdit->validator() : 0; +} +#endif // QT_NO_VALIDATOR + +#ifndef QT_NO_COMPLETER + +/*! + \fn void QComboBox::setCompleter(QCompleter *completer) + \since 4.2 + + Sets the \a completer to use instead of the current completer. + If \a completer is 0, auto completion is disabled. + + By default, for an editable combo box, a QCompleter that + performs case insensitive inline completion is automatically created. +*/ +void QComboBox::setCompleter(QCompleter *c) +{ + Q_D(QComboBox); + if (!d->lineEdit) + return; + d->lineEdit->setCompleter(c); + if (c) + c->setWidget(this); +} + +/*! + \since 4.2 + + Returns the completer that is used to auto complete text input for the + combobox. + + \sa editable +*/ +QCompleter *QComboBox::completer() const +{ + Q_D(const QComboBox); + return d->lineEdit ? d->lineEdit->completer() : 0; +} + +#endif // QT_NO_COMPLETER + +/*! + Returns the item delegate used by the popup list view. + + \sa setItemDelegate() +*/ +QAbstractItemDelegate *QComboBox::itemDelegate() const +{ + return view()->itemDelegate(); +} + +/*! + Sets the item \a delegate for the popup list view. + The combobox takes ownership of the delegate. + + \warning You should not share the same instance of a delegate between comboboxes, + widget mappers or 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. + + \sa itemDelegate() +*/ +void QComboBox::setItemDelegate(QAbstractItemDelegate *delegate) +{ + if (!delegate) { + qWarning("QComboBox::setItemDelegate: cannot set a 0 delegate"); + return; + } + delete view()->itemDelegate(); + view()->setItemDelegate(delegate); +} + +/*! + Returns the model used by the combobox. +*/ + +QAbstractItemModel *QComboBox::model() const +{ + Q_D(const QComboBox); + if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) { + QComboBox *that = const_cast<QComboBox*>(this); + that->setModel(new QStandardItemModel(0, 1, that)); + } + return d->model; +} + +/*! + Sets the model to be \a model. \a model must not be 0. + If you want to clear the contents of a model, call clear(). + + \sa clear() +*/ +void QComboBox::setModel(QAbstractItemModel *model) +{ + Q_D(QComboBox); + + if (!model) { + qWarning("QComboBox::setModel: cannot set a 0 model"); + return; + } + +#ifndef QT_NO_COMPLETER + if (d->lineEdit && d->lineEdit->completer() + && d->lineEdit->completer() == d->completer) + d->lineEdit->completer()->setModel(model); +#endif + if (d->model) { + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + disconnect(d->model, SIGNAL(modelReset()), + this, SLOT(_q_modelReset())); + if (d->model->QObject::parent() == this) + delete d->model; + } + + d->model = model; + + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + connect(model, SIGNAL(modelReset()), + this, SLOT(_q_modelReset())); + + if (d->container) + d->container->itemView()->setModel(model); + + bool currentReset = false; + + if (count()) { + for (int pos=0; pos < count(); pos++) { + if (d->model->index(pos, d->modelColumn, d->root).flags() & Qt::ItemIsEnabled) { + setCurrentIndex(pos); + currentReset = true; + break; + } + } + } + + if (!currentReset) + setCurrentIndex(-1); + + d->modelChanged(); +} + +/*! + Returns the root model item index for the items in the combobox. + + \sa setRootModelIndex() +*/ + +QModelIndex QComboBox::rootModelIndex() const +{ + Q_D(const QComboBox); + return QModelIndex(d->root); +} + +/*! + Sets the root model item \a index for the items in the combobox. + + \sa rootModelIndex() +*/ +void QComboBox::setRootModelIndex(const QModelIndex &index) +{ + Q_D(QComboBox); + d->root = QPersistentModelIndex(index); + view()->setRootIndex(index); + update(); +} + +/*! + \property QComboBox::currentIndex + \brief the index of the current item in the combobox. + + The current index can change when inserting or removing items. + + By default, for an empty combo box or a combo box in which no current + item is set, this property has a value of -1. +*/ +int QComboBox::currentIndex() const +{ + Q_D(const QComboBox); + return d->currentIndex.row(); +} + +void QComboBox::setCurrentIndex(int index) +{ + Q_D(QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + d->setCurrentIndex(mi); +} + +void QComboBoxPrivate::setCurrentIndex(const QModelIndex &mi) +{ + Q_Q(QComboBox); + bool indexChanged = (mi != currentIndex); + if (indexChanged) + currentIndex = QPersistentModelIndex(mi); + if (lineEdit) { + QString newText = q->itemText(currentIndex.row()); + if (lineEdit->text() != newText) + lineEdit->setText(q->itemText(currentIndex.row())); + updateLineEditGeometry(); + } + if (indexChanged) { + q->update(); + _q_emitCurrentIndexChanged(currentIndex); + } +} + +/*! + \property QComboBox::currentText + \brief the text of the current item + + By default, for an empty combo box or a combo box in which no current + item is set, this property contains an empty string. +*/ +QString QComboBox::currentText() const +{ + Q_D(const QComboBox); + if (d->lineEdit) + return d->lineEdit->text(); + else if (d->currentIndex.isValid()) + return d->itemText(d->currentIndex); + else + return QString(); +} + +/*! + Returns the text for the given \a index in the combobox. +*/ +QString QComboBox::itemText(int index) const +{ + Q_D(const QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + return d->itemText(mi); +} + +/*! + Returns the icon for the given \a index in the combobox. +*/ +QIcon QComboBox::itemIcon(int index) const +{ + Q_D(const QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + return d->itemIcon(mi); +} + +/*! + Returns the data for the given \a role in the given \a index in the + combobox, or QVariant::Invalid if there is no data for this role. +*/ +QVariant QComboBox::itemData(int index, int role) const +{ + Q_D(const QComboBox); + QModelIndex mi = d->model->index(index, d->modelColumn, d->root); + return d->model->data(mi, role); +} + +/*! + \fn void QComboBox::insertItem(int index, const QString &text, const QVariant &userData) + + Inserts the \a text and \a userData (stored in the Qt::UserRole) + into the combobox at the given \a index. + + If the index is equal to or higher than the total number of items, + the new item is appended to the list of existing items. If the + index is zero or negative, the new item is prepended to the list + of existing items. + + \sa insertItems() +*/ + +/*! + + Inserts the \a icon, \a text and \a userData (stored in the + Qt::UserRole) into the combobox at the given \a index. + + If the index is equal to or higher than the total number of items, + the new item is appended to the list of existing items. If the + index is zero or negative, the new item is prepended to the list + of existing items. + + \sa insertItems() +*/ +void QComboBox::insertItem(int index, const QIcon &icon, const QString &text, const QVariant &userData) +{ + Q_D(QComboBox); + int itemCount = count(); + index = qBound(0, index, itemCount); + if (index >= d->maxCount) + return; + + // For the common case where we are using the built in QStandardItemModel + // construct a QStandardItem, reducing the number of expensive signals from the model + if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(d->model)) { + QStandardItem *item = new QStandardItem(text); + if (!icon.isNull()) item->setData(icon, Qt::DecorationRole); + if (userData.isValid()) item->setData(userData, Qt::UserRole); + m->insertRow(index, item); + ++itemCount; + } else { + d->inserting = true; + if (d->model->insertRows(index, 1, d->root)) { + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (icon.isNull() && !userData.isValid()) { + d->model->setData(item, text, Qt::EditRole); + } else { + QMap<int, QVariant> values; + if (!text.isNull()) values.insert(Qt::EditRole, text); + if (!icon.isNull()) values.insert(Qt::DecorationRole, icon); + if (userData.isValid()) values.insert(Qt::UserRole, userData); + if (!values.isEmpty()) d->model->setItemData(item, values); + } + d->inserting = false; + d->_q_rowsInserted(d->root, index, index); + ++itemCount; + } else { + d->inserting = false; + } + } + + if (itemCount > d->maxCount) + d->model->removeRows(itemCount - 1, itemCount - d->maxCount, d->root); +} + +/*! + Inserts the strings from the \a list into the combobox as separate items, + starting at the \a index specified. + + If the index is equal to or higher than the total number of items, the new items + are appended to the list of existing items. If the index is zero or negative, the + new items are prepended to the list of existing items. + + \sa insertItem() + */ +void QComboBox::insertItems(int index, const QStringList &list) +{ + Q_D(QComboBox); + if (list.isEmpty()) + return; + index = qBound(0, index, count()); + int insertCount = qMin(d->maxCount - index, list.count()); + if (insertCount <= 0) + return; + // For the common case where we are using the built in QStandardItemModel + // construct a QStandardItem, reducing the number of expensive signals from the model + if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(d->model)) { + QList<QStandardItem *> items; + QStandardItem *hiddenRoot = m->invisibleRootItem(); + for (int i = 0; i < insertCount; ++i) + items.append(new QStandardItem(list.at(i))); + hiddenRoot->insertRows(index, items); + } else { + d->inserting = true; + if (d->model->insertRows(index, insertCount, d->root)) { + QModelIndex item; + for (int i = 0; i < insertCount; ++i) { + item = d->model->index(i+index, d->modelColumn, d->root); + d->model->setData(item, list.at(i), Qt::EditRole); + } + d->inserting = false; + d->_q_rowsInserted(d->root, index, index + insertCount - 1); + } else { + d->inserting = false; + } + } + + int mc = count(); + if (mc > d->maxCount) + d->model->removeRows(d->maxCount, mc - d->maxCount, d->root); +} + +/*! + \since 4.4 + + Inserts a separator item into the combobox at the given \a index. + + If the index is equal to or higher than the total number of items, the new item + is appended to the list of existing items. If the index is zero or negative, the + new item is prepended to the list of existing items. + + \sa insertItem() +*/ +void QComboBox::insertSeparator(int index) +{ + Q_D(QComboBox); + int itemCount = count(); + index = qBound(0, index, itemCount); + if (index >= d->maxCount) + return; + insertItem(index, QIcon(), QString()); + QComboBoxDelegate::setSeparator(d->model, d->model->index(index, 0, d->root)); +} + +/*! + Removes the item at the given \a index from the combobox. + This will update the current index if the index is removed. +*/ +void QComboBox::removeItem(int index) +{ + Q_ASSERT(index >= 0 && index < count()); + Q_D(QComboBox); + d->model->removeRows(index, 1, d->root); +} + +/*! + Sets the \a text for the item on the given \a index in the combobox. +*/ +void QComboBox::setItemText(int index, const QString &text) +{ + Q_D(const QComboBox); + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (item.isValid()) { + d->model->setData(item, text, Qt::EditRole); + } +} + +/*! + Sets the \a icon for the item on the given \a index in the combobox. +*/ +void QComboBox::setItemIcon(int index, const QIcon &icon) +{ + Q_D(const QComboBox); + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (item.isValid()) { + d->model->setData(item, icon, Qt::DecorationRole); + } +} + +/*! + Sets the data \a role for the item on the given \a index in the combobox + to the specified \a value. +*/ +void QComboBox::setItemData(int index, const QVariant &value, int role) +{ + Q_D(const QComboBox); + QModelIndex item = d->model->index(index, d->modelColumn, d->root); + if (item.isValid()) { + d->model->setData(item, value, role); + } +} + +/*! + Returns the list view used for the combobox popup. +*/ +QAbstractItemView *QComboBox::view() const +{ + Q_D(const QComboBox); + return const_cast<QComboBoxPrivate*>(d)->viewContainer()->itemView(); +} + +/*! + Sets the view to be used in the combobox popup to the given \a + itemView. The combobox takes ownership of the view. + + Note: If you want to use the convenience views (like QListWidget, + QTableWidget or QTreeWidget), make sure to call setModel() on the + combobox with the convenience widgets model before calling this + function. +*/ +void QComboBox::setView(QAbstractItemView *itemView) +{ + Q_D(QComboBox); + if (!itemView) { + qWarning("QComboBox::setView: cannot set a 0 view"); + return; + } + + if (itemView->model() != d->model) + itemView->setModel(d->model); + d->viewContainer()->setItemView(itemView); +} + +/*! + \reimp +*/ +QSize QComboBox::minimumSizeHint() const +{ + Q_D(const QComboBox); + return d->recomputeSizeHint(d->minimumSizeHint); +} + +/*! + \reimp + + This implementation caches the size hint to avoid resizing when + the contents change dynamically. To invalidate the cached value + change the \l sizeAdjustPolicy. +*/ +QSize QComboBox::sizeHint() const +{ + Q_D(const QComboBox); + return d->recomputeSizeHint(d->sizeHint); +} + +/*! + Displays the list of items in the combobox. If the list is empty + then the no items will be shown. + + If you reimplement this function to show a custom pop-up, make + sure you call hidePopup() to reset the internal state. + + \sa hidePopup() +*/ +void QComboBox::showPopup() +{ + Q_D(QComboBox); + if (count() <= 0) + return; + +#ifdef QT_KEYPAD_NAVIGATION +#ifndef QT_NO_COMPLETER + if (QApplication::keypadNavigationEnabled() && d->completer) { + // editable combo box is line edit plus completer + setEditFocus(true); + d->completer->complete(); // show popup + return; + } +#endif +#endif + + QStyle * const style = this->style(); + + // set current item and select it + view()->selectionModel()->setCurrentIndex(d->currentIndex, + QItemSelectionModel::ClearAndSelect); + QComboBoxPrivateContainer* container = d->viewContainer(); + QStyleOptionComboBox opt; + initStyleOption(&opt); + QRect listRect(style->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxListBoxPopup, this)); + QRect screen = d->popupGeometry(QApplication::desktop()->screenNumber(this)); + QPoint below = mapToGlobal(listRect.bottomLeft()); + int belowHeight = screen.bottom() - below.y(); + QPoint above = mapToGlobal(listRect.topLeft()); + int aboveHeight = above.y() - screen.y(); + bool boundToScreen = !window()->testAttribute(Qt::WA_DontShowOnScreen); + + const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this); + + { + int listHeight = 0; + int count = 0; + QStack<QModelIndex> toCheck; + toCheck.push(view()->rootIndex()); +#ifndef QT_NO_TREEVIEW + QTreeView *treeView = qobject_cast<QTreeView*>(view()); + if (treeView && treeView->header() && !treeView->header()->isHidden()) + listHeight += treeView->header()->height(); +#endif + while (!toCheck.isEmpty()) { + QModelIndex parent = toCheck.pop(); + for (int i = 0; i < d->model->rowCount(parent); ++i) { + QModelIndex idx = d->model->index(i, d->modelColumn, parent); + if (!idx.isValid()) + continue; + listHeight += view()->visualRect(idx).height() + container->spacing(); +#ifndef QT_NO_TREEVIEW + if (d->model->hasChildren(idx) && treeView && treeView->isExpanded(idx)) + toCheck.push(idx); +#endif + ++count; + if (!usePopup && count > d->maxVisibleItems) { + toCheck.clear(); + break; + } + } + } + listRect.setHeight(listHeight); + } + + // ### Adjusting by PM_DefaultFrameWidth is not enough. Since QFrame supports + // SE_FrameContents, QFrame needs API to return the frameWidths + listRect.setHeight(listRect.height() + 2*container->spacing() + + style->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this) * 2); + + // Add space for margin at top and bottom if the style wants it. + if (usePopup) + listRect.setHeight(listRect.height() + style->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) * 2); + + // Make sure the popup is wide enough to display its contents. + if (usePopup) { + const int diff = d->computeWidthHint() - width(); + if (diff > 0) + listRect.setWidth(listRect.width() + diff); + } + + //takes account of the minimum/maximum size of the container + listRect.setSize( listRect.size().expandedTo(container->minimumSize()) + .boundedTo(container->maximumSize())); + + // make sure the widget fits on screen + if (boundToScreen) { + if (listRect.width() > screen.width() ) + listRect.setWidth(screen.width()); + if (mapToGlobal(listRect.bottomRight()).x() > screen.right()) { + below.setX(screen.x() + screen.width() - listRect.width()); + above.setX(screen.x() + screen.width() - listRect.width()); + } + if (mapToGlobal(listRect.topLeft()).x() < screen.x() ) { + below.setX(screen.x()); + above.setX(screen.x()); + } + } + + if (usePopup) { + // Position horizontally. + listRect.moveLeft(above.x()); + + // Position vertically so the curently selected item lines up + // with the combo box. + const QRect currentItemRect = view()->visualRect(view()->currentIndex()); + const int offset = listRect.top() - currentItemRect.top(); + listRect.moveTop(above.y() + offset - listRect.top()); + + + // Clamp the listRect height and vertical position so we don't expand outside the + // available screen geometry.This may override the vertical position, but it is more + // important to show as much as possible of the popup. + const int height = !boundToScreen ? listRect.height() : qMin(listRect.height(), screen.height()); + listRect.setHeight(height); + if (boundToScreen) { + if (listRect.top() < screen.top()) + listRect.moveTop(screen.top()); + if (listRect.bottom() > screen.bottom()) + listRect.moveBottom(screen.bottom()); + } + } else if (!boundToScreen || listRect.height() <= belowHeight) { + listRect.moveTopLeft(below); + } else if (listRect.height() <= aboveHeight) { + listRect.moveBottomLeft(above); + } else if (belowHeight >= aboveHeight) { + listRect.setHeight(belowHeight); + listRect.moveTopLeft(below); + } else { + listRect.setHeight(aboveHeight); + listRect.moveBottomLeft(above); + } + +#ifndef QT_NO_IM + if (QInputContext *qic = inputContext()) + qic->reset(); +#endif + QScrollBar *sb = view()->horizontalScrollBar(); + Qt::ScrollBarPolicy policy = view()->horizontalScrollBarPolicy(); + bool needHorizontalScrollBar = (policy == Qt::ScrollBarAsNeeded || policy == Qt::ScrollBarAlwaysOn) + && sb->minimum() < sb->maximum(); + if (needHorizontalScrollBar) { + listRect.adjust(0, 0, 0, sb->height()); + } + container->setGeometry(listRect); + + bool updatesEnabled = container->updatesEnabled(); +#if defined(Q_WS_WIN) && !defined(QT_NO_EFFECTS) + bool scrollDown = (listRect.topLeft() == below); + if (QApplication::isEffectEnabled(Qt::UI_AnimateCombo) + && !style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) && !window()->testAttribute(Qt::WA_DontShowOnScreen)) + qScrollEffect(container, scrollDown ? QEffects::DownScroll : QEffects::UpScroll, 150); +#endif + container->setUpdatesEnabled(false); + container->raise(); + container->show(); + container->updateScrollers(); + view()->setFocus(); + + view()->scrollTo(view()->currentIndex(), + style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) + ? QAbstractItemView::PositionAtCenter + : QAbstractItemView::EnsureVisible); + + container->setUpdatesEnabled(updatesEnabled); + container->update(); +} + +/*! + Hides the list of items in the combobox if it is currently visible + and resets the internal state, so that if the custom pop-up was + shown inside the reimplemented showPopup(), then you also need to + reimplement the hidePopup() function to hide your custom pop-up + and call the base class implementation to reset the internal state + whenever your custom pop-up widget is hidden. + + \sa showPopup() +*/ +void QComboBox::hidePopup() +{ + Q_D(QComboBox); + if (d->container && d->container->isVisible()) { +#if !defined(QT_NO_EFFECTS) + d->model->blockSignals(true); + d->container->itemView()->blockSignals(true); + d->container->blockSignals(true); + // Flash selected/triggered item (if any). + if (style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem)) { + QItemSelectionModel *selectionModel = view() ? view()->selectionModel() : 0; + if (selectionModel && selectionModel->hasSelection()) { + QEventLoop eventLoop; + const QItemSelection selection = selectionModel->selection(); + + // Deselect item and wait 60 ms. + selectionModel->select(selection, QItemSelectionModel::Toggle); + QTimer::singleShot(60, &eventLoop, SLOT(quit())); + eventLoop.exec(); + + // Select item and wait 20 ms. + selectionModel->select(selection, QItemSelectionModel::Toggle); + QTimer::singleShot(20, &eventLoop, SLOT(quit())); + eventLoop.exec(); + } + } + + // Fade out. + bool needFade = style()->styleHint(QStyle::SH_Menu_FadeOutOnHide); + if (needFade) { +#if defined(Q_WS_MAC) + macWindowFade(qt_mac_window_for(d->container)); +#endif // Q_WS_MAC + // Other platform implementations welcome :-) + } + d->model->blockSignals(false); + d->container->itemView()->blockSignals(false); + d->container->blockSignals(false); + + if (!needFade) +#endif // QT_NO_EFFECTS + // Fade should implicitly hide as well ;-) + d->container->hide(); + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && isEditable() && hasFocus()) + setEditFocus(true); +#endif + d->_q_resetButton(); +} + +/*! + Clears the combobox, removing all items. + + Note: If you have set an external model on the combobox this model + will still be cleared when calling this function. +*/ +void QComboBox::clear() +{ + Q_D(QComboBox); + d->model->removeRows(0, d->model->rowCount(d->root), d->root); +} + +/*! + \fn void QComboBox::clearValidator() + + Use setValidator(0) instead. +*/ + +/*! + Clears the contents of the line edit used for editing in the combobox. +*/ +void QComboBox::clearEditText() +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->clear(); +} + +/*! + Sets the \a text in the combobox's text edit. +*/ +void QComboBox::setEditText(const QString &text) +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->setText(text); +} + +/*! + \reimp +*/ +void QComboBox::focusInEvent(QFocusEvent *e) +{ + Q_D(QComboBox); + update(); + if (d->lineEdit) { + d->lineEdit->event(e); +#ifndef QT_NO_COMPLETER + if (d->lineEdit->completer()) + d->lineEdit->completer()->setWidget(this); +#endif + } +} + +/*! + \reimp +*/ +void QComboBox::focusOutEvent(QFocusEvent *e) +{ + Q_D(QComboBox); + update(); + if (d->lineEdit) + d->lineEdit->event(e); +} + +/*! \reimp */ +void QComboBox::changeEvent(QEvent *e) +{ + Q_D(QComboBox); + switch (e->type()) { + case QEvent::StyleChange: + d->updateDelegate(); +#ifdef Q_WS_MAC + case QEvent::MacSizeChange: +#endif + d->sizeHint = QSize(); // invalidate size hint + d->minimumSizeHint = QSize(); + d->updateLayoutDirection(); + if (d->lineEdit) + d->updateLineEditGeometry(); + d->setLayoutItemMargins(QStyle::SE_ComboBoxLayoutItem); + // ### need to update scrollers etc. as well here + break; + case QEvent::EnabledChange: + if (!isEnabled()) + hidePopup(); + break; + case QEvent::PaletteChange: { + QStyleOptionComboBox opt; + initStyleOption(&opt); +#ifndef QT_NO_MENU + if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) { + QMenu menu; + menu.ensurePolished(); + d->viewContainer()->setPalette(menu.palette()); + d->viewContainer()->setWindowOpacity(menu.windowOpacity()); + } else +#endif + { + d->viewContainer()->setPalette(palette()); + d->viewContainer()->setWindowOpacity(1.0); + } + break; + } + case QEvent::FontChange: + d->sizeHint = QSize(); // invalidate size hint + d->viewContainer()->setFont(font()); + if (d->lineEdit) + d->updateLineEditGeometry(); + break; + default: + break; + } + QWidget::changeEvent(e); +} + +/*! + \reimp +*/ +void QComboBox::resizeEvent(QResizeEvent *) +{ + Q_D(QComboBox); + d->updateLineEditGeometry(); +} + +/*! + \reimp +*/ +void QComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + +/*! + \reimp +*/ +void QComboBox::showEvent(QShowEvent *e) +{ + Q_D(QComboBox); + if (!d->shownOnce && d->sizeAdjustPolicy == QComboBox::AdjustToContentsOnFirstShow) { + d->sizeHint = QSize(); + updateGeometry(); + } + d->shownOnce = true; + QWidget::showEvent(e); +} + +/*! + \reimp +*/ +void QComboBox::hideEvent(QHideEvent *) +{ + hidePopup(); +} + +/*! + \reimp +*/ +bool QComboBox::event(QEvent *event) +{ + Q_D(QComboBox); + switch(event->type()) { + case QEvent::LayoutDirectionChange: + case QEvent::ApplicationLayoutDirectionChange: + d->updateLayoutDirection(); + d->updateLineEditGeometry(); + break; + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) + d->updateHoverControl(he->pos()); + break; + case QEvent::ShortcutOverride: + if (d->lineEdit) + return d->lineEdit->event(event); + break; +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + if (!d->lineEdit) + setEditFocus(false); // We never want edit focus if we are not editable + else + d->lineEdit->event(event); //so cursor starts + break; + case QEvent::LeaveEditFocus: + if (d->lineEdit) + d->lineEdit->event(event); //so cursor stops + break; +#endif + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +void QComboBox::mousePressEvent(QMouseEvent *e) +{ + Q_D(QComboBox); + QStyleOptionComboBox opt; + initStyleOption(&opt); + QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->pos(), + this); + if (e->button() == Qt::LeftButton && (sc == QStyle::SC_ComboBoxArrow || !isEditable()) + && !d->viewContainer()->isVisible()) { + if (sc == QStyle::SC_ComboBoxArrow) + d->updateArrow(QStyle::State_Sunken); +#ifdef QT_KEYPAD_NAVIGATION + if (!d->lineEdit) { +#endif + // We've restricted the next couple of lines, because by not calling + // viewContainer(), we avoid creating the QComboBoxPrivateContainer. + d->viewContainer()->blockMouseReleaseTimer.start(QApplication::doubleClickInterval()); + d->viewContainer()->initialClickPosition = mapToGlobal(e->pos()); +#ifdef QT_KEYPAD_NAVIGATION + } +#endif + showPopup(); + } else { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && sc == QStyle::SC_ComboBoxEditField && d->lineEdit) { + d->lineEdit->event(e); //so lineedit can move cursor, etc + return; + } +#endif + QWidget::mousePressEvent(e); + } +} + +/*! + \reimp +*/ +void QComboBox::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QComboBox); + Q_UNUSED(e); + d->updateArrow(QStyle::State_None); +} + +/*! + \reimp +*/ +void QComboBox::keyPressEvent(QKeyEvent *e) +{ + Q_D(QComboBox); + +#ifndef QT_NO_COMPLETER + if (d->lineEdit + && d->lineEdit->completer() + && d->lineEdit->completer()->popup() + && d->lineEdit->completer()->popup()->isVisible()) { + // provide same autocompletion support as line edit + d->lineEdit->event(e); + return; + } +#endif + + enum Move { NoMove=0 , MoveUp , MoveDown , MoveFirst , MoveLast}; + + Move move = NoMove; + int newIndex = currentIndex(); + switch (e->key()) { + case Qt::Key_Up: + if (e->modifiers() & Qt::ControlModifier) + break; // pass to line edit for auto completion + case Qt::Key_PageUp: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + e->ignore(); + else +#endif + move = MoveUp; + break; + case Qt::Key_Down: + if (e->modifiers() & Qt::AltModifier) { + showPopup(); + return; + } else if (e->modifiers() & Qt::ControlModifier) + break; // pass to line edit for auto completion + // fall through + case Qt::Key_PageDown: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + e->ignore(); + else +#endif + move = MoveDown; + break; + case Qt::Key_Home: + if (!d->lineEdit) + move = MoveFirst; + break; + case Qt::Key_End: + if (!d->lineEdit) + move = MoveLast; + break; + case Qt::Key_F4: + if (!e->modifiers()) { + showPopup(); + return; + } + break; + case Qt::Key_Space: + if (!d->lineEdit) { + showPopup(); + return; + } + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Escape: + if (!d->lineEdit) + e->ignore(); + break; +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled() + && (!hasEditFocus() || !d->lineEdit)) { + showPopup(); + return; + } + break; + case Qt::Key_Left: + case Qt::Key_Right: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) + e->ignore(); + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus() || !d->lineEdit) + e->ignore(); + } else { + e->ignore(); // let the surounding dialog have it + } + break; +#endif + default: + if (!d->lineEdit) { + if (!e->text().isEmpty()) + d->keyboardSearchString(e->text()); + else + e->ignore(); + } + } + + if (move != NoMove) { + e->accept(); + switch (move) { + case MoveFirst: + newIndex = -1; + case MoveDown: + newIndex++; + while ((newIndex < count()) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex++; + break; + case MoveLast: + newIndex = count(); + case MoveUp: + newIndex--; + while ((newIndex >= 0) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex--; + break; + default: + e->ignore(); + break; + } + + if (newIndex >= 0 && newIndex < count() && newIndex != currentIndex()) { + setCurrentIndex(newIndex); + d->emitActivated(d->currentIndex); + } + } else if (d->lineEdit) { + d->lineEdit->event(e); + } +} + + +/*! + \reimp +*/ +void QComboBox::keyReleaseEvent(QKeyEvent *e) +{ + Q_D(QComboBox); + if (d->lineEdit) + d->lineEdit->event(e); +} + +/*! + \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QComboBox::wheelEvent(QWheelEvent *e) +{ + Q_D(QComboBox); + if (!d->viewContainer()->isVisible()) { + int newIndex = currentIndex(); + + if (e->delta() > 0) { + newIndex--; + while ((newIndex >= 0) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex--; + } else { + newIndex++; + while ((newIndex < count()) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled)) + newIndex++; + } + + if (newIndex >= 0 && newIndex < count() && newIndex != currentIndex()) { + setCurrentIndex(newIndex); + d->emitActivated(d->currentIndex); + } + e->accept(); + } +} +#endif + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QComboBox::contextMenuEvent(QContextMenuEvent *e) +{ + Q_D(QComboBox); + if (d->lineEdit) { + Qt::ContextMenuPolicy p = d->lineEdit->contextMenuPolicy(); + d->lineEdit->setContextMenuPolicy(Qt::DefaultContextMenu); + d->lineEdit->event(e); + d->lineEdit->setContextMenuPolicy(p); + } +} +#endif // QT_NO_CONTEXTMENU + +void QComboBoxPrivate::keyboardSearchString(const QString &text) +{ + // use keyboardSearch from the listView so we do not duplicate code + QAbstractItemView *view = viewContainer()->itemView(); + view->setCurrentIndex(currentIndex); + int currentRow = view->currentIndex().row(); + view->keyboardSearch(text); + if (currentRow != view->currentIndex().row()) { + setCurrentIndex(view->currentIndex()); + emitActivated(currentIndex); + } +} + +void QComboBoxPrivate::modelChanged() +{ + Q_Q(QComboBox); + + if (sizeAdjustPolicy == QComboBox::AdjustToContents) { + sizeHint = QSize(); + adjustComboBoxSize(); + q->updateGeometry(); + } +} + +/*! + \reimp +*/ +void QComboBox::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QComboBox); + if (d->lineEdit) { + d->lineEdit->event(e); + } else { + if (!e->commitString().isEmpty()) + d->keyboardSearchString(e->commitString()); + else + e->ignore(); + } +} + +/*! + \reimp +*/ +QVariant QComboBox::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QComboBox); + if (d->lineEdit) + return d->lineEdit->inputMethodQuery(query); + return QWidget::inputMethodQuery(query); +} + +/*! + \fn bool QComboBox::editable() const + + Use isEditable() instead. +*/ + +/*! + \fn void QComboBox::insertItem(const QPixmap &pixmap, int index) + + Use an insertItem() function that takes a QIcon instead, for + example, insertItem(index, QIcon(pixmap)). +*/ + +/*! + \fn void QComboBox::insertItem(const QPixmap &pixmap, const QString &text, int index) + + Use an insertItem() function that takes a QIcon instead, for + example, insertItem(index, QIcon(pixmap), text). + + \sa insertItems() +*/ + +/*! + \fn void QComboBox::changeItem(const QString &text, int index) + + Use setItemText() instead. +*/ + +/*! + \fn void QComboBox::changeItem(const QPixmap &pixmap, int index) + + Use setItemIcon() instead, for example, + setItemIcon(index, QIcon(pixmap)). +*/ + +/*! + \fn void QComboBox::changeItem(const QPixmap &pixmap, const QString &text, int index) + + Use setItem() instead, for example, setItem(index, QIcon(pixmap),text). +*/ + +/*! + \fn void QComboBox::addItem(const QString &text, const QVariant &userData) + + Adds an item to the combobox with the given \a text, and + containing the specified \a userData (stored in the Qt::UserRole). + The item is appended to the list of existing items. +*/ + +/*! + \fn void QComboBox::addItem(const QIcon &icon, const QString &text, + const QVariant &userData) + + Adds an item to the combobox with the given \a icon and \a text, + and containing the specified \a userData (stored in the + Qt::UserRole). The item is appended to the list of existing items. +*/ + +/*! + \fn void QComboBox::addItems(const QStringList &texts) + + Adds each of the strings in the given \a texts to the combobox. Each item + is appended to the list of existing items in turn. +*/ + +/*! + \fn void QComboBox::editTextChanged(const QString &text) + + This signal is emitted when the text in the combobox's line edit + widget is changed. The new text is specified by \a text. +*/ + +/*! + \fn QComboBox::InsertPolicy QComboBox::insertionPolicy() const + \compat + + Use QComboBox::insertPolicy instead. +*/ + +/*! + \fn void QComboBox::setInsertionPolicy(InsertPolicy policy) + \compat + + Use QComboBox::insertPolicy instead. +*/ + +/*! + \fn void QComboBox::setCurrentText(const QString &text) + \compat + + Use setItemText() instead. + + \sa currentIndex() +*/ + +/*! + \fn QString QComboBox::text(int index) const + \compat + + Use itemText() instead. +*/ + +/*! + \fn QPixmap QComboBox::pixmap(int index) const + \compat + + Use itemIcon() instead. +*/ + +/*! + \fn void QComboBox::insertStringList(const QStringList &list, int index) + \compat + + Use insertItems() instead. +*/ + +/*! + \fn void QComboBox::insertItem(const QString &text, int index) + \compat +*/ + +/*! + \fn void QComboBox::clearEdit() + \compat + + Use clearEditText() instead. +*/ + + +/*! + \property QComboBox::frame + \brief whether the combo box draws itself with a frame + + + If enabled (the default) the combo box draws itself inside a + frame, otherwise the combo box draws itself without any frame. +*/ +bool QComboBox::hasFrame() const +{ + Q_D(const QComboBox); + return d->frame; +} + + +void QComboBox::setFrame(bool enable) +{ + Q_D(QComboBox); + d->frame = enable; + update(); + updateGeometry(); +} + +/*! + \property QComboBox::modelColumn + \brief the column in the model that is visible. + + If set prior to populating the combo box, the pop-up view will + not be affected and will show the first column (using this property's + default value). + + By default, this property has a value of 0. +*/ +int QComboBox::modelColumn() const +{ + Q_D(const QComboBox); + return d->modelColumn; +} + +void QComboBox::setModelColumn(int visibleColumn) +{ + Q_D(QComboBox); + d->modelColumn = visibleColumn; + QListView *lv = qobject_cast<QListView *>(d->viewContainer()->itemView()); + if (lv) + lv->setModelColumn(visibleColumn); +#ifndef QT_NO_COMPLETER + if (d->lineEdit && d->lineEdit->completer() + && d->lineEdit->completer() == d->completer) + d->lineEdit->completer()->setCompletionColumn(visibleColumn); +#endif + setCurrentIndex(currentIndex()); //update the text to the text of the new column; +} + +/*! + \fn int QComboBox::currentItem() const + + Use currentIndex() instead. +*/ + +/*! + \fn void QComboBox::setCurrentItem(int) + + Use setCurrentIndex(int) instead. +*/ + +/*! + \fn void QComboBox::popup() + + Use showPopup() instead. +*/ + +/*! + \fn void QComboBox::textChanged(const QString &text) + + Use the editTextChanged(const QString &text) signal instead. +*/ + +/*! + \typedef QComboBox::Policy + \compat + + Use QComboBox::InsertPolicy instead. +*/ + +QT_END_NAMESPACE + +#include "moc_qcombobox.cpp" + +#endif // QT_NO_COMBOBOX diff --git a/src/gui/widgets/qcombobox.h b/src/gui/widgets/qcombobox.h new file mode 100644 index 0000000..2cb2e8f --- /dev/null +++ b/src/gui/widgets/qcombobox.h @@ -0,0 +1,338 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMBOBOX_H +#define QCOMBOBOX_H + +#include <QtGui/qwidget.h> +#include <QtGui/qabstractitemdelegate.h> +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_COMBOBOX + +class QAbstractItemView; +class QLineEdit; +class QComboBoxPrivate; +class QCompleter; + +class Q_GUI_EXPORT QComboBox : public QWidget +{ + Q_OBJECT + + Q_ENUMS(InsertPolicy) + Q_ENUMS(SizeAdjustPolicy) + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + Q_PROPERTY(int count READ count) + Q_PROPERTY(QString currentText READ currentText) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(int maxVisibleItems READ maxVisibleItems WRITE setMaxVisibleItems) + Q_PROPERTY(int maxCount READ maxCount WRITE setMaxCount) + Q_PROPERTY(InsertPolicy insertPolicy READ insertPolicy WRITE setInsertPolicy) + Q_PROPERTY(SizeAdjustPolicy sizeAdjustPolicy READ sizeAdjustPolicy WRITE setSizeAdjustPolicy) + Q_PROPERTY(int minimumContentsLength READ minimumContentsLength WRITE setMinimumContentsLength) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + +#ifndef QT_NO_COMPLETER + Q_PROPERTY(bool autoCompletion READ autoCompletion WRITE setAutoCompletion DESIGNABLE false) + Q_PROPERTY(Qt::CaseSensitivity autoCompletionCaseSensitivity READ autoCompletionCaseSensitivity WRITE setAutoCompletionCaseSensitivity DESIGNABLE false) +#endif // QT_NO_COMPLETER + + Q_PROPERTY(bool duplicatesEnabled READ duplicatesEnabled WRITE setDuplicatesEnabled) + Q_PROPERTY(bool frame READ hasFrame WRITE setFrame) + Q_PROPERTY(int modelColumn READ modelColumn WRITE setModelColumn) + +public: + explicit QComboBox(QWidget *parent = 0); + ~QComboBox(); + + int maxVisibleItems() const; + void setMaxVisibleItems(int maxItems); + + int count() const; + void setMaxCount(int max); + int maxCount() const; + +#ifndef QT_NO_COMPLETER + bool autoCompletion() const; + void setAutoCompletion(bool enable); + + Qt::CaseSensitivity autoCompletionCaseSensitivity() const; + void setAutoCompletionCaseSensitivity(Qt::CaseSensitivity sensitivity); +#endif + + bool duplicatesEnabled() const; + void setDuplicatesEnabled(bool enable); + + void setFrame(bool); + bool hasFrame() const; + + inline int findText(const QString &text, + Qt::MatchFlags flags = Qt::MatchExactly|Qt::MatchCaseSensitive) const + { return findData(text, Qt::DisplayRole, flags); } + int findData(const QVariant &data, int role = Qt::UserRole, + Qt::MatchFlags flags = Qt::MatchExactly|Qt::MatchCaseSensitive) const; + + enum InsertPolicy { + NoInsert, + InsertAtTop, + InsertAtCurrent, + InsertAtBottom, + InsertAfterCurrent, + InsertBeforeCurrent, + InsertAlphabetically +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + , + NoInsertion = NoInsert, + AtTop = InsertAtTop, + AtCurrent = InsertAtCurrent, + AtBottom = InsertAtBottom, + AfterCurrent = InsertAfterCurrent, + BeforeCurrent = InsertBeforeCurrent +#endif + }; +#ifdef QT3_SUPPORT + typedef InsertPolicy Policy; +#endif + + InsertPolicy insertPolicy() const; + void setInsertPolicy(InsertPolicy policy); + + enum SizeAdjustPolicy { + AdjustToContents, + AdjustToContentsOnFirstShow, + AdjustToMinimumContentsLength, // ### Qt 5: remove + AdjustToMinimumContentsLengthWithIcon + }; + + SizeAdjustPolicy sizeAdjustPolicy() const; + void setSizeAdjustPolicy(SizeAdjustPolicy policy); + int minimumContentsLength() const; + void setMinimumContentsLength(int characters); + QSize iconSize() const; + void setIconSize(const QSize &size); + + bool isEditable() const; + void setEditable(bool editable); + void setLineEdit(QLineEdit *edit); + QLineEdit *lineEdit() const; +#ifndef QT_NO_VALIDATOR + void setValidator(const QValidator *v); + const QValidator *validator() const; +#endif + +#ifndef QT_NO_COMPLETER + void setCompleter(QCompleter *c); + QCompleter *completer() const; +#endif + + QAbstractItemDelegate *itemDelegate() const; + void setItemDelegate(QAbstractItemDelegate *delegate); + + QAbstractItemModel *model() const; + void setModel(QAbstractItemModel *model); + + QModelIndex rootModelIndex() const; + void setRootModelIndex(const QModelIndex &index); + + int modelColumn() const; + void setModelColumn(int visibleColumn); + + int currentIndex() const; + + QString currentText() const; + + QString itemText(int index) const; + QIcon itemIcon(int index) const; + QVariant itemData(int index, int role = Qt::UserRole) const; + + inline void addItem(const QString &text, const QVariant &userData = QVariant()); + inline void addItem(const QIcon &icon, const QString &text, + const QVariant &userData = QVariant()); + inline void addItems(const QStringList &texts) + { insertItems(count(), texts); } + + inline void insertItem(int index, const QString &text, const QVariant &userData = QVariant()); + void insertItem(int index, const QIcon &icon, const QString &text, + const QVariant &userData = QVariant()); + void insertItems(int index, const QStringList &texts); + void insertSeparator(int index); + + void removeItem(int index); + + void setItemText(int index, const QString &text); + void setItemIcon(int index, const QIcon &icon); + void setItemData(int index, const QVariant &value, int role = Qt::UserRole); + + QAbstractItemView *view() const; + void setView(QAbstractItemView *itemView); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + virtual void showPopup(); + virtual void hidePopup(); + + bool event(QEvent *event); + +public Q_SLOTS: + void clear(); + void clearEditText(); + void setEditText(const QString &text); + void setCurrentIndex(int index); + +Q_SIGNALS: + void editTextChanged(const QString &); + void activated(int index); + void activated(const QString &); + void highlighted(int index); + void highlighted(const QString &); + void currentIndexChanged(int index); + void currentIndexChanged(const QString &); + +protected: + void focusInEvent(QFocusEvent *e); + void focusOutEvent(QFocusEvent *e); + void changeEvent(QEvent *e); + void resizeEvent(QResizeEvent *e); + void paintEvent(QPaintEvent *e); + void showEvent(QShowEvent *e); + void hideEvent(QHideEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void keyPressEvent(QKeyEvent *e); + void keyReleaseEvent(QKeyEvent *e); + void wheelEvent(QWheelEvent *e); + void contextMenuEvent(QContextMenuEvent *e); + void inputMethodEvent(QInputMethodEvent *); + QVariant inputMethodQuery(Qt::InputMethodQuery) const; + void initStyleOption(QStyleOptionComboBox *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QComboBox(QWidget *parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QComboBox(bool rw, QWidget *parent, const char *name = 0); + inline QT3_SUPPORT int currentItem() const { return currentIndex(); } + inline QT3_SUPPORT void setCurrentItem(int index) { setCurrentIndex(index); } + inline QT3_SUPPORT InsertPolicy insertionPolicy() const { return insertPolicy(); } + inline QT3_SUPPORT void setInsertionPolicy(InsertPolicy policy) { setInsertPolicy(policy); } + inline QT3_SUPPORT bool editable() const { return isEditable(); } + inline QT3_SUPPORT void popup() { showPopup(); } + inline QT3_SUPPORT void setCurrentText(const QString& text) { + int i = findText(text); + if (i != -1) + setCurrentIndex(i); + else if (isEditable()) + setEditText(text); + else + setItemText(currentIndex(), text); + } + inline QT3_SUPPORT QString text(int index) const { return itemText(index); } + + inline QT3_SUPPORT QPixmap pixmap(int index) const + { return itemIcon(index).pixmap(iconSize(), isEnabled() ? QIcon::Normal : QIcon::Disabled); } + inline QT3_SUPPORT void insertStringList(const QStringList &list, int index = -1) + { insertItems((index < 0 ? count() : index), list); } + inline QT3_SUPPORT void insertItem(const QString &text, int index = -1) + { insertItem((index < 0 ? count() : index), text); } + inline QT3_SUPPORT void insertItem(const QPixmap &pix, int index = -1) + { insertItem((index < 0 ? count() : index), QIcon(pix), QString()); } + inline QT3_SUPPORT void insertItem(const QPixmap &pix, const QString &text, int index = -1) + { insertItem((index < 0 ? count() : index), QIcon(pix), text); } + inline QT3_SUPPORT void changeItem(const QString &text, int index) + { setItemText(index, text); } + inline QT3_SUPPORT void changeItem(const QPixmap &pix, int index) + { setItemIcon(index, QIcon(pix)); } + inline QT3_SUPPORT void changeItem(const QPixmap &pix, const QString &text, int index) + { setItemIcon(index, QIcon(pix)); setItemText(index, text); } + inline QT3_SUPPORT void clearValidator() { setValidator(0); } + inline QT3_SUPPORT void clearEdit() { clearEditText(); } + +Q_SIGNALS: + QT_MOC_COMPAT void textChanged(const QString &); +#endif + +protected: + QComboBox(QComboBoxPrivate &, QWidget *); + +private: + Q_DECLARE_PRIVATE(QComboBox) + Q_DISABLE_COPY(QComboBox) + Q_PRIVATE_SLOT(d_func(), void _q_itemSelected(const QModelIndex &item)) + Q_PRIVATE_SLOT(d_func(), void _q_emitHighlighted(const QModelIndex &)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentIndexChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_returnPressed()) + Q_PRIVATE_SLOT(d_func(), void _q_resetButton()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &, const QModelIndex &)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeInserted(const QModelIndex & parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsInserted(const QModelIndex & parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsRemoved(const QModelIndex & parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) + Q_PRIVATE_SLOT(d_func(), void _q_modelReset()) +#ifdef QT_KEYPAD_NAVIGATION + Q_PRIVATE_SLOT(d_func(), void _q_completerActivated()) +#endif +}; + +inline void QComboBox::addItem(const QString &atext, const QVariant &auserData) +{ insertItem(count(), atext, auserData); } +inline void QComboBox::addItem(const QIcon &aicon, const QString &atext, + const QVariant &auserData) +{ insertItem(count(), aicon, atext, auserData); } + +inline void QComboBox::insertItem(int aindex, const QString &atext, + const QVariant &auserData) +{ insertItem(aindex, QIcon(), atext, auserData); } + +#endif // QT_NO_COMBOBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOMBOBOX_H diff --git a/src/gui/widgets/qcombobox_p.h b/src/gui/widgets/qcombobox_p.h new file mode 100644 index 0000000..f1203dc --- /dev/null +++ b/src/gui/widgets/qcombobox_p.h @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMBOBOX_P_H +#define QCOMBOBOX_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 "QtGui/qcombobox.h" + +#ifndef QT_NO_COMBOBOX +#include "QtGui/qabstractslider.h" +#include "QtGui/qapplication.h" +#include "QtGui/qitemdelegate.h" +#include "QtGui/qstandarditemmodel.h" +#include "QtGui/qlineedit.h" +#include "QtGui/qlistview.h" +#include "QtGui/qpainter.h" +#include "QtGui/qstyle.h" +#include "QtGui/qstyleoption.h" +#include "QtCore/qhash.h" +#include "QtCore/qpair.h" +#include "QtCore/qtimer.h" +#include "private/qwidget_p.h" +#include "QtCore/qpointer.h" +#include "QtGui/qcompleter.h" +#include "QtGui/qevent.h" +#include "QtCore/qdebug.h" + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +class QComboBoxListView : public QListView +{ + Q_OBJECT +public: + QComboBoxListView(QComboBox *cmb = 0) : combo(cmb) {} + +protected: + void resizeEvent(QResizeEvent *event) + { + resizeContents(viewport()->width(), contentsSize().height()); + QListView::resizeEvent(event); + } + + QStyleOptionViewItem viewOptions() const + { + QStyleOptionViewItem option = QListView::viewOptions(); + option.showDecorationSelected = true; + if (combo) + option.font = combo->font(); + return option; + } + + void paintEvent(QPaintEvent *e) + { + if (combo) { + QStyleOptionComboBox opt; + opt.initFrom(combo); + opt.editable = combo->isEditable(); + if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)) { + //we paint the empty menu area to avoid having blank space that can happen when scrolling + QStyleOptionMenuItem menuOpt; + menuOpt.initFrom(this); + menuOpt.palette = palette(); + menuOpt.state = QStyle::State_None; + menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; + menuOpt.menuRect = e->rect(); + menuOpt.maxIconWidth = 0; + menuOpt.tabWidth = 0; + QPainter p(viewport()); + combo->style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this); + } + } + QListView::paintEvent(e); + } + +private: + QComboBox *combo; +}; + + +class QStandardItemModel; + +class Q_AUTOTEST_EXPORT QComboBoxPrivateScroller : public QWidget +{ + Q_OBJECT + +public: + QComboBoxPrivateScroller(QAbstractSlider::SliderAction action, QWidget *parent) + : QWidget(parent), sliderAction(action) + { + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + setAttribute(Qt::WA_NoMousePropagation); + } + QSize sizeHint() const { + return QSize(20, style()->pixelMetric(QStyle::PM_MenuScrollerHeight)); + } + +protected: + inline void stopTimer() { + timer.stop(); + } + + inline void startTimer() { + timer.start(100, this); + fast = false; + } + + void enterEvent(QEvent *) { + startTimer(); + } + + void leaveEvent(QEvent *) { + stopTimer(); + } + void timerEvent(QTimerEvent *e) { + if (e->timerId() == timer.timerId()) { + emit doScroll(sliderAction); + if (fast) { + emit doScroll(sliderAction); + emit doScroll(sliderAction); + } + } + } + void hideEvent(QHideEvent *) { + stopTimer(); + } + + void mouseMoveEvent(QMouseEvent *e) + { + // Enable fast scrolling if the cursor is directly above or below the popup. + const int mouseX = e->pos().x(); + const int mouseY = e->pos().y(); + const bool horizontallyInside = pos().x() < mouseX && mouseX < rect().right() + 1; + const bool verticallyOutside = (sliderAction == QAbstractSlider::SliderSingleStepAdd) ? + rect().bottom() + 1 < mouseY : mouseY < pos().y(); + + fast = horizontallyInside && verticallyOutside; + } + + void paintEvent(QPaintEvent *) { + QPainter p(this); + QStyleOptionMenuItem menuOpt; + menuOpt.init(this); + menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; + menuOpt.menuRect = rect(); + menuOpt.maxIconWidth = 0; + menuOpt.tabWidth = 0; + menuOpt.menuItemType = QStyleOptionMenuItem::Scroller; + if (sliderAction == QAbstractSlider::SliderSingleStepAdd) + menuOpt.state |= QStyle::State_DownArrow; + p.eraseRect(rect()); + style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p); + } + +Q_SIGNALS: + void doScroll(int action); + +private: + QAbstractSlider::SliderAction sliderAction; + QBasicTimer timer; + bool fast; +}; + +class Q_AUTOTEST_EXPORT QComboBoxPrivateContainer : public QFrame +{ + Q_OBJECT + +public: + QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent); + QAbstractItemView *itemView() const; + void setItemView(QAbstractItemView *itemView); + int spacing() const; + void updateTopBottomMargin(); + + QTimer blockMouseReleaseTimer; + QBasicTimer adjustSizeTimer; + QPoint initialClickPosition; + +public Q_SLOTS: + void scrollItemView(int action); + void updateScrollers(); + void setCurrentIndex(const QModelIndex &index); + void viewDestroyed(); + +protected: + void changeEvent(QEvent *e); + bool eventFilter(QObject *o, QEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void showEvent(QShowEvent *e); + void hideEvent(QHideEvent *e); + void timerEvent(QTimerEvent *timerEvent); + void leaveEvent(QEvent *e); + void resizeEvent(QResizeEvent *e); + QStyleOptionComboBox comboStyleOption() const; + +Q_SIGNALS: + void itemSelected(const QModelIndex &); + void resetButton(); + +private: + QComboBox *combo; + QAbstractItemView *view; + QComboBoxPrivateScroller *top; + QComboBoxPrivateScroller *bottom; +}; + +class QComboMenuDelegate : public QAbstractItemDelegate +{ +public: + QComboMenuDelegate(QObject *parent, QComboBox *cmb) : QAbstractItemDelegate(parent), mCombo(cmb) {} + +protected: + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { + QStyleOptionMenuItem opt = getStyleOption(option, index); + painter->eraseRect(option.rect); + mCombo->style()->drawControl(QStyle::CE_MenuItem, &opt, painter, mCombo); + } + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const { + QStyleOptionMenuItem opt = getStyleOption(option, index); + return mCombo->style()->sizeFromContents( + QStyle::CT_MenuItem, &opt, option.rect.size(), mCombo); + } + +private: + QStyleOptionMenuItem getStyleOption(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QComboBox *mCombo; +}; + +// Note that this class is intentionally not using QStyledItemDelegate +// Vista does not use the new theme for combo boxes and there might +// be other side effects from using the new class +class QComboBoxDelegate : public QItemDelegate +{ +public: + QComboBoxDelegate(QObject *parent, QComboBox *cmb) : QItemDelegate(parent), mCombo(cmb) {} + + static bool isSeparator(const QModelIndex &index) { + return index.data(Qt::AccessibleDescriptionRole).toString() == QString::fromLatin1("separator"); + } + static void setSeparator(QAbstractItemModel *model, const QModelIndex &index) { + model->setData(index, QString::fromLatin1("separator"), Qt::AccessibleDescriptionRole); + if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(model)) + if (QStandardItem *item = m->itemFromIndex(index)) + item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); + } + +protected: + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { + if (isSeparator(index)) { + QRect rect = option.rect; + if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3*>(&option)) + if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(v3->widget)) + rect.setWidth(view->viewport()->width()); + QStyleOption opt; + opt.rect = rect; + mCombo->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter, mCombo); + } else { + QItemDelegate::paint(painter, option, index); + } + } + + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const { + if (isSeparator(index)) { + int pm = mCombo->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, mCombo); + return QSize(pm, pm); + } + return QItemDelegate::sizeHint(option, index); + } +private: + QComboBox *mCombo; +}; + +class QComboBoxPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QComboBox) +public: + QComboBoxPrivate(); + ~QComboBoxPrivate() {} + void init(); + QComboBoxPrivateContainer* viewContainer(); + void updateLineEditGeometry(); + void _q_returnPressed(); + void _q_complete(); + void _q_itemSelected(const QModelIndex &item); + bool contains(const QString &text, int role); + void emitActivated(const QModelIndex&); + void _q_emitHighlighted(const QModelIndex&); + void _q_emitCurrentIndexChanged(const QModelIndex &index); + void _q_modelDestroyed(); + void _q_modelReset(); +#ifdef QT_KEYPAD_NAVIGATION + void _q_completerActivated(); +#endif + void _q_resetButton(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void _q_rowsAboutToBeInserted(const QModelIndex & parent, int start, int end); + void _q_rowsInserted(const QModelIndex & parent, int start, int end); + void _q_rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end); + void _q_rowsRemoved(const QModelIndex & parent, int start, int end); + void updateArrow(QStyle::StateFlag state); + bool updateHoverControl(const QPoint &pos); + QRect popupGeometry(int screen = -1) const; + QStyle::SubControl newHoverControl(const QPoint &pos); + int computeWidthHint() const; + QSize recomputeSizeHint(QSize &sh) const; + void adjustComboBoxSize(); + QString itemText(const QModelIndex &index) const; + QIcon itemIcon(const QModelIndex &index) const; + int itemRole() const; + void updateLayoutDirection(); + void setCurrentIndex(const QModelIndex &index); + void updateDelegate(); + void keyboardSearchString(const QString &text); + void modelChanged(); + + QAbstractItemModel *model; + QLineEdit *lineEdit; + QComboBoxPrivateContainer *container; + QComboBox::InsertPolicy insertPolicy; + QComboBox::SizeAdjustPolicy sizeAdjustPolicy; + int minimumContentsLength; + QSize iconSize; + uint shownOnce : 1; + uint autoCompletion : 1; + uint duplicatesEnabled : 1; + uint frame : 1; + uint padding : 26; + int maxVisibleItems; + int maxCount; + int modelColumn; + bool inserting; + mutable QSize minimumSizeHint; + mutable QSize sizeHint; + QStyle::StateFlag arrowState; + QStyle::SubControl hoverControl; + QRect hoverRect; + QPersistentModelIndex currentIndex; + QPersistentModelIndex root; + Qt::CaseSensitivity autoCompletionCaseSensitivity; + int indexBeforeChange; +#ifndef QT_NO_COMPLETER + QPointer<QCompleter> completer; +#endif + static QPalette viewContainerPalette(QComboBox *cmb) + { return cmb->d_func()->viewContainer()->palette(); } +}; + +QT_END_NAMESPACE + +#endif // QT_NO_COMBOBOX + +#endif // QCOMBOBOX_P_H diff --git a/src/gui/widgets/qcommandlinkbutton.cpp b/src/gui/widgets/qcommandlinkbutton.cpp new file mode 100644 index 0000000..13ee6af --- /dev/null +++ b/src/gui/widgets/qcommandlinkbutton.cpp @@ -0,0 +1,384 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcommandlinkbutton.h" +#include "qstylepainter.h" +#include "qstyleoption.h" +#include "qtextdocument.h" +#include "qtextlayout.h" +#include "qcolor.h" +#include "qfont.h" + +#include "private/qpushbutton_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QCommandLinkButton + \since 4.4 + \brief The QCommandLinkButton widget provides a Vista style command link button. + + \ingroup basicwidgets + \mainclass + + The command link is a new control that was introduced by Windows Vista. It's + intended use is similar to that of a radio button in that it is used to choose + between a set of mutually exclusive options. Command link buttons should not + be used by themselves but rather as an alternative to radio buttons in + Wizards and dialogs and makes pressing the "next" button redundant. + The appearance is generally similar to that of a flat pushbutton, but + it allows for a descriptive text in addition to the normal button text. + By default it will also carry an arrow icon, indicating that pressing the + control will open another window or page. + + \sa QPushButton QRadioButton +*/ + +/*! + \property QCommandLinkButton::description + \brief A descriptive label to complement the button text + + Setting this property will set a descriptive text on the + button, complementing the text label. This will usually + be displayed in a smaller font than the primary text. +*/ + +/*! + \property QCommandLinkButton::flat + \brief This property determines whether the button is displayed as a flat + panel or with a border. + + By default, this property is set to false. + + \sa QPushButton::flat +*/ + +class QCommandLinkButtonPrivate : public QPushButtonPrivate +{ + Q_DECLARE_PUBLIC(QCommandLinkButton) + +public: + QCommandLinkButtonPrivate() + : QPushButtonPrivate(){} + + void init(); + qreal titleSize() const; + bool usingVistaStyle() const; + + QFont titleFont() const; + QFont descriptionFont() const; + + QRect titleRect() const; + QRect descriptionRect() const; + + int textOffset() const; + int descriptionOffset() const; + int descriptionHeight(int width) const; + QColor mergedColors(const QColor &a, const QColor &b, int value) const; + + int topMargin() const { return 10; } + int leftMargin() const { return 7; } + int rightMargin() const { return 4; } + int bottomMargin() const { return 4; } + + QString description; + QColor currentColor; +}; + +// Mix colors a and b with a ratio in the range [0-255] +QColor QCommandLinkButtonPrivate::mergedColors(const QColor &a, const QColor &b, int value = 50) const +{ + Q_ASSERT(value >= 0); + Q_ASSERT(value <= 255); + QColor tmp = a; + tmp.setRed((tmp.red() * value) / 255 + (b.red() * (255 - value)) / 255); + tmp.setGreen((tmp.green() * value) / 255 + (b.green() * (255 - value)) / 255); + tmp.setBlue((tmp.blue() * value) / 255 + (b.blue() * (255 - value)) / 255); + return tmp; +} + +QFont QCommandLinkButtonPrivate::titleFont() const +{ + Q_Q(const QCommandLinkButton); + QFont font = q->font(); + if (usingVistaStyle()) { + font.setPointSizeF(12.0); + } else { + font.setBold(true); + font.setPointSizeF(9.0); + } + return font; +} + +QFont QCommandLinkButtonPrivate::descriptionFont() const +{ + Q_Q(const QCommandLinkButton); + QFont font = q->font(); + font.setPointSizeF(9.0); + return font; +} + +QRect QCommandLinkButtonPrivate::titleRect() const +{ + Q_Q(const QCommandLinkButton); + return q->rect().adjusted(textOffset(), topMargin(), + -rightMargin(), 0); +} + +QRect QCommandLinkButtonPrivate::descriptionRect() const +{ + Q_Q(const QCommandLinkButton); + return q->rect().adjusted(textOffset(), descriptionOffset(), + -rightMargin(), -bottomMargin()); +} + +int QCommandLinkButtonPrivate::textOffset() const +{ + Q_Q(const QCommandLinkButton); + return q->icon().actualSize(q->iconSize()).width() + leftMargin() + 6; +} + +int QCommandLinkButtonPrivate::descriptionOffset() const +{ + QFontMetrics fm(titleFont()); + return topMargin() + fm.height(); +} + +bool QCommandLinkButtonPrivate::usingVistaStyle() const +{ + Q_Q(const QCommandLinkButton); + //### This is a hack to detect if we are indeed running Vista style themed and not in classic + // When we add api to query for this, we should change this implementation to use it. + return q->style()->inherits("QWindowsVistaStyle") + && !q->style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal); +} + +void QCommandLinkButtonPrivate::init() +{ + Q_Q(QCommandLinkButton); + QPushButtonPrivate::init(); + q->setAttribute(Qt::WA_Hover); + + QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::PushButton); + policy.setHeightForWidth(true); + q->setSizePolicy(policy); + + q->setIconSize(QSize(20, 20)); + q->setIcon(q->style()->standardIcon(QStyle::SP_CommandLink)); +} + +// Calculates the height of the description text based on widget width +int QCommandLinkButtonPrivate::descriptionHeight(int widgetWidth) const +{ + // Calc width of actual paragraph + int lineWidth = widgetWidth - textOffset() - rightMargin(); + + qreal descriptionheight = 0; + if (!description.isEmpty()) { + QTextLayout layout(description); + layout.setFont(descriptionFont()); + layout.beginLayout(); + while (true) { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, descriptionheight)); + descriptionheight += line.height(); + } + layout.endLayout(); + } + return qRound(descriptionheight); +} + +/*! + \reimp + */ +QSize QCommandLinkButton::minimumSizeHint() const +{ + Q_D(const QCommandLinkButton); + QSize size = sizeHint(); + int minimumHeight = qMax(d->descriptionOffset() + d->bottomMargin(), + iconSize().height() + d->topMargin()); + size.setHeight(minimumHeight); + return size; +} + +/*! + Constructs a command link with no text and a \a parent. +*/ + +QCommandLinkButton::QCommandLinkButton(QWidget *parent) +: QPushButton(*new QCommandLinkButtonPrivate, parent) +{ + Q_D(QCommandLinkButton); + d->init(); +} + +/*! + Constructs a command link with the parent \a parent and the text \a + text. +*/ + +QCommandLinkButton::QCommandLinkButton(const QString &text, QWidget *parent) + : QPushButton(*new QCommandLinkButtonPrivate, parent) +{ + Q_D(QCommandLinkButton); + setText(text); + d->init(); +} + +/*! + Constructs a command link with a \a text, a \a description, and a \a parent. +*/ +QCommandLinkButton::QCommandLinkButton(const QString &text, const QString &description, QWidget *parent) + : QPushButton(*new QCommandLinkButtonPrivate, parent) +{ + Q_D(QCommandLinkButton); + setText(text); + setDescription(description); + d->init(); +} + +/*! \reimp */ +bool QCommandLinkButton::event(QEvent *e) +{ + return QPushButton::event(e); +} + +/*! \reimp */ +QSize QCommandLinkButton::sizeHint() const +{ +// Standard size hints from UI specs +// Without note: 135, 41 +// With note: 135, 60 + Q_D(const QCommandLinkButton); + + QSize size = QPushButton::sizeHint(); + QFontMetrics fm(d->titleFont()); + int textWidth = qMax(fm.width(text()), 135); + int buttonWidth = textWidth + d->textOffset() + d->rightMargin(); + int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin(); + + size.setWidth(qMax(size.width(), buttonWidth)); + size.setHeight(qMax(d->description.isEmpty() ? 41 : 60, + heightWithoutDescription + d->descriptionHeight(buttonWidth))); + return size; +} + +/*! \reimp */ +int QCommandLinkButton::heightForWidth(int width) const +{ + Q_D(const QCommandLinkButton); + int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin(); + // find the width available for the description area + return heightWithoutDescription + d->descriptionHeight(width); +} + +/*! \reimp */ +void QCommandLinkButton::paintEvent(QPaintEvent *) +{ + Q_D(QCommandLinkButton); + QStylePainter p(this); + p.save(); + + QStyleOptionButton option; + initStyleOption(&option); + + //Enable command link appearence on Vista + option.features |= QStyleOptionButton::CommandLinkButton; + option.text = QString(); + option.icon = QIcon(); //we draw this ourselves + QSize pixmapSize = icon().actualSize(iconSize()); + + int vOffset = isDown() ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical) : 0; + int hOffset = isDown() ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal) : 0; + + //Draw icon + p.drawControl(QStyle::CE_PushButton, option); + if (!icon().isNull()) + p.drawPixmap(d->leftMargin() + hOffset, d->topMargin() + vOffset, + icon().pixmap(pixmapSize, isEnabled() ? QIcon::Normal : QIcon::Disabled, + isChecked() ? QIcon::On : QIcon::Off)); + + //Draw title + QColor textColor = palette().buttonText().color(); + if (isEnabled() && d->usingVistaStyle()) { + textColor = QColor(21, 28, 85); + if (underMouse() && !isDown()) + textColor = QColor(7, 64, 229); + //A simple text color transition + d->currentColor = d->mergedColors(textColor, d->currentColor, 60); + option.palette.setColor(QPalette::ButtonText, d->currentColor); + } + + int textflags = Qt::TextShowMnemonic; + if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &option, this)) + textflags |= Qt::TextHideMnemonic; + + p.setFont(d->titleFont()); + p.drawItemText(d->titleRect().translated(hOffset, vOffset), + textflags, option.palette, isEnabled(), text(), QPalette::ButtonText); + + //Draw description + textflags |= Qt::TextWordWrap | Qt::ElideRight; + p.setFont(d->descriptionFont()); + p.drawItemText(d->descriptionRect().translated(hOffset, vOffset), textflags, + option.palette, isEnabled(), description(), QPalette::ButtonText); + p.restore(); +} + +void QCommandLinkButton::setDescription(const QString &description) +{ + Q_D(QCommandLinkButton); + d->description = description; + updateGeometry(); + update(); +} + +QString QCommandLinkButton::description() const +{ + Q_D(const QCommandLinkButton); + return d->description; +} + +QT_END_NAMESPACE + diff --git a/src/gui/widgets/qcommandlinkbutton.h b/src/gui/widgets/qcommandlinkbutton.h new file mode 100644 index 0000000..93d91cb --- /dev/null +++ b/src/gui/widgets/qcommandlinkbutton.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMMANDLINKBUTTON_H +#define QCOMMANDLINKBUTTON_H + +#include <QtGui/qpushbutton.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QCommandLinkButtonPrivate; + +class Q_GUI_EXPORT QCommandLinkButton: public QPushButton +{ + Q_OBJECT + + Q_PROPERTY(QString description READ description WRITE setDescription) + Q_PROPERTY(bool flat READ isFlat WRITE setFlat DESIGNABLE false) + +public: + explicit QCommandLinkButton(QWidget *parent=0); + explicit QCommandLinkButton(const QString &text, QWidget *parent=0); + QCommandLinkButton(const QString &text, const QString &description, QWidget *parent=0); + QString description() const; + void setDescription(const QString &description); + +protected: + QSize sizeHint() const; + int heightForWidth(int) const; + QSize minimumSizeHint() const; + bool event(QEvent *e); + void paintEvent(QPaintEvent *); + +private: + Q_DISABLE_COPY(QCommandLinkButton) + Q_DECLARE_PRIVATE(QCommandLinkButton) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOMMANDLINKBUTTON diff --git a/src/gui/widgets/qdatetimeedit.cpp b/src/gui/widgets/qdatetimeedit.cpp new file mode 100644 index 0000000..83bec68 --- /dev/null +++ b/src/gui/widgets/qdatetimeedit.cpp @@ -0,0 +1,2647 @@ +/****************************************************************************) +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <math.h> +#include <private/qdatetimeedit_p.h> +#include <qabstractspinbox.h> +#include <qapplication.h> +#include <qdatetimeedit.h> +#include <qdesktopwidget.h> +#include <qdebug.h> +#include <qevent.h> +#include <qlineedit.h> +#include <private/qlineedit_p.h> +#include <qlocale.h> +#include <qpainter.h> +#include <qlayout.h> +#include <qset.h> +#include <qstyle.h> + +#ifndef QT_NO_DATETIMEEDIT + +//#define QDATETIMEEDIT_QDTEDEBUG +#ifdef QDATETIMEEDIT_QDTEDEBUG +# define QDTEDEBUG qDebug() << QString::fromLatin1("%1:%2").arg(__FILE__).arg(__LINE__) +# define QDTEDEBUGN qDebug +#else +# define QDTEDEBUG if (false) qDebug() +# define QDTEDEBUGN if (false) qDebug +#endif + +QT_BEGIN_NAMESPACE + +// --- QDateTimeEdit --- + +/*! + \class QDateTimeEdit + \brief The QDateTimeEdit class provides a widget for editing dates and times. + + \ingroup basicwidgets + \mainclass + + QDateTimeEdit allows the user to edit dates by using the keyboard or + the arrow keys to increase and decrease date and time values. The + arrow keys can be used to move from section to section within the + QDateTimeEdit box. Dates and times appear in accordance with the + format set; see setDisplayFormat(). + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 0 + + Here we've created a new QDateTimeEdit object initialized with + today's date, and restricted the valid date range to today plus or + minus 365 days. We've set the order to month, day, year. + + The minimum value for QDateTimeEdit is 14 September 1752, + and 2 January 4713BC for QDate. You can change this by calling + setMinimumDate(), setMaximumDate(), setMinimumTime(), + and setMaximumTime(). + + \section1 Using a Pop-up Calendar Widget + + QDateTimeEdit can be configured to allow a QCalendarWidget to be used + to select dates. This is enabled by setting the calendarPopup property. + Additionally, you can supply a custom calendar widget for use as the + calendar pop-up by calling the setCalendarWidget() function. The existing + calendar widget can be retrieved with calendarWidget(). + + \table 100% + \row \o \inlineimage windowsxp-datetimeedit.png Screenshot of a Windows XP style date time editing widget + \o A date time editing widget shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage macintosh-datetimeedit.png Screenshot of a Macintosh style date time editing widget + \o A date time editing widget shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage plastique-datetimeedit.png Screenshot of a Plastique style date time editing widget + \o A date time editing widget shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QDateEdit, QTimeEdit, QDate, QTime +*/ + +/*! + \enum QDateTimeEdit::Section + + \value NoSection + \value AmPmSection + \value MSecSection + \value SecondSection + \value MinuteSection + \value HourSection + \value DaySection + \value MonthSection + \value YearSection + \omitvalue DateSections_Mask + \omitvalue TimeSections_Mask +*/ + +/*! + \fn void QDateTimeEdit::dateTimeChanged(const QDateTime &datetime) + + This signal is emitted whenever the date or time is changed. The + new date and time is passed in \a datetime. +*/ + +/*! + \fn void QDateTimeEdit::timeChanged(const QTime &time) + + This signal is emitted whenever the time is changed. The new time + is passed in \a time. +*/ + +/*! + \fn void QDateTimeEdit::dateChanged(const QDate &date) + + This signal is emitted whenever the date is changed. The new date + is passed in \a date. +*/ + + +/*! + Constructs an empty date time editor with a \a parent. +*/ + +QDateTimeEdit::QDateTimeEdit(QWidget *parent) + : QAbstractSpinBox(*new QDateTimeEditPrivate, parent) +{ + Q_D(QDateTimeEdit); + d->init(QDateTime(QDATETIMEEDIT_DATE_INITIAL, QDATETIMEEDIT_TIME_MIN)); +} + +/*! + Constructs an empty date time editor with a \a parent. The value + is set to \a datetime. +*/ + +QDateTimeEdit::QDateTimeEdit(const QDateTime &datetime, QWidget *parent) + : QAbstractSpinBox(*new QDateTimeEditPrivate, parent) +{ + Q_D(QDateTimeEdit); + d->init(datetime.isValid() ? datetime : QDateTime(QDATETIMEEDIT_DATE_INITIAL, + QDATETIMEEDIT_TIME_MIN)); +} + +/*! + \fn QDateTimeEdit::QDateTimeEdit(const QDate &date, QWidget *parent) + + Constructs an empty date time editor with a \a parent. + The value is set to \a date. +*/ + +QDateTimeEdit::QDateTimeEdit(const QDate &date, QWidget *parent) + : QAbstractSpinBox(*new QDateTimeEditPrivate, parent) +{ + Q_D(QDateTimeEdit); + d->init(date.isValid() ? date : QDATETIMEEDIT_DATE_INITIAL); +} + +/*! + \fn QDateTimeEdit::QDateTimeEdit(const QTime &time, QWidget *parent) + + Constructs an empty date time editor with a \a parent. + The value is set to \a time. +*/ + +QDateTimeEdit::QDateTimeEdit(const QTime &time, QWidget *parent) + : QAbstractSpinBox(*new QDateTimeEditPrivate, parent) +{ + Q_D(QDateTimeEdit); + d->init(time.isValid() ? time : QDATETIMEEDIT_TIME_MIN); +} + +/*! + \internal +*/ + +QDateTimeEdit::QDateTimeEdit(const QVariant &var, QVariant::Type parserType, QWidget *parent) + : QAbstractSpinBox(*new QDateTimeEditPrivate, parent) +{ + Q_D(QDateTimeEdit); + d->parserType = parserType; + d->init(var); +} + +/*! + \property QDateTimeEdit::dateTime + \brief the QDateTime that is set in the QDateTimeEdit + + By default, this property contains a date that refers to January 1, + 2000 and a time of 00:00:00 and 0 milliseconds. + + \sa date, time +*/ + +QDateTime QDateTimeEdit::dateTime() const +{ + Q_D(const QDateTimeEdit); + return d->value.toDateTime(); +} + +void QDateTimeEdit::setDateTime(const QDateTime &datetime) +{ + Q_D(QDateTimeEdit); + if (datetime.isValid()) { + d->clearCache(); + if (!(d->sections & DateSections_Mask)) + setDateRange(datetime.date(), datetime.date()); + d->setValue(QVariant(datetime), EmitIfChanged); + } +} + +/*! + \property QDateTimeEdit::date + \brief the QDate that is set in the QDateTimeEdit + + By default, this property contains a date that refers to January 1, 2000. + + \sa time, dateTime +*/ + +/*! + Returns the date of the date time edit. +*/ +QDate QDateTimeEdit::date() const +{ + Q_D(const QDateTimeEdit); + return d->value.toDate(); +} + +void QDateTimeEdit::setDate(const QDate &date) +{ + Q_D(QDateTimeEdit); + if (date.isValid()) { + if (!(d->sections & DateSections_Mask)) + setDateRange(date, date); + + d->clearCache(); + d->setValue(QDateTime(date, d->value.toTime(), d->spec), EmitIfChanged); + d->updateTimeSpec(); + } +} + +/*! + \property QDateTimeEdit::time + \brief the QTime that is set in the QDateTimeEdit + + By default, this property contains a time of 00:00:00 and 0 milliseconds. + + \sa date, dateTime +*/ + +/*! + Returns the time of the date time edit. +*/ +QTime QDateTimeEdit::time() const +{ + Q_D(const QDateTimeEdit); + return d->value.toTime(); +} + +void QDateTimeEdit::setTime(const QTime &time) +{ + Q_D(QDateTimeEdit); + if (time.isValid()) { + d->clearCache(); + d->setValue(QDateTime(d->value.toDate(), time, d->spec), EmitIfChanged); + } +} + + +/*! + \property QDateTimeEdit::minimumDateTime + \since 4.4 + + \brief the minimum datetime of the date time edit + + When setting this property the \l maximumDateTime() is adjusted if + necessary to ensure that the range remains valid. If the datetime is + not a valid QDateTime object, this function does nothing. + + The default minimumDateTime can be restored with + clearMinimumDateTime() + + By default, this property contains a date that refers to September 14, + 1752 and a time of 00:00:00 and 0 milliseconds. + + \sa maximumDateTime(), minimumTime(), maximumTime(), minimumDate(), + maximumDate(), setDateTimeRange(), setDateRange(), setTimeRange(), + clearMaximumDateTime(), clearMinimumDate(), + clearMaximumDate(), clearMinimumTime(), clearMaximumTime() +*/ + +QDateTime QDateTimeEdit::minimumDateTime() const +{ + Q_D(const QDateTimeEdit); + return d->minimum.toDateTime(); +} + +void QDateTimeEdit::clearMinimumDateTime() +{ + setMinimumDateTime(QDateTime(QDATETIMEEDIT_COMPAT_DATE_MIN, QDATETIMEEDIT_TIME_MIN)); +} + +void QDateTimeEdit::setMinimumDateTime(const QDateTime &dt) +{ + Q_D(QDateTimeEdit); + if (dt.isValid() && dt.date() >= QDATETIMEEDIT_DATE_MIN) { + const QDateTime m = dt.toTimeSpec(d->spec); + const QDateTime max = d->maximum.toDateTime(); + d->setRange(m, (max > m ? max : m)); + } +} + +/*! + \property QDateTimeEdit::maximumDateTime + \since 4.4 + + \brief the maximum datetime of the date time edit + + When setting this property the \l minimumDateTime() is adjusted if + necessary to ensure that the range remains valid. If the datetime is + not a valid QDateTime object, this function does nothing. + + The default maximumDateTime can be restored with + clearMaximumDateTime(). + + By default, this property contains a date that refers to 31 December, + 7999 and a time of 23:59:59 and 999 milliseconds. + + \sa minimumDateTime(), minimumTime(), maximumTime(), minimumDate(), + maximumDate(), setDateTimeRange(), setDateRange(), setTimeRange(), + clearMinimumDateTime(), clearMinimumDate(), + clearMaximumDate(), clearMinimumTime(), clearMaximumTime() +*/ + +QDateTime QDateTimeEdit::maximumDateTime() const +{ + Q_D(const QDateTimeEdit); + return d->maximum.toDateTime(); +} + +void QDateTimeEdit::clearMaximumDateTime() +{ + setMaximumDateTime(QDATETIMEEDIT_DATETIME_MAX); +} + +void QDateTimeEdit::setMaximumDateTime(const QDateTime &dt) +{ + Q_D(QDateTimeEdit); + if (dt.isValid() && dt.date() <= QDATETIMEEDIT_DATE_MAX) { + const QDateTime m = dt.toTimeSpec(d->spec); + const QDateTime min = d->minimum.toDateTime(); + d->setRange((min < m ? min : m), m); + } +} + + +/*! + Convenience function to set minimum and maximum date time with one + function call. + \since 4.4 + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 1 + + is analogous to: + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 2 + + If either \a min or \a max are not valid, this function does + nothing. + + \sa setMinimumDate(), maximumDate(), setMaximumDate(), + clearMinimumDate(), setMinimumTime(), maximumTime(), + setMaximumTime(), clearMinimumTime(), QDateTime::isValid() +*/ + +void QDateTimeEdit::setDateTimeRange(const QDateTime &min, const QDateTime &max) +{ + Q_D(QDateTimeEdit); + const QDateTime minimum = min.toTimeSpec(d->spec); + QDateTime maximum = max.toTimeSpec(d->spec); + if (min > max) + maximum = minimum; + d->setRange(minimum, maximum); +} + +/*! + \property QDateTimeEdit::minimumDate + + \brief the minimum date of the date time edit + + When setting this property the \l maximumDate is adjusted if + necessary, to ensure that the range remains valid. If the date is + not a valid QDate object, this function does nothing. + + By default, this property contains a date that refers to September 14, 1752. + The minimum date must be at least the first day in year 100, otherwise + setMinimumDate() has no effect. + + \sa minimumTime(), maximumTime(), setDateRange() +*/ + +QDate QDateTimeEdit::minimumDate() const +{ + Q_D(const QDateTimeEdit); + return d->minimum.toDate(); +} + +void QDateTimeEdit::setMinimumDate(const QDate &min) +{ + Q_D(QDateTimeEdit); + if (min.isValid() && min >= QDATETIMEEDIT_DATE_MIN) { + setMinimumDateTime(QDateTime(min, d->minimum.toTime(), d->spec)); + } +} + +void QDateTimeEdit::clearMinimumDate() +{ + setMinimumDate(QDATETIMEEDIT_COMPAT_DATE_MIN); +} + +/*! + \property QDateTimeEdit::maximumDate + + \brief the maximum date of the date time edit + + When setting this property the \l minimumDate is adjusted if + necessary to ensure that the range remains valid. If the date is + not a valid QDate object, this function does nothing. + + By default, this property contains a date that refers to December 31, 7999. + + \sa minimumDate, minimumTime, maximumTime, setDateRange() +*/ + +QDate QDateTimeEdit::maximumDate() const +{ + Q_D(const QDateTimeEdit); + return d->maximum.toDate(); +} + +void QDateTimeEdit::setMaximumDate(const QDate &max) +{ + Q_D(QDateTimeEdit); + if (max.isValid()) { + setMaximumDateTime(QDateTime(max, d->maximum.toTime(), d->spec)); + } +} + +void QDateTimeEdit::clearMaximumDate() +{ + setMaximumDate(QDATETIMEEDIT_DATE_MAX); +} + +/*! + \property QDateTimeEdit::minimumTime + + \brief the minimum time of the date time edit + + When setting this property the \l maximumTime is adjusted if + necessary, to ensure that the range remains valid. If the time is + not a valid QTime object, this function does nothing. + + By default, this property contains a time of 00:00:00 and 0 milliseconds. + + \sa maximumTime, minimumDate, maximumDate, setTimeRange() +*/ + +QTime QDateTimeEdit::minimumTime() const +{ + Q_D(const QDateTimeEdit); + return d->minimum.toTime(); +} + +void QDateTimeEdit::setMinimumTime(const QTime &min) +{ + Q_D(QDateTimeEdit); + if (min.isValid()) { + const QDateTime m(d->minimum.toDate(), min, d->spec); + setMinimumDateTime(m); + } +} + +void QDateTimeEdit::clearMinimumTime() +{ + setMinimumTime(QDATETIMEEDIT_TIME_MIN); +} + +/*! + \property QDateTimeEdit::maximumTime + + \brief the maximum time of the date time edit + + When setting this property, the \l minimumTime is adjusted if + necessary to ensure that the range remains valid. If the time is + not a valid QTime object, this function does nothing. + + By default, this property contains a time of 23:59:59 and 999 milliseconds. + + \sa minimumTime, minimumDate, maximumDate, setTimeRange() +*/ +QTime QDateTimeEdit::maximumTime() const +{ + Q_D(const QDateTimeEdit); + return d->maximum.toTime(); +} + +void QDateTimeEdit::setMaximumTime(const QTime &max) +{ + Q_D(QDateTimeEdit); + if (max.isValid()) { + const QDateTime m(d->maximum.toDate(), max); + setMaximumDateTime(m); + } +} + +void QDateTimeEdit::clearMaximumTime() +{ + setMaximumTime(QDATETIMEEDIT_TIME_MAX); +} + +/*! + Convenience function to set minimum and maximum date with one + function call. + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 3 + + is analogous to: + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 4 + + If either \a min or \a max are not valid, this function does + nothing. + + \sa setMinimumDate(), maximumDate(), setMaximumDate(), + clearMinimumDate(), setMinimumTime(), maximumTime(), + setMaximumTime(), clearMinimumTime(), QDate::isValid() +*/ + +void QDateTimeEdit::setDateRange(const QDate &min, const QDate &max) +{ + Q_D(QDateTimeEdit); + if (min.isValid() && max.isValid()) { + setDateTimeRange(QDateTime(min, d->minimum.toTime(), d->spec), + QDateTime(max, d->maximum.toTime(), d->spec)); + } +} + +/*! + Convenience function to set minimum and maximum time with one + function call. + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 5 + + is analogous to: + + \snippet doc/src/snippets/code/src_gui_widgets_qdatetimeedit.cpp 6 + + If either \a min or \a max are not valid, this function does + nothing. + + \sa setMinimumDate(), maximumDate(), setMaximumDate(), + clearMinimumDate(), setMinimumTime(), maximumTime(), + setMaximumTime(), clearMinimumTime(), QTime::isValid() +*/ + +void QDateTimeEdit::setTimeRange(const QTime &min, const QTime &max) +{ + Q_D(QDateTimeEdit); + if (min.isValid() && max.isValid()) { + setDateTimeRange(QDateTime(d->minimum.toDate(), min, d->spec), + QDateTime(d->maximum.toDate(), max, d->spec)); + } +} + +/*! + \property QDateTimeEdit::displayedSections + + \brief the currently displayed fields of the date time edit + + Returns a bit set of the displayed sections for this format. + \a setDisplayFormat(), displayFormat() +*/ + +QDateTimeEdit::Sections QDateTimeEdit::displayedSections() const +{ + Q_D(const QDateTimeEdit); + return d->sections; +} + +/*! + \property QDateTimeEdit::currentSection + + \brief the current section of the spinbox + \a setCurrentSection() +*/ + +QDateTimeEdit::Section QDateTimeEdit::currentSection() const +{ + Q_D(const QDateTimeEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && d->focusOnButton) + return NoSection; +#endif + return d->convertToPublic(d->sectionType(d->currentSectionIndex)); +} + +void QDateTimeEdit::setCurrentSection(Section section) +{ + Q_D(QDateTimeEdit); + if (section == NoSection || !(section & d->sections)) + return; + + d->updateCache(d->value, d->displayText()); + const int size = d->sectionNodes.size(); + int index = d->currentSectionIndex + 1; + for (int i=0; i<2; ++i) { + while (index < size) { + if (d->convertToPublic(d->sectionType(index)) == section) { + d->edit->setCursorPosition(d->sectionPos(index)); + QDTEDEBUG << d->sectionPos(index); + return; + } + ++index; + } + index = 0; + } +} + +/*! + \since 4.3 + + Returns the Section at \a index. + + If the format is 'yyyy/MM/dd', sectionAt(0) returns YearSection, + sectionAt(1) returns MonthSection, and sectionAt(2) returns + YearSection, +*/ + +QDateTimeEdit::Section QDateTimeEdit::sectionAt(int index) const +{ + Q_D(const QDateTimeEdit); + if (index < 0 || index >= d->sectionNodes.size()) + return NoSection; + return d->convertToPublic(d->sectionType(index)); +} + +/*! + \since 4.3 + + \property QDateTimeEdit::sectionCount + + \brief the number of sections displayed. + If the format is 'yyyy/yy/yyyy', sectionCount returns 3 +*/ + +int QDateTimeEdit::sectionCount() const +{ + Q_D(const QDateTimeEdit); + return d->sectionNodes.size(); +} + + +/*! + \since 4.3 + + \property QDateTimeEdit::currentSectionIndex + + \brief the current section index of the spinbox + + If the format is 'yyyy/MM/dd', the displayText is '2001/05/21' and + the cursorPosition is 5 currentSectionIndex returns 1. If the + cursorPosition is 3 currentSectionIndex is 0 etc. + + \a setCurrentSection() + \sa currentSection() +*/ + +int QDateTimeEdit::currentSectionIndex() const +{ + Q_D(const QDateTimeEdit); + return d->currentSectionIndex; +} + +void QDateTimeEdit::setCurrentSectionIndex(int index) +{ + Q_D(QDateTimeEdit); + if (index < 0 || index >= d->sectionNodes.size()) + return; + d->edit->setCursorPosition(d->sectionPos(index)); +} + +/*! + \since 4.4 + + \brief Returns the calendar widget for the editor if calendarPopup is + set to true and (sections() & DateSections_Mask) != 0. + + This function creates and returns a calendar widget if none has been set. +*/ + + +QCalendarWidget *QDateTimeEdit::calendarWidget() const +{ + Q_D(const QDateTimeEdit); + if (!d->calendarPopup || !(d->sections & QDateTimeParser::DateSectionMask)) + return 0; + if (!d->monthCalendar) { + const_cast<QDateTimeEditPrivate*>(d)->initCalendarPopup(); + } + return d->monthCalendar->calendarWidget(); +} + +/*! + \since 4.4 + + Sets the given \a calendarWidget as the widget to be used for the calendar + pop-up. The editor does not automatically take ownership of the calendar widget. + + \sa calendarPopup +*/ +void QDateTimeEdit::setCalendarWidget(QCalendarWidget *calendarWidget) +{ + Q_D(QDateTimeEdit); + if (!calendarWidget) { + qWarning("QDateTimeEdit::setCalendarWidget: Cannot set a null calendar widget"); + return; + } + + if (!d->calendarPopup) { + qWarning("QDateTimeEdit::setCalendarWidget: calendarPopup is set to false"); + return; + } + + if (!(d->display & QDateTimeParser::DateSectionMask)) { + qWarning("QDateTimeEdit::setCalendarWidget: no date sections specified"); + return; + } + d->initCalendarPopup(calendarWidget); +} + + +/*! + \since 4.2 + + Selects \a section. If \a section doesn't exist in the currently + displayed sections this function does nothing. If \a section is + NoSection this function will unselect all text in the editor. + Otherwise this function will move the cursor and the current section + to the selected section. + + \sa currentSection() +*/ + +void QDateTimeEdit::setSelectedSection(Section section) +{ + Q_D(QDateTimeEdit); + if (section == NoSection) { + d->edit->setSelection(d->edit->cursorPosition(), 0); + } else if (section & d->sections) { + if (currentSection() != section) + setCurrentSection(section); + d->setSelected(d->currentSectionIndex); + } +} + + + +/*! + \fn QString QDateTimeEdit::sectionText(Section section) const + + Returns the text from the given \a section. + + \sa currentSection() +*/ + +QString QDateTimeEdit::sectionText(Section section) const +{ + Q_D(const QDateTimeEdit); + if (section == QDateTimeEdit::NoSection || !(section & d->sections)) { + return QString(); + } + + d->updateCache(d->value, d->displayText()); + const int sectionIndex = d->absoluteIndex(section, 0); + return d->sectionText(sectionIndex); +} + +/*! + \property QDateTimeEdit::displayFormat + + \brief the format used to display the time/date of the date time edit + + This format is the same as the one used described in QDateTime::toString() + and QDateTime::fromString() + + Example format strings(assuming that the date is 2nd of July 1969): + + \table + \header \i Format \i Result + \row \i dd.MM.yyyy \i 02.07.1969 + \row \i MMM d yy \i Jul 2 69 + \row \i MMMM d yy \i July 2 69 + \endtable + + Note that if you specify a two digit year, it will be interpreted + to be in the century in which the date time edit was initialized. + The default century is the 21 (2000-2099). + + If you specify an invalid format the format will not be set. + + \sa QDateTime::toString(), displayedSections() +*/ + +QString QDateTimeEdit::displayFormat() const +{ + Q_D(const QDateTimeEdit); + return isRightToLeft() ? d->unreversedFormat : d->displayFormat; +} + +template<typename C> static inline C reverse(const C &l) +{ + C ret; + for (int i=l.size() - 1; i>=0; --i) + ret.append(l.at(i)); + return ret; +} + +void QDateTimeEdit::setDisplayFormat(const QString &format) +{ + Q_D(QDateTimeEdit); + if (d->parseFormat(format)) { + d->unreversedFormat.clear(); + if (isRightToLeft()) { + d->unreversedFormat = format; + d->displayFormat.clear(); + for (int i=d->sectionNodes.size() - 1; i>=0; --i) { + d->displayFormat += d->separators.at(i + 1); + d->displayFormat += d->sectionFormat(i); + } + d->displayFormat += d->separators.at(0); + d->separators = reverse(d->separators); + d->sectionNodes = reverse(d->sectionNodes); + } + + d->formatExplicitlySet = true; + d->sections = d->convertSections(d->display); + d->clearCache(); + + d->currentSectionIndex = qMin(d->currentSectionIndex, d->sectionNodes.size() - 1); + const bool timeShown = (d->sections & TimeSections_Mask); + const bool dateShown = (d->sections & DateSections_Mask); + Q_ASSERT(dateShown || timeShown); + if (timeShown && !dateShown) { + setDateRange(d->value.toDate(), d->value.toDate()); + } else if (dateShown && !timeShown) { + setTimeRange(QDATETIMEEDIT_TIME_MIN, QDATETIMEEDIT_TIME_MAX); + d->value = QDateTime(d->value.toDate(), QTime(), d->spec); + } + d->updateEdit(); + d->_q_editorCursorPositionChanged(-1, 0); + } +} + +/*! + \property QDateTimeEdit::calendarPopup + \brief the current calender pop-up showing mode. + \since 4.2 + + The calendar pop-up will be shown upon clicking the arrow button. + This property is valid only if there is a valid date display format. + + \sa setDisplayFormat() +*/ + +bool QDateTimeEdit::calendarPopup() const +{ + Q_D(const QDateTimeEdit); + return d->calendarPopup; +} + +void QDateTimeEdit::setCalendarPopup(bool enable) +{ + Q_D(QDateTimeEdit); + if (enable == d->calendarPopup) + return; + setAttribute(Qt::WA_MacShowFocusRect, !enable); + d->calendarPopup = enable; +#ifdef QT_KEYPAD_NAVIGATION + if (!enable) + d->focusOnButton = false; +#endif + d->updateEditFieldGeometry(); + update(); +} + +/*! + \property QDateTimeEdit::timeSpec + \brief the current timespec used by the date time edit. + \since 4.4 + + All dates/passed to the date time edit will be converted to this + timespec. +*/ + +Qt::TimeSpec QDateTimeEdit::timeSpec() const +{ + Q_D(const QDateTimeEdit); + return d->spec; +} + +void QDateTimeEdit::setTimeSpec(Qt::TimeSpec spec) +{ + Q_D(QDateTimeEdit); + if (spec != d->spec) { + d->spec = spec; + d->updateTimeSpec(); + } +} + +/*! + \reimp +*/ + +QSize QDateTimeEdit::sizeHint() const +{ + Q_D(const QDateTimeEdit); + if (d->cachedSizeHint.isEmpty()) { + ensurePolished(); + + const QFontMetrics fm(fontMetrics()); + int h = d->edit->sizeHint().height(); + int w = 0; + QString s; + s = d->textFromValue(d->minimum) + QLatin1String(" "); + w = qMax<int>(w, fm.width(s)); + s = d->textFromValue(d->maximum) + QLatin1String(" "); + w = qMax<int>(w, fm.width(s)); + if (d->specialValueText.size()) { + s = d->specialValueText; + w = qMax<int>(w, fm.width(s)); + } + w += 2; // cursor blinking space + + QSize hint(w, h); + +#ifdef Q_WS_MAC + if (d->calendarPopupEnabled()) { + QStyleOptionComboBox opt; + d->cachedSizeHint = style()->sizeFromContents(QStyle::CT_ComboBox, &opt, hint, this); + } else { +#else + { +#endif + QSize extra(35, 6); + QStyleOptionSpinBox opt; + initStyleOption(&opt); + opt.rect.setSize(hint + extra); + extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, this).size(); + // get closer to final result by repeating the calculation + opt.rect.setSize(hint + extra); + extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt, + QStyle::SC_SpinBoxEditField, this).size(); + hint += extra; + + opt.rect = rect(); + d->cachedSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this) + .expandedTo(QApplication::globalStrut()); + } + + d->cachedMinimumSizeHint = d->cachedSizeHint; + // essentially make minimumSizeHint return the same as sizeHint for datetimeedits + } + return d->cachedSizeHint; +} + +/*! + \reimp +*/ + +bool QDateTimeEdit::event(QEvent *event) +{ + Q_D(QDateTimeEdit); + switch (event->type()) { + case QEvent::ApplicationLayoutDirectionChange: { + const bool was = d->formatExplicitlySet; + const QString oldFormat = d->displayFormat; + d->displayFormat.clear(); + setDisplayFormat(oldFormat); + d->formatExplicitlySet = was; + break; } + case QEvent::LocaleChange: + d->updateEdit(); + break; + case QEvent::StyleChange: +#ifdef Q_WS_MAC + case QEvent::MacSizeChange: +#endif + d->setLayoutItemMargins(QStyle::SE_DateTimeEditLayoutItem); + break; + default: + break; + } + return QAbstractSpinBox::event(event); +} + +/*! + \reimp +*/ + +void QDateTimeEdit::clear() +{ + Q_D(QDateTimeEdit); + d->clearSection(d->currentSectionIndex); +} +/*! + \reimp +*/ + +void QDateTimeEdit::keyPressEvent(QKeyEvent *event) +{ + Q_D(QDateTimeEdit); + int oldCurrent = d->currentSectionIndex; + bool select = true; + bool inserted = false; + + switch (event->key()) { +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_NumberSign: //shortcut to popup calendar + if (QApplication::keypadNavigationEnabled() && d->calendarPopupEnabled()) { + d->initCalendarPopup(); + d->positionCalendarPopup(); + d->monthCalendar->show(); + return; + } + break; + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (hasEditFocus()) { + if (d->focusOnButton) { + d->initCalendarPopup(); + d->positionCalendarPopup(); + d->monthCalendar->show(); + d->focusOnButton = false; + return; + } + setEditFocus(false); + selectAll(); + } else { + setEditFocus(true); + + //hide cursor + d->edit->d_func()->setCursorVisible(false); + if (d->edit->d_func()->cursorTimer > 0) + killTimer(d->edit->d_func()->cursorTimer); + d->edit->d_func()->cursorTimer = 0; + + d->setSelected(0); + } + } + return; +#endif + case Qt::Key_Enter: + case Qt::Key_Return: + d->interpret(AlwaysEmit); + d->setSelected(d->currentSectionIndex, true); + event->ignore(); + emit editingFinished(); + return; + default: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus() + && !event->text().isEmpty() && event->text().at(0).isLetterOrNumber()) { + setEditFocus(true); + + //hide cursor + d->edit->d_func()->setCursorVisible(false); + if (d->edit->d_func()->cursorTimer > 0) + killTimer(d->edit->d_func()->cursorTimer); + d->edit->d_func()->cursorTimer = 0; + + d->setSelected(0); + oldCurrent = 0; + } +#endif + if (!d->isSeparatorKey(event)) { + inserted = select = !event->text().isEmpty() && event->text().at(0).isPrint() + && !(event->modifiers() & ~(Qt::ShiftModifier|Qt::KeypadModifier)); + break; + } + case Qt::Key_Left: + case Qt::Key_Right: + if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right) { +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || !hasEditFocus()) { + select = false; + break; + } +#else + if (!(event->modifiers() & Qt::ControlModifier)) { + select = false; + break; + } +#ifdef Q_WS_MAC + else { + select = (event->modifiers() & Qt::ShiftModifier); + break; + } +#endif +#endif // QT_KEYPAD_NAVIGATION + } + // else fall through + case Qt::Key_Backtab: + case Qt::Key_Tab: { + event->accept(); + if (d->specialValue()) { + d->edit->setSelection(d->edit->cursorPosition(), 0); + return; + } + const bool forward = event->key() != Qt::Key_Left && event->key() != Qt::Key_Backtab + && (event->key() != Qt::Key_Tab || !(event->modifiers() & Qt::ShiftModifier)); +#ifdef QT_KEYPAD_NAVIGATION + int newSection = d->nextPrevSection(d->currentSectionIndex, forward); + if (QApplication::keypadNavigationEnabled()) { + if (d->focusOnButton) { + newSection = forward ? 0 : d->sectionNodes.size() - 1; + d->focusOnButton = false; + update(); + } else if (newSection < 0 && select && d->calendarPopupEnabled()) { + setSelectedSection(NoSection); + d->focusOnButton = true; + update(); + return; + } + } + // only allow date/time sections to be selected. + if (newSection & ~(QDateTimeParser::TimeSectionMask | QDateTimeParser::DateSectionMask)) + return; +#endif + //key tab and backtab will be managed thrgout QWidget::event + if (event->key() != Qt::Key_Backtab && event->key() != Qt::Key_Tab) + focusNextPrevChild(forward); + + return; } + } + QAbstractSpinBox::keyPressEvent(event); + if (select && !(event->modifiers() & Qt::ShiftModifier) && !d->edit->hasSelectedText()) { + if (inserted && d->sectionAt(d->edit->cursorPosition()) == QDateTimeParser::NoSectionIndex) { + QString str = d->displayText(); + int pos = d->edit->cursorPosition(); + if (validate(str, pos) == QValidator::Acceptable + && (d->sectionNodes.at(oldCurrent).count != 1 + || d->sectionMaxSize(oldCurrent) == d->sectionSize(oldCurrent) + || d->skipToNextSection(oldCurrent, d->value.toDateTime(), d->sectionText(oldCurrent)))) { + QDTEDEBUG << "Setting currentsection to" + << d->closestSection(d->edit->cursorPosition(), true) << event->key() + << oldCurrent << str; + const int tmp = d->closestSection(d->edit->cursorPosition(), true); + if (tmp >= 0) + d->currentSectionIndex = tmp; + } + } + if (d->currentSectionIndex != oldCurrent) { + d->setSelected(d->currentSectionIndex); + } + } + if (d->specialValue()) { + d->edit->setSelection(d->edit->cursorPosition(), 0); + } +} + +/*! + \reimp +*/ + +#ifndef QT_NO_WHEELEVENT +void QDateTimeEdit::wheelEvent(QWheelEvent *event) +{ + QAbstractSpinBox::wheelEvent(event); +} +#endif + +/*! + \reimp +*/ + +void QDateTimeEdit::focusInEvent(QFocusEvent *event) +{ + Q_D(QDateTimeEdit); + QAbstractSpinBox::focusInEvent(event); + QString *frm = 0; + const int oldPos = d->edit->cursorPosition(); + if (!d->formatExplicitlySet) { + if (d->displayFormat == d->defaultTimeFormat) { + frm = &d->defaultTimeFormat; + } else if (d->displayFormat == d->defaultDateFormat) { + frm = &d->defaultDateFormat; + } else if (d->displayFormat == d->defaultDateTimeFormat) { + frm = &d->defaultDateTimeFormat; + } + + if (frm) { + d->readLocaleSettings(); + if (d->displayFormat != *frm) { + setDisplayFormat(*frm); + d->formatExplicitlySet = false; + d->edit->setCursorPosition(oldPos); + } + } + } + const bool oldHasHadFocus = d->hasHadFocus; + d->hasHadFocus = true; + bool first = true; + switch (event->reason()) { + case Qt::BacktabFocusReason: + first = false; + break; + case Qt::MouseFocusReason: + case Qt::PopupFocusReason: + return; + case Qt::ActiveWindowFocusReason: + if (oldHasHadFocus) + return; + case Qt::ShortcutFocusReason: + case Qt::TabFocusReason: + default: + break; + } + if (isRightToLeft()) + first = !first; + d->updateEdit(); // needed to make it update specialValueText + + d->setSelected(first ? 0 : d->sectionNodes.size() - 1); +} + +/*! + \reimp +*/ + +bool QDateTimeEdit::focusNextPrevChild(bool next) +{ + Q_D(QDateTimeEdit); + const int newSection = d->nextPrevSection(d->currentSectionIndex, next); + switch (d->sectionType(newSection)) { + case QDateTimeParser::NoSection: + case QDateTimeParser::FirstSection: + case QDateTimeParser::LastSection: + return QAbstractSpinBox::focusNextPrevChild(next); + default: + d->edit->deselect(); + d->edit->setCursorPosition(d->sectionPos(newSection)); + QDTEDEBUG << d->sectionPos(newSection); + d->setSelected(newSection, true); + return false; + } +} + +/*! + \reimp +*/ + +void QDateTimeEdit::stepBy(int steps) +{ + Q_D(QDateTimeEdit); +#ifdef QT_KEYPAD_NAVIGATION + // with keypad navigation and not editFocus, left right change the date/time by a fixed amount. + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + // if date based, shift by day. else shift by 15min + if (d->sections & DateSections_Mask) { + setDateTime(dateTime().addDays(steps)); + } else { + int minutes = time().hour()*60 + time().minute(); + int blocks = minutes/15; + blocks += steps; + /* rounding involved */ + if (minutes % 15) { + if (steps < 0) { + blocks += 1; // do one less step; + } + } + + minutes = blocks * 15; + + /* need to take wrapping into account */ + if (!d->wrapping) { + int max_minutes = d->maximum.toTime().hour()*60 + d->maximum.toTime().minute(); + int min_minutes = d->minimum.toTime().hour()*60 + d->minimum.toTime().minute(); + + if (minutes >= max_minutes) { + setTime(maximumTime()); + return; + } else if (minutes <= min_minutes) { + setTime(minimumTime()); + return; + } + } + setTime(QTime(minutes/60, minutes%60)); + } + return; + } +#endif + // don't optimize away steps == 0. This is the only way to select + // the currentSection in Qt 4.1.x + if (d->specialValue() && displayedSections() != AmPmSection) { + for (int i=0; i<d->sectionNodes.size(); ++i) { + if (d->sectionType(i) != QDateTimeParser::AmPmSection) { + d->currentSectionIndex = i; + break; + } + } + } + d->setValue(d->stepBy(d->currentSectionIndex, steps, false), EmitIfChanged); + d->updateCache(d->value, d->displayText()); + + d->setSelected(d->currentSectionIndex); + d->updateTimeSpec(); +} + +/*! + This virtual function is used by the date time edit whenever it + needs to display \a dateTime. + + If you reimplement this, you may also need to reimplement validate(). + + \sa dateTimeFromText(), validate() +*/ +QString QDateTimeEdit::textFromDateTime(const QDateTime &dateTime) const +{ + Q_D(const QDateTimeEdit); + return locale().toString(dateTime, d->displayFormat); +} + + +/*! + Returns an appropriate datetime for the given \a text. + + This virtual function is used by the datetime edit whenever it + needs to interpret text entered by the user as a value. + + \sa textFromDateTime(), validate() +*/ +QDateTime QDateTimeEdit::dateTimeFromText(const QString &text) const +{ + Q_D(const QDateTimeEdit); + QString copy = text; + int pos = d->edit->cursorPosition(); + QValidator::State state = QValidator::Acceptable; + return d->validateAndInterpret(copy, pos, state); +} + +/*! + \reimp +*/ + +QValidator::State QDateTimeEdit::validate(QString &text, int &pos) const +{ + Q_D(const QDateTimeEdit); + QValidator::State state; + d->validateAndInterpret(text, pos, state); + return state; +} + +/*! + \reimp +*/ + + +void QDateTimeEdit::fixup(QString &input) const +{ + Q_D(const QDateTimeEdit); + QValidator::State state; + int copy = d->edit->cursorPosition(); + + d->validateAndInterpret(input, copy, state, true); +} + + +/*! + \reimp +*/ + +QDateTimeEdit::StepEnabled QDateTimeEdit::stepEnabled() const +{ + Q_D(const QDateTimeEdit); + if (d->readOnly) + return StepEnabled(0); + if (d->specialValue()) { + return (d->minimum == d->maximum ? StepEnabled(0) : StepEnabled(StepUpEnabled)); + } + + QAbstractSpinBox::StepEnabled ret = 0; + +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + if (d->wrapping) + return StepEnabled(StepUpEnabled | StepDownEnabled); + // 3 cases. date, time, datetime. each case look + // at just the relavant component. + QVariant max, min, val; + if (!(d->sections & DateSections_Mask)) { + // time only, no date + max = d->maximum.toTime(); + min = d->minimum.toTime(); + val = d->value.toTime(); + } else if (!(d->sections & TimeSections_Mask)) { + // date only, no time + max = d->maximum.toDate(); + min = d->minimum.toDate(); + val = d->value.toDate(); + } else { + // both + max = d->maximum; + min = d->minimum; + val = d->value; + } + if (val != min) + ret |= QAbstractSpinBox::StepDownEnabled; + if (val != max) + ret |= QAbstractSpinBox::StepUpEnabled; + return ret; + } +#endif + switch (d->sectionType(d->currentSectionIndex)) { + case QDateTimeParser::NoSection: + case QDateTimeParser::FirstSection: + case QDateTimeParser::LastSection: return 0; + default: break; + } + if (d->wrapping) + return StepEnabled(StepDownEnabled|StepUpEnabled); + + QVariant v = d->stepBy(d->currentSectionIndex, 1, true); + if (v != d->value) { + ret |= QAbstractSpinBox::StepUpEnabled; + } + v = d->stepBy(d->currentSectionIndex, -1, true); + if (v != d->value) { + ret |= QAbstractSpinBox::StepDownEnabled; + } + + return ret; +} + + +/*! + \reimp +*/ + +void QDateTimeEdit::mousePressEvent(QMouseEvent *event) +{ + Q_D(QDateTimeEdit); + if (!d->calendarPopupEnabled()) { + QAbstractSpinBox::mousePressEvent(event); + return; + } + d->updateHoverControl(event->pos()); + if (d->hoverControl == QStyle::SC_ComboBoxArrow) { + event->accept(); + if (d->readOnly) { + return; + } + d->updateArrow(QStyle::State_Sunken); + d->initCalendarPopup(); + d->positionCalendarPopup(); + //Show the calendar + d->monthCalendar->show(); + } else { + QAbstractSpinBox::mousePressEvent(event); + } +} + +/*! + \class QTimeEdit + \brief The QTimeEdit class provides a widget for editing times based on + the QDateTimeEdit widget. + + \ingroup basicwidgets + \mainclass + + Many of the properties and functions provided by QTimeEdit are implemented in + QDateTimeEdit. The following properties are most relevant to users of this + class: + + \list + \o \l{QDateTimeEdit::time}{time} holds the date displayed by the widget. + \o \l{QDateTimeEdit::minimumTime}{minimumTime} defines the minimum (earliest) time + that can be set by the user. + \o \l{QDateTimeEdit::maximumTime}{maximumTime} defines the maximum (latest) time + that can be set by the user. + \o \l{QDateTimeEdit::displayFormat}{displayFormat} contains a string that is used + to format the time displayed in the widget. + \endlist + + \table 100% + \row \o \inlineimage windowsxp-timeedit.png Screenshot of a Windows XP style time editing widget + \o A time editing widget shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage macintosh-timeedit.png Screenshot of a Macintosh style time editing widget + \o A time editing widget shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage plastique-timeedit.png Screenshot of a Plastique style time editing widget + \o A time editing widget shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QDateEdit, QDateTimeEdit +*/ + +/*! + Constructs an empty time editor with a \a parent. +*/ + + +QTimeEdit::QTimeEdit(QWidget *parent) + : QDateTimeEdit(QDATETIMEEDIT_TIME_MIN, QVariant::Time, parent) +{ +} + +/*! + Constructs an empty time editor with a \a parent. The time is set + to \a time. +*/ + +QTimeEdit::QTimeEdit(const QTime &time, QWidget *parent) + : QDateTimeEdit(time, QVariant::Time, parent) +{ +} + +/*! + \property QTimeEdit::time + \brief the QTime that is shown in the widget + + By default, this property contains a time of 00:00:00 and 0 milliseconds. +*/ + + +/*! + \class QDateEdit + \brief The QDateEdit class provides a widget for editing dates based on + the QDateTimeEdit widget. + + \ingroup basicwidgets + \mainclass + + Many of the properties and functions provided by QDateEdit are implemented in + QDateTimeEdit. The following properties are most relevant to users of this + class: + + \list + \o \l{QDateTimeEdit::date}{date} holds the date displayed by the widget. + \o \l{QDateTimeEdit::minimumDate}{minimumDate} defines the minimum (earliest) + date that can be set by the user. + \o \l{QDateTimeEdit::maximumDate}{maximumDate} defines the maximum (latest) date + that can be set by the user. + \o \l{QDateTimeEdit::displayFormat}{displayFormat} contains a string that is used + to format the date displayed in the widget. + \endlist + + \table 100% + \row \o \inlineimage windowsxp-dateedit.png Screenshot of a Windows XP style date editing widget + \o A date editing widget shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage macintosh-dateedit.png Screenshot of a Macintosh style date editing widget + \o A date editing widget shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage plastique-dateedit.png Screenshot of a Plastique style date editing widget + \o A date editing widget shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QTimeEdit, QDateTimeEdit +*/ + +/*! + Constructs an empty date editor with a \a parent. +*/ + +QDateEdit::QDateEdit(QWidget *parent) + : QDateTimeEdit(QDATETIMEEDIT_DATE_INITIAL, QVariant::Date, parent) +{ +} + +/*! + Constructs an empty date editor with a \a parent. The date is set + to \a date. +*/ + +QDateEdit::QDateEdit(const QDate &date, QWidget *parent) + : QDateTimeEdit(date, QVariant::Date, parent) +{ +} + +/*! + \property QDateEdit::date + \brief the QDate that is shown in the widget + + By default, this property contains a date referring to January 1, 2000. +*/ + + +// --- QDateTimeEditPrivate --- + +/*! + \internal + Constructs a QDateTimeEditPrivate object +*/ + + +QDateTimeEditPrivate::QDateTimeEditPrivate() + : QDateTimeParser(QVariant::DateTime, QDateTimeParser::DateTimeEdit) +{ + hasHadFocus = false; + formatExplicitlySet = false; + cacheGuard = false; + fixday = true; + type = QVariant::DateTime; + sections = 0; + cachedDay = -1; + currentSectionIndex = FirstSectionIndex; + + layoutDirection = QApplication::layoutDirection(); + first.type = FirstSection; + last.type = LastSection; + none.type = NoSection; + first.pos = 0; + last.pos = -1; + none.pos = -1; + sections = 0; + calendarPopup = false; + minimum = QDATETIMEEDIT_COMPAT_DATETIME_MIN; + maximum = QDATETIMEEDIT_DATETIME_MAX; + arrowState = QStyle::State_None; + monthCalendar = 0; + readLocaleSettings(); + +#ifdef QT_KEYPAD_NAVIGATION + focusOnButton = false; +#endif +} + +void QDateTimeEditPrivate::updateTimeSpec() +{ + minimum = minimum.toDateTime().toTimeSpec(spec); + maximum = maximum.toDateTime().toTimeSpec(spec); + value = value.toDateTime().toTimeSpec(spec); +} + +void QDateTimeEditPrivate::updateEdit() +{ + const QString newText = (specialValue() ? specialValueText : textFromValue(value)); + if (newText == displayText()) + return; + int selsize = edit->selectedText().size(); + const bool sb = edit->blockSignals(true); + + edit->setText(newText); + + if (!specialValue() +#ifdef QT_KEYPAD_NAVIGATION + && !(QApplication::keypadNavigationEnabled() && !edit->hasEditFocus()) +#endif + ) { + int cursor = sectionPos(currentSectionIndex); + QDTEDEBUG << "cursor is " << cursor << currentSectionIndex; + cursor = qBound(0, cursor, displayText().size()); + QDTEDEBUG << cursor; + if (selsize > 0) { + edit->setSelection(cursor, selsize); + QDTEDEBUG << cursor << selsize; + } else { + edit->setCursorPosition(cursor); + QDTEDEBUG << cursor; + + } + } + edit->blockSignals(sb); +} + + +/*! + \internal + + Selects the section \a s. If \a forward is false selects backwards. +*/ + +void QDateTimeEditPrivate::setSelected(int sectionIndex, bool forward) +{ + if (specialValue() +#ifdef QT_KEYPAD_NAVIGATION + || (QApplication::keypadNavigationEnabled() && !edit->hasEditFocus()) +#endif + ) { + edit->selectAll(); + } else { + const SectionNode &node = sectionNode(sectionIndex); + if (node.type == NoSection || node.type == LastSection || node.type == FirstSection) + return; + + updateCache(value, displayText()); + const int size = sectionSize(sectionIndex); + if (forward) { + edit->setSelection(sectionPos(node), size); + } else { + edit->setSelection(sectionPos(node) + size, -size); + } + } +} + +/*! + \internal + + Returns the section at index \a index or NoSection if there are no sections there. +*/ + +int QDateTimeEditPrivate::sectionAt(int pos) const +{ + if (pos < separators.first().size()) { + return (pos == 0 ? FirstSectionIndex : NoSectionIndex); + } else if (displayText().size() - pos < separators.last().size() + 1) { + if (separators.last().size() == 0) { + return sectionNodes.count() - 1; + } + return (pos == displayText().size() ? LastSectionIndex : NoSectionIndex); + } + updateCache(value, displayText()); + + for (int i=0; i<sectionNodes.size(); ++i) { + const int tmp = sectionPos(i); + if (pos < tmp + sectionSize(i)) { + return (pos < tmp ? -1 : i); + } + } + return -1; +} + +/*! + \internal + + Returns the closest section of index \a index. Searches forward + for a section if \a forward is true. Otherwise searches backwards. +*/ + +int QDateTimeEditPrivate::closestSection(int pos, bool forward) const +{ + Q_ASSERT(pos >= 0); + if (pos < separators.first().size()) { + return forward ? 0 : FirstSectionIndex; + } else if (displayText().size() - pos < separators.last().size() + 1) { + return forward ? LastSectionIndex : sectionNodes.size() - 1; + } + updateCache(value, displayText()); + for (int i=0; i<sectionNodes.size(); ++i) { + const int tmp = sectionPos(sectionNodes.at(i)); + if (pos < tmp + sectionSize(i)) { + if (pos < tmp && !forward) { + return i-1; + } + return i; + } else if (i == sectionNodes.size() - 1 && pos > tmp) { + return i; + } + } + qWarning("QDateTimeEdit: Internal Error: closestSection returned NoSection"); + return NoSectionIndex; +} + +/*! + \internal + + Returns a copy of the section that is before or after \a current, depending on \a forward. +*/ + +int QDateTimeEditPrivate::nextPrevSection(int current, bool forward) const +{ + Q_Q(const QDateTimeEdit); + if (q->isRightToLeft()) + forward = !forward; + + switch (current) { + case FirstSectionIndex: return forward ? 0 : FirstSectionIndex; + case LastSectionIndex: return (forward ? LastSectionIndex : sectionNodes.size() - 1); + case NoSectionIndex: return FirstSectionIndex; + default: break; + } + Q_ASSERT(current >= 0 && current < sectionNodes.size()); + + current += (forward ? 1 : -1); + if (current >= sectionNodes.size()) { + return LastSectionIndex; + } else if (current < 0) { + return FirstSectionIndex; + } + + return current; +} + +/*! + \internal + + Clears the text of section \a s. +*/ + +void QDateTimeEditPrivate::clearSection(int index) +{ + const QLatin1Char space(' '); + int cursorPos = edit->cursorPosition(); + bool blocked = edit->blockSignals(true); + QString t = edit->text(); + const int pos = sectionPos(index); + if (pos == -1) { + qWarning("QDateTimeEdit: Internal error (%s:%d)", __FILE__, __LINE__); + return; + } + const int size = sectionSize(index); + t.replace(pos, size, QString().fill(space, size)); + edit->setText(t); + edit->setCursorPosition(cursorPos); + QDTEDEBUG << cursorPos; + + edit->blockSignals(blocked); +} + + +/*! + \internal + + updates the cached values +*/ + +void QDateTimeEditPrivate::updateCache(const QVariant &val, const QString &str) const +{ + if (val != cachedValue || str != cachedText || cacheGuard) { + cacheGuard = true; + QString copy = str; + int unused = edit->cursorPosition(); + QValidator::State unusedState; + validateAndInterpret(copy, unused, unusedState); + cacheGuard = false; + } +} + +/*! + \internal + + parses and validates \a input +*/ + +QDateTime QDateTimeEditPrivate::validateAndInterpret(QString &input, int &position, + QValidator::State &state, bool fixup) const +{ + if (input.isEmpty()) { + if (sectionNodes.size() == 1 || !specialValueText.isEmpty()) { + state = QValidator::Intermediate; + } else { + state = QValidator::Invalid; + } + return getZeroVariant().toDateTime(); + } else if (cachedText == input && !fixup) { + state = cachedState; + return cachedValue.toDateTime(); + } else if (!specialValueText.isEmpty()) { + bool changeCase = false; + const int max = qMin(specialValueText.size(), input.size()); + int i; + for (i=0; i<max; ++i) { + const QChar ic = input.at(i); + const QChar sc = specialValueText.at(i); + if (ic != sc) { + if (sc.toLower() == ic.toLower()) { + changeCase = true; + } else { + break; + } + } + } + if (i == max) { + state = specialValueText.size() == input.size() ? QValidator::Acceptable : QValidator::Intermediate; + if (changeCase) { + input = specialValueText.left(max); + } + return minimum.toDateTime(); + } + } + StateNode tmp = parse(input, position, value.toDateTime(), fixup); + input = tmp.input; + state = QValidator::State(int(tmp.state)); + if (state == QValidator::Acceptable) { + if (tmp.conflicts && conflictGuard != tmp.value) { + conflictGuard = tmp.value; + clearCache(); + input = textFromValue(tmp.value); + updateCache(tmp.value, input); + conflictGuard.clear(); + } else { + cachedText = input; + cachedState = state; + cachedValue = tmp.value; + } + } else { + clearCache(); + } + return (tmp.value.isNull() ? getZeroVariant().toDateTime() : tmp.value); +} + + +/*! + \internal + \reimp +*/ + +QString QDateTimeEditPrivate::textFromValue(const QVariant &f) const +{ + Q_Q(const QDateTimeEdit); + return q->textFromDateTime(f.toDateTime()); +} + +/*! + \internal + \reimp + + This function's name is slightly confusing; it is not to be confused + with QAbstractSpinBox::valueFromText(). +*/ + +QVariant QDateTimeEditPrivate::valueFromText(const QString &f) const +{ + Q_Q(const QDateTimeEdit); + return q->dateTimeFromText(f).toTimeSpec(spec); +} + + +/*! + \internal + + Internal function called by QDateTimeEdit::stepBy(). Also takes a + Section for which section to step on and a bool \a test for + whether or not to modify the internal cachedDay variable. This is + necessary because the function is called from the const function + QDateTimeEdit::stepEnabled() as well as QDateTimeEdit::stepBy(). +*/ + +QDateTime QDateTimeEditPrivate::stepBy(int sectionIndex, int steps, bool test) const +{ + Q_Q(const QDateTimeEdit); + QDateTime v = value.toDateTime(); + QString str = displayText(); + int pos = edit->cursorPosition(); + const SectionNode sn = sectionNode(sectionIndex); + + int val; + // to make sure it behaves reasonably when typing something and then stepping in non-tracking mode + if (!test && pendingEmit) { + if (q->validate(str, pos) != QValidator::Acceptable) { + v = value.toDateTime(); + } else { + v = q->dateTimeFromText(str); + } + val = getDigit(v, sectionIndex); + } else { + val = getDigit(v, sectionIndex); + } + + val += steps; + + const int min = absoluteMin(sectionIndex); + const int max = absoluteMax(sectionIndex, value.toDateTime()); + + if (val < min) { + val = (wrapping ? max - (min - val) + 1 : min); + } else if (val > max) { + val = (wrapping ? min + val - max - 1 : max); + } + + + const int oldDay = v.date().day(); + + setDigit(v, sectionIndex, val); + // if this sets year or month it will make + // sure that days are lowered if needed. + + const QDateTime minimumDateTime = minimum.toDateTime(); + const QDateTime maximumDateTime = maximum.toDateTime(); + // changing one section should only modify that section, if possible + if (sn.type != AmPmSection && (v < minimumDateTime || v > maximumDateTime)) { + const int localmin = getDigit(minimumDateTime, sectionIndex); + const int localmax = getDigit(maximumDateTime, sectionIndex); + + if (wrapping) { + // just because we hit the roof in one direction, it + // doesn't mean that we hit the floor in the other + if (steps > 0) { + setDigit(v, sectionIndex, min); + if (!(sn.type & (DaySection|DayOfWeekSection)) && sections & DateSectionMask) { + const int daysInMonth = v.date().daysInMonth(); + if (v.date().day() < oldDay && v.date().day() < daysInMonth) { + const int adds = qMin(oldDay, daysInMonth); + v = v.addDays(adds - v.date().day()); + } + } + + if (v < minimumDateTime) { + setDigit(v, sectionIndex, localmin); + if (v < minimumDateTime) + setDigit(v, sectionIndex, localmin + 1); + } + } else { + setDigit(v, sectionIndex, max); + if (!(sn.type & (DaySection|DayOfWeekSection)) && sections & DateSectionMask) { + const int daysInMonth = v.date().daysInMonth(); + if (v.date().day() < oldDay && v.date().day() < daysInMonth) { + const int adds = qMin(oldDay, daysInMonth); + v = v.addDays(adds - v.date().day()); + } + } + + if (v > maximumDateTime) { + setDigit(v, sectionIndex, localmax); + if (v > maximumDateTime) + setDigit(v, sectionIndex, localmax - 1); + } + } + } else { + setDigit(v, sectionIndex, (steps > 0 ? localmax : localmin)); + } + } + if (!test && oldDay != v.date().day() && !(sn.type & (DaySection|DayOfWeekSection))) { + // this should not happen when called from stepEnabled + cachedDay = qMax<int>(oldDay, cachedDay); + } + + if (v < minimumDateTime) { + if (wrapping) { + QDateTime t = v; + setDigit(t, sectionIndex, steps < 0 ? max : min); + bool mincmp = (t >= minimumDateTime); + bool maxcmp = (t <= maximumDateTime); + if (!mincmp || !maxcmp) { + setDigit(t, sectionIndex, getDigit(steps < 0 + ? maximumDateTime + : minimumDateTime, sectionIndex)); + mincmp = (t >= minimumDateTime); + maxcmp = (t <= maximumDateTime); + } + if (mincmp && maxcmp) { + v = t; + } + } else { + v = value.toDateTime(); + } + } else if (v > maximumDateTime) { + if (wrapping) { + QDateTime t = v; + setDigit(t, sectionIndex, steps > 0 ? min : max); + bool mincmp = (t >= minimumDateTime); + bool maxcmp = (t <= maximumDateTime); + if (!mincmp || !maxcmp) { + setDigit(t, sectionIndex, getDigit(steps > 0 ? + minimumDateTime : + maximumDateTime, sectionIndex)); + mincmp = (t >= minimumDateTime); + maxcmp = (t <= maximumDateTime); + } + if (mincmp && maxcmp) { + v = t; + } + } else { + v = value.toDateTime(); + } + } + + const QDateTime ret = bound(v, value, steps).toDateTime().toTimeSpec(spec); + return ret; +} + +/*! + \internal + \reimp +*/ + +void QDateTimeEditPrivate::emitSignals(EmitPolicy ep, const QVariant &old) +{ + Q_Q(QDateTimeEdit); + if (ep == NeverEmit) { + return; + } + pendingEmit = false; + + const bool dodate = value.toDate().isValid() && (sections & DateSectionMask); + const bool datechanged = (ep == AlwaysEmit || old.toDate() != value.toDate()); + const bool dotime = value.toTime().isValid() && (sections & TimeSectionMask); + const bool timechanged = (ep == AlwaysEmit || old.toTime() != value.toTime()); + + updateCache(value, displayText()); + + syncCalendarWidget(); + if (datechanged || timechanged) + emit q->dateTimeChanged(value.toDateTime()); + if (dodate && datechanged) + emit q->dateChanged(value.toDate()); + if (dotime && timechanged) + emit q->timeChanged(value.toTime()); + +} + +/*! + \internal + \reimp +*/ + +void QDateTimeEditPrivate::_q_editorCursorPositionChanged(int oldpos, int newpos) +{ + if (ignoreCursorPositionChanged || specialValue()) + return; + const QString oldText = displayText(); + updateCache(value, oldText); + + const bool allowChange = !edit->hasSelectedText(); + const bool forward = oldpos <= newpos; + ignoreCursorPositionChanged = true; + int s = sectionAt(newpos); + if (s == NoSectionIndex && forward && newpos > 0) { + s = sectionAt(newpos - 1); + } + + int c = newpos; + + const int selstart = edit->selectionStart(); + const int selSection = sectionAt(selstart); + const int l = selSection != -1 ? sectionSize(selSection) : 0; + + if (s == NoSectionIndex) { + if (l > 0 && selstart == sectionPos(selSection) && edit->selectedText().size() == l) { + s = selSection; + if (allowChange) + setSelected(selSection, true); + c = -1; + } else { + int closest = closestSection(newpos, forward); + c = sectionPos(closest) + (forward ? 0 : qMax<int>(0, sectionSize(closest))); + + if (allowChange) { + edit->setCursorPosition(c); + QDTEDEBUG << c; + } + s = closest; + } + } + + if (allowChange && currentSectionIndex != s) { + interpret(EmitIfChanged); + } + if (c == -1) { + setSelected(s, true); + } else if (!edit->hasSelectedText()) { + if (oldpos < newpos) { + edit->setCursorPosition(displayText().size() - (oldText.size() - c)); + } else { + edit->setCursorPosition(c); + } + } + + QDTEDEBUG << "currentSectionIndex is set to" << sectionName(sectionType(s)) + << oldpos << newpos + << "was" << sectionName(sectionType(currentSectionIndex)); + + currentSectionIndex = s; + Q_ASSERT_X(currentSectionIndex < sectionNodes.size(), + "QDateTimeEditPrivate::_q_editorCursorPositionChanged()", + qPrintable(QString::fromAscii("Internal error (%1 %2)"). + arg(currentSectionIndex). + arg(sectionNodes.size()))); + + ignoreCursorPositionChanged = false; +} + +/*! + \internal + + Try to get the format from the local settings +*/ +void QDateTimeEditPrivate::readLocaleSettings() +{ + const QLocale loc; + defaultTimeFormat = loc.timeFormat(QLocale::ShortFormat); + defaultDateFormat = loc.dateFormat(QLocale::ShortFormat); + defaultDateTimeFormat = loc.dateTimeFormat(QLocale::ShortFormat); +} + +QDateTimeEdit::Section QDateTimeEditPrivate::convertToPublic(QDateTimeParser::Section s) +{ + switch (s & ~Internal) { + case AmPmSection: return QDateTimeEdit::AmPmSection; + case MSecSection: return QDateTimeEdit::MSecSection; + case SecondSection: return QDateTimeEdit::SecondSection; + case MinuteSection: return QDateTimeEdit::MinuteSection; + case DayOfWeekSection: + case DaySection: return QDateTimeEdit::DaySection; + case MonthSection: return QDateTimeEdit::MonthSection; + case YearSection2Digits: + case YearSection: return QDateTimeEdit::YearSection; + case Hour12Section: + case Hour24Section: return QDateTimeEdit::HourSection; + case FirstSection: + case NoSection: + case LastSection: break; + } + return QDateTimeEdit::NoSection; +} + +QDateTimeEdit::Sections QDateTimeEditPrivate::convertSections(QDateTimeParser::Sections s) +{ + QDateTimeEdit::Sections ret = 0; + if (s & QDateTimeParser::MSecSection) + ret |= QDateTimeEdit::MSecSection; + if (s & QDateTimeParser::SecondSection) + ret |= QDateTimeEdit::SecondSection; + if (s & QDateTimeParser::MinuteSection) + ret |= QDateTimeEdit::MinuteSection; + if (s & (QDateTimeParser::Hour24Section|QDateTimeParser::Hour12Section)) + ret |= QDateTimeEdit::HourSection; + if (s & QDateTimeParser::AmPmSection) + ret |= QDateTimeEdit::AmPmSection; + if (s & (QDateTimeParser::DaySection|QDateTimeParser::DayOfWeekSection)) + ret |= QDateTimeEdit::DaySection; + if (s & QDateTimeParser::MonthSection) + ret |= QDateTimeEdit::MonthSection; + if (s & (QDateTimeParser::YearSection|QDateTimeParser::YearSection2Digits)) + ret |= QDateTimeEdit::YearSection; + + return ret; +} + +/*! + \reimp +*/ + +void QDateTimeEdit::paintEvent(QPaintEvent *event) +{ + Q_D(QDateTimeEdit); + if (!d->calendarPopupEnabled()) { + QAbstractSpinBox::paintEvent(event); + return; + } + + QStyleOptionSpinBox opt; + initStyleOption(&opt); + + QStyleOptionComboBox optCombo; + + optCombo.init(this); + optCombo.editable = true; + optCombo.subControls = opt.subControls; + optCombo.activeSubControls = opt.activeSubControls; + optCombo.state = opt.state; + if (d->readOnly) { + optCombo.state &= ~QStyle::State_Enabled; + } + + QPainter p(this); + style()->drawComplexControl(QStyle::CC_ComboBox, &optCombo, &p, this); +} + +QString QDateTimeEditPrivate::getAmPmText(AmPm ap, Case cs) const +{ + if (ap == AmText) { + return (cs == UpperCase ? QDateTimeEdit::tr("AM") : QDateTimeEdit::tr("am")); + } else { + return (cs == UpperCase ? QDateTimeEdit::tr("PM") : QDateTimeEdit::tr("pm")); + } +} + +int QDateTimeEditPrivate::absoluteIndex(QDateTimeEdit::Section s, int index) const +{ + for (int i=0; i<sectionNodes.size(); ++i) { + if (convertToPublic(sectionNodes.at(i).type) == s && index-- == 0) { + return i; + } + } + return NoSectionIndex; +} + +int QDateTimeEditPrivate::absoluteIndex(const SectionNode &s) const +{ + return sectionNodes.indexOf(s); +} + +void QDateTimeEditPrivate::interpret(EmitPolicy ep) +{ + Q_Q(QDateTimeEdit); + QString tmp = displayText(); + int pos = edit->cursorPosition(); + const QValidator::State state = q->validate(tmp, pos); + if (state != QValidator::Acceptable + && correctionMode == QAbstractSpinBox::CorrectToPreviousValue + && (state == QValidator::Invalid || !(fieldInfo(currentSectionIndex) & AllowPartial))) { + setValue(value, ep); + updateTimeSpec(); + } else { + QAbstractSpinBoxPrivate::interpret(ep); + } +} + +void QDateTimeEditPrivate::clearCache() const +{ + QAbstractSpinBoxPrivate::clearCache(); + cachedDay = -1; +} + +/*! + Initialize \a option with the values from this QDataTimeEdit. This method + is useful for subclasses when they need a QStyleOptionSpinBox, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QDateTimeEdit::initStyleOption(QStyleOptionSpinBox *option) const +{ + if (!option) + return; + + Q_D(const QDateTimeEdit); + QAbstractSpinBox::initStyleOption(option); + if (d->calendarPopupEnabled()) { + option->subControls = QStyle::SC_ComboBoxFrame | QStyle::SC_ComboBoxEditField + | QStyle::SC_ComboBoxArrow; + if (d->arrowState == QStyle::State_Sunken) + option->state |= QStyle::State_Sunken; + else + option->state &= ~QStyle::State_Sunken; + } +} + +void QDateTimeEditPrivate::init(const QVariant &var) +{ + Q_Q(QDateTimeEdit); + switch (var.type()) { + case QVariant::Date: + value = QDateTime(var.toDate(), QDATETIMEEDIT_TIME_MIN); + q->setDisplayFormat(defaultDateFormat); + if (sectionNodes.isEmpty()) // ### safeguard for broken locale + q->setDisplayFormat(QLatin1String("dd/MM/yyyy")); + break; + case QVariant::DateTime: + value = var; + q->setDisplayFormat(defaultDateTimeFormat); + if (sectionNodes.isEmpty()) // ### safeguard for broken locale + q->setDisplayFormat(QLatin1String("dd/MM/yyyy hh:mm:ss")); + break; + case QVariant::Time: + value = QDateTime(QDATETIMEEDIT_DATE_INITIAL, var.toTime()); + q->setDisplayFormat(defaultTimeFormat); + if (sectionNodes.isEmpty()) // ### safeguard for broken locale + q->setDisplayFormat(QLatin1String("hh:mm:ss")); + break; + default: + Q_ASSERT_X(0, "QDateTimeEditPrivate::init", "Internal error"); + break; + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + q->setCalendarPopup(true); +#endif + updateTimeSpec(); + setLayoutItemMargins(QStyle::SE_DateTimeEditLayoutItem); +} + +void QDateTimeEditPrivate::_q_resetButton() +{ + updateArrow(QStyle::State_None); +} + +void QDateTimeEditPrivate::updateArrow(QStyle::StateFlag state) +{ + Q_Q(QDateTimeEdit); + + if (arrowState == state) + return; + arrowState = state; + if (arrowState != QStyle::State_None) + buttonState |= Mouse; + else { + buttonState = 0; + hoverControl = QStyle::SC_ComboBoxFrame; + } + q->update(); +} + +/*! + \internal + Returns the hover control at \a pos. + This will update the hoverRect and hoverControl. +*/ +QStyle::SubControl QDateTimeEditPrivate::newHoverControl(const QPoint &pos) +{ + if (!calendarPopupEnabled()) + return QAbstractSpinBoxPrivate::newHoverControl(pos); + + Q_Q(QDateTimeEdit); + + QStyleOptionComboBox optCombo; + optCombo.init(q); + optCombo.editable = true; + optCombo.subControls = QStyle::SC_All; + hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ComboBox, &optCombo, pos, q); + return hoverControl; +} + +void QDateTimeEditPrivate::updateEditFieldGeometry() +{ + if (!calendarPopupEnabled()) { + QAbstractSpinBoxPrivate::updateEditFieldGeometry(); + return; + } + + Q_Q(QDateTimeEdit); + + QStyleOptionComboBox optCombo; + optCombo.init(q); + optCombo.editable = true; + optCombo.subControls = QStyle::SC_ComboBoxEditField; + edit->setGeometry(q->style()->subControlRect(QStyle::CC_ComboBox, &optCombo, + QStyle::SC_ComboBoxEditField, q)); +} + +QVariant QDateTimeEditPrivate::getZeroVariant() const +{ + Q_ASSERT(type == QVariant::DateTime); + return QDateTime(QDATETIMEEDIT_DATE_INITIAL, QTime(), spec); +} + +void QDateTimeEditPrivate::setRange(const QVariant &min, const QVariant &max) +{ + QAbstractSpinBoxPrivate::setRange(min, max); + syncCalendarWidget(); +} + + +bool QDateTimeEditPrivate::isSeparatorKey(const QKeyEvent *ke) const +{ + if (!ke->text().isEmpty() && currentSectionIndex + 1 < sectionNodes.size() && currentSectionIndex >= 0) { + if (fieldInfo(currentSectionIndex) & Numeric) { + if (ke->text().at(0).isNumber()) + return false; + } else if (ke->text().at(0).isLetterOrNumber()) { + return false; + } + return separators.at(currentSectionIndex + 1).contains(ke->text()); + } + return false; +} + +void QDateTimeEditPrivate::initCalendarPopup(QCalendarWidget *cw) +{ + Q_Q(QDateTimeEdit); + if (!monthCalendar) { + monthCalendar = new QCalendarPopup(q, cw); + monthCalendar->setObjectName(QLatin1String("qt_datetimedit_calendar")); + QObject::connect(monthCalendar, SIGNAL(newDateSelected(QDate)), q, SLOT(setDate(QDate))); + QObject::connect(monthCalendar, SIGNAL(hidingCalendar(QDate)), q, SLOT(setDate(QDate))); + QObject::connect(monthCalendar, SIGNAL(activated(QDate)), q, SLOT(setDate(QDate))); + QObject::connect(monthCalendar, SIGNAL(activated(QDate)), monthCalendar, SLOT(close())); + QObject::connect(monthCalendar, SIGNAL(resetButton()), q, SLOT(_q_resetButton())); + } else if (cw) { + monthCalendar->setCalendarWidget(cw); + } + syncCalendarWidget(); +} + +void QDateTimeEditPrivate::positionCalendarPopup() +{ + Q_Q(QDateTimeEdit); + QPoint pos = (q->layoutDirection() == Qt::RightToLeft) ? q->rect().bottomRight() : q->rect().bottomLeft(); + QPoint pos2 = (q->layoutDirection() == Qt::RightToLeft) ? q->rect().topRight() : q->rect().topLeft(); + pos = q->mapToGlobal(pos); + pos2 = q->mapToGlobal(pos2); + QSize size = monthCalendar->sizeHint(); + QRect screen = QApplication::desktop()->availableGeometry(pos); + //handle popup falling "off screen" + if (q->layoutDirection() == Qt::RightToLeft) { + pos.setX(pos.x()-size.width()); + pos2.setX(pos2.x()-size.width()); + if (pos.x() < screen.left()) + pos.setX(qMax(pos.x(), screen.left())); + else if (pos.x()+size.width() > screen.right()) + pos.setX(qMax(pos.x()-size.width(), screen.right()-size.width())); + } else { + if (pos.x()+size.width() > screen.right()) + pos.setX(screen.right()-size.width()); + pos.setX(qMax(pos.x(), screen.left())); + } + if (pos.y() + size.height() > screen.bottom()) + pos.setY(pos2.y() - size.height()); + else if (pos.y() < screen.top()) + pos.setY(screen.top()); + if (pos.y() < screen.top()) + pos.setY(screen.top()); + if (pos.y()+size.height() > screen.bottom()) + pos.setY(screen.bottom()-size.height()); + monthCalendar->move(pos); +} + +bool QDateTimeEditPrivate::calendarPopupEnabled() const +{ + return (calendarPopup && (sections & (DateSectionMask))); +} + +void QDateTimeEditPrivate::syncCalendarWidget() +{ + Q_Q(QDateTimeEdit); + if (monthCalendar) { + monthCalendar->setDateRange(q->minimumDate(), q->maximumDate()); + monthCalendar->setDate(q->date()); + } +} + +QCalendarPopup::QCalendarPopup(QWidget * parent, QCalendarWidget *cw) + : QWidget(parent, Qt::Popup), calendar(0) +{ + setAttribute(Qt::WA_WindowPropagation); + + dateChanged = false; + if (!cw) { + cw = new QCalendarWidget(this); + cw->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + cw->setHorizontalHeaderFormat(QCalendarWidget::SingleLetterDayNames); +#endif + } + setCalendarWidget(cw); +} + +void QCalendarPopup::setCalendarWidget(QCalendarWidget *cw) +{ + Q_ASSERT(cw); + QVBoxLayout *widgetLayout = qobject_cast<QVBoxLayout*>(layout()); + if (!widgetLayout) { + widgetLayout = new QVBoxLayout(this); + widgetLayout->setMargin(0); + widgetLayout->setSpacing(0); + } + delete calendar; + calendar = cw; + widgetLayout->addWidget(calendar); + + connect(calendar, SIGNAL(activated(QDate)), this, SLOT(dateSelected(QDate))); + connect(calendar, SIGNAL(clicked(QDate)), this, SLOT(dateSelected(QDate))); + connect(calendar, SIGNAL(selectionChanged()), this, SLOT(dateSelectionChanged())); + + calendar->setFocus(); +} + + +void QCalendarPopup::setDate(const QDate &date) +{ + oldDate = date; + calendar->setSelectedDate(date); +} + +void QCalendarPopup::setDateRange(const QDate &min, const QDate &max) +{ + calendar->setMinimumDate(min); + calendar->setMaximumDate(max); +} + +void QCalendarPopup::mousePressEvent(QMouseEvent *event) +{ + QDateTimeEdit *dateTime = qobject_cast<QDateTimeEdit *>(parentWidget()); + if (dateTime) { + QStyleOptionComboBox opt; + opt.init(dateTime); + QRect arrowRect = dateTime->style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxArrow, dateTime); + arrowRect.moveTo(dateTime->mapToGlobal(arrowRect .topLeft())); + if (arrowRect.contains(event->globalPos()) || rect().contains(event->pos())) + setAttribute(Qt::WA_NoMouseReplay); + } + QWidget::mousePressEvent(event); +} + +void QCalendarPopup::mouseReleaseEvent(QMouseEvent*) +{ + emit resetButton(); +} + +bool QCalendarPopup::event(QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key()== Qt::Key_Escape) + dateChanged = false; + } + return QWidget::event(event); +} + +void QCalendarPopup::dateSelectionChanged() +{ + dateChanged = true; + emit newDateSelected(calendar->selectedDate()); +} +void QCalendarPopup::dateSelected(const QDate &date) +{ + dateChanged = true; + emit activated(date); + close(); +} + +void QCalendarPopup::hideEvent(QHideEvent *) +{ + emit resetButton(); + if (!dateChanged) + emit hidingCalendar(oldDate); +} + +QT_END_NAMESPACE +#include "moc_qdatetimeedit.cpp" + +#endif // QT_NO_DATETIMEEDIT diff --git a/src/gui/widgets/qdatetimeedit.h b/src/gui/widgets/qdatetimeedit.h new file mode 100644 index 0000000..26c0e66 --- /dev/null +++ b/src/gui/widgets/qdatetimeedit.h @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATETIMEEDIT_H +#define QDATETIMEEDIT_H + +#include <QtCore/qdatetime.h> +#include <QtCore/qvariant.h> +#include <QtGui/qabstractspinbox.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DATETIMEEDIT + +class QDateTimeEditPrivate; +class QStyleOptionSpinBox; +class QCalendarWidget; + +class Q_GUI_EXPORT QDateTimeEdit : public QAbstractSpinBox +{ + Q_OBJECT + + Q_ENUMS(Section) + Q_FLAGS(Sections) + Q_PROPERTY(QDateTime dateTime READ dateTime WRITE setDateTime NOTIFY dateTimeChanged USER true) + Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged) + Q_PROPERTY(QTime time READ time WRITE setTime NOTIFY timeChanged) + Q_PROPERTY(QDateTime maximumDateTime READ maximumDateTime WRITE setMaximumDateTime RESET clearMaximumDateTime) + Q_PROPERTY(QDateTime minimumDateTime READ minimumDateTime WRITE setMinimumDateTime RESET clearMinimumDateTime) + Q_PROPERTY(QDate maximumDate READ maximumDate WRITE setMaximumDate RESET clearMaximumDate) + Q_PROPERTY(QDate minimumDate READ minimumDate WRITE setMinimumDate RESET clearMinimumDate) + Q_PROPERTY(QTime maximumTime READ maximumTime WRITE setMaximumTime RESET clearMaximumTime) + Q_PROPERTY(QTime minimumTime READ minimumTime WRITE setMinimumTime RESET clearMinimumTime) + Q_PROPERTY(Section currentSection READ currentSection WRITE setCurrentSection) + Q_PROPERTY(Sections displayedSections READ displayedSections) + Q_PROPERTY(QString displayFormat READ displayFormat WRITE setDisplayFormat) + Q_PROPERTY(bool calendarPopup READ calendarPopup WRITE setCalendarPopup) + Q_PROPERTY(int currentSectionIndex READ currentSectionIndex WRITE setCurrentSectionIndex) + Q_PROPERTY(int sectionCount READ sectionCount) + Q_PROPERTY(Qt::TimeSpec timeSpec READ timeSpec WRITE setTimeSpec) +public: + enum Section { + NoSection = 0x0000, + AmPmSection = 0x0001, + MSecSection = 0x0002, + SecondSection = 0x0004, + MinuteSection = 0x0008, + HourSection = 0x0010, + DaySection = 0x0100, + MonthSection = 0x0200, + YearSection = 0x0400, + TimeSections_Mask = AmPmSection|MSecSection|SecondSection|MinuteSection|HourSection, + DateSections_Mask = DaySection|MonthSection|YearSection + }; + + Q_DECLARE_FLAGS(Sections, Section) + + explicit QDateTimeEdit(QWidget *parent = 0); + explicit QDateTimeEdit(const QDateTime &dt, QWidget *parent = 0); + explicit QDateTimeEdit(const QDate &d, QWidget *parent = 0); + explicit QDateTimeEdit(const QTime &t, QWidget *parent = 0); + + QDateTime dateTime() const; + QDate date() const; + QTime time() const; + + QDateTime minimumDateTime() const; + void clearMinimumDateTime(); + void setMinimumDateTime(const QDateTime &dt); + + QDateTime maximumDateTime() const; + void clearMaximumDateTime(); + void setMaximumDateTime(const QDateTime &dt); + + void setDateTimeRange(const QDateTime &min, const QDateTime &max); + + QDate minimumDate() const; + void setMinimumDate(const QDate &min); + void clearMinimumDate(); + + QDate maximumDate() const; + void setMaximumDate(const QDate &max); + void clearMaximumDate(); + + void setDateRange(const QDate &min, const QDate &max); + + QTime minimumTime() const; + void setMinimumTime(const QTime &min); + void clearMinimumTime(); + + QTime maximumTime() const; + void setMaximumTime(const QTime &max); + void clearMaximumTime(); + + void setTimeRange(const QTime &min, const QTime &max); + + Sections displayedSections() const; + Section currentSection() const; + Section sectionAt(int index) const; + void setCurrentSection(Section section); + + int currentSectionIndex() const; + void setCurrentSectionIndex(int index); + + QCalendarWidget *calendarWidget() const; + void setCalendarWidget(QCalendarWidget *calendarWidget); + + int sectionCount() const; + + void setSelectedSection(Section section); + + QString sectionText(Section section) const; + + QString displayFormat() const; + void setDisplayFormat(const QString &format); + + bool calendarPopup() const; + void setCalendarPopup(bool enable); + + Qt::TimeSpec timeSpec() const; + void setTimeSpec(Qt::TimeSpec spec); + + QSize sizeHint() const; + + virtual void clear(); + virtual void stepBy(int steps); + + bool event(QEvent *event); +Q_SIGNALS: + void dateTimeChanged(const QDateTime &date); + void timeChanged(const QTime &date); + void dateChanged(const QDate &date); + +public Q_SLOTS: + void setDateTime(const QDateTime &dateTime); + void setDate(const QDate &date); + void setTime(const QTime &time); + +protected: + virtual void keyPressEvent(QKeyEvent *event); +#ifndef QT_NO_WHEELEVENT + virtual void wheelEvent(QWheelEvent *event); +#endif + virtual void focusInEvent(QFocusEvent *event); + virtual bool focusNextPrevChild(bool next); + virtual QValidator::State validate(QString &input, int &pos) const; + virtual void fixup(QString &input) const; + + virtual QDateTime dateTimeFromText(const QString &text) const; + virtual QString textFromDateTime(const QDateTime &dt) const; + virtual StepEnabled stepEnabled() const; + virtual void mousePressEvent(QMouseEvent *event); + virtual void paintEvent(QPaintEvent *event); + void initStyleOption(QStyleOptionSpinBox *option) const; + + QDateTimeEdit(const QVariant &val, QVariant::Type parserType, QWidget *parent = 0); +private: + Q_DECLARE_PRIVATE(QDateTimeEdit) + Q_DISABLE_COPY(QDateTimeEdit) + + Q_PRIVATE_SLOT(d_func(), void _q_resetButton()) +}; + +class Q_GUI_EXPORT QTimeEdit : public QDateTimeEdit +{ + Q_OBJECT + Q_PROPERTY(QTime time READ time WRITE setTime NOTIFY timeChanged USER true) +public: + QTimeEdit(QWidget *parent = 0); + QTimeEdit(const QTime &time, QWidget *parent = 0); +}; + +class Q_GUI_EXPORT QDateEdit : public QDateTimeEdit +{ + Q_OBJECT + Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged USER true) +public: + QDateEdit(QWidget *parent = 0); + QDateEdit(const QDate &date, QWidget *parent = 0); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDateTimeEdit::Sections) + +#endif // QT_NO_DATETIMEEDIT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDATETIMEEDIT_H diff --git a/src/gui/widgets/qdatetimeedit_p.h b/src/gui/widgets/qdatetimeedit_p.h new file mode 100644 index 0000000..5710935 --- /dev/null +++ b/src/gui/widgets/qdatetimeedit_p.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATETIMEEDIT_P_H +#define QDATETIMEEDIT_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 "QtGui/qcombobox.h" +#include "QtGui/qcalendarwidget.h" +#include "QtGui/qspinbox.h" +#include "QtGui/qtoolbutton.h" +#include "QtGui/qmenu.h" +#include "QtGui/qlabel.h" +#include "QtGui/qdatetimeedit.h" +#include "QtGui/private/qabstractspinbox_p.h" +#include "QtCore/private/qdatetime_p.h" + +#include "qdebug.h" + +#ifndef QT_NO_DATETIMEEDIT + +QT_BEGIN_NAMESPACE + +class QCalendarPopup; +class QDateTimeEditPrivate : public QAbstractSpinBoxPrivate, public QDateTimeParser +{ + Q_DECLARE_PUBLIC(QDateTimeEdit) +public: + QDateTimeEditPrivate(); + + void init(const QVariant &var); + void readLocaleSettings(); + + void emitSignals(EmitPolicy ep, const QVariant &old); + QString textFromValue(const QVariant &f) const; + QVariant valueFromText(const QString &f) const; + virtual void _q_editorCursorPositionChanged(int oldpos, int newpos); + virtual void interpret(EmitPolicy ep); + virtual void clearCache() const; + + QDateTime validateAndInterpret(QString &input, int &, QValidator::State &state, + bool fixup = false) const; + void clearSection(int index); + virtual QString displayText() const { return edit->displayText(); } // this is from QDateTimeParser + + int absoluteIndex(QDateTimeEdit::Section s, int index) const; + int absoluteIndex(const SectionNode &s) const; + void updateEdit(); + QDateTime stepBy(int index, int steps, bool test = false) const; + int sectionAt(int pos) const; + int closestSection(int index, bool forward) const; + int nextPrevSection(int index, bool forward) const; + void setSelected(int index, bool forward = false); + + void updateCache(const QVariant &val, const QString &str) const; + + void updateTimeSpec(); + virtual QDateTime getMinimum() const { return minimum.toDateTime(); } + virtual QDateTime getMaximum() const { return maximum.toDateTime(); } + virtual QLocale locale() const { return q_func()->locale(); } + QString valueToText(const QVariant &var) const { return textFromValue(var); } + QString getAmPmText(AmPm ap, Case cs) const; + int cursorPosition() const { return edit ? edit->cursorPosition() : -1; } + + virtual QStyle::SubControl newHoverControl(const QPoint &pos); + virtual void updateEditFieldGeometry(); + virtual QVariant getZeroVariant() const; + virtual void setRange(const QVariant &min, const QVariant &max); + + void _q_resetButton(); + void updateArrow(QStyle::StateFlag state); + bool calendarPopupEnabled() const; + void syncCalendarWidget(); + + bool isSeparatorKey(const QKeyEvent *k) const; + + static QDateTimeEdit::Sections convertSections(QDateTimeParser::Sections s); + static QDateTimeEdit::Section convertToPublic(QDateTimeParser::Section s); + + void initCalendarPopup(QCalendarWidget *cw = 0); + void positionCalendarPopup(); + + QDateTimeEdit::Sections sections; + mutable bool cacheGuard; + + QString defaultDateFormat, defaultTimeFormat, defaultDateTimeFormat, unreversedFormat; + Qt::LayoutDirection layoutDirection; + mutable QVariant conflictGuard; + bool hasHadFocus, formatExplicitlySet, calendarPopup; + QStyle::StateFlag arrowState; + QCalendarPopup *monthCalendar; + +#ifdef QT_KEYPAD_NAVIGATION + bool focusOnButton; +#endif +}; + + +class QCalendarPopup : public QWidget +{ + Q_OBJECT +public: + QCalendarPopup(QWidget *parent = 0, QCalendarWidget *cw = 0); + QDate selectedDate() { return calendar->selectedDate(); } + void setDate(const QDate &date); + void setDateRange(const QDate &min, const QDate &max); + void setFirstDayOfWeek(Qt::DayOfWeek dow) { calendar->setFirstDayOfWeek(dow); } + QCalendarWidget *calendarWidget() const { return calendar; } + void setCalendarWidget(QCalendarWidget *cw); +Q_SIGNALS: + void activated(const QDate &date); + void newDateSelected(const QDate &newDate); + void hidingCalendar(const QDate &oldDate); + void resetButton(); + +private Q_SLOTS: + void dateSelected(const QDate &date); + void dateSelectionChanged(); + +protected: + void hideEvent(QHideEvent *); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *); + bool event(QEvent *e); + +private: + QCalendarWidget *calendar; + QDate oldDate; + bool dateChanged; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_DATETIMEEDIT + +#endif // QDATETIMEEDIT_P_H diff --git a/src/gui/widgets/qdial.cpp b/src/gui/widgets/qdial.cpp new file mode 100644 index 0000000..e19ce6a --- /dev/null +++ b/src/gui/widgets/qdial.cpp @@ -0,0 +1,530 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdial.h" + +#ifndef QT_NO_DIAL + +#include <qapplication.h> +#include <qbitmap.h> +#include <qcolor.h> +#include <qevent.h> +#include <qpainter.h> +#include <qpolygon.h> +#include <qregion.h> +#include <qstyle.h> +#include <qstylepainter.h> +#include <qstyleoption.h> +#include <qslider.h> +#include <private/qabstractslider_p.h> +#include <private/qmath_p.h> +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif + +QT_BEGIN_NAMESPACE + +class QDialPrivate : public QAbstractSliderPrivate +{ + Q_DECLARE_PUBLIC(QDial) +public: + QDialPrivate() + { + wrapping = false; + tracking = true; + doNotEmit = false; + target = qreal(3.7); + } + + qreal target; + uint showNotches : 1; + uint wrapping : 1; + uint doNotEmit : 1; + + int valueFromPoint(const QPoint &) const; + double angle(const QPoint &, const QPoint &) const; + void init(); +}; + +void QDialPrivate::init() +{ + Q_Q(QDial); + showNotches = false; + q->setFocusPolicy(Qt::WheelFocus); +#ifdef QT3_SUPPORT + QObject::connect(q, SIGNAL(sliderPressed()), q, SIGNAL(dialPressed())); + QObject::connect(q, SIGNAL(sliderMoved(int)), q, SIGNAL(dialMoved(int))); + QObject::connect(q, SIGNAL(sliderReleased()), q, SIGNAL(dialReleased())); +#endif +} + +/*! + Initialize \a option with the values from this QDial. This method + is useful for subclasses when they need a QStyleOptionSlider, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QDial::initStyleOption(QStyleOptionSlider *option) const +{ + if (!option) + return; + + Q_D(const QDial); + option->initFrom(this); + option->minimum = d->minimum; + option->maximum = d->maximum; + option->sliderPosition = d->position; + option->sliderValue = d->value; + option->singleStep = d->singleStep; + option->pageStep = d->pageStep; + option->upsideDown = !d->invertedAppearance; + option->notchTarget = d->target; + option->dialWrapping = d->wrapping; + option->subControls = QStyle::SC_All; + option->activeSubControls = QStyle::SC_None; + if (!d->showNotches) { + option->subControls &= ~QStyle::SC_DialTickmarks; + option->tickPosition = QSlider::TicksAbove; + } else { + option->tickPosition = QSlider::NoTicks; + } + option->tickInterval = notchSize(); +} + +int QDialPrivate::valueFromPoint(const QPoint &p) const +{ + Q_Q(const QDial); + double yy = (double)q->height()/2.0 - p.y(); + double xx = (double)p.x() - q->width()/2.0; + double a = (xx || yy) ? atan2(yy, xx) : 0; + + if (a < Q_PI / -2) + a = a + Q_PI * 2; + + int dist = 0; + int minv = minimum, maxv = maximum; + + if (minimum < 0) { + dist = -minimum; + minv = 0; + maxv = maximum + dist; + } + + int r = maxv - minv; + int v; + if (wrapping) + v = (int)(0.5 + minv + r * (Q_PI * 3 / 2 - a) / (2 * Q_PI)); + else + v = (int)(0.5 + minv + r* (Q_PI * 4 / 3 - a) / (Q_PI * 10 / 6)); + + if (dist > 0) + v -= dist; + + return !invertedAppearance ? bound(v) : maximum - bound(v); +} + +/*! + \class QDial + + \brief The QDial class provides a rounded range control (like a speedometer or potentiometer). + + \ingroup basicwidgets + \mainclass + + QDial is used when the user needs to control a value within a + program-definable range, and the range either wraps around + (for example, with angles measured from 0 to 359 degrees) or the + dialog layout needs a square widget. + + Since QDial inherits from QAbstractSlider, the dial behaves in + a similar way to a \l{QSlider}{slider}. When wrapping() is false + (the default setting) there is no real difference between a slider + and a dial. They both share the same signals, slots and member + functions. Which one you use depends on the expectations of + your users and on the type of application. + + The dial initially emits valueChanged() signals continuously while + the slider is being moved; you can make it emit the signal less + often by disabling the \l{QAbstractSlider::tracking} {tracking} + property. The sliderMoved() signal is emitted continuously even + when tracking is disabled. + + The dial also emits sliderPressed() and sliderReleased() signals + when the mouse button is pressed and released. Note that the + dial's value can change without these signals being emitted since + the keyboard and wheel can also be used to change the value. + + Unlike the slider, QDial attempts to draw a "nice" number of + notches rather than one per line step. If possible, the number of + notches drawn is one per line step, but if there aren't enough pixels + to draw every one, QDial will skip notches to try and draw a uniform + set (e.g. by drawing every second or third notch). + + Like the slider, the dial makes the QAbstractSlider functions + setValue(), addLine(), subtractLine(), addPage() and + subtractPage() available as slots. + + The dial's keyboard interface is fairly simple: The + \key{left}/\key{up} and \key{right}/\key{down} arrow keys adjust + the dial's \l {QAbstractSlider::value} {value} by the defined + \l {QAbstractSlider::singleStep} {singleStep}, \key{Page Up} and + \key{Page Down} by the defined \l {QAbstractSlider::pageStep} + {pageStep}, and the \key Home and \key End keys set the value to + the defined \l {QAbstractSlider::minimum} {minimum} and + \l {QAbstractSlider::maximum} {maximum} values. + + If you are using the mouse wheel to adjust the dial, the increment + value is determined by the lesser value of + \l{QApplication::wheelScrollLines()} {wheelScrollLines} multipled + by \l {QAbstractSlider::singleStep} {singleStep}, and + \l {QAbstractSlider::pageStep} {pageStep}. + + \table + \row \o \inlineimage plastique-dial.png Screenshot of a dial in the Plastique widget style + \o \inlineimage windowsxp-dial.png Screenshot of a dial in the Windows XP widget style + \o \inlineimage macintosh-dial.png Screenshot of a dial in the Macintosh widget style + \row \o {3,1} Dials shown in various widget styles (from left to right): + \l{Plastique Style Widget Gallery}{Plastique}, + \l{Windows XP Style Widget Gallery}{Windows XP}, + \l{Macintosh Style Widget Gallery}{Macintosh}. + \endtable + + \sa QScrollBar, QSpinBox, QSlider, {fowler}{GUI Design Handbook: Slider}, {Sliders Example} +*/ + +/*! + Constructs a dial. + + The \a parent argument is sent to the QAbstractSlider constructor. +*/ +QDial::QDial(QWidget *parent) + : QAbstractSlider(*new QDialPrivate, parent) +{ + Q_D(QDial); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QDial::QDial(QWidget *parent, const char *name) + : QAbstractSlider(*new QDialPrivate, parent) +{ + Q_D(QDial); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QDial::QDial(int minValue, int maxValue, int pageStep, int value, + QWidget *parent, const char *name) + : QAbstractSlider(*new QDialPrivate, parent) +{ + Q_D(QDial); + setObjectName(QString::fromAscii(name)); + d->minimum = minValue; + d->maximum = maxValue; + d->pageStep = pageStep; + d->position = d->value = value; + d->init(); +} +#endif +/*! + Destroys the dial. +*/ +QDial::~QDial() +{ +} + +/*! \reimp */ +void QDial::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); +} + +/*! + \reimp +*/ + +void QDial::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionSlider option; + initStyleOption(&option); + p.drawComplexControl(QStyle::CC_Dial, option); +} + +/*! + \reimp +*/ + +void QDial::mousePressEvent(QMouseEvent *e) +{ + Q_D(QDial); + if (d->maximum == d->minimum || + (e->button() != Qt::LeftButton) || + (e->buttons() ^ e->button())) { + e->ignore(); + return; + } + e->accept(); + setSliderPosition(d->valueFromPoint(e->pos())); + // ### This isn't quite right, + // we should be doing a hit test and only setting this if it's + // the actual dial thingie (similar to what QSlider does), but we have no + // subControls for QDial. + setSliderDown(true); +} + + +/*! + \reimp +*/ + +void QDial::mouseReleaseEvent(QMouseEvent * e) +{ + Q_D(QDial); + if (e->buttons() & (~e->button()) || + (e->button() != Qt::LeftButton)) { + e->ignore(); + return; + } + e->accept(); + setValue(d->valueFromPoint(e->pos())); + setSliderDown(false); +} + + +/*! + \reimp +*/ + +void QDial::mouseMoveEvent(QMouseEvent * e) +{ + Q_D(QDial); + if (!(e->buttons() & Qt::LeftButton)) { + e->ignore(); + return; + } + e->accept(); + d->doNotEmit = true; + setSliderPosition(d->valueFromPoint(e->pos())); + d->doNotEmit = false; +} + + +/*! + \reimp +*/ + +void QDial::sliderChange(SliderChange change) +{ + QAbstractSlider::sliderChange(change); +} + +void QDial::setWrapping(bool enable) +{ + Q_D(QDial); + if (d->wrapping == enable) + return; + d->wrapping = enable; + update(); +} + + +/*! + \property QDial::wrapping + \brief whether wrapping is enabled + + If true, wrapping is enabled; otherwise some space is inserted at the bottom + of the dial to separate the ends of the range of valid values. + + If enabled, the arrow can be oriented at any angle on the dial. If disabled, + the arrow will be restricted to the upper part of the dial; if it is rotated + into the space at the bottom of the dial, it will be clamped to the closest + end of the valid range of values. + + By default this property is false. +*/ + +bool QDial::wrapping() const +{ + Q_D(const QDial); + return d->wrapping; +} + + +/*! + \property QDial::notchSize + \brief the current notch size + + The notch size is in range control units, not pixels, and if + possible it is a multiple of singleStep() that results in an + on-screen notch size near notchTarget(). + + By default, this property has a value of 1. + + \sa notchTarget(), singleStep() +*/ + +int QDial::notchSize() const +{ + Q_D(const QDial); + // radius of the arc + int r = qMin(width(), height())/2; + // length of the whole arc + int l = (int)(r * (d->wrapping ? 6 : 5) * Q_PI / 6); + // length of the arc from minValue() to minValue()+pageStep() + if (d->maximum > d->minimum + d->pageStep) + l = (int)(0.5 + l * d->pageStep / (d->maximum - d->minimum)); + // length of a singleStep arc + l = l * d->singleStep / (d->pageStep ? d->pageStep : 1); + if (l < 1) + l = 1; + // how many times singleStep can be draw in d->target pixels + l = (int)(0.5 + d->target / l); + // we want notchSize() to be a non-zero multiple of lineStep() + if (!l) + l = 1; + return d->singleStep * l; +} + +void QDial::setNotchTarget(double target) +{ + Q_D(QDial); + d->target = target; + update(); +} + +/*! + \property QDial::notchTarget + \brief the target number of pixels between notches + + The notch target is the number of pixels QDial attempts to put + between each notch. + + The actual size may differ from the target size. + + The default notch target is 3.7 pixels. +*/ +qreal QDial::notchTarget() const +{ + Q_D(const QDial); + return d->target; +} + + +void QDial::setNotchesVisible(bool visible) +{ + Q_D(QDial); + d->showNotches = visible; + update(); +} + +/*! + \property QDial::notchesVisible + \brief whether the notches are shown + + If the property is true, a series of notches are drawn around the dial + to indicate the range of values available; otherwise no notches are + shown. + + By default, this property is disabled. +*/ +bool QDial::notchesVisible() const +{ + Q_D(const QDial); + return d->showNotches; +} + +/*! + \reimp +*/ + +QSize QDial::minimumSizeHint() const +{ + return QSize(50, 50); +} + +/*! + \reimp +*/ + +QSize QDial::sizeHint() const +{ + return QSize(100, 100).expandedTo(QApplication::globalStrut()); +} + +/*! + \reimp +*/ +bool QDial::event(QEvent *e) +{ + return QAbstractSlider::event(e); +} + +/*! + \fn void QDial::dialPressed(); + + Use QAbstractSlider::sliderPressed() instead. +*/ + +/*! + \fn void QDial::dialMoved(int value); + + Use QAbstractSlider::sliderMoved() instead. +*/ + +/*! + \fn void QDial::dialReleased(); + + Use QAbstractSlider::sliderReleased() instead. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_DIAL diff --git a/src/gui/widgets/qdial.h b/src/gui/widgets/qdial.h new file mode 100644 index 0000000..1aa3d11 --- /dev/null +++ b/src/gui/widgets/qdial.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QDIAL_H +#define QDIAL_H + +#include <QtGui/qabstractslider.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DIAL + +class QDialPrivate; +class QStyleOptionSlider; + +class Q_GUI_EXPORT QDial: public QAbstractSlider +{ + Q_OBJECT + + Q_PROPERTY(bool wrapping READ wrapping WRITE setWrapping) + Q_PROPERTY(int notchSize READ notchSize) + Q_PROPERTY(qreal notchTarget READ notchTarget WRITE setNotchTarget) + Q_PROPERTY(bool notchesVisible READ notchesVisible WRITE setNotchesVisible) +public: + explicit QDial(QWidget *parent = 0); + + ~QDial(); + + bool wrapping() const; + + int notchSize() const; + + void setNotchTarget(double target); + qreal notchTarget() const; + bool notchesVisible() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + +public Q_SLOTS: + void setNotchesVisible(bool visible); + void setWrapping(bool on); + +protected: + bool event(QEvent *e); + void resizeEvent(QResizeEvent *re); + void paintEvent(QPaintEvent *pe); + + void mousePressEvent(QMouseEvent *me); + void mouseReleaseEvent(QMouseEvent *me); + void mouseMoveEvent(QMouseEvent *me); + + void sliderChange(SliderChange change); + void initStyleOption(QStyleOptionSlider *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QDial(int minValue, int maxValue, int pageStep, int value, + QWidget* parent = 0, const char* name = 0); + QT3_SUPPORT_CONSTRUCTOR QDial(QWidget *parent, const char *name); + +Q_SIGNALS: + QT_MOC_COMPAT void dialPressed(); + QT_MOC_COMPAT void dialMoved(int value); + QT_MOC_COMPAT void dialReleased(); +#endif + +private: + Q_DECLARE_PRIVATE(QDial) + Q_DISABLE_COPY(QDial) +}; + +#endif // QT_NO_DIAL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIAL_H diff --git a/src/gui/widgets/qdialogbuttonbox.cpp b/src/gui/widgets/qdialogbuttonbox.cpp new file mode 100644 index 0000000..246da95 --- /dev/null +++ b/src/gui/widgets/qdialogbuttonbox.cpp @@ -0,0 +1,1136 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qhash.h> +#include <QtGui/qpushbutton.h> +#include <QtGui/qstyle.h> +#include <QtGui/qlayout.h> +#include <QtGui/qdialog.h> +#include <QtGui/qapplication.h> +#include <QtGui/private/qwidget_p.h> + +#include "qdialogbuttonbox.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDialogButtonBox + \since 4.2 + \brief The QDialogButtonBox class is a widget that presents buttons in a + layout that is appropriate to the current widget style. + + \ingroup application + \mainclass + + Dialogs and message boxes typically present buttons in a layout that + conforms to the interface guidelines for that platform. Invariably, + different platforms have different layouts for their dialogs. + QDialogButtonBox allows a developer to add buttons to it and will + automatically use the appropriate layout for the user's desktop + environment. + + Most buttons for a dialog follow certain roles. Such roles include: + + \list + \o Accepting or rejecting the dialog. + \o Asking for help. + \o Performing actions on the dialog itself (such as resetting fields or + applying changes). + \endlist + + There can also be alternate ways of dismissing the dialog which may cause + destructive results. + + Most dialogs have buttons that can almost be considered standard (e.g. + \gui OK and \gui Cancel buttons). It is sometimes convenient to create these + buttons in a standard way. + + There are a couple ways of using QDialogButtonBox. One ways is to create + the buttons (or button texts) yourself and add them to the button box, + specifying their role. + + \snippet examples/dialogs/extension/finddialog.cpp 1 + + Alternatively, QDialogButtonBox provides several standard buttons (e.g. OK, Cancel, Save) + that you can use. They exist as flags so you can OR them together in the constructor. + + \snippet examples/dialogs/tabdialog/tabdialog.cpp 2 + + You can mix and match normal buttons and standard buttons. + + Currently the buttons are laid out in the following way if the button box is horizontal: + \table 100% + \row \o \inlineimage buttonbox-gnomelayout-horizontal.png GnomeLayout Horizontal + \o Button box laid out in horizontal GnomeLayout + \row \o \inlineimage buttonbox-kdelayout-horizontal.png KdeLayout Horizontal + \o Button box laid out in horizontal KdeLayout + \row \o \inlineimage buttonbox-maclayout-horizontal.png MacLayout Horizontal + \o Button box laid out in horizontal MacLayout + \row \o \inlineimage buttonbox-winlayout-horizontal.png WinLayout Horizontal + \o Button box laid out in horizontal WinLayout + \endtable + + The buttons are laid out the following way if the button box is vertical: + + \table 100% + \row \o \inlineimage buttonbox-gnomelayout-vertical.png GnomeLayout Vertical + \o Button box laid out in vertical GnomeLayout + \row \o \inlineimage buttonbox-kdelayout-vertical.png KdeLayout Vertical + \o Button box laid out in vertical KdeLayout + \row \o \inlineimage buttonbox-maclayout-vertical.png MacLayout Vertical + \o Button box laid out in vertical MacLayout + \row \o \inlineimage buttonbox-winlayout-vertical.png WinLayout Vertical + \o Button box laid out in vertical WinLayout + \endtable + + Additionally, button boxes that contain only buttons with ActionRole or + HelpRole can be considered modeless and have an alternate look on the mac: + + \table 100% + \row \o \inlineimage buttonbox-mac-modeless-horizontal.png Screenshot of modeless horizontal MacLayout + \o modeless horizontal MacLayout + \row \o \inlineimage buttonbox-mac-modeless-vertical.png Screenshot of modeless vertical MacLayout + \o modeless vertical MacLayout + \endtable + + When a button is clicked in the button box, the clicked() signal is emitted + for the actual button is that is pressed. For convenience, if the button + has an AcceptRole, RejectRole, or HelpRole, the accepted(), rejected(), or + helpRequested() signals are emitted respectively. + + If you want a specific button to be default you need to call + QPushButton::setDefault() on it yourself. However, if there is no default + button set and to preserve which button is the default button across + platforms when using the QPushButton::autoDefault property, the first push + button with the accept role is made the default button when the + QDialogButtonBox is shown, + + \sa QMessageBox, QPushButton, QDialog +*/ + +enum { + AcceptRole = QDialogButtonBox::AcceptRole, + RejectRole = QDialogButtonBox::RejectRole, + DestructiveRole = QDialogButtonBox::DestructiveRole, + ActionRole = QDialogButtonBox::ActionRole, + HelpRole = QDialogButtonBox::HelpRole, + YesRole = QDialogButtonBox::YesRole, + NoRole = QDialogButtonBox::NoRole, + ApplyRole = QDialogButtonBox::ApplyRole, + ResetRole = QDialogButtonBox::ResetRole, + + AlternateRole = 0x10000000, + Stretch = 0x20000000, + EOL = 0x40000000, + Reverse = 0x80000000 +}; + +static QDialogButtonBox::ButtonRole roleFor(QDialogButtonBox::StandardButton button) +{ + switch (button) { + case QDialogButtonBox::Ok: + case QDialogButtonBox::Save: + case QDialogButtonBox::Open: + case QDialogButtonBox::SaveAll: + case QDialogButtonBox::Retry: + case QDialogButtonBox::Ignore: + return QDialogButtonBox::AcceptRole; + + case QDialogButtonBox::Cancel: + case QDialogButtonBox::Close: + case QDialogButtonBox::Abort: + return QDialogButtonBox::RejectRole; + + case QDialogButtonBox::Discard: + return QDialogButtonBox::DestructiveRole; + + case QDialogButtonBox::Help: + return QDialogButtonBox::HelpRole; + + case QDialogButtonBox::Apply: + return QDialogButtonBox::ApplyRole; + + case QDialogButtonBox::Yes: + case QDialogButtonBox::YesToAll: + return QDialogButtonBox::YesRole; + + case QDialogButtonBox::No: + case QDialogButtonBox::NoToAll: + return QDialogButtonBox::NoRole; + + case QDialogButtonBox::RestoreDefaults: + case QDialogButtonBox::Reset: + return QDialogButtonBox::ResetRole; + + case QDialogButtonBox::NoButton: // NoButton means zero buttons, not "No" button + ; + } + + return QDialogButtonBox::InvalidRole; +} + +static const int layouts[2][5][14] = +{ + // Qt::Horizontal + { + // WinLayout + { ResetRole, Stretch, YesRole, AcceptRole, AlternateRole, DestructiveRole, NoRole, ActionRole, RejectRole, ApplyRole, + HelpRole, EOL, EOL, EOL }, + + // MacLayout + { HelpRole, ResetRole, ApplyRole, ActionRole, Stretch, DestructiveRole | Reverse, + AlternateRole | Reverse, RejectRole | Reverse, AcceptRole | Reverse, NoRole | Reverse, YesRole | Reverse, EOL, EOL }, + + // KdeLayout + { HelpRole, ResetRole, Stretch, YesRole, NoRole, ActionRole, AcceptRole, AlternateRole, + ApplyRole, DestructiveRole, RejectRole, EOL }, + + // GnomeLayout + { HelpRole, ResetRole, Stretch, ActionRole, ApplyRole | Reverse, DestructiveRole | Reverse, + AlternateRole | Reverse, RejectRole | Reverse, AcceptRole | Reverse, NoRole | Reverse, YesRole | Reverse, EOL }, + + // Mac modeless + { ResetRole, ApplyRole, ActionRole, Stretch, HelpRole, EOL, EOL, EOL, EOL, EOL, EOL, EOL, EOL, EOL } + }, + + // Qt::Vertical + { + // WinLayout + { ActionRole, YesRole, AcceptRole, AlternateRole, DestructiveRole, NoRole, RejectRole, ApplyRole, ResetRole, + HelpRole, Stretch, EOL, EOL, EOL }, + + // MacLayout + { YesRole, NoRole, AcceptRole, RejectRole, AlternateRole, DestructiveRole, Stretch, ActionRole, ApplyRole, + ResetRole, HelpRole, EOL, EOL }, + + // KdeLayout + { AcceptRole, AlternateRole, ApplyRole, ActionRole, YesRole, NoRole, Stretch, ResetRole, + DestructiveRole, RejectRole, HelpRole, EOL }, + + // GnomeLayout + { YesRole, NoRole, AcceptRole, RejectRole, AlternateRole, DestructiveRole, ApplyRole, ActionRole, Stretch, + ResetRole, HelpRole, EOL, EOL, EOL }, + + // Mac modeless + { ActionRole, ApplyRole, ResetRole, Stretch, HelpRole, EOL, EOL, EOL, EOL, EOL, EOL, EOL, EOL, EOL } + } +}; + +class QDialogButtonBoxPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QDialogButtonBox) + +public: + QDialogButtonBoxPrivate(Qt::Orientation orient); + + QList<QAbstractButton *> buttonLists[QDialogButtonBox::NRoles]; + QHash<QPushButton *, QDialogButtonBox::StandardButton> standardButtonHash; + + Qt::Orientation orientation; + QDialogButtonBox::ButtonLayout layoutPolicy; + QBoxLayout *buttonLayout; + bool internalRemove; + bool center; + + void createStandardButtons(QDialogButtonBox::StandardButtons buttons); + + void layoutButtons(); + void initLayout(); + void resetLayout(); + QPushButton *createButton(QDialogButtonBox::StandardButton button, bool doLayout = true); + void addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role, bool doLayout = true); + void _q_handleButtonDestroyed(); + void _q_handleButtonClicked(); + void addButtonsToLayout(const QList<QAbstractButton *> &buttonList, bool reverse); + void retranslateStrings(); + const char *standardButtonText(QDialogButtonBox::StandardButton sbutton) const; +}; + +QDialogButtonBoxPrivate::QDialogButtonBoxPrivate(Qt::Orientation orient) + : orientation(orient), buttonLayout(0), internalRemove(false), center(false) +{ +} + +void QDialogButtonBoxPrivate::initLayout() +{ + Q_Q(QDialogButtonBox); + layoutPolicy = QDialogButtonBox::ButtonLayout(q->style()->styleHint(QStyle::SH_DialogButtonLayout, 0, q)); + bool createNewLayout = buttonLayout == 0 + || (orientation == Qt::Horizontal && qobject_cast<QVBoxLayout *>(buttonLayout) != 0) + || (orientation == Qt::Vertical && qobject_cast<QHBoxLayout *>(buttonLayout) != 0); + if (createNewLayout) { + delete buttonLayout; + if (orientation == Qt::Horizontal) + buttonLayout = new QHBoxLayout(q); + else + buttonLayout = new QVBoxLayout(q); + } + + int left, top, right, bottom; + setLayoutItemMargins(QStyle::SE_PushButtonLayoutItem); + getLayoutItemMargins(&left, &top, &right, &bottom); + buttonLayout->setContentsMargins(-left, -top, -right, -bottom); + + if (!q->testAttribute(Qt::WA_WState_OwnSizePolicy)) { + QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::ButtonBox); + if (orientation == Qt::Vertical) + sp.transpose(); + q->setSizePolicy(sp); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + } + + // ### move to a real init() function + q->setFocusPolicy(Qt::TabFocus); +} + +void QDialogButtonBoxPrivate::resetLayout() +{ + //delete buttonLayout; + initLayout(); + layoutButtons(); +} + +void QDialogButtonBoxPrivate::addButtonsToLayout(const QList<QAbstractButton *> &buttonList, + bool reverse) +{ + int start = reverse ? buttonList.count() - 1 : 0; + int end = reverse ? -1 : buttonList.count(); + int step = reverse ? -1 : 1; + + for (int i = start; i != end; i += step) { + QAbstractButton *button = buttonList.at(i); + buttonLayout->addWidget(button); + button->show(); + } +} + +void QDialogButtonBoxPrivate::layoutButtons() +{ + Q_Q(QDialogButtonBox); + const int MacGap = 36 - 8; // 8 is the default gap between a widget and a spacer item + + for (int i = buttonLayout->count() - 1; i >= 0; --i) { + QLayoutItem *item = buttonLayout->takeAt(i); + if (QWidget *widget = item->widget()) + widget->hide(); + delete item; + } + + int tmpPolicy = layoutPolicy; + + static const int M = 5; + static const int ModalRoles[M] = { AcceptRole, RejectRole, DestructiveRole, YesRole, NoRole }; + if (tmpPolicy == QDialogButtonBox::MacLayout) { + bool hasModalButton = false; + for (int i = 0; i < M; ++i) { + if (!buttonLists[ModalRoles[i]].isEmpty()) { + hasModalButton = true; + break; + } + } + if (!hasModalButton) + tmpPolicy = 4; // Mac modeless + } + + const int *currentLayout = layouts[orientation == Qt::Vertical][tmpPolicy]; + + if (center) + buttonLayout->addStretch(); + + QList<QAbstractButton *> acceptRoleList = buttonLists[AcceptRole]; + + while (*currentLayout != EOL) { + int role = (*currentLayout & ~Reverse); + bool reverse = (*currentLayout & Reverse); + + switch (role) { + case Stretch: + if (!center) + buttonLayout->addStretch(); + break; + case AcceptRole: { + if (acceptRoleList.isEmpty()) + break; + // Only the first one + QAbstractButton *button = acceptRoleList.first(); + buttonLayout->addWidget(button); + button->show(); + } + break; + case AlternateRole: + { + if (acceptRoleList.size() < 2) + break; + QList<QAbstractButton *> list = acceptRoleList; + list.removeFirst(); + addButtonsToLayout(list, reverse); + } + break; + case DestructiveRole: + { + const QList<QAbstractButton *> &list = buttonLists[role]; + + /* + Mac: Insert a gap on the left of the destructive + buttons to ensure that they don't get too close to + the help and action buttons (but only if there are + some buttons to the left of the destructive buttons + (and the stretch, whence buttonLayout->count() > 1 + and not 0)). + */ + if (tmpPolicy == QDialogButtonBox::MacLayout + && !list.isEmpty() && buttonLayout->count() > 1) + buttonLayout->addSpacing(MacGap); + + addButtonsToLayout(list, reverse); + + /* + Insert a gap between the destructive buttons and the + accept and reject buttons. + */ + if (tmpPolicy == QDialogButtonBox::MacLayout && !list.isEmpty()) + buttonLayout->addSpacing(MacGap); + } + break; + case RejectRole: + case ActionRole: + case HelpRole: + case YesRole: + case NoRole: + case ApplyRole: + case ResetRole: + addButtonsToLayout(buttonLists[role], reverse); + } + ++currentLayout; + } + + QWidget *lastWidget = 0; + q->setFocusProxy(0); + for (int i = 0; i < buttonLayout->count(); ++i) { + QLayoutItem *item = buttonLayout->itemAt(i); + if (QWidget *widget = item->widget()) { + if (lastWidget) + QWidget::setTabOrder(lastWidget, widget); + else + q->setFocusProxy(widget); + lastWidget = widget; + } + } + + if (center) + buttonLayout->addStretch(); +} + +QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardButton sbutton, + bool doLayout) +{ + Q_Q(QDialogButtonBox); + const char *buttonText = 0; + int icon = 0; + + switch (sbutton) { + case QDialogButtonBox::Ok: + icon = QStyle::SP_DialogOkButton; + break; + case QDialogButtonBox::Save: + icon = QStyle::SP_DialogSaveButton; + break; + case QDialogButtonBox::Open: + icon = QStyle::SP_DialogOpenButton; + break; + case QDialogButtonBox::Cancel: + icon = QStyle::SP_DialogCancelButton; + break; + case QDialogButtonBox::Close: + icon = QStyle::SP_DialogCloseButton; + break; + case QDialogButtonBox::Apply: + icon = QStyle::SP_DialogApplyButton; + break; + case QDialogButtonBox::Reset: + icon = QStyle::SP_DialogResetButton; + break; + case QDialogButtonBox::Help: + icon = QStyle::SP_DialogHelpButton; + break; + case QDialogButtonBox::Discard: + icon = QStyle::SP_DialogDiscardButton; + break; + case QDialogButtonBox::Yes: + icon = QStyle::SP_DialogYesButton; + break; + case QDialogButtonBox::No: + icon = QStyle::SP_DialogNoButton; + break; + case QDialogButtonBox::YesToAll: + case QDialogButtonBox::NoToAll: + case QDialogButtonBox::SaveAll: + case QDialogButtonBox::Abort: + case QDialogButtonBox::Retry: + case QDialogButtonBox::Ignore: + case QDialogButtonBox::RestoreDefaults: + break; + case QDialogButtonBox::NoButton: + return 0; + ; + } + buttonText = standardButtonText(sbutton); + + QPushButton *button = new QPushButton(QDialogButtonBox::tr(buttonText), q); + QStyle *style = q->style(); + if (style->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons, 0, q) && icon != 0) + button->setIcon(style->standardIcon(QStyle::StandardPixmap(icon), 0, q)); + if (style != QApplication::style()) // Propagate style + button->setStyle(style); + standardButtonHash.insert(button, sbutton); + if (roleFor(sbutton) != QDialogButtonBox::InvalidRole) { + addButton(button, roleFor(sbutton), doLayout); + } else { + qWarning("QDialogButtonBox::createButton: Invalid ButtonRole, button not added"); + } + return button; +} + +void QDialogButtonBoxPrivate::addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role, + bool doLayout) +{ + Q_Q(QDialogButtonBox); + QObject::connect(button, SIGNAL(clicked()), q, SLOT(_q_handleButtonClicked())); + QObject::connect(button, SIGNAL(destroyed()), q, SLOT(_q_handleButtonDestroyed())); + buttonLists[role].append(button); + if (doLayout) + layoutButtons(); +} + +void QDialogButtonBoxPrivate::createStandardButtons(QDialogButtonBox::StandardButtons buttons) +{ + uint i = QDialogButtonBox::FirstButton; + while (i <= QDialogButtonBox::LastButton) { + if (i & buttons) { + createButton(QDialogButtonBox::StandardButton(i), false); + } + i = i << 1; + } + layoutButtons(); +} + +const char *QDialogButtonBoxPrivate::standardButtonText(QDialogButtonBox::StandardButton sbutton) const +{ + const char *buttonText = 0; + bool gnomeLayout = (layoutPolicy == QDialogButtonBox::GnomeLayout); + switch (sbutton) { + case QDialogButtonBox::Ok: + buttonText = gnomeLayout ? QT_TRANSLATE_NOOP("QDialogButtonBox", "&OK") : QT_TRANSLATE_NOOP("QDialogButtonBox", "OK"); + break; + case QDialogButtonBox::Save: + buttonText = gnomeLayout ? QT_TRANSLATE_NOOP("QDialogButtonBox", "&Save") : QT_TRANSLATE_NOOP("QDialogButtonBox", "Save"); + break; + case QDialogButtonBox::Open: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Open"); + break; + case QDialogButtonBox::Cancel: + buttonText = gnomeLayout ? QT_TRANSLATE_NOOP("QDialogButtonBox", "&Cancel") : QT_TRANSLATE_NOOP("QDialogButtonBox", "Cancel"); + break; + case QDialogButtonBox::Close: + buttonText = gnomeLayout ? QT_TRANSLATE_NOOP("QDialogButtonBox", "&Close") : QT_TRANSLATE_NOOP("QDialogButtonBox", "Close"); + break; + case QDialogButtonBox::Apply: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Apply"); + break; + case QDialogButtonBox::Reset: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Reset"); + break; + case QDialogButtonBox::Help: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Help"); + break; + case QDialogButtonBox::Discard: + if (layoutPolicy == QDialogButtonBox::MacLayout) + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Don't Save"); + else if (layoutPolicy == QDialogButtonBox::GnomeLayout) + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Close without Saving"); + else + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Discard"); + break; + case QDialogButtonBox::Yes: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "&Yes"); + break; + case QDialogButtonBox::YesToAll: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Yes to &All"); + break; + case QDialogButtonBox::No: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "&No"); + break; + case QDialogButtonBox::NoToAll: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "N&o to All"); + break; + case QDialogButtonBox::SaveAll: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Save All"); + break; + case QDialogButtonBox::Abort: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Abort"); + break; + case QDialogButtonBox::Retry: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Retry"); + break; + case QDialogButtonBox::Ignore: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Ignore"); + break; + case QDialogButtonBox::RestoreDefaults: + buttonText = QT_TRANSLATE_NOOP("QDialogButtonBox", "Restore Defaults"); + break; + case QDialogButtonBox::NoButton: + ; + } // switch + return buttonText; +} + +void QDialogButtonBoxPrivate::retranslateStrings() +{ + const char *buttonText = 0; + QHash<QPushButton *, QDialogButtonBox::StandardButton>::iterator it = standardButtonHash.begin(); + while (it != standardButtonHash.end()) { + buttonText = standardButtonText(it.value()); + if (buttonText) { + QPushButton *button = it.key(); + button->setText(QDialogButtonBox::tr(buttonText)); + } + ++it; + } +} + +/*! + Constructs an empty, horizontal button box with the given \a parent. + + \sa orientation, addButton() +*/ +QDialogButtonBox::QDialogButtonBox(QWidget *parent) + : QWidget(*new QDialogButtonBoxPrivate(Qt::Horizontal), parent, 0) +{ + d_func()->initLayout(); +} + +/*! + Constructs an empty button box with the given \a orientation and \a parent. + + \sa orientation, addButton() +*/ +QDialogButtonBox::QDialogButtonBox(Qt::Orientation orientation, QWidget *parent) + : QWidget(*new QDialogButtonBoxPrivate(orientation), parent, 0) +{ + d_func()->initLayout(); +} + +/*! + Constructs a button box with the given \a orientation and \a parent, containing + the standard buttons specified by \a buttons. + + \sa orientation, addButton() +*/ +QDialogButtonBox::QDialogButtonBox(StandardButtons buttons, Qt::Orientation orientation, + QWidget *parent) + : QWidget(*new QDialogButtonBoxPrivate(orientation), parent, 0) +{ + d_func()->initLayout(); + d_func()->createStandardButtons(buttons); +} + +/*! + Destroys the button box. +*/ +QDialogButtonBox::~QDialogButtonBox() +{ +} + +/*! + \enum QDialogButtonBox::ButtonRole + \enum QMessageBox::ButtonRole + + This enum describes the roles that can be used to describe buttons in + the button box. Combinations of these roles are as flags used to + describe different aspects of their behavior. + + \value InvalidRole The button is invalid. + \value AcceptRole Clicking the button causes the dialog to be accepted + (e.g. OK). + \value RejectRole Clicking the button causes the dialog to be rejected + (e.g. Cancel). + \value DestructiveRole Clicking the button causes a destructive change + (e.g. for Discarding Changes) and closes the dialog. + \value ActionRole Clicking the button causes changes to the elements within + the dialog. + \value HelpRole The button can be clicked to request help. + \value YesRole The button is a "Yes"-like button. + \value NoRole The button is a "No"-like button. + \value ApplyRole The button applies current changes. + \value ResetRole The button resets the dialog's fields to default values. + + \omitvalue NRoles + + \sa StandardButton +*/ + +/*! + \enum QDialogButtonBox::StandardButton + + 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 + + \sa ButtonRole, standardButtons +*/ + +/*! + \enum QDialogButtonBox::ButtonLayout + + This enum describes the layout policy to be used when arranging the buttons + contained in the button box. + + \value WinLayout Use a policy appropriate for applications on Windows. + \value MacLayout Use a policy appropriate for applications on Mac OS X. + \value KdeLayout Use a policy appropriate for applications on KDE. + \value GnomeLayout Use a policy appropriate for applications on GNOME. + + The button layout is specified by the \l{style()}{current style}. +*/ + +/*! + \fn void QDialogButtonBox::clicked(QAbstractButton *button) + + This signal is emitted when a button inside the button box is clicked. The + specific button that was pressed is specified by \a button. + + \sa accepted(), rejected(), helpRequested() +*/ + +/*! + \fn void QDialogButtonBox::accepted() + + This signal is emitted when a button inside the button box is clicked, as long + as it was defined with the \l AcceptRole or \l YesRole. + + \sa rejected(), clicked() helpRequested() +*/ + +/*! + \fn void QDialogButtonBox::rejected() + + This signal is emitted when a button inside the button box is clicked, as long + as it was defined with the \l RejectRole or \l NoRole. + + \sa accepted() helpRequested() clicked() +*/ + +/*! + \fn void QDialogButtonBox::helpRequested() + + This signal is emitted when a button inside the button box is clicked, as long + as it was defined with the \l HelpRole. + + \sa accepted() rejected() clicked() +*/ + +/*! + \property QDialogButtonBox::orientation + \brief the orientation of the button box + + By default, the orientation is horizontal (i.e. the buttons are laid out + side by side). The possible orientations are Qt::Horizontal and + Qt::Vertical. +*/ +Qt::Orientation QDialogButtonBox::orientation() const +{ + return d_func()->orientation; +} + +void QDialogButtonBox::setOrientation(Qt::Orientation orientation) +{ + Q_D(QDialogButtonBox); + if (orientation == d->orientation) + return; + + d->orientation = orientation; + d->resetLayout(); +} + +/*! + Clears the button box, deleting all buttons within it. + + \sa removeButton(), addButton() +*/ +void QDialogButtonBox::clear() +{ + Q_D(QDialogButtonBox); + // Remove the created standard buttons, they should be in the other lists, which will + // do the deletion + d->standardButtonHash.clear(); + for (int i = 0; i < NRoles; ++i) { + QList<QAbstractButton *> &list = d->buttonLists[i]; + while (list.count()) { + QAbstractButton *button = list.takeAt(0); + QObject::disconnect(button, SIGNAL(destroyed()), this, SLOT(_q_handleButtonDestroyed())); + delete button; + } + } +} + +/*! + Returns a list of all the buttons that have been added to the button box. + + \sa buttonRole(), addButton(), removeButton() +*/ +QList<QAbstractButton *> QDialogButtonBox::buttons() const +{ + Q_D(const QDialogButtonBox); + QList<QAbstractButton *> finalList; + for (int i = 0; i < NRoles; ++i) { + const QList<QAbstractButton *> &list = d->buttonLists[i]; + for (int j = 0; j < list.count(); ++j) + finalList.append(list.at(j)); + } + return finalList; +} + +/*! + 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 button box. + + \sa buttons(), addButton() +*/ +QDialogButtonBox::ButtonRole QDialogButtonBox::buttonRole(QAbstractButton *button) const +{ + Q_D(const QDialogButtonBox); + for (int i = 0; i < NRoles; ++i) { + const QList<QAbstractButton *> &list = d->buttonLists[i]; + for (int j = 0; j < list.count(); ++j) { + if (list.at(j) == button) + return ButtonRole(i); + } + } + return InvalidRole; +} + +/*! + Removes \a button from the button box without deleting it and sets its parent to zero. + + \sa clear(), buttons(), addButton() +*/ +void QDialogButtonBox::removeButton(QAbstractButton *button) +{ + Q_D(QDialogButtonBox); + + if (!button) + return; + + // Remove it from the standard button hash first and then from the roles + if (QPushButton *pushButton = qobject_cast<QPushButton *>(button)) + d->standardButtonHash.remove(pushButton); + for (int i = 0; i < NRoles; ++i) { + QList<QAbstractButton *> &list = d->buttonLists[i]; + for (int j = 0; j < list.count(); ++j) { + if (list.at(j) == button) { + list.takeAt(j); + if (!d->internalRemove) { + disconnect(button, SIGNAL(clicked()), this, SLOT(_q_handleButtonClicked())); + disconnect(button, SIGNAL(destroyed()), this, SLOT(_q_handleButtonDestroyed())); + } + break; + } + } + } + if (!d->internalRemove) + button->setParent(0); +} + +/*! + Adds the given \a button to the button box with the specified \a role. + If the role is invalid, the button is not added. + + If the button has already been added, it is removed and added again with the + new role. + + \sa removeButton(), clear() +*/ +void QDialogButtonBox::addButton(QAbstractButton *button, ButtonRole role) +{ + Q_D(QDialogButtonBox); + if (role <= InvalidRole || role >= NRoles) { + qWarning("QDialogButtonBox::addButton: Invalid ButtonRole, button not added"); + return; + } + removeButton(button); + button->setParent(this); + d->addButton(button, role); +} + +/*! + Creates a push button with the given \a text, adds it to the button box for the + specified \a role, and returns the corresponding push button. If \a role is + invalid, no button is created, and zero is returned. + + \sa removeButton(), clear() +*/ +QPushButton *QDialogButtonBox::addButton(const QString &text, ButtonRole role) +{ + Q_D(QDialogButtonBox); + if (role <= InvalidRole || role >= NRoles) { + qWarning("QDialogButtonBox::addButton: Invalid ButtonRole, button not added"); + return 0; + } + QPushButton *button = new QPushButton(text, this); + d->addButton(button, role); + return button; +} + +/*! + Adds a standard \a button to the button box if it is valid to do so, and returns + a push button. If \a button is invalid, it is not added to the button box, and + zero is returned. + + \sa removeButton(), clear() +*/ +QPushButton *QDialogButtonBox::addButton(StandardButton button) +{ + Q_D(QDialogButtonBox); + return d->createButton(button); +} + +/*! + \property QDialogButtonBox::standardButtons + \brief collection of standard buttons in the button box + + This property controls which standard buttons are used by the button box. + + \sa addButton() +*/ +void QDialogButtonBox::setStandardButtons(StandardButtons buttons) +{ + Q_D(QDialogButtonBox); + // Clear out all the old standard buttons, then recreate them. + qDeleteAll(d->standardButtonHash.keys()); + d->standardButtonHash.clear(); + + d->createStandardButtons(buttons); +} + +QDialogButtonBox::StandardButtons QDialogButtonBox::standardButtons() const +{ + Q_D(const QDialogButtonBox); + StandardButtons standardButtons = NoButton; + QHash<QPushButton *, StandardButton>::const_iterator it = d->standardButtonHash.constBegin(); + while (it != d->standardButtonHash.constEnd()) { + standardButtons |= it.value(); + ++it; + } + return standardButtons; +} + +/*! + Returns the QPushButton corresponding to the standard button \a which, + or 0 if the standard button doesn't exist in this button box. + + \sa standardButton(), standardButtons(), buttons() +*/ +QPushButton *QDialogButtonBox::button(StandardButton which) const +{ + Q_D(const QDialogButtonBox); + return d->standardButtonHash.key(which); +} + +/*! + 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(), buttons(), standardButtons() +*/ +QDialogButtonBox::StandardButton QDialogButtonBox::standardButton(QAbstractButton *button) const +{ + Q_D(const QDialogButtonBox); + return d->standardButtonHash.value(static_cast<QPushButton *>(button)); +} + +void QDialogButtonBoxPrivate::_q_handleButtonClicked() +{ + Q_Q(QDialogButtonBox); + if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) { + emit q->clicked(button); + + switch (q->buttonRole(button)) { + case AcceptRole: + case YesRole: + emit q->accepted(); + break; + case RejectRole: + case NoRole: + emit q->rejected(); + break; + case HelpRole: + emit q->helpRequested(); + break; + default: + break; + } + } +} + +void QDialogButtonBoxPrivate::_q_handleButtonDestroyed() +{ + Q_Q(QDialogButtonBox); + if (QObject *object = q->sender()) { + QBoolBlocker skippy(internalRemove); + q->removeButton(static_cast<QAbstractButton *>(object)); + } +} + +/*! + \property QDialogButtonBox::centerButtons + \brief whether the buttons in the button box are centered + + By default, this property is false. This behavior is appopriate + for most types of dialogs. A notable exception is message boxes + on most platforms (e.g. Windows), where the button box is + centered horizontally. + + \sa QMessageBox +*/ +void QDialogButtonBox::setCenterButtons(bool center) +{ + Q_D(QDialogButtonBox); + if (d->center != center) { + d->center = center; + d->resetLayout(); + } +} + +bool QDialogButtonBox::centerButtons() const +{ + Q_D(const QDialogButtonBox); + return d->center; +} + +/*! + \reimp +*/ +void QDialogButtonBox::changeEvent(QEvent *event) +{ + typedef QHash<QPushButton *, QDialogButtonBox::StandardButton> StandardButtonHash; + + Q_D(QDialogButtonBox); + switch (event->type()) { + case QEvent::StyleChange: // Propagate style + if (!d->standardButtonHash.empty()) { + QStyle *newStyle = style(); + const StandardButtonHash::iterator end = d->standardButtonHash.end(); + for (StandardButtonHash::iterator it = d->standardButtonHash.begin(); it != end; ++it) + it.key()->setStyle(newStyle); + } + // fallthrough intended +#ifdef Q_WS_MAC + case QEvent::MacSizeChange: +#endif + d->resetLayout(); + QWidget::changeEvent(event); + break; + default: + QWidget::changeEvent(event); + break; + } +} + +/*! + \reimp +*/ +bool QDialogButtonBox::event(QEvent *event) +{ + Q_D(QDialogButtonBox); + if (event->type() == QEvent::Show) { + QList<QAbstractButton *> acceptRoleList = d->buttonLists[AcceptRole]; + QPushButton *firstAcceptButton = acceptRoleList.isEmpty() ? 0 : qobject_cast<QPushButton *>(acceptRoleList.at(0)); + bool hasDefault = false; + QWidget *dialog = 0; + QWidget *p = this; + while (p && !p->isWindow()) { + p = p->parentWidget(); + if ((dialog = qobject_cast<QDialog *>(p))) + break; + } + + foreach (QPushButton *pb, qFindChildren<QPushButton *>(dialog ? dialog : this)) { + if (pb->isDefault() && pb != firstAcceptButton) { + hasDefault = true; + break; + } + } + if (!hasDefault && firstAcceptButton) + firstAcceptButton->setDefault(true); + }else if (event->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + + return QWidget::event(event); +} + +QT_END_NAMESPACE + +#include "moc_qdialogbuttonbox.cpp" diff --git a/src/gui/widgets/qdialogbuttonbox.h b/src/gui/widgets/qdialogbuttonbox.h new file mode 100644 index 0000000..c4f3cf5 --- /dev/null +++ b/src/gui/widgets/qdialogbuttonbox.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIALOGBUTTONBOX_H +#define QDIALOGBUTTONBOX_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAbstractButton; +class QPushButton; +class QDialogButtonBoxPrivate; + +class Q_GUI_EXPORT QDialogButtonBox : public QWidget +{ + Q_OBJECT + Q_FLAGS(StandardButtons) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(StandardButtons standardButtons READ standardButtons WRITE setStandardButtons) + Q_PROPERTY(bool centerButtons READ centerButtons WRITE setCenterButtons) + +public: + enum ButtonRole { + // keep this in sync with QMessageBox::ButtonRole + InvalidRole = -1, + AcceptRole, + RejectRole, + DestructiveRole, + ActionRole, + HelpRole, + YesRole, + NoRole, + ResetRole, + ApplyRole, + + NRoles + }; + + enum StandardButton { + // keep this in sync with QMessageBox::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, + +#ifndef Q_MOC_RUN + FirstButton = Ok, + LastButton = RestoreDefaults +#endif + }; + + Q_DECLARE_FLAGS(StandardButtons, StandardButton) + + enum ButtonLayout { + WinLayout, + MacLayout, + KdeLayout, + GnomeLayout + }; + + QDialogButtonBox(QWidget *parent = 0); + QDialogButtonBox(Qt::Orientation orientation, QWidget *parent = 0); + QDialogButtonBox(StandardButtons buttons, Qt::Orientation orientation = Qt::Horizontal, + QWidget *parent = 0); + ~QDialogButtonBox(); + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + void addButton(QAbstractButton *button, ButtonRole role); + QPushButton *addButton(const QString &text, ButtonRole role); + QPushButton *addButton(StandardButton button); + void removeButton(QAbstractButton *button); + void clear(); + + QList<QAbstractButton *> buttons() const; + ButtonRole buttonRole(QAbstractButton *button) const; + + void setStandardButtons(StandardButtons buttons); + StandardButtons standardButtons() const; + StandardButton standardButton(QAbstractButton *button) const; + QPushButton *button(StandardButton which) const; + + void setCenterButtons(bool center); + bool centerButtons() const; + +Q_SIGNALS: + void clicked(QAbstractButton *button); + void accepted(); + void helpRequested(); + void rejected(); + +protected: + void changeEvent(QEvent *event); + bool event(QEvent *event); + +private: + Q_DISABLE_COPY(QDialogButtonBox) + Q_DECLARE_PRIVATE(QDialogButtonBox) + Q_PRIVATE_SLOT(d_func(), void _q_handleButtonClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_handleButtonDestroyed()) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDialogButtonBox::StandardButtons) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIALOGBUTTONBOX_H diff --git a/src/gui/widgets/qdockarealayout.cpp b/src/gui/widgets/qdockarealayout.cpp new file mode 100644 index 0000000..9261c63 --- /dev/null +++ b/src/gui/widgets/qdockarealayout.cpp @@ -0,0 +1,3316 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "QtGui/qapplication.h" +#include "QtGui/qwidget.h" +#include "QtGui/qtabbar.h" +#include "QtGui/qstyle.h" +#include "QtGui/qdesktopwidget.h" +#include "QtCore/qvariant.h" +#include "qdockarealayout_p.h" +#include "qdockwidget.h" +#include "qmainwindow.h" +#include "qwidgetanimator_p.h" +#include "qmainwindowlayout_p.h" +#include "qdockwidget_p.h" +#include <private/qlayoutengine_p.h> + +#include <qpainter.h> +#include <qstyleoption.h> + +#ifndef QT_NO_DOCKWIDGET + +QT_BEGIN_NAMESPACE + +enum { StateFlagVisible = 1, StateFlagFloating = 2 }; + +/****************************************************************************** +** QPlaceHolderItem +*/ + +QPlaceHolderItem::QPlaceHolderItem(QWidget *w) +{ + objectName = w->objectName(); + hidden = w->isHidden(); + window = w->isWindow(); + if (window) + topLevelRect = w->geometry(); +} + +/****************************************************************************** +** QDockAreaLayoutItem +*/ + +QDockAreaLayoutItem::QDockAreaLayoutItem(QLayoutItem *_widgetItem) + : widgetItem(_widgetItem), subinfo(0), placeHolderItem(0), pos(0), size(-1), flags(NoFlags) +{ +} + +QDockAreaLayoutItem::QDockAreaLayoutItem(QDockAreaLayoutInfo *_subinfo) + : widgetItem(0), subinfo(_subinfo), placeHolderItem(0), pos(0), size(-1), flags(NoFlags) +{ +} + +QDockAreaLayoutItem::QDockAreaLayoutItem(QPlaceHolderItem *_placeHolderItem) + : widgetItem(0), subinfo(0), placeHolderItem(_placeHolderItem), pos(0), size(-1), flags(NoFlags) +{ +} + +QDockAreaLayoutItem::QDockAreaLayoutItem(const QDockAreaLayoutItem &other) + : widgetItem(other.widgetItem), subinfo(0), placeHolderItem(0), pos(other.pos), + size(other.size), flags(other.flags) +{ + if (other.subinfo != 0) + subinfo = new QDockAreaLayoutInfo(*other.subinfo); + else if (other.placeHolderItem != 0) + placeHolderItem = new QPlaceHolderItem(*other.placeHolderItem); +} + +QDockAreaLayoutItem::~QDockAreaLayoutItem() +{ + delete subinfo; + delete placeHolderItem; +} + +bool QDockAreaLayoutItem::skip() const +{ + if (placeHolderItem != 0) + return true; + + if (flags & GapItem) + return false; + + if (widgetItem != 0) + return widgetItem->isEmpty(); + + if (subinfo != 0) { + for (int i = 0; i < subinfo->item_list.count(); ++i) { + if (!subinfo->item_list.at(i).skip()) + return false; + } + } + + return true; +} + +QSize QDockAreaLayoutItem::minimumSize() const +{ + if (widgetItem != 0) { + int left, top, right, bottom; + widgetItem->widget()->getContentsMargins(&left, &top, &right, &bottom); + return widgetItem->minimumSize() + QSize(left+right, top+bottom); + } + if (subinfo != 0) + return subinfo->minimumSize(); + return QSize(0, 0); +} + +QSize QDockAreaLayoutItem::maximumSize() const +{ + if (widgetItem != 0) { + int left, top, right, bottom; + widgetItem->widget()->getContentsMargins(&left, &top, &right, &bottom); + return widgetItem->maximumSize()+ QSize(left+right, top+bottom); + } + if (subinfo != 0) + return subinfo->maximumSize(); + return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); +} + +bool QDockAreaLayoutItem::expansive(Qt::Orientation o) const +{ + if ((flags & GapItem) || placeHolderItem != 0) + return false; + if (widgetItem != 0) + return ((widgetItem->expandingDirections() & o) == o); + if (subinfo != 0) + return subinfo->expansive(o); + return false; +} + +QSize QDockAreaLayoutItem::sizeHint() const +{ + if (placeHolderItem != 0) + return QSize(0, 0); + if (widgetItem != 0) { + int left, top, right, bottom; + widgetItem->widget()->getContentsMargins(&left, &top, &right, &bottom); + return widgetItem->sizeHint() + QSize(left+right, top+bottom); + } + if (subinfo != 0) + return subinfo->sizeHint(); + return QSize(-1, -1); +} + +QDockAreaLayoutItem + &QDockAreaLayoutItem::operator = (const QDockAreaLayoutItem &other) +{ + widgetItem = other.widgetItem; + if (other.subinfo == 0) + subinfo = 0; + else + subinfo = new QDockAreaLayoutInfo(*other.subinfo); + + delete placeHolderItem; + if (other.placeHolderItem == 0) + placeHolderItem = 0; + else + placeHolderItem = new QPlaceHolderItem(*other.placeHolderItem); + + pos = other.pos; + size = other.size; + flags = other.flags; + + return *this; +} + +/****************************************************************************** +** QDockAreaLayoutInfo +*/ + +#ifndef QT_NO_TABBAR +static quintptr tabId(const QDockAreaLayoutItem &item) +{ + if (item.widgetItem == 0) + return 0; + return reinterpret_cast<quintptr>(item.widgetItem->widget()); +} +#endif + +QDockAreaLayoutInfo::QDockAreaLayoutInfo() + : sep(0), dockPos(QInternal::LeftDock), o(Qt::Horizontal), rect(0, 0, -1, -1), mainWindow(0) +#ifndef QT_NO_TABBAR + , tabbed(false), tabBar(0), tabBarShape(QTabBar::RoundedSouth) +#endif +{ +} + +QDockAreaLayoutInfo::QDockAreaLayoutInfo(int _sep, QInternal::DockPosition _dockPos, + Qt::Orientation _o, int tbshape, + QMainWindow *window) + : sep(_sep), dockPos(_dockPos), o(_o), rect(0, 0, -1, -1), mainWindow(window) +#ifndef QT_NO_TABBAR + , tabbed(false), tabBar(0), tabBarShape(static_cast<QTabBar::Shape>(tbshape)) +#endif +{ +#ifdef QT_NO_TABBAR + Q_UNUSED(tbshape); +#endif +} + +QSize QDockAreaLayoutInfo::size() const +{ + return isEmpty() ? QSize(0, 0) : rect.size(); +} + +void QDockAreaLayoutInfo::clear() +{ + item_list.clear(); + rect = QRect(0, 0, -1, -1); +#ifndef QT_NO_TABBAR + tabbed = false; + tabBar = 0; +#endif +} + +bool QDockAreaLayoutInfo::isEmpty() const +{ + return next(-1) == -1; +} + +QSize QDockAreaLayoutInfo::minimumSize() const +{ + if (isEmpty()) + return QSize(0, 0); + + int a = 0, b = 0; + bool first = true; + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + + QSize min_size = item.minimumSize(); +#ifndef QT_NO_TABBAR + if (tabbed) { + a = qMax(a, pick(o, min_size)); + } else +#endif + { + if (!first) + a += sep; + a += pick(o, min_size); + } + b = qMax(b, perp(o, min_size)); + + first = false; + } + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + +#ifndef QT_NO_TABBAR + if (tabbed) { + QSize tbm = tabBarMinimumSize(); + switch (tabBarShape) { + case QTabBar::RoundedNorth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularNorth: + case QTabBar::TriangularSouth: + result.rheight() += tbm.height(); + result.rwidth() = qMax(tbm.width(), result.width()); + break; + case QTabBar::RoundedEast: + case QTabBar::RoundedWest: + case QTabBar::TriangularEast: + case QTabBar::TriangularWest: + result.rheight() = qMax(tbm.height(), result.height()); + result.rwidth() += tbm.width(); + break; + default: + break; + } + } +#endif // QT_NO_TABBAR + + return result; +} + +QSize QDockAreaLayoutInfo::maximumSize() const +{ + if (isEmpty()) + return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + + int a = 0, b = QWIDGETSIZE_MAX; +#ifndef QT_NO_TABBAR + if (tabbed) + a = QWIDGETSIZE_MAX; +#endif + + int min_perp = 0; + + bool first = true; + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + + QSize max_size = item.maximumSize(); + min_perp = qMax(min_perp, perp(o, item.minimumSize())); + +#ifndef QT_NO_TABBAR + if (tabbed) { + a = qMin(a, pick(o, max_size)); + } else +#endif + { + if (!first) + a += sep; + a += pick(o, max_size); + } + b = qMin(b, perp(o, max_size)); + + a = qMin(a, int(QWIDGETSIZE_MAX)); + b = qMin(b, int(QWIDGETSIZE_MAX)); + + first = false; + } + + b = qMax(b, min_perp); + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + +#ifndef QT_NO_TABBAR + if (tabbed) { + QSize tbh = tabBarSizeHint(); + switch (tabBarShape) { + case QTabBar::RoundedNorth: + case QTabBar::RoundedSouth: + result.rheight() += tbh.height(); + break; + case QTabBar::RoundedEast: + case QTabBar::RoundedWest: + result.rwidth() += tbh.width(); + break; + default: + break; + } + } +#endif // QT_NO_TABBAR + + return result; +} + +QSize QDockAreaLayoutInfo::sizeHint() const +{ + if (isEmpty()) + return QSize(0, 0); + + int a = 0, b = 0; + bool prev_gap = false; + bool first = true; + int min_perp = 0; + int max_perp = QWIDGETSIZE_MAX; + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + + bool gap = item.flags & QDockAreaLayoutItem::GapItem; + + QSize size_hint = item.sizeHint(); + min_perp = qMax(min_perp, perp(o, item.minimumSize())); + max_perp = qMin(max_perp, perp(o, item.maximumSize())); + +#ifndef QT_NO_TABBAR + if (tabbed) { + a = qMax(a, gap ? item.size : pick(o, size_hint)); + } else +#endif + { + if (!first && !gap && !prev_gap) + a += sep; + a += gap ? item.size : pick(o, size_hint); + } + b = qMax(b, perp(o, size_hint)); + + prev_gap = gap; + first = false; + } + + max_perp = qMax(max_perp, min_perp); + b = qMax(b, min_perp); + b = qMin(b, max_perp); + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + +#ifndef QT_NO_TABBAR + if (tabbed) { + QSize tbh = tabBarSizeHint(); + switch (tabBarShape) { + case QTabBar::RoundedNorth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularNorth: + case QTabBar::TriangularSouth: + result.rheight() += tbh.height(); + result.rwidth() = qMax(tbh.width(), result.width()); + break; + case QTabBar::RoundedEast: + case QTabBar::RoundedWest: + case QTabBar::TriangularEast: + case QTabBar::TriangularWest: + result.rheight() = qMax(tbh.height(), result.height()); + result.rwidth() += tbh.width(); + break; + default: + break; + } + } +#endif // QT_NO_TABBAR + + return result; +} + +bool QDockAreaLayoutInfo::expansive(Qt::Orientation o) const +{ + for (int i = 0; i < item_list.size(); ++i) { + if (item_list.at(i).expansive(o)) + return true; + } + return false; +} + +/* QDockAreaLayoutInfo::maximumSize() doesn't return the real max size. For example, + if the layout is empty, it returns QWIDGETSIZE_MAX. This is so that empty dock areas + don't constrain the size of the QMainWindow, but sometimes we really need to know the + maximum size. Also, these functions take into account widgets that want to keep their + size (f.ex. when they are hidden and then shown, they should not change size). +*/ + +static int realMinSize(const QDockAreaLayoutInfo &info) +{ + int result = 0; + bool first = true; + for (int i = 0; i < info.item_list.size(); ++i) { + const QDockAreaLayoutItem &item = info.item_list.at(i); + if (item.skip()) + continue; + + int min = 0; + if ((item.flags & QDockAreaLayoutItem::KeepSize) && item.size != -1) + min = item.size; + else + min = pick(info.o, item.minimumSize()); + + if (!first) + result += info.sep; + result += min; + + first = false; + } + + return result; +} + +static int realMaxSize(const QDockAreaLayoutInfo &info) +{ + int result = 0; + bool first = true; + for (int i = 0; i < info.item_list.size(); ++i) { + const QDockAreaLayoutItem &item = info.item_list.at(i); + if (item.skip()) + continue; + + int max = 0; + if ((item.flags & QDockAreaLayoutItem::KeepSize) && item.size != -1) + max = item.size; + else + max = pick(info.o, item.maximumSize()); + + if (!first) + result += info.sep; + result += max; + + if (result >= QWIDGETSIZE_MAX) + return QWIDGETSIZE_MAX; + + first = false; + } + + return result; +} + +void QDockAreaLayoutInfo::fitItems() +{ +#ifndef QT_NO_TABBAR + if (tabbed) { + return; + } +#endif + + QVector<QLayoutStruct> layout_struct_list(item_list.size()*2); + int j = 0; + + int size = pick(o, rect.size()); + int min_size = realMinSize(*this); + int max_size = realMaxSize(*this); + int last_index = -1; + + bool prev_gap = false; + bool first = true; + for (int i = 0; i < item_list.size(); ++i) { + QDockAreaLayoutItem &item = item_list[i]; + if (item.skip()) + continue; + + bool gap = item.flags & QDockAreaLayoutItem::GapItem; + if (!first && !gap && !prev_gap) { + QLayoutStruct &ls = layout_struct_list[j++]; + ls.init(); + ls.minimumSize = sep; + ls.maximumSize = sep; + ls.sizeHint = sep; + ls.empty = false; + } + + if (item.flags & QDockAreaLayoutItem::KeepSize) { + // Check if the item can keep its size, without violating size constraints + // of other items. + + if (size < min_size) { + // There is too little space to keep this widget's size + item.flags &= ~QDockAreaLayoutItem::KeepSize; + min_size -= item.size; + min_size += pick(o, item.minimumSize()); + min_size = qMax(0, min_size); + } else if (size > max_size) { + // There is too much space to keep this widget's size + item.flags &= ~QDockAreaLayoutItem::KeepSize; + max_size -= item.size; + max_size += pick(o, item.maximumSize()); + max_size = qMin<int>(QWIDGETSIZE_MAX, max_size); + } + } + + last_index = j; + QLayoutStruct &ls = layout_struct_list[j++]; + ls.init(); + ls.empty = false; + if (gap || (item.flags & QDockAreaLayoutItem::KeepSize)) { + ls.minimumSize = ls.maximumSize = ls.sizeHint = item.size; + ls.expansive = false; + ls.stretch = 0; + } else { + ls.maximumSize = pick(o, item.maximumSize()); + ls.expansive = item.expansive(o); + ls.minimumSize = pick(o, item.minimumSize()); + ls.sizeHint = item.size == -1 ? pick(o, item.sizeHint()) : item.size; + ls.stretch = ls.expansive ? ls.sizeHint : 0; + } + + item.flags &= ~QDockAreaLayoutItem::KeepSize; + prev_gap = gap; + first = false; + } + layout_struct_list.resize(j); + + // If there is more space than the widgets can take (due to maximum size constraints), + // we detect it here and stretch the last widget to take up the rest of the space. + if (size > max_size && last_index != -1) { + layout_struct_list[last_index].maximumSize = QWIDGETSIZE_MAX; + layout_struct_list[last_index].expansive = true; + } + + qGeomCalc(layout_struct_list, 0, j, pick(o, rect.topLeft()), size, 0); + + j = 0; + prev_gap = false; + first = true; + for (int i = 0; i < item_list.size(); ++i) { + QDockAreaLayoutItem &item = item_list[i]; + if (item.skip()) + continue; + + bool gap = item.flags & QDockAreaLayoutItem::GapItem; + if (!first && !gap && !prev_gap) + ++j; + + const QLayoutStruct &ls = layout_struct_list.at(j++); + item.size = ls.size; + item.pos = ls.pos; + + if (item.subinfo != 0) { + item.subinfo->rect = itemRect(i); + item.subinfo->fitItems(); + } + + prev_gap = gap; + first = false; + } +} + +static QInternal::DockPosition dockPosHelper(const QRect &rect, const QPoint &_pos, + Qt::Orientation o, + bool nestingEnabled, + QDockAreaLayoutInfo::TabMode tabMode) +{ + if (tabMode == QDockAreaLayoutInfo::ForceTabs) + return QInternal::DockCount; + + QPoint pos = _pos - rect.topLeft(); + + int x = pos.x(); + int y = pos.y(); + int w = rect.width(); + int h = rect.height(); + + if (tabMode != QDockAreaLayoutInfo::NoTabs) { + // is it in the center? + if (nestingEnabled) { + /* 2/3 + +--------------+ + | | + | CCCCCCCC | + 2/3 | CCCCCCCC | + | CCCCCCCC | + | | + +--------------+ */ + + QRect center(w/6, h/6, 2*w/3, 2*h/3); + if (center.contains(pos)) + return QInternal::DockCount; + } else if (o == Qt::Horizontal) { + /* 2/3 + +--------------+ + | CCCCCCCC | + | CCCCCCCC | + | CCCCCCCC | + | CCCCCCCC | + | CCCCCCCC | + +--------------+ */ + + if (x > w/6 && x < w*5/6) + return QInternal::DockCount; + } else { + /* + +--------------+ + | | + 2/3 |CCCCCCCCCCCCCC| + |CCCCCCCCCCCCCC| + | | + +--------------+ */ + if (y > h/6 && y < 5*h/6) + return QInternal::DockCount; + } + } + + // not in the center. which edge? + if (nestingEnabled) { + if (o == Qt::Horizontal) { + /* 1/3 1/3 1/3 + +------------+ (we've already ruled out the center) + |LLLLTTTTRRRR| + |LLLLTTTTRRRR| + |LLLLBBBBRRRR| + |LLLLBBBBRRRR| + +------------+ */ + + if (x < w/3) + return QInternal::LeftDock; + if (x > 2*w/3) + return QInternal::RightDock; + if (y < h/2) + return QInternal::TopDock; + return QInternal::BottomDock; + } else { + /* +------------+ (we've already ruled out the center) + 1/3 |TTTTTTTTTTTT| + |LLLLLLRRRRRR| + 1/3 |LLLLLLRRRRRR| + 1/3 |BBBBBBBBBBBB| + +------------+ */ + + if (y < h/3) + return QInternal::TopDock; + if (y > 2*h/3) + return QInternal::BottomDock; + if (x < w/2) + return QInternal::LeftDock; + return QInternal::RightDock; + } + } else { + if (o == Qt::Horizontal) { + return x < w/2 + ? QInternal::LeftDock + : QInternal::RightDock; + } else { + return y < h/2 + ? QInternal::TopDock + : QInternal::BottomDock; + } + } +} + +QList<int> QDockAreaLayoutInfo::gapIndex(const QPoint& _pos, + bool nestingEnabled, TabMode tabMode) const +{ + QList<int> result; + QRect item_rect; + int item_index = 0; + +#ifndef QT_NO_TABBAR + if (tabbed) { + item_rect = tabContentRect(); + } else +#endif + { + int pos = pick(o, _pos); + + int last = -1; + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + + last = i; + + if (item.pos + item.size < pos) + continue; + + if (item.subinfo != 0 +#ifndef QT_NO_TABBAR + && !item.subinfo->tabbed +#endif + ) { + result = item.subinfo->gapIndex(_pos, nestingEnabled, + tabMode); + result.prepend(i); + return result; + } + + item_rect = itemRect(i); + item_index = i; + break; + } + + if (item_rect.isNull()) { + result.append(last + 1); + return result; + } + } + + Q_ASSERT(!item_rect.isNull()); + + QInternal::DockPosition dock_pos + = dockPosHelper(item_rect, _pos, o, nestingEnabled, tabMode); + + switch (dock_pos) { + case QInternal::LeftDock: + if (o == Qt::Horizontal) + result << item_index; + else + result << item_index << 0; // this subinfo doesn't exist yet, but insertGap() + // handles this by inserting it + break; + case QInternal::RightDock: + if (o == Qt::Horizontal) + result << item_index + 1; + else + result << item_index << 1; + break; + case QInternal::TopDock: + if (o == Qt::Horizontal) + result << item_index << 0; + else + result << item_index; + break; + case QInternal::BottomDock: + if (o == Qt::Horizontal) + result << item_index << 1; + else + result << item_index + 1; + break; + case QInternal::DockCount: + result << (-item_index - 1) << 0; // negative item_index means "on top of" + // -item_index - 1, insertGap() + // will insert a tabbed subinfo + break; + default: + break; + } + + return result; +} + +static inline int shrink(QLayoutStruct &ls, int delta) +{ + if (ls.empty) + return 0; + int old_size = ls.size; + ls.size = qMax(ls.size - delta, ls.minimumSize); + return old_size - ls.size; +} + +static inline int grow(QLayoutStruct &ls, int delta) +{ + if (ls.empty) + return 0; + int old_size = ls.size; + ls.size = qMin(ls.size + delta, ls.maximumSize); + return ls.size - old_size; +} + +static int separatorMoveHelper(QVector<QLayoutStruct> &list, int index, int delta, int sep) +{ + // adjust sizes + int pos = -1; + for (int i = 0; i < list.size(); ++i) { + const QLayoutStruct &ls = list.at(i); + if (!ls.empty) { + pos = ls.pos; + break; + } + } + if (pos == -1) + return 0; + + if (delta > 0) { + int growlimit = 0; + for (int i = 0; i<=index; ++i) { + const QLayoutStruct &ls = list.at(i); + if (ls.empty) + continue; + if (ls.maximumSize == QLAYOUTSIZE_MAX) { + growlimit = QLAYOUTSIZE_MAX; + break; + } + growlimit += ls.maximumSize - ls.size; + } + if (delta > growlimit) + delta = growlimit; + + int d = 0; + for (int i = index + 1; d < delta && i < list.count(); ++i) + d += shrink(list[i], delta - d); + delta = d; + d = 0; + for (int i = index; d < delta && i >= 0; --i) + d += grow(list[i], delta - d); + } else if (delta < 0) { + int growlimit = 0; + for (int i = index + 1; i < list.count(); ++i) { + const QLayoutStruct &ls = list.at(i); + if (ls.empty) + continue; + if (ls.maximumSize == QLAYOUTSIZE_MAX) { + growlimit = QLAYOUTSIZE_MAX; + break; + } + growlimit += ls.maximumSize - ls.size; + } + if (-delta > growlimit) + delta = -growlimit; + + int d = 0; + for (int i = index; d < -delta && i >= 0; --i) + d += shrink(list[i], -delta - d); + delta = -d; + d = 0; + for (int i = index + 1; d < -delta && i < list.count(); ++i) + d += grow(list[i], -delta - d); + } + + // adjust positions + bool first = true; + for (int i = 0; i < list.size(); ++i) { + QLayoutStruct &ls = list[i]; + if (ls.empty) { + ls.pos = pos + (first ? 0 : sep); + continue; + } + if (!first) + pos += sep; + ls.pos = pos; + pos += ls.size; + first = false; + } + + return delta; +} + +int QDockAreaLayoutInfo::separatorMove(int index, int delta, QVector<QLayoutStruct> *cache) +{ +#ifndef QT_NO_TABBAR + Q_ASSERT(!tabbed); +#endif + + if (cache->isEmpty()) { + QVector<QLayoutStruct> &list = *cache; + list.resize(item_list.size()); + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + QLayoutStruct &ls = list[i]; + Q_ASSERT(!(item.flags & QDockAreaLayoutItem::GapItem)); + if (item.skip()) { + ls.empty = true; + } else { + ls.empty = false; + ls.pos = item.pos; + ls.size = item.size; + ls.minimumSize = pick(o, item.minimumSize()); + ls.maximumSize = pick(o, item.maximumSize()); + } + } + } + + QVector<QLayoutStruct> list = *cache; + + delta = separatorMoveHelper(list, index, delta, sep); + + for (int i = 0; i < item_list.size(); ++i) { + QDockAreaLayoutItem &item = item_list[i]; + if (item.skip()) + continue; + QLayoutStruct &ls = list[i]; + item.size = ls.size; + item.pos = ls.pos; + + if (item.subinfo != 0) { + item.subinfo->rect = itemRect(i); + item.subinfo->fitItems(); + } + } + + return delta; +} + +void QDockAreaLayoutInfo::unnest(int index) +{ + QDockAreaLayoutItem &item = item_list[index]; + if (item.subinfo == 0) + return; + if (item.subinfo->item_list.count() > 1) + return; + + if (item.subinfo->item_list.count() == 0) { + item_list.removeAt(index); + } else if (item.subinfo->item_list.count() == 1) { + QDockAreaLayoutItem &child = item.subinfo->item_list.first(); + if (child.widgetItem != 0) { + item.widgetItem = child.widgetItem; + delete item.subinfo; + item.subinfo = 0; + } else if (child.subinfo != 0) { + QDockAreaLayoutInfo *tmp = item.subinfo; + item.subinfo = child.subinfo; + child.subinfo = 0; + tmp->item_list.clear(); + delete tmp; + } + } +} + +void QDockAreaLayoutInfo::remove(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + + if (path.count() > 1) { + int index = path.takeFirst(); + QDockAreaLayoutItem &item = item_list[index]; + Q_ASSERT(item.subinfo != 0); + item.subinfo->remove(path); + unnest(index); + } else { + int index = path.first(); + item_list.removeAt(index); + } +} + +QLayoutItem *QDockAreaLayoutInfo::plug(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + + int index = path.takeFirst(); + if (index < 0) + index = -index - 1; + + if (!path.isEmpty()) { + const QDockAreaLayoutItem &item = item_list.at(index); + Q_ASSERT(item.subinfo != 0); + return item.subinfo->plug(path); + } + + QDockAreaLayoutItem &item = item_list[index]; + + Q_ASSERT(item.widgetItem != 0); + Q_ASSERT(item.flags & QDockAreaLayoutItem::GapItem); + item.flags &= ~QDockAreaLayoutItem::GapItem; + + QRect result; + +#ifndef QT_NO_TABBAR + if (tabbed) { + } else +#endif + { + int prev = this->prev(index); + int next = this->next(index); + + if (prev != -1 && !(item_list.at(prev).flags & QDockAreaLayoutItem::GapItem)) { + item.pos += sep; + item.size -= sep; + } + if (next != -1 && !(item_list.at(next).flags & QDockAreaLayoutItem::GapItem)) + item.size -= sep; + + QPoint pos; + rpick(o, pos) = item.pos; + rperp(o, pos) = perp(o, rect.topLeft()); + QSize s; + rpick(o, s) = item.size; + rperp(o, s) = perp(o, rect.size()); + result = QRect(pos, s); + } + + return item.widgetItem; +} + +QLayoutItem *QDockAreaLayoutInfo::unplug(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + + if (path.count() > 1) { + int index = path.takeFirst(); + const QDockAreaLayoutItem &item = item_list.at(index); + Q_ASSERT(item.subinfo != 0); + return item.subinfo->unplug(path); + } + + int index = path.first(); + QDockAreaLayoutItem &item = item_list[index]; + int prev = this->prev(index); + int next = this->next(index); + + Q_ASSERT(!(item.flags & QDockAreaLayoutItem::GapItem)); + item.flags |= QDockAreaLayoutItem::GapItem; + +#ifndef QT_NO_TABBAR + if (tabbed) { + } else +#endif + { + if (prev != -1 && !(item_list.at(prev).flags & QDockAreaLayoutItem::GapItem)) { + item.pos -= sep; + item.size += sep; + } + if (next != -1 && !(item_list.at(next).flags & QDockAreaLayoutItem::GapItem)) + item.size += sep; + } + + return item.widgetItem; +} + +#ifndef QT_NO_TABBAR + +quintptr QDockAreaLayoutInfo::currentTabId() const +{ + if (!tabbed || tabBar == 0) + return 0; + + int index = tabBar->currentIndex(); + if (index == -1) + return 0; + + return qvariant_cast<quintptr>(tabBar->tabData(index)); +} + +void QDockAreaLayoutInfo::setCurrentTab(QWidget *widget) +{ + setCurrentTabId(reinterpret_cast<quintptr>(widget)); +} + +void QDockAreaLayoutInfo::setCurrentTabId(quintptr id) +{ + if (!tabbed || tabBar == 0) + return; + + for (int i = 0; i < tabBar->count(); ++i) { + if (qvariant_cast<quintptr>(tabBar->tabData(i)) == id) { + tabBar->setCurrentIndex(i); + return; + } + } +} + +#endif // QT_NO_TABBAR + +static QRect dockedGeometry(QWidget *widget) +{ + int titleHeight = 0; + + QDockWidgetLayout *layout + = qobject_cast<QDockWidgetLayout*>(widget->layout()); + if(layout != 0 && layout->nativeWindowDeco()) + titleHeight = layout->titleHeight(); + + QRect result = widget->geometry(); + result.adjust(0, -titleHeight, 0, 0); + return result; +} + +bool QDockAreaLayoutInfo::insertGap(QList<int> path, QLayoutItem *dockWidgetItem) +{ + Q_ASSERT(!path.isEmpty()); + + bool insert_tabbed = false; + int index = path.takeFirst(); + if (index < 0) { + insert_tabbed = true; + index = -index - 1; + } + +// dump(qDebug() << "insertGap() before:" << index << tabIndex, *this, QString()); + + if (!path.isEmpty()) { + QDockAreaLayoutItem &item = item_list[index]; + + if (item.subinfo == 0 +#ifndef QT_NO_TABBAR + || (item.subinfo->tabbed && !insert_tabbed) +#endif + ) { + + // this is not yet a nested layout - make it + + QDockAreaLayoutInfo *subinfo = item.subinfo; + QLayoutItem *widgetItem = item.widgetItem; + QRect r = subinfo == 0 ? dockedGeometry(widgetItem->widget()) : subinfo->rect; + + Qt::Orientation opposite = o == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal; +#ifdef QT_NO_TABBAR + const int tabBarShape = 0; +#endif + QDockAreaLayoutInfo *new_info + = new QDockAreaLayoutInfo(sep, dockPos, opposite, tabBarShape, mainWindow); + + item.subinfo = new_info; + item.widgetItem = 0; + + QDockAreaLayoutItem new_item + = widgetItem == 0 + ? QDockAreaLayoutItem(subinfo) + : QDockAreaLayoutItem(widgetItem); + new_item.size = pick(opposite, r.size()); + new_item.pos = pick(opposite, r.topLeft()); + new_info->item_list.append(new_item); +#ifndef QT_NO_TABBAR + if (insert_tabbed) { + new_info->tabbed = true; + } +#endif + } + + bool result = item.subinfo->insertGap(path, dockWidgetItem); + return result; + } + + // create the gap item + QDockAreaLayoutItem gap_item; + gap_item.flags |= QDockAreaLayoutItem::GapItem; + gap_item.widgetItem = dockWidgetItem; // so minimumSize(), maximumSize() and + // sizeHint() will work +#ifndef QT_NO_TABBAR + if (!tabbed) +#endif + { + int prev = this->prev(index); + int next = this->next(index - 1); + // find out how much space we have in the layout + int space = 0; + if (isEmpty()) { + // I am an empty dock area, therefore I am a top-level dock area. + switch (dockPos) { + case QInternal::LeftDock: + case QInternal::RightDock: + if (o == Qt::Vertical) { + // the "size" is the height of the dock area (remember we are empty) + space = pick(Qt::Vertical, rect.size()); + } else { + space = pick(Qt::Horizontal, dockWidgetItem->widget()->size()); + } + break; + case QInternal::TopDock: + case QInternal::BottomDock: + default: + if (o == Qt::Horizontal) { + // the "size" is width of the dock area + space = pick(Qt::Horizontal, rect.size()); + } else { + space = pick(Qt::Vertical, dockWidgetItem->widget()->size()); + } + break; + } + } else { + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + Q_ASSERT(!(item.flags & QDockAreaLayoutItem::GapItem)); + space += item.size - pick(o, item.minimumSize()); + } + } + + // find the actual size of the gap + int gap_size = 0; + int sep_size = 0; + if (isEmpty()) { + gap_size = space; + sep_size = 0; + } else { + QRect r = dockedGeometry(dockWidgetItem->widget()); + gap_size = pick(o, r.size()); + if (prev != -1 && !(item_list.at(prev).flags & QDockAreaLayoutItem::GapItem)) + sep_size += sep; + if (next != -1 && !(item_list.at(next).flags & QDockAreaLayoutItem::GapItem)) + sep_size += sep; + } + if (gap_size + sep_size > space) + gap_size = pick(o, gap_item.minimumSize()); + gap_item.size = gap_size + sep_size; + } + + // finally, insert the gap + item_list.insert(index, gap_item); + +// dump(qDebug() << "insertGap() after:" << index << tabIndex, *this, QString()); + + return true; +} + +QDockAreaLayoutInfo *QDockAreaLayoutInfo::info(QWidget *widget) +{ + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + +#ifndef QT_NO_TABBAR + if (tabbed && widget == tabBar) + return this; +#endif + + if (item.widgetItem != 0 && item.widgetItem->widget() == widget) + return this; + + if (item.subinfo != 0) { + if (QDockAreaLayoutInfo *result = item.subinfo->info(widget)) + return result; + } + } + + return 0; +} + +QDockAreaLayoutInfo *QDockAreaLayoutInfo::info(QList<int> path) +{ + int index = path.takeFirst(); + if (index < 0) + index = -index - 1; + if (index >= item_list.count()) + return this; + if (path.isEmpty() || item_list.at(index).subinfo == 0) + return this; + return item_list.at(index).subinfo->info(path); +} + +QRect QDockAreaLayoutInfo::itemRect(int index) const +{ + const QDockAreaLayoutItem &item = item_list.at(index); + + if (item.skip()) + return QRect(); + + QRect result; + +#ifndef QT_NO_TABBAR + if (tabbed) { + if (tabId(item) == currentTabId()) + result = tabContentRect(); + } else +#endif + { + QPoint pos; + rpick(o, pos) = item.pos; + rperp(o, pos) = perp(o, rect.topLeft()); + QSize s; + rpick(o, s) = item.size; + rperp(o, s) = perp(o, rect.size()); + result = QRect(pos, s); + } + + return result; +} + +QRect QDockAreaLayoutInfo::itemRect(QList<int> path) const +{ + Q_ASSERT(!path.isEmpty()); + + if (path.count() > 1) { + const QDockAreaLayoutItem &item = item_list.at(path.takeFirst()); + Q_ASSERT(item.subinfo != 0); + return item.subinfo->itemRect(path); + } + + return itemRect(path.first()); +} + +QRect QDockAreaLayoutInfo::separatorRect(int index) const +{ +#ifndef QT_NO_TABBAR + if (tabbed) + return QRect(); +#endif + + const QDockAreaLayoutItem &item = item_list.at(index); + if (item.skip()) + return QRect(); + + QPoint pos = rect.topLeft(); + rpick(o, pos) = item.pos + item.size; + QSize s = rect.size(); + rpick(o, s) = sep; + + return QRect(pos, s); +} + +QRect QDockAreaLayoutInfo::separatorRect(QList<int> path) const +{ + Q_ASSERT(!path.isEmpty()); + + if (path.count() > 1) { + const QDockAreaLayoutItem &item = item_list.at(path.takeFirst()); + Q_ASSERT(item.subinfo != 0); + return item.subinfo->separatorRect(path); + } + return separatorRect(path.first()); +} + +QList<int> QDockAreaLayoutInfo::findSeparator(const QPoint &_pos) const +{ +#ifndef QT_NO_TABBAR + if (tabbed) + return QList<int>(); +#endif + + int pos = pick(o, _pos); + + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip() || (item.flags & QDockAreaLayoutItem::GapItem)) + continue; + + if (item.pos + item.size > pos) { + if (item.subinfo != 0) { + QList<int> result = item.subinfo->findSeparator(_pos); + if (!result.isEmpty()) { + result.prepend(i); + return result; + } else { + return QList<int>(); + } + } + } + + int next = this->next(i); + if (next == -1 || (item_list.at(next).flags & QDockAreaLayoutItem::GapItem)) + continue; + + int margin = (sep == 1? 2 : 0); + if (pos >= item.pos + item.size - margin && item.pos + item.size + sep + margin > pos) { + QList<int> result; + result.append(i); + return result; + } + + } + + return QList<int>(); +} + +QList<int> QDockAreaLayoutInfo::indexOfPlaceHolder(const QString &objectName) const +{ + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + + if (item.subinfo != 0) { + QList<int> result = item.subinfo->indexOfPlaceHolder(objectName); + if (!result.isEmpty()) { + result.prepend(i); + return result; + } + continue; + } + + if (item.placeHolderItem != 0 && item.placeHolderItem->objectName == objectName) { + QList<int> result; + result << i; + return result; + } + } + + return QList<int>(); +} + +QList<int> QDockAreaLayoutInfo::indexOf(QWidget *widget) const +{ + for (int i = 0; i < item_list.size(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + + if (item.placeHolderItem != 0) + continue; + + if (item.subinfo != 0) { + QList<int> result = item.subinfo->indexOf(widget); + if (!result.isEmpty()) { + result.prepend(i); + return result; + } + continue; + } + + if (!(item.flags & QDockAreaLayoutItem::GapItem) && item.widgetItem->widget() == widget) { + QList<int> result; + result << i; + return result; + } + } + + return QList<int>(); +} + +QMainWindowLayout *QDockAreaLayoutInfo::mainWindowLayout() const +{ + QMainWindowLayout *result = qobject_cast<QMainWindowLayout*>(mainWindow->layout()); + Q_ASSERT(result != 0); + return result; +} + +void QDockAreaLayoutInfo::apply(bool animate) +{ + QWidgetAnimator *widgetAnimator = mainWindowLayout()->widgetAnimator; + +#ifndef QT_NO_TABBAR + if (tabbed) { + QRect tab_rect; + QSize tbh = tabBarSizeHint(); + + if (tabBarVisible) { + switch (tabBarShape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + tab_rect = QRect(rect.left(), rect.top(), rect.width(), tbh.height()); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + tab_rect = QRect(rect.left(), rect.bottom() - tbh.height() + 1, + rect.width(), tbh.height()); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + tab_rect = QRect(rect.right() - tbh.width() + 1, rect.top(), + tbh.width(), rect.height()); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + tab_rect = QRect(rect.left(), rect.top(), + tbh.width(), rect.height()); + break; + default: + break; + } + } + + widgetAnimator->animate(tabBar, tab_rect, animate); + } +#endif // QT_NO_TABBAR + + for (int i = 0; i < item_list.size(); ++i) { + QDockAreaLayoutItem &item = item_list[i]; + + if (item.flags & QDockAreaLayoutItem::GapItem) + continue; + + if (item.subinfo != 0) { + item.subinfo->apply(animate); + continue; + } + + if (item.skip()) + continue; + + Q_ASSERT(item.widgetItem); + QRect r = itemRect(i); + QWidget *w = item.widgetItem->widget(); + + QRect geo = w->geometry(); + widgetAnimator->animate(w, r, animate); + if (!w->isHidden()) { + QDockWidget *dw = qobject_cast<QDockWidget*>(w); + if (!r.isValid() && geo.right() >= 0 && geo.bottom() >= 0) { + dw->lower(); + emit dw->visibilityChanged(false); + } else if (r.isValid() + && (geo.right() < 0 || geo.bottom() < 0)) { + emit dw->visibilityChanged(true); + } + } + } + + if (sep == 1) + updateSeparatorWidgets(); +} + +static void paintSep(QPainter *p, QWidget *w, const QRect &r, Qt::Orientation o, bool mouse_over) +{ + QStyleOption opt(0); + opt.state = QStyle::State_None; + if (w->isEnabled()) + opt.state |= QStyle::State_Enabled; + if (o != Qt::Horizontal) + opt.state |= QStyle::State_Horizontal; + if (mouse_over) + opt.state |= QStyle::State_MouseOver; + opt.rect = r; + opt.palette = w->palette(); + + w->style()->drawPrimitive(QStyle::PE_IndicatorDockWidgetResizeHandle, &opt, p, w); +} + +QRegion QDockAreaLayoutInfo::separatorRegion() const +{ + QRegion result; + + if (isEmpty()) + return result; +#ifndef QT_NO_TABBAR + if (tabbed) + return result; +#endif + + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + + if (item.skip()) + continue; + + int next = this->next(i); + + if (item.subinfo) + result |= item.subinfo->separatorRegion(); + + if (next == -1) + break; + result |= separatorRect(i); + } + + return result; +} + +void QDockAreaLayoutInfo::paintSeparators(QPainter *p, QWidget *widget, + const QRegion &clip, + const QPoint &mouse) const +{ + if (isEmpty()) + return; +#ifndef QT_NO_TABBAR + if (tabbed) + return; +#endif + + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + + if (item.skip()) + continue; + + int next = this->next(i); + if ((item.flags & QDockAreaLayoutItem::GapItem) + || (next != -1 && (item_list.at(next).flags & QDockAreaLayoutItem::GapItem))) + continue; + + if (item.subinfo) { + if (clip.contains(item.subinfo->rect)) + item.subinfo->paintSeparators(p, widget, clip, mouse); + } + + if (next == -1) + break; + QRect r = separatorRect(i); + if (clip.contains(r)) + paintSep(p, widget, r, o, r.contains(mouse)); + } +} + +int QDockAreaLayoutInfo::next(int index) const +{ + for (int i = index + 1; i < item_list.size(); ++i) { + if (!item_list.at(i).skip()) + return i; + } + return -1; +} + +int QDockAreaLayoutInfo::prev(int index) const +{ + for (int i = index - 1; i >= 0; --i) { + if (!item_list.at(i).skip()) + return i; + } + return -1; +} + +void QDockAreaLayoutInfo::tab(int index, QLayoutItem *dockWidgetItem) +{ +#ifdef QT_NO_TABBAR + Q_UNUSED(index); + Q_UNUSED(dockWidgetItem); +#else + if (tabbed) { + item_list.append(QDockAreaLayoutItem(dockWidgetItem)); + updateTabBar(); + setCurrentTab(dockWidgetItem->widget()); + } else { + QDockAreaLayoutInfo *new_info + = new QDockAreaLayoutInfo(sep, dockPos, o, tabBarShape, mainWindow); + item_list[index].subinfo = new_info; + new_info->item_list.append(item_list.at(index).widgetItem); + item_list[index].widgetItem = 0; + new_info->item_list.append(dockWidgetItem); + new_info->tabbed = true; + new_info->updateTabBar(); + new_info->setCurrentTab(dockWidgetItem->widget()); + } +#endif // QT_NO_TABBAR +} + +void QDockAreaLayoutInfo::split(int index, Qt::Orientation orientation, + QLayoutItem *dockWidgetItem) +{ + if (orientation == o) { + item_list.insert(index + 1, QDockAreaLayoutItem(dockWidgetItem)); + } else { +#ifdef QT_NO_TABBAR + const int tabBarShape = 0; +#endif + QDockAreaLayoutInfo *new_info + = new QDockAreaLayoutInfo(sep, dockPos, orientation, tabBarShape, mainWindow); + item_list[index].subinfo = new_info; + new_info->item_list.append(item_list.at(index).widgetItem); + item_list[index].widgetItem = 0; + new_info->item_list.append(dockWidgetItem); + } +} + +QDockAreaLayoutItem &QDockAreaLayoutInfo::item(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + if (path.count() > 1) { + QDockAreaLayoutItem &item = item_list[path.takeFirst()]; + Q_ASSERT(item.subinfo != 0); + return item.subinfo->item(path); + } + return item_list[path.first()]; +} + +QLayoutItem *QDockAreaLayoutInfo::itemAt(int *x, int index) const +{ + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.placeHolderItem != 0) + continue; + if (item.subinfo) { + if (QLayoutItem *ret = item.subinfo->itemAt(x, index)) + return ret; + } else if (item.widgetItem) { + if ((*x)++ == index) + return item.widgetItem; + } + } + return 0; +} + +QLayoutItem *QDockAreaLayoutInfo::takeAt(int *x, int index) +{ + for (int i = 0; i < item_list.count(); ++i) { + QDockAreaLayoutItem &item = item_list[i]; + if (item.placeHolderItem != 0) + continue; + else if (item.subinfo) { + if (QLayoutItem *ret = item.subinfo->takeAt(x, index)) { + unnest(i); + return ret; + } + } else if (item.widgetItem) { + if ((*x)++ == index) { + item.placeHolderItem = new QPlaceHolderItem(item.widgetItem->widget()); + QLayoutItem *ret = item.widgetItem; + item.widgetItem = 0; + if (item.size != -1) + item.flags |= QDockAreaLayoutItem::KeepSize; + return ret; + } + } + } + return 0; +} + +void QDockAreaLayoutInfo::deleteAllLayoutItems() +{ + for (int i = 0; i < item_list.count(); ++i) { + QDockAreaLayoutItem &item= item_list[i]; + if (item.subinfo) { + item.subinfo->deleteAllLayoutItems(); + } else { + delete item.widgetItem; + item.widgetItem = 0; + } + } +} + +void QDockAreaLayoutInfo::saveState(QDataStream &stream) const +{ +#ifndef QT_NO_TABBAR + if (tabbed) { + stream << (uchar) TabMarker; + + // write the index in item_list of the widget that's currently on top. + quintptr id = currentTabId(); + int index = -1; + for (int i = 0; i < item_list.count(); ++i) { + if (tabId(item_list.at(i)) == id) { + index = i; + break; + } + } + stream << index; + } else +#endif // QT_NO_TABBAR + { + stream << (uchar) SequenceMarker; + } + + stream << (uchar) o << item_list.count(); + + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.widgetItem != 0) { + stream << (uchar) WidgetMarker; + QWidget *w = item.widgetItem->widget(); + QString name = w->objectName(); + if (name.isEmpty()) { + qWarning("QMainWindow::saveState(): 'objectName' not set for QDockWidget %p '%s;", + w, qPrintable(w->windowTitle())); + } + stream << name; + + uchar flags = 0; + if (!w->isHidden()) + flags |= StateFlagVisible; + if (w->isWindow()) + flags |= StateFlagFloating; + stream << flags; + + if (w->isWindow()) { + stream << w->x() << w->y() << w->width() << w->height(); + } else { + stream << item.pos << item.size << pick(o, item.minimumSize()) + << pick(o, item.maximumSize()); + } + } else if (item.placeHolderItem != 0) { + stream << (uchar) WidgetMarker; + stream << item.placeHolderItem->objectName; + uchar flags = 0; + if (!item.placeHolderItem->hidden) + flags |= StateFlagVisible; + if (item.placeHolderItem->window) + flags |= StateFlagFloating; + stream << flags; + if (item.placeHolderItem->window) { + QRect r = item.placeHolderItem->topLevelRect; + stream << r.x() << r.y() << r.width() << r.height(); + } else { + stream << item.pos << item.size << (int)0 << (int)0; + } + } else if (item.subinfo != 0) { + stream << (uchar) SequenceMarker << item.pos << item.size << pick(o, item.minimumSize()) << pick(o, item.maximumSize()); + item.subinfo->saveState(stream); + } + } +} + +#ifdef Q_WS_MAC +static Qt::DockWidgetArea toDockWidgetArea(QInternal::DockPosition pos) +{ + switch (pos) { + case QInternal::LeftDock: return Qt::LeftDockWidgetArea; + case QInternal::RightDock: return Qt::RightDockWidgetArea; + case QInternal::TopDock: return Qt::TopDockWidgetArea; + case QInternal::BottomDock: return Qt::BottomDockWidgetArea; + default: break; + } + return Qt::NoDockWidgetArea; +} +#endif + +static QRect constrainedRect(QRect rect, const QRect &desktop) +{ + if (desktop.isValid()) { + rect.setWidth(qMin(rect.width(), desktop.width())); + rect.setHeight(qMin(rect.height(), desktop.height())); + rect.moveLeft(qMax(rect.left(), desktop.left())); + rect.moveTop(qMax(rect.top(), desktop.top())); + rect.moveRight(qMin(rect.right(), desktop.right())); + rect.moveBottom(qMin(rect.bottom(), desktop.bottom())); + } + + return rect; +} + +bool QDockAreaLayoutInfo::restoreState(QDataStream &stream, QList<QDockWidget*> &widgets, bool testing) +{ + uchar marker; + stream >> marker; + if (marker != TabMarker && marker != SequenceMarker) + return false; + +#ifndef QT_NO_TABBAR + tabbed = marker == TabMarker; + + int index = -1; + if (tabbed) + stream >> index; +#endif + + uchar orientation; + stream >> orientation; + o = static_cast<Qt::Orientation>(orientation); + + int cnt; + stream >> cnt; + + for (int i = 0; i < cnt; ++i) { + uchar nextMarker; + stream >> nextMarker; + if (nextMarker == WidgetMarker) { + QString name; + uchar flags; + stream >> name >> flags; + if (name.isEmpty()) { + int dummy; + stream >> dummy >> dummy >> dummy >> dummy; + continue; + } + + QDockWidget *widget = 0; + for (int j = 0; j < widgets.count(); ++j) { + if (widgets.at(j)->objectName() == name) { + widget = widgets.takeAt(j); + break; + } + } + + if (widget == 0) { + QPlaceHolderItem *placeHolder = new QPlaceHolderItem; + QDockAreaLayoutItem item(placeHolder); + + placeHolder->objectName = name; + placeHolder->window = flags & StateFlagFloating; + placeHolder->hidden = !(flags & StateFlagVisible); + if (placeHolder->window) { + int x, y, w, h; + stream >> x >> y >> w >> h; + placeHolder->topLevelRect = QRect(x, y, w, h); + } else { + int dummy; + stream >> item.pos >> item.size >> dummy >> dummy; + } + if (item.size != -1) + item.flags |= QDockAreaLayoutItem::KeepSize; + if (!testing) + item_list.append(item); + } else { + QDockAreaLayoutItem item(new QDockWidgetItem(widget)); + if (flags & StateFlagFloating) { + bool drawer = false; +#ifdef Q_WS_MAC // drawer support + extern bool qt_mac_is_macdrawer(const QWidget *); //qwidget_mac.cpp + extern bool qt_mac_set_drawer_preferred_edge(QWidget *, Qt::DockWidgetArea); //qwidget_mac.cpp + drawer = qt_mac_is_macdrawer(widget); +#endif + + if (!testing) { + widget->hide(); + if (!drawer) + widget->setFloating(true); + } + + int x, y, w, h; + stream >> x >> y >> w >> h; + +#ifdef Q_WS_MAC // drawer support + if (drawer) { + mainWindow->window()->createWinId(); + widget->window()->createWinId(); + qt_mac_set_drawer_preferred_edge(widget, toDockWidgetArea(dockPos)); + } else +#endif + if (!testing) { + QRect r(x, y, w, h); + QDesktopWidget *desktop = QApplication::desktop(); + if (desktop->isVirtualDesktop()) + r = constrainedRect(r, desktop->screenGeometry(desktop->screenNumber(r.topLeft()))); + else + r = constrainedRect(r, desktop->screenGeometry(widget)); + widget->move(r.topLeft()); + widget->resize(r.size()); + } + + if (!testing) { + widget->setVisible(flags & StateFlagVisible); + } + } else { + int dummy; + stream >> item.pos >> item.size >> dummy >> dummy; + // qDebug() << widget << item.pos << item.size; + if (!testing) { + widget->setFloating(false); + widget->setVisible(flags & StateFlagVisible); + } + } + + if (!testing) { + item_list.append(item); + } + } + } else if (nextMarker == SequenceMarker) { + int dummy; +#ifdef QT_NO_TABBAR + const int tabBarShape = 0; +#endif + QDockAreaLayoutInfo *info = new QDockAreaLayoutInfo(sep, dockPos, o, + tabBarShape, mainWindow); + QDockAreaLayoutItem item(info); + stream >> item.pos >> item.size >> dummy >> dummy; + if (!info->restoreState(stream, widgets, testing)) + return false; + + if (!testing) { + item_list.append(item); + } + } else { + return false; + } + } + +#ifndef QT_NO_TABBAR + if (!testing && tabbed && index >= 0 && index < item_list.count()) { + updateTabBar(); + setCurrentTabId(tabId(item_list.at(index))); + } +#endif + if (!testing && sep == 1) + updateSeparatorWidgets(); + + return true; +} + +void QDockAreaLayoutInfo::updateSeparatorWidgets() const +{ + QDockAreaLayoutInfo *that = const_cast<QDockAreaLayoutInfo*>(this); + + if (tabbed) { + that->separatorWidgets.clear(); + return; + } + + int j = 0; + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + + if (item.skip()) + continue; + + int next = this->next(i); + if ((item.flags & QDockAreaLayoutItem::GapItem) + || (next != -1 && (item_list.at(next).flags & QDockAreaLayoutItem::GapItem))) + continue; + + if (item.subinfo) { + item.subinfo->updateSeparatorWidgets(); + } + + if (next == -1) + break; + + QWidget *sepWidget; + if (j < separatorWidgets.size() && separatorWidgets.at(j)) { + sepWidget = separatorWidgets.at(j); + } else { + sepWidget = mainWindowLayout()->getSeparatorWidget(); + that->separatorWidgets.append(sepWidget); + } + j++; + +#ifndef QT_MAC_USE_COCOA + sepWidget->raise(); +#endif + QRect sepRect = separatorRect(i).adjusted(-2, -2, 2, 2); + sepWidget->setGeometry(sepRect); + sepWidget->setMask( QRegion(separatorRect(i).translated( - sepRect.topLeft()))); + sepWidget->show(); + } + + for (int k = j; k < that->separatorWidgets.size(); ++k) { + that->separatorWidgets[k]->hide(); + } + that->separatorWidgets.resize(j); + Q_ASSERT(separatorWidgets.size() == j); +} + +#ifndef QT_NO_TABBAR +void QDockAreaLayoutInfo::updateTabBar() const +{ + if (!tabbed) + return; + + QDockAreaLayoutInfo *that = const_cast<QDockAreaLayoutInfo*>(this); + + if (tabBar == 0) { + that->tabBar = mainWindowLayout()->getTabBar(); + that->tabBar->setShape(static_cast<QTabBar::Shape>(tabBarShape)); + that->tabBar->setDrawBase(true); + } + + bool blocked = tabBar->blockSignals(true); + bool gap = false; + + int tab_idx = 0; + bool changed = false; + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.skip()) + continue; + if (item.flags & QDockAreaLayoutItem::GapItem) { + gap = true; + continue; + } + if (item.widgetItem == 0) + continue; + + QDockWidget *dw = qobject_cast<QDockWidget*>(item.widgetItem->widget()); + QString title = dw->d_func()->fixedWindowTitle; + quintptr id = tabId(item); + if (tab_idx == tabBar->count()) { + tabBar->insertTab(tab_idx, title); +#ifndef QT_NO_TOOLTIP + tabBar->setTabToolTip(tab_idx, title); +#endif + tabBar->setTabData(tab_idx, id); + changed = true; + } else if (qvariant_cast<quintptr>(tabBar->tabData(tab_idx)) != id) { + if (tab_idx + 1 < tabBar->count() + && qvariant_cast<quintptr>(tabBar->tabData(tab_idx + 1)) == id) + tabBar->removeTab(tab_idx); + else { + tabBar->insertTab(tab_idx, title); +#ifndef QT_NO_TOOLTIP + tabBar->setTabToolTip(tab_idx, title); +#endif + tabBar->setTabData(tab_idx, id); + } + changed = true; + } + + if (title != tabBar->tabText(tab_idx)) { + tabBar->setTabText(tab_idx, title); +#ifndef QT_NO_TOOLTIP + tabBar->setTabToolTip(tab_idx, title); +#endif + changed = true; + } + + ++tab_idx; + } + + while (tab_idx < tabBar->count()) { + tabBar->removeTab(tab_idx); + changed = true; + } + + tabBar->blockSignals(blocked); + + that->tabBarVisible = ( (gap ? 1 : 0) + tabBar->count()) > 1; + + if (changed || !tabBarMin.isValid() | !tabBarHint.isValid()) { + that->tabBarMin = tabBar->minimumSizeHint(); + that->tabBarHint = tabBar->sizeHint(); + } +} + +void QDockAreaLayoutInfo::setTabBarShape(int shape) +{ + if (shape == tabBarShape) + return; + tabBarShape = shape; + if (tabBar != 0) { + tabBar->setShape(static_cast<QTabBar::Shape>(shape)); + tabBarMin = QSize(); + tabBarHint = QSize(); + } + + for (int i = 0; i < item_list.count(); ++i) { + QDockAreaLayoutItem &item = item_list[i]; + if (item.subinfo != 0) + item.subinfo->setTabBarShape(shape); + } +} + +QSize QDockAreaLayoutInfo::tabBarMinimumSize() const +{ + if (!tabbed) + return QSize(0, 0); + + updateTabBar(); + + return tabBarMin; +} + +QSize QDockAreaLayoutInfo::tabBarSizeHint() const +{ + if (!tabbed) + return QSize(0, 0); + + updateTabBar(); + + return tabBarHint; +} + +QSet<QTabBar*> QDockAreaLayoutInfo::usedTabBars() const +{ + QSet<QTabBar*> result; + + if (tabbed) { + updateTabBar(); + result.insert(tabBar); + } + + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.subinfo != 0) + result += item.subinfo->usedTabBars(); + } + + return result; +} + +// returns a set of all used separator widgets for this dockarelayout info +// and all subinfos +QSet<QWidget*> QDockAreaLayoutInfo::usedSeparatorWidgets() const +{ + QSet<QWidget*> result; + + foreach (QWidget *sepWidget, separatorWidgets) + result << sepWidget; + + for (int i = 0; i < item_list.count(); ++i) { + const QDockAreaLayoutItem &item = item_list.at(i); + if (item.subinfo != 0) + result += item.subinfo->usedSeparatorWidgets(); + } + + return result; +} + +QRect QDockAreaLayoutInfo::tabContentRect() const +{ + if (!tabbed) + return QRect(); + + QRect result = rect; + QSize tbh = tabBarSizeHint(); + + if (tabBarVisible) { + switch (tabBarShape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + result.adjust(0, tbh.height(), 0, 0); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + result.adjust(0, 0, 0, -tbh.height()); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + result.adjust(0, 0, -tbh.width(), 0); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + result.adjust(tbh.width(), 0, 0, 0); + break; + default: + break; + } + } + + return result; +} +#endif // QT_NO_TABBAR + +/****************************************************************************** +** QDockAreaLayout +*/ + +QDockAreaLayout::QDockAreaLayout(QMainWindow *win) +{ + mainWindow = win; + sep = win->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, 0, win); +#ifndef QT_NO_TABBAR + const int tabShape = QTabBar::RoundedSouth; +#else + const int tabShape = 0; +#endif + docks[QInternal::LeftDock] + = QDockAreaLayoutInfo(sep, QInternal::LeftDock, Qt::Vertical, tabShape, win); + docks[QInternal::RightDock] + = QDockAreaLayoutInfo(sep, QInternal::RightDock, Qt::Vertical, tabShape, win); + docks[QInternal::TopDock] + = QDockAreaLayoutInfo(sep, QInternal::TopDock, Qt::Horizontal, tabShape, win); + docks[QInternal::BottomDock] + = QDockAreaLayoutInfo(sep, QInternal::BottomDock, Qt::Horizontal, tabShape, win); + centralWidgetItem = 0; + centralWidgetRect = QRect(0, 0, -1, -1); + + corners[Qt::TopLeftCorner] = Qt::TopDockWidgetArea; + corners[Qt::TopRightCorner] = Qt::TopDockWidgetArea; + corners[Qt::BottomLeftCorner] = Qt::BottomDockWidgetArea; + corners[Qt::BottomRightCorner] = Qt::BottomDockWidgetArea; +} + +bool QDockAreaLayout::isValid() const +{ + return rect.isValid(); +} + +void QDockAreaLayout::saveState(QDataStream &stream) const +{ + stream << (uchar) DockWidgetStateMarker; + int cnt = 0; + for (int i = 0; i < QInternal::DockCount; ++i) { + if (!docks[i].item_list.isEmpty()) + ++cnt; + } + stream << cnt; + for (int i = 0; i < QInternal::DockCount; ++i) { + if (docks[i].item_list.isEmpty()) + continue; + stream << i << docks[i].rect.size(); + docks[i].saveState(stream); + } + + stream << centralWidgetRect.size(); + + for (int i = 0; i < 4; ++i) + stream << static_cast<int>(corners[i]); +} + +bool QDockAreaLayout::restoreState(QDataStream &stream, const QList<QDockWidget*> &_dockwidgets, bool testing) +{ + QList<QDockWidget*> dockwidgets = _dockwidgets; + + int cnt; + stream >> cnt; + for (int i = 0; i < cnt; ++i) { + int pos; + stream >> pos; + QSize size; + stream >> size; + if (!testing) { + docks[pos].rect = QRect(QPoint(0, 0), size); + } + if (!docks[pos].restoreState(stream, dockwidgets, testing)) { + stream.setStatus(QDataStream::ReadCorruptData); + return false; + } + } + + QSize size; + stream >> size; + centralWidgetRect = QRect(QPoint(0, 0), size); + + bool ok = stream.status() == QDataStream::Ok; + + if (ok) { + int cornerData[4]; + for (int i = 0; i < 4; ++i) + stream >> cornerData[i]; + if (stream.status() == QDataStream::Ok) { + for (int i = 0; i < 4; ++i) + corners[i] = static_cast<Qt::DockWidgetArea>(cornerData[i]); + } + } + + return ok; +} + +QList<int> QDockAreaLayout::indexOfPlaceHolder(const QString &objectName) const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + QList<int> result = docks[i].indexOfPlaceHolder(objectName); + if (!result.isEmpty()) { + result.prepend(i); + return result; + } + } + return QList<int>(); +} + +QList<int> QDockAreaLayout::indexOf(QWidget *dockWidget) const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + QList<int> result = docks[i].indexOf(dockWidget); + if (!result.isEmpty()) { + result.prepend(i); + return result; + } + } + return QList<int>(); +} + +QList<int> QDockAreaLayout::gapIndex(const QPoint &pos) const +{ + QMainWindow::DockOptions opts = mainWindow->dockOptions(); + bool nestingEnabled = opts & QMainWindow::AllowNestedDocks; + QDockAreaLayoutInfo::TabMode tabMode = QDockAreaLayoutInfo::NoTabs; +#ifndef QT_NO_TABBAR + if (opts & QMainWindow::AllowTabbedDocks + || opts & QMainWindow::VerticalTabs) + tabMode = QDockAreaLayoutInfo::AllowTabs; + if (opts & QMainWindow::ForceTabbedDocks) + tabMode = QDockAreaLayoutInfo::ForceTabs; + + if (tabMode == QDockAreaLayoutInfo::ForceTabs) + nestingEnabled = false; +#endif + + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &info = docks[i]; + + if (!info.isEmpty() && info.rect.contains(pos)) { + QList<int> result + = docks[i].gapIndex(pos, nestingEnabled, tabMode); + if (!result.isEmpty()) + result.prepend(i); + return result; + } + } + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &info = docks[i]; + + if (info.isEmpty()) { + QRect r; + switch (i) { + case QInternal::LeftDock: + r = QRect(rect.left(), rect.top(), EmptyDropAreaSize, rect.height()); + break; + case QInternal::RightDock: + r = QRect(rect.right() - EmptyDropAreaSize, rect.top(), + EmptyDropAreaSize, rect.height()); + break; + case QInternal::TopDock: + r = QRect(rect.left(), rect.top(), rect.width(), EmptyDropAreaSize); + break; + case QInternal::BottomDock: + r = QRect(rect.left(), rect.bottom() - EmptyDropAreaSize, + rect.width(), EmptyDropAreaSize); + break; + } + if (r.contains(pos)) { + if (opts & QMainWindow::ForceTabbedDocks && !info.item_list.isEmpty()) { + //in case of ForceTabbedDocks, we pass -1 in order to force the gap to be tabbed + //it mustn't be completely empty otherwise it won't work + return QList<int>() << i << -1 << 0; + } else { + return QList<int>() << i << 0; + } + } + } + } + + return QList<int>(); +} + +QList<int> QDockAreaLayout::findSeparator(const QPoint &pos) const +{ + QList<int> result; + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &info = docks[i]; + if (info.isEmpty()) + continue; + QRect rect = separatorRect(i); + if (sep == 1) + rect.adjust(-2, -2, 2, 2); + if (rect.contains(pos)) { + result << i; + break; + } else if (info.rect.contains(pos)) { + result = docks[i].findSeparator(pos); + if (!result.isEmpty()) { + result.prepend(i); + break; + } + } + } + + return result; +} + +QDockAreaLayoutInfo *QDockAreaLayout::info(QWidget *widget) +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + if (QDockAreaLayoutInfo *result = docks[i].info(widget)) + return result; + } + + return 0; +} + +QDockAreaLayoutInfo *QDockAreaLayout::info(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + + if (path.isEmpty()) + return &docks[index]; + + return docks[index].info(path); +} + +const QDockAreaLayoutInfo *QDockAreaLayout::info(QList<int> path) const +{ + return const_cast<QDockAreaLayout*>(this)->info(path); +} + +QDockAreaLayoutItem &QDockAreaLayout::item(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + return docks[index].item(path); +} + +QRect QDockAreaLayout::itemRect(QList<int> path) const +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + return docks[index].itemRect(path); +} + +QRect QDockAreaLayout::separatorRect(int index) const +{ + if (docks[index].isEmpty()) + return QRect(); + QRect r = docks[index].rect; + switch (index) { + case QInternal::LeftDock: + return QRect(r.right() + 1, r.top(), sep, r.height()); + case QInternal::RightDock: + return QRect(r.left() - sep, r.top(), sep, r.height()); + case QInternal::TopDock: + return QRect(r.left(), r.bottom() + 1, r.width(), sep); + case QInternal::BottomDock: + return QRect(r.left(), r.top() - sep, r.width(), sep); + default: + break; + } + return QRect(); +} + +QRect QDockAreaLayout::separatorRect(QList<int> path) const +{ + Q_ASSERT(!path.isEmpty()); + + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + + if (path.isEmpty()) + return separatorRect(index); + else + return docks[index].separatorRect(path); +} + +bool QDockAreaLayout::insertGap(QList<int> path, QLayoutItem *dockWidgetItem) +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + return docks[index].insertGap(path, dockWidgetItem); +} + +QLayoutItem *QDockAreaLayout::plug(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + return docks[index].plug(path); +} + +QLayoutItem *QDockAreaLayout::unplug(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + return docks[index].unplug(path); +} + +void QDockAreaLayout::remove(QList<int> path) +{ + Q_ASSERT(!path.isEmpty()); + int index = path.takeFirst(); + Q_ASSERT(index >= 0 && index < QInternal::DockCount); + docks[index].remove(path); +} + +static inline int qMin(int i1, int i2, int i3) { return qMin(i1, qMin(i2, i3)); } +static inline int qMax(int i1, int i2, int i3) { return qMax(i1, qMax(i2, i3)); } + +void QDockAreaLayout::getGrid(QVector<QLayoutStruct> *_ver_struct_list, + QVector<QLayoutStruct> *_hor_struct_list) +{ + QSize center_hint(0, 0); + QSize center_min(0, 0); + bool have_central = centralWidgetItem != 0 && !centralWidgetItem->isEmpty(); + if (have_central) { + center_hint = centralWidgetRect.size(); + if (!center_hint.isValid()) + center_hint = centralWidgetItem->sizeHint(); + center_min = centralWidgetItem->minimumSize(); + } + + QRect center_rect = rect; + if (!docks[QInternal::LeftDock].isEmpty()) + center_rect.setLeft(rect.left() + docks[QInternal::LeftDock].rect.width() + sep); + if (!docks[QInternal::TopDock].isEmpty()) + center_rect.setTop(rect.top() + docks[QInternal::TopDock].rect.height() + sep); + if (!docks[QInternal::RightDock].isEmpty()) + center_rect.setRight(rect.right() - docks[QInternal::RightDock].rect.width() - sep); + if (!docks[QInternal::BottomDock].isEmpty()) + center_rect.setBottom(rect.bottom() - docks[QInternal::BottomDock].rect.height() - sep); + + QSize left_hint = docks[QInternal::LeftDock].size(); + if (!left_hint.isValid()) + left_hint = docks[QInternal::LeftDock].sizeHint(); + QSize left_min = docks[QInternal::LeftDock].minimumSize(); + QSize left_max = docks[QInternal::LeftDock].maximumSize(); + left_hint = left_hint.boundedTo(left_max).expandedTo(left_min); + + QSize right_hint = docks[QInternal::RightDock].size(); + if (!right_hint.isValid()) + right_hint = docks[QInternal::RightDock].sizeHint(); + QSize right_min = docks[QInternal::RightDock].minimumSize(); + QSize right_max = docks[QInternal::RightDock].maximumSize(); + right_hint = right_hint.boundedTo(right_max).expandedTo(right_min); + + QSize top_hint = docks[QInternal::TopDock].size(); + if (!top_hint.isValid()) + top_hint = docks[QInternal::TopDock].sizeHint(); + QSize top_min = docks[QInternal::TopDock].minimumSize(); + QSize top_max = docks[QInternal::TopDock].maximumSize(); + top_hint = top_hint.boundedTo(top_max).expandedTo(top_min); + + QSize bottom_hint = docks[QInternal::BottomDock].size(); + if (!bottom_hint.isValid()) + bottom_hint = docks[QInternal::BottomDock].sizeHint(); + QSize bottom_min = docks[QInternal::BottomDock].minimumSize(); + QSize bottom_max = docks[QInternal::BottomDock].maximumSize(); + bottom_hint = bottom_hint.boundedTo(bottom_max).expandedTo(bottom_min); + + if (_ver_struct_list != 0) { + QVector<QLayoutStruct> &ver_struct_list = *_ver_struct_list; + ver_struct_list.resize(3); + + // top -------------------------------------------------- + ver_struct_list[0].init(); + ver_struct_list[0].stretch = 0; + ver_struct_list[0].sizeHint = top_hint.height(); + ver_struct_list[0].minimumSize = top_min.height(); + ver_struct_list[0].maximumSize = top_max.height(); + ver_struct_list[0].expansive = false; + ver_struct_list[0].empty = docks[QInternal::TopDock].isEmpty(); + ver_struct_list[0].pos = docks[QInternal::TopDock].rect.top(); + ver_struct_list[0].size = docks[QInternal::TopDock].rect.height(); + + // center -------------------------------------------------- + ver_struct_list[1].init(); + ver_struct_list[1].stretch = center_hint.height(); + + bool tl_significant = corners[Qt::TopLeftCorner] == Qt::TopDockWidgetArea + || docks[QInternal::TopDock].isEmpty(); + bool bl_significant = corners[Qt::BottomLeftCorner] == Qt::BottomDockWidgetArea + || docks[QInternal::BottomDock].isEmpty(); + bool tr_significant = corners[Qt::TopRightCorner] == Qt::TopDockWidgetArea + || docks[QInternal::TopDock].isEmpty(); + bool br_significant = corners[Qt::BottomRightCorner] == Qt::BottomDockWidgetArea + || docks[QInternal::BottomDock].isEmpty(); + + int left = (tl_significant && bl_significant) ? left_hint.height() : 0; + int right = (tr_significant && br_significant) ? right_hint.height() : 0; + ver_struct_list[1].sizeHint = qMax(left, center_hint.height(), right); + + left = (tl_significant && bl_significant) ? left_min.height() : 0; + right = (tr_significant && br_significant) ? right_min.height() : 0; + ver_struct_list[1].minimumSize = qMax(left, center_min.height(), right); + ver_struct_list[1].maximumSize = have_central ? QWIDGETSIZE_MAX : 0; + ver_struct_list[1].expansive = have_central; + ver_struct_list[1].empty = docks[QInternal::LeftDock].isEmpty() + && !have_central + && docks[QInternal::RightDock].isEmpty(); + ver_struct_list[1].pos = center_rect.top(); + ver_struct_list[1].size = center_rect.height(); + + // bottom -------------------------------------------------- + ver_struct_list[2].init(); + ver_struct_list[2].stretch = 0; + ver_struct_list[2].sizeHint = bottom_hint.height(); + ver_struct_list[2].minimumSize = bottom_min.height(); + ver_struct_list[2].maximumSize = bottom_max.height(); + ver_struct_list[2].expansive = false; + ver_struct_list[2].empty = docks[QInternal::BottomDock].isEmpty(); + ver_struct_list[2].pos = docks[QInternal::BottomDock].rect.top(); + ver_struct_list[2].size = docks[QInternal::BottomDock].rect.height(); + + for (int i = 0; i < 3; ++i) { + ver_struct_list[i].sizeHint + = qMax(ver_struct_list[i].sizeHint, ver_struct_list[i].minimumSize); + } + } + + if (_hor_struct_list != 0) { + QVector<QLayoutStruct> &hor_struct_list = *_hor_struct_list; + hor_struct_list.resize(3); + + // left -------------------------------------------------- + hor_struct_list[0].init(); + hor_struct_list[0].stretch = 0; + hor_struct_list[0].sizeHint = left_hint.width(); + hor_struct_list[0].minimumSize = left_min.width(); + hor_struct_list[0].maximumSize = left_max.width(); + hor_struct_list[0].expansive = false; + hor_struct_list[0].empty = docks[QInternal::LeftDock].isEmpty(); + hor_struct_list[0].pos = docks[QInternal::LeftDock].rect.left(); + hor_struct_list[0].size = docks[QInternal::LeftDock].rect.width(); + + // center -------------------------------------------------- + hor_struct_list[1].init(); + hor_struct_list[1].stretch = center_hint.width(); + + bool tl_significant = corners[Qt::TopLeftCorner] == Qt::LeftDockWidgetArea + || docks[QInternal::LeftDock].isEmpty(); + bool tr_significant = corners[Qt::TopRightCorner] == Qt::RightDockWidgetArea + || docks[QInternal::RightDock].isEmpty(); + bool bl_significant = corners[Qt::BottomLeftCorner] == Qt::LeftDockWidgetArea + || docks[QInternal::LeftDock].isEmpty(); + bool br_significant = corners[Qt::BottomRightCorner] == Qt::RightDockWidgetArea + || docks[QInternal::RightDock].isEmpty(); + + int top = (tl_significant && tr_significant) ? top_hint.width() : 0; + int bottom = (bl_significant && br_significant) ? bottom_hint.width() : 0; + hor_struct_list[1].sizeHint = qMax(top, center_hint.width(), bottom); + + top = (tl_significant && tr_significant) ? top_min.width() : 0; + bottom = (bl_significant && br_significant) ? bottom_min.width() : 0; + hor_struct_list[1].minimumSize = qMax(top, center_min.width(), bottom); + + hor_struct_list[1].maximumSize = have_central ? QWIDGETSIZE_MAX : 0; + hor_struct_list[1].expansive = have_central; + hor_struct_list[1].empty = !have_central; + hor_struct_list[1].pos = center_rect.left(); + hor_struct_list[1].size = center_rect.width(); + + // right -------------------------------------------------- + hor_struct_list[2].init(); + hor_struct_list[2].stretch = 0; + hor_struct_list[2].sizeHint = right_hint.width(); + hor_struct_list[2].minimumSize = right_min.width(); + hor_struct_list[2].maximumSize = right_max.width(); + hor_struct_list[2].expansive = false; + hor_struct_list[2].empty = docks[QInternal::RightDock].isEmpty(); + hor_struct_list[2].pos = docks[QInternal::RightDock].rect.left(); + hor_struct_list[2].size = docks[QInternal::RightDock].rect.width(); + + for (int i = 0; i < 3; ++i) { + hor_struct_list[i].sizeHint + = qMax(hor_struct_list[i].sizeHint, hor_struct_list[i].minimumSize); + } + } +} + +void QDockAreaLayout::setGrid(QVector<QLayoutStruct> *ver_struct_list, + QVector<QLayoutStruct> *hor_struct_list) +{ + + // top --------------------------------------------------- + + if (!docks[QInternal::TopDock].isEmpty()) { + QRect r = docks[QInternal::TopDock].rect; + if (hor_struct_list != 0) { + r.setLeft(corners[Qt::TopLeftCorner] == Qt::TopDockWidgetArea + || docks[QInternal::LeftDock].isEmpty() + ? rect.left() : hor_struct_list->at(1).pos); + r.setRight(corners[Qt::TopRightCorner] == Qt::TopDockWidgetArea + || docks[QInternal::RightDock].isEmpty() + ? rect.right() : hor_struct_list->at(2).pos - sep - 1); + } + if (ver_struct_list != 0) { + r.setTop(rect.top()); + r.setBottom(ver_struct_list->at(1).pos - sep - 1); + } + docks[QInternal::TopDock].rect = r; + docks[QInternal::TopDock].fitItems(); + } + + // bottom --------------------------------------------------- + + if (!docks[QInternal::BottomDock].isEmpty()) { + QRect r = docks[QInternal::BottomDock].rect; + if (hor_struct_list != 0) { + r.setLeft(corners[Qt::BottomLeftCorner] == Qt::BottomDockWidgetArea + || docks[QInternal::LeftDock].isEmpty() + ? rect.left() : hor_struct_list->at(1).pos); + r.setRight(corners[Qt::BottomRightCorner] == Qt::BottomDockWidgetArea + || docks[QInternal::RightDock].isEmpty() + ? rect.right() : hor_struct_list->at(2).pos - sep - 1); + } + if (ver_struct_list != 0) { + r.setTop(ver_struct_list->at(2).pos); + r.setBottom(rect.bottom()); + } + docks[QInternal::BottomDock].rect = r; + docks[QInternal::BottomDock].fitItems(); + } + + // left --------------------------------------------------- + + if (!docks[QInternal::LeftDock].isEmpty()) { + QRect r = docks[QInternal::LeftDock].rect; + if (hor_struct_list != 0) { + r.setLeft(rect.left()); + r.setRight(hor_struct_list->at(1).pos - sep - 1); + } + if (ver_struct_list != 0) { + r.setTop(corners[Qt::TopLeftCorner] == Qt::LeftDockWidgetArea + || docks[QInternal::TopDock].isEmpty() + ? rect.top() : ver_struct_list->at(1).pos); + r.setBottom(corners[Qt::BottomLeftCorner] == Qt::LeftDockWidgetArea + || docks[QInternal::BottomDock].isEmpty() + ? rect.bottom() : ver_struct_list->at(2).pos - sep - 1); + } + docks[QInternal::LeftDock].rect = r; + docks[QInternal::LeftDock].fitItems(); + } + + // right --------------------------------------------------- + + if (!docks[QInternal::RightDock].isEmpty()) { + QRect r = docks[QInternal::RightDock].rect; + if (hor_struct_list != 0) { + r.setLeft(hor_struct_list->at(2).pos); + r.setRight(rect.right()); + } + if (ver_struct_list != 0) { + r.setTop(corners[Qt::TopRightCorner] == Qt::RightDockWidgetArea + || docks[QInternal::TopDock].isEmpty() + ? rect.top() : ver_struct_list->at(1).pos); + r.setBottom(corners[Qt::BottomRightCorner] == Qt::RightDockWidgetArea + || docks[QInternal::BottomDock].isEmpty() + ? rect.bottom() : ver_struct_list->at(2).pos - sep - 1); + } + docks[QInternal::RightDock].rect = r; + docks[QInternal::RightDock].fitItems(); + } + + // center --------------------------------------------------- + + if (hor_struct_list != 0) { + centralWidgetRect.setLeft(hor_struct_list->at(1).pos); + centralWidgetRect.setWidth(hor_struct_list->at(1).size); + } + if (ver_struct_list != 0) { + centralWidgetRect.setTop(ver_struct_list->at(1).pos); + centralWidgetRect.setHeight(ver_struct_list->at(1).size); + } +} + +void QDockAreaLayout::fitLayout() +{ + QVector<QLayoutStruct> ver_struct_list(3); + QVector<QLayoutStruct> hor_struct_list(3); + getGrid(&ver_struct_list, &hor_struct_list); + + qGeomCalc(ver_struct_list, 0, 3, rect.top(), rect.height(), sep); + qGeomCalc(hor_struct_list, 0, 3, rect.left(), rect.width(), sep); + + setGrid(&ver_struct_list, &hor_struct_list); +} + +void QDockAreaLayout::clear() +{ + for (int i = 0; i < QInternal::DockCount; ++i) + docks[i].clear(); + + rect = QRect(0, 0, -1, -1); + centralWidgetRect = QRect(0, 0, -1, -1); +} + +QSize QDockAreaLayout::sizeHint() const +{ + int left_sep = 0; + int right_sep = 0; + int top_sep = 0; + int bottom_sep = 0; + + if (centralWidgetItem != 0) { + left_sep = docks[QInternal::LeftDock].isEmpty() ? 0 : sep; + right_sep = docks[QInternal::RightDock].isEmpty() ? 0 : sep; + top_sep = docks[QInternal::TopDock].isEmpty() ? 0 : sep; + bottom_sep = docks[QInternal::BottomDock].isEmpty() ? 0 : sep; + } + + QSize left = docks[QInternal::LeftDock].sizeHint() + QSize(left_sep, 0); + QSize right = docks[QInternal::RightDock].sizeHint() + QSize(right_sep, 0); + QSize top = docks[QInternal::TopDock].sizeHint() + QSize(0, top_sep); + QSize bottom = docks[QInternal::BottomDock].sizeHint() + QSize(0, bottom_sep); + QSize center = centralWidgetItem == 0 ? QSize(0, 0) : centralWidgetItem->sizeHint(); + + int row1 = top.width(); + int row2 = left.width() + center.width() + right.width(); + int row3 = bottom.width(); + int col1 = left.height(); + int col2 = top.height() + center.height() + bottom.height(); + int col3 = right.height(); + + if (corners[Qt::TopLeftCorner] == Qt::LeftDockWidgetArea) + row1 += left.width(); + else + col1 += top.height(); + + if (corners[Qt::TopRightCorner] == Qt::RightDockWidgetArea) + row1 += right.width(); + else + col3 += top.height(); + + if (corners[Qt::BottomLeftCorner] == Qt::LeftDockWidgetArea) + row3 += left.width(); + else + col1 += bottom.height(); + + if (corners[Qt::BottomRightCorner] == Qt::RightDockWidgetArea) + row3 += right.width(); + else + col3 += bottom.height(); + + return QSize(qMax(row1, row2, row3), qMax(col1, col2, col3)); +} + +QSize QDockAreaLayout::minimumSize() const +{ + int left_sep = 0; + int right_sep = 0; + int top_sep = 0; + int bottom_sep = 0; + + if (centralWidgetItem != 0) { + left_sep = docks[QInternal::LeftDock].isEmpty() ? 0 : sep; + right_sep = docks[QInternal::RightDock].isEmpty() ? 0 : sep; + top_sep = docks[QInternal::TopDock].isEmpty() ? 0 : sep; + bottom_sep = docks[QInternal::BottomDock].isEmpty() ? 0 : sep; + } + + QSize left = docks[QInternal::LeftDock].minimumSize() + QSize(left_sep, 0); + QSize right = docks[QInternal::RightDock].minimumSize() + QSize(right_sep, 0); + QSize top = docks[QInternal::TopDock].minimumSize() + QSize(0, top_sep); + QSize bottom = docks[QInternal::BottomDock].minimumSize() + QSize(0, bottom_sep); + QSize center = centralWidgetItem == 0 ? QSize(0, 0) : centralWidgetItem->minimumSize(); + + int row1 = top.width(); + int row2 = left.width() + center.width() + right.width(); + int row3 = bottom.width(); + int col1 = left.height(); + int col2 = top.height() + center.height() + bottom.height(); + int col3 = right.height(); + + if (corners[Qt::TopLeftCorner] == Qt::LeftDockWidgetArea) + row1 += left.width(); + else + col1 += top.height(); + + if (corners[Qt::TopRightCorner] == Qt::RightDockWidgetArea) + row1 += right.width(); + else + col3 += top.height(); + + if (corners[Qt::BottomLeftCorner] == Qt::LeftDockWidgetArea) + row3 += left.width(); + else + col1 += bottom.height(); + + if (corners[Qt::BottomRightCorner] == Qt::RightDockWidgetArea) + row3 += right.width(); + else + col3 += bottom.height(); + + return QSize(qMax(row1, row2, row3), qMax(col1, col2, col3)); +} + +bool QDockAreaLayout::restoreDockWidget(QDockWidget *dockWidget) +{ + QList<int> index = indexOfPlaceHolder(dockWidget->objectName()); + if (index.isEmpty()) + return false; + + QDockAreaLayoutItem &item = this->item(index); + QPlaceHolderItem *placeHolder = item.placeHolderItem; + Q_ASSERT(placeHolder != 0); + + item.widgetItem = new QDockWidgetItem(dockWidget); + + if (placeHolder->window) { + QDesktopWidget desktop; + QRect r = constrainedRect(placeHolder->topLevelRect, desktop.screenGeometry(dockWidget)); + dockWidget->d_func()->setWindowState(true, true, r); + } + dockWidget->show(); +// dockWidget->setVisible(!placeHolder->hidden); +#ifdef Q_WS_X11 + if (placeHolder->window) // gets rid of the X11BypassWindowManager window flag + dockWidget->d_func()->setWindowState(true); +#endif + + item.placeHolderItem = 0; + delete placeHolder; + + return true; +} + +void QDockAreaLayout::addDockWidget(QInternal::DockPosition pos, QDockWidget *dockWidget, + Qt::Orientation orientation) +{ + QLayoutItem *dockWidgetItem = new QDockWidgetItem(dockWidget); + QDockAreaLayoutInfo &info = docks[pos]; + if (orientation == info.o || info.item_list.count() <= 1) { + // empty dock areas, or dock areas containing exactly one widget can have their orientation + // switched. + info.o = orientation; + + QDockAreaLayoutItem new_item(dockWidgetItem); + info.item_list.append(new_item); +#ifndef QT_NO_TABBAR + if (info.tabbed && !new_item.skip()) { + info.updateTabBar(); + info.setCurrentTabId(tabId(new_item)); + } +#endif + } else { +#ifndef QT_NO_TABBAR + int tbshape = info.tabBarShape; +#else + int tbshape = 0; +#endif + QDockAreaLayoutInfo new_info(sep, pos, orientation, tbshape, mainWindow); + new_info.item_list.append(new QDockAreaLayoutInfo(info)); + new_info.item_list.append(dockWidgetItem); + info = new_info; + } + + QList<int> index = indexOfPlaceHolder(dockWidget->objectName()); + if (!index.isEmpty()) + remove(index); +} + +void QDockAreaLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second) +{ + QList<int> path = indexOf(first); + if (path.isEmpty()) + return; + + QDockAreaLayoutInfo *info = this->info(path); + Q_ASSERT(info != 0); + info->tab(path.last(), new QDockWidgetItem(second)); + + QList<int> index = indexOfPlaceHolder(second->objectName()); + if (!index.isEmpty()) + remove(index); +} + +void QDockAreaLayout::splitDockWidget(QDockWidget *after, + QDockWidget *dockWidget, + Qt::Orientation orientation) +{ + QList<int> path = indexOf(after); + if (path.isEmpty()) + return; + + QDockAreaLayoutInfo *info = this->info(path); + Q_ASSERT(info != 0); + info->split(path.last(), orientation, new QDockWidgetItem(dockWidget)); + + QList<int> index = indexOfPlaceHolder(dockWidget->objectName()); + if (!index.isEmpty()) + remove(index); +} + +void QDockAreaLayout::apply(bool animate) +{ + QWidgetAnimator *widgetAnimator + = qobject_cast<QMainWindowLayout*>(mainWindow->layout())->widgetAnimator; + + for (int i = 0; i < QInternal::DockCount; ++i) + docks[i].apply(animate); + if (centralWidgetItem != 0 && !centralWidgetItem->isEmpty()) { + widgetAnimator->animate(centralWidgetItem->widget(), centralWidgetRect, + animate); + } + + if (sep == 1) + updateSeparatorWidgets(); +} + +void QDockAreaLayout::paintSeparators(QPainter *p, QWidget *widget, + const QRegion &clip, + const QPoint &mouse) const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &dock = docks[i]; + if (dock.isEmpty()) + continue; + QRect r = separatorRect(i); + if (clip.contains(r)) { + Qt::Orientation opposite = dock.o == Qt::Horizontal + ? Qt::Vertical : Qt::Horizontal; + paintSep(p, widget, r, opposite, r.contains(mouse)); + } + if (clip.contains(dock.rect)) + dock.paintSeparators(p, widget, clip, mouse); + } +} + +QRegion QDockAreaLayout::separatorRegion() const +{ + QRegion result; + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &dock = docks[i]; + if (dock.isEmpty()) + continue; + result |= separatorRect(i); + result |= dock.separatorRegion(); + } + + return result; +} + +int QDockAreaLayout::separatorMove(QList<int> separator, const QPoint &origin, + const QPoint &dest, + QVector<QLayoutStruct> *cache) +{ + int delta = 0; + int index = separator.last(); + + if (separator.count() > 1) { + QDockAreaLayoutInfo *info = this->info(separator); + delta = pick(info->o, dest - origin); + if (delta != 0) + delta = info->separatorMove(index, delta, cache); + info->apply(false); + return delta; + } + + if (cache->isEmpty()) { + QVector<QLayoutStruct> &list = *cache; + + if (index == QInternal::LeftDock || index == QInternal::RightDock) + getGrid(0, &list); + else + getGrid(&list, 0); + } + + QVector<QLayoutStruct> list = *cache; + int sep_index = index == QInternal::LeftDock || index == QInternal::TopDock + ? 0 : 1; + Qt::Orientation o = index == QInternal::LeftDock || index == QInternal::RightDock + ? Qt::Horizontal + : Qt::Vertical; + + delta = pick(o, dest - origin); + delta = separatorMoveHelper(list, sep_index, delta, sep); + + if (index == QInternal::LeftDock || index == QInternal::RightDock) + setGrid(0, &list); + else + setGrid(&list, 0); + + apply(false); + + return delta; +} + +// Sets the correct positions for the seperator widgets +// Allocates new sepearator widgets with getSeparatorWidget +void QDockAreaLayout::updateSeparatorWidgets() const +{ + QDockAreaLayout *that = const_cast<QDockAreaLayout*>(this); + + int j = 0; + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &dock = docks[i]; + if (dock.isEmpty()) + continue; + + QWidget *sepWidget; + if (j < separatorWidgets.size()) { + sepWidget = separatorWidgets.at(j); + } else { + sepWidget = qobject_cast<QMainWindowLayout*>(mainWindow->layout())->getSeparatorWidget(); + that->separatorWidgets.append(sepWidget); + } + j++; + +#ifndef QT_MAC_USE_COCOA + sepWidget->raise(); +#endif + QRect sepRect = separatorRect(i).adjusted(-2, -2, 2, 2); + sepWidget->setGeometry(sepRect); + sepWidget->setMask( QRegion(separatorRect(i).translated( - sepRect.topLeft()))); + sepWidget->show(); + } + for (int i = j; i < separatorWidgets.size(); ++i) + separatorWidgets.at(i)->hide(); + + that->separatorWidgets.resize(j); +} + +QLayoutItem *QDockAreaLayout::itemAt(int *x, int index) const +{ + Q_ASSERT(x != 0); + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &dock = docks[i]; + if (QLayoutItem *ret = dock.itemAt(x, index)) + return ret; + } + + if (centralWidgetItem && (*x)++ == index) + return centralWidgetItem; + + return 0; +} + +QLayoutItem *QDockAreaLayout::takeAt(int *x, int index) +{ + Q_ASSERT(x != 0); + + for (int i = 0; i < QInternal::DockCount; ++i) { + QDockAreaLayoutInfo &dock = docks[i]; + if (QLayoutItem *ret = dock.takeAt(x, index)) + return ret; + } + + if (centralWidgetItem && (*x)++ == index) { + QLayoutItem *ret = centralWidgetItem; + centralWidgetItem = 0; + return ret; + } + + return 0; +} + +void QDockAreaLayout::deleteAllLayoutItems() +{ + for (int i = 0; i < QInternal::DockCount; ++i) + docks[i].deleteAllLayoutItems(); +} + +#ifndef QT_NO_TABBAR +QSet<QTabBar*> QDockAreaLayout::usedTabBars() const +{ + QSet<QTabBar*> result; + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &dock = docks[i]; + result += dock.usedTabBars(); + } + return result; +} +#endif + +// Returns the set of all used separator widgets +QSet<QWidget*> QDockAreaLayout::usedSeparatorWidgets() const +{ + QSet<QWidget*> result; + + foreach (QWidget *sepWidget, separatorWidgets) + result << sepWidget; + for (int i = 0; i < QInternal::DockCount; ++i) { + const QDockAreaLayoutInfo &dock = docks[i]; + result += dock.usedSeparatorWidgets(); + } + return result; +} + +QRect QDockAreaLayout::gapRect(QList<int> path) const +{ + const QDockAreaLayoutInfo *info = this->info(path); + if (info == 0) + return QRect(); + const QList<QDockAreaLayoutItem> &item_list = info->item_list; + Qt::Orientation o = info->o; + int index = path.last(); + if (index < 0 || index >= item_list.count()) + return QRect(); + const QDockAreaLayoutItem &item = item_list.at(index); + if (!(item.flags & QDockAreaLayoutItem::GapItem)) + return QRect(); + + QRect result; + +#ifndef QT_NO_TABBAR + if (info->tabbed) { + result = info->tabContentRect(); + } else +#endif + { + int pos = item.pos; + int size = item.size; + + int prev = info->prev(index); + int next = info->next(index); + + if (prev != -1 && !(item_list.at(prev).flags & QDockAreaLayoutItem::GapItem)) { + pos += sep; + size -= sep; + } + if (next != -1 && !(item_list.at(next).flags & QDockAreaLayoutItem::GapItem)) + size -= sep; + + QPoint p; + rpick(o, p) = pos; + rperp(o, p) = perp(o, info->rect.topLeft()); + QSize s; + rpick(o, s) = size; + rperp(o, s) = perp(o, info->rect.size()); + + result = QRect(p, s); + } + + return result; +} + +void QDockAreaLayout::keepSize(QDockWidget *w) +{ + QList<int> path = indexOf(w); + if (path.isEmpty()) + return; + QDockAreaLayoutItem &item = this->item(path); + if (item.size != -1) + item.flags |= QDockAreaLayoutItem::KeepSize; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DOCKWIDGET diff --git a/src/gui/widgets/qdockarealayout_p.h b/src/gui/widgets/qdockarealayout_p.h new file mode 100644 index 0000000..7c5bd18 --- /dev/null +++ b/src/gui/widgets/qdockarealayout_p.h @@ -0,0 +1,303 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDOCKAREALAYOUT_P_H +#define QDOCKAREALAYOUT_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 "QtCore/qrect.h" +#include "QtCore/qpair.h" +#include "QtCore/qlist.h" +#include "QtCore/qvector.h" +#include "QtGui/qlayout.h" + +#ifndef QT_NO_DOCKWIDGET + +QT_BEGIN_NAMESPACE + +class QLayoutItem; +class QWidget; +class QLayoutItem; +class QDockAreaLayoutInfo; +class QPlaceHolderItem; +class QDockWidget; +class QMainWindow; +class QWidgetAnimator; +class QMainWindowLayout; +struct QLayoutStruct; +class QTabBar; + +// The classes in this file represent the tree structure that represents all the docks +// Also see the wiki internal documentation +// At the root of the tree is: QDockAreaLayout, which handles all 4 sides, so there is only one. +// For each side it has one QDockAreaLayoutInfo child. (See QDockAreaLayout::docks.) +// The QDockAreaLayoutInfo have QDockAreaLayoutItems as children (See QDockAreaLayoutInfo::item_list), +// which then has one QDockAreaLayoutInfo as a child. (QDockAreaLayoutItem::subInfo) or +// a widgetItem if this is a node of the tree (QDockAreaLayoutItem::widgetItem) +// +// A path indetifies uniquely one object in this tree, the first number beeing the side and all the following +// indexes into the QDockAreaLayoutInfo::item_list. + +struct QDockAreaLayoutItem +{ + enum ItemFlags { NoFlags = 0, GapItem = 1, KeepSize = 2 }; + + QDockAreaLayoutItem(QLayoutItem *_widgetItem = 0); + QDockAreaLayoutItem(QDockAreaLayoutInfo *_subinfo); + QDockAreaLayoutItem(QPlaceHolderItem *_placeHolderItem); + QDockAreaLayoutItem(const QDockAreaLayoutItem &other); + ~QDockAreaLayoutItem(); + + QDockAreaLayoutItem &operator = (const QDockAreaLayoutItem &other); + + bool skip() const; + QSize minimumSize() const; + QSize maximumSize() const; + QSize sizeHint() const; + bool expansive(Qt::Orientation o) const; + + QLayoutItem *widgetItem; + QDockAreaLayoutInfo *subinfo; + QPlaceHolderItem *placeHolderItem; + int pos; + int size; + uint flags; +}; + +class Q_AUTOTEST_EXPORT QPlaceHolderItem +{ +public: + QPlaceHolderItem() : hidden(false), window(false) {} + QPlaceHolderItem(QWidget *w); + + QString objectName; + bool hidden, window; + QRect topLevelRect; +}; + +class Q_AUTOTEST_EXPORT QDockAreaLayoutInfo +{ +public: + QDockAreaLayoutInfo(); + QDockAreaLayoutInfo(int _sep, QInternal::DockPosition _dockPos, Qt::Orientation _o, + int tbhape, QMainWindow *window); + + QSize minimumSize() const; + QSize maximumSize() const; + QSize sizeHint() const; + QSize size() const; + + bool insertGap(QList<int> path, QLayoutItem *dockWidgetItem); + QLayoutItem *plug(QList<int> path); + QLayoutItem *unplug(QList<int> path); + enum TabMode { NoTabs, AllowTabs, ForceTabs }; + QList<int> gapIndex(const QPoint &pos, bool nestingEnabled, + TabMode tabMode) const; + void remove(QList<int> path); + void unnest(int index); + void split(int index, Qt::Orientation orientation, QLayoutItem *dockWidgetItem); + void tab(int index, QLayoutItem *dockWidgetItem); + QDockAreaLayoutItem &item(QList<int> path); + QDockAreaLayoutInfo *info(QList<int> path); + QDockAreaLayoutInfo *info(QWidget *widget); + + enum { // sentinel values used to validate state data + SequenceMarker = 0xfc, + TabMarker = 0xfa, + WidgetMarker = 0xfb + }; + void saveState(QDataStream &stream) const; + bool restoreState(QDataStream &stream, QList<QDockWidget*> &widgets, bool testing); + + void fitItems(); + bool expansive(Qt::Orientation o) const; + int changeSize(int index, int size, bool below); + QRect itemRect(int index) const; + QRect itemRect(QList<int> path) const; + QRect separatorRect(int index) const; + QRect separatorRect(QList<int> path) const; + + void clear(); + bool isEmpty() const; + QList<int> findSeparator(const QPoint &pos) const; + int next(int idx) const; + int prev(int idx) const; + + QList<int> indexOf(QWidget *widget) const; + QList<int> indexOfPlaceHolder(const QString &objectName) const; + + void apply(bool animate); + + void paintSeparators(QPainter *p, QWidget *widget, const QRegion &clip, + const QPoint &mouse) const; + QRegion separatorRegion() const; + int separatorMove(int index, int delta, QVector<QLayoutStruct> *cache); + + QLayoutItem *itemAt(int *x, int index) const; + QLayoutItem *takeAt(int *x, int index); + void deleteAllLayoutItems(); + + QMainWindowLayout *mainWindowLayout() const; + + int sep; + QVector<QWidget*> separatorWidgets; + QInternal::DockPosition dockPos; + Qt::Orientation o; + QRect rect; + QMainWindow *mainWindow; + QList<QDockAreaLayoutItem> item_list; + + void updateSeparatorWidgets() const; + QSet<QWidget*> usedSeparatorWidgets() const; + +#ifndef QT_NO_TABBAR + quintptr currentTabId() const; + void setCurrentTab(QWidget *widget); + void setCurrentTabId(quintptr id); + QRect tabContentRect() const; + bool tabbed; + QTabBar *tabBar; + QSize tabBarMin, tabBarHint; + int tabBarShape; + bool tabBarVisible; + + void updateTabBar() const; + void setTabBarShape(int shape); + QSize tabBarMinimumSize() const; + QSize tabBarSizeHint() const; + + QSet<QTabBar*> usedTabBars() const; +#endif // QT_NO_TABBAR +}; + +class Q_AUTOTEST_EXPORT QDockAreaLayout +{ +public: + enum { EmptyDropAreaSize = 80 }; // when a dock area is empty, how "wide" is it? + + Qt::DockWidgetArea corners[4]; // use a Qt::Corner for indexing + QRect rect; + QLayoutItem *centralWidgetItem; + QMainWindow *mainWindow; + QRect centralWidgetRect; + QDockAreaLayout(QMainWindow *win); + QDockAreaLayoutInfo docks[4]; + int sep; // separator extent + QVector<QWidget*> separatorWidgets; + + bool isValid() const; + + enum { DockWidgetStateMarker = 0xfd }; + void saveState(QDataStream &stream) const; + bool restoreState(QDataStream &stream, const QList<QDockWidget*> &widgets, bool testing = false); + + QList<int> indexOfPlaceHolder(const QString &objectName) const; + QList<int> indexOf(QWidget *dockWidget) const; + QList<int> gapIndex(const QPoint &pos) const; + QList<int> findSeparator(const QPoint &pos) const; + + QDockAreaLayoutItem &item(QList<int> path); + QDockAreaLayoutInfo *info(QList<int> path); + const QDockAreaLayoutInfo *info(QList<int> path) const; + QDockAreaLayoutInfo *info(QWidget *widget); + QRect itemRect(QList<int> path) const; + QRect separatorRect(int index) const; + QRect separatorRect(QList<int> path) const; + + bool insertGap(QList<int> path, QLayoutItem *dockWidgetItem); + QLayoutItem *plug(QList<int> path); + QLayoutItem *unplug(QList<int> path); + void remove(QList<int> path); + + void fitLayout(); + + void clear(); + + QSize sizeHint() const; + QSize minimumSize() const; + + void addDockWidget(QInternal::DockPosition pos, QDockWidget *dockWidget, Qt::Orientation orientation); + bool restoreDockWidget(QDockWidget *dockWidget); + void splitDockWidget(QDockWidget *after, QDockWidget *dockWidget, + Qt::Orientation orientation); + void tabifyDockWidget(QDockWidget *first, QDockWidget *second); + + void apply(bool animate); + + void paintSeparators(QPainter *p, QWidget *widget, const QRegion &clip, + const QPoint &mouse) const; + QRegion separatorRegion() const; + int separatorMove(QList<int> separator, const QPoint &origin, const QPoint &dest, + QVector<QLayoutStruct> *cache); + void updateSeparatorWidgets() const; + + QLayoutItem *itemAt(int *x, int index) const; + QLayoutItem *takeAt(int *x, int index); + void deleteAllLayoutItems(); + + void getGrid(QVector<QLayoutStruct> *ver_struct_list, + QVector<QLayoutStruct> *hor_struct_list); + void setGrid(QVector<QLayoutStruct> *ver_struct_list, + QVector<QLayoutStruct> *hor_struct_list); + + QRect gapRect(QList<int> path) const; + + void keepSize(QDockWidget *w); + + QSet<QTabBar*> usedTabBars() const; + QSet<QWidget*> usedSeparatorWidgets() const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_QDOCKWIDGET + +#endif // QDOCKAREALAYOUT_P_H diff --git a/src/gui/widgets/qdockwidget.cpp b/src/gui/widgets/qdockwidget.cpp new file mode 100644 index 0000000..5ff7bf5 --- /dev/null +++ b/src/gui/widgets/qdockwidget.cpp @@ -0,0 +1,1591 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdockwidget.h" + +#ifndef QT_NO_DOCKWIDGET +#include <qaction.h> +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qdrawutil.h> +#include <qevent.h> +#include <qfontmetrics.h> +#include <qmainwindow.h> +#include <qrubberband.h> +#include <qstylepainter.h> +#include <qtoolbutton.h> +#include <qdebug.h> + +#include <private/qwidgetresizehandler_p.h> + +#include "qdockwidget_p.h" +#include "qmainwindowlayout_p.h" +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#include <qmacstyle_mac.h> +#endif + +QT_BEGIN_NAMESPACE + +extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); // qwidget.cpp + +extern QHash<QByteArray, QFont> *qt_app_fonts_hash(); // qapplication.cpp + +static inline bool hasFeature(const QDockWidget *dockwidget, QDockWidget::DockWidgetFeature feature) +{ return (dockwidget->features() & feature) == feature; } + + +/* + A Dock Window: + + [+] is the float button + [X] is the close button + + +-------------------------------+ + | Dock Window Title [+][X]| + +-------------------------------+ + | | + | place to put the single | + | QDockWidget child (this space | + | does not yet have a name) | + | | + | | + | | + | | + | | + | | + | | + | | + | | + +-------------------------------+ + +*/ + +/****************************************************************************** +** QDockWidgetTitleButton +*/ + +class QDockWidgetTitleButton : public QAbstractButton +{ + Q_OBJECT + +public: + QDockWidgetTitleButton(QDockWidget *dockWidget); + + QSize sizeHint() const; + inline QSize minimumSizeHint() const + { return sizeHint(); } + + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); +}; + + +QDockWidgetTitleButton::QDockWidgetTitleButton(QDockWidget *dockWidget) + : QAbstractButton(dockWidget) +{ + setFocusPolicy(Qt::NoFocus); +} + +QSize QDockWidgetTitleButton::sizeHint() const +{ + ensurePolished(); + + int size = 2*style()->pixelMetric(QStyle::PM_DockWidgetTitleBarButtonMargin, 0, this); + if (!icon().isNull()) { + int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + QSize sz = icon().actualSize(QSize(iconSize, iconSize)); + size += qMax(sz.width(), sz.height()); + } + + return QSize(size, size); +} + +void QDockWidgetTitleButton::enterEvent(QEvent *event) +{ + if (isEnabled()) update(); + QAbstractButton::enterEvent(event); +} + +void QDockWidgetTitleButton::leaveEvent(QEvent *event) +{ + if (isEnabled()) update(); + QAbstractButton::leaveEvent(event); +} + +void QDockWidgetTitleButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + + QRect r = rect(); + QStyleOptionToolButton opt; + opt.init(this); + opt.state |= QStyle::State_AutoRaise; + + if (style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) + { + if (isEnabled() && underMouse() && !isChecked() && !isDown()) + opt.state |= QStyle::State_Raised; + if (isChecked()) + opt.state |= QStyle::State_On; + if (isDown()) + opt.state |= QStyle::State_Sunken; + style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, &p, this); + } + + opt.icon = icon(); + opt.subControls = 0; + opt.activeSubControls = 0; + opt.features = QStyleOptionToolButton::None; + opt.arrowType = Qt::NoArrow; + int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + opt.iconSize = QSize(size, size); + style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &p, this); +} + +/****************************************************************************** +** QDockWidgetLayout +*/ + +QDockWidgetLayout::QDockWidgetLayout(QWidget *parent) + : QLayout(parent), verticalTitleBar(false), item_list(RoleCount, 0) +{ +} + +QDockWidgetLayout::~QDockWidgetLayout() +{ + qDeleteAll(item_list); +} + +bool QDockWidgetLayout::nativeWindowDeco() const +{ + return nativeWindowDeco(parentWidget()->isWindow()); +} + +bool QDockWidgetLayout::nativeWindowDeco(bool floating) const +{ +#if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_WINCE) + Q_UNUSED(floating); + return false; +#else + return floating && item_list[QDockWidgetLayout::TitleBar] == 0; +#endif +} + + +void QDockWidgetLayout::addItem(QLayoutItem*) +{ + qWarning() << "QDockWidgetLayout::addItem(): please use QDockWidgetLayout::setWidget()"; + return; +} + +QLayoutItem *QDockWidgetLayout::itemAt(int index) const +{ + int cnt = 0; + foreach (QLayoutItem *item, item_list) { + if (item == 0) + continue; + if (index == cnt++) + return item; + } + return 0; +} + +QLayoutItem *QDockWidgetLayout::takeAt(int index) +{ + int j = 0; + for (int i = 0; i < item_list.count(); ++i) { + QLayoutItem *item = item_list.at(i); + if (item == 0) + continue; + if (index == j) { + item_list[i] = 0; + invalidate(); + return item; + } + ++j; + } + return 0; +} + +int QDockWidgetLayout::count() const +{ + int result = 0; + foreach (QLayoutItem *item, item_list) { + if (item != 0) + ++result; + } + return result; +} + +QSize QDockWidgetLayout::sizeFromContent(const QSize &content, bool floating) const +{ + QSize result = content; + + if (verticalTitleBar) { + result.setHeight(qMax(result.height(), minimumTitleWidth())); + result.setWidth(qMax(content.width(), 0)); + } else { + result.setHeight(qMax(result.height(), 0)); + result.setWidth(qMax(content.width(), minimumTitleWidth())); + } + + QDockWidget *w = qobject_cast<QDockWidget*>(parentWidget()); + const bool nativeDeco = nativeWindowDeco(floating); + + int fw = floating && !nativeDeco + ? w->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, w) + : 0; + + const int th = titleHeight(); + if (!nativeDeco) { + if (verticalTitleBar) + result += QSize(th + 2*fw, 2*fw); + else + result += QSize(2*fw, th + 2*fw); + } + + result.setHeight(qMin(result.height(), (int) QWIDGETSIZE_MAX)); + result.setWidth(qMin(result.width(), (int) QWIDGETSIZE_MAX)); + + if (content.width() < 0) + result.setWidth(-1); + if (content.height() < 0) + result.setHeight(-1); + + QSize min = w->minimumSize(); + QSize max = w->maximumSize(); + + /* A floating dockwidget will automatically get its minimumSize set to the layout's + minimum size + deco. We're *not* interested in this, we only take minimumSize() + into account if the user set it herself. Otherwise we end up expanding the result + of a calculation for a non-floating dock widget to a floating dock widget's + minimum size + window decorations. */ + + uint explicitMin = 0; + uint explicitMax = 0; + if (w->d_func()->extra != 0) { + explicitMin = w->d_func()->extra->explicitMinSize; + explicitMax = w->d_func()->extra->explicitMaxSize; + } + + if (!(explicitMin & Qt::Horizontal) || min.width() == 0) + min.setWidth(-1); + if (!(explicitMin & Qt::Vertical) || min.height() == 0) + min.setHeight(-1); + + if (!(explicitMax & Qt::Horizontal)) + max.setWidth(QWIDGETSIZE_MAX); + if (!(explicitMax & Qt::Vertical)) + max.setHeight(QWIDGETSIZE_MAX); + + return result.boundedTo(max).expandedTo(min); +} + +QSize QDockWidgetLayout::sizeHint() const +{ + QDockWidget *w = qobject_cast<QDockWidget*>(parentWidget()); + + QSize content(-1, -1); + if (item_list[Content] != 0) + content = item_list[Content]->sizeHint(); + + return sizeFromContent(content, w->isFloating()); +} + +QSize QDockWidgetLayout::maximumSize() const +{ + if (item_list[Content] != 0) { + const QSize content = item_list[Content]->maximumSize(); + return sizeFromContent(content, parentWidget()->isWindow()); + } else { + return parentWidget()->maximumSize(); + } + +} + +QSize QDockWidgetLayout::minimumSize() const +{ + QDockWidget *w = qobject_cast<QDockWidget*>(parentWidget()); + + QSize content(0, 0); + if (item_list[Content] != 0) + content = item_list[Content]->minimumSize(); + + return sizeFromContent(content, w->isFloating()); +} + +QWidget *QDockWidgetLayout::widgetForRole(Role r) const +{ + QLayoutItem *item = item_list.at(r); + return item == 0 ? 0 : item->widget(); +} + +QLayoutItem *QDockWidgetLayout::itemForRole(Role r) const +{ + return item_list.at(r); +} + +void QDockWidgetLayout::setWidgetForRole(Role r, QWidget *w) +{ + QWidget *old = widgetForRole(r); + if (old != 0) { + old->hide(); + removeWidget(old); + } + + if (w != 0) { + addChildWidget(w); + item_list[r] = new QWidgetItemV2(w); + w->show(); + } else { + item_list[r] = 0; + } + + invalidate(); +} + +static inline int pick(bool vertical, const QSize &size) +{ + return vertical ? size.height() : size.width(); +} + +static inline int perp(bool vertical, const QSize &size) +{ + return vertical ? size.width() : size.height(); +} + +int QDockWidgetLayout::minimumTitleWidth() const +{ + QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget()); + + if (QWidget *title = widgetForRole(TitleBar)) + return pick(verticalTitleBar, title->minimumSizeHint()); + + QSize closeSize(0, 0); + QSize floatSize(0, 0); + if (QLayoutItem *item = item_list[CloseButton]) + closeSize = item->sizeHint(); + if (QLayoutItem *item = item_list[FloatButton]) + floatSize = item->sizeHint(); + + int titleHeight = this->titleHeight(); + + int mw = q->style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, q); + int fw = q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q); + + return pick(verticalTitleBar, closeSize) + + pick(verticalTitleBar, floatSize) + + titleHeight + 2*fw + 3*mw; +} + +int QDockWidgetLayout::titleHeight() const +{ + QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget()); + + if (QWidget *title = widgetForRole(TitleBar)) + return perp(verticalTitleBar, title->sizeHint()); + + QSize closeSize(0, 0); + QSize floatSize(0, 0); + if (QLayoutItem *item = item_list[CloseButton]) + closeSize = item->widget()->sizeHint(); + if (QLayoutItem *item = item_list[FloatButton]) + floatSize = item->widget()->sizeHint(); + + int buttonHeight = qMax(perp(verticalTitleBar, closeSize), + perp(verticalTitleBar, floatSize)); + + QFontMetrics titleFontMetrics = q->fontMetrics(); +#ifdef Q_WS_MAC + if (qobject_cast<QMacStyle *>(q->style())) { + //### this breaks on proxy styles. (But is this code still called?) + QFont font = qt_app_fonts_hash()->value("QToolButton", q->font()); + titleFontMetrics = QFontMetrics(font); + } +#endif + + int mw = q->style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, q); + + return qMax(buttonHeight + 2, titleFontMetrics.lineSpacing() + 2*mw); +} + +void QDockWidgetLayout::setGeometry(const QRect &geometry) +{ + QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget()); + + bool nativeDeco = nativeWindowDeco(); + + int fw = q->isFloating() && !nativeDeco + ? q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q) + : 0; + + if (nativeDeco) { + if (QLayoutItem *item = item_list[Content]) + item->setGeometry(geometry); + } else { + int titleHeight = this->titleHeight(); + + if (verticalTitleBar) { + _titleArea = QRect(QPoint(fw, fw), + QSize(titleHeight, geometry.height() - (fw * 2))); + } else { + _titleArea = QRect(QPoint(fw, fw), + QSize(geometry.width() - (fw * 2), titleHeight)); + } + + if (QLayoutItem *item = item_list[TitleBar]) { + item->setGeometry(_titleArea); + } else { + QStyleOptionDockWidgetV2 opt; + q->initStyleOption(&opt); + + if (QLayoutItem *item = item_list[CloseButton]) { + if (!item->isEmpty()) { + QRect r = q->style() + ->subElementRect(QStyle::SE_DockWidgetCloseButton, + &opt, q); + if (!r.isNull()) + item->setGeometry(r); + } + } + + if (QLayoutItem *item = item_list[FloatButton]) { + if (!item->isEmpty()) { + QRect r = q->style() + ->subElementRect(QStyle::SE_DockWidgetFloatButton, + &opt, q); + if (!r.isNull()) + item->setGeometry(r); + } + } + } + + if (QLayoutItem *item = item_list[Content]) { + QRect r = geometry; + if (verticalTitleBar) { + r.setLeft(_titleArea.right() + 1); + r.adjust(0, fw, -fw, -fw); + } else { + r.setTop(_titleArea.bottom() + 1); + r.adjust(fw, 0, -fw, -fw); + } + item->setGeometry(r); + } + } +} + +void QDockWidgetLayout::setVerticalTitleBar(bool b) +{ + if (b == verticalTitleBar) + return; + verticalTitleBar = b; + invalidate(); + parentWidget()->update(); +} + +/****************************************************************************** +** QDockWidgetItem +*/ + +QDockWidgetItem::QDockWidgetItem(QDockWidget *dockWidget) + : QWidgetItem(dockWidget) +{ +} + +QSize QDockWidgetItem::minimumSize() const +{ + QSize widgetMin(0, 0); + if (QLayoutItem *item = dockWidgetChildItem()) + widgetMin = item->minimumSize(); + return dockWidgetLayout()->sizeFromContent(widgetMin, false); +} + +QSize QDockWidgetItem::maximumSize() const +{ + if (QLayoutItem *item = dockWidgetChildItem()) { + return dockWidgetLayout()->sizeFromContent(item->maximumSize(), false); + } else { + return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + } +} + + +QSize QDockWidgetItem::sizeHint() const +{ + if (QLayoutItem *item = dockWidgetChildItem()) { + return dockWidgetLayout()->sizeFromContent(item->sizeHint(), false); + } else { + return QWidgetItem::sizeHint(); + } +} + +/****************************************************************************** +** QDockWidgetPrivate +*/ + +void QDockWidgetPrivate::init() +{ + Q_Q(QDockWidget); + + QDockWidgetLayout *layout = new QDockWidgetLayout(q); + layout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + QAbstractButton *button = new QDockWidgetTitleButton(q); + button->setObjectName(QLatin1String("qt_dockwidget_floatbutton")); + QObject::connect(button, SIGNAL(clicked()), q, SLOT(_q_toggleTopLevel())); + layout->setWidgetForRole(QDockWidgetLayout::FloatButton, button); + + button = new QDockWidgetTitleButton(q); + button->setObjectName(QLatin1String("qt_dockwidget_closebutton")); + QObject::connect(button, SIGNAL(clicked()), q, SLOT(close())); + layout->setWidgetForRole(QDockWidgetLayout::CloseButton, button); + + resizer = new QWidgetResizeHandler(q); + resizer->setMovingEnabled(false); + resizer->setActive(false); + +#ifndef QT_NO_ACTION + toggleViewAction = new QAction(q); + toggleViewAction->setCheckable(true); + fixedWindowTitle = qt_setWindowTitle_helperHelper(q->windowTitle(), q); + toggleViewAction->setText(fixedWindowTitle); + QObject::connect(toggleViewAction, SIGNAL(triggered(bool)), + q, SLOT(_q_toggleView(bool))); +#endif + + updateButtons(); +} + +/*! + Initialize \a option with the values from this QDockWidget. This method + is useful for subclasses when they need a QStyleOptionDockWidget, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QDockWidget::initStyleOption(QStyleOptionDockWidget *option) const +{ + Q_D(const QDockWidget); + + if (!option) + return; + QDockWidgetLayout *dwlayout = qobject_cast<QDockWidgetLayout*>(layout()); + + option->initFrom(this); + option->rect = dwlayout->titleArea(); + option->title = d->fixedWindowTitle; + option->closable = hasFeature(this, QDockWidget::DockWidgetClosable); + option->movable = hasFeature(this, QDockWidget::DockWidgetMovable); + option->floatable = hasFeature(this, QDockWidget::DockWidgetFloatable); + + QDockWidgetLayout *l = qobject_cast<QDockWidgetLayout*>(layout()); + QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<QStyleOptionDockWidgetV2*>(option); + if (v2 != 0) + v2->verticalTitleBar = l->verticalTitleBar; +} + +void QDockWidgetPrivate::_q_toggleView(bool b) +{ + Q_Q(QDockWidget); + if (b == q->isHidden()) { + if (b) + q->show(); + else + q->close(); + } +} + +void QDockWidgetPrivate::updateButtons() +{ + Q_Q(QDockWidget); + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(q->layout()); + + QStyleOptionDockWidget opt; + q->initStyleOption(&opt); + + bool customTitleBar = layout->widgetForRole(QDockWidgetLayout::TitleBar) != 0; + bool nativeDeco = layout->nativeWindowDeco(); + bool hideButtons = nativeDeco || customTitleBar; + + bool canClose = hasFeature(q, QDockWidget::DockWidgetClosable); + bool canFloat = hasFeature(q, QDockWidget::DockWidgetFloatable); + + QAbstractButton *button + = qobject_cast<QAbstractButton*>(layout->widgetForRole(QDockWidgetLayout::FloatButton)); + button->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarNormalButton, &opt, q)); + button->setVisible(canFloat && !hideButtons); + + button + = qobject_cast <QAbstractButton*>(layout->widgetForRole(QDockWidgetLayout::CloseButton)); + button->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarCloseButton, &opt, q)); + button->setVisible(canClose && !hideButtons); + + q->setAttribute(Qt::WA_ContentsPropagated, + (canFloat || canClose) && !hideButtons); + + layout->invalidate(); +} + +void QDockWidgetPrivate::_q_toggleTopLevel() +{ + Q_Q(QDockWidget); + q->setFloating(!q->isFloating()); +} + +void QDockWidgetPrivate::initDrag(const QPoint &pos, bool nca) +{ + Q_Q(QDockWidget); + + if (state != 0) + return; + + QMainWindow *win = qobject_cast<QMainWindow*>(q->parentWidget()); + Q_ASSERT(win != 0); + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(win->layout()); + Q_ASSERT(layout != 0); + if (layout->layoutState.indexOf(q).isEmpty()) //The dock widget has not been added into the main window + return; + if (layout->pluggingWidget != 0) // the main window is animating a docking operation + return; + + state = new QDockWidgetPrivate::DragState; + state->pressPos = pos; + state->dragging = false; + state->widgetItem = 0; + state->ownWidgetItem = false; + state->nca = nca; + state->ctrlDrag = false; +} + +void QDockWidgetPrivate::startDrag() +{ + Q_Q(QDockWidget); + + if (state == 0 || state->dragging) + return; + + QMainWindowLayout *layout + = qobject_cast<QMainWindowLayout *>(q->parentWidget()->layout()); + Q_ASSERT(layout != 0); + + state->widgetItem = layout->unplug(q); + if (state->widgetItem == 0) { + /* I have a QMainWindow parent, but I was never inserted with + QMainWindow::addDockWidget, so the QMainWindowLayout has no + widget item for me. :( I have to create it myself, and then + delete it if I don't get dropped into a dock area. */ + state->widgetItem = new QDockWidgetItem(q); + state->ownWidgetItem = true; + } + + if (state->ctrlDrag) + layout->restore(); + + state->dragging = true; +} + +void QDockWidgetPrivate::endDrag(bool abort) +{ + Q_Q(QDockWidget); + Q_ASSERT(state != 0); + + q->releaseMouse(); + + if (state->dragging) { + QMainWindowLayout *layout = + qobject_cast<QMainWindowLayout *>(q->parentWidget()->layout()); + Q_ASSERT(layout != 0); + + if (abort || !layout->plug(state->widgetItem)) { + if (hasFeature(q, QDockWidget::DockWidgetFloatable)) { + if (state->ownWidgetItem) + delete state->widgetItem; + layout->restore(); +#ifdef Q_WS_X11 + // get rid of the X11BypassWindowManager window flag and activate the resizer + Qt::WindowFlags flags = q->windowFlags(); + flags &= ~Qt::X11BypassWindowManagerHint; + q->setWindowFlags(flags); + resizer->setActive(QWidgetResizeHandler::Resize, true); + q->show(); +#else + QDockWidgetLayout *myLayout + = qobject_cast<QDockWidgetLayout*>(q->layout()); + resizer->setActive(QWidgetResizeHandler::Resize, + myLayout->widgetForRole(QDockWidgetLayout::TitleBar) != 0); +#endif + undockedGeometry = q->geometry(); + q->activateWindow(); + } else { + layout->revert(state->widgetItem); + } + } + } + delete state; + state = 0; +} + +bool QDockWidgetPrivate::isAnimating() const +{ + Q_Q(const QDockWidget); + + QMainWindow *mainWin = qobject_cast<QMainWindow*>(q->parentWidget()); + if (mainWin == 0) + return false; + + QMainWindowLayout *mainWinLayout + = qobject_cast<QMainWindowLayout*>(mainWin->layout()); + if (mainWinLayout == 0) + return false; + + return (void*)mainWinLayout->pluggingWidget == (void*)q; +} + +bool QDockWidgetPrivate::mousePressEvent(QMouseEvent *event) +{ +#if !defined(QT_NO_MAINWINDOW) + Q_Q(QDockWidget); + + QDockWidgetLayout *layout + = qobject_cast<QDockWidgetLayout*>(q->layout()); + + if (!layout->nativeWindowDeco()) { + QRect titleArea = layout->titleArea(); + + if (event->button() != Qt::LeftButton || + !titleArea.contains(event->pos()) || + // check if the tool window is movable... do nothing if it + // is not (but allow moving if the window is floating) + (!hasFeature(q, QDockWidget::DockWidgetMovable) && !q->isFloating()) || + qobject_cast<QMainWindow*>(q->parentWidget()) == 0 || + isAnimating() || state != 0) { + return false; + } + + initDrag(event->pos(), false); + + if (state) + state->ctrlDrag = hasFeature(q, QDockWidget::DockWidgetFloatable) && event->modifiers() & Qt::ControlModifier; + + return true; + } + +#endif // !defined(QT_NO_MAINWINDOW) + return false; +} + +bool QDockWidgetPrivate::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_Q(QDockWidget); + + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(q->layout()); + + if (!layout->nativeWindowDeco()) { + QRect titleArea = layout->titleArea(); + + if (event->button() == Qt::LeftButton && titleArea.contains(event->pos()) && + hasFeature(q, QDockWidget::DockWidgetFloatable)) { + _q_toggleTopLevel(); + return true; + } + } + return false; +} + +bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event) +{ + bool ret = false; +#if !defined(QT_NO_MAINWINDOW) + Q_Q(QDockWidget); + + if (!state) + return ret; + + QDockWidgetLayout *dwlayout + = qobject_cast<QDockWidgetLayout*>(q->layout()); + QMainWindowLayout *mwlayout + = qobject_cast<QMainWindowLayout*>(q->parentWidget()->layout()); + if (!dwlayout->nativeWindowDeco()) { + if (!state->dragging + && mwlayout->pluggingWidget == 0 + && (event->pos() - state->pressPos).manhattanLength() + > QApplication::startDragDistance()) { + startDrag(); +#ifdef Q_OS_WIN + grabMouseWhileInWindow(); +#else + q->grabMouse(); +#endif + ret = true; + } + } + + if (state->dragging && !state->nca) { + QPoint pos = event->globalPos() - state->pressPos; + q->move(pos); + + if (!state->ctrlDrag) + mwlayout->hover(state->widgetItem, event->globalPos()); + + ret = true; + } + +#endif // !defined(QT_NO_MAINWINDOW) + return ret; +} + +bool QDockWidgetPrivate::mouseReleaseEvent(QMouseEvent *event) +{ +#if !defined(QT_NO_MAINWINDOW) + + if (event->button() == Qt::LeftButton && state && !state->nca) { + endDrag(); + return true; //filter out the event + } + +#endif // !defined(QT_NO_MAINWINDOW) + return false; +} + +void QDockWidgetPrivate::nonClientAreaMouseEvent(QMouseEvent *event) +{ + Q_Q(QDockWidget); + + int fw = q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q); + + QRect geo = q->geometry(); + QRect titleRect = q->frameGeometry(); +#ifdef Q_WS_MAC + if ((features & QDockWidget::DockWidgetVerticalTitleBar)) { + titleRect.setTop(geo.top()); + titleRect.setBottom(geo.bottom()); + titleRect.setRight(geo.left() - 1); + } else +#endif + { + titleRect.setLeft(geo.left()); + titleRect.setRight(geo.right()); + titleRect.setBottom(geo.top() - 1); + titleRect.adjust(0, fw, 0, 0); + } + + switch (event->type()) { + case QEvent::NonClientAreaMouseButtonPress: + if (!titleRect.contains(event->globalPos())) + break; + if (state != 0) + break; + if (qobject_cast<QMainWindow*>(q->parentWidget()) == 0) + break; + if (isAnimating()) + break; + initDrag(event->pos(), true); + if (state == 0) + break; +#ifdef Q_OS_WIN + // On Windows, NCA mouse events don't contain modifier info + state->ctrlDrag = GetKeyState(VK_CONTROL) & 0x8000; +#else + state->ctrlDrag = event->modifiers() & Qt::ControlModifier; +#endif + startDrag(); + break; + case QEvent::NonClientAreaMouseMove: + if (state == 0 || !state->dragging) + break; + if (state->nca) { + endDrag(); + } +#ifdef Q_OS_MAC + else { // workaround for lack of mouse-grab on Mac + QMainWindowLayout *layout + = qobject_cast<QMainWindowLayout *>(q->parentWidget()->layout()); + Q_ASSERT(layout != 0); + + q->move(event->globalPos() - state->pressPos); + if (!state->ctrlDrag) + layout->hover(state->widgetItem, event->globalPos()); + } +#endif + break; + case QEvent::NonClientAreaMouseButtonRelease: +#ifdef Q_OS_MAC + if (state) + endDrag(); +#endif + break; + case QEvent::NonClientAreaMouseButtonDblClick: + _q_toggleTopLevel(); + break; + default: + break; + } +} + +void QDockWidgetPrivate::moveEvent(QMoveEvent *event) +{ + Q_Q(QDockWidget); + + if (state == 0 || !state->dragging || !state->nca || !q->isWindow()) + return; + + // When the native window frame is being dragged, all we get is these mouse + // move events. + + if (state->ctrlDrag) + return; + + QMainWindowLayout *layout + = qobject_cast<QMainWindowLayout *>(q->parentWidget()->layout()); + Q_ASSERT(layout != 0); + + QPoint globalMousePos = event->pos() + state->pressPos; + layout->hover(state->widgetItem, globalMousePos); +} + +void QDockWidgetPrivate::unplug(const QRect &rect) +{ + Q_Q(QDockWidget); + QRect r = rect; + r.moveTopLeft(q->mapToGlobal(QPoint(0, 0))); + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(q->layout()); + if (layout->nativeWindowDeco(true)) + r.adjust(0, layout->titleHeight(), 0, 0); + setWindowState(true, true, r); +} + +void QDockWidgetPrivate::plug(const QRect &rect) +{ + setWindowState(false, false, rect); +} + +void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect &rect) +{ + Q_Q(QDockWidget); + + bool wasFloating = q->isFloating(); + bool hidden = q->isHidden(); + + if (q->isVisible()) + q->hide(); + + Qt::WindowFlags flags = floating ? Qt::Tool : Qt::Widget; + + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(q->layout()); + const bool nativeDeco = layout->nativeWindowDeco(floating); + + if (nativeDeco) { + flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint; + if (hasFeature(q, QDockWidget::DockWidgetClosable)) + flags |= Qt::WindowCloseButtonHint; + } else { + flags |= Qt::FramelessWindowHint; + } + + if (unplug) + flags |= Qt::X11BypassWindowManagerHint; + + q->setWindowFlags(flags); + +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + if (floating && nativeDeco && (q->features() & QDockWidget::DockWidgetVerticalTitleBar)) { + ChangeWindowAttributes(HIViewGetWindow(HIViewRef(q->winId())), kWindowSideTitlebarAttribute, 0); + } +#endif + + if (!rect.isNull()) + q->setGeometry(rect); + + updateButtons(); + + if (!hidden) + q->show(); + + if (floating != wasFloating) { + emit q->topLevelChanged(floating); + if (!floating && q->parentWidget()) { + QMainWindowLayout *mwlayout = qobject_cast<QMainWindowLayout *>(q->parentWidget()->layout()); + if (mwlayout) + emit q->dockLocationChanged(mwlayout->dockWidgetArea(q)); + } + } + + resizer->setActive(QWidgetResizeHandler::Resize, !unplug && floating && !nativeDeco); +} + +/*! + \class QDockWidget + + \brief The QDockWidget class provides a widget that can be docked + inside a QMainWindow or floated as a top-level window on the + desktop. + + \ingroup application + + QDockWidget provides the concept of dock widgets, also know as + tool palettes or utility windows. Dock windows are secondary + windows placed in the \e {dock widget area} around the + \l{QMainWindow::centralWidget()}{central widget} in a + QMainWindow. + + \image mainwindow-docks.png + + Dock windows can be moved inside their current area, moved into + new areas and floated (e.g., undocked) by the end-user. The + QDockWidget API allows the programmer to restrict the dock widgets + ability to move, float and close, as well as the areas in which + they can be placed. + + \section1 Appearance + + A QDockWidget consists of a title bar and the content area. The + title bar displays the dock widgets \link QWidget::windowTitle() + window title\endlink, a \e float button and a \e close button. + Depending on the state of the QDockWidget, the \e float and \e + close buttons may be either disabled or not shown at all. + + The visual appearance of the title bar and buttons is dependent + on the \l{QStyle}{style} in use. + + A QDockWidget acts as a wrapper for its child widget, set with setWidget(). + Custom size hints, minimum and maximum sizes and size policies should be + implemented in the child widget. QDockWidget will respect them, adjusting + its own constraints to include the frame and title. Size constraints + should not be set on the QDockWidget itself, because they change depending + on whether it is docked; a docked QDockWidget has no frame and a smaller title + bar. + + \sa QMainWindow, {Dock Widgets Example} +*/ + +/*! + \enum QDockWidget::DockWidgetFeature + + \value DockWidgetClosable The dock widget can be closed. On some systems the dock + widget always has a close button when it's floating + (for example on MacOS 10.5). + \value DockWidgetMovable The dock widget can be moved between docks + by the user. + \value DockWidgetFloatable The dock widget can be detached from the + main window, and floated as an independent + window. + \value DockWidgetVerticalTitleBar The dock widget displays a vertical title + bar on its left side. This can be used to + increase the amount of vertical space in + a QMainWindow. + \value AllDockWidgetFeatures (Deprecated) The dock widget can be closed, moved, + and floated. Since new features might be added in future + releases, the look and behavior of dock widgets might + change if you use this flag. Please specify individual + flags instead. + \value NoDockWidgetFeatures The dock widget cannot be closed, moved, + or floated. + + \omitvalue DockWidgetFeatureMask + \omitvalue Reserved +*/ + +/*! + \property QDockWidget::windowTitle + \internal + + By default, this property contains an empty string. +*/ + +/*! + Constructs a QDockWidget with parent \a parent and window flags \a + flags. The dock widget will be placed in the left dock widget + area. +*/ +QDockWidget::QDockWidget(QWidget *parent, Qt::WindowFlags flags) + : QWidget(*new QDockWidgetPrivate, parent, flags) +{ + Q_D(QDockWidget); + d->init(); +} + +/*! + Constructs a QDockWidget with parent \a parent and window flags \a + flags. The dock widget will be placed in the left dock widget + area. + + The window title is set to \a title. This title is used when the + QDockWidget is docked and undocked. It is also used in the context + menu provided by QMainWindow. + + \sa setWindowTitle() +*/ +QDockWidget::QDockWidget(const QString &title, QWidget *parent, Qt::WindowFlags flags) + : QWidget(*new QDockWidgetPrivate, parent, flags) +{ + Q_D(QDockWidget); + d->init(); + setWindowTitle(title); +} + +/*! + Destroys the dock widget. +*/ +QDockWidget::~QDockWidget() +{ } + +/*! + Returns the widget for the dock widget. This function returns zero + if the widget has not been set. + + \sa setWidget() +*/ +QWidget *QDockWidget::widget() const +{ + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(this->layout()); + return layout->widgetForRole(QDockWidgetLayout::Content); +} + +/*! + Sets the widget for the dock widget to \a widget. + + If the dock widget is visible when \a widget is added, you must + \l{QWidget::}{show()} it explicitly. + + Note that you must add the layout of the \a widget before you call + this function; if not, the \a widget will not be visible. + + \sa widget() +*/ +void QDockWidget::setWidget(QWidget *widget) +{ + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(this->layout()); + layout->setWidgetForRole(QDockWidgetLayout::Content, widget); +} + +/*! + \property QDockWidget::features + \brief whether the dock widget is movable, closable, and floatable + + By default, this property is set to a combination of DockWidgetClosable, + DockWidgetMovable and DockWidgetFloatable. + + \sa DockWidgetFeature +*/ + +void QDockWidget::setFeatures(QDockWidget::DockWidgetFeatures features) +{ + Q_D(QDockWidget); + features &= DockWidgetFeatureMask; + if (d->features == features) + return; + d->features = features; + QDockWidgetLayout *layout + = qobject_cast<QDockWidgetLayout*>(this->layout()); + layout->setVerticalTitleBar(features & DockWidgetVerticalTitleBar); + d->updateButtons(); + d->toggleViewAction->setEnabled((d->features & DockWidgetClosable) == DockWidgetClosable); + emit featuresChanged(d->features); + update(); +} + +QDockWidget::DockWidgetFeatures QDockWidget::features() const +{ + Q_D(const QDockWidget); + return d->features; +} + +/*! + \property QDockWidget::floating + \brief whether the dock widget is floating + + A floating dock widget is presented to the user as an independent + window "on top" of its parent QMainWindow, instead of being + docked in the QMainWindow. + + By default, this property is true. + + \sa isWindow() +*/ +void QDockWidget::setFloating(bool floating) +{ + Q_D(QDockWidget); + + // the initial click of a double-click may have started a drag... + if (d->state != 0) + d->endDrag(true); + + QRect r = d->undockedGeometry; + + d->setWindowState(floating, false, floating ? r : QRect()); + if (floating && r.isNull()) { + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(this->layout()); + QRect titleArea = layout->titleArea(); + int h = layout->verticalTitleBar ? titleArea.width() : titleArea.height(); + QPoint p = mapToGlobal(QPoint(h, h)); + move(p); + } +} + +/*! + \property QDockWidget::allowedAreas + \brief areas where the dock widget may be placed + + The default is Qt::AllDockWidgetAreas. + + \sa Qt::DockWidgetArea +*/ + +void QDockWidget::setAllowedAreas(Qt::DockWidgetAreas areas) +{ + Q_D(QDockWidget); + areas &= Qt::DockWidgetArea_Mask; + if (areas == d->allowedAreas) + return; + d->allowedAreas = areas; + emit allowedAreasChanged(d->allowedAreas); +} + +Qt::DockWidgetAreas QDockWidget::allowedAreas() const +{ + Q_D(const QDockWidget); + return d->allowedAreas; +} + +/*! + \fn bool QDockWidget::isAreaAllowed(Qt::DockWidgetArea area) const + + Returns true if this dock widget can be placed in the given \a area; + otherwise returns false. +*/ + +/*! \reimp */ +void QDockWidget::changeEvent(QEvent *event) +{ + Q_D(QDockWidget); + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(this->layout()); + + switch (event->type()) { + case QEvent::ModifiedChange: + case QEvent::WindowTitleChange: + update(layout->titleArea()); +#ifndef QT_NO_ACTION + d->fixedWindowTitle = qt_setWindowTitle_helperHelper(windowTitle(), this); + d->toggleViewAction->setText(d->fixedWindowTitle); +#endif +#ifndef QT_NO_TABBAR + { + QMainWindow *win = qobject_cast<QMainWindow*>(parentWidget()); + if (QMainWindowLayout *winLayout = + (win ? qobject_cast<QMainWindowLayout*>(win->layout()) : 0)) + if (QDockAreaLayoutInfo *info = + (winLayout ? winLayout->layoutState.dockAreaLayout.info(this) : 0)) + info->updateTabBar(); + } +#endif // QT_NO_TABBAR + break; + default: + break; + } + QWidget::changeEvent(event); +} + +/*! \reimp */ +void QDockWidget::closeEvent(QCloseEvent *event) +{ + Q_D(QDockWidget); + if (d->state) + d->endDrag(true); + QWidget::closeEvent(event); +} + +/*! \reimp */ +void QDockWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QDockWidgetLayout *layout + = qobject_cast<QDockWidgetLayout*>(this->layout()); + bool customTitleBar = layout->widgetForRole(QDockWidgetLayout::TitleBar) != 0; + bool nativeDeco = layout->nativeWindowDeco(); + + if (!nativeDeco && !customTitleBar) { + QStylePainter p(this); + // ### Add PixelMetric to change spacers, so style may show border + // when not floating. + if (isFloating()) { + QStyleOptionFrame framOpt; + framOpt.init(this); + p.drawPrimitive(QStyle::PE_FrameDockWidget, framOpt); + } + + // Title must be painted after the frame, since the areas overlap, and + // the title may wish to extend out to all sides (eg. XP style) + QStyleOptionDockWidgetV2 titleOpt; + initStyleOption(&titleOpt); + p.drawControl(QStyle::CE_DockWidgetTitle, titleOpt); + } +} + +/*! \reimp */ +bool QDockWidget::event(QEvent *event) +{ + Q_D(QDockWidget); + + QMainWindow *win = qobject_cast<QMainWindow*>(parentWidget()); + QMainWindowLayout *layout = 0; + if (win != 0) + layout = qobject_cast<QMainWindowLayout*>(win->layout()); + + switch (event->type()) { +#ifndef QT_NO_ACTION + case QEvent::Hide: + if (layout != 0) + layout->keepSize(this); + d->toggleViewAction->setChecked(false); + emit visibilityChanged(false); + break; + case QEvent::Show: + d->toggleViewAction->setChecked(true); + emit visibilityChanged(true); + break; +#endif + case QEvent::ApplicationLayoutDirectionChange: + case QEvent::LayoutDirectionChange: + case QEvent::StyleChange: + case QEvent::ParentChange: + d->updateButtons(); + break; + case QEvent::ZOrderChange: { + bool onTop = false; + if (win != 0) { + const QObjectList &siblings = win->children(); + onTop = siblings.count() > 0 && siblings.last() == (QObject*)this; + } + if (!isFloating() && layout != 0 && onTop) + layout->raise(this); + break; + } + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + update(qobject_cast<QDockWidgetLayout *>(this->layout())->titleArea()); + break; + case QEvent::ContextMenu: + if (d->state) { + event->accept(); + return true; + } + break; + // return true after calling the handler since we don't want + // them to be passed onto the default handlers + case QEvent::MouseButtonPress: + if (d->mousePressEvent(static_cast<QMouseEvent *>(event))) + return true; + break; + case QEvent::MouseButtonDblClick: + if (d->mouseDoubleClickEvent(static_cast<QMouseEvent *>(event))) + return true; + break; + case QEvent::MouseMove: + if (d->mouseMoveEvent(static_cast<QMouseEvent *>(event))) + return true; + break; +#ifdef Q_OS_WIN + case QEvent::Leave: + if (d->state != 0 && d->state->dragging && !d->state->nca) { + // This is a workaround for loosing the mouse on Vista. + QPoint pos = QCursor::pos(); + QMouseEvent fake(QEvent::MouseMove, mapFromGlobal(pos), pos, Qt::NoButton, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + d->mouseMoveEvent(&fake); + } + break; +#endif + case QEvent::MouseButtonRelease: + if (d->mouseReleaseEvent(static_cast<QMouseEvent *>(event))) + return true; + break; + case QEvent::NonClientAreaMouseMove: + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::NonClientAreaMouseButtonRelease: + case QEvent::NonClientAreaMouseButtonDblClick: + d->nonClientAreaMouseEvent(static_cast<QMouseEvent*>(event)); + return true; + case QEvent::Move: + d->moveEvent(static_cast<QMoveEvent*>(event)); + break; + case QEvent::Resize: + // if the mainwindow is plugging us, we don't want to update undocked geometry + if (isFloating() && layout != 0 && layout->pluggingWidget != this) + d->undockedGeometry = geometry(); + break; + default: + break; + } + return QWidget::event(event); +} + +#ifndef QT_NO_ACTION +/*! + Returns a checkable action that can be used to show or close this + dock widget. + + The action's text is set to the dock widget's window title. + + \sa QAction::text QWidget::windowTitle + */ +QAction * QDockWidget::toggleViewAction() const +{ + Q_D(const QDockWidget); + return d->toggleViewAction; +} +#endif // QT_NO_ACTION + +/*! + \fn void QDockWidget::featuresChanged(QDockWidget::DockWidgetFeatures features) + + This signal is emitted when the \l features property changes. The + \a features parameter gives the new value of the property. +*/ + +/*! + \fn void QDockWidget::topLevelChanged(bool topLevel) + + This signal is emitted when the \l floating property changes. + The \a topLevel parameter is true if the dock widget is now floating; + otherwise it is false. + + \sa isWindow() +*/ + +/*! + \fn void QDockWidget::allowedAreasChanged(Qt::DockWidgetAreas allowedAreas) + + This signal is emitted when the \l allowedAreas property changes. The + \a allowedAreas parameter gives the new value of the property. +*/ + +/*! + \fn void QDockWidget::visibilityChanged(bool visible) + \since 4.3 + + This signal is emitted when the dock widget becomes \a visible (or + invisible). This happens when the widget is hidden or shown, as + well as when it is docked in a tabbed dock area and its tab + becomes selected or unselected. +*/ + +/*! + \fn void QDockWidget::dockLocationChanged(Qt::DockWidgetArea area) + \since 4.3 + + This signal is emitted when the dock widget is moved to another + dock \a area, or is moved to a different location in its current + dock area. This happens when the dock widget is moved + programmatically or is dragged to a new location by the user. +*/ + +/*! + \since 4.3 + Sets an arbitrary \a widget as the dock widget's title bar. If \a widget + is 0, the title bar widget is removed, but not deleted. + + If a title bar widget is set, QDockWidget will not use native window + decorations when it is floated. + + Here are some tips for implementing custom title bars: + + \list + \i Mouse events that are not explicitly handled by the title bar widget + must be ignored by calling QMouseEvent::ignore(). These events then + propagate to the QDockWidget parent, which handles them in the usual + manner, moving when the title bar is dragged, docking and undocking + when it is double-clicked, etc. + + \i When DockWidgetVerticalTitleBar is set on QDockWidget, the title + bar widget is repositioned accordingly. In resizeEvent(), the title + bar should check what orientation it should assume: + \snippet doc/src/snippets/code/src_gui_widgets_qdockwidget.cpp 0 + + \i The title bar widget must have a valid QWidget::sizeHint() and + QWidget::minimumSizeHint(). These functions should take into account + the current orientation of the title bar. + \endlist + + Using qobject_cast as shown above, the title bar widget has full access + to its parent QDockWidget. Hence it can perform such operations as docking + and hiding in response to user actions. + + \sa titleBarWidget() DockWidgetVerticalTitleBar +*/ + +void QDockWidget::setTitleBarWidget(QWidget *widget) +{ + Q_D(QDockWidget); + QDockWidgetLayout *layout + = qobject_cast<QDockWidgetLayout*>(this->layout()); + layout->setWidgetForRole(QDockWidgetLayout::TitleBar, widget); + d->updateButtons(); + if (isWindow()) { + //this ensures the native decoration is drawn + d->setWindowState(true /*floating*/, true /*unplug*/); + } +} + +/*! + \since 4.3 + Returns the custom title bar widget set on the QDockWidget, or 0 if no + custom title bar has been set. + + \sa setTitleBarWidget() +*/ + +QWidget *QDockWidget::titleBarWidget() const +{ + QDockWidgetLayout *layout + = qobject_cast<QDockWidgetLayout*>(this->layout()); + return layout->widgetForRole(QDockWidgetLayout::TitleBar); +} + +QT_END_NAMESPACE + +#include "qdockwidget.moc" +#include "moc_qdockwidget.cpp" + +#endif // QT_NO_DOCKWIDGET diff --git a/src/gui/widgets/qdockwidget.h b/src/gui/widgets/qdockwidget.h new file mode 100644 index 0000000..938a2fd --- /dev/null +++ b/src/gui/widgets/qdockwidget.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICDOCKWIDGET_H +#define QDYNAMICDOCKWIDGET_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DOCKWIDGET + +class QDockAreaLayout; +class QDockWidgetPrivate; +class QMainWindow; +class QStyleOptionDockWidget; + +class Q_GUI_EXPORT QDockWidget : public QWidget +{ + Q_OBJECT + + Q_FLAGS(DockWidgetFeatures) + Q_PROPERTY(bool floating READ isFloating WRITE setFloating) + Q_PROPERTY(DockWidgetFeatures features READ features WRITE setFeatures NOTIFY featuresChanged) + Q_PROPERTY(Qt::DockWidgetAreas allowedAreas READ allowedAreas + WRITE setAllowedAreas NOTIFY allowedAreasChanged) + Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle DESIGNABLE true) + +public: + explicit QDockWidget(const QString &title, QWidget *parent = 0, Qt::WindowFlags flags = 0); + explicit QDockWidget(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QDockWidget(); + + QWidget *widget() const; + void setWidget(QWidget *widget); + + enum DockWidgetFeature { + DockWidgetClosable = 0x01, + DockWidgetMovable = 0x02, + DockWidgetFloatable = 0x04, + DockWidgetVerticalTitleBar = 0x08, + + DockWidgetFeatureMask = 0x0f, + AllDockWidgetFeatures = DockWidgetClosable|DockWidgetMovable|DockWidgetFloatable, // ### remove in 5.0 + NoDockWidgetFeatures = 0x00, + + Reserved = 0xff + }; + Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature) + + void setFeatures(DockWidgetFeatures features); + DockWidgetFeatures features() const; + + void setFloating(bool floating); + inline bool isFloating() const { return isWindow(); } + + void setAllowedAreas(Qt::DockWidgetAreas areas); + Qt::DockWidgetAreas allowedAreas() const; + + void setTitleBarWidget(QWidget *widget); + QWidget *titleBarWidget() const; + + inline bool isAreaAllowed(Qt::DockWidgetArea area) const + { return (allowedAreas() & area) == area; } + +#ifndef QT_NO_ACTION + QAction *toggleViewAction() const; +#endif + +Q_SIGNALS: + void featuresChanged(QDockWidget::DockWidgetFeatures features); + void topLevelChanged(bool topLevel); + void allowedAreasChanged(Qt::DockWidgetAreas allowedAreas); + void visibilityChanged(bool visible); + void dockLocationChanged(Qt::DockWidgetArea area); + +protected: + void changeEvent(QEvent *event); + void closeEvent(QCloseEvent *event); + void paintEvent(QPaintEvent *event); + bool event(QEvent *event); + void initStyleOption(QStyleOptionDockWidget *option) const; + +private: + Q_DECLARE_PRIVATE(QDockWidget) + Q_DISABLE_COPY(QDockWidget) + Q_PRIVATE_SLOT(d_func(), void _q_toggleView(bool)) + Q_PRIVATE_SLOT(d_func(), void _q_toggleTopLevel()) + friend class QDockAreaLayout; + friend class QDockWidgetItem; + friend class QMainWindowLayout; + friend class QDockWidgetLayout; + friend class QDockAreaLayoutInfo; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDockWidget::DockWidgetFeatures) + +#endif // QT_NO_DOCKWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDYNAMICDOCKWIDGET_H diff --git a/src/gui/widgets/qdockwidget_p.h b/src/gui/widgets/qdockwidget_p.h new file mode 100644 index 0000000..0bc619c --- /dev/null +++ b/src/gui/widgets/qdockwidget_p.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICDOCKWIDGET_P_H +#define QDYNAMICDOCKWIDGET_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 "QtGui/qstyleoption.h" +#include "private/qwidget_p.h" +#include "QtGui/qboxlayout.h" +#include "QtGui/qdockwidget.h" + +#ifndef QT_NO_DOCKWIDGET + +QT_BEGIN_NAMESPACE + +class QGridLayout; +class QWidgetResizeHandler; +class QRubberBand; +class QDockWidgetTitleButton; +class QSpacerItem; +class QDockWidgetItem; + +class QDockWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QDockWidget) + + struct DragState { + QPoint pressPos; + bool dragging; + QLayoutItem *widgetItem; + bool ownWidgetItem; + bool nca; + bool ctrlDrag; + }; + +public: + inline QDockWidgetPrivate() + : QWidgetPrivate(), state(0), + features(QDockWidget::DockWidgetClosable + | QDockWidget::DockWidgetMovable + | QDockWidget::DockWidgetFloatable), + allowedAreas(Qt::AllDockWidgetAreas) + { } + + void init(); + void _q_toggleView(bool); // private slot + void _q_toggleTopLevel(); // private slot + + void updateButtons(); + DragState *state; + + QDockWidget::DockWidgetFeatures features; + Qt::DockWidgetAreas allowedAreas; + + QWidgetResizeHandler *resizer; + +#ifndef QT_NO_ACTION + QAction *toggleViewAction; +#endif + +// QMainWindow *findMainWindow(QWidget *widget) const; + QRect undockedGeometry; + QString fixedWindowTitle; + + bool mousePressEvent(QMouseEvent *event); + bool mouseDoubleClickEvent(QMouseEvent *event); + bool mouseMoveEvent(QMouseEvent *event); + bool mouseReleaseEvent(QMouseEvent *event); + void setWindowState(bool floating, bool unplug = false, const QRect &rect = QRect()); + void nonClientAreaMouseEvent(QMouseEvent *event); + void initDrag(const QPoint &pos, bool nca); + void startDrag(); + void endDrag(bool abort = false); + void moveEvent(QMoveEvent *event); + + void unplug(const QRect &rect); + void plug(const QRect &rect); + + bool isAnimating() const; +}; + +class Q_GUI_EXPORT QDockWidgetLayout : public QLayout +{ + Q_OBJECT +public: + QDockWidgetLayout(QWidget *parent = 0); + ~QDockWidgetLayout(); + void addItem(QLayoutItem *item); + QLayoutItem *itemAt(int index) const; + QLayoutItem *takeAt(int index); + int count() const; + + QSize maximumSize() const; + QSize minimumSize() const; + QSize sizeHint() const; + + QSize sizeFromContent(const QSize &content, bool floating) const; + + void setGeometry(const QRect &r); + + enum Role { Content, CloseButton, FloatButton, TitleBar, RoleCount }; + QWidget *widgetForRole(Role r) const; + void setWidgetForRole(Role r, QWidget *w); + QLayoutItem *itemForRole(Role r) const; + + QRect titleArea() const { return _titleArea; } + + int minimumTitleWidth() const; + int titleHeight() const; + void updateMaxSize(); + bool nativeWindowDeco() const; + bool nativeWindowDeco(bool floating) const; + + void setVerticalTitleBar(bool b); + + bool verticalTitleBar; + +private: + QVector<QLayoutItem*> item_list; + QRect _titleArea; +}; + +/* The size hints of a QDockWidget will depend on wether it is docked or not. + This layout item always returns the size hints as if the dock widget was docked. */ + +class QDockWidgetItem : public QWidgetItem +{ +public: + QDockWidgetItem(QDockWidget *dockWidget); + QSize minimumSize() const; + QSize maximumSize() const; + QSize sizeHint() const; + +private: + inline QLayoutItem *dockWidgetChildItem() const; + inline QDockWidgetLayout *dockWidgetLayout() const; +}; + +inline QLayoutItem *QDockWidgetItem::dockWidgetChildItem() const +{ + if (QDockWidgetLayout *layout = dockWidgetLayout()) + return layout->itemForRole(QDockWidgetLayout::Content); + return 0; +} + +inline QDockWidgetLayout *QDockWidgetItem::dockWidgetLayout() const +{ + QWidget *w = const_cast<QDockWidgetItem*>(this)->widget(); + if (w != 0) + return qobject_cast<QDockWidgetLayout*>(w->layout()); + return 0; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DOCKWIDGET + +#endif // QDYNAMICDOCKWIDGET_P_H diff --git a/src/gui/widgets/qeffects.cpp b/src/gui/widgets/qeffects.cpp new file mode 100644 index 0000000..140953d --- /dev/null +++ b/src/gui/widgets/qeffects.cpp @@ -0,0 +1,632 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#ifndef QT_NO_EFFECTS +#include "qdatetime.h" +#include "qdesktopwidget.h" +#include "qeffects_p.h" +#include "qevent.h" +#include "qimage.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qpointer.h" +#include "qtimer.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +/* + Internal class to get access to protected QWidget-members +*/ + +class QAccessWidget : public QWidget +{ + friend class QAlphaWidget; + friend class QRollEffect; +public: + QAccessWidget(QWidget* parent=0, Qt::WindowFlags f = 0) + : QWidget(parent, f) {} +}; + +/* + Internal class QAlphaWidget. + + The QAlphaWidget is shown while the animation lasts + and displays the pixmap resulting from the alpha blending. +*/ + +class QAlphaWidget: public QWidget, private QEffects +{ + Q_OBJECT +public: + QAlphaWidget(QWidget* w, Qt::WindowFlags f = 0); + ~QAlphaWidget(); + + void run(int time); + +protected: + void paintEvent(QPaintEvent* e); + void closeEvent(QCloseEvent*); + void alphaBlend(); + bool eventFilter(QObject *, QEvent *); + +protected slots: + void render(); + +private: + QPixmap pm; + double alpha; + QImage backImage; + QImage frontImage; + QImage mixedImage; + QPointer<QAccessWidget> widget; + int duration; + int elapsed; + bool showWidget; + QTimer anim; + QTime checkTime; + double windowOpacity; +}; + +static QAlphaWidget* q_blend = 0; + +/* + Constructs a QAlphaWidget. +*/ +QAlphaWidget::QAlphaWidget(QWidget* w, Qt::WindowFlags f) + : QWidget(QApplication::desktop()->screen(QApplication::desktop()->screenNumber(w)), f) +{ +#ifndef Q_WS_WIN + setEnabled(false); +#endif + setAttribute(Qt::WA_NoSystemBackground, true); + widget = (QAccessWidget*)w; + windowOpacity = w->windowOpacity(); + alpha = 0; +} + +QAlphaWidget::~QAlphaWidget() +{ +#ifdef Q_WS_WIN + // Restore user-defined opacity value + if (widget && QSysInfo::WindowsVersion >= QSysInfo::WV_2000 && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + widget->setWindowOpacity(windowOpacity); +#endif +} + +/* + \reimp +*/ +void QAlphaWidget::paintEvent(QPaintEvent*) +{ + QPainter p(this); + p.drawPixmap(0, 0, pm); +} + +/* + Starts the alphablending animation. + The animation will take about \a time ms +*/ +void QAlphaWidget::run(int time) +{ + duration = time; + + if (duration < 0) + duration = 150; + + if (!widget) + return; + + elapsed = 0; + checkTime.start(); + + showWidget = true; +#if defined(Q_OS_WIN) + if (QSysInfo::WindowsVersion >= QSysInfo::WV_2000 && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) { + qApp->installEventFilter(this); + widget->setWindowOpacity(0.0); + widget->show(); + connect(&anim, SIGNAL(timeout()), this, SLOT(render())); + anim.start(1); + } else +#endif + { + //This is roughly equivalent to calling setVisible(true) without actually showing the widget + widget->setAttribute(Qt::WA_WState_ExplicitShowHide, true); + widget->setAttribute(Qt::WA_WState_Hidden, false); + + qApp->installEventFilter(this); + + move(widget->geometry().x(),widget->geometry().y()); + resize(widget->size().width(), widget->size().height()); + + frontImage = QPixmap::grabWidget(widget).toImage(); + backImage = QPixmap::grabWindow(QApplication::desktop()->winId(), + widget->geometry().x(), widget->geometry().y(), + widget->geometry().width(), widget->geometry().height()).toImage(); + + if (!backImage.isNull() && checkTime.elapsed() < duration / 2) { + mixedImage = backImage.copy(); + pm = QPixmap::fromImage(mixedImage); + show(); + setEnabled(false); + + connect(&anim, SIGNAL(timeout()), this, SLOT(render())); + anim.start(1); + } else { + duration = 0; + render(); + } + } +} + +/* + \reimp +*/ +bool QAlphaWidget::eventFilter(QObject *o, QEvent *e) +{ + switch (e->type()) { + case QEvent::Move: + if (o != widget) + break; + move(widget->geometry().x(),widget->geometry().y()); + update(); + break; + case QEvent::Hide: + case QEvent::Close: + if (o != widget) + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + showWidget = false; + render(); + break; + case QEvent::KeyPress: { + QKeyEvent *ke = (QKeyEvent*)e; + if (ke->key() == Qt::Key_Escape) { + showWidget = false; + } else { + duration = 0; + } + render(); + break; + } + default: + break; + } + return QWidget::eventFilter(o, e); +} + +/* + \reimp +*/ +void QAlphaWidget::closeEvent(QCloseEvent *e) +{ + e->accept(); + if (!q_blend) + return; + + showWidget = false; + render(); + + QWidget::closeEvent(e); +} + +/* + Render alphablending for the time elapsed. + + Show the blended widget and free all allocated source + if the blending is finished. +*/ +void QAlphaWidget::render() +{ + int tempel = checkTime.elapsed(); + if (elapsed >= tempel) + elapsed++; + else + elapsed = tempel; + + if (duration != 0) + alpha = tempel / double(duration); + else + alpha = 1; + +#if defined(Q_OS_WIN) + if (QSysInfo::WindowsVersion >= QSysInfo::WV_2000 && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) { + if (alpha >= windowOpacity || !showWidget) { + anim.stop(); + qApp->removeEventFilter(this); + widget->setWindowOpacity(windowOpacity); + q_blend = 0; + deleteLater(); + } else { + widget->setWindowOpacity(alpha); + } + } else +#endif + if (alpha >= 1 || !showWidget) { + anim.stop(); + qApp->removeEventFilter(this); + + if (widget) { + if (!showWidget) { +#ifdef Q_WS_WIN + setEnabled(true); + setFocus(); +#endif + widget->hide(); + } else { + //Since we are faking the visibility of the widget + //we need to unset the hidden state on it before calling show + widget->setAttribute(Qt::WA_WState_Hidden, true); + widget->show(); + lower(); + } + } + q_blend = 0; + deleteLater(); + } else { + alphaBlend(); + pm = QPixmap::fromImage(mixedImage); + repaint(); + } +} + +/* + Calculate an alphablended image. +*/ +void QAlphaWidget::alphaBlend() +{ + const int a = qRound(alpha*256); + const int ia = 256 - a; + + const int sw = frontImage.width(); + const int sh = frontImage.height(); + const int bpl = frontImage.bytesPerLine(); + switch(frontImage.depth()) { + case 32: + { + uchar *mixed_data = mixedImage.bits(); + const uchar *back_data = backImage.bits(); + const uchar *front_data = frontImage.bits(); + + for (int sy = 0; sy < sh; sy++) { + quint32* mixed = (quint32*)mixed_data; + const quint32* back = (const quint32*)back_data; + const quint32* front = (const quint32*)front_data; + for (int sx = 0; sx < sw; sx++) { + quint32 bp = back[sx]; + quint32 fp = front[sx]; + + mixed[sx] = qRgb((qRed(bp)*ia + qRed(fp)*a)>>8, + (qGreen(bp)*ia + qGreen(fp)*a)>>8, + (qBlue(bp)*ia + qBlue(fp)*a)>>8); + } + mixed_data += bpl; + back_data += bpl; + front_data += bpl; + } + } + default: + break; + } +} + +/* + Internal class QRollEffect + + The QRollEffect widget is shown while the animation lasts + and displays a scrolling pixmap. +*/ + +class QRollEffect : public QWidget, private QEffects +{ + Q_OBJECT +public: + QRollEffect(QWidget* w, Qt::WindowFlags f, DirFlags orient); + + void run(int time); + +protected: + void paintEvent(QPaintEvent*); + void closeEvent(QCloseEvent*); + +private slots: + void scroll(); + +private: + QPointer<QAccessWidget> widget; + + int currentHeight; + int currentWidth; + int totalHeight; + int totalWidth; + + int duration; + int elapsed; + bool done; + bool showWidget; + int orientation; + + QTimer anim; + QTime checkTime; + + QPixmap pm; +}; + +static QRollEffect* q_roll = 0; + +/* + Construct a QRollEffect widget. +*/ +QRollEffect::QRollEffect(QWidget* w, Qt::WindowFlags f, DirFlags orient) + : QWidget(0, f), orientation(orient) +{ +#ifndef Q_WS_WIN + setEnabled(false); +#endif + + widget = (QAccessWidget*) w; + Q_ASSERT(widget); + + setAttribute(Qt::WA_NoSystemBackground, true); + + if (widget->testAttribute(Qt::WA_Resized)) { + totalWidth = widget->width(); + totalHeight = widget->height(); + } else { + totalWidth = widget->sizeHint().width(); + totalHeight = widget->sizeHint().height(); + } + + currentHeight = totalHeight; + currentWidth = totalWidth; + + if (orientation & (RightScroll|LeftScroll)) + currentWidth = 0; + if (orientation & (DownScroll|UpScroll)) + currentHeight = 0; + + pm = QPixmap::grabWidget(widget); +} + +/* + \reimp +*/ +void QRollEffect::paintEvent(QPaintEvent*) +{ + int x = orientation & RightScroll ? qMin(0, currentWidth - totalWidth) : 0; + int y = orientation & DownScroll ? qMin(0, currentHeight - totalHeight) : 0; + + QPainter p(this); + p.drawPixmap(x, y, pm); +} + +/* + \reimp +*/ +void QRollEffect::closeEvent(QCloseEvent *e) +{ + e->accept(); + if (done) + return; + + showWidget = false; + done = true; + scroll(); + + QWidget::closeEvent(e); +} + +/* + Start the animation. + + The animation will take about \a time ms, or is + calculated if \a time is negative +*/ +void QRollEffect::run(int time) +{ + if (!widget) + return; + + duration = time; + elapsed = 0; + + if (duration < 0) { + int dist = 0; + if (orientation & (RightScroll|LeftScroll)) + dist += totalWidth - currentWidth; + if (orientation & (DownScroll|UpScroll)) + dist += totalHeight - currentHeight; + duration = qMin(qMax(dist/3, 50), 120); + } + + connect(&anim, SIGNAL(timeout()), this, SLOT(scroll())); + + move(widget->geometry().x(),widget->geometry().y()); + resize(qMin(currentWidth, totalWidth), qMin(currentHeight, totalHeight)); + + //This is roughly equivalent to calling setVisible(true) without actually showing the widget + widget->setAttribute(Qt::WA_WState_ExplicitShowHide, true); + widget->setAttribute(Qt::WA_WState_Hidden, false); + + show(); + setEnabled(false); + + qApp->installEventFilter(this); + + showWidget = true; + done = false; + anim.start(1); + checkTime.start(); +} + +/* + Roll according to the time elapsed. +*/ +void QRollEffect::scroll() +{ + if (!done && widget) { + int tempel = checkTime.elapsed(); + if (elapsed >= tempel) + elapsed++; + else + elapsed = tempel; + + if (currentWidth != totalWidth) { + currentWidth = totalWidth * (elapsed/duration) + + (2 * totalWidth * (elapsed%duration) + duration) + / (2 * duration); + // equiv. to int((totalWidth*elapsed) / duration + 0.5) + done = (currentWidth >= totalWidth); + } + if (currentHeight != totalHeight) { + currentHeight = totalHeight * (elapsed/duration) + + (2 * totalHeight * (elapsed%duration) + duration) + / (2 * duration); + // equiv. to int((totalHeight*elapsed) / duration + 0.5) + done = (currentHeight >= totalHeight); + } + done = (currentHeight >= totalHeight) && + (currentWidth >= totalWidth); + + int w = totalWidth; + int h = totalHeight; + int x = widget->geometry().x(); + int y = widget->geometry().y(); + + if (orientation & RightScroll || orientation & LeftScroll) + w = qMin(currentWidth, totalWidth); + if (orientation & DownScroll || orientation & UpScroll) + h = qMin(currentHeight, totalHeight); + + setUpdatesEnabled(false); + if (orientation & UpScroll) + y = widget->geometry().y() + qMax(0, totalHeight - currentHeight); + if (orientation & LeftScroll) + x = widget->geometry().x() + qMax(0, totalWidth - currentWidth); + if (orientation & UpScroll || orientation & LeftScroll) + move(x, y); + + resize(w, h); + setUpdatesEnabled(true); + repaint(); + } + if (done) { + anim.stop(); + qApp->removeEventFilter(this); + if (widget) { + if (!showWidget) { +#ifdef Q_WS_WIN + setEnabled(true); + setFocus(); +#endif + widget->hide(); + } else { + //Since we are faking the visibility of the widget + //we need to unset the hidden state on it before calling show + widget->setAttribute(Qt::WA_WState_Hidden, true); + widget->show(); + lower(); + } + } + q_roll = 0; + deleteLater(); + } +} + +/*! + Scroll widget \a w in \a time ms. \a orient may be 1 (vertical), 2 + (horizontal) or 3 (diagonal). +*/ +void qScrollEffect(QWidget* w, QEffects::DirFlags orient, int time) +{ + if (q_roll) { + q_roll->deleteLater(); + q_roll = 0; + } + + if (!w) + return; + + qApp->sendPostedEvents(w, QEvent::Move); + qApp->sendPostedEvents(w, QEvent::Resize); + Qt::WindowFlags flags = Qt::ToolTip; + + // those can be popups - they would steal the focus, but are disabled + q_roll = new QRollEffect(w, flags, orient); + q_roll->run(time); +} + +/*! + Fade in widget \a w in \a time ms. +*/ +void qFadeEffect(QWidget* w, int time) +{ + if (q_blend) { + q_blend->deleteLater(); + q_blend = 0; + } + + if (!w) + return; + + qApp->sendPostedEvents(w, QEvent::Move); + qApp->sendPostedEvents(w, QEvent::Resize); + + Qt::WindowFlags flags = Qt::ToolTip; + + // those can be popups - they would steal the focus, but are disabled + q_blend = new QAlphaWidget(w, flags); + + q_blend->run(time); +} + +QT_END_NAMESPACE + +/* + Delete this after timeout +*/ + +#include "qeffects.moc" + +#endif //QT_NO_EFFECTS diff --git a/src/gui/widgets/qeffects_p.h b/src/gui/widgets/qeffects_p.h new file mode 100644 index 0000000..edff5a9 --- /dev/null +++ b/src/gui/widgets/qeffects_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEFFECTS_P_H +#define QEFFECTS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qeffects.cpp, qcombobox.cpp, qpopupmenu.cpp and qtooltip.cpp. +// This header file may change from version to version without notice, +// or even be removed. +// +// We mean it. +// + +#include "QtCore/qnamespace.h" + +#ifndef QT_NO_EFFECTS + +QT_BEGIN_NAMESPACE + +class QWidget; + +struct QEffects +{ + enum Direction { + LeftScroll = 0x0001, + RightScroll = 0x0002, + UpScroll = 0x0004, + DownScroll = 0x0008 + }; + + typedef uint DirFlags; +}; + +extern void Q_GUI_EXPORT qScrollEffect(QWidget*, QEffects::DirFlags dir = QEffects::DownScroll, int time = -1); +extern void Q_GUI_EXPORT qFadeEffect(QWidget*, int time = -1); + +QT_END_NAMESPACE + +#endif // QT_NO_EFFECTS + +#endif // QEFFECTS_P_H diff --git a/src/gui/widgets/qfocusframe.cpp b/src/gui/widgets/qfocusframe.cpp new file mode 100644 index 0000000..4e5b630 --- /dev/null +++ b/src/gui/widgets/qfocusframe.cpp @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfocusframe.h" +#include "qstyle.h" +#include "qbitmap.h" +#include "qstylepainter.h" +#include "qstyleoption.h" +#include "qdebug.h" +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +class QFocusFramePrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QFocusFrame) + QWidget *widget; + +public: + QFocusFramePrivate() { + widget = 0; + sendChildEvents = false; + } + void updateSize(); + void update(); +}; + +void QFocusFramePrivate::update() +{ + Q_Q(QFocusFrame); + q->setParent(widget->parentWidget()); + updateSize(); + if (q->parentWidget()->rect().intersects(q->geometry())) { + if (q->style()->styleHint(QStyle::SH_FocusFrame_AboveWidget, 0, q)) + q->raise(); + else + q->stackUnder(widget); + q->show(); + } else { + q->hide(); + } +} + +void QFocusFramePrivate::updateSize() +{ + Q_Q(QFocusFrame); + int vmargin = q->style()->pixelMetric(QStyle::PM_FocusFrameVMargin), + hmargin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + QRect geom(widget->x()-hmargin, widget->y()-vmargin, + widget->width()+(hmargin*2), widget->height()+(vmargin*2)); + if(q->geometry() == geom) + return; + + q->setGeometry(geom); + QStyleHintReturnMask mask; + QStyleOption opt; + q->initStyleOption(&opt); + if (q->style()->styleHint(QStyle::SH_FocusFrame_Mask, &opt, q, &mask)) + q->setMask(mask.region); +} + +/*! + Initialize \a option with the values from this QFocusFrame. This method is useful + for subclasses when they need a QStyleOption, but don't want to fill + in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QFocusFrame::initStyleOption(QStyleOption *option) const +{ + if (!option) + return; + + option->initFrom(this); +} + +/*! + \class QFocusFrame + \brief The QFocusFrame widget provides a focus frame which can be + outside of a widget's normal paintable area. + + \ingroup basicwidgets + \mainclass + + Normally an application will not need to create its own + QFocusFrame as QStyle will handle this detail for + you. A style writer can optionally use a QFocusFrame to have a + focus area outside of the widget's paintable geometry. In this way + space need not be reserved for the widget to have focus but only + set on a QWidget with QFocusFrame::setWidget. It is, however, + legal to create your own QFocusFrame on a custom widget and set + its geometry manually via QWidget::setGeometry however you will + not get auto-placement when the focused widget changes size or + placement. +*/ + +/*! + Constructs a QFocusFrame. + + The focus frame will not monitor \a parent for updates but rather + can be placed manually or by using QFocusFrame::setWidget. A + QFocusFrame sets Qt::WA_NoChildEventsForParent attribute; as a + result the parent will not receive a QEvent::ChildInserted event, + this will make it possible to manually set the geometry of the + QFocusFrame inside of a QSplitter or other child event monitoring + widget. + + \sa QFocusFrame::setWidget() +*/ + +QFocusFrame::QFocusFrame(QWidget *parent) + : QWidget(*new QFocusFramePrivate, parent, 0) +{ + setAttribute(Qt::WA_TransparentForMouseEvents); + setFocusPolicy(Qt::NoFocus); + setAttribute(Qt::WA_NoChildEventsForParent, true); + setAttribute(Qt::WA_AcceptDrops, style()->styleHint(QStyle::SH_FocusFrame_AboveWidget, 0, this)); +} + +/*! + Destructor. +*/ + +QFocusFrame::~QFocusFrame() +{ +} + +/*! + QFocusFrame will track changes to \a widget and resize itself automatically. + If the monitored widget's parent changes, QFocusFrame will follow the widget + and place itself around the widget automatically. If the monitored widget is deleted, + QFocusFrame will set it to zero. + + \sa QFocusFrame::widget() +*/ + +void +QFocusFrame::setWidget(QWidget *widget) +{ + Q_D(QFocusFrame); + if(widget == d->widget) + return; + + if(d->widget) + d->widget->removeEventFilter(this); + if(widget && !widget->isWindow() && widget->parentWidget()->windowType() != Qt::SubWindow) { + d->widget = widget; + widget->installEventFilter(this); + d->update(); + } else { + d->widget = 0; + hide(); + } +} + +/*! + Returns the currently monitored widget for automatically resize and + update. + + \sa QFocusFrame::setWidget() +*/ + +QWidget * +QFocusFrame::widget() const +{ + Q_D(const QFocusFrame); + return d->widget; +} + + +/*! \reimp */ +void +QFocusFrame::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOption option; + initStyleOption(&option); + p.drawControl(QStyle::CE_FocusFrame, option); +} + + +/*! \reimp */ +bool +QFocusFrame::eventFilter(QObject *o, QEvent *e) +{ + Q_D(QFocusFrame); + if(o == d->widget) { + switch(e->type()) { + case QEvent::Move: + case QEvent::Resize: + d->updateSize(); + break; + case QEvent::Hide: + case QEvent::StyleChange: + hide(); + break; + case QEvent::ParentChange: + d->update(); + break; + case QEvent::Show: + d->update(); + show(); + break; + case QEvent::PaletteChange: + setPalette(d->widget->palette()); + break; + case QEvent::ZOrderChange: + if (style()->styleHint(QStyle::SH_FocusFrame_AboveWidget, 0, this)) + raise(); + else + stackUnder(d->widget); + break; + case QEvent::Destroy: + setWidget(0); + break; + default: + break; + } + } + return false; +} + +/*! \reimp */ +bool QFocusFrame::event(QEvent *e) +{ + return QWidget::event(e); +} + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qfocusframe.h b/src/gui/widgets/qfocusframe.h new file mode 100644 index 0000000..d886e09 --- /dev/null +++ b/src/gui/widgets/qfocusframe.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFOCUSFRAME_H +#define QFOCUSFRAME_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFocusFramePrivate; +class QStyleOption; + +class Q_GUI_EXPORT QFocusFrame : public QWidget +{ + Q_OBJECT +public: + QFocusFrame(QWidget *parent=0); + ~QFocusFrame(); + + void setWidget(QWidget *widget); + QWidget *widget() const; + +protected: + bool event(QEvent *e); + + bool eventFilter(QObject *, QEvent *); + void paintEvent(QPaintEvent *); + void initStyleOption(QStyleOption *option) const; + +private: + Q_DECLARE_PRIVATE(QFocusFrame) + Q_DISABLE_COPY(QFocusFrame) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFOCUSFRAME_H diff --git a/src/gui/widgets/qfontcombobox.cpp b/src/gui/widgets/qfontcombobox.cpp new file mode 100644 index 0000000..3c7e691 --- /dev/null +++ b/src/gui/widgets/qfontcombobox.cpp @@ -0,0 +1,467 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontcombobox.h" + +#ifndef QT_NO_FONTCOMBOBOX + +#include <qstringlistmodel.h> +#include <qitemdelegate.h> +#include <qlistview.h> +#include <qpainter.h> +#include <qevent.h> +#include <qapplication.h> +#include <private/qcombobox_p.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +static QFontDatabase::WritingSystem writingSystemForFont(const QFont &font, bool *hasLatin) +{ + *hasLatin = true; + + QList<QFontDatabase::WritingSystem> writingSystems = QFontDatabase().writingSystems(font.family()); +// qDebug() << font.family() << writingSystems; + + // this just confuses the algorithm below. Vietnamese is Latin with lots of special chars + writingSystems.removeAll(QFontDatabase::Vietnamese); + + QFontDatabase::WritingSystem system = QFontDatabase::Any; + + if (!writingSystems.contains(QFontDatabase::Latin)) { + *hasLatin = false; + // we need to show something + if (writingSystems.count()) + system = writingSystems.last(); + } else { + writingSystems.removeAll(QFontDatabase::Latin); + } + + if (writingSystems.isEmpty()) + return system; + + if (writingSystems.count() == 1 && writingSystems.at(0) > QFontDatabase::Cyrillic) { + system = writingSystems.at(0); + return system; + } + + if (writingSystems.count() <= 2 + && writingSystems.last() > QFontDatabase::Armenian + && writingSystems.last() < QFontDatabase::Vietnamese) { + system = writingSystems.last(); + return system; + } + + if (writingSystems.count() <= 5 + && writingSystems.last() >= QFontDatabase::SimplifiedChinese + && writingSystems.last() <= QFontDatabase::Korean) + system = writingSystems.last(); + + return system; +} + +class QFontFamilyDelegate : public QAbstractItemDelegate +{ + Q_OBJECT +public: + explicit QFontFamilyDelegate(QObject *parent); + + // painting + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + QIcon truetype; + QIcon bitmap; + QFontDatabase::WritingSystem writingSystem; +}; + +QFontFamilyDelegate::QFontFamilyDelegate(QObject *parent) + : QAbstractItemDelegate(parent) +{ + truetype = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fonttruetype-16.png")); + bitmap = QIcon(QLatin1String(":/trolltech/styles/commonstyle/images/fontbitmap-16.png")); + writingSystem = QFontDatabase::Any; +} + +void QFontFamilyDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QString text = index.data(Qt::DisplayRole).toString(); + QFont font(option.font); + font.setPointSize(QFontInfo(font).pointSize() * 3 / 2); + QFont font2 = font; + font2.setFamily(text); + + bool hasLatin; + QFontDatabase::WritingSystem system = writingSystemForFont(font2, &hasLatin); + if (hasLatin) + font = font2; + + QRect r = option.rect; + + if (option.state & QStyle::State_Selected) { + painter->save(); + painter->setBrush(option.palette.highlight()); + painter->setPen(Qt::NoPen); + painter->drawRect(option.rect); + painter->setPen(QPen(option.palette.highlightedText(), 0)); + } + + const QIcon *icon = &bitmap; + if (QFontDatabase().isSmoothlyScalable(text)) { + icon = &truetype; + } + QSize actualSize = icon->actualSize(r.size()); + + icon->paint(painter, r, Qt::AlignLeft|Qt::AlignVCenter); + if (option.direction == Qt::RightToLeft) + r.setRight(r.right() - actualSize.width() - 4); + else + r.setLeft(r.left() + actualSize.width() + 4); + + QFont old = painter->font(); + painter->setFont(font); + painter->drawText(r, Qt::AlignVCenter|Qt::AlignLeading|Qt::TextSingleLine, text); + + if (writingSystem != QFontDatabase::Any) + system = writingSystem; + + if (system != QFontDatabase::Any) { + int w = painter->fontMetrics().width(text + QLatin1String(" ")); + painter->setFont(font2); + QString sample = QFontDatabase().writingSystemSample(system); + if (option.direction == Qt::RightToLeft) + r.setRight(r.right() - w); + else + r.setLeft(r.left() + w); + painter->drawText(r, Qt::AlignVCenter|Qt::AlignLeading|Qt::TextSingleLine, sample); + } + painter->setFont(old); + + if (option.state & QStyle::State_Selected) + painter->restore(); + +} + +QSize QFontFamilyDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QString text = index.data(Qt::DisplayRole).toString(); + QFont font(option.font); +// font.setFamily(text); + font.setPointSize(QFontInfo(font).pointSize() * 3/2); + QFontMetrics fontMetrics(font); + return QSize(fontMetrics.width(text), fontMetrics.lineSpacing()); +} + + +class QFontComboBoxPrivate : public QComboBoxPrivate +{ +public: + inline QFontComboBoxPrivate() { filters = QFontComboBox::AllFonts; } + + QFontComboBox::FontFilters filters; + QFont currentFont; + + void _q_updateModel(); + void _q_currentChanged(const QString &); + + Q_DECLARE_PUBLIC(QFontComboBox) +}; + + +void QFontComboBoxPrivate::_q_updateModel() +{ + Q_Q(QFontComboBox); + const int scalableMask = (QFontComboBox::ScalableFonts | QFontComboBox::NonScalableFonts); + const int spacingMask = (QFontComboBox::ProportionalFonts | QFontComboBox::MonospacedFonts); + + QStringListModel *m = qobject_cast<QStringListModel *>(q->model()); + if (!m) + return; + QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(q->view()->itemDelegate()); + QFontDatabase::WritingSystem system = delegate ? delegate->writingSystem : QFontDatabase::Any; + + QFontDatabase fdb; + QStringList list = fdb.families(system); + QStringList result; + + int offset = 0; + QFontInfo fi(currentFont); + + for (int i = 0; i < list.size(); ++i) { + if ((filters & scalableMask) && (filters & scalableMask) != scalableMask) { + if (bool(filters & QFontComboBox::ScalableFonts) != fdb.isSmoothlyScalable(list.at(i))) + continue; + } + if ((filters & spacingMask) && (filters & spacingMask) != spacingMask) { + if (bool(filters & QFontComboBox::MonospacedFonts) != fdb.isFixedPitch(list.at(i))) + continue; + } + result += list.at(i); + if (list.at(i) == fi.family() || list.at(i).startsWith(fi.family() + QLatin1String(" ["))) + offset = result.count() - 1; + } + list = result; + + m->setStringList(list); + if (list.isEmpty()) { + if (currentFont != QFont()) { + currentFont = QFont(); + emit q->currentFontChanged(currentFont); + } + } else { + q->setCurrentIndex(offset); + } +} + + +void QFontComboBoxPrivate::_q_currentChanged(const QString &text) +{ + Q_Q(QFontComboBox); + QFont newFont(text); + if (currentFont != newFont) { + currentFont = newFont; + emit q->currentFontChanged(currentFont); + } +} + +/*! + \class QFontComboBox + \brief The QFontComboBox widget is a combobox that lets the user + select a font family. + + \since 4.2 + \ingroup basicwidgets + \ingroup text + + The combobox is populated with an alphabetized list of font + family names, such as Arial, Helvetica, and Times New Roman. + Family names are displayed using the actual font when possible. + For fonts such as Symbol, where the name is not representable in + the font itself, a sample of the font is displayed next to the + family name. + + QFontComboBox is often used in toolbars, in conjunction with a + QComboBox for controlling the font size and two \l{QToolButton}s + for bold and italic. + + When the user selects a new font, the currentFontChanged() signal + is emitted in addition to currentIndexChanged(). + + Call setWritingSystem() to tell QFontComboBox to show only fonts + that support a given writing system, and setFontFilters() to + filter out certain types of fonts as e.g. non scalable fonts or + monospaced fonts. + + \image windowsxp-fontcombobox.png Screenshot of QFontComboBox on Windows XP + + \sa QComboBox, QFont, QFontInfo, QFontMetrics, QFontDatabase, {Character Map Example} +*/ + +/*! + \fn void QFontComboBox::setWritingSystem(QFontDatabase::WritingSystem script) +*/ + +/*! + \fn void QFontComboBox::setCurrentFont(const QFont &font); +*/ + +/*! + Constructs a font combobox with the given \a parent. +*/ +QFontComboBox::QFontComboBox(QWidget *parent) + : QComboBox(*new QFontComboBoxPrivate, parent) +{ + Q_D(QFontComboBox); + d->currentFont = font(); + setEditable(true); + + QStringListModel *m = new QStringListModel(this); + setModel(m); + setItemDelegate(new QFontFamilyDelegate(this)); + QListView *lview = qobject_cast<QListView*>(view()); + if (lview) + lview->setUniformItemSizes(true); + setWritingSystem(QFontDatabase::Any); + + connect(this, SIGNAL(currentIndexChanged(QString)), + this, SLOT(_q_currentChanged(QString))); + + connect(qApp, SIGNAL(fontDatabaseChanged()), + this, SLOT(_q_updateModel())); +} + + +/*! + Destroys the combobox. +*/ +QFontComboBox::~QFontComboBox() +{ +} + +/*! + \property QFontComboBox::writingSystem + \brief the writing system that serves as a filter for the combobox + + If \a script is QFontDatabase::Any (the default), all fonts are + listed. + + \sa fontFilters +*/ + +void QFontComboBox::setWritingSystem(QFontDatabase::WritingSystem script) +{ + Q_D(QFontComboBox); + QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(view()->itemDelegate()); + if (delegate) + delegate->writingSystem = script; + d->_q_updateModel(); +} + +QFontDatabase::WritingSystem QFontComboBox::writingSystem() const +{ + QFontFamilyDelegate *delegate = qobject_cast<QFontFamilyDelegate *>(view()->itemDelegate()); + if (delegate) + return delegate->writingSystem; + return QFontDatabase::Any; +} + + +/*! + \enum QFontComboBox::FontFilter + + This enum can be used to only show certain types of fonts in the font combo box. + + \value AllFonts Show all fonts + \value ScalableFonts Show scalable fonts + \value NonScalableFonts Show non scalable fonts + \value MonospacedFonts Show monospaced fonts + \value ProportionalFonts Show proportional fonts +*/ + +/*! + \property QFontComboBox::fontFilters + \brief the filter for the combobox + + By default, all fonts are listed. + + \sa writingSystem +*/ +void QFontComboBox::setFontFilters(FontFilters filters) +{ + Q_D(QFontComboBox); + d->filters = filters; + d->_q_updateModel(); +} + +QFontComboBox::FontFilters QFontComboBox::fontFilters() const +{ + Q_D(const QFontComboBox); + return d->filters; +} + +/*! + \property QFontComboBox::currentFont + \brief the currently selected font + + \sa currentFontChanged(), currentIndex, currentText +*/ +QFont QFontComboBox::currentFont() const +{ + Q_D(const QFontComboBox); + return d->currentFont; +} + +void QFontComboBox::setCurrentFont(const QFont &font) +{ + Q_D(QFontComboBox); + if (font != d->currentFont) { + d->currentFont = font; + emit currentFontChanged(d->currentFont); + d->_q_updateModel(); + } +} + +/*! + \fn QFontComboBox::currentFontChanged(const QFont &font) + + This signal is emitted whenever the current font changes, with + the new \a font. + + \sa currentFont +*/ + +/*! + \reimp +*/ +bool QFontComboBox::event(QEvent *e) +{ + if (e->type() == QEvent::Resize) { + QListView *lview = qobject_cast<QListView*>(view()); + if (lview) + lview->window()->setFixedWidth(width() * 5 / 3); + } + return QComboBox::event(e); +} + +/*! + \reimp +*/ +QSize QFontComboBox::sizeHint() const +{ + QSize sz = QComboBox::sizeHint(); + QFontMetrics fm(font()); + sz.setWidth(fm.width(QLatin1Char('m'))*14); + return sz; +} + +QT_END_NAMESPACE + +#include "qfontcombobox.moc" +#include "moc_qfontcombobox.cpp" + +#endif // QT_NO_FONTCOMBOBOX diff --git a/src/gui/widgets/qfontcombobox.h b/src/gui/widgets/qfontcombobox.h new file mode 100644 index 0000000..4929ff3 --- /dev/null +++ b/src/gui/widgets/qfontcombobox.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTCOMBOBOX_H +#define QFONTCOMBOBOX_H + +#include <QtGui/qcombobox.h> +#include <QtGui/qfontdatabase.h> + +#ifndef QT_NO_FONTCOMBOBOX + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFontComboBoxPrivate; + +class Q_GUI_EXPORT QFontComboBox : public QComboBox +{ + Q_OBJECT + Q_FLAGS(FontFilters) + Q_PROPERTY(QFontDatabase::WritingSystem writingSystem READ writingSystem WRITE setWritingSystem) + Q_PROPERTY(FontFilters fontFilters READ fontFilters WRITE setFontFilters) + Q_PROPERTY(QFont currentFont READ currentFont WRITE setCurrentFont NOTIFY currentFontChanged) + Q_ENUMS(FontSelection) + +public: + explicit QFontComboBox(QWidget *parent = 0); + ~QFontComboBox(); + + void setWritingSystem(QFontDatabase::WritingSystem); + QFontDatabase::WritingSystem writingSystem() const; + + enum FontFilter { + AllFonts = 0, + ScalableFonts = 0x1, + NonScalableFonts = 0x2, + MonospacedFonts = 0x4, + ProportionalFonts = 0x8 + }; + Q_DECLARE_FLAGS(FontFilters, FontFilter) + + void setFontFilters(FontFilters filters); + FontFilters fontFilters() const; + + QFont currentFont() const; + QSize sizeHint() const; + +public Q_SLOTS: + void setCurrentFont(const QFont &f); + +Q_SIGNALS: + void currentFontChanged(const QFont &f); + +protected: + bool event(QEvent *e); + +private: + Q_DISABLE_COPY(QFontComboBox) + Q_DECLARE_PRIVATE(QFontComboBox) + Q_PRIVATE_SLOT(d_func(), void _q_currentChanged(const QString &)) + Q_PRIVATE_SLOT(d_func(), void _q_updateModel()) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFontComboBox::FontFilters) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_FONTCOMBOBOX +#endif diff --git a/src/gui/widgets/qframe.cpp b/src/gui/widgets/qframe.cpp new file mode 100644 index 0000000..6f81331 --- /dev/null +++ b/src/gui/widgets/qframe.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qframe.h" +#include "qbitmap.h" +#include "qdrawutil.h" +#include "qevent.h" +#include "qpainter.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qapplication.h" + +#include "qframe_p.h" + +QT_BEGIN_NAMESPACE + +QFramePrivate::QFramePrivate() + : frect(QRect(0, 0, 0, 0)), + frameStyle(QFrame::NoFrame | QFrame::Plain), + lineWidth(1), + midLineWidth(0), + frameWidth(0), + leftFrameWidth(0), rightFrameWidth(0), + topFrameWidth(0), bottomFrameWidth(0), + oldFrameStyle(QFrame::NoFrame | QFrame::Plain) +{ +} + +inline void QFramePrivate::init() +{ + setLayoutItemMargins(QStyle::SE_FrameLayoutItem); +} + +/*! + \class QFrame + \brief The QFrame class is the base class of widgets that can have a frame. + + \ingroup abstractwidgets + \mainclass + + QMenu uses this to "raise" the menu above the surrounding + screen. QProgressBar has a "sunken" look. QLabel has a flat look. + The frames of widgets like these can be changed. + + \snippet doc/src/snippets/code/src_gui_widgets_qframe.cpp 0 + + The QFrame class can also be used directly for creating simple + placeholder frames without any contents. + + The frame style is specified by a \l{QFrame::Shape}{frame shape} and + a \l{QFrame::Shadow}{shadow style} that is used to visually separate + the frame from surrounding widgets. These properties can be set + together using the setFrameStyle() function and read with frameStyle(). + + The frame shapes are \l NoFrame, \l Box, \l Panel, \l StyledPanel, + HLine and \l VLine; the shadow styles are \l Plain, \l Raised and + \l Sunken. + + A frame widget has three attributes that describe the thickness of the + border: \l lineWidth, \l midLineWidth, and \l frameWidth. + + \list + \o The line width is the width of the frame border. It can be modified + to customize the frame's appearance. + + \o The mid-line width specifies the width of an extra line in the + middle of the frame, which uses a third color to obtain a special + 3D effect. Notice that a mid-line is only drawn for \l Box, \l + HLine and \l VLine frames that are raised or sunken. + + \o The frame width is determined by the frame style, and the frameWidth() + function is used to obtain the value defined for the style used. + \endlist + + The margin between the frame and the contents of the frame can be + customized with the QWidget::setContentsMargins() function. + + \target picture + This table shows some of the combinations of styles and line widths: + + \image frames.png Table of frame styles +*/ + + +/*! + \enum QFrame::Shape + + This enum type defines the shapes of frame available. + + \value NoFrame QFrame draws nothing + \value Box QFrame draws a box around its contents + \value Panel QFrame draws a panel to make the contents appear + raised or sunken + \value StyledPanel draws a rectangular panel with a look that + depends on the current GUI style. It can be raised or sunken. + \value HLine QFrame draws a horizontal line that frames nothing + (useful as separator) + \value VLine QFrame draws a vertical line that frames nothing + (useful as separator) + \value WinPanel draws a rectangular panel that can be raised or + sunken like those in Windows 95. Specifying this shape sets + the line width to 2 pixels. WinPanel is provided for compatibility. + For GUI style independence we recommend using StyledPanel instead. + + \omitvalue GroupBoxPanel + \omitvalue ToolBarPanel + \omitvalue MenuBarPanel + \omitvalue PopupPanel + \omitvalue LineEditPanel + \omitvalue TabWidgetPanel + + When it does not call QStyle, Shape interacts with QFrame::Shadow, + the lineWidth() and the midLineWidth() to create the total result. + See the picture of the frames in the main class documentation. + + \sa QFrame::Shadow QFrame::style() QStyle::drawPrimitive() +*/ + + +/*! + \enum QFrame::Shadow + + This enum type defines the types of shadow that are used to give + a 3D effect to frames. + + \value Plain the frame and contents appear level with the + surroundings; draws using the palette QPalette::WindowText color + (without any 3D effect) + + \value Raised the frame and contents appear raised; draws a 3D + raised line using the light and dark colors of the current color + group + \value Sunken the frame and contents appear sunken; draws a 3D + sunken line using the light and dark colors of the current color + group + + Shadow interacts with QFrame::Shape, the lineWidth() and the + midLineWidth(). See the picture of the frames in the main class + documentation. + + \sa QFrame::Shape lineWidth() midLineWidth() +*/ + +/*! + \enum QFrame::StyleMask + + This enum defines two constants that can be used to extract the + two components of frameStyle(): + + \value Shadow_Mask The \l Shadow part of frameStyle() + \value Shape_Mask The \l Shape part of frameStyle() + + \omitvalue MShadow + \omitvalue MShape + + Normally, you don't need to use these, since frameShadow() and + frameShape() already extract the \l Shadow and the \l Shape parts + of frameStyle(). + + \sa frameStyle(), setFrameStyle() +*/ + +/*! + Constructs a frame widget with frame style \l NoFrame and a + 1-pixel frame width. + + The \a parent and \a f arguments are passed to the QWidget + constructor. +*/ + +QFrame::QFrame(QWidget* parent, Qt::WindowFlags f) + : QWidget(*new QFramePrivate, parent, f) +{ + Q_D(QFrame); + d->init(); +} + +/*! \internal */ +QFrame::QFrame(QFramePrivate &dd, QWidget* parent, Qt::WindowFlags f) + : QWidget(dd, parent, f) +{ + Q_D(QFrame); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QFrame::QFrame(QWidget *parent, const char *name, Qt::WindowFlags f) + : QWidget(*new QFramePrivate, parent, f) +{ + Q_D(QFrame); + setObjectName(QString::fromAscii(name)); + d->init(); +} +#endif + +/*! + Destroys the frame. + */ +QFrame::~QFrame() +{ +} + +/*! + Returns the frame style. + + The default value is QFrame::NoFrame. + + \sa setFrameStyle(), frameShape(), frameShadow() +*/ +int QFrame::frameStyle() const +{ + Q_D(const QFrame); + return d->frameStyle; +} + +/*! + \property QFrame::frameShape + \brief the frame shape value from the frame style + + \sa frameStyle(), frameShadow() +*/ + +QFrame::Shape QFrame::frameShape() const +{ + Q_D(const QFrame); + return (Shape) (d->frameStyle & Shape_Mask); +} + +void QFrame::setFrameShape(QFrame::Shape s) +{ + Q_D(QFrame); + setFrameStyle((d->frameStyle & Shadow_Mask) | s); +} + + +/*! + \property QFrame::frameShadow + \brief the frame shadow value from the frame style + + \sa frameStyle(), frameShape() +*/ +QFrame::Shadow QFrame::frameShadow() const +{ + Q_D(const QFrame); + return (Shadow) (d->frameStyle & Shadow_Mask); +} + +void QFrame::setFrameShadow(QFrame::Shadow s) +{ + Q_D(QFrame); + setFrameStyle((d->frameStyle & Shape_Mask) | s); +} + +/*! + Sets the frame style to \a style. + + The \a style is the bitwise OR between a frame shape and a frame + shadow style. See the picture of the frames in the main class + documentation. + + The frame shapes are given in \l{QFrame::Shape} and the shadow + styles in \l{QFrame::Shadow}. + + If a mid-line width greater than 0 is specified, an additional + line is drawn for \l Raised or \l Sunken \l Box, \l HLine, and \l + VLine frames. The mid-color of the current color group is used for + drawing middle lines. + + \sa frameStyle() +*/ + +void QFrame::setFrameStyle(int style) +{ + Q_D(QFrame); + if (!testAttribute(Qt::WA_WState_OwnSizePolicy)) { + QSizePolicy sp; + + switch (style & Shape_Mask) { + case HLine: + sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::Line); + break; + case VLine: + sp = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum, QSizePolicy::Line); + break; + default: + sp = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::Frame); + } + setSizePolicy(sp); + setAttribute(Qt::WA_WState_OwnSizePolicy, false); + } + d->frameStyle = (short)style; + update(); + d->updateFrameWidth(); + d->oldFrameStyle = (short)style; +} + +/*! + \property QFrame::lineWidth + \brief the line width + + Note that the \e total line width for frames used as separators + (\l HLine and \l VLine) is specified by \l frameWidth. + + The default value is 1. + + \sa midLineWidth, frameWidth +*/ + +void QFrame::setLineWidth(int w) +{ + Q_D(QFrame); + if (short(w) == d->lineWidth) + return; + d->lineWidth = short(w); + d->updateFrameWidth(); +} + +int QFrame::lineWidth() const +{ + Q_D(const QFrame); + return d->lineWidth; +} + +/*! + \property QFrame::midLineWidth + \brief the width of the mid-line + + The default value is 0. + + \sa lineWidth, frameWidth +*/ + +void QFrame::setMidLineWidth(int w) +{ + Q_D(QFrame); + if (short(w) == d->midLineWidth) + return; + d->midLineWidth = short(w); + d->updateFrameWidth(); +} + +int QFrame::midLineWidth() const +{ + Q_D(const QFrame); + return d->midLineWidth; +} + +/*! + \internal + Updates the frame widths from the style. +*/ +void QFramePrivate::updateStyledFrameWidths() +{ + Q_Q(const QFrame); + QStyleOptionFrameV3 opt; + opt.initFrom(q); + opt.lineWidth = lineWidth; + opt.midLineWidth = midLineWidth; + opt.frameShape = QFrame::Shape(frameStyle & QFrame::Shape_Mask); + + QRect cr = q->style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt, q); + leftFrameWidth = cr.left() - opt.rect.left(); + topFrameWidth = cr.top() - opt.rect.top(); + rightFrameWidth = opt.rect.right() - cr.right(), + bottomFrameWidth = opt.rect.bottom() - cr.bottom(); + frameWidth = qMax(qMax(leftFrameWidth, rightFrameWidth), + qMax(topFrameWidth, bottomFrameWidth)); +} + +/*! + \internal + Updated the frameWidth parameter. +*/ + +void QFramePrivate::updateFrameWidth() +{ + Q_Q(QFrame); + QRect fr = q->frameRect(); + updateStyledFrameWidths(); + q->setFrameRect(fr); + setLayoutItemMargins(QStyle::SE_FrameLayoutItem); +} + +/*! + \property QFrame::frameWidth + \brief the width of the frame that is drawn. + + Note that the frame width depends on the \l{QFrame::setFrameStyle()}{frame style}, + not only the line width and the mid-line width. For example, the style specified + by \l NoFrame always has a frame width of 0, whereas the style \l Panel has a + frame width equivalent to the line width. + + \sa lineWidth(), midLineWidth(), frameStyle() +*/ +int QFrame::frameWidth() const +{ + Q_D(const QFrame); + return d->frameWidth; +} + + +/*! + \property QFrame::frameRect + \brief the frame's rectangle + + The frame's rectangle is the rectangle the frame is drawn in. By + default, this is the entire widget. Setting the rectangle does + does \e not cause a widget update. The frame rectangle is + automatically adjusted when the widget changes size. + + If you set the rectangle to a null rectangle (for example, + QRect(0, 0, 0, 0)), then the resulting frame rectangle is + equivalent to the \link QWidget::rect() widget rectangle\endlink. +*/ + +QRect QFrame::frameRect() const +{ + Q_D(const QFrame); + QRect fr = contentsRect(); + fr.adjust(-d->leftFrameWidth, -d->topFrameWidth, d->rightFrameWidth, d->bottomFrameWidth); + return fr; +} + +void QFrame::setFrameRect(const QRect &r) +{ + Q_D(QFrame); + QRect cr = r.isValid() ? r : rect(); + cr.adjust(d->leftFrameWidth, d->topFrameWidth, -d->rightFrameWidth, -d->bottomFrameWidth); + setContentsMargins(cr.left(), cr.top(), rect().right() - cr.right(), rect().bottom() - cr.bottom()); +} + +/*!\reimp +*/ +QSize QFrame::sizeHint() const +{ + Q_D(const QFrame); + // Returns a size hint for the frame - for HLine and VLine + // shapes, this is stretchable one way and 3 pixels wide the + // other. For other shapes, QWidget::sizeHint() is used. + switch (d->frameStyle & Shape_Mask) { + case HLine: + return QSize(-1,3); + case VLine: + return QSize(3,-1); + default: + return QWidget::sizeHint(); + } +} + +/*!\reimp +*/ + +void QFrame::paintEvent(QPaintEvent *) +{ + QPainter paint(this); + drawFrame(&paint); +} + +/*! + \internal + + Mostly for the sake of Q3Frame + */ +void QFrame::drawFrame(QPainter *p) +{ + Q_D(QFrame); + QStyleOptionFrameV3 opt; + opt.init(this); + int frameShape = d->frameStyle & QFrame::Shape_Mask; + int frameShadow = d->frameStyle & QFrame::Shadow_Mask; + opt.frameShape = Shape(int(opt.frameShape) | frameShape); + opt.rect = frameRect(); + switch (frameShape) { + case QFrame::Box: + case QFrame::HLine: + case QFrame::VLine: + case QFrame::StyledPanel: + case QFrame::Panel: + opt.lineWidth = d->lineWidth; + opt.midLineWidth = d->midLineWidth; + break; + default: + // most frame styles do not handle customized line and midline widths + // (see updateFrameWidth()). + opt.lineWidth = d->frameWidth; + break; + } + + if (frameShadow == Sunken) + opt.state |= QStyle::State_Sunken; + else if (frameShadow == Raised) + opt.state |= QStyle::State_Raised; + + style()->drawControl(QStyle::CE_ShapedFrame, &opt, p, this); +} + + +/*!\reimp + */ +void QFrame::changeEvent(QEvent *ev) +{ + Q_D(QFrame); + if (ev->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || ev->type() == QEvent::MacSizeChange +#endif + ) + d->updateFrameWidth(); + QWidget::changeEvent(ev); +} + +/*! \reimp */ +bool QFrame::event(QEvent *e) +{ + if (e->type() == QEvent::ParentChange) + d_func()->updateFrameWidth(); + bool result = QWidget::event(e); + //this has to be done after the widget has been polished + if (e->type() == QEvent::Polish) + d_func()->updateFrameWidth(); + return result; +} + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qframe.h b/src/gui/widgets/qframe.h new file mode 100644 index 0000000..7bffb59 --- /dev/null +++ b/src/gui/widgets/qframe.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFRAME_H +#define QFRAME_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFramePrivate; + +class Q_GUI_EXPORT QFrame : public QWidget +{ + Q_OBJECT + + Q_ENUMS(Shape Shadow) + Q_PROPERTY(Shape frameShape READ frameShape WRITE setFrameShape) + Q_PROPERTY(Shadow frameShadow READ frameShadow WRITE setFrameShadow) + Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth) + Q_PROPERTY(int midLineWidth READ midLineWidth WRITE setMidLineWidth) + Q_PROPERTY(int frameWidth READ frameWidth) + Q_PROPERTY(QRect frameRect READ frameRect WRITE setFrameRect DESIGNABLE false) + +public: + explicit QFrame(QWidget* parent = 0, Qt::WindowFlags f = 0); + ~QFrame(); + + int frameStyle() const; + void setFrameStyle(int); + + int frameWidth() const; + + QSize sizeHint() const; + + enum Shape { + NoFrame = 0, // no frame + Box = 0x0001, // rectangular box + Panel = 0x0002, // rectangular panel + WinPanel = 0x0003, // rectangular panel (Windows) + HLine = 0x0004, // horizontal line + VLine = 0x0005, // vertical line + StyledPanel = 0x0006 // rectangular panel depending on the GUI style + +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + ,PopupPanel = StyledPanel, // rectangular panel depending on the GUI style + MenuBarPanel = StyledPanel, + ToolBarPanel = StyledPanel, + LineEditPanel = StyledPanel, + TabWidgetPanel = StyledPanel, + GroupBoxPanel = StyledPanel +#endif + }; + enum Shadow { + Plain = 0x0010, // plain line + Raised = 0x0020, // raised shadow effect + Sunken = 0x0030 // sunken shadow effect + }; + + enum StyleMask { + Shadow_Mask = 0x00f0, // mask for the shadow + Shape_Mask = 0x000f // mask for the shape +#if defined(QT3_SUPPORT) + ,MShadow = Shadow_Mask, + MShape = Shape_Mask +#endif + }; + + Shape frameShape() const; + void setFrameShape(Shape); + Shadow frameShadow() const; + void setFrameShadow(Shadow); + + int lineWidth() const; + void setLineWidth(int); + + int midLineWidth() const; + void setMidLineWidth(int); + + QRect frameRect() const; + void setFrameRect(const QRect &); + +protected: + bool event(QEvent *e); + void paintEvent(QPaintEvent *); + void changeEvent(QEvent *); + void drawFrame(QPainter *); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QFrame(QWidget* parent, const char* name, Qt::WindowFlags f = 0); +#endif + +protected: + QFrame(QFramePrivate &dd, QWidget* parent = 0, Qt::WindowFlags f = 0); + +private: + Q_DISABLE_COPY(QFrame) + Q_DECLARE_PRIVATE(QFrame) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFRAME_H diff --git a/src/gui/widgets/qframe_p.h b/src/gui/widgets/qframe_p.h new file mode 100644 index 0000000..4fd341d --- /dev/null +++ b/src/gui/widgets/qframe_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFRAME_P_H +#define QFRAME_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 "qframe.h" + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QFramePrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QFrame) +public: + QFramePrivate(); + + void updateFrameWidth(); + void updateStyledFrameWidths(); + + QRect frect; + int frameStyle; + short lineWidth; + short midLineWidth; + short frameWidth; + short leftFrameWidth, rightFrameWidth; + short topFrameWidth, bottomFrameWidth; + short oldFrameStyle; + + inline void init(); + +}; + +QT_END_NAMESPACE + +#endif // QFRAME_P_H diff --git a/src/gui/widgets/qgroupbox.cpp b/src/gui/widgets/qgroupbox.cpp new file mode 100644 index 0000000..6a82483 --- /dev/null +++ b/src/gui/widgets/qgroupbox.cpp @@ -0,0 +1,792 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgroupbox.h" +#ifndef QT_NO_GROUPBOX +#include "qapplication.h" +#include "qbitmap.h" +#include "qdrawutil.h" +#include "qevent.h" +#include "qlayout.h" +#include "qradiobutton.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qstylepainter.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#include <private/qwidget_p.h> + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +class QGroupBoxPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QGroupBox) + +public: + void skip(); + void init(); + void calculateFrame(); + QString title; + int align; +#ifndef QT_NO_SHORTCUT + int shortcutId; +#endif + + void _q_fixFocus(Qt::FocusReason reason); + void _q_setChildrenEnabled(bool b); + void click(); + bool flat; + bool checkable; + bool checked; + bool hover; + bool overCheckBox; + QStyle::SubControl pressedControl; +}; + +/*! + Initialize \a option with the values from this QGroupBox. This method + is useful for subclasses when they need a QStyleOptionGroupBox, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QGroupBox::initStyleOption(QStyleOptionGroupBox *option) const +{ + if (!option) + return; + + Q_D(const QGroupBox); + option->initFrom(this); + option->text = d->title; + option->lineWidth = 1; + option->midLineWidth = 0; + option->textAlignment = Qt::Alignment(d->align); + option->activeSubControls |= d->pressedControl; + option->subControls = QStyle::SC_GroupBoxFrame; + + if (d->hover) + option->state |= QStyle::State_MouseOver; + else + option->state &= ~QStyle::State_MouseOver; + + if (d->flat) + option->features |= QStyleOptionFrameV2::Flat; + + if (d->checkable) { + option->subControls |= QStyle::SC_GroupBoxCheckBox; + option->state |= (d->checked ? QStyle::State_On : QStyle::State_Off); + if ((d->pressedControl == QStyle::SC_GroupBoxCheckBox + || d->pressedControl == QStyle::SC_GroupBoxLabel) && (d->hover || d->overCheckBox)) + option->state |= QStyle::State_Sunken; + } + + if (!option->palette.isBrushSet(isEnabled() ? QPalette::Active : + QPalette::Disabled, QPalette::WindowText)) + option->textColor = QColor(style()->styleHint(QStyle::SH_GroupBox_TextLabelColor, + option, this)); + + if (!d->title.isEmpty()) + option->subControls |= QStyle::SC_GroupBoxLabel; +} + +void QGroupBoxPrivate::click() +{ + Q_Q(QGroupBox); + + QPointer<QGroupBox> guard(q); + q->setChecked(!checked); + if (!guard) + return; + emit q->clicked(checked); +} + +/*! + \class QGroupBox + \brief The QGroupBox widget provides a group box frame with a title. + + \ingroup organizers + \ingroup geomanagement + \ingroup appearance + \mainclass + + A group box provides a frame, a title and a keyboard shortcut, and + displays various other widgets inside itself. The title is on top, + the keyboard shortcut moves keyboard focus to one of the group + box's child widgets. + + QGroupBox also lets you set the \l title (normally set in the + constructor) and the title's \l alignment. Group boxes can be + \l checkable; child widgets in checkable group boxes are enabled or + disabled depending on whether or not the group box is \l checked. + + You can minimize the space consumption of a group box by enabling + the \l flat property. In most \l{QStyle}{styles}, enabling this + property results in the removal of the left, right and bottom + edges of the frame. + + QGroupBox doesn't automatically lay out the child widgets (which + are often \l{QCheckBox}es or \l{QRadioButton}s but can be any + widgets). The following example shows how we can set up a + QGroupBox with a layout: + + \snippet examples/widgets/groupbox/window.cpp 2 + + \table 100% + \row \o \inlineimage windowsxp-groupbox.png Screenshot of a Windows XP style group box + \o \inlineimage macintosh-groupbox.png Screenshot of a Macintosh style group box + \o \inlineimage plastique-groupbox.png Screenshot of a Plastique style group box + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} group box. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} group box. + \o A \l{Plastique Style Widget Gallery}{Plastique style} group box. + \endtable + + \sa QButtonGroup, {Group Box Example} +*/ + + + +/*! + Constructs a group box widget with the given \a parent but with no title. +*/ + +QGroupBox::QGroupBox(QWidget *parent) + : QWidget(*new QGroupBoxPrivate, parent, 0) +{ + Q_D(QGroupBox); + d->init(); +} + +/*! + Constructs a group box with the given \a title and \a parent. +*/ + +QGroupBox::QGroupBox(const QString &title, QWidget *parent) + : QWidget(*new QGroupBoxPrivate, parent, 0) +{ + Q_D(QGroupBox); + d->init(); + setTitle(title); +} + + +/*! + Destroys the group box. +*/ +QGroupBox::~QGroupBox() +{ +} + +void QGroupBoxPrivate::init() +{ + Q_Q(QGroupBox); + align = Qt::AlignLeft; +#ifndef QT_NO_SHORTCUT + shortcutId = 0; +#endif + flat = false; + checkable = false; + checked = true; + hover = false; + overCheckBox = false; + pressedControl = QStyle::SC_None; + calculateFrame(); + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, + QSizePolicy::GroupBox)); +} + +void QGroupBox::setTitle(const QString &title) +{ + Q_D(QGroupBox); + if (d->title == title) // no change + return; + d->title = title; +#ifndef QT_NO_SHORTCUT + releaseShortcut(d->shortcutId); + d->shortcutId = grabShortcut(QKeySequence::mnemonic(title)); +#endif + d->calculateFrame(); + + update(); + updateGeometry(); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::NameChanged); +#endif +} + +/*! + \property QGroupBox::title + \brief the group box title text + + The group box title text will have a keyboard shortcut if the title + contains an ampersand ('&') followed by a letter. + + \snippet doc/src/snippets/code/src_gui_widgets_qgroupbox.cpp 0 + + In the example above, \key Alt+U moves the keyboard focus to the + group box. See the \l {QShortcut#mnemonic}{QShortcut} + documentation for details (to display an actual ampersand, use + '&&'). + + There is no default title text. + + \sa alignment +*/ + +QString QGroupBox::title() const +{ + Q_D(const QGroupBox); + return d->title; +} + +/*! + \property QGroupBox::alignment + \brief the alignment of the group box title. + + Most styles place the title at the top of the frame. The horizontal + alignment of the title can be specified using single values from + the following list: + + \list + \i Qt::AlignLeft aligns the title text with the left-hand side of the group box. + \i Qt::AlignRight aligns the title text with the right-hand side of the group box. + \i Qt::AlignHCenter aligns the title text with the horizontal center of the group box. + \endlist + + The default alignment is Qt::AlignLeft. + + \sa Qt::Alignment +*/ +Qt::Alignment QGroupBox::alignment() const +{ + Q_D(const QGroupBox); + return QFlag(d->align); +} + +void QGroupBox::setAlignment(int alignment) +{ + Q_D(QGroupBox); + d->align = alignment; + updateGeometry(); + update(); +} + +/*! \reimp +*/ +void QGroupBox::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); +} + +/*! \reimp +*/ + +void QGroupBox::paintEvent(QPaintEvent *) +{ + QStylePainter paint(this); + QStyleOptionGroupBox option; + initStyleOption(&option); + paint.drawComplexControl(QStyle::CC_GroupBox, option); +} + +/*! \reimp */ +bool QGroupBox::event(QEvent *e) +{ + Q_D(QGroupBox); +#ifndef QT_NO_SHORTCUT + if (e->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(e); + if (se->shortcutId() == d->shortcutId) { + if (!isCheckable()) { + d->_q_fixFocus(Qt::ShortcutFocusReason); + } else { + d->click(); + setFocus(Qt::ShortcutFocusReason); + } + return true; + } + } +#endif + QStyleOptionGroupBox box; + initStyleOption(&box); + switch (e->type()) { + case QEvent::HoverEnter: + case QEvent::HoverMove: { + QStyle::SubControl control = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box, + static_cast<QHoverEvent *>(e)->pos(), + this); + bool oldHover = d->hover; + d->hover = d->checkable && (control == QStyle::SC_GroupBoxLabel || control == QStyle::SC_GroupBoxCheckBox); + if (oldHover != d->hover) { + QRect rect = style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this) + | style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxLabel, this); + update(rect); + } + return true; + } + case QEvent::HoverLeave: + d->hover = false; + if (d->checkable) { + QRect rect = style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this) + | style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxLabel, this); + update(rect); + } + return true; + case QEvent::KeyPress: { + QKeyEvent *k = static_cast<QKeyEvent*>(e); + if (!k->isAutoRepeat() && (k->key() == Qt::Key_Select || k->key() == Qt::Key_Space)) { + d->pressedControl = QStyle::SC_GroupBoxCheckBox; + update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)); + return true; + } + break; + } + case QEvent::KeyRelease: { + QKeyEvent *k = static_cast<QKeyEvent*>(e); + if (!k->isAutoRepeat() && (k->key() == Qt::Key_Select || k->key() == Qt::Key_Space)) { + bool toggle = (d->pressedControl == QStyle::SC_GroupBoxLabel + || d->pressedControl == QStyle::SC_GroupBoxCheckBox); + d->pressedControl = QStyle::SC_None; + if (toggle) + d->click(); + return true; + } + break; + } + default: + break; + } + return QWidget::event(e); +} + +/*!\reimp */ +void QGroupBox::childEvent(QChildEvent *c) +{ + Q_D(QGroupBox); + if (c->type() != QEvent::ChildAdded || !c->child()->isWidgetType()) + return; + QWidget *w = (QWidget*)c->child(); + if (d->checkable) { + if (d->checked) { + if (!w->testAttribute(Qt::WA_ForceDisabled)) + w->setEnabled(true); + } else { + if (w->isEnabled()) { + w->setEnabled(false); + w->setAttribute(Qt::WA_ForceDisabled, false); + } + } + } +} + + +/*! + \internal + + This private slot finds a widget in this group box that can accept + focus, and gives the focus to that widget. +*/ + +void QGroupBoxPrivate::_q_fixFocus(Qt::FocusReason reason) +{ + Q_Q(QGroupBox); + QWidget *fw = q->focusWidget(); + if (!fw) { + QWidget * best = 0; + QWidget * candidate = 0; + QWidget * w = q; + while ((w = w->nextInFocusChain()) != q) { + if (q->isAncestorOf(w) && (w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus && w->isVisibleTo(q)) { + if (!best && qobject_cast<QRadioButton*>(w) && ((QRadioButton*)w)->isChecked()) + // we prefer a checked radio button or a widget that + // already has focus, if there is one + best = w; + else + if (!candidate) + // but we'll accept anything that takes focus + candidate = w; + } + } + if (best) + fw = best; + else + if (candidate) + fw = candidate; + } + if (fw) + fw->setFocus(reason); +} + +/* + Sets the right frame rect depending on the title. +*/ +void QGroupBoxPrivate::calculateFrame() +{ + Q_Q(QGroupBox); + QStyleOptionGroupBox box; + q->initStyleOption(&box); + QRect contentsRect = q->style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxContents, q); + q->setContentsMargins(contentsRect.left() - box.rect.left(), contentsRect.top() - box.rect.top(), + box.rect.right() - contentsRect.right(), box.rect.bottom() - contentsRect.bottom()); + setLayoutItemMargins(QStyle::SE_GroupBoxLayoutItem, &box); +} + +/*! \reimp + */ +void QGroupBox::focusInEvent(QFocusEvent *fe) +{ // note no call to super + Q_D(QGroupBox); + if (focusPolicy() == Qt::NoFocus) { + d->_q_fixFocus(fe->reason()); + } else { + QStyleOptionGroupBox box; + initStyleOption(&box); + QRect rect = style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this) + | style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxLabel, this); + update(rect); + } +} + + +/*! + \reimp +*/ +QSize QGroupBox::minimumSizeHint() const +{ + Q_D(const QGroupBox); + QStyleOptionGroupBox option; + initStyleOption(&option); + + QFontMetrics metrics(fontMetrics()); + + int baseWidth = metrics.width(d->title) + metrics.width(QLatin1Char(' ')); + int baseHeight = metrics.height(); + if (d->checkable) { + baseWidth += style()->pixelMetric(QStyle::PM_IndicatorWidth); + baseWidth += style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing); + baseHeight = qMax(baseHeight, style()->pixelMetric(QStyle::PM_IndicatorHeight)); + } + + QSize size = style()->sizeFromContents(QStyle::CT_GroupBox, &option, QSize(baseWidth, baseHeight), this); + return size.expandedTo(QWidget::minimumSizeHint()); +} + +/*! + \property QGroupBox::flat + \brief whether the group box is painted flat or has a frame + + A group box usually consists of a surrounding frame with a title + at the top. If this property is enabled, only the top part of the frame is + drawn in most styles; otherwise the whole frame is drawn. + + By default, this property is disabled; i.e. group boxes are not flat unless + explicitly specified. + + \bold{Note:} In some styles, flat and non-flat group boxes have similar + representations and may not be as distinguishable as they are in other + styles. + + \sa title +*/ +bool QGroupBox::isFlat() const +{ + Q_D(const QGroupBox); + return d->flat; +} + +void QGroupBox::setFlat(bool b) +{ + Q_D(QGroupBox); + if (d->flat == b) + return; + d->flat = b; + updateGeometry(); + update(); +} + + +/*! + \property QGroupBox::checkable + \brief whether the group box has a checkbox in its title + + If this property is true, the group box displays its title using + a checkbox in place of an ordinary label. If the checkbox is checked, + the group box's children are enabled; otherwise they are disabled and + inaccessible. + + By default, group boxes are not checkable. + + If this property is enabled for a group box, it will also be initially + checked to ensure that its contents are enabled. + + \sa checked +*/ +void QGroupBox::setCheckable(bool checkable) +{ + Q_D(QGroupBox); + + bool wasCheckable = d->checkable; + d->checkable = checkable; + + if (checkable) { + setChecked(true); + if (!wasCheckable) { + setFocusPolicy(Qt::StrongFocus); + d->_q_setChildrenEnabled(true); + updateGeometry(); + } + } else { + if (wasCheckable) { + setFocusPolicy(Qt::NoFocus); + d->_q_setChildrenEnabled(true); + updateGeometry(); + } + d->_q_setChildrenEnabled(true); + } + + if (wasCheckable != checkable) { + d->calculateFrame(); + update(); + } +} + +bool QGroupBox::isCheckable() const +{ + Q_D(const QGroupBox); + return d->checkable; +} + + +bool QGroupBox::isChecked() const +{ + Q_D(const QGroupBox); + return d->checkable && d->checked; +} + + +/*! + \fn void QGroupBox::toggled(bool on) + + If the group box is checkable, this signal is emitted when the check box + is toggled. \a on is true if the check box is checked; otherwise it is false. + + \sa checkable +*/ + + +/*! + \fn void QGroupBox::clicked(bool checked) + \since 4.2 + + This signal is emitted when the check box is activated (i.e. pressed down + then released while the mouse cursor is inside the button), or when the + shortcut key is typed, Notably, this signal is \e not emitted if you call + setChecked(). + + If the check box is checked \a checked is true; it is false if the check + box is unchecked. + + \sa checkable, toggled(), checked +*/ + +/*! + \property QGroupBox::checked + \brief whether the group box is checked + + If the group box is checkable, it is displayed with a check box. + If the check box is checked, the group box's children are enabled; + otherwise the children are disabled and are inaccessible to the user. + + By default, checkable group boxes are also checked. + + \sa checkable +*/ +void QGroupBox::setChecked(bool b) +{ + Q_D(QGroupBox); + if (d->checkable) { + if (d->checked != b) + update(); + bool wasToggled = (b != d->checked); + d->checked = b; + if (wasToggled) { + d->_q_setChildrenEnabled(b); + emit toggled(b); + } + } +} + +/* + sets all children of the group box except the qt_groupbox_checkbox + to either disabled/enabled +*/ +void QGroupBoxPrivate::_q_setChildrenEnabled(bool b) +{ + Q_Q(QGroupBox); + QObjectList childList = q->children(); + for (int i = 0; i < childList.size(); ++i) { + QObject *o = childList.at(i); + if (o->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(o); + if (b) { + if (!w->testAttribute(Qt::WA_ForceDisabled)) + w->setEnabled(true); + } else { + if (w->isEnabled()) { + w->setEnabled(false); + w->setAttribute(Qt::WA_ForceDisabled, false); + } + } + } + } +} + +/*! \reimp */ +void QGroupBox::changeEvent(QEvent *ev) +{ + Q_D(QGroupBox); + if (ev->type() == QEvent::EnabledChange) { + if (d->checkable && isEnabled()) { + // we are being enabled - disable children + if (!d->checked) + d->_q_setChildrenEnabled(false); + } + } else if (ev->type() == QEvent::FontChange +#ifdef Q_WS_MAC + || ev->type() == QEvent::MacSizeChange +#endif + || ev->type() == QEvent::StyleChange) { + d->calculateFrame(); + } + QWidget::changeEvent(ev); +} + +/*! \reimp */ +void QGroupBox::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + + Q_D(QGroupBox); + QStyleOptionGroupBox box; + initStyleOption(&box); + d->pressedControl = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box, + event->pos(), this); + if (d->checkable && (d->pressedControl & (QStyle::SC_GroupBoxCheckBox | QStyle::SC_GroupBoxLabel))) { + d->overCheckBox = true; + update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)); + } +} + +/*! \reimp */ +void QGroupBox::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QGroupBox); + QStyleOptionGroupBox box; + initStyleOption(&box); + QStyle::SubControl pressed = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box, + event->pos(), this); + bool oldOverCheckBox = d->overCheckBox; + d->overCheckBox = (pressed == QStyle::SC_GroupBoxCheckBox || pressed == QStyle::SC_GroupBoxLabel); + if (d->checkable && (d->pressedControl == QStyle::SC_GroupBoxCheckBox || d->pressedControl == QStyle::SC_GroupBoxLabel) + && (d->overCheckBox != oldOverCheckBox)) + update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)); +} + +/*! \reimp */ +void QGroupBox::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + + Q_D(QGroupBox); + QStyleOptionGroupBox box; + initStyleOption(&box); + QStyle::SubControl released = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box, + event->pos(), this); + bool toggle = d->checkable && (released == QStyle::SC_GroupBoxLabel + || released == QStyle::SC_GroupBoxCheckBox); + d->pressedControl = QStyle::SC_None; + d->overCheckBox = false; + if (toggle) + d->click(); + else if (d->checkable) + update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QGroupBox::QGroupBox(QWidget *parent, const char *name) + : QWidget(*new QGroupBoxPrivate, parent, 0) +{ + Q_D(QGroupBox); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QGroupBox::QGroupBox(const QString &title, QWidget *parent, const char *name) + : QWidget(*new QGroupBoxPrivate, parent, 0) +{ + Q_D(QGroupBox); + setObjectName(QString::fromAscii(name)); + d->init(); + setTitle(title); +} +#endif // QT3_SUPPORT + +QT_END_NAMESPACE + +#include "moc_qgroupbox.cpp" + +#endif //QT_NO_GROUPBOX diff --git a/src/gui/widgets/qgroupbox.h b/src/gui/widgets/qgroupbox.h new file mode 100644 index 0000000..85f1a0d --- /dev/null +++ b/src/gui/widgets/qgroupbox.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGROUPBOX_H +#define QGROUPBOX_H + +#include <QtGui/qframe.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_GROUPBOX + +class QGroupBoxPrivate; +class QStyleOptionGroupBox; +class Q_GUI_EXPORT QGroupBox : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(bool flat READ isFlat WRITE setFlat) + Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable) + Q_PROPERTY(bool checked READ isChecked WRITE setChecked DESIGNABLE isCheckable NOTIFY toggled USER true) +public: + explicit QGroupBox(QWidget* parent=0); + explicit QGroupBox(const QString &title, QWidget* parent=0); + ~QGroupBox(); + + QString title() const; + void setTitle(const QString &title); + + Qt::Alignment alignment() const; + void setAlignment(int alignment); + + QSize minimumSizeHint() const; + + bool isFlat() const; + void setFlat(bool flat); + bool isCheckable() const; + void setCheckable(bool checkable); + bool isChecked() const; + +public Q_SLOTS: + void setChecked(bool checked); + +Q_SIGNALS: + void clicked(bool checked = false); + void toggled(bool); + +protected: + bool event(QEvent *event); + void childEvent(QChildEvent *event); + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); + void focusInEvent(QFocusEvent *event); + void changeEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void initStyleOption(QStyleOptionGroupBox *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QGroupBox(QWidget* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QGroupBox(const QString &title, QWidget* parent, const char* name); +#endif + +private: + Q_DISABLE_COPY(QGroupBox) + Q_DECLARE_PRIVATE(QGroupBox) + Q_PRIVATE_SLOT(d_func(), void _q_setChildrenEnabled(bool b)) +}; + +#endif // QT_NO_GROUPBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGROUPBOX_H diff --git a/src/gui/widgets/qlabel.cpp b/src/gui/widgets/qlabel.cpp new file mode 100644 index 0000000..63c1315 --- /dev/null +++ b/src/gui/widgets/qlabel.cpp @@ -0,0 +1,1606 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpainter.h" +#include "qevent.h" +#include "qdrawutil.h" +#include "qapplication.h" +#include "qabstractbutton.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include <limits.h> +#include "qaction.h" +#include "qclipboard.h" +#include <qdebug.h> +#include <qurl.h> +#include "qlabel_p.h" +#include "private/qstylesheetstyle_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QLabel + \brief The QLabel widget provides a text or image display. + + \ingroup basicwidgets + \ingroup text + \mainclass + + QLabel is used for displaying text or an image. No user + interaction functionality is provided. The visual appearance of + the label can be configured in various ways, and it can be used + for specifying a focus mnemonic key for another widget. + + A QLabel can contain any of the following content types: + + \table + \header \o Content \o Setting + \row \o Plain text + \o Pass a QString to setText(). + \row \o Rich text + \o Pass a QString that contains rich text to setText(). + \row \o A pixmap + \o Pass a QPixmap to setPixmap(). + \row \o A movie + \o Pass a QMovie to setMovie(). + \row \o A number + \o Pass an \e int or a \e double to setNum(), which converts + the number to plain text. + \row \o Nothing + \o The same as an empty plain text. This is the default. Set + by clear(). + \endtable + + When the content is changed using any of these functions, any + previous content is cleared. + + By default, labels display \l{alignment}{left-aligned, vertically-centered} + text and images, where any tabs in the text to be displayed are + \l{Qt::TextExpandTabs}{automatically expanded}. However, the look + of a QLabel can be adjusted and fine-tuned in several ways. + + The positioning of the content within the QLabel widget area can + be tuned with setAlignment() and setIndent(). Text content can + also wrap lines along word boundaries with setWordWrap(). For + example, this code sets up a sunken panel with a two-line text in + the bottom right corner (both lines being flush with the right + side of the label): + + \snippet doc/src/snippets/code/src_gui_widgets_qlabel.cpp 0 + + The properties and functions QLabel inherits from QFrame can also + be used to specify the widget frame to be used for any given label. + + A QLabel is often used as a label for an interactive widget. For + this use QLabel provides a useful mechanism for adding an + mnemonic (see QKeySequence) that will set the keyboard focus to + the other widget (called the QLabel's "buddy"). For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qlabel.cpp 1 + + In this example, keyboard focus is transferred to the label's + buddy (the QLineEdit) when the user presses Alt+P. If the buddy + was a button (inheriting from QAbstractButton), triggering the + mnemonic would emulate a button click. + + \table 100% + \row + \o \inlineimage macintosh-label.png Screenshot of a Macintosh style label + \o A label shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row + \o \inlineimage plastique-label.png Screenshot of a Plastique style label + \o A label shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \row + \o \inlineimage windowsxp-label.png Screenshot of a Windows XP style label + \o A label shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \endtable + + \sa QLineEdit, QTextEdit, QPixmap, QMovie, + {fowler}{GUI Design Handbook: Label} +*/ + +#ifndef QT_NO_PICTURE +/*! + Returns the label's picture or 0 if the label doesn't have a + picture. +*/ + +const QPicture *QLabel::picture() const +{ + Q_D(const QLabel); + return d->picture; +} +#endif + + +/*! + Constructs an empty label. + + The \a parent and widget flag \a f, arguments are passed + to the QFrame constructor. + + \sa setAlignment(), setFrameStyle(), setIndent() +*/ +QLabel::QLabel(QWidget *parent, Qt::WindowFlags f) + : QFrame(*new QLabelPrivate(), parent, f) +{ + Q_D(QLabel); + d->init(); +} + +/*! + Constructs a label that displays the text, \a text. + + The \a parent and widget flag \a f, arguments are passed + to the QFrame constructor. + + \sa setText(), setAlignment(), setFrameStyle(), setIndent() +*/ +QLabel::QLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) + : QFrame(*new QLabelPrivate(), parent, f) +{ + Q_D(QLabel); + d->init(); + setText(text); +} + + +#ifdef QT3_SUPPORT +/*! \obsolete + Constructs an empty label. + + The \a parent, \a name and widget flag \a f, arguments are passed + to the QFrame constructor. + + \sa setAlignment(), setFrameStyle(), setIndent() +*/ + +QLabel::QLabel(QWidget *parent, const char *name, Qt::WindowFlags f) + : QFrame(*new QLabelPrivate(), parent, f) +{ + Q_D(QLabel); + if (name) + setObjectName(QString::fromAscii(name)); + d->init(); +} + + +/*! \obsolete + Constructs a label that displays the text, \a text. + + The \a parent, \a name and widget flag \a f, arguments are passed + to the QFrame constructor. + + \sa setText(), setAlignment(), setFrameStyle(), setIndent() +*/ + +QLabel::QLabel(const QString &text, QWidget *parent, const char *name, + Qt::WindowFlags f) + : QFrame(*new QLabelPrivate(), parent, f) +{ + Q_D(QLabel); + if (name) + setObjectName(QString::fromAscii(name)); + d->init(); + setText(text); +} + + +/*! \obsolete + Constructs a label that displays the text \a text. The label has a + buddy widget, \a buddy. + + If the \a text contains an underlined letter (a letter preceded by + an ampersand, \&), when the user presses Alt+ the underlined letter, + focus is passed to the buddy widget. + + The \a parent, \a name and widget flag, \a f, arguments are passed + to the QFrame constructor. + + \sa setText(), setBuddy(), setAlignment(), setFrameStyle(), + setIndent() +*/ +QLabel::QLabel(QWidget *buddy, const QString &text, + QWidget *parent, const char *name, Qt::WindowFlags f) + : QFrame(*new QLabelPrivate(), parent, f) +{ + Q_D(QLabel); + if (name) + setObjectName(QString::fromAscii(name)); + d->init(); +#ifndef QT_NO_SHORTCUT + setBuddy(buddy); +#endif + setText(text); +} +#endif //QT3_SUPPORT + +/*! + Destroys the label. +*/ + +QLabel::~QLabel() +{ + Q_D(QLabel); + d->clearContents(); +} + +void QLabelPrivate::init() +{ + Q_Q(QLabel); + + valid_hints = false; + margin = 0; +#ifndef QT_NO_MOVIE + movie = 0; +#endif +#ifndef QT_NO_SHORTCUT + shortcutId = 0; +#endif + pixmap = 0; + scaledpixmap = 0; + cachedimage = 0; +#ifndef QT_NO_PICTURE + picture = 0; +#endif + align = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextExpandTabs; + indent = -1; + scaledcontents = false; + textLayoutDirty = false; + textDirty = false; + textformat = Qt::AutoText; + control = 0; + textInteractionFlags = Qt::LinksAccessibleByMouse; + isRichText = false; + isTextLabel = false; + + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, + QSizePolicy::Label)); + +#ifndef QT_NO_CURSOR + validCursor = false; + onAnchor = false; +#endif + + openExternalLinks = false; + + setLayoutItemMargins(QStyle::SE_LabelLayoutItem); +} + + +/*! + \property QLabel::text + \brief the label's text + + If no text has been set this will return an empty string. Setting + the text clears any previous content. + + The text will be interpreted either as plain text or as rich + text, depending on the text format setting; see setTextFormat(). + The default setting is Qt::AutoText; i.e. QLabel will try to + auto-detect the format of the text set. + + If a buddy has been set, the buddy mnemonic key is updated + from the new text. + + Note that QLabel is well-suited to display small rich text + documents, such as small documents that get their document + specific settings (font, text color, link color) from the label's + palette and font properties. For large documents, use QTextEdit + in read-only mode instead. QTextEdit can also provide a scroll bar + when necessary. + + \note This function enables mouse tracking if \a text contains rich + text. + + \sa setTextFormat(), setBuddy(), alignment +*/ + +void QLabel::setText(const QString &text) +{ + Q_D(QLabel); + if (d->text == text) + return; + + QTextControl *oldControl = d->control; + d->control = 0; + + d->clearContents(); + d->text = text; + d->isTextLabel = true; + d->textDirty = true; + d->isRichText = d->textformat == Qt::RichText + || (d->textformat == Qt::AutoText && Qt::mightBeRichText(d->text)); + + d->control = oldControl; + + if (d->needTextControl()) { + d->ensureTextControl(); + } else { + delete d->control; + d->control = 0; + } + + if (d->isRichText) { + setMouseTracking(true); + } else { + // Note: mouse tracking not disabled intentionally + } + +#ifndef QT_NO_SHORTCUT + if (d->buddy) + d->updateShortcut(); +#endif + + d->updateLabel(); +} + +QString QLabel::text() const +{ + Q_D(const QLabel); + return d->text; +} + +/*! + Clears any label contents. +*/ + +void QLabel::clear() +{ + Q_D(QLabel); + d->clearContents(); + d->updateLabel(); +} + +/*! + \property QLabel::pixmap + \brief the label's pixmap + + If no pixmap has been set this will return 0. + + Setting the pixmap clears any previous content. The buddy + shortcut, if any, is disabled. +*/ +void QLabel::setPixmap(const QPixmap &pixmap) +{ + Q_D(QLabel); + if (!d->pixmap || d->pixmap->cacheKey() != pixmap.cacheKey()) { + d->clearContents(); + d->pixmap = new QPixmap(pixmap); + } + + if (d->pixmap->depth() == 1 && !d->pixmap->mask()) + d->pixmap->setMask(*((QBitmap *)d->pixmap)); + + d->updateLabel(); +} + +const QPixmap *QLabel::pixmap() const +{ + Q_D(const QLabel); + return d->pixmap; +} + +#ifndef QT_NO_PICTURE +/*! + Sets the label contents to \a picture. Any previous content is + cleared. + + The buddy shortcut, if any, is disabled. + + \sa picture(), setBuddy() +*/ + +void QLabel::setPicture(const QPicture &picture) +{ + Q_D(QLabel); + d->clearContents(); + d->picture = new QPicture(picture); + + d->updateLabel(); +} +#endif // QT_NO_PICTURE + +/*! + Sets the label contents to plain text containing the textual + representation of integer \a num. Any previous content is cleared. + Does nothing if the integer's string representation is the same as + the current contents of the label. + + The buddy shortcut, if any, is disabled. + + \sa setText(), QString::setNum(), setBuddy() +*/ + +void QLabel::setNum(int num) +{ + QString str; + str.setNum(num); + setText(str); +} + +/*! + \overload + + Sets the label contents to plain text containing the textual + representation of double \a num. Any previous content is cleared. + Does nothing if the double's string representation is the same as + the current contents of the label. + + The buddy shortcut, if any, is disabled. + + \sa setText(), QString::setNum(), setBuddy() +*/ + +void QLabel::setNum(double num) +{ + QString str; + str.setNum(num); + setText(str); +} + +/*! + \property QLabel::alignment + \brief the alignment of the label's contents + + By default, the contents of the label are left-aligned and vertically-centered. + + \sa text +*/ + +void QLabel::setAlignment(Qt::Alignment alignment) +{ + Q_D(QLabel); + if (alignment == (d->align & (Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask))) + return; + d->align = (d->align & ~(Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask)) + | (alignment & (Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask)); + + d->updateLabel(); +} + +#ifdef QT3_SUPPORT +/*! + Use setAlignment(Qt::Alignment) instead. + + If \a alignment specifies text flags as well, use setTextFormat() + to set those. +*/ +void QLabel::setAlignment(int alignment) +{ + Q_D(QLabel); + d->align = alignment & ~(Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask|Qt::TextWordWrap); + setAlignment(Qt::Alignment(QFlag(alignment))); +} +#endif + +Qt::Alignment QLabel::alignment() const +{ + Q_D(const QLabel); + return QFlag(d->align & (Qt::AlignVertical_Mask|Qt::AlignHorizontal_Mask)); +} + + +/*! + \property QLabel::wordWrap + \brief the label's word-wrapping policy + + If this property is true then label text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + + By default, word wrap is disabled. + + \sa text +*/ +void QLabel::setWordWrap(bool on) +{ + Q_D(QLabel); + if (on) + d->align |= Qt::TextWordWrap; + else + d->align &= ~Qt::TextWordWrap; + + d->updateLabel(); +} + +bool QLabel::wordWrap() const +{ + Q_D(const QLabel); + return d->align & Qt::TextWordWrap; +} + +/*! + \property QLabel::indent + \brief the label's text indent in pixels + + If a label displays text, the indent applies to the left edge if + alignment() is Qt::AlignLeft, to the right edge if alignment() is + Qt::AlignRight, to the top edge if alignment() is Qt::AlignTop, and + to to the bottom edge if alignment() is Qt::AlignBottom. + + If indent is negative, or if no indent has been set, the label + computes the effective indent as follows: If frameWidth() is 0, + the effective indent becomes 0. If frameWidth() is greater than 0, + the effective indent becomes half the width of the "x" character + of the widget's current font(). + + By default, the indent is -1, meaning that an effective indent is + calculating in the manner described above. + + \sa alignment, margin, frameWidth(), font() +*/ + +void QLabel::setIndent(int indent) +{ + Q_D(QLabel); + d->indent = indent; + d->updateLabel(); +} + +int QLabel::indent() const +{ + Q_D(const QLabel); + return d->indent; +} + + +/*! + \property QLabel::margin + \brief the width of the margin + + The margin is the distance between the innermost pixel of the + frame and the outermost pixel of contents. + + The default margin is 0. + + \sa indent +*/ +int QLabel::margin() const +{ + Q_D(const QLabel); + return d->margin; +} + +void QLabel::setMargin(int margin) +{ + Q_D(QLabel); + if (d->margin == margin) + return; + d->margin = margin; + d->updateLabel(); +} + +/*! + Returns the size that will be used if the width of the label is \a + w. If \a w is -1, the sizeHint() is returned. If \a w is 0 minimumSizeHint() is returned +*/ +QSize QLabelPrivate::sizeForWidth(int w) const +{ + Q_Q(const QLabel); + if(q->minimumWidth() > 0) + w = qMax(w, q->minimumWidth()); + QSize contentsMargin(leftmargin + rightmargin, topmargin + bottommargin); + + QRect br; + + int hextra = 2 * margin; + int vextra = hextra; + QFontMetrics fm = q->fontMetrics(); + + if (pixmap && !pixmap->isNull()) + br = pixmap->rect(); +#ifndef QT_NO_PICTURE + else if (picture && !picture->isNull()) + br = picture->boundingRect(); +#endif +#ifndef QT_NO_MOVIE + else if (movie && !movie->currentPixmap().isNull()) + br = movie->currentPixmap().rect(); +#endif + else if (isTextLabel) { + int align = QStyle::visualAlignment(q->layoutDirection(), QFlag(this->align)); + // Add indentation + int m = indent; + + if (m < 0 && q->frameWidth()) // no indent, but we do have a frame + m = fm.width(QLatin1Char('x')) - margin*2; + if (m > 0) { + if ((align & Qt::AlignLeft) || (align & Qt::AlignRight)) + hextra += m; + if ((align & Qt::AlignTop) || (align & Qt::AlignBottom)) + vextra += m; + } + + if (control) { + ensureTextLayouted(); + const qreal oldTextWidth = control->textWidth(); + // Calculate the length of document if w is the width + if (align & Qt::TextWordWrap) { + if (w >= 0) { + w = qMax(w-hextra-contentsMargin.width(), 0); // strip margin and indent + control->setTextWidth(w); + } else { + control->adjustSize(); + } + } else { + control->setTextWidth(-1); + } + br = QRect(QPoint(0, 0), control->size().toSize()); + + // restore state + control->setTextWidth(oldTextWidth); + } else { + // Turn off center alignment in order to avoid rounding errors for centering, + // since centering involves a division by 2. At the end, all we want is the size. + int flags = align & ~(Qt::AlignVCenter | Qt::AlignHCenter); + if (hasShortcut) { + flags |= Qt::TextShowMnemonic; + QStyleOption opt; + opt.initFrom(q); + if (!q->style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, q)) + flags |= Qt::TextHideMnemonic; + } + + bool tryWidth = (w < 0) && (align & Qt::TextWordWrap); + if (tryWidth) + w = fm.averageCharWidth() * 80; + else if (w < 0) + w = 2000; + w -= (hextra + contentsMargin.width()); + br = fm.boundingRect(0, 0, w ,2000, flags, text); + if (tryWidth && br.height() < 4*fm.lineSpacing() && br.width() > w/2) + br = fm.boundingRect(0, 0, w/2, 2000, flags, text); + if (tryWidth && br.height() < 2*fm.lineSpacing() && br.width() > w/4) + br = fm.boundingRect(0, 0, w/4, 2000, flags, text); + } + } else { + br = QRect(QPoint(0, 0), QSize(fm.averageCharWidth(), fm.lineSpacing())); + } + + const QSize contentsSize(br.width() + hextra, br.height() + vextra); + return (contentsSize + contentsMargin).expandedTo(q->minimumSize()); +} + + +/*! + \reimp +*/ + +int QLabel::heightForWidth(int w) const +{ + Q_D(const QLabel); + if (d->isTextLabel) + return d->sizeForWidth(w).height(); + return QWidget::heightForWidth(w); +} + +/*! + \property QLabel::openExternalLinks + \since 4.2 + + Specifies whether QLabel should automatically open links using + QDesktopServices::openUrl() instead of emitting the + linkActivated() signal. + + \bold{Note:} The textInteractionFlags set on the label need to include + either LinksAccessibleByMouse or LinksAccessibleByKeyboard. + + The default value is false. + + \sa textInteractionFlags() +*/ +bool QLabel::openExternalLinks() const +{ + Q_D(const QLabel); + return d->openExternalLinks; +} + +void QLabel::setOpenExternalLinks(bool open) +{ + Q_D(QLabel); + d->openExternalLinks = open; + if (d->control) + d->control->setOpenExternalLinks(open); +} + +/*! + \property QLabel::textInteractionFlags + \since 4.2 + + Specifies how the label should interact with user input if it displays text. + + If the flags contain Qt::LinksAccessibleByKeyboard the focus policy is also + automatically set to Qt::StrongFocus. If Qt::TextSelectableByKeyboard is set + then the focus policy is set to Qt::ClickFocus. + + The default value is Qt::LinksAccessibleByMouse. +*/ +void QLabel::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QLabel); + if (d->textInteractionFlags == flags) + return; + d->textInteractionFlags = flags; + if (flags & Qt::LinksAccessibleByKeyboard) + setFocusPolicy(Qt::StrongFocus); + else if (flags & (Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse)) + setFocusPolicy(Qt::ClickFocus); + else + setFocusPolicy(Qt::NoFocus); + + if (d->needTextControl()) { + d->ensureTextControl(); + } else { + delete d->control; + d->control = 0; + } + + if (d->control) + d->control->setTextInteractionFlags(d->textInteractionFlags); +} + +Qt::TextInteractionFlags QLabel::textInteractionFlags() const +{ + Q_D(const QLabel); + return d->textInteractionFlags; +} + +/*!\reimp +*/ +QSize QLabel::sizeHint() const +{ + Q_D(const QLabel); + if (!d->valid_hints) + (void) QLabel::minimumSizeHint(); + return d->sh; +} + +/*! + \reimp +*/ +QSize QLabel::minimumSizeHint() const +{ + Q_D(const QLabel); + if (d->valid_hints) { + if (d->sizePolicy == sizePolicy()) + return d->msh; + } + + ensurePolished(); + d->valid_hints = true; + d->sh = d->sizeForWidth(-1); // wrap ? golden ratio : min doc size + QSize msh(-1, -1); + + if (!d->isTextLabel) { + msh = d->sh; + } else { + msh.rheight() = d->sizeForWidth(QWIDGETSIZE_MAX).height(); // height for one line + msh.rwidth() = d->sizeForWidth(0).width(); // wrap ? size of biggest word : min doc size + if (d->sh.height() < msh.height()) + msh.rheight() = d->sh.height(); + } + d->msh = msh; + d->sizePolicy = sizePolicy(); + return msh; +} + +/*!\reimp +*/ +void QLabel::mousePressEvent(QMouseEvent *ev) +{ + Q_D(QLabel); + d->sendControlEvent(ev); +} + +/*!\reimp +*/ +void QLabel::mouseMoveEvent(QMouseEvent *ev) +{ + Q_D(QLabel); + d->sendControlEvent(ev); +} + +/*!\reimp +*/ +void QLabel::mouseReleaseEvent(QMouseEvent *ev) +{ + Q_D(QLabel); + d->sendControlEvent(ev); +} + +/*!\reimp +*/ +void QLabel::contextMenuEvent(QContextMenuEvent *ev) +{ +#ifdef QT_NO_CONTEXTMENU + Q_UNUSED(ev); +#else + Q_D(QLabel); + if (!d->isTextLabel) { + ev->ignore(); + return; + } + QMenu *menu = d->createStandardContextMenu(ev->pos()); + if (!menu) { + ev->ignore(); + return; + } + ev->accept(); + menu->exec(ev->globalPos()); + delete menu; +#endif +} + +/*! + \reimp +*/ +void QLabel::focusInEvent(QFocusEvent *ev) +{ + Q_D(QLabel); + if (d->isTextLabel) { + d->ensureTextControl(); + d->sendControlEvent(ev); + } + QFrame::focusInEvent(ev); +} + +/*! + \reimp +*/ +void QLabel::focusOutEvent(QFocusEvent *ev) +{ + Q_D(QLabel); + d->sendControlEvent(ev); + QFrame::focusOutEvent(ev); +} + +/*!\reimp +*/ +bool QLabel::focusNextPrevChild(bool next) +{ + Q_D(QLabel); + if (d->control && d->control->setFocusToNextOrPreviousAnchor(next)) + return true; + return QFrame::focusNextPrevChild(next); +} + +/*!\reimp +*/ +void QLabel::keyPressEvent(QKeyEvent *ev) +{ + Q_D(QLabel); + d->sendControlEvent(ev); +} + +/*!\reimp +*/ +bool QLabel::event(QEvent *e) +{ + Q_D(QLabel); + QEvent::Type type = e->type(); + +#ifndef QT_NO_SHORTCUT + if (type == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(e); + if (se->shortcutId() == d->shortcutId) { + QWidget * w = d->buddy; + QAbstractButton *button = qobject_cast<QAbstractButton *>(w); + if (w->focusPolicy() != Qt::NoFocus) + w->setFocus(Qt::ShortcutFocusReason); + if (button && !se->isAmbiguous()) + button->animateClick(); + else + window()->setAttribute(Qt::WA_KeyboardFocusChange); + return true; + } + } else +#endif + if (type == QEvent::Resize) { + if (d->control) + d->textLayoutDirty = true; + } else if (e->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || e->type() == QEvent::MacSizeChange +#endif + ) { + d->setLayoutItemMargins(QStyle::SE_LabelLayoutItem); + d->updateLabel(); + } + + return QFrame::event(e); +} + +/*!\reimp +*/ +void QLabel::paintEvent(QPaintEvent *) +{ + Q_D(QLabel); + QStyle *style = QWidget::style(); + QPainter painter(this); + drawFrame(&painter); + QRect cr = contentsRect(); + cr.adjust(d->margin, d->margin, -d->margin, -d->margin); + int align = QStyle::visualAlignment(layoutDirection(), QFlag(d->align)); + +#ifndef QT_NO_MOVIE + if (d->movie) { + if (d->scaledcontents) + style->drawItemPixmap(&painter, cr, align, d->movie->currentPixmap().scaled(cr.size())); + else + style->drawItemPixmap(&painter, cr, align, d->movie->currentPixmap()); + } + else +#endif + if (d->isTextLabel) { + QRectF lr = d->layoutRect(); + if (d->control) { +#ifndef QT_NO_SHORTCUT + const bool underline = (bool)style->styleHint(QStyle::SH_UnderlineShortcut, 0, this, 0); + if (d->shortcutId != 0 + && underline != d->shortcutCursor.charFormat().fontUnderline()) { + QTextCharFormat fmt; + fmt.setFontUnderline(underline); + d->shortcutCursor.mergeCharFormat(fmt); + } +#endif + d->ensureTextLayouted(); + + QAbstractTextDocumentLayout::PaintContext context; + QStyleOption opt(0); + opt.init(this); + + if (!isEnabled() && style->styleHint(QStyle::SH_EtchDisabledText, &opt, this)) { + context.palette = palette(); + context.palette.setColor(QPalette::Text, context.palette.light().color()); + painter.save(); + painter.translate(lr.x() + 1, lr.y() + 1); + painter.setClipRect(lr.translated(-lr.x() - 1, -lr.y() - 1)); + QAbstractTextDocumentLayout *layout = d->control->document()->documentLayout(); + layout->draw(&painter, context); + painter.restore(); + } + + // Adjust the palette + context.palette = palette(); +#ifndef QT_NO_STYLE_STYLESHEET + if (QStyleSheetStyle* cssStyle = qobject_cast<QStyleSheetStyle*>(style)) { + cssStyle->focusPalette(this, &opt, &context.palette); + } +#endif + + if (foregroundRole() != QPalette::Text && isEnabled()) + context.palette.setColor(QPalette::Text, context.palette.color(foregroundRole())); + + painter.save(); + painter.translate(lr.topLeft()); + painter.setClipRect(lr.translated(-lr.x(), -lr.y())); + d->control->setPalette(context.palette); + d->control->drawContents(&painter, QRectF(), this); + painter.restore(); + } else { + int flags = align; + if (d->hasShortcut) { + flags |= Qt::TextShowMnemonic; + QStyleOption opt; + opt.initFrom(this); + if (!style->styleHint(QStyle::SH_UnderlineShortcut, &opt, this)) + flags |= Qt::TextHideMnemonic; + } + style->drawItemText(&painter, lr.toRect(), flags, palette(), isEnabled(), d->text, foregroundRole()); + } + } else +#ifndef QT_NO_PICTURE + if (d->picture) { + QRect br = d->picture->boundingRect(); + int rw = br.width(); + int rh = br.height(); + if (d->scaledcontents) { + painter.save(); + painter.translate(cr.x(), cr.y()); + painter.scale((double)cr.width()/rw, (double)cr.height()/rh); + painter.drawPicture(-br.x(), -br.y(), *d->picture); + painter.restore(); + } else { + int xo = 0; + int yo = 0; + if (align & Qt::AlignVCenter) + yo = (cr.height()-rh)/2; + else if (align & Qt::AlignBottom) + yo = cr.height()-rh; + if (align & Qt::AlignRight) + xo = cr.width()-rw; + else if (align & Qt::AlignHCenter) + xo = (cr.width()-rw)/2; + painter.drawPicture(cr.x()+xo-br.x(), cr.y()+yo-br.y(), *d->picture); + } + } else +#endif + if (d->pixmap && !d->pixmap->isNull()) { + QPixmap pix; + if (d->scaledcontents) { + if (!d->scaledpixmap || d->scaledpixmap->size() != cr.size()) { + if (!d->cachedimage) + d->cachedimage = new QImage(d->pixmap->toImage()); + delete d->scaledpixmap; + d->scaledpixmap = new QPixmap(QPixmap::fromImage(d->cachedimage->scaled(cr.size(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation))); + } + pix = *d->scaledpixmap; + } else + pix = *d->pixmap; + QStyleOption opt; + opt.initFrom(this); + if (!isEnabled()) + pix = style->generatedIconPixmap(QIcon::Disabled, pix, &opt); + style->drawItemPixmap(&painter, cr, align, pix); + } +} + + +/*! + Updates the label, but not the frame. +*/ + +void QLabelPrivate::updateLabel() +{ + Q_Q(QLabel); + valid_hints = false; + + if (isTextLabel) { + QSizePolicy policy = q->sizePolicy(); + const bool wrap = align & Qt::TextWordWrap; + policy.setHeightForWidth(wrap); + if (policy != q->sizePolicy()) // ### should be replaced by WA_WState_OwnSizePolicy idiom + q->setSizePolicy(policy); + textLayoutDirty = true; + } + q->updateGeometry(); + q->update(q->contentsRect()); +} + +#ifndef QT_NO_SHORTCUT +/*! + Sets this label's buddy to \a buddy. + + When the user presses the shortcut key indicated by this label, + the keyboard focus is transferred to the label's buddy widget. + + The buddy mechanism is only available for QLabels that contain + text in which one character is prefixed with an ampersand, '&'. + This character is set as the shortcut key. See the \l + QKeySequence::mnemonic() documentation for details (to display an + actual ampersand, use '&&'). + + In a dialog, you might create two data entry widgets and a label + for each, and set up the geometry layout so each label is just to + the left of its data entry widget (its "buddy"), for example: + \snippet doc/src/snippets/code/src_gui_widgets_qlabel.cpp 2 + + With the code above, the focus jumps to the Name field when the + user presses Alt+N, and to the Phone field when the user presses + Alt+P. + + To unset a previously set buddy, call this function with \a buddy + set to 0. + + \sa buddy(), setText(), QShortcut, setAlignment() +*/ + +void QLabel::setBuddy(QWidget *buddy) +{ + Q_D(QLabel); + d->buddy = buddy; + if (d->isTextLabel) { + if (d->shortcutId) + releaseShortcut(d->shortcutId); + d->shortcutId = 0; + d->textDirty = true; + if (buddy) + d->updateShortcut(); // grab new shortcut + d->updateLabel(); + } +} + + +/*! + Returns this label's buddy, or 0 if no buddy is currently set. + + \sa setBuddy() +*/ + +QWidget * QLabel::buddy() const +{ + Q_D(const QLabel); + return d->buddy; +} + +void QLabelPrivate::updateShortcut() +{ + Q_Q(QLabel); + Q_ASSERT(shortcutId == 0); + // Introduce an extra boolean to indicate the presence of a shortcut in the + // text. We cannot use the shortcutId itself because on the mac mnemonics are + // off by default, so QKeySequence::mnemonic always returns an empty sequence. + // But then we do want to hide the ampersands, so we can't use shortcutId. + hasShortcut = false; + + if (control) { + ensureTextPopulated(); + // Underline the first character that follows an ampersand + shortcutCursor = control->document()->find(QLatin1String("&")); + if (shortcutCursor.isNull()) + return; + hasShortcut = true; + shortcutId = q->grabShortcut(QKeySequence::mnemonic(text)); + shortcutCursor.deleteChar(); // remove the ampersand + shortcutCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + } else { + if (!text.contains(QLatin1String("&"))) + return; + hasShortcut = true; + shortcutId = q->grabShortcut(QKeySequence::mnemonic(text)); + } +} + +#endif // QT_NO_SHORTCUT + +#ifndef QT_NO_MOVIE +void QLabelPrivate::_q_movieUpdated(const QRect& rect) +{ + Q_Q(QLabel); + if (movie && movie->isValid()) { + QRect r; + if (scaledcontents) { + QRect cr = q->contentsRect(); + QRect pixmapRect(cr.topLeft(), movie->currentPixmap().size()); + if (pixmapRect.isEmpty()) + return; + r.setRect(cr.left(), cr.top(), + (rect.width() * cr.width()) / pixmapRect.width(), + (rect.height() * cr.height()) / pixmapRect.height()); + } else { + r = q->style()->itemPixmapRect(q->contentsRect(), align, movie->currentPixmap()); + r.translate(rect.x(), rect.y()); + r.setWidth(qMin(r.width(), rect.width())); + r.setHeight(qMin(r.height(), rect.height())); + } + q->update(r); + } +} + +void QLabelPrivate::_q_movieResized(const QSize& size) +{ + Q_Q(QLabel); + q->update(); //we need to refresh the whole background in case the new size is smaler + valid_hints = false; + _q_movieUpdated(QRect(QPoint(0,0), size)); + q->updateGeometry(); +} + +/*! + Sets the label contents to \a movie. Any previous content is + cleared. The label does NOT take ownership of the movie. + + The buddy shortcut, if any, is disabled. + + \sa movie(), setBuddy() +*/ + +void QLabel::setMovie(QMovie *movie) +{ + Q_D(QLabel); + d->clearContents(); + + if (!movie) + return; + + d->movie = movie; + connect(movie, SIGNAL(resized(QSize)), this, SLOT(_q_movieResized(QSize))); + connect(movie, SIGNAL(updated(QRect)), this, SLOT(_q_movieUpdated(QRect))); + + // Assume that if the movie is running, + // resize/update signals will come soon enough + if (movie->state() != QMovie::Running) + d->updateLabel(); +} + +#endif // QT_NO_MOVIE + +/*! + \internal + + Clears any contents, without updating/repainting the label. +*/ + +void QLabelPrivate::clearContents() +{ + delete control; + control = 0; + isTextLabel = false; + hasShortcut = false; + +#ifndef QT_NO_PICTURE + delete picture; + picture = 0; +#endif + delete scaledpixmap; + scaledpixmap = 0; + delete cachedimage; + cachedimage = 0; + delete pixmap; + pixmap = 0; + + text.clear(); + Q_Q(QLabel); +#ifndef QT_NO_SHORTCUT + if (shortcutId) + q->releaseShortcut(shortcutId); + shortcutId = 0; +#endif +#ifndef QT_NO_MOVIE + if (movie) { + QObject::disconnect(movie, SIGNAL(resized(QSize)), q, SLOT(_q_movieResized(QSize))); + QObject::disconnect(movie, SIGNAL(updated(QRect)), q, SLOT(_q_movieUpdated(QRect))); + } + movie = 0; +#endif +#ifndef QT_NO_CURSOR + if (onAnchor) { + if (validCursor) + q->setCursor(cursor); + else + q->unsetCursor(); + } + validCursor = false; + onAnchor = false; +#endif +} + + +#ifndef QT_NO_MOVIE + +/*! + Returns a pointer to the label's movie, or 0 if no movie has been + set. + + \sa setMovie() +*/ + +QMovie *QLabel::movie() const +{ + Q_D(const QLabel); + return d->movie; +} + +#endif // QT_NO_MOVIE + +/*! + \property QLabel::textFormat + \brief the label's text format + + See the Qt::TextFormat enum for an explanation of the possible + options. + + The default format is Qt::AutoText. + + \sa text() +*/ + +Qt::TextFormat QLabel::textFormat() const +{ + Q_D(const QLabel); + return d->textformat; +} + +void QLabel::setTextFormat(Qt::TextFormat format) +{ + Q_D(QLabel); + if (format != d->textformat) { + d->textformat = format; + QString t = d->text; + if (!t.isNull()) { + d->text.clear(); + setText(t); + } + } +} + +/*! + \reimp +*/ +void QLabel::changeEvent(QEvent *ev) +{ + Q_D(QLabel); + if(ev->type() == QEvent::FontChange || ev->type() == QEvent::ApplicationFontChange) { + if (d->isTextLabel) { + if (d->control) + d->control->document()->setDefaultFont(font()); + d->updateLabel(); + } + } else if (ev->type() == QEvent::PaletteChange && d->control) { + d->control->setPalette(palette()); + } else if (ev->type() == QEvent::ContentsRectChange) { + d->updateLabel(); + } else if (ev->type() == QEvent::LayoutDirectionChange) { + if (d->isTextLabel && d->control) { + d->sendControlEvent(ev); + } + } + QFrame::changeEvent(ev); +} + +/*! + \property QLabel::scaledContents + \brief whether the label will scale its contents to fill all + available space. + + When enabled and the label shows a pixmap, it will scale the + pixmap to fill the available space. + + This property's default is false. +*/ +bool QLabel::hasScaledContents() const +{ + Q_D(const QLabel); + return d->scaledcontents; +} + +void QLabel::setScaledContents(bool enable) +{ + Q_D(QLabel); + if ((bool)d->scaledcontents == enable) + return; + d->scaledcontents = enable; + if (!enable) { + delete d->scaledpixmap; + d->scaledpixmap = 0; + delete d->cachedimage; + d->cachedimage = 0; + } + update(contentsRect()); +} + + +/*! + \fn void QLabel::setAlignment(Qt::AlignmentFlag flag) + \internal + + Without this function, a call to e.g. setAlignment(Qt::AlignTop) + results in the \c QT3_SUPPORT function setAlignment(int) being called, + rather than setAlignment(Qt::Alignment). +*/ + +// Returns the rect that is available for us to draw the document +QRect QLabelPrivate::documentRect() const +{ + Q_Q(const QLabel); + Q_ASSERT_X(isTextLabel, "documentRect", "document rect called for label that is not a text label!"); + QRect cr = q->contentsRect(); + cr.adjust(margin, margin, -margin, -margin); + const int align = QStyle::visualAlignment(q->layoutDirection(), QFlag(this->align)); + int m = indent; + if (m < 0 && q->frameWidth()) // no indent, but we do have a frame + m = q->fontMetrics().width(QLatin1Char('x')) / 2 - margin; + if (m > 0) { + if (align & Qt::AlignLeft) + cr.setLeft(cr.left() + m); + if (align & Qt::AlignRight) + cr.setRight(cr.right() - m); + if (align & Qt::AlignTop) + cr.setTop(cr.top() + m); + if (align & Qt::AlignBottom) + cr.setBottom(cr.bottom() - m); + } + return cr; +} + +void QLabelPrivate::ensureTextPopulated() const +{ + if (!textDirty) + return; + if (control) { + QTextDocument *doc = control->document(); + if (textDirty) { +#ifndef QT_NO_TEXTHTMLPARSER + if (isRichText) + doc->setHtml(text); + else + doc->setPlainText(text); +#else + doc->setPlainText(text); +#endif + doc->setUndoRedoEnabled(false); + } + } + textDirty = false; +} + +void QLabelPrivate::ensureTextLayouted() const +{ + if (!textLayoutDirty) + return; + ensureTextPopulated(); + Q_Q(const QLabel); + if (control) { + QTextDocument *doc = control->document(); + QTextOption opt = doc->defaultTextOption(); + + opt.setAlignment(QFlag(this->align)); + + if (this->align & Qt::TextWordWrap) + opt.setWrapMode(QTextOption::WordWrap); + else + opt.setWrapMode(QTextOption::ManualWrap); + + opt.setTextDirection(q->layoutDirection()); + + doc->setDefaultTextOption(opt); + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(0); + doc->rootFrame()->setFrameFormat(fmt); + doc->setTextWidth(documentRect().width()); + } + textLayoutDirty = false; +} + +void QLabelPrivate::ensureTextControl() const +{ + Q_Q(const QLabel); + if (!isTextLabel) + return; + if (!control) { + control = new QTextControl(const_cast<QLabel *>(q)); + control->document()->setUndoRedoEnabled(false); + control->document()->setDefaultFont(q->font()); + control->setTextInteractionFlags(textInteractionFlags); + control->setOpenExternalLinks(openExternalLinks); + control->setPalette(q->palette()); + control->setFocus(q->hasFocus()); + QObject::connect(control, SIGNAL(updateRequest(QRectF)), + q, SLOT(update())); + QObject::connect(control, SIGNAL(linkHovered(QString)), + q, SLOT(_q_linkHovered(QString))); + QObject::connect(control, SIGNAL(linkActivated(QString)), + q, SIGNAL(linkActivated(QString))); + textLayoutDirty = true; + textDirty = true; + } +} + +void QLabelPrivate::sendControlEvent(QEvent *e) +{ + Q_Q(QLabel); + if (!isTextLabel || !control || textInteractionFlags == Qt::NoTextInteraction) { + e->ignore(); + return; + } + control->processEvent(e, -layoutRect().topLeft(), q); +} + +void QLabelPrivate::_q_linkHovered(const QString &anchor) +{ + Q_Q(QLabel); +#ifndef QT_NO_CURSOR + if (anchor.isEmpty()) { // restore cursor + if (validCursor) + q->setCursor(cursor); + else + q->unsetCursor(); + onAnchor = false; + } else if (!onAnchor) { + validCursor = q->testAttribute(Qt::WA_SetCursor); + if (validCursor) { + cursor = q->cursor(); + } + q->setCursor(Qt::PointingHandCursor); + onAnchor = true; + } +#endif + emit q->linkHovered(anchor); +} + +// Return the layout rect - this is the rect that is given to the layout painting code +// This may be different from the document rect since vertical alignment is not +// done by the text layout code +QRectF QLabelPrivate::layoutRect() const +{ + QRectF cr = documentRect(); + if (!control) + return cr; + ensureTextLayouted(); + // Caculate y position manually + qreal rh = control->document()->documentLayout()->documentSize().height(); + qreal yo = 0; + if (align & Qt::AlignVCenter) + yo = qMax((cr.height()-rh)/2, qreal(0)); + else if (align & Qt::AlignBottom) + yo = qMax(cr.height()-rh, qreal(0)); + return QRectF(cr.x(), yo + cr.y(), cr.width(), cr.height()); +} + +// Returns the point in the document rect adjusted with p +QPoint QLabelPrivate::layoutPoint(const QPoint& p) const +{ + QRect lr = layoutRect().toRect(); + return p - lr.topLeft(); +} + +#ifndef QT_NO_CONTEXTMENU +QMenu *QLabelPrivate::createStandardContextMenu(const QPoint &pos) +{ + QString linkToCopy; + QPoint p; + if (control && isRichText) { + p = layoutPoint(pos); + linkToCopy = control->document()->documentLayout()->anchorAt(p); + } + + if (linkToCopy.isEmpty() && !control) + return 0; + + return control->createStandardContextMenu(p, q_func()); +} +#endif + +/*! + \fn void QLabel::linkHovered(const QString &link) + \since 4.2 + + This signal is emitted when the user hovers over a link. The URL + referred to by the anchor is passed in \a link. + + \sa linkActivated() +*/ + + +/*! + \fn void QLabel::linkActivated(const QString &link) + \since 4.2 + + This signal is emitted when the user clicks a link. The URL + referred to by the anchor is passed in \a link. + + \sa linkHovered() +*/ + +QT_END_NAMESPACE + +#include "moc_qlabel.cpp" diff --git a/src/gui/widgets/qlabel.h b/src/gui/widgets/qlabel.h new file mode 100644 index 0000000..34f397f --- /dev/null +++ b/src/gui/widgets/qlabel.h @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLABEL_H +#define QLABEL_H + +#include <QtGui/qframe.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QLabelPrivate; + +class Q_GUI_EXPORT QLabel : public QFrame +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat) + Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap) + Q_PROPERTY(bool scaledContents READ hasScaledContents WRITE setScaledContents) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(int margin READ margin WRITE setMargin) + Q_PROPERTY(int indent READ indent WRITE setIndent) + Q_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) + Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags WRITE setTextInteractionFlags) + +public: + explicit QLabel(QWidget *parent=0, Qt::WindowFlags f=0); + explicit QLabel(const QString &text, QWidget *parent=0, Qt::WindowFlags f=0); + ~QLabel(); + + QString text() const; + const QPixmap *pixmap() const; +#ifndef QT_NO_PICTURE + const QPicture *picture() const; +#endif +#ifndef QT_NO_MOVIE + QMovie *movie() const; +#endif + + Qt::TextFormat textFormat() const; + void setTextFormat(Qt::TextFormat); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment); + + void setWordWrap(bool on); + bool wordWrap() const; + + int indent() const; + void setIndent(int); + + int margin() const; + void setMargin(int); + + bool hasScaledContents() const; + void setScaledContents(bool); + QSize sizeHint() const; + QSize minimumSizeHint() const; +#ifndef QT_NO_SHORTCUT + void setBuddy(QWidget *); + QWidget *buddy() const; +#endif + int heightForWidth(int) const; + + bool openExternalLinks() const; + void setOpenExternalLinks(bool open); + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + +public Q_SLOTS: + void setText(const QString &); + void setPixmap(const QPixmap &); +#ifndef QT_NO_PICTURE + void setPicture(const QPicture &); +#endif +#ifndef QT_NO_MOVIE + void setMovie(QMovie *movie); +#endif + void setNum(int); + void setNum(double); + void clear(); + +Q_SIGNALS: + void linkActivated(const QString& link); + void linkHovered(const QString& link); + +protected: + bool event(QEvent *e); + void keyPressEvent(QKeyEvent *ev); + void paintEvent(QPaintEvent *); + void changeEvent(QEvent *); + void mousePressEvent(QMouseEvent *ev); + void mouseMoveEvent(QMouseEvent *ev); + void mouseReleaseEvent(QMouseEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + void focusInEvent(QFocusEvent *ev); + void focusOutEvent(QFocusEvent *ev); + bool focusNextPrevChild(bool next); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QLabel(QWidget *parent, const char* name, Qt::WindowFlags f=0); + QT3_SUPPORT_CONSTRUCTOR QLabel(const QString &text, QWidget *parent, const char* name, + Qt::WindowFlags f=0); + QT3_SUPPORT_CONSTRUCTOR QLabel(QWidget *buddy, const QString &, + QWidget *parent=0, const char* name=0, Qt::WindowFlags f=0); + QT3_SUPPORT void setAlignment(int alignment); + + // don't mark the next function with QT3_SUPPORT + inline void setAlignment(Qt::AlignmentFlag flag) { setAlignment((Qt::Alignment)flag); } +#endif + +private: + Q_DISABLE_COPY(QLabel) + Q_DECLARE_PRIVATE(QLabel) +#ifndef QT_NO_MOVIE + Q_PRIVATE_SLOT(d_func(), void _q_movieUpdated(const QRect&)) + Q_PRIVATE_SLOT(d_func(), void _q_movieResized(const QSize&)) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_linkHovered(const QString &)) + + friend class QTipLabel; + friend class QMessageBoxPrivate; + friend class QBalloonTip; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLABEL_H diff --git a/src/gui/widgets/qlabel_p.h b/src/gui/widgets/qlabel_p.h new file mode 100644 index 0000000..4d83f35 --- /dev/null +++ b/src/gui/widgets/qlabel_p.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLABEL_P_H +#define QLABEL_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 "qlabel.h" + +#include "../text/qtextdocumentlayout_p.h" +#include "private/qtextcontrol_p.h" +#include "qtextdocumentfragment.h" +#include "qframe_p.h" +#include "qtextdocument.h" +#include "qmovie.h" +#include "qimage.h" +#include "qbitmap.h" +#include "qpicture.h" +#include "qmenu.h" + +QT_BEGIN_NAMESPACE + +class QLabelPrivate : public QFramePrivate +{ + Q_DECLARE_PUBLIC(QLabel) +public: + QLabelPrivate() {} + + void init(); + void clearContents(); + void updateLabel(); + QSize sizeForWidth(int w) const; + + mutable QSize sh; + mutable QSize msh; + mutable bool valid_hints; + mutable QSizePolicy sizePolicy; + int margin; + QString text; + QPixmap *pixmap; + QPixmap *scaledpixmap; + QImage *cachedimage; +#ifndef QT_NO_PICTURE + QPicture *picture; +#endif +#ifndef QT_NO_MOVIE + QPointer<QMovie> movie; + void _q_movieUpdated(const QRect&); + void _q_movieResized(const QSize&); +#endif +#ifndef QT_NO_SHORTCUT + void updateShortcut(); +#endif +#ifndef QT_NO_SHORTCUT + QPointer<QWidget> buddy; + int shortcutId; +#endif + ushort align; + short indent; + uint scaledcontents :1; + mutable uint textLayoutDirty : 1; + mutable uint textDirty : 1; + mutable uint isRichText : 1; + mutable uint isTextLabel : 1; + mutable uint hasShortcut : 1; + Qt::TextFormat textformat; + mutable QTextControl *control; + QTextCursor shortcutCursor; + Qt::TextInteractionFlags textInteractionFlags; + + inline bool needTextControl() const { + return isTextLabel + && (isRichText + || (!isRichText && (textInteractionFlags & (Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard)))); + } + + void ensureTextPopulated() const; + void ensureTextLayouted() const; + void ensureTextControl() const; + void sendControlEvent(QEvent *e); + + void _q_linkHovered(const QString &link); + + QRectF layoutRect() const; + QRect documentRect() const; + QPoint layoutPoint(const QPoint& p) const; +#ifndef QT_NO_CONTEXTMENU + QMenu *createStandardContextMenu(const QPoint &pos); +#endif + + bool openExternalLinks; + +#ifndef QT_NO_CURSOR + uint validCursor : 1; + uint onAnchor : 1; + QCursor cursor; +#endif + + friend class QMessageBoxPrivate; +}; + +QT_END_NAMESPACE + +#endif // QLABEL_P_H diff --git a/src/gui/widgets/qlcdnumber.cpp b/src/gui/widgets/qlcdnumber.cpp new file mode 100644 index 0000000..0136f1a --- /dev/null +++ b/src/gui/widgets/qlcdnumber.cpp @@ -0,0 +1,1282 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlcdnumber.h" +#ifndef QT_NO_LCDNUMBER +#include "qbitarray.h" +#include "qpainter.h" +#include "private/qframe_p.h" + +QT_BEGIN_NAMESPACE + +class QLCDNumberPrivate : public QFramePrivate +{ + Q_DECLARE_PUBLIC(QLCDNumber) +public: + void init(); + void internalSetString(const QString& s); + void drawString(const QString& s, QPainter &, QBitArray * = 0, bool = true); + //void drawString(const QString &, QPainter &, QBitArray * = 0) const; + void drawDigit(const QPoint &, QPainter &, int, char, char = ' '); + void drawSegment(const QPoint &, char, QPainter &, int, bool = false); + + int ndigits; + double val; + uint base : 2; + uint smallPoint : 1; + uint fill : 1; + uint shadow : 1; + QString digitStr; + QBitArray points; +}; + +/*! + \class QLCDNumber + + \brief The QLCDNumber widget displays a number with LCD-like digits. + + \ingroup basicwidgets + \mainclass + + It can display a number in just about any size. It can display + decimal, hexadecimal, octal or binary numbers. It is easy to + connect to data sources using the display() slot, which is + overloaded to take any of five argument types. + + There are also slots to change the base with setMode() and the + decimal point with setSmallDecimalPoint(). + + QLCDNumber emits the overflow() signal when it is asked to display + something beyond its range. The range is set by setNumDigits(), + but setSmallDecimalPoint() also influences it. If the display is + set to hexadecimal, octal or binary, the integer equivalent of the + value is displayed. + + These digits and other symbols can be shown: 0/O, 1, 2, 3, 4, 5/S, + 6, 7, 8, 9/g, minus, decimal point, A, B, C, D, E, F, h, H, L, o, + P, r, u, U, Y, colon, degree sign (which is specified as single + quote in the string) and space. QLCDNumber substitutes spaces for + illegal characters. + + It is not possible to retrieve the contents of a QLCDNumber + object, although you can retrieve the numeric value with value(). + If you really need the text, we recommend that you connect the + signals that feed the display() slot to another slot as well and + store the value there. + + Incidentally, QLCDNumber is the very oldest part of Qt, tracing + its roots back to a BASIC program on the \link + http://www.nvg.ntnu.no/sinclair/computers/zxspectrum/zxspectrum.htm + Sinclair Spectrum\endlink. + + \table + \row \o \inlineimage motif-lcdnumber.png Screenshot of a Motif style LCD number widget + \inlineimage cde-lcdnumber.png Screenshot of a CDE style LCD number widget + \inlineimage windows-lcdnumber.png Screenshot of a Windows style LCD number widget + \inlineimage windowsxp-lcdnumber.png Screenshot of a Windows XP style LCD number widget + \inlineimage macintosh-lcdnumber.png Screenshot of a Macintosh style LCD number widget + \inlineimage plastique-lcdnumber.png Screenshot of a Plastique style LCD number widget + \row \o LCD number widgets shown in various widget styles (from left to right): + \l{Motif Style Widget Gallery}{Motif}, \l{CDE Style Widget Gallery}{CDE}, + \l{Windows Style Widget Gallery}{Windows}, \l{Windows XP Style Widget Gallery}{Windows XP}, + \l{Macintosh Style Widget Gallery}{Macintosh}, \l{Plastique Style Widget Gallery}{Plastique}. + \endtable + + \sa QLabel, QFrame, {Digital Clock Example}, {Tetrix Example} +*/ + +/*! + \enum QLCDNumber::Mode + + This type determines how numbers are shown. + + \value Hex Hexadecimal + \value Dec Decimal + \value Oct Octal + \value Bin Binary + \omitvalue HEX + \omitvalue DEC + \omitvalue OCT + \omitvalue BIN + + If the display is set to hexadecimal, octal or binary, the integer + equivalent of the value is displayed. +*/ + +/*! + \enum QLCDNumber::SegmentStyle + + This type determines the visual appearance of the QLCDNumber + widget. + + \value Outline gives raised segments filled with the background color. + \value Filled gives raised segments filled with the windowText color. + \value Flat gives flat segments filled with the windowText color. +*/ + + + +/*! + \fn void QLCDNumber::overflow() + + This signal is emitted whenever the QLCDNumber is asked to display + a too-large number or a too-long string. + + It is never emitted by setNumDigits(). +*/ + + +static QString int2string(int num, int base, int ndigits, bool *oflow) +{ + QString s; + bool negative; + if (num < 0) { + negative = true; + num = -num; + } else { + negative = false; + } + switch(base) { + case QLCDNumber::Hex: + s.sprintf("%*x", ndigits, num); + break; + case QLCDNumber::Dec: + s.sprintf("%*i", ndigits, num); + break; + case QLCDNumber::Oct: + s.sprintf("%*o", ndigits, num); + break; + case QLCDNumber::Bin: + { + char buf[42]; + char *p = &buf[41]; + uint n = num; + int len = 0; + *p = '\0'; + do { + *--p = (char)((n&1)+'0'); + n >>= 1; + len++; + } while (n != 0); + len = ndigits - len; + if (len > 0) + s.fill(QLatin1Char(' '), len); + s += QString::fromLatin1(p); + } + break; + } + if (negative) { + for (int i=0; i<(int)s.length(); i++) { + if (s[i] != QLatin1Char(' ')) { + if (i != 0) { + s[i-1] = QLatin1Char('-'); + } else { + s.insert(0, QLatin1Char('-')); + } + break; + } + } + } + if (oflow) + *oflow = (int)s.length() > ndigits; + return s; +} + + +static QString double2string(double num, int base, int ndigits, bool *oflow) +{ + QString s; + if (base != QLCDNumber::Dec) { + bool of = num >= 2147483648.0 || num < -2147483648.0; + if (of) { // oops, integer overflow + if (oflow) + *oflow = true; + return s; + } + s = int2string((int)num, base, ndigits, 0); + } else { // decimal base + int nd = ndigits; + do { + s.sprintf("%*.*g", ndigits, nd, num); + int i = s.indexOf(QLatin1Char('e')); + if (i > 0 && s[i+1]==QLatin1Char('+')) { + s[i] = QLatin1Char(' '); + s[i+1] = QLatin1Char('e'); + } + } while (nd-- && (int)s.length() > ndigits); + } + if (oflow) + *oflow = (int)s.length() > ndigits; + return s; +} + + +static const char *getSegments(char ch) // gets list of segments for ch +{ + static const char segments[30][8] = + { { 0, 1, 2, 4, 5, 6,99, 0}, // 0 0 / O + { 2, 5,99, 0, 0, 0, 0, 0}, // 1 1 + { 0, 2, 3, 4, 6,99, 0, 0}, // 2 2 + { 0, 2, 3, 5, 6,99, 0, 0}, // 3 3 + { 1, 2, 3, 5,99, 0, 0, 0}, // 4 4 + { 0, 1, 3, 5, 6,99, 0, 0}, // 5 5 / S + { 0, 1, 3, 4, 5, 6,99, 0}, // 6 6 + { 0, 2, 5,99, 0, 0, 0, 0}, // 7 7 + { 0, 1, 2, 3, 4, 5, 6,99}, // 8 8 + { 0, 1, 2, 3, 5, 6,99, 0}, // 9 9 / g + { 3,99, 0, 0, 0, 0, 0, 0}, // 10 - + { 7,99, 0, 0, 0, 0, 0, 0}, // 11 . + { 0, 1, 2, 3, 4, 5,99, 0}, // 12 A + { 1, 3, 4, 5, 6,99, 0, 0}, // 13 B + { 0, 1, 4, 6,99, 0, 0, 0}, // 14 C + { 2, 3, 4, 5, 6,99, 0, 0}, // 15 D + { 0, 1, 3, 4, 6,99, 0, 0}, // 16 E + { 0, 1, 3, 4,99, 0, 0, 0}, // 17 F + { 1, 3, 4, 5,99, 0, 0, 0}, // 18 h + { 1, 2, 3, 4, 5,99, 0, 0}, // 19 H + { 1, 4, 6,99, 0, 0, 0, 0}, // 20 L + { 3, 4, 5, 6,99, 0, 0, 0}, // 21 o + { 0, 1, 2, 3, 4,99, 0, 0}, // 22 P + { 3, 4,99, 0, 0, 0, 0, 0}, // 23 r + { 4, 5, 6,99, 0, 0, 0, 0}, // 24 u + { 1, 2, 4, 5, 6,99, 0, 0}, // 25 U + { 1, 2, 3, 5, 6,99, 0, 0}, // 26 Y + { 8, 9,99, 0, 0, 0, 0, 0}, // 27 : + { 0, 1, 2, 3,99, 0, 0, 0}, // 28 ' + {99, 0, 0, 0, 0, 0, 0, 0} }; // 29 empty + + if (ch >= '0' && ch <= '9') + return segments[ch - '0']; + if (ch >= 'A' && ch <= 'F') + return segments[ch - 'A' + 12]; + if (ch >= 'a' && ch <= 'f') + return segments[ch - 'a' + 12]; + + int n; + switch (ch) { + case '-': + n = 10; break; + case 'O': + n = 0; break; + case 'g': + n = 9; break; + case '.': + n = 11; break; + case 'h': + n = 18; break; + case 'H': + n = 19; break; + case 'l': + case 'L': + n = 20; break; + case 'o': + n = 21; break; + case 'p': + case 'P': + n = 22; break; + case 'r': + case 'R': + n = 23; break; + case 's': + case 'S': + n = 5; break; + case 'u': + n = 24; break; + case 'U': + n = 25; break; + case 'y': + case 'Y': + n = 26; break; + case ':': + n = 27; break; + case '\'': + n = 28; break; + default: + n = 29; break; + } + return segments[n]; +} + + +#ifdef QT3_SUPPORT +/*! \obsolete + Constructs an LCD number, sets the number of digits to 5, the base + to decimal, the decimal point mode to 'small' and the frame style + to a raised box. The segmentStyle() is set to \c Outline. + + The \a parent and \a name arguments are passed to the QFrame + constructor. + + \sa setNumDigits(), setSmallDecimalPoint() +*/ + +QLCDNumber::QLCDNumber(QWidget *parent, const char *name) + : QFrame(*new QLCDNumberPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + Q_D(QLCDNumber); + d->ndigits = 5; + d->init(); +} + + +/*! \obsolete + Constructs an LCD number, sets the number of digits to \a + numDigits, the base to decimal, the decimal point mode to 'small' + and the frame style to a raised box. The segmentStyle() is set to + \c Outline. + + The \a parent and \a name arguments are passed to the QFrame + constructor. + + \sa setNumDigits(), setSmallDecimalPoint() +*/ + +QLCDNumber::QLCDNumber(uint numDigits, QWidget *parent, const char *name) + : QFrame(*new QLCDNumberPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + Q_D(QLCDNumber); + d->ndigits = numDigits; + d->init(); +} +#endif //QT3_SUPPORT + +/*! + Constructs an LCD number, sets the number of digits to 5, the base + to decimal, the decimal point mode to 'small' and the frame style + to a raised box. The segmentStyle() is set to \c Outline. + + The \a parent argument is passed to the QFrame constructor. + + \sa setNumDigits(), setSmallDecimalPoint() +*/ + +QLCDNumber::QLCDNumber(QWidget *parent) + : QFrame(*new QLCDNumberPrivate, parent) +{ + Q_D(QLCDNumber); + d->ndigits = 5; + d->init(); +} + + +/*! + Constructs an LCD number, sets the number of digits to \a + numDigits, the base to decimal, the decimal point mode to 'small' + and the frame style to a raised box. The segmentStyle() is set to + \c Outline. + + The \a parent argument is passed to the QFrame constructor. + + \sa setNumDigits(), setSmallDecimalPoint() +*/ + +QLCDNumber::QLCDNumber(uint numDigits, QWidget *parent) + : QFrame(*new QLCDNumberPrivate, parent) +{ + Q_D(QLCDNumber); + d->ndigits = numDigits; + d->init(); +} + +void QLCDNumberPrivate::init() +{ + Q_Q(QLCDNumber); + + q->setFrameStyle(QFrame::Box | QFrame::Raised); + val = 0; + base = QLCDNumber::Dec; + smallPoint = false; + q->setNumDigits(ndigits); + q->setSegmentStyle(QLCDNumber::Outline); + q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); +} + +/*! + Destroys the LCD number. +*/ + +QLCDNumber::~QLCDNumber() +{ +} + + +/*! + \property QLCDNumber::numDigits + \brief the current number of digits displayed + + Corresponds to the current number of digits. If \l + QLCDNumber::smallDecimalPoint is false, the decimal point occupies + one digit position. + + By default, this property contains a value of 5. + + \sa smallDecimalPoint +*/ + +void QLCDNumber::setNumDigits(int numDigits) +{ + Q_D(QLCDNumber); + if (numDigits > 99) { + qWarning("QLCDNumber::setNumDigits: (%s) Max 99 digits allowed", + objectName().toLocal8Bit().constData()); + numDigits = 99; + } + if (numDigits < 0) { + qWarning("QLCDNumber::setNumDigits: (%s) Min 0 digits allowed", + objectName().toLocal8Bit().constData()); + numDigits = 0; + } + if (d->digitStr.isNull()) { // from constructor + d->ndigits = numDigits; + d->digitStr.fill(QLatin1Char(' '), d->ndigits); + d->points.fill(0, d->ndigits); + d->digitStr[d->ndigits - 1] = QLatin1Char('0'); // "0" is the default number + } else { + bool doDisplay = d->ndigits == 0; + if (numDigits == d->ndigits) // no change + return; + register int i; + int dif; + if (numDigits > d->ndigits) { // expand + dif = numDigits - d->ndigits; + QString buf; + buf.fill(QLatin1Char(' '), dif); + d->digitStr.insert(0, buf); + d->points.resize(numDigits); + for (i=numDigits-1; i>=dif; i--) + d->points.setBit(i, d->points.testBit(i-dif)); + for (i=0; i<dif; i++) + d->points.clearBit(i); + } else { // shrink + dif = d->ndigits - numDigits; + d->digitStr = d->digitStr.right(numDigits); + QBitArray tmpPoints = d->points; + d->points.resize(numDigits); + for (i=0; i<(int)numDigits; i++) + d->points.setBit(i, tmpPoints.testBit(i+dif)); + } + d->ndigits = numDigits; + if (doDisplay) + display(value()); + update(); + } +} + +int QLCDNumber::numDigits() const +{ + Q_D(const QLCDNumber); + return d->ndigits; +} + +/*! + \overload + + Returns true if \a num is too big to be displayed in its entirety; + otherwise returns false. + + \sa display(), numDigits(), smallDecimalPoint() +*/ + +bool QLCDNumber::checkOverflow(int num) const +{ + Q_D(const QLCDNumber); + bool of; + int2string(num, d->base, d->ndigits, &of); + return of; +} + + +/*! + Returns true if \a num is too big to be displayed in its entirety; + otherwise returns false. + + \sa display(), numDigits(), smallDecimalPoint() +*/ + +bool QLCDNumber::checkOverflow(double num) const +{ + Q_D(const QLCDNumber); + bool of; + double2string(num, d->base, d->ndigits, &of); + return of; +} + + +/*! + \property QLCDNumber::mode + \brief the current display mode (number base) + + Corresponds to the current display mode, which is one of \c Bin, + \c Oct, \c Dec (the default) and \c Hex. \c Dec mode can display + floating point values, the other modes display the integer + equivalent. + + \sa smallDecimalPoint(), setHexMode(), setDecMode(), setOctMode(), setBinMode() +*/ + +QLCDNumber::Mode QLCDNumber::mode() const +{ + Q_D(const QLCDNumber); + return (QLCDNumber::Mode) d->base; +} + +void QLCDNumber::setMode(Mode m) +{ + Q_D(QLCDNumber); + d->base = m; + display(d->val); +} + + +/*! + \property QLCDNumber::value + \brief the displayed value + + This property corresponds to the current value displayed by the + LCDNumber. + + If the displayed value is not a number, the property has a value + of 0. + + By default, this property contains a value of 0. +*/ + +double QLCDNumber::value() const +{ + Q_D(const QLCDNumber); + return d->val; +} + +/*! + \overload + + Displays the number \a num. +*/ +void QLCDNumber::display(double num) +{ + Q_D(QLCDNumber); + d->val = num; + bool of; + QString s = double2string(d->val, d->base, d->ndigits, &of); + if (of) + emit overflow(); + else + d->internalSetString(s); +} + +/*! + \property QLCDNumber::intValue + \brief the displayed value rounded to the nearest integer + + This property corresponds to the nearest integer to the current + value displayed by the LCDNumber. This is the value used for + hexadecimal, octal and binary modes. + + If the displayed value is not a number, the property has a value + of 0. + + By default, this property contains a value of 0. +*/ +int QLCDNumber::intValue() const +{ + Q_D(const QLCDNumber); + return qRound(d->val); +} + + +/*! + \overload + + Displays the number \a num. +*/ +void QLCDNumber::display(int num) +{ + Q_D(QLCDNumber); + d->val = (double)num; + bool of; + QString s = int2string(num, d->base, d->ndigits, &of); + if (of) + emit overflow(); + else + d->internalSetString(s); +} + + +/*! + Displays the number represented by the string \a s. + + This version of the function disregards mode() and + smallDecimalPoint(). + + These digits and other symbols can be shown: 0/O, 1, 2, 3, 4, 5/S, + 6, 7, 8, 9/g, minus, decimal point, A, B, C, D, E, F, h, H, L, o, + P, r, u, U, Y, colon, degree sign (which is specified as single + quote in the string) and space. QLCDNumber substitutes spaces for + illegal characters. +*/ + +void QLCDNumber::display(const QString &s) +{ + Q_D(QLCDNumber); + d->val = 0; + bool ok = false; + double v = s.toDouble(&ok); + if (ok) + d->val = v; + d->internalSetString(s); +} + +/*! + Calls setMode(Hex). Provided for convenience (e.g. for + connecting buttons to it). + + \sa setMode(), setDecMode(), setOctMode(), setBinMode(), mode() +*/ + +void QLCDNumber::setHexMode() +{ + setMode(Hex); +} + + +/*! + Calls setMode(Dec). Provided for convenience (e.g. for + connecting buttons to it). + + \sa setMode(), setHexMode(), setOctMode(), setBinMode(), mode() +*/ + +void QLCDNumber::setDecMode() +{ + setMode(Dec); +} + + +/*! + Calls setMode(Oct). Provided for convenience (e.g. for + connecting buttons to it). + + \sa setMode(), setHexMode(), setDecMode(), setBinMode(), mode() +*/ + +void QLCDNumber::setOctMode() +{ + setMode(Oct); +} + + +/*! + Calls setMode(Bin). Provided for convenience (e.g. for + connecting buttons to it). + + \sa setMode(), setHexMode(), setDecMode(), setOctMode(), mode() +*/ + +void QLCDNumber::setBinMode() +{ + setMode(Bin); +} + + +/*! + \property QLCDNumber::smallDecimalPoint + \brief the style of the decimal point + + If true the decimal point is drawn between two digit positions. + Otherwise it occupies a digit position of its own, i.e. is drawn + in a digit position. The default is false. + + The inter-digit space is made slightly wider when the decimal + point is drawn between the digits. + + \sa mode +*/ + +void QLCDNumber::setSmallDecimalPoint(bool b) +{ + Q_D(QLCDNumber); + d->smallPoint = b; + update(); +} + +bool QLCDNumber::smallDecimalPoint() const +{ + Q_D(const QLCDNumber); + return d->smallPoint; +} + + + +/*!\reimp +*/ + + +void QLCDNumber::paintEvent(QPaintEvent *) +{ + Q_D(QLCDNumber); + QPainter p(this); + drawFrame(&p); + if (d->smallPoint) + d->drawString(d->digitStr, p, &d->points, false); + else + d->drawString(d->digitStr, p, 0, false); +} + + +void QLCDNumberPrivate::internalSetString(const QString& s) +{ + Q_Q(QLCDNumber); + QString buffer; + int i; + int len = s.length(); + QBitArray newPoints(ndigits); + + if (!smallPoint) { + if (len == ndigits) + buffer = s; + else + buffer = s.right(ndigits).rightJustified(ndigits, QLatin1Char(' ')); + } else { + int index = -1; + bool lastWasPoint = true; + newPoints.clearBit(0); + for (i=0; i<len; i++) { + if (s[i] == QLatin1Char('.')) { + if (lastWasPoint) { // point already set for digit? + if (index == ndigits - 1) // no more digits + break; + index++; + buffer[index] = QLatin1Char(' '); // 2 points in a row, add space + } + newPoints.setBit(index); // set decimal point + lastWasPoint = true; + } else { + if (index == ndigits - 1) + break; + index++; + buffer[index] = s[i]; + newPoints.clearBit(index); // decimal point default off + lastWasPoint = false; + } + } + if (index < ((int) ndigits) - 1) { + for(i=index; i>=0; i--) { + buffer[ndigits - 1 - index + i] = buffer[i]; + newPoints.setBit(ndigits - 1 - index + i, + newPoints.testBit(i)); + } + for(i=0; i<ndigits-index-1; i++) { + buffer[i] = QLatin1Char(' '); + newPoints.clearBit(i); + } + } + } + + if (buffer == digitStr) + return; + + digitStr = buffer; + if (smallPoint) + points = newPoints; + q->update(); +} + +/*! + \internal +*/ + +void QLCDNumberPrivate::drawString(const QString &s, QPainter &p, + QBitArray *newPoints, bool newString) +{ + Q_Q(QLCDNumber); + QPoint pos; + + int digitSpace = smallPoint ? 2 : 1; + int xSegLen = q->width()*5/(ndigits*(5 + digitSpace) + digitSpace); + int ySegLen = q->height()*5/12; + int segLen = ySegLen > xSegLen ? xSegLen : ySegLen; + int xAdvance = segLen*(5 + digitSpace)/5; + int xOffset = (q->width() - ndigits*xAdvance + segLen/5)/2; + int yOffset = (q->height() - segLen*2)/2; + + for (int i=0; i<ndigits; i++) { + pos = QPoint(xOffset + xAdvance*i, yOffset); + if (newString) + drawDigit(pos, p, segLen, s[i].toLatin1(), digitStr[i].toLatin1()); + else + drawDigit(pos, p, segLen, s[i].toLatin1()); + if (newPoints) { + char newPoint = newPoints->testBit(i) ? '.' : ' '; + if (newString) { + char oldPoint = points.testBit(i) ? '.' : ' '; + drawDigit(pos, p, segLen, newPoint, oldPoint); + } else { + drawDigit(pos, p, segLen, newPoint); + } + } + } + if (newString) { + digitStr = s; + digitStr.truncate(ndigits); + if (newPoints) + points = *newPoints; + } +} + + +/*! + \internal +*/ + +void QLCDNumberPrivate::drawDigit(const QPoint &pos, QPainter &p, int segLen, + char newCh, char oldCh) +{ +// Draws and/or erases segments to change display of a single digit +// from oldCh to newCh + + char updates[18][2]; // can hold 2 times number of segments, only + // first 9 used if segment table is correct + int nErases; + int nUpdates; + const char *segs; + int i,j; + + const char erase = 0; + const char draw = 1; + const char leaveAlone = 2; + + segs = getSegments(oldCh); + for (nErases=0; segs[nErases] != 99; nErases++) { + updates[nErases][0] = erase; // get segments to erase to + updates[nErases][1] = segs[nErases]; // remove old char + } + nUpdates = nErases; + segs = getSegments(newCh); + for(i = 0 ; segs[i] != 99 ; i++) { + for (j=0; j<nErases; j++) + if (segs[i] == updates[j][1]) { // same segment ? + updates[j][0] = leaveAlone; // yes, already on screen + break; + } + if (j == nErases) { // if not already on screen + updates[nUpdates][0] = draw; + updates[nUpdates][1] = segs[i]; + nUpdates++; + } + } + for (i=0; i<nUpdates; i++) { + if (updates[i][0] == draw) + drawSegment(pos, updates[i][1], p, segLen); + if (updates[i][0] == erase) + drawSegment(pos, updates[i][1], p, segLen, true); + } +} + + +static void addPoint(QPolygon &a, const QPoint &p) +{ + uint n = a.size(); + a.resize(n + 1); + a.setPoint(n, p); +} + +/*! + \internal +*/ + +void QLCDNumberPrivate::drawSegment(const QPoint &pos, char segmentNo, QPainter &p, + int segLen, bool erase) +{ + Q_Q(QLCDNumber); + QPoint ppt; + QPoint pt = pos; + int width = segLen/5; + + const QPalette &pal = q->palette(); + QColor lightColor,darkColor,fgColor; + if (erase){ + lightColor = pal.color(q->backgroundRole()); + darkColor = lightColor; + fgColor = lightColor; + } else { + lightColor = pal.light().color(); + darkColor = pal.dark().color(); + fgColor = pal.color(q->foregroundRole()); + } + + +#define LINETO(X,Y) addPoint(a, QPoint(pt.x() + (X),pt.y() + (Y))) +#define LIGHT +#define DARK + + if (fill) { + QPolygon a(0); + //The following is an exact copy of the switch below. + //don't make any changes here + switch (segmentNo) { + case 0 : + ppt = pt; + LIGHT; + LINETO(segLen - 1,0); + DARK; + LINETO(segLen - width - 1,width); + LINETO(width,width); + LINETO(0,0); + break; + case 1 : + pt += QPoint(0 , 1); + ppt = pt; + LIGHT; + LINETO(width,width); + DARK; + LINETO(width,segLen - width/2 - 2); + LINETO(0,segLen - 2); + LIGHT; + LINETO(0,0); + break; + case 2 : + pt += QPoint(segLen - 1 , 1); + ppt = pt; + DARK; + LINETO(0,segLen - 2); + LINETO(-width,segLen - width/2 - 2); + LIGHT; + LINETO(-width,width); + LINETO(0,0); + break; + case 3 : + pt += QPoint(0 , segLen); + ppt = pt; + LIGHT; + LINETO(width,-width/2); + LINETO(segLen - width - 1,-width/2); + LINETO(segLen - 1,0); + DARK; + if (width & 1) { // adjust for integer division error + LINETO(segLen - width - 3,width/2 + 1); + LINETO(width + 2,width/2 + 1); + } else { + LINETO(segLen - width - 1,width/2); + LINETO(width,width/2); + } + LINETO(0,0); + break; + case 4 : + pt += QPoint(0 , segLen + 1); + ppt = pt; + LIGHT; + LINETO(width,width/2); + DARK; + LINETO(width,segLen - width - 2); + LINETO(0,segLen - 2); + LIGHT; + LINETO(0,0); + break; + case 5 : + pt += QPoint(segLen - 1 , segLen + 1); + ppt = pt; + DARK; + LINETO(0,segLen - 2); + LINETO(-width,segLen - width - 2); + LIGHT; + LINETO(-width,width/2); + LINETO(0,0); + break; + case 6 : + pt += QPoint(0 , segLen*2); + ppt = pt; + LIGHT; + LINETO(width,-width); + LINETO(segLen - width - 1,-width); + LINETO(segLen - 1,0); + DARK; + LINETO(0,0); + break; + case 7 : + if (smallPoint) // if smallpoint place'.' between other digits + pt += QPoint(segLen + width/2 , segLen*2); + else + pt += QPoint(segLen/2 , segLen*2); + ppt = pt; + DARK; + LINETO(width,0); + LINETO(width,-width); + LIGHT; + LINETO(0,-width); + LINETO(0,0); + break; + case 8 : + pt += QPoint(segLen/2 - width/2 + 1 , segLen/2 + width); + ppt = pt; + DARK; + LINETO(width,0); + LINETO(width,-width); + LIGHT; + LINETO(0,-width); + LINETO(0,0); + break; + case 9 : + pt += QPoint(segLen/2 - width/2 + 1 , 3*segLen/2 + width); + ppt = pt; + DARK; + LINETO(width,0); + LINETO(width,-width); + LIGHT; + LINETO(0,-width); + LINETO(0,0); + break; + default : + qWarning("QLCDNumber::drawSegment: (%s) Illegal segment id: %d\n", + q->objectName().toLocal8Bit().constData(), segmentNo); + } + // End exact copy + p.setPen(fgColor); + p.setBrush(fgColor); + p.drawPolygon(a); + p.setBrush(Qt::NoBrush); + + pt = pos; + } +#undef LINETO +#undef LIGHT +#undef DARK + +#define LINETO(X,Y) p.drawLine(ppt.x(), ppt.y(), pt.x()+(X), pt.y()+(Y)); \ + ppt = QPoint(pt.x()+(X), pt.y()+(Y)) +#define LIGHT p.setPen(lightColor) +#define DARK p.setPen(darkColor) + if (shadow) + switch (segmentNo) { + case 0 : + ppt = pt; + LIGHT; + LINETO(segLen - 1,0); + DARK; + LINETO(segLen - width - 1,width); + LINETO(width,width); + LINETO(0,0); + break; + case 1 : + pt += QPoint(0,1); + ppt = pt; + LIGHT; + LINETO(width,width); + DARK; + LINETO(width,segLen - width/2 - 2); + LINETO(0,segLen - 2); + LIGHT; + LINETO(0,0); + break; + case 2 : + pt += QPoint(segLen - 1 , 1); + ppt = pt; + DARK; + LINETO(0,segLen - 2); + LINETO(-width,segLen - width/2 - 2); + LIGHT; + LINETO(-width,width); + LINETO(0,0); + break; + case 3 : + pt += QPoint(0 , segLen); + ppt = pt; + LIGHT; + LINETO(width,-width/2); + LINETO(segLen - width - 1,-width/2); + LINETO(segLen - 1,0); + DARK; + if (width & 1) { // adjust for integer division error + LINETO(segLen - width - 3,width/2 + 1); + LINETO(width + 2,width/2 + 1); + } else { + LINETO(segLen - width - 1,width/2); + LINETO(width,width/2); + } + LINETO(0,0); + break; + case 4 : + pt += QPoint(0 , segLen + 1); + ppt = pt; + LIGHT; + LINETO(width,width/2); + DARK; + LINETO(width,segLen - width - 2); + LINETO(0,segLen - 2); + LIGHT; + LINETO(0,0); + break; + case 5 : + pt += QPoint(segLen - 1 , segLen + 1); + ppt = pt; + DARK; + LINETO(0,segLen - 2); + LINETO(-width,segLen - width - 2); + LIGHT; + LINETO(-width,width/2); + LINETO(0,0); + break; + case 6 : + pt += QPoint(0 , segLen*2); + ppt = pt; + LIGHT; + LINETO(width,-width); + LINETO(segLen - width - 1,-width); + LINETO(segLen - 1,0); + DARK; + LINETO(0,0); + break; + case 7 : + if (smallPoint) // if smallpoint place'.' between other digits + pt += QPoint(segLen + width/2 , segLen*2); + else + pt += QPoint(segLen/2 , segLen*2); + ppt = pt; + DARK; + LINETO(width,0); + LINETO(width,-width); + LIGHT; + LINETO(0,-width); + LINETO(0,0); + break; + case 8 : + pt += QPoint(segLen/2 - width/2 + 1 , segLen/2 + width); + ppt = pt; + DARK; + LINETO(width,0); + LINETO(width,-width); + LIGHT; + LINETO(0,-width); + LINETO(0,0); + break; + case 9 : + pt += QPoint(segLen/2 - width/2 + 1 , 3*segLen/2 + width); + ppt = pt; + DARK; + LINETO(width,0); + LINETO(width,-width); + LIGHT; + LINETO(0,-width); + LINETO(0,0); + break; + default : + qWarning("QLCDNumber::drawSegment: (%s) Illegal segment id: %d\n", + q->objectName().toLocal8Bit().constData(), segmentNo); + } + +#undef LINETO +#undef LIGHT +#undef DARK +} + + + +/*! + \property QLCDNumber::segmentStyle + \brief the style of the LCDNumber + + \table + \header \i Style \i Result + \row \i \c Outline + \i Produces raised segments filled with the background color + (this is the default). + \row \i \c Filled + \i Produces raised segments filled with the foreground color. + \row \i \c Flat + \i Produces flat segments filled with the foreground color. + \endtable + + \c Outline and \c Filled will additionally use + QPalette::light() and QPalette::dark() for shadow effects. +*/ +void QLCDNumber::setSegmentStyle(SegmentStyle s) +{ + Q_D(QLCDNumber); + d->fill = (s == Flat || s == Filled); + d->shadow = (s == Outline || s == Filled); + update(); +} + +QLCDNumber::SegmentStyle QLCDNumber::segmentStyle() const +{ + Q_D(const QLCDNumber); + Q_ASSERT(d->fill || d->shadow); + if (!d->fill && d->shadow) + return Outline; + if (d->fill && d->shadow) + return Filled; + return Flat; +} + + +/*!\reimp +*/ +QSize QLCDNumber::sizeHint() const +{ + return QSize(10 + 9 * (numDigits() + (smallDecimalPoint() ? 0 : 1)), 23); +} + +/*! \reimp */ +bool QLCDNumber::event(QEvent *e) +{ + return QFrame::event(e); +} + +/*! + \fn void QLCDNumber::setMargin(int margin) + Sets the width of the margin around the contents of the widget to \a margin. + + Use QWidget::setContentsMargins() instead. + \sa margin(), QWidget::setContentsMargins() +*/ + +/*! + \fn int QLCDNumber::margin() const + Returns the with of the the margin around the contents of the widget. + + Use QWidget::getContentsMargins() instead. + \sa setMargin(), QWidget::getContentsMargins() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_LCDNUMBER diff --git a/src/gui/widgets/qlcdnumber.h b/src/gui/widgets/qlcdnumber.h new file mode 100644 index 0000000..626c85d --- /dev/null +++ b/src/gui/widgets/qlcdnumber.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLCDNUMBER_H +#define QLCDNUMBER_H + +#include <QtGui/qframe.h> +#include <QtCore/qbitarray.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LCDNUMBER + +class QLCDNumberPrivate; +class Q_GUI_EXPORT QLCDNumber : public QFrame // LCD number widget +{ + Q_OBJECT + Q_ENUMS(Mode SegmentStyle) + Q_PROPERTY(bool smallDecimalPoint READ smallDecimalPoint WRITE setSmallDecimalPoint) + Q_PROPERTY(int numDigits READ numDigits WRITE setNumDigits) + Q_PROPERTY(Mode mode READ mode WRITE setMode) + Q_PROPERTY(SegmentStyle segmentStyle READ segmentStyle WRITE setSegmentStyle) + Q_PROPERTY(double value READ value WRITE display) + Q_PROPERTY(int intValue READ intValue WRITE display) + +public: + explicit QLCDNumber(QWidget* parent = 0); + explicit QLCDNumber(uint numDigits, QWidget* parent = 0); + ~QLCDNumber(); + + enum Mode { + Hex, Dec, Oct, Bin +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + , HEX = Hex, DEC = Dec, OCT = Oct, BIN = Bin +#endif + }; + enum SegmentStyle { + Outline, Filled, Flat + }; + + bool smallDecimalPoint() const; + + int numDigits() const; + void setNumDigits(int nDigits); + + bool checkOverflow(double num) const; + bool checkOverflow(int num) const; + + Mode mode() const; + void setMode(Mode); + + SegmentStyle segmentStyle() const; + void setSegmentStyle(SegmentStyle); + + double value() const; + int intValue() const; + + QSize sizeHint() const; + +public Q_SLOTS: + void display(const QString &str); + void display(int num); + void display(double num); + void setHexMode(); + void setDecMode(); + void setOctMode(); + void setBinMode(); + void setSmallDecimalPoint(bool); + +Q_SIGNALS: + void overflow(); + +protected: + bool event(QEvent *e); + void paintEvent(QPaintEvent *); + +public: +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QLCDNumber(QWidget* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QLCDNumber(uint numDigits, QWidget* parent, const char* name); + + QT3_SUPPORT void setMargin(int margin) { setContentsMargins(margin, margin, margin, margin); } + QT3_SUPPORT int margin() const + { int margin; int dummy; getContentsMargins(&margin, &dummy, &dummy, &dummy); return margin; } +#endif + +private: + Q_DISABLE_COPY(QLCDNumber) + Q_DECLARE_PRIVATE(QLCDNumber) +}; + +#endif // QT_NO_LCDNUMBER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLCDNUMBER_H diff --git a/src/gui/widgets/qlineedit.cpp b/src/gui/widgets/qlineedit.cpp new file mode 100644 index 0000000..b03df9e --- /dev/null +++ b/src/gui/widgets/qlineedit.cpp @@ -0,0 +1,3696 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlineedit.h" +#include "qlineedit_p.h" + +#ifndef QT_NO_LINEEDIT +#include "qaction.h" +#include "qapplication.h" +#include "qclipboard.h" +#include "qdrag.h" +#include "qdrawutil.h" +#include "qevent.h" +#include "qfontmetrics.h" +#include "qmenu.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qpointer.h" +#include "qstringlist.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qtimer.h" +#include "qvalidator.h" +#include "qvariant.h" +#include "qvector.h" +#include "qwhatsthis.h" +#include "qdebug.h" +#include "qtextedit.h" +#include <private/qtextedit_p.h> +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#ifndef QT_NO_IM +#include "qinputcontext.h" +#include "qlist.h" +#endif +#include "qabstractitemview.h" +#include "private/qstylesheetstyle_p.h" + +#ifndef QT_NO_SHORTCUT +#include "private/qapplication_p.h" +#include "private/qshortcutmap_p.h" +#include "qkeysequence.h" +#define ACCEL_KEY(k) (!qApp->d_func()->shortcutMap.hasShortcutForKeySequence(k) ? QLatin1String("\t") + QString(QKeySequence(k)) : QString()) +#else +#define ACCEL_KEY(k) QString() +#endif + +#include <limits.h> + +#define verticalMargin 1 +#define horizontalMargin 2 + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_MAC +extern void qt_mac_secure_keyboard(bool); //qapplication_mac.cpp +#endif + +static inline bool shouldEnableInputMethod(QLineEdit *lineedit) +{ + const QLineEdit::EchoMode mode = lineedit->echoMode(); + return !lineedit->isReadOnly() && (mode == QLineEdit::Normal || mode == QLineEdit::PasswordEchoOnEdit); +} + +/*! + Initialize \a option with the values from this QLineEdit. This method + is useful for subclasses when they need a QStyleOptionFrame or QStyleOptionFrameV2, but don't want + to fill in all the information themselves. This function will check the version + of the QStyleOptionFrame and fill in the additional values for a + QStyleOptionFrameV2. + + \sa QStyleOption::initFrom() +*/ +void QLineEdit::initStyleOption(QStyleOptionFrame *option) const +{ + if (!option) + return; + + Q_D(const QLineEdit); + option->initFrom(this); + option->rect = contentsRect(); + option->lineWidth = d->frame ? style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this) + : 0; + option->midLineWidth = 0; + option->state |= QStyle::State_Sunken; + if (d->readOnly) + option->state |= QStyle::State_ReadOnly; +#ifdef QT_KEYPAD_NAVIGATION + if (hasEditFocus()) + option->state |= QStyle::State_HasEditFocus; +#endif + if (QStyleOptionFrameV2 *optionV2 = qstyleoption_cast<QStyleOptionFrameV2 *>(option)) + optionV2->features = QStyleOptionFrameV2::None; +} + +/*! + \class QLineEdit + \brief The QLineEdit widget is a one-line text editor. + + \ingroup basicwidgets + \mainclass + + A line edit allows the user to enter and edit a single line of + plain text with a useful collection of editing functions, + including undo and redo, cut and paste, and drag and drop. + + By changing the echoMode() of a line edit, it can also be used as + a "write-only" field, for inputs such as passwords. + + The length of the text can be constrained to maxLength(). The text + can be arbitrarily constrained using a validator() or an + inputMask(), or both. + + A related class is QTextEdit which allows multi-line, rich text + editing. + + You can change the text with setText() or insert(). The text is + retrieved with text(); the displayed text (which may be different, + see \l{EchoMode}) is retrieved with displayText(). Text can be + selected with setSelection() or selectAll(), and the selection can + be cut(), copy()ied and paste()d. The text can be aligned with + setAlignment(). + + When the text changes the textChanged() signal is emitted; when + the text changes other than by calling setText() the textEdited() + signal is emitted; when the cursor is moved the + cursorPositionChanged() signal is emitted; and when the Return or + Enter key is pressed the returnPressed() signal is emitted. + + When editing is finished, either because the line edit lost focus + or Return/Enter is pressed the editingFinished() signal is + emitted. + + Note that if there is a validator set on the line edit, the + returnPressed()/editingFinished() signals will only be emitted if + the validator returns QValidator::Acceptable. + + By default, QLineEdits have a frame as specified by the Windows + and Motif style guides; you can turn it off by calling + setFrame(false). + + The default key bindings are described below. The line edit also + provides a context menu (usually invoked by a right mouse click) + that presents some of these editing options. + \target desc + \table + \header \i Keypress \i Action + \row \i Left Arrow \i Moves the cursor one character to the left. + \row \i Shift+Left Arrow \i Moves and selects text one character to the left. + \row \i Right Arrow \i Moves the cursor one character to the right. + \row \i Shift+Right Arrow \i Moves and selects text one character to the right. + \row \i Home \i Moves the cursor to the beginning of the line. + \row \i End \i Moves the cursor to the end of the line. + \row \i Backspace \i Deletes the character to the left of the cursor. + \row \i Ctrl+Backspace \i Deletes the word to the left of the cursor. + \row \i Delete \i Deletes the character to the right of the cursor. + \row \i Ctrl+Delete \i Deletes the word to the right of the cursor. + \row \i Ctrl+A \i Select all. + \row \i Ctrl+C \i Copies the selected text to the clipboard. + \row \i Ctrl+Insert \i Copies the selected text to the clipboard. + \row \i Ctrl+K \i Deletes to the end of the line. + \row \i Ctrl+V \i Pastes the clipboard text into line edit. + \row \i Shift+Insert \i Pastes the clipboard text into line edit. + \row \i Ctrl+X \i Deletes the selected text and copies it to the clipboard. + \row \i Shift+Delete \i Deletes the selected text and copies it to the clipboard. + \row \i Ctrl+Z \i Undoes the last operation. + \row \i Ctrl+Y \i Redoes the last undone operation. + \endtable + + Any other key sequence that represents a valid character, will + cause the character to be inserted into the line edit. + + \table 100% + \row \o \inlineimage macintosh-lineedit.png Screenshot of a Macintosh style line edit + \o A line edit shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage windows-lineedit.png Screenshot of a Windows XP style line edit + \o A line edit shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage plastique-lineedit.png Screenshot of a Plastique style line edit + \o A line edit shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QTextEdit, QLabel, QComboBox, {fowler}{GUI Design Handbook: Field, Entry}, {Line Edits Example} +*/ + + +/*! + \fn void QLineEdit::textChanged(const QString &text) + + This signal is emitted whenever the text changes. The \a text + argument is the new text. + + Unlike textEdited(), this signal is also emitted when the text is + changed programmatically, for example, by calling setText(). +*/ + +/*! + \fn void QLineEdit::textEdited(const QString &text) + + This signal is emitted whenever the text is edited. The \a text + argument is the next text. + + Unlike textChanged(), this signal is not emitted when the text is + changed programmatically, for example, by calling setText(). +*/ + +/*! + \fn void QLineEdit::cursorPositionChanged(int old, int new) + + This signal is emitted whenever the cursor moves. The previous + position is given by \a old, and the new position by \a new. + + \sa setCursorPosition(), cursorPosition() +*/ + +/*! + \fn void QLineEdit::selectionChanged() + + This signal is emitted whenever the selection changes. + + \sa hasSelectedText(), selectedText() +*/ + +/*! + Constructs a line edit with no text. + + The maximum text length is set to 32767 characters. + + The \a parent argument is sent to the QWidget constructor. + + \sa setText(), setMaxLength() +*/ +QLineEdit::QLineEdit(QWidget* parent) + : QWidget(*new QLineEditPrivate, parent,0) +{ + Q_D(QLineEdit); + d->init(QString()); +} + +/*! + Constructs a line edit containing the text \a contents. + + The cursor position is set to the end of the line and the maximum + text length to 32767 characters. + + The \a parent and argument is sent to the QWidget + constructor. + + \sa text(), setMaxLength() +*/ +QLineEdit::QLineEdit(const QString& contents, QWidget* parent) + : QWidget(*new QLineEditPrivate, parent, 0) +{ + Q_D(QLineEdit); + d->init(contents); +} + + +#ifdef QT3_SUPPORT +/*! + Constructs a line edit with no text. + + The maximum text length is set to 32767 characters. + + The \a parent and \a name arguments are sent to the QWidget constructor. + + \sa setText(), setMaxLength() +*/ +QLineEdit::QLineEdit(QWidget* parent, const char* name) + : QWidget(*new QLineEditPrivate, parent,0) +{ + Q_D(QLineEdit); + setObjectName(QString::fromAscii(name)); + d->init(QString()); +} + +/*! + Constructs a line edit containing the text \a contents. + + The cursor position is set to the end of the line and the maximum + text length to 32767 characters. + + The \a parent and \a name arguments are sent to the QWidget + constructor. + + \sa text(), setMaxLength() +*/ + +QLineEdit::QLineEdit(const QString& contents, QWidget* parent, const char* name) + : QWidget(*new QLineEditPrivate, parent, 0) +{ + Q_D(QLineEdit); + setObjectName(QString::fromAscii(name)); + d->init(contents); +} + +/*! + Constructs a line edit with an input \a inputMask and the text \a + contents. + + The cursor position is set to the end of the line and the maximum + text length is set to the length of the mask (the number of mask + characters and separators). + + The \a parent and \a name arguments are sent to the QWidget + constructor. + + \sa setMask() text() +*/ +QLineEdit::QLineEdit(const QString& contents, const QString &inputMask, QWidget* parent, const char* name) + : QWidget(*new QLineEditPrivate, parent, 0) +{ + Q_D(QLineEdit); + setObjectName(QString::fromAscii(name)); + d->parseInputMask(inputMask); + if (d->maskData) { + QString ms = d->maskString(0, contents); + d->init(ms + d->clearString(ms.length(), d->maxLength - ms.length())); + d->cursor = d->nextMaskBlank(ms.length()); + } else { + d->init(contents); + } +} +#endif + +/*! + Destroys the line edit. +*/ + +QLineEdit::~QLineEdit() +{ +} + + +/*! + \property QLineEdit::text + \brief the line edit's text + + Setting this property clears the selection, clears the undo/redo + history, moves the cursor to the end of the line and resets the + \l modified property to false. The text is not validated when + inserted with setText(). + + The text is truncated to maxLength() length. + + By default, this property contains an empty string. + + \sa insert(), clear() +*/ +QString QLineEdit::text() const +{ + Q_D(const QLineEdit); + QString res = d->text; + if (d->maskData) + res = d->stripString(d->text); + return (res.isNull() ? QString::fromLatin1("") : res); +} + +void QLineEdit::setText(const QString& text) +{ + Q_D(QLineEdit); + d->setText(text, -1, false); +#ifdef QT_KEYPAD_NAVIGATION + d->origText = d->text; +#endif +} + + +/*! + \property QLineEdit::displayText + \brief the displayed text + + If \l echoMode is \l Normal this returns the same as text(); if + \l EchoMode is \l Password or \l PasswordEchoOnEdit it returns a string of asterisks + text().length() characters long, e.g. "******"; if \l EchoMode is + \l NoEcho returns an empty string, "". + + By default, this property contains an empty string. + + \sa setEchoMode() text() EchoMode +*/ + +QString QLineEdit::displayText() const +{ + Q_D(const QLineEdit); + if (d->echoMode == NoEcho) + return QString::fromLatin1(""); + QString res = d->text; + + if (d->echoMode == Password || (d->echoMode == PasswordEchoOnEdit + && !d->passwordEchoEditing)) { + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + res.fill(style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter, &opt, this)); + } + return (res.isNull() ? QString::fromLatin1("") : res); +} + + +/*! + \property QLineEdit::maxLength + \brief the maximum permitted length of the text + + If the text is too long, it is truncated at the limit. + + If truncation occurs any selected text will be unselected, the + cursor position is set to 0 and the first part of the string is + shown. + + If the line edit has an input mask, the mask defines the maximum + string length. + + By default, this property contains a value of 32767. + + \sa inputMask +*/ + +int QLineEdit::maxLength() const +{ + Q_D(const QLineEdit); + return d->maxLength; +} + +void QLineEdit::setMaxLength(int maxLength) +{ + Q_D(QLineEdit); + if (d->maskData) + return; + d->maxLength = maxLength; + setText(d->text); +} + + + +/*! + \property QLineEdit::frame + \brief whether the line edit draws itself with a frame + + If enabled (the default) the line edit draws itself inside a + frame, otherwise the line edit draws itself without any frame. +*/ +bool QLineEdit::hasFrame() const +{ + Q_D(const QLineEdit); + return d->frame; +} + + +void QLineEdit::setFrame(bool enable) +{ + Q_D(QLineEdit); + d->frame = enable; + update(); + updateGeometry(); +} + + +/*! + \enum QLineEdit::EchoMode + + This enum type describes how a line edit should display its + contents. + + \value Normal Display characters as they are entered. This is the + default. + \value NoEcho Do not display anything. This may be appropriate + for passwords where even the length of the + password should be kept secret. + \value Password Display asterisks instead of the characters + actually entered. + \value PasswordEchoOnEdit Display characters as they are entered + while editing otherwise display asterisks. + + \sa setEchoMode() echoMode() +*/ + + +/*! + \property QLineEdit::echoMode + \brief the line edit's echo mode + + The echo mode determines how the text entered in the line edit is + displayed (or echoed) to the user. + + The most common setting is \l Normal, in which the text entered by the + user is displayed verbatim, but QLineEdit also supports modes that allow + the entered text to be suppressed or obscured: these include \l NoEcho, + \l Password and \l PasswordEchoOnEdit. + + The widget's display and the ability to copy or drag the text is + affected by this setting. + + By default, this property is set to \l Normal. + + \sa EchoMode displayText() +*/ + +QLineEdit::EchoMode QLineEdit::echoMode() const +{ + Q_D(const QLineEdit); + return (EchoMode) d->echoMode; +} + +void QLineEdit::setEchoMode(EchoMode mode) +{ + Q_D(QLineEdit); + if (mode == (EchoMode)d->echoMode) + return; + setAttribute(Qt::WA_InputMethodEnabled, shouldEnableInputMethod(this)); + d->echoMode = mode; + d->passwordEchoEditing = false; + d->updateTextLayout(); + update(); +#ifdef Q_WS_MAC + if (hasFocus()) + qt_mac_secure_keyboard(d->echoMode == Password || d->echoMode == NoEcho); +#endif +} + + +#ifndef QT_NO_VALIDATOR +/*! + Returns a pointer to the current input validator, or 0 if no + validator has been set. + + \sa setValidator() +*/ + +const QValidator * QLineEdit::validator() const +{ + Q_D(const QLineEdit); + return d->validator; +} + +/*! + Sets this line edit to only accept input that the validator, \a v, + will accept. This allows you to place any arbitrary constraints on + the text which may be entered. + + If \a v == 0, setValidator() removes the current input validator. + The initial setting is to have no input validator (i.e. any input + is accepted up to maxLength()). + + \sa validator() QIntValidator QDoubleValidator QRegExpValidator +*/ + +void QLineEdit::setValidator(const QValidator *v) +{ + Q_D(QLineEdit); + d->validator = const_cast<QValidator*>(v); +} +#endif // QT_NO_VALIDATOR + +#ifndef QT_NO_COMPLETER +/*! + \since 4.2 + + Sets this line edit to provide auto completions from the completer, \a c. + The completion mode is set using QCompleter::setCompletionMode(). + + To use a QCompleter with a QValidator or QLineEdit::inputMask, you need to + ensure that the model provided to QCompleter contains valid entries. You can + use the QSortFilterProxyModel to ensure that the QCompleter's model contains + only valid entries. + + If \a c == 0, setCompleter() removes the current completer, effectively + disabling auto completion. + + \sa QCompleter +*/ +void QLineEdit::setCompleter(QCompleter *c) +{ + Q_D(QLineEdit); + if (c == d->completer) + return; + if (d->completer) { + disconnect(d->completer, 0, this, 0); + d->completer->setWidget(0); + if (d->completer->parent() == this) + delete d->completer; + } + d->completer = c; + if (!c) + return; + if (c->widget() == 0) + c->setWidget(this); + if (hasFocus()) { + QObject::connect(d->completer, SIGNAL(activated(QString)), + this, SLOT(setText(QString))); + QObject::connect(d->completer, SIGNAL(highlighted(QString)), + this, SLOT(_q_completionHighlighted(QString))); + } +} + +/*! + \since 4.2 + + Returns the current QCompleter that provides completions. +*/ +QCompleter *QLineEdit::completer() const +{ + Q_D(const QLineEdit); + return d->completer; +} + +// looks for an enabled item iterating forward(dir=1)/backward(dir=-1) from the +// current row based. dir=0 indicates a new completion prefix was set. +bool QLineEditPrivate::advanceToEnabledItem(int dir) +{ + int start = completer->currentRow(); + if (start == -1) + return false; + int i = start + dir; + if (dir == 0) dir = 1; + do { + if (!completer->setCurrentRow(i)) { + if (!completer->wrapAround()) + break; + i = i > 0 ? 0 : completer->completionCount() - 1; + } else { + QModelIndex currentIndex = completer->currentIndex(); + if (completer->completionModel()->flags(currentIndex) & Qt::ItemIsEnabled) + return true; + i += dir; + } + } while (i != start); + + completer->setCurrentRow(start); // restore + return false; +} + +void QLineEditPrivate::complete(int key) +{ + if (!completer || readOnly || echoMode != QLineEdit::Normal) + return; + + if (completer->completionMode() == QCompleter::InlineCompletion) { + if (key == Qt::Key_Backspace) + return; + int n = 0; + if (key == Qt::Key_Up || key == Qt::Key_Down) { + if (selend != 0 && selend != text.length()) + return; + QString prefix = hasSelectedText() ? text.left(selstart) : text; + if (text.compare(completer->currentCompletion(), completer->caseSensitivity()) != 0 + || prefix.compare(completer->completionPrefix(), completer->caseSensitivity()) != 0) { + completer->setCompletionPrefix(prefix); + } else { + n = (key == Qt::Key_Up) ? -1 : +1; + } + } else { + completer->setCompletionPrefix(text); + } + if (!advanceToEnabledItem(n)) + return; + } else { +#ifndef QT_KEYPAD_NAVIGATION + if (text.isEmpty()) { + completer->popup()->hide(); + return; + } +#endif + completer->setCompletionPrefix(text); + } + + completer->complete(); +} + +void QLineEditPrivate::_q_completionHighlighted(QString newText) +{ + Q_Q(QLineEdit); + if (completer->completionMode() != QCompleter::InlineCompletion) + q->setText(newText); + else { + int c = cursor; + q->setText(text.left(c) + newText.mid(c)); + q->setSelection(text.length(), c - newText.length()); + } +} +#endif // QT_NO_COMPLETER + +/*! + Returns a recommended size for the widget. + + The width returned, in pixels, is usually enough for about 15 to + 20 characters. +*/ + +QSize QLineEdit::sizeHint() const +{ + Q_D(const QLineEdit); + ensurePolished(); + QFontMetrics fm(font()); + int h = qMax(fm.lineSpacing(), 14) + 2*verticalMargin + + d->topTextMargin + d->bottomTextMargin + + d->topmargin + d->bottommargin; + int w = fm.width(QLatin1Char('x')) * 17 + 2*horizontalMargin + + d->leftTextMargin + d->rightTextMargin + + d->leftmargin + d->rightmargin; // "some" + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + return (style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(w, h). + expandedTo(QApplication::globalStrut()), this)); +} + + +/*! + Returns a minimum size for the line edit. + + The width returned is enough for at least one character. +*/ + +QSize QLineEdit::minimumSizeHint() const +{ + Q_D(const QLineEdit); + ensurePolished(); + QFontMetrics fm = fontMetrics(); + int h = fm.height() + qMax(2*verticalMargin, fm.leading()) + + d->topmargin + d->bottommargin; + int w = fm.maxWidth() + d->leftmargin + d->rightmargin; + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + return (style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(w, h). + expandedTo(QApplication::globalStrut()), this)); +} + + +/*! + \property QLineEdit::cursorPosition + \brief the current cursor position for this line edit + + Setting the cursor position causes a repaint when appropriate. + + By default, this property contains a value of 0. +*/ + +int QLineEdit::cursorPosition() const +{ + Q_D(const QLineEdit); + return d->cursor; +} + +void QLineEdit::setCursorPosition(int pos) +{ + Q_D(QLineEdit); + if (pos < 0) + pos = 0; + + if (pos <= d->text.length()) + d->moveCursor(pos); +} + +/*! + Returns the cursor position under the point \a pos. +*/ +// ### What should this do if the point is outside of contentsRect? Currently returns 0. +int QLineEdit::cursorPositionAt(const QPoint &pos) +{ + Q_D(QLineEdit); + return d->xToPos(pos.x()); +} + + +#ifdef QT3_SUPPORT +/*! \obsolete + + Use setText(), setCursorPosition() and setSelection() instead. +*/ +bool QLineEdit::validateAndSet(const QString &newText, int newPos, + int newMarkAnchor, int newMarkDrag) +{ + Q_D(QLineEdit); + int priorState = d->undoState; + d->selstart = 0; + d->selend = d->text.length(); + d->removeSelectedText(); + d->insert(newText); + d->finishChange(priorState); + if (d->undoState > priorState) { + d->cursor = newPos; + d->selstart = qMin(newMarkAnchor, newMarkDrag); + d->selend = qMax(newMarkAnchor, newMarkDrag); + update(); + d->emitCursorPositionChanged(); + return true; + } + return false; +} +#endif //QT3_SUPPORT + +/*! + \property QLineEdit::alignment + \brief the alignment of the line edit + + Both horizontal and vertical alignment is allowed here, Qt::AlignJustify + will map to Qt::AlignLeft. + + By default, this property contains a combination of Qt::AlignLeft and Qt::AlignVCenter. + + \sa Qt::Alignment +*/ + +Qt::Alignment QLineEdit::alignment() const +{ + Q_D(const QLineEdit); + return QFlag(d->alignment); +} + +void QLineEdit::setAlignment(Qt::Alignment alignment) +{ + Q_D(QLineEdit); + d->alignment = alignment; + update(); +} + + +/*! + Moves the cursor forward \a steps characters. If \a mark is true + each character moved over is added to the selection; if \a mark is + false the selection is cleared. + + \sa cursorBackward() +*/ + +void QLineEdit::cursorForward(bool mark, int steps) +{ + Q_D(QLineEdit); + int cursor = d->cursor; + if (steps > 0) { + while(steps--) + cursor = d->textLayout.nextCursorPosition(cursor); + } else if (steps < 0) { + while (steps++) + cursor = d->textLayout.previousCursorPosition(cursor); + } + d->moveCursor(cursor, mark); +} + + +/*! + Moves the cursor back \a steps characters. If \a mark is true each + character moved over is added to the selection; if \a mark is + false the selection is cleared. + + \sa cursorForward() +*/ +void QLineEdit::cursorBackward(bool mark, int steps) +{ + cursorForward(mark, -steps); +} + +/*! + Moves the cursor one word forward. If \a mark is true, the word is + also selected. + + \sa cursorWordBackward() +*/ +void QLineEdit::cursorWordForward(bool mark) +{ + Q_D(QLineEdit); + d->moveCursor(d->textLayout.nextCursorPosition(d->cursor, QTextLayout::SkipWords), mark); +} + +/*! + Moves the cursor one word backward. If \a mark is true, the word + is also selected. + + \sa cursorWordForward() +*/ + +void QLineEdit::cursorWordBackward(bool mark) +{ + Q_D(QLineEdit); + d->moveCursor(d->textLayout.previousCursorPosition(d->cursor, QTextLayout::SkipWords), mark); +} + + +/*! + If no text is selected, deletes the character to the left of the + text cursor and moves the cursor one position to the left. If any + text is selected, the cursor is moved to the beginning of the + selected text and the selected text is deleted. + + \sa del() +*/ +void QLineEdit::backspace() +{ + Q_D(QLineEdit); + int priorState = d->undoState; + if (d->hasSelectedText()) { + d->removeSelectedText(); + } else if (d->cursor) { + --d->cursor; + if (d->maskData) + d->cursor = d->prevMaskBlank(d->cursor); + QChar uc = d->text.at(d->cursor); + if (d->cursor > 0 && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) { + // second half of a surrogate, check if we have the first half as well, + // if yes delete both at once + uc = d->text.at(d->cursor - 1); + if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00) { + d->del(true); + --d->cursor; + } + } + d->del(true); + } + d->finishChange(priorState); +} + +/*! + If no text is selected, deletes the character to the right of the + text cursor. If any text is selected, the cursor is moved to the + beginning of the selected text and the selected text is deleted. + + \sa backspace() +*/ + +void QLineEdit::del() +{ + Q_D(QLineEdit); + int priorState = d->undoState; + if (d->hasSelectedText()) { + d->removeSelectedText(); + } else { + int n = d->textLayout.nextCursorPosition(d->cursor) - d->cursor; + while (n--) + d->del(); + } + d->finishChange(priorState); +} + +/*! + Moves the text cursor to the beginning of the line unless it is + already there. If \a mark is true, text is selected towards the + first position; otherwise, any selected text is unselected if the + cursor is moved. + + \sa end() +*/ + +void QLineEdit::home(bool mark) +{ + Q_D(QLineEdit); + d->moveCursor(0, mark); +} + +/*! + Moves the text cursor to the end of the line unless it is already + there. If \a mark is true, text is selected towards the last + position; otherwise, any selected text is unselected if the cursor + is moved. + + \sa home() +*/ + +void QLineEdit::end(bool mark) +{ + Q_D(QLineEdit); + d->moveCursor(d->text.length(), mark); +} + + +/*! + \property QLineEdit::modified + \brief whether the line edit's contents has been modified by the user + + The modified flag is never read by QLineEdit; it has a default value + of false and is changed to true whenever the user changes the line + edit's contents. + + This is useful for things that need to provide a default value but + do not start out knowing what the default should be (perhaps it + depends on other fields on the form). Start the line edit without + the best default, and when the default is known, if modified() + returns false (the user hasn't entered any text), insert the + default value. + + Calling setText() resets the modified flag to false. +*/ + +bool QLineEdit::isModified() const +{ + Q_D(const QLineEdit); + return d->modifiedState != d->undoState; +} + +void QLineEdit::setModified(bool modified) +{ + Q_D(QLineEdit); + if (modified) + d->modifiedState = -1; + else + d->modifiedState = d->undoState; +} + + +/*!\fn QLineEdit::clearModified() + +Use setModified(false) instead. + + \sa isModified() +*/ + + +/*! + \property QLineEdit::hasSelectedText + \brief whether there is any text selected + + hasSelectedText() returns true if some or all of the text has been + selected by the user; otherwise returns false. + + By default, this property is false. + + \sa selectedText() +*/ + + +bool QLineEdit::hasSelectedText() const +{ + Q_D(const QLineEdit); + return d->hasSelectedText(); +} + +/*! + \property QLineEdit::selectedText + \brief the selected text + + If there is no selected text this property's value is + an empty string. + + By default, this property contains an empty string. + + \sa hasSelectedText() +*/ + +QString QLineEdit::selectedText() const +{ + Q_D(const QLineEdit); + if (d->hasSelectedText()) + return d->text.mid(d->selstart, d->selend - d->selstart); + return QString(); +} + +/*! + selectionStart() returns the index of the first selected character in the + line edit or -1 if no text is selected. + + \sa selectedText() +*/ + +int QLineEdit::selectionStart() const +{ + Q_D(const QLineEdit); + return d->hasSelectedText() ? d->selstart : -1; +} + + +#ifdef QT3_SUPPORT + +/*! + \fn void QLineEdit::lostFocus() + + This signal is emitted when the line edit has lost focus. + + Use editingFinished() instead + \sa editingFinished(), returnPressed() +*/ + +/*! + Use isModified() instead. +*/ +bool QLineEdit::edited() const { return isModified(); } +/*! + Use setModified() or setText(). +*/ +void QLineEdit::setEdited(bool on) { setModified(on); } + +/*! + There exists no equivalent functionality in Qt 4. +*/ +int QLineEdit::characterAt(int xpos, QChar *chr) const +{ + Q_D(const QLineEdit); + int pos = d->xToPos(xpos + contentsRect().x() - d->hscroll + horizontalMargin); + if (chr && pos < (int) d->text.length()) + *chr = d->text.at(pos); + return pos; + +} + +/*! + Use selectedText() and selectionStart() instead. +*/ +bool QLineEdit::getSelection(int *start, int *end) +{ + Q_D(QLineEdit); + if (d->hasSelectedText() && start && end) { + *start = d->selstart; + *end = d->selend; + return true; + } + return false; +} +#endif + + +/*! + Selects text from position \a start and for \a length characters. + Negative lengths are allowed. + + \sa deselect() selectAll() selectedText() +*/ + +void QLineEdit::setSelection(int start, int length) +{ + Q_D(QLineEdit); + if (start < 0 || start > (int)d->text.length()) { + qWarning("QLineEdit::setSelection: Invalid start position (%d)", start); + return; + } else { + if (length > 0) { + d->selstart = start; + d->selend = qMin(start + length, (int)d->text.length()); + d->cursor = d->selend; + } else { + d->selstart = qMax(start + length, 0); + d->selend = start; + d->cursor = d->selstart; + } + } + + if (d->hasSelectedText()){ + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + if (!style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, &opt, this)) + d->setCursorVisible(false); + } + + update(); + d->emitCursorPositionChanged(); +} + + +/*! + \property QLineEdit::undoAvailable + \brief whether undo is available + + Undo becomes available once the user has modified the text in the line edit. + + By default, this property is false. +*/ + +bool QLineEdit::isUndoAvailable() const +{ + Q_D(const QLineEdit); + return d->isUndoAvailable(); +} + +/*! + \property QLineEdit::redoAvailable + \brief whether redo is available + + Redo becomes available once the user has performed one or more undo operations + on text in the line edit. + + By default, this property is false. +*/ + +bool QLineEdit::isRedoAvailable() const +{ + Q_D(const QLineEdit); + return d->isRedoAvailable(); +} + +/*! + \property QLineEdit::dragEnabled + \brief whether the lineedit starts a drag if the user presses and + moves the mouse on some selected text + + Dragging is disabled by default. +*/ + +bool QLineEdit::dragEnabled() const +{ + Q_D(const QLineEdit); + return d->dragEnabled; +} + +void QLineEdit::setDragEnabled(bool b) +{ + Q_D(QLineEdit); + d->dragEnabled = b; +} + + +/*! + \property QLineEdit::acceptableInput + \brief whether the input satisfies the inputMask and the + validator. + + By default, this property is true. + + \sa setInputMask(), setValidator() +*/ +bool QLineEdit::hasAcceptableInput() const +{ + Q_D(const QLineEdit); + return d->hasAcceptableInput(d->text); +} + +/*! + Sets the margins around the text inside the frame to have the + sizes \a left, \a top, \a right, and \a bottom. + \since 4.5 + + See also getTextMargins(). +*/ +void QLineEdit::setTextMargins(int left, int top, int right, int bottom) +{ + Q_D(QLineEdit); + d->leftTextMargin = left; + d->topTextMargin = top; + d->rightTextMargin = right; + d->bottomTextMargin = bottom; + updateGeometry(); + update(); +} + +/*! + Returns the widget's text margins for \a left, \a top, \a right, and \a bottom. + \since 4.5 + + \sa setTextMargins() +*/ +void QLineEdit::getTextMargins(int *left, int *top, int *right, int *bottom) const +{ + Q_D(const QLineEdit); + if (left) + *left = d->leftTextMargin; + if (top) + *top = d->topTextMargin; + if (right) + *right = d->rightTextMargin; + if (bottom) + *bottom = d->bottomTextMargin; +} + +/*! + \property QLineEdit::inputMask + \brief The validation input mask + + If no mask is set, inputMask() returns an empty string. + + Sets the QLineEdit's validation mask. Validators can be used + instead of, or in conjunction with masks; see setValidator(). + + Unset the mask and return to normal QLineEdit operation by passing + an empty string ("") or just calling setInputMask() with no + arguments. + + The table below shows the characters that can be used in an input mask. + A space character, the default character for a blank, is needed for cases + where a character is \e{permitted but not required}. + + \table + \header \i Character \i Meaning + \row \i \c A \i ASCII alphabetic character required. A-Z, a-z. + \row \i \c a \i ASCII alphabetic character permitted but not required. + \row \i \c N \i ASCII alphanumeric character required. A-Z, a-z, 0-9. + \row \i \c n \i ASCII alphanumeric character permitted but not required. + \row \i \c X \i Any character required. + \row \i \c x \i Any character permitted but not required. + \row \i \c 9 \i ASCII digit required. 0-9. + \row \i \c 0 \i ASCII digit permitted but not required. + \row \i \c D \i ASCII digit required. 1-9. + \row \i \c d \i ASCII digit permitted but not required (1-9). + \row \i \c # \i ASCII digit or plus/minus sign permitted but not required. + \row \i \c H \i Hexadecimal character required. A-F, a-f, 0-9. + \row \i \c h \i Hexadecimal character permitted but not required. + \row \i \c B \i Binary character required. 0-1. + \row \i \c b \i Binary character permitted but not required. + \row \i \c > \i All following alphabetic characters are uppercased. + \row \i \c < \i All following alphabetic characters are lowercased. + \row \i \c ! \i Switch off case conversion. + \row \i \tt{\\} \i Use \tt{\\} to escape the special + characters listed above to use them as + separators. + \endtable + + The mask consists of a string of mask characters and separators, + optionally followed by a semicolon and the character used for + blanks. The blank characters are always removed from the text + after editing. + + Examples: + \table + \header \i Mask \i Notes + \row \i \c 000.000.000.000;_ \i IP address; blanks are \c{_}. + \row \i \c HH:HH:HH:HH:HH:HH;_ \i MAC address + \row \i \c 0000-00-00 \i ISO Date; blanks are \c space + \row \i \c >AAAAA-AAAAA-AAAAA-AAAAA-AAAAA;# \i License number; + blanks are \c - and all (alphabetic) characters are converted to + uppercase. + \endtable + + To get range control (e.g., for an IP address) use masks together + with \link setValidator() validators\endlink. + + \sa maxLength +*/ +QString QLineEdit::inputMask() const +{ + Q_D(const QLineEdit); + return (d->maskData ? d->inputMask + QLatin1Char(';') + d->blank : QString()); +} + +void QLineEdit::setInputMask(const QString &inputMask) +{ + Q_D(QLineEdit); + d->parseInputMask(inputMask); + if (d->maskData) + d->moveCursor(d->nextMaskBlank(0)); +} + +/*! + Selects all the text (i.e. highlights it) and moves the cursor to + the end. This is useful when a default value has been inserted + because if the user types before clicking on the widget, the + selected text will be deleted. + + \sa setSelection() deselect() +*/ + +void QLineEdit::selectAll() +{ + Q_D(QLineEdit); + d->selstart = d->selend = d->cursor = 0; + d->moveCursor(d->text.length(), true); +} + +/*! + Deselects any selected text. + + \sa setSelection() selectAll() +*/ + +void QLineEdit::deselect() +{ + Q_D(QLineEdit); + d->deselect(); + d->finishChange(); +} + + +/*! + Deletes any selected text, inserts \a newText, and validates the + result. If it is valid, it sets it as the new contents of the line + edit. + + \sa setText(), clear() +*/ +void QLineEdit::insert(const QString &newText) +{ +// q->resetInputContext(); //#### FIX ME IN QT + Q_D(QLineEdit); + int priorState = d->undoState; + d->removeSelectedText(); + d->insert(newText); + d->finishChange(priorState); +} + +/*! + Clears the contents of the line edit. + + \sa setText(), insert() +*/ +void QLineEdit::clear() +{ + Q_D(QLineEdit); + int priorState = d->undoState; + resetInputContext(); + d->selstart = 0; + d->selend = d->text.length(); + d->removeSelectedText(); + d->separate(); + d->finishChange(priorState, /*update*/false, /*edited*/false); +} + +/*! + Undoes the last operation if undo is \link + QLineEdit::undoAvailable available\endlink. Deselects any current + selection, and updates the selection start to the current cursor + position. +*/ +void QLineEdit::undo() +{ + Q_D(QLineEdit); + resetInputContext(); + d->undo(); + d->finishChange(-1, true); +} + +/*! + Redoes the last operation if redo is \link + QLineEdit::redoAvailable available\endlink. +*/ +void QLineEdit::redo() +{ + Q_D(QLineEdit); + resetInputContext(); + d->redo(); + d->finishChange(); +} + + +/*! + \property QLineEdit::readOnly + \brief whether the line edit is read only. + + In read-only mode, the user can still copy the text to the + clipboard, or drag and drop the text (if echoMode() is \l Normal), + but cannot edit it. + + QLineEdit does not show a cursor in read-only mode. + + By default, this property is false. + + \sa setEnabled() +*/ + +bool QLineEdit::isReadOnly() const +{ + Q_D(const QLineEdit); + return d->readOnly; +} + +void QLineEdit::setReadOnly(bool enable) +{ + Q_D(QLineEdit); + if (d->readOnly != enable) { + d->readOnly = enable; + setAttribute(Qt::WA_MacShowFocusRect, !d->readOnly); + setAttribute(Qt::WA_InputMethodEnabled, shouldEnableInputMethod(this)); +#ifndef QT_NO_CURSOR + setCursor(enable ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + update(); + } +} + + +#ifndef QT_NO_CLIPBOARD +/*! + Copies the selected text to the clipboard and deletes it, if there + is any, and if echoMode() is \l Normal. + + If the current validator disallows deleting the selected text, + cut() will copy without deleting. + + \sa copy() paste() setValidator() +*/ + +void QLineEdit::cut() +{ + if (hasSelectedText()) { + copy(); + del(); + } +} + + +/*! + Copies the selected text to the clipboard, if there is any, and if + echoMode() is \l Normal. + + \sa cut() paste() +*/ + +void QLineEdit::copy() const +{ + Q_D(const QLineEdit); + d->copy(); +} + +/*! + Inserts the clipboard's text at the cursor position, deleting any + selected text, providing the line edit is not \link + QLineEdit::readOnly read-only\endlink. + + If the end result would not be acceptable to the current + \link setValidator() validator\endlink, nothing happens. + + \sa copy() cut() +*/ + +void QLineEdit::paste() +{ + Q_D(QLineEdit); + if (echoMode() == PasswordEchoOnEdit && !d->passwordEchoEditing) { + // Clear the edit and reset to normal echo mode when pasting; the echo + // mode switches back when the edit loses focus. ### changes a public + // property, resets current content + d->updatePasswordEchoEditing(true); + clear(); + } + insert(QApplication::clipboard()->text(QClipboard::Clipboard)); +} + +void QLineEditPrivate::copy(bool clipboard) const +{ + Q_Q(const QLineEdit); + QString t = q->selectedText(); + if (!t.isEmpty() && echoMode == QLineEdit::Normal) { + q->disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), q, 0); + QApplication::clipboard()->setText(t, clipboard ? QClipboard::Clipboard : QClipboard::Selection); + q->connect(QApplication::clipboard(), SIGNAL(selectionChanged()), + q, SLOT(_q_clipboardChanged())); + } +} + +#endif // !QT_NO_CLIPBOARD + +/*! \reimp +*/ +bool QLineEdit::event(QEvent * e) +{ + Q_D(QLineEdit); +#ifndef QT_NO_SHORTCUT + if (e->type() == QEvent::ShortcutOverride && !d->readOnly) { + QKeyEvent* ke = (QKeyEvent*) e; + if (ke == QKeySequence::Copy + || ke == QKeySequence::Paste + || ke == QKeySequence::Cut + || ke == QKeySequence::Redo + || ke == QKeySequence::Undo + || ke == QKeySequence::MoveToNextWord + || ke == QKeySequence::MoveToPreviousWord + || ke == QKeySequence::MoveToStartOfDocument + || ke == QKeySequence::MoveToEndOfDocument + || ke == QKeySequence::SelectNextWord + || ke == QKeySequence::SelectPreviousWord + || ke == QKeySequence::SelectStartOfLine + || ke == QKeySequence::SelectEndOfLine + || ke == QKeySequence::SelectStartOfBlock + || ke == QKeySequence::SelectEndOfBlock + || ke == QKeySequence::SelectStartOfDocument + || ke == QKeySequence::SelectAll + || ke == QKeySequence::SelectEndOfDocument) { + ke->accept(); + } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier + || ke->modifiers() == Qt::KeypadModifier) { + if (ke->key() < Qt::Key_Escape) { + ke->accept(); + } else { + switch (ke->key()) { + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + ke->accept(); + default: + break; + } + } + } + } else +#endif + if (e->type() == QEvent::Timer) { + // should be timerEvent, is here for binary compatibility + int timerId = ((QTimerEvent*)e)->timerId(); + if (timerId == d->cursorTimer) { + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + if(!hasSelectedText() + || style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, &opt, this)) + d->setCursorVisible(!d->cursorVisible); +#ifndef QT_NO_DRAGANDDROP + } else if (timerId == d->dndTimer.timerId()) { + d->drag(); +#endif + } + else if (timerId == d->tripleClickTimer.timerId()) + d->tripleClickTimer.stop(); +#ifdef QT_KEYPAD_NAVIGATION + else if (timerId == d->deleteAllTimer.timerId()) { + d->deleteAllTimer.stop(); + clear(); + } +#endif + } else if (e->type() == QEvent::ContextMenu) { +#ifndef QT_NO_IM + if (d->composeMode()) + return true; +#endif + d->separate(); + } else if (e->type() == QEvent::WindowActivate) { + QTimer::singleShot(0, this, SLOT(_q_handleWindowActivate())); + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + if ((e->type() == QEvent::KeyPress) || (e->type() == QEvent::KeyRelease)) { + QKeyEvent *ke = (QKeyEvent *)e; + if (ke->key() == Qt::Key_Back) { + if (ke->isAutoRepeat()) { + // Swallow it. We don't want back keys running amok. + ke->accept(); + return true; + } + if ((e->type() == QEvent::KeyRelease) + && !isReadOnly() + && d->deleteAllTimer.isActive()) { + d->deleteAllTimer.stop(); + backspace(); + ke->accept(); + return true; + } + } + } else if (e->type() == QEvent::EnterEditFocus) { + end(false); + if (!d->cursorTimer) { + int cft = QApplication::cursorFlashTime(); + d->cursorTimer = cft ? startTimer(cft/2) : -1; + } + } else if (e->type() == QEvent::LeaveEditFocus) { + d->setCursorVisible(false); + if (d->cursorTimer > 0) + killTimer(d->cursorTimer); + d->cursorTimer = 0; + + if (!d->emitingEditingFinished) { + if (hasAcceptableInput() || d->fixup()) { + d->emitingEditingFinished = true; + emit editingFinished(); + d->emitingEditingFinished = false; + } + } + } + } +#endif + return QWidget::event(e); +} + +/*! \reimp +*/ +void QLineEdit::mousePressEvent(QMouseEvent* e) +{ + Q_D(QLineEdit); + if (d->sendMouseEventToInputContext(e)) + return; + if (e->button() == Qt::RightButton) + return; +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + setEditFocus(true); + // Get the completion list to pop up. + if (d->completer) + d->completer->complete(); + } +#endif + if (d->tripleClickTimer.isActive() && (e->pos() - d->tripleClick).manhattanLength() < + QApplication::startDragDistance()) { + selectAll(); + return; + } + bool mark = e->modifiers() & Qt::ShiftModifier; + int cursor = d->xToPos(e->pos().x()); +#ifndef QT_NO_DRAGANDDROP + if (!mark && d->dragEnabled && d->echoMode == Normal && + e->button() == Qt::LeftButton && d->inSelection(e->pos().x())) { + d->cursor = cursor; + update(); + d->dndPos = e->pos(); + if (!d->dndTimer.isActive()) + d->dndTimer.start(QApplication::startDragTime(), this); + d->emitCursorPositionChanged(); + } else +#endif + { + d->moveCursor(cursor, mark); + } +} + +/*! \reimp +*/ +void QLineEdit::mouseMoveEvent(QMouseEvent * e) +{ + Q_D(QLineEdit); + if (d->sendMouseEventToInputContext(e)) + return; + + if (e->buttons() & Qt::LeftButton) { +#ifndef QT_NO_DRAGANDDROP + if (d->dndTimer.isActive()) { + if ((d->dndPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) + d->drag(); + } else +#endif + { + d->moveCursor(d->xToPos(e->pos().x()), true); + } + } +} + +/*! \reimp +*/ +void QLineEdit::mouseReleaseEvent(QMouseEvent* e) +{ + Q_D(QLineEdit); + if (d->sendMouseEventToInputContext(e)) + return; +#ifndef QT_NO_DRAGANDDROP + if (e->button() == Qt::LeftButton) { + if (d->dndTimer.isActive()) { + d->dndTimer.stop(); + deselect(); + return; + } + } +#endif +#ifndef QT_NO_CLIPBOARD + if (QApplication::clipboard()->supportsSelection()) { + if (e->button() == Qt::LeftButton) { + d->copy(false); + } else if (!d->readOnly && e->button() == Qt::MidButton) { + d->deselect(); + insert(QApplication::clipboard()->text(QClipboard::Selection)); + } + } +#endif +} + +/*! \reimp +*/ +void QLineEdit::mouseDoubleClickEvent(QMouseEvent* e) +{ + Q_D(QLineEdit); + if (d->sendMouseEventToInputContext(e)) + return; + if (e->button() == Qt::LeftButton) { + deselect(); + d->cursor = d->xToPos(e->pos().x()); + d->cursor = d->textLayout.previousCursorPosition(d->cursor, QTextLayout::SkipWords); + // ## text layout should support end of words. + int end = d->textLayout.nextCursorPosition(d->cursor, QTextLayout::SkipWords); + while (end > d->cursor && d->text[end-1].isSpace()) + --end; + d->moveCursor(end, true); + d->tripleClickTimer.start(QApplication::doubleClickInterval(), this); + d->tripleClick = e->pos(); + } +} + +/*! + \fn void QLineEdit::returnPressed() + + This signal is emitted when the Return or Enter key is pressed. + Note that if there is a validator() or inputMask() set on the line + edit, the returnPressed() signal will only be emitted if the input + follows the inputMask() and the validator() returns + QValidator::Acceptable. +*/ + +/*! + \fn void QLineEdit::editingFinished() + + This signal is emitted when the Return or Enter key is pressed or + the line edit loses focus. Note that if there is a validator() or + inputMask() set on the line edit and enter/return is pressed, the + editingFinished() signal will only be emitted if the input follows + the inputMask() and the validator() returns QValidator::Acceptable. +*/ + +/*! + Converts the given key press \a event into a line edit action. + + If Return or Enter is pressed and the current text is valid (or + can be \link QValidator::fixup() made valid\endlink by the + validator), the signal returnPressed() is emitted. + + The default key bindings are listed in the class's detailed + description. +*/ + +void QLineEdit::keyPressEvent(QKeyEvent *event) +{ + Q_D(QLineEdit); + + bool inlineCompletionAccepted = false; + +#ifndef QT_NO_COMPLETER + if (d->completer) { + QCompleter::CompletionMode completionMode = d->completer->completionMode(); + if ((completionMode == QCompleter::PopupCompletion + || completionMode == QCompleter::UnfilteredPopupCompletion) + &&d->completer->popup() + && d->completer->popup()->isVisible()) { + // The following keys are forwarded by the completer to the widget + // Ignoring the events lets the completer provide suitable default behavior + switch (event->key()) { + case Qt::Key_Escape: + event->ignore(); + return; + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_F4: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (!QApplication::keypadNavigationEnabled()) + break; +#endif + d->completer->popup()->hide(); // just hide. will end up propagating to parent + default: + break; // normal key processing + } + } else if (completionMode == QCompleter::InlineCompletion) { + switch (event->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_F4: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (!QApplication::keypadNavigationEnabled()) + break; +#endif + if (!d->completer->currentCompletion().isEmpty() && d->selend > d->selstart + && d->selend == d->text.length()) { + setText(d->completer->currentCompletion()); + inlineCompletionAccepted = true; + } + default: + break; // normal key processing + } + } + } +#endif // QT_NO_COMPLETER + +#ifdef QT_KEYPAD_NAVIGATION + bool select = false; + switch (event->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (hasEditFocus()) { + setEditFocus(false); + if (d->completer && d->completer->popup()->isVisible()) + d->completer->popup()->hide(); + select = true; + } + } + break; + case Qt::Key_Back: + case Qt::Key_No: + if (!QApplication::keypadNavigationEnabled() || !hasEditFocus()) { + event->ignore(); + return; + } + break; + default: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus() && !(event->modifiers() & Qt::ControlModifier)) { + if (!event->text().isEmpty() && event->text().at(0).isPrint() + && !isReadOnly()) + { + setEditFocus(true); + clear(); + } else { + event->ignore(); + return; + } + } + } + } + + + + if (QApplication::keypadNavigationEnabled() && !select && !hasEditFocus()) { + setEditFocus(true); + if (event->key() == Qt::Key_Select) + return; // Just start. No action. + } +#endif + + if (echoMode() == PasswordEchoOnEdit + && !d->passwordEchoEditing + && !isReadOnly() + && !event->text().isEmpty() +#ifdef QT_KEYPAD_NAVIGATION + && event->key() != Qt::Key_Select + && event->key() != Qt::Key_Up + && event->key() != Qt::Key_Down + && event->key() != Qt::Key_Back +#endif + && !(event->modifiers() & Qt::ControlModifier)) { + // Clear the edit and reset to normal echo mode while editing; the + // echo mode switches back when the edit loses focus. ### changes a + // public property, resets current content. dubious code; you can + // navigate with keys up, down, back, and select(?), but if you press + // "left" or "right" it clears? + d->updatePasswordEchoEditing(true); + clear(); + } + + d->setCursorVisible(true); + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { + if (hasAcceptableInput() || d->fixup()) { + emit returnPressed(); + d->emitingEditingFinished = true; + emit editingFinished(); + d->emitingEditingFinished = false; + } + if (inlineCompletionAccepted) + event->accept(); + else + event->ignore(); + return; + } + bool unknown = false; + + if (false) { + } +#ifndef QT_NO_SHORTCUT + else if (event == QKeySequence::Undo) { + if (!d->readOnly) + undo(); + } + else if (event == QKeySequence::Redo) { + if (!d->readOnly) + redo(); + } + else if (event == QKeySequence::SelectAll) { + selectAll(); + } +#ifndef QT_NO_CLIPBOARD + else if (event == QKeySequence::Copy) { + copy(); + } + else if (event == QKeySequence::Paste) { + if (!d->readOnly) + paste(); + } + else if (event == QKeySequence::Cut) { + if (!d->readOnly) { + cut(); + } + } + else if (event == QKeySequence::DeleteEndOfLine) { + if (!d->readOnly) { + setSelection(d->cursor, d->text.size()); + copy(); + del(); + } + } +#endif //QT_NO_CLIPBOARD + else if (event == QKeySequence::MoveToStartOfLine) { + home(0); + } + else if (event == QKeySequence::MoveToEndOfLine) { + end(0); + } + else if (event == QKeySequence::SelectStartOfLine) { + home(1); + } + else if (event == QKeySequence::SelectEndOfLine) { + end(1); + } + else if (event == QKeySequence::MoveToNextChar) { +#if !defined(Q_WS_WIN) || defined(QT_NO_COMPLETER) + if (d->hasSelectedText()) { +#else + if (d->hasSelectedText() && d->completer + && d->completer->completionMode() == QCompleter::InlineCompletion) { +#endif + d->moveCursor(d->selend, false); + } else { + cursorForward(0, layoutDirection() == Qt::LeftToRight ? 1 : -1); + } + } + else if (event == QKeySequence::SelectNextChar) { + cursorForward(1, layoutDirection() == Qt::LeftToRight ? 1 : -1); + } + else if (event == QKeySequence::MoveToPreviousChar) { +#if !defined(Q_WS_WIN) || defined(QT_NO_COMPLETER) + if (d->hasSelectedText()) { +#else + if (d->hasSelectedText() && d->completer + && d->completer->completionMode() == QCompleter::InlineCompletion) { +#endif + d->moveCursor(d->selstart, false); + } else { + cursorBackward(0, layoutDirection() == Qt::LeftToRight ? 1 : -1); + } + } + else if (event == QKeySequence::SelectPreviousChar) { + cursorBackward(1, layoutDirection() == Qt::LeftToRight ? 1 : -1); + } + else if (event == QKeySequence::MoveToNextWord) { + if (echoMode() == Normal) + layoutDirection() == Qt::LeftToRight ? cursorWordForward(0) : cursorWordBackward(0); + else + layoutDirection() == Qt::LeftToRight ? end(0) : home(0); + } + else if (event == QKeySequence::MoveToPreviousWord) { + if (echoMode() == Normal) + layoutDirection() == Qt::LeftToRight ? cursorWordBackward(0) : cursorWordForward(0); + else if (!d->readOnly) { + layoutDirection() == Qt::LeftToRight ? home(0) : end(0); + } + } + else if (event == QKeySequence::SelectNextWord) { + if (echoMode() == Normal) + layoutDirection() == Qt::LeftToRight ? cursorWordForward(1) : cursorWordBackward(1); + else + layoutDirection() == Qt::LeftToRight ? end(1) : home(1); + } + else if (event == QKeySequence::SelectPreviousWord) { + if (echoMode() == Normal) + layoutDirection() == Qt::LeftToRight ? cursorWordBackward(1) : cursorWordForward(1); + else + layoutDirection() == Qt::LeftToRight ? home(1) : end(1); + } + else if (event == QKeySequence::Delete) { + if (!d->readOnly) + del(); + } + else if (event == QKeySequence::DeleteEndOfWord) { + if (!d->readOnly) { + cursorWordForward(true); + del(); + } + } + else if (event == QKeySequence::DeleteStartOfWord) { + if (!d->readOnly) { + cursorWordBackward(true); + del(); + } + } +#endif // QT_NO_SHORTCUT + else { +#ifdef Q_WS_MAC + if (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down) { + Qt::KeyboardModifiers myModifiers = (event->modifiers() & ~Qt::KeypadModifier); + if (myModifiers & Qt::ShiftModifier) { + if (myModifiers == (Qt::ControlModifier|Qt::ShiftModifier) + || myModifiers == (Qt::AltModifier|Qt::ShiftModifier) + || myModifiers == Qt::ShiftModifier) { + + event->key() == Qt::Key_Up ? home(1) : end(1); + } + } else { + if ((myModifiers == Qt::ControlModifier + || myModifiers == Qt::AltModifier + || myModifiers == Qt::NoModifier)) { + event->key() == Qt::Key_Up ? home(0) : end(0); + } + } + } +#endif + if (event->modifiers() & Qt::ControlModifier) { + switch (event->key()) { + case Qt::Key_Backspace: + if (!d->readOnly) { + cursorWordBackward(true); + del(); + } + break; +#ifndef QT_NO_COMPLETER + case Qt::Key_Up: + case Qt::Key_Down: + d->complete(event->key()); + break; +#endif +#if defined(Q_WS_X11) + case Qt::Key_E: + end(0); + break; + + case Qt::Key_U: + if (!d->readOnly) { + setSelection(0, d->text.size()); +#ifndef QT_NO_CLIPBOARD + copy(); +#endif + del(); + } + break; +#endif + default: + unknown = true; + } + } else { // ### check for *no* modifier + switch (event->key()) { + case Qt::Key_Backspace: + if (!d->readOnly) { + backspace(); +#ifndef QT_NO_COMPLETER + d->complete(Qt::Key_Backspace); +#endif + } + break; +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled() && !event->isAutoRepeat() + && !isReadOnly()) { + if (text().length() == 0) { + setText(d->origText); + + if (d->passwordEchoEditing) + d->updatePasswordEchoEditing(false); + + setEditFocus(false); + } else if (!d->deleteAllTimer.isActive()) { + d->deleteAllTimer.start(750, this); + } + } else { + unknown = true; + } + break; +#endif + + default: + unknown = true; + } + } + } + + if (event->key() == Qt::Key_Direction_L || event->key() == Qt::Key_Direction_R) { + setLayoutDirection((event->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft); + d->updateTextLayout(); + update(); + unknown = false; + } + + if (unknown && !d->readOnly) { + QString t = event->text(); + if (!t.isEmpty() && t.at(0).isPrint()) { + insert(t); +#ifndef QT_NO_COMPLETER + d->complete(event->key()); +#endif + event->accept(); + return; + } + } + + if (unknown) + event->ignore(); + else + event->accept(); +} + +/*! + \since 4.4 + + Returns a rectangle that includes the lineedit cursor. +*/ +QRect QLineEdit::cursorRect() const +{ + Q_D(const QLineEdit); + return d->cursorRect(); +} + +/*! + This function is not intended as polymorphic usage. Just a shared code + fragment that calls QInputContext::mouseHandler for this + class. +*/ +bool QLineEditPrivate::sendMouseEventToInputContext( QMouseEvent *e ) +{ +#if !defined QT_NO_IM + Q_Q(QLineEdit); + if ( composeMode() ) { + int tmp_cursor = xToPos(e->pos().x()); + int mousePos = tmp_cursor - cursor; + if ( mousePos < 0 || mousePos > textLayout.preeditAreaText().length() ) { + mousePos = -1; + // don't send move events outside the preedit area + if ( e->type() == QEvent::MouseMove ) + return true; + } + + QInputContext *qic = q->inputContext(); + if ( qic ) + // may be causing reset() in some input methods + qic->mouseHandler(mousePos, e); + if (!textLayout.preeditAreaText().isEmpty()) + return true; + } +#else + Q_UNUSED(e); +#endif + + return false; +} + +/*! \reimp + */ +void QLineEdit::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QLineEdit); + if (d->readOnly) { + e->ignore(); + return; + } + + if (echoMode() == PasswordEchoOnEdit && !d->passwordEchoEditing) { + // Clear the edit and reset to normal echo mode while entering input + // method data; the echo mode switches back when the edit loses focus. + // ### changes a public property, resets current content. + d->updatePasswordEchoEditing(true); + clear(); + } + +#ifdef QT_KEYPAD_NAVIGATION + // Focus in if currently in navigation focus on the widget + // Only focus in on preedits, to allow input methods to + // commit text as they focus out without interfering with focus + if (QApplication::keypadNavigationEnabled() + && hasFocus() && !hasEditFocus() + && !e->preeditString().isEmpty()) { + setEditFocus(true); + selectAll(); // so text is replaced rather than appended to + } +#endif + + int priorState = d->undoState; + d->removeSelectedText(); + + int c = d->cursor; // cursor position after insertion of commit string + if (e->replacementStart() <= 0) + c += e->commitString().length() + qMin(-e->replacementStart(), e->replacementLength()); + + d->cursor += e->replacementStart(); + + // insert commit string + if (e->replacementLength()) { + d->selstart = d->cursor; + d->selend = d->selstart + e->replacementLength(); + d->removeSelectedText(); + } + if (!e->commitString().isEmpty()) + d->insert(e->commitString()); + + d->cursor = qMin(c, d->text.length()); + + d->textLayout.setPreeditArea(d->cursor, e->preeditString()); + d->preeditCursor = e->preeditString().length(); + d->hideCursor = false; + QList<QTextLayout::FormatRange> formats; + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Cursor) { + d->preeditCursor = a.start; + d->hideCursor = !a.length; + } else if (a.type == QInputMethodEvent::TextFormat) { + QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat(); + if (f.isValid()) { + QTextLayout::FormatRange o; + o.start = a.start + d->cursor; + o.length = a.length; + o.format = f; + formats.append(o); + } + } + } + d->textLayout.setAdditionalFormats(formats); + d->updateTextLayout(); + update(); + if (!e->commitString().isEmpty()) + d->emitCursorPositionChanged(); + d->finishChange(priorState); +#ifndef QT_NO_COMPLETER + if (!e->commitString().isEmpty()) + d->complete(Qt::Key_unknown); +#endif +} + +/*!\reimp +*/ +QVariant QLineEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QLineEdit); + switch(property) { + case Qt::ImMicroFocus: + return d->cursorRect(); + case Qt::ImFont: + return font(); + case Qt::ImCursorPosition: + return QVariant((d->selend - d->selstart == 0) ? d->cursor : d->selend); + case Qt::ImSurroundingText: + return QVariant(d->text); + case Qt::ImCurrentSelection: + return QVariant(selectedText()); + default: + return QVariant(); + } +} + +/*!\reimp +*/ + +void QLineEdit::focusInEvent(QFocusEvent *e) +{ + Q_D(QLineEdit); + if (e->reason() == Qt::TabFocusReason || + e->reason() == Qt::BacktabFocusReason || + e->reason() == Qt::ShortcutFocusReason) { + if (d->maskData) + d->moveCursor(d->nextMaskBlank(0)); + else if (!d->hasSelectedText()) + selectAll(); + } +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || (hasEditFocus() && e->reason() == Qt::PopupFocusReason)) +#endif + if (!d->cursorTimer) { + int cft = QApplication::cursorFlashTime(); + d->cursorTimer = cft ? startTimer(cft/2) : -1; + } + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + if((!hasSelectedText() && d->textLayout.preeditAreaText().isEmpty()) + || style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, &opt, this)) + d->setCursorVisible(true); +#ifdef Q_WS_MAC + if (d->echoMode == Password || d->echoMode == NoEcho) + qt_mac_secure_keyboard(true); +#endif +#ifdef QT_KEYPAD_NAVIGATION + d->origText = d->text; +#endif +#ifndef QT_NO_COMPLETER + if (d->completer) { + d->completer->setWidget(this); + QObject::connect(d->completer, SIGNAL(activated(QString)), + this, SLOT(setText(QString))); + QObject::connect(d->completer, SIGNAL(highlighted(QString)), + this, SLOT(_q_completionHighlighted(QString))); + } +#endif + update(); +} + +/*!\reimp +*/ + +void QLineEdit::focusOutEvent(QFocusEvent *e) +{ + Q_D(QLineEdit); + if (d->passwordEchoEditing) { + // Reset the echomode back to PasswordEchoOnEdit when the widget loses + // focus. + d->updatePasswordEchoEditing(false); + } + + Qt::FocusReason reason = e->reason(); + if (reason != Qt::ActiveWindowFocusReason && + reason != Qt::PopupFocusReason) + deselect(); + + d->setCursorVisible(false); + if (d->cursorTimer > 0) + killTimer(d->cursorTimer); + d->cursorTimer = 0; + +#ifdef QT_KEYPAD_NAVIGATION + // editingFinished() is already emitted on LeaveEditFocus + if (!QApplication::keypadNavigationEnabled()) +#endif + if (reason != Qt::PopupFocusReason + || !(QApplication::activePopupWidget() && QApplication::activePopupWidget()->parentWidget() == this)) { + if (!d->emitingEditingFinished) { + if (hasAcceptableInput() || d->fixup()) { + d->emitingEditingFinished = true; + emit editingFinished(); + d->emitingEditingFinished = false; + } + } +#ifdef QT3_SUPPORT + emit lostFocus(); +#endif + } +#ifdef Q_WS_MAC + if (d->echoMode == Password || d->echoMode == NoEcho) + qt_mac_secure_keyboard(false); +#endif +#ifdef QT_KEYPAD_NAVIGATION + d->origText = QString(); +#endif +#ifndef QT_NO_COMPLETER + if (d->completer) { + QObject::disconnect(d->completer, 0, this, 0); + } +#endif + update(); +} + +/*!\reimp +*/ +void QLineEdit::paintEvent(QPaintEvent *) +{ + Q_D(QLineEdit); + QPainter p(this); + + QRect r = rect(); + QPalette pal = palette(); + + QStyleOptionFrameV2 panel; + initStyleOption(&panel); + style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this); + r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this); + r.setX(r.x() + d->leftTextMargin); + r.setY(r.y() + d->topTextMargin); + r.setRight(r.right() - d->rightTextMargin); + r.setBottom(r.bottom() - d->bottomTextMargin); + p.setClipRect(r); + + QFontMetrics fm = fontMetrics(); + Qt::Alignment va = QStyle::visualAlignment(layoutDirection(), QFlag(d->alignment)); + switch (va & Qt::AlignVertical_Mask) { + case Qt::AlignBottom: + d->vscroll = r.y() + r.height() - fm.height() - verticalMargin; + break; + case Qt::AlignTop: + d->vscroll = r.y() + verticalMargin; + break; + default: + //center + d->vscroll = r.y() + (r.height() - fm.height() + 1) / 2; + break; + } + QRect lineRect(r.x() + horizontalMargin, d->vscroll, r.width() - 2*horizontalMargin, fm.height()); + QTextLine line = d->textLayout.lineAt(0); + + int cursor = d->cursor; + if (d->preeditCursor != -1) + cursor += d->preeditCursor; + // locate cursor position + int cix = qRound(line.cursorToX(cursor)); + + // horizontal scrolling. d->hscroll is the left indent from the beginning + // of the text line to the left edge of lineRect. we update this value + // depending on the delta from the last paint event; in effect this means + // the below code handles all scrolling based on the textline (widthUsed, + // minLB, minRB), the line edit rect (lineRect) and the cursor position + // (cix). + int minLB = qMax(0, -fm.minLeftBearing()); + int minRB = qMax(0, -fm.minRightBearing()); + int widthUsed = qRound(line.naturalTextWidth()) + 1 + minRB; + if ((minLB + widthUsed) <= lineRect.width()) { + // text fits in lineRect; use hscroll for alignment + switch (va & ~(Qt::AlignAbsolute|Qt::AlignVertical_Mask)) { + case Qt::AlignRight: + d->hscroll = widthUsed - lineRect.width() + 1; + break; + case Qt::AlignHCenter: + d->hscroll = (widthUsed - lineRect.width()) / 2; + break; + default: + // Left + d->hscroll = 0; + break; + } + d->hscroll -= minLB; + } else if (cix - d->hscroll >= lineRect.width()) { + // text doesn't fit, cursor is to the right of lineRect (scroll right) + d->hscroll = cix - lineRect.width() + 1; + } else if (cix - d->hscroll < 0 && d->hscroll < widthUsed) { + // text doesn't fit, cursor is to the left of lineRect (scroll left) + d->hscroll = cix; + } else if (widthUsed - d->hscroll < lineRect.width()) { + // text doesn't fit, text document is to the left of lineRect; align + // right + d->hscroll = widthUsed - lineRect.width() + 1; + } + // the y offset is there to keep the baseline constant in case we have script changes in the text. + QPoint topLeft = lineRect.topLeft() - QPoint(d->hscroll, d->ascent - fm.ascent()); + + // draw text, selections and cursors +#ifndef QT_NO_STYLE_STYLESHEET + if (QStyleSheetStyle* cssStyle = qobject_cast<QStyleSheetStyle*>(style())) { + cssStyle->focusPalette(this, &panel, &pal); + } +#endif + p.setPen(pal.text().color()); + + QVector<QTextLayout::FormatRange> selections; +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || hasEditFocus()) +#endif + if (d->selstart < d->selend || (d->cursorVisible && d->maskData && !d->readOnly)) { + QTextLayout::FormatRange o; + if (d->selstart < d->selend) { + o.start = d->selstart; + o.length = d->selend - d->selstart; + o.format.setBackground(pal.brush(QPalette::Highlight)); + o.format.setForeground(pal.brush(QPalette::HighlightedText)); + } else { + // mask selection + o.start = d->cursor; + o.length = 1; + o.format.setBackground(pal.brush(QPalette::Text)); + o.format.setForeground(pal.brush(QPalette::Window)); + } + selections.append(o); + } + + // Asian users see an IM selection text as cursor on candidate + // selection phase of input method, so the ordinary cursor should be + // invisible if we have a preedit string. + d->textLayout.draw(&p, topLeft, selections, r); + if (d->cursorVisible && !d->readOnly && !d->hideCursor) + d->textLayout.drawCursor(&p, topLeft, cursor, style()->pixelMetric(QStyle::PM_TextCursorWidth)); +} + + +#ifndef QT_NO_DRAGANDDROP +/*!\reimp +*/ +void QLineEdit::dragMoveEvent(QDragMoveEvent *e) +{ + Q_D(QLineEdit); + if (!d->readOnly && e->mimeData()->hasFormat(QLatin1String("text/plain"))) { + e->acceptProposedAction(); + d->cursor = d->xToPos(e->pos().x()); + d->cursorVisible = true; + update(); + d->emitCursorPositionChanged(); + } +} + +/*!\reimp */ +void QLineEdit::dragEnterEvent(QDragEnterEvent * e) +{ + QLineEdit::dragMoveEvent(e); +} + +/*!\reimp */ +void QLineEdit::dragLeaveEvent(QDragLeaveEvent *) +{ + Q_D(QLineEdit); + if (d->cursorVisible) { + d->cursorVisible = false; + update(); + } +} + +/*!\reimp */ +void QLineEdit::dropEvent(QDropEvent* e) +{ + Q_D(QLineEdit); + QString str = e->mimeData()->text(); + + if (!str.isNull() && !d->readOnly) { + if (e->source() == this && e->dropAction() == Qt::CopyAction) + deselect(); + d->cursor =d->xToPos(e->pos().x()); + int selStart = d->cursor; + int oldSelStart = d->selstart; + int oldSelEnd = d->selend; + d->cursorVisible = false; + e->acceptProposedAction(); + insert(str); + if (e->source() == this) { + if (e->dropAction() == Qt::MoveAction) { + if (selStart > oldSelStart && selStart <= oldSelEnd) + setSelection(oldSelStart, str.length()); + else if (selStart > oldSelEnd) + setSelection(selStart - str.length(), str.length()); + else + setSelection(selStart, str.length()); + } else { + setSelection(selStart, str.length()); + } + } + } else { + e->ignore(); + update(); + } +} + +void QLineEditPrivate::drag() +{ + Q_Q(QLineEdit); + dndTimer.stop(); + QMimeData *data = new QMimeData; + data->setText(q->selectedText()); + QDrag *drag = new QDrag(q); + drag->setMimeData(data); + Qt::DropAction action = drag->start(); + if (action == Qt::MoveAction && !readOnly && drag->target() != q) { + int priorState = undoState; + removeSelectedText(); + finishChange(priorState); + } +} + +#endif // QT_NO_DRAGANDDROP + +#ifndef QT_NO_CONTEXTMENU +/*! + Shows the standard context menu created with + createStandardContextMenu(). + + If you do not want the line edit to have a context menu, you can set + its \l contextMenuPolicy to Qt::NoContextMenu. If you want to + customize the context menu, reimplement this function. If you want + to extend the standard context menu, reimplement this function, call + createStandardContextMenu() and extend the menu returned. + + \snippet doc/src/snippets/code/src_gui_widgets_qlineedit.cpp 0 + + The \a event parameter is used to obtain the position where + the mouse cursor was when the event was generated. + + \sa setContextMenuPolicy() +*/ +void QLineEdit::contextMenuEvent(QContextMenuEvent *event) +{ + QPointer<QMenu> menu = createStandardContextMenu(); + menu->exec(event->globalPos()); + delete menu; +} + +#if defined(Q_WS_WIN) + extern bool qt_use_rtl_extensions; +#endif + +/*! This function creates the standard context menu which is shown + when the user clicks on the line edit with the right mouse + button. It is called from the default contextMenuEvent() handler. + The popup menu's ownership is transferred to the caller. +*/ + +QMenu *QLineEdit::createStandardContextMenu() +{ + Q_D(QLineEdit); + QMenu *popup = new QMenu(this); + popup->setObjectName(QLatin1String("qt_edit_menu")); + + QAction *action = popup->addAction(QLineEdit::tr("&Undo") + ACCEL_KEY(QKeySequence::Undo)); + action->setEnabled(d->isUndoAvailable()); + connect(action, SIGNAL(triggered()), SLOT(undo())); + + action = popup->addAction(QLineEdit::tr("&Redo") + ACCEL_KEY(QKeySequence::Redo)); + action->setEnabled(d->isRedoAvailable()); + connect(action, SIGNAL(triggered()), SLOT(redo())); + + popup->addSeparator(); + +#ifndef QT_NO_CLIPBOARD + action = popup->addAction(QLineEdit::tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut)); + action->setEnabled(!d->readOnly && d->hasSelectedText()); + connect(action, SIGNAL(triggered()), SLOT(cut())); + + action = popup->addAction(QLineEdit::tr("&Copy") + ACCEL_KEY(QKeySequence::Copy)); + action->setEnabled(d->hasSelectedText()); + connect(action, SIGNAL(triggered()), SLOT(copy())); + + action = popup->addAction(QLineEdit::tr("&Paste") + ACCEL_KEY(QKeySequence::Paste)); + action->setEnabled(!d->readOnly && !QApplication::clipboard()->text().isEmpty()); + connect(action, SIGNAL(triggered()), SLOT(paste())); +#endif + + action = popup->addAction(QLineEdit::tr("Delete")); + action->setEnabled(!d->readOnly && !d->text.isEmpty() && d->hasSelectedText()); + connect(action, SIGNAL(triggered()), SLOT(_q_deleteSelected())); + + popup->addSeparator(); + + action = popup->addAction(QLineEdit::tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll)); + action->setEnabled(!d->text.isEmpty() && !d->allSelected()); + d->selectAllAction = action; + connect(action, SIGNAL(triggered()), SLOT(selectAll())); + +#if !defined(QT_NO_IM) + QInputContext *qic = inputContext(); + if (qic) { + QList<QAction *> imActions = qic->actions(); + for (int i = 0; i < imActions.size(); ++i) + popup->addAction(imActions.at(i)); + } +#endif + +#if defined(Q_WS_WIN) + if (!d->readOnly && qt_use_rtl_extensions) { +#else + if (!d->readOnly) { +#endif + popup->addSeparator(); + QUnicodeControlCharacterMenu *ctrlCharacterMenu = new QUnicodeControlCharacterMenu(this, popup); + popup->addMenu(ctrlCharacterMenu); + } + return popup; +} +#endif // QT_NO_CONTEXTMENU + +/*! \reimp */ +void QLineEdit::changeEvent(QEvent *ev) +{ + Q_D(QLineEdit); + if(ev->type() == QEvent::ActivationChange) { + if (!palette().isEqual(QPalette::Active, QPalette::Inactive)) + update(); + } else if (ev->type() == QEvent::FontChange + || ev->type() == QEvent::StyleChange + || ev->type() == QEvent::LayoutDirectionChange) { + d->updateTextLayout(); + } + QWidget::changeEvent(ev); +} + +void QLineEditPrivate::_q_clipboardChanged() +{ +} + +void QLineEditPrivate::_q_handleWindowActivate() +{ + Q_Q(QLineEdit); + if (!q->hasFocus() && q->hasSelectedText()) + q->deselect(); +} + +void QLineEditPrivate::_q_deleteSelected() +{ + Q_Q(QLineEdit); + if (!hasSelectedText()) + return; + + int priorState = undoState; + q->resetInputContext(); + removeSelectedText(); + separate(); + finishChange(priorState); +} + +void QLineEditPrivate::init(const QString& txt) +{ + Q_Q(QLineEdit); +#ifndef QT_NO_CURSOR + q->setCursor(Qt::IBeamCursor); +#endif + q->setFocusPolicy(Qt::StrongFocus); + q->setAttribute(Qt::WA_InputMethodEnabled); + // Specifies that this widget can use more, but is able to survive on + // less, horizontal space; and is fixed vertically. + q->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::LineEdit)); + q->setBackgroundRole(QPalette::Base); + q->setAttribute(Qt::WA_KeyCompression); + q->setMouseTracking(true); + q->setAcceptDrops(true); + text = txt; + updateTextLayout(); + cursor = text.length(); + + q->setAttribute(Qt::WA_MacShowFocusRect); +} + +void QLineEditPrivate::updatePasswordEchoEditing(bool editing) +{ + Q_Q(QLineEdit); + passwordEchoEditing = editing; + q->setAttribute(Qt::WA_InputMethodEnabled, shouldEnableInputMethod(q)); + updateTextLayout(); + q->update(); +} + +void QLineEditPrivate::updateTextLayout() +{ + // replace certain non-printable characters with spaces (to avoid + // drawing boxes when using fonts that don't have glyphs for such + // characters) + Q_Q(QLineEdit); + QString str = q->displayText(); + QChar* uc = str.data(); + for (int i = 0; i < (int)str.length(); ++i) { + if ((uc[i] < 0x20 && uc[i] != 0x09) + || uc[i] == QChar::LineSeparator + || uc[i] == QChar::ParagraphSeparator + || uc[i] == QChar::ObjectReplacementCharacter) + uc[i] = QChar(0x0020); + } + textLayout.setFont(q->font()); + textLayout.setText(str); + QTextOption option; + option.setTextDirection(q->layoutDirection()); + option.setFlags(QTextOption::IncludeTrailingSpaces); + textLayout.setTextOption(option); + + textLayout.beginLayout(); + QTextLine l = textLayout.createLine(); + textLayout.endLayout(); + ascent = qRound(l.ascent()); +} + +int QLineEditPrivate::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const +{ + QRect cr = adjustedContentsRect(); + x-= cr.x() - hscroll + horizontalMargin; + QTextLine l = textLayout.lineAt(0); + return l.xToCursor(x, betweenOrOn); +} + +QRect QLineEditPrivate::cursorRect() const +{ + Q_Q(const QLineEdit); + QRect cr = adjustedContentsRect(); + int cix = cr.x() - hscroll + horizontalMargin; + QTextLine l = textLayout.lineAt(0); + int c = cursor; + if (preeditCursor != -1) + c += preeditCursor; + cix += qRound(l.cursorToX(c)); + int ch = qMin(cr.height(), q->fontMetrics().height() + 1); + int w = q->style()->pixelMetric(QStyle::PM_TextCursorWidth); + return QRect(cix-5, vscroll, w + 9, ch); +} + +QRect QLineEditPrivate::adjustedContentsRect() const +{ + Q_Q(const QLineEdit); + QStyleOptionFrameV2 opt; + q->initStyleOption(&opt); + QRect r = q->style()->subElementRect(QStyle::SE_LineEditContents, &opt, q); + r.setX(r.x() + leftTextMargin); + r.setY(r.y() + topTextMargin); + r.setRight(r.right() - rightTextMargin); + r.setBottom(r.bottom() - bottomTextMargin); + return r; +} + +bool QLineEditPrivate::fixup() // this function assumes that validate currently returns != Acceptable +{ +#ifndef QT_NO_VALIDATOR + if (validator) { + QString textCopy = text; + int cursorCopy = cursor; + validator->fixup(textCopy); + if (validator->validate(textCopy, cursorCopy) == QValidator::Acceptable) { + if (textCopy != text || cursorCopy != cursor) + setText(textCopy, cursorCopy); + return true; + } + } +#endif + return false; +} + +void QLineEditPrivate::moveCursor(int pos, bool mark) +{ + Q_Q(QLineEdit); + if (pos != cursor) { + separate(); + if (maskData) + pos = pos > cursor ? nextMaskBlank(pos) : prevMaskBlank(pos); + } + bool fullUpdate = mark || hasSelectedText(); + if (mark) { + int anchor; + if (selend > selstart && cursor == selstart) + anchor = selend; + else if (selend > selstart && cursor == selend) + anchor = selstart; + else + anchor = cursor; + selstart = qMin(anchor, pos); + selend = qMax(anchor, pos); + updateTextLayout(); + } else { + deselect(); + } + if (fullUpdate) { + cursor = pos; + q->update(); + } else { + setCursorVisible(false); + cursor = pos; + setCursorVisible(true); + if (!adjustedContentsRect().contains(cursorRect())) + q->update(); + } + QStyleOptionFrameV2 opt; + q->initStyleOption(&opt); + if (mark && !q->style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, &opt, q)) + setCursorVisible(false); + if (mark || selDirty) { + selDirty = false; + emit q->selectionChanged(); + } + emitCursorPositionChanged(); +} + +void QLineEditPrivate::finishChange(int validateFromState, bool update, bool edited) +{ + Q_Q(QLineEdit); + bool lineDirty = selDirty; + if (textDirty) { + // do validation + bool wasValidInput = validInput; + validInput = true; +#ifndef QT_NO_VALIDATOR + if (validator) { + validInput = false; + QString textCopy = text; + int cursorCopy = cursor; + validInput = (validator->validate(textCopy, cursorCopy) != QValidator::Invalid); + if (validInput) { + if (text != textCopy) { + setText(textCopy, cursorCopy); + return; + } + cursor = cursorCopy; + } + } +#endif + if (validateFromState >= 0 && wasValidInput && !validInput) { + undo(validateFromState); + history.resize(undoState); + if (modifiedState > undoState) + modifiedState = -1; + validInput = true; + textDirty = false; + } + updateTextLayout(); + lineDirty |= textDirty; + if (textDirty) { + textDirty = false; + QString actualText = maskData ? stripString(text) : text; + if (edited) + emit q->textEdited(actualText); + q->updateMicroFocus(); +#ifndef QT_NO_COMPLETER + if (edited && completer && completer->completionMode() != QCompleter::InlineCompletion) + complete(-1); // update the popup on cut/paste/del +#endif + emit q->textChanged(actualText); + } +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(q, 0, QAccessible::ValueChanged); +#endif + } + if (selDirty) { + selDirty = false; + emit q->selectionChanged(); + } + if (lineDirty || update) + q->update(); + emitCursorPositionChanged(); +} + +void QLineEditPrivate::emitCursorPositionChanged() +{ + Q_Q(QLineEdit); + if (cursor != lastCursorPos) { + const int oldLast = lastCursorPos; + lastCursorPos = cursor; + emit q->cursorPositionChanged(oldLast, cursor); + } +} + +void QLineEditPrivate::setText(const QString& txt, int pos, bool edited) +{ + Q_Q(QLineEdit); + q->resetInputContext(); + deselect(); + QString oldText = text; + if (maskData) { + text = maskString(0, txt, true); + text += clearString(text.length(), maxLength - text.length()); + } else { + text = txt.isEmpty() ? txt : txt.left(maxLength); + } + history.clear(); + modifiedState = undoState = 0; + cursor = (pos < 0 || pos > text.length()) ? text.length() : pos; + textDirty = (oldText != text); + finishChange(-1, true, edited); +} + + +void QLineEditPrivate::setCursorVisible(bool visible) +{ + Q_Q(QLineEdit); + if ((bool)cursorVisible == visible) + return; + if (cursorTimer) + cursorVisible = visible; + QRect r = cursorRect(); + if (maskData) + q->update(); + else + q->update(r); +} + +void QLineEditPrivate::addCommand(const Command& cmd) +{ + if (separator && undoState && history[undoState-1].type != Separator) { + history.resize(undoState + 2); + history[undoState++] = Command(Separator, cursor, 0, selstart, selend); + } else { + history.resize(undoState + 1); + } + separator = false; + history[undoState++] = cmd; +} + +void QLineEditPrivate::insert(const QString& s) +{ + if (hasSelectedText()) + addCommand(Command(SetSelection, cursor, 0, selstart, selend)); + if (maskData) { + QString ms = maskString(cursor, s); + for (int i = 0; i < (int) ms.length(); ++i) { + addCommand (Command(DeleteSelection, cursor+i, text.at(cursor+i), -1, -1)); + addCommand(Command(Insert, cursor+i, ms.at(i), -1, -1)); + } + text.replace(cursor, ms.length(), ms); + cursor += ms.length(); + cursor = nextMaskBlank(cursor); + textDirty = true; + } else { + int remaining = maxLength - text.length(); + if (remaining != 0) { + text.insert(cursor, s.left(remaining)); + for (int i = 0; i < (int) s.left(remaining).length(); ++i) + addCommand(Command(Insert, cursor++, s.at(i), -1, -1)); + textDirty = true; + } + } +} + +void QLineEditPrivate::del(bool wasBackspace) +{ + if (cursor < (int) text.length()) { + if (hasSelectedText()) + addCommand(Command(SetSelection, cursor, 0, selstart, selend)); + addCommand (Command((CommandType)((maskData?2:0)+(wasBackspace?Remove:Delete)), cursor, text.at(cursor), -1, -1)); + if (maskData) { + text.replace(cursor, 1, clearString(cursor, 1)); + addCommand(Command(Insert, cursor, text.at(cursor), -1, -1)); + } else { + text.remove(cursor, 1); + } + textDirty = true; + } +} + +void QLineEditPrivate::removeSelectedText() +{ + if (selstart < selend && selend <= (int) text.length()) { + separate(); + int i ; + addCommand(Command(SetSelection, cursor, 0, selstart, selend)); + if (selstart <= cursor && cursor < selend) { + // cursor is within the selection. Split up the commands + // to be able to restore the correct cursor position + for (i = cursor; i >= selstart; --i) + addCommand (Command(DeleteSelection, i, text.at(i), -1, 1)); + for (i = selend - 1; i > cursor; --i) + addCommand (Command(DeleteSelection, i - cursor + selstart - 1, text.at(i), -1, -1)); + } else { + for (i = selend-1; i >= selstart; --i) + addCommand (Command(RemoveSelection, i, text.at(i), -1, -1)); + } + if (maskData) { + text.replace(selstart, selend - selstart, clearString(selstart, selend - selstart)); + for (int i = 0; i < selend - selstart; ++i) + addCommand(Command(Insert, selstart + i, text.at(selstart + i), -1, -1)); + } else { + text.remove(selstart, selend - selstart); + } + if (cursor > selstart) + cursor -= qMin(cursor, selend) - selstart; + deselect(); + textDirty = true; + + // adjust hscroll to avoid gap + const int minRB = qMax(0, -q_func()->fontMetrics().minRightBearing()); + updateTextLayout(); + const QTextLine line = textLayout.lineAt(0); + const int widthUsed = qRound(line.naturalTextWidth()) + 1 + minRB; + hscroll = qMin(hscroll, widthUsed); + } +} + +void QLineEditPrivate::parseInputMask(const QString &maskFields) +{ + int delimiter = maskFields.indexOf(QLatin1Char(';')); + if (maskFields.isEmpty() || delimiter == 0) { + if (maskData) { + delete [] maskData; + maskData = 0; + maxLength = 32767; + setText(QString()); + } + return; + } + + if (delimiter == -1) { + blank = QLatin1Char(' '); + inputMask = maskFields; + } else { + inputMask = maskFields.left(delimiter); + blank = (delimiter + 1 < maskFields.length()) ? maskFields[delimiter + 1] : QLatin1Char(' '); + } + + // calculate maxLength / maskData length + maxLength = 0; + QChar c = 0; + for (int i=0; i<inputMask.length(); i++) { + c = inputMask.at(i); + if (i > 0 && inputMask.at(i-1) == QLatin1Char('\\')) { + maxLength++; + continue; + } + if (c != QLatin1Char('\\') && c != QLatin1Char('!') && + c != QLatin1Char('<') && c != QLatin1Char('>') && + c != QLatin1Char('{') && c != QLatin1Char('}') && + c != QLatin1Char('[') && c != QLatin1Char(']')) + maxLength++; + } + + delete [] maskData; + maskData = new MaskInputData[maxLength]; + + MaskInputData::Casemode m = MaskInputData::NoCaseMode; + c = 0; + bool s; + bool escape = false; + int index = 0; + for (int i = 0; i < inputMask.length(); i++) { + c = inputMask.at(i); + if (escape) { + s = true; + maskData[index].maskChar = c; + maskData[index].separator = s; + maskData[index].caseMode = m; + index++; + escape = false; + } else if (c == QLatin1Char('<')) { + m = MaskInputData::Lower; + } else if (c == QLatin1Char('>')) { + m = MaskInputData::Upper; + } else if (c == QLatin1Char('!')) { + m = MaskInputData::NoCaseMode; + } else if (c != QLatin1Char('{') && c != QLatin1Char('}') && c != QLatin1Char('[') && c != QLatin1Char(']')) { + switch (c.unicode()) { + case 'A': + case 'a': + case 'N': + case 'n': + case 'X': + case 'x': + case '9': + case '0': + case 'D': + case 'd': + case '#': + case 'H': + case 'h': + case 'B': + case 'b': + s = false; + break; + case '\\': + escape = true; + default: + s = true; + break; + } + + if (!escape) { + maskData[index].maskChar = c; + maskData[index].separator = s; + maskData[index].caseMode = m; + index++; + } + } + } + setText(text); +} + + +/* checks if the key is valid compared to the inputMask */ +bool QLineEditPrivate::isValidInput(QChar key, QChar mask) const +{ + switch (mask.unicode()) { + case 'A': + if (key.isLetter()) + return true; + break; + case 'a': + if (key.isLetter() || key == blank) + return true; + break; + case 'N': + if (key.isLetterOrNumber()) + return true; + break; + case 'n': + if (key.isLetterOrNumber() || key == blank) + return true; + break; + case 'X': + if (key.isPrint()) + return true; + break; + case 'x': + if (key.isPrint() || key == blank) + return true; + break; + case '9': + if (key.isNumber()) + return true; + break; + case '0': + if (key.isNumber() || key == blank) + return true; + break; + case 'D': + if (key.isNumber() && key.digitValue() > 0) + return true; + break; + case 'd': + if ((key.isNumber() && key.digitValue() > 0) || key == blank) + return true; + break; + case '#': + if (key.isNumber() || key == QLatin1Char('+') || key == QLatin1Char('-') || key == blank) + return true; + break; + case 'B': + if (key == QLatin1Char('0') || key == QLatin1Char('1')) + return true; + break; + case 'b': + if (key == QLatin1Char('0') || key == QLatin1Char('1') || key == blank) + return true; + break; + case 'H': + if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F'))) + return true; + break; + case 'h': + if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')) || key == blank) + return true; + break; + default: + break; + } + return false; +} + +bool QLineEditPrivate::hasAcceptableInput(const QString &str) const +{ +#ifndef QT_NO_VALIDATOR + QString textCopy = str; + int cursorCopy = cursor; + if (validator && validator->validate(textCopy, cursorCopy) + != QValidator::Acceptable) + return false; +#endif + + if (!maskData) + return true; + + if (str.length() != maxLength) + return false; + + for (int i=0; i < maxLength; ++i) { + if (maskData[i].separator) { + if (str.at(i) != maskData[i].maskChar) + return false; + } else { + if (!isValidInput(str.at(i), maskData[i].maskChar)) + return false; + } + } + return true; +} + +/* + Applies the inputMask on \a str starting from position \a pos in the mask. \a clear + specifies from where characters should be gotten when a separator is met in \a str - true means + that blanks will be used, false that previous input is used. + Calling this when no inputMask is set is undefined. +*/ +QString QLineEditPrivate::maskString(uint pos, const QString &str, bool clear) const +{ + if (pos >= (uint)maxLength) + return QString::fromLatin1(""); + + QString fill; + fill = clear ? clearString(0, maxLength) : text; + + int strIndex = 0; + QString s = QString::fromLatin1(""); + int i = pos; + while (i < maxLength) { + if (strIndex < str.length()) { + if (maskData[i].separator) { + s += maskData[i].maskChar; + if (str[(int)strIndex] == maskData[i].maskChar) + strIndex++; + ++i; + } else { + if (isValidInput(str[(int)strIndex], maskData[i].maskChar)) { + switch (maskData[i].caseMode) { + case MaskInputData::Upper: + s += str[(int)strIndex].toUpper(); + break; + case MaskInputData::Lower: + s += str[(int)strIndex].toLower(); + break; + default: + s += str[(int)strIndex]; + } + ++i; + } else { + // search for separator first + int n = findInMask(i, true, true, str[(int)strIndex]); + if (n != -1) { + if (str.length() != 1 || i == 0 || (i > 0 && (!maskData[i-1].separator || maskData[i-1].maskChar != str[(int)strIndex]))) { + s += fill.mid(i, n-i+1); + i = n + 1; // update i to find + 1 + } + } else { + // search for valid blank if not + n = findInMask(i, true, false, str[(int)strIndex]); + if (n != -1) { + s += fill.mid(i, n-i); + switch (maskData[n].caseMode) { + case MaskInputData::Upper: + s += str[(int)strIndex].toUpper(); + break; + case MaskInputData::Lower: + s += str[(int)strIndex].toLower(); + break; + default: + s += str[(int)strIndex]; + } + i = n + 1; // updates i to find + 1 + } + } + } + strIndex++; + } + } else + break; + } + + return s; +} + + + +/* + Returns a "cleared" string with only separators and blank chars. + Calling this when no inputMask is set is undefined. +*/ +QString QLineEditPrivate::clearString(uint pos, uint len) const +{ + if (pos >= (uint)maxLength) + return QString(); + + QString s; + int end = qMin((uint)maxLength, pos + len); + for (int i=pos; i<end; i++) + if (maskData[i].separator) + s += maskData[i].maskChar; + else + s += blank; + + return s; +} + +/* + Strips blank parts of the input in a QLineEdit when an inputMask is set, + separators are still included. Typically "127.0__.0__.1__" becomes "127.0.0.1". +*/ +QString QLineEditPrivate::stripString(const QString &str) const +{ + if (!maskData) + return str; + + QString s; + int end = qMin(maxLength, (int)str.length()); + for (int i=0; i < end; i++) + if (maskData[i].separator) + s += maskData[i].maskChar; + else + if (str[i] != blank) + s += str[i]; + + return s; +} + +/* searches forward/backward in maskData for either a separator or a blank */ +int QLineEditPrivate::findInMask(int pos, bool forward, bool findSeparator, QChar searchChar) const +{ + if (pos >= maxLength || pos < 0) + return -1; + + int end = forward ? maxLength : -1; + int step = forward ? 1 : -1; + int i = pos; + + while (i != end) { + if (findSeparator) { + if (maskData[i].separator && maskData[i].maskChar == searchChar) + return i; + } else { + if (!maskData[i].separator) { + if (searchChar.isNull()) + return i; + else if (isValidInput(searchChar, maskData[i].maskChar)) + return i; + } + } + i += step; + } + return -1; +} + +void QLineEditPrivate::undo(int until) +{ + if (!isUndoAvailable()) + return; + deselect(); + while (undoState && undoState > until) { + Command& cmd = history[--undoState]; + switch (cmd.type) { + case Insert: + text.remove(cmd.pos, 1); + cursor = cmd.pos; + break; + case SetSelection: + selstart = cmd.selStart; + selend = cmd.selEnd; + cursor = cmd.pos; + break; + case Remove: + case RemoveSelection: + text.insert(cmd.pos, cmd.uc); + cursor = cmd.pos + 1; + break; + case Delete: + case DeleteSelection: + text.insert(cmd.pos, cmd.uc); + cursor = cmd.pos; + break; + case Separator: + continue; + } + if (until < 0 && undoState) { + Command& next = history[undoState-1]; + if (next.type != cmd.type && next.type < RemoveSelection + && (cmd.type < RemoveSelection || next.type == Separator)) + break; + } + } + textDirty = true; + emitCursorPositionChanged(); +} + +void QLineEditPrivate::redo() { + if (!isRedoAvailable()) + return; + deselect(); + while (undoState < (int)history.size()) { + Command& cmd = history[undoState++]; + switch (cmd.type) { + case Insert: + text.insert(cmd.pos, cmd.uc); + cursor = cmd.pos + 1; + break; + case SetSelection: + selstart = cmd.selStart; + selend = cmd.selEnd; + cursor = cmd.pos; + break; + case Remove: + case Delete: + case RemoveSelection: + case DeleteSelection: + text.remove(cmd.pos, 1); + cursor = cmd.pos; + break; + case Separator: + selstart = cmd.selStart; + selend = cmd.selEnd; + cursor = cmd.pos; + break; + } + if (undoState < (int)history.size()) { + Command& next = history[undoState]; + if (next.type != cmd.type && cmd.type < RemoveSelection && next.type != Separator + && (next.type < RemoveSelection || cmd.type == Separator)) + break; + } + } + textDirty = true; + emitCursorPositionChanged(); +} + +/*! + \fn void QLineEdit::repaintArea(int a, int b) + + Use update() instead. +*/ + +/*! + \fn void QLineEdit::cursorLeft(bool mark, int steps) + + Use cursorForward() with a negative number of steps instead. For + example, cursorForward(mark, -steps). +*/ + +/*! + \fn void QLineEdit::cursorRight(bool mark, int steps) + + Use cursorForward() instead. +*/ + +/*! + \fn bool QLineEdit::frame() const + + Use hasFrame() instead. +*/ + +/*! + \fn void QLineEdit::clearValidator() + + Use setValidator(0) instead. +*/ + +/*! + \fn bool QLineEdit::hasMarkedText() const + + Use hasSelectedText() instead. +*/ + +/*! + \fn QString QLineEdit::markedText() const + + Use selectedText() instead. +*/ + +/*! + \fn void QLineEdit::setFrameRect(QRect) + \internal +*/ + +/*! + \fn QRect QLineEdit::frameRect() const + \internal +*/ +/*! + \enum QLineEdit::DummyFrame + \internal + + \value Box + \value Sunken + \value Plain + \value Raised + \value MShadow + \value NoFrame + \value Panel + \value StyledPanel + \value HLine + \value VLine + \value GroupBoxPanel + \value WinPanel + \value ToolBarPanel + \value MenuBarPanel + \value PopupPanel + \value LineEditPanel + \value TabWidgetPanel + \value MShape +*/ + +/*! + \fn void QLineEdit::setFrameShadow(DummyFrame) + \internal +*/ + +/*! + \fn DummyFrame QLineEdit::frameShadow() const + \internal +*/ + +/*! + \fn void QLineEdit::setFrameShape(DummyFrame) + \internal +*/ + +/*! + \fn DummyFrame QLineEdit::frameShape() const + \internal +*/ + +/*! + \fn void QLineEdit::setFrameStyle(int) + \internal +*/ + +/*! + \fn int QLineEdit::frameStyle() const + \internal +*/ + +/*! + \fn int QLineEdit::frameWidth() const + \internal +*/ + +/*! + \fn void QLineEdit::setLineWidth(int) + \internal +*/ + +/*! + \fn int QLineEdit::lineWidth() const + \internal +*/ + +/*! + \fn void QLineEdit::setMargin(int margin) + Sets the width of the margin around the contents of the widget to \a margin. + + Use QWidget::setContentsMargins() instead. + \sa margin(), QWidget::setContentsMargins() +*/ + +/*! + \fn int QLineEdit::margin() const + Returns the with of the the margin around the contents of the widget. + + Use QWidget::getContentsMargins() instead. + \sa setMargin(), QWidget::getContentsMargins() +*/ + +/*! + \fn void QLineEdit::setMidLineWidth(int) + \internal +*/ + +/*! + \fn int QLineEdit::midLineWidth() const + \internal +*/ + +QT_END_NAMESPACE + +#include "moc_qlineedit.cpp" + +#endif // QT_NO_LINEEDIT diff --git a/src/gui/widgets/qlineedit.h b/src/gui/widgets/qlineedit.h new file mode 100644 index 0000000..c0d9892 --- /dev/null +++ b/src/gui/widgets/qlineedit.h @@ -0,0 +1,283 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLINEEDIT_H +#define QLINEEDIT_H + +#include <QtGui/qframe.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LINEEDIT + +class QValidator; +class QMenu; +class QLineEditPrivate; +class QCompleter; +class QStyleOptionFrame; +class QAbstractSpinBox; +class QDateTimeEdit; + +class Q_GUI_EXPORT QLineEdit : public QWidget +{ + Q_OBJECT + + Q_ENUMS(EchoMode) + Q_PROPERTY(QString inputMask READ inputMask WRITE setInputMask) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged USER true) + Q_PROPERTY(int maxLength READ maxLength WRITE setMaxLength) + Q_PROPERTY(bool frame READ hasFrame WRITE setFrame) + Q_PROPERTY(EchoMode echoMode READ echoMode WRITE setEchoMode) + Q_PROPERTY(QString displayText READ displayText) + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(bool modified READ isModified WRITE setModified DESIGNABLE false) + Q_PROPERTY(bool hasSelectedText READ hasSelectedText) + Q_PROPERTY(QString selectedText READ selectedText) + Q_PROPERTY(bool dragEnabled READ dragEnabled WRITE setDragEnabled) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool undoAvailable READ isUndoAvailable) + Q_PROPERTY(bool redoAvailable READ isRedoAvailable) + Q_PROPERTY(bool acceptableInput READ hasAcceptableInput) + +public: + explicit QLineEdit(QWidget* parent=0); + explicit QLineEdit(const QString &, QWidget* parent=0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QLineEdit(QWidget* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QLineEdit(const QString &, QWidget* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QLineEdit(const QString &, const QString &, QWidget* parent=0, const char* name=0); +#endif + ~QLineEdit(); + + QString text() const; + + QString displayText() const; + + int maxLength() const; + void setMaxLength(int); + + void setFrame(bool); + bool hasFrame() const; + + enum EchoMode { Normal, NoEcho, Password, PasswordEchoOnEdit }; + EchoMode echoMode() const; + void setEchoMode(EchoMode); + + bool isReadOnly() const; + void setReadOnly(bool); + +#ifndef QT_NO_VALIDATOR + void setValidator(const QValidator *); + const QValidator * validator() const; +#endif + +#ifndef QT_NO_COMPLETER + void setCompleter(QCompleter *completer); + QCompleter *completer() const; +#endif + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + int cursorPosition() const; + void setCursorPosition(int); + int cursorPositionAt(const QPoint &pos); + + void setAlignment(Qt::Alignment flag); + Qt::Alignment alignment() const; + + void cursorForward(bool mark, int steps = 1); + void cursorBackward(bool mark, int steps = 1); + void cursorWordForward(bool mark); + void cursorWordBackward(bool mark); + void backspace(); + void del(); + void home(bool mark); + void end(bool mark); + + bool isModified() const; + void setModified(bool); + + void setSelection(int, int); + bool hasSelectedText() const; + QString selectedText() const; + int selectionStart() const; + + bool isUndoAvailable() const; + bool isRedoAvailable() const; + + void setDragEnabled(bool b); + bool dragEnabled() const; + + QString inputMask() const; + void setInputMask(const QString &inputMask); + bool hasAcceptableInput() const; + + void setTextMargins(int left, int top, int right, int bottom); + void getTextMargins(int *left, int *top, int *right, int *bottom) const; + +public Q_SLOTS: + void setText(const QString &); + void clear(); + void selectAll(); + void undo(); + void redo(); +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy() const; + void paste(); +#endif + +public: + void deselect(); + void insert(const QString &); +#ifndef QT_NO_CONTEXTMENU + QMenu *createStandardContextMenu(); +#endif + +Q_SIGNALS: + void textChanged(const QString &); + void textEdited(const QString &); + void cursorPositionChanged(int, int); + void returnPressed(); + void editingFinished(); + void selectionChanged(); + +protected: + void mousePressEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mouseDoubleClickEvent(QMouseEvent *); + void keyPressEvent(QKeyEvent *); + void focusInEvent(QFocusEvent *); + void focusOutEvent(QFocusEvent *); + void paintEvent(QPaintEvent *); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *); + void dragMoveEvent(QDragMoveEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dropEvent(QDropEvent *); +#endif + void changeEvent(QEvent *); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *); +#endif +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void repaintArea(int, int) { update(); } +#endif + + void inputMethodEvent(QInputMethodEvent *); + void initStyleOption(QStyleOptionFrame *option) const; +public: + QVariant inputMethodQuery(Qt::InputMethodQuery) const; + bool event(QEvent *); +protected: + QRect cursorRect() const; + +public: +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void clearModified() { setModified(false); } + inline QT3_SUPPORT void cursorLeft(bool mark, int steps = 1) { cursorForward(mark, -steps); } + inline QT3_SUPPORT void cursorRight(bool mark, int steps = 1) { cursorForward(mark, steps); } + QT3_SUPPORT bool validateAndSet(const QString &, int, int, int); + inline QT3_SUPPORT bool frame() const { return hasFrame(); } +#ifndef QT_NO_VALIDATOR + inline QT3_SUPPORT void clearValidator() { setValidator(0); } +#endif + inline QT3_SUPPORT bool hasMarkedText() const { return hasSelectedText(); } + inline QT3_SUPPORT QString markedText() const { return selectedText(); } + QT3_SUPPORT bool edited() const; + QT3_SUPPORT void setEdited(bool); + QT3_SUPPORT int characterAt(int, QChar*) const; + QT3_SUPPORT bool getSelection(int *, int *); + + QT3_SUPPORT void setFrameRect(QRect) {} + QT3_SUPPORT QRect frameRect() const { return QRect(); } + enum DummyFrame { Box, Sunken, Plain, Raised, MShadow, NoFrame, Panel, StyledPanel, + HLine, VLine, GroupBoxPanel, WinPanel, ToolBarPanel, MenuBarPanel, + PopupPanel, LineEditPanel, TabWidgetPanel, MShape }; + QT3_SUPPORT void setFrameShadow(DummyFrame) {} + QT3_SUPPORT DummyFrame frameShadow() const { return Plain; } + QT3_SUPPORT void setFrameShape(DummyFrame) {} + QT3_SUPPORT DummyFrame frameShape() const { return NoFrame; } + QT3_SUPPORT void setFrameStyle(int) {} + QT3_SUPPORT int frameStyle() const { return 0; } + QT3_SUPPORT int frameWidth() const { return 0; } + QT3_SUPPORT void setLineWidth(int) {} + QT3_SUPPORT int lineWidth() const { return 0; } + QT3_SUPPORT void setMargin(int margin) { setContentsMargins(margin, margin, margin, margin); } + QT3_SUPPORT int margin() const + { int margin; int dummy; getContentsMargins(&margin, &dummy, &dummy, &dummy); return margin; } + QT3_SUPPORT void setMidLineWidth(int) {} + QT3_SUPPORT int midLineWidth() const { return 0; } + +Q_SIGNALS: + QT_MOC_COMPAT void lostFocus(); +#endif + +private: + friend class QAbstractSpinBox; +#ifdef QT_KEYPAD_NAVIGATION + friend class QDateTimeEdit; +#endif + Q_DISABLE_COPY(QLineEdit) + Q_DECLARE_PRIVATE(QLineEdit) + Q_PRIVATE_SLOT(d_func(), void _q_clipboardChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_handleWindowActivate()) + Q_PRIVATE_SLOT(d_func(), void _q_deleteSelected()) +#ifndef QT_NO_COMPLETER + Q_PRIVATE_SLOT(d_func(), void _q_completionHighlighted(QString)) +#endif +}; + +#endif // QT_NO_LINEEDIT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLINEEDIT_H diff --git a/src/gui/widgets/qlineedit_p.h b/src/gui/widgets/qlineedit_p.h new file mode 100644 index 0000000..532528b --- /dev/null +++ b/src/gui/widgets/qlineedit_p.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLINEEDIT_P_H +#define QLINEEDIT_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 "QtCore/qglobal.h" + +#ifndef QT_NO_LINEEDIT +#include "private/qwidget_p.h" +#include "QtGui/qlineedit.h" +#include "QtGui/qtextlayout.h" +#include "QtGui/qstyleoption.h" +#include "QtCore/qbasictimer.h" +#include "QtGui/qcompleter.h" +#include "QtCore/qpointer.h" +#include "QtGui/qlineedit.h" + +QT_BEGIN_NAMESPACE + +class QLineEditPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QLineEdit) +public: + + QLineEditPrivate() + : cursor(0), preeditCursor(0), cursorTimer(0), frame(1), + cursorVisible(0), hideCursor(false), separator(0), readOnly(0), + dragEnabled(0), contextMenuEnabled(1), echoMode(0), textDirty(0), + selDirty(0), validInput(1), alignment(Qt::AlignLeading | Qt::AlignVCenter), ascent(0), + maxLength(32767), hscroll(0), vscroll(0), lastCursorPos(-1), maskData(0), + modifiedState(0), undoState(0), selstart(0), selend(0), userInput(false), + emitingEditingFinished(false), passwordEchoEditing(false) +#ifndef QT_NO_COMPLETER + , completer(0) +#endif + , leftTextMargin(0), topTextMargin(0), rightTextMargin(0), bottomTextMargin(0) + { + } + + ~QLineEditPrivate() + { + delete [] maskData; + } + void init(const QString&); + + QString text; + int cursor; + int preeditCursor; + int cursorTimer; // -1 for non blinking cursor. + QPoint tripleClick; + QBasicTimer tripleClickTimer; + uint frame : 1; + uint cursorVisible : 1; + uint hideCursor : 1; // used to hide the cursor inside preedit areas + uint separator : 1; + uint readOnly : 1; + uint dragEnabled : 1; + uint contextMenuEnabled : 1; + uint echoMode : 2; + uint textDirty : 1; + uint selDirty : 1; + uint validInput : 1; + uint alignment; + int ascent; + int maxLength; + int hscroll; + int vscroll; + int lastCursorPos; + +#ifndef QT_NO_CONTEXTMENU + QPointer<QAction> selectAllAction; +#endif + + inline void emitCursorPositionChanged(); + bool sendMouseEventToInputContext(QMouseEvent *e); + + void finishChange(int validateFromState = -1, bool update = false, bool edited = true); + + QPointer<QValidator> validator; + struct MaskInputData { + enum Casemode { NoCaseMode, Upper, Lower }; + QChar maskChar; // either the separator char or the inputmask + bool separator; + Casemode caseMode; + }; + QString inputMask; + QChar blank; + MaskInputData *maskData; + inline int nextMaskBlank(int pos) { + int c = findInMask(pos, true, false); + separator |= (c != pos); + return (c != -1 ? c : maxLength); + } + inline int prevMaskBlank(int pos) { + int c = findInMask(pos, false, false); + separator |= (c != pos); + return (c != -1 ? c : 0); + } + + void setCursorVisible(bool visible); + + + // undo/redo handling + enum CommandType { Separator, Insert, Remove, Delete, RemoveSelection, DeleteSelection, SetSelection }; + struct Command { + inline Command() {} + inline Command(CommandType t, int p, QChar c, int ss, int se) : type(t),uc(c),pos(p),selStart(ss),selEnd(se) {} + uint type : 4; + QChar uc; + int pos, selStart, selEnd; + }; + int modifiedState; + int undoState; + QVector<Command> history; + void addCommand(const Command& cmd); + void insert(const QString& s); + void del(bool wasBackspace = false); + void remove(int pos); + + inline void separate() { separator = true; } + void undo(int until = -1); + void redo(); + inline bool isUndoAvailable() const { return !readOnly && undoState; } + inline bool isRedoAvailable() const { return !readOnly && undoState < (int)history.size(); } + + // selection + int selstart, selend; + inline bool allSelected() const { return !text.isEmpty() && selstart == 0 && selend == (int)text.length(); } + inline bool hasSelectedText() const { return !text.isEmpty() && selend > selstart; } + inline void deselect() { selDirty |= (selend > selstart); selstart = selend = 0; } + void removeSelectedText(); +#ifndef QT_NO_CLIPBOARD + void copy(bool clipboard = true) const; +#endif + inline bool inSelection(int x) const + { if (selstart >= selend) return false; + int pos = xToPos(x, QTextLine::CursorOnCharacter); return pos >= selstart && pos < selend; } + + // masking + void parseInputMask(const QString &maskFields); + bool isValidInput(QChar key, QChar mask) const; + bool hasAcceptableInput(const QString &text) const; + QString maskString(uint pos, const QString &str, bool clear = false) const; + QString clearString(uint pos, uint len) const; + QString stripString(const QString &str) const; + int findInMask(int pos, bool forward, bool findSeparator, QChar searchChar = QChar()) const; + + // input methods + bool composeMode() const { return !textLayout.preeditAreaText().isEmpty(); } + + // complex text layout + QTextLayout textLayout; + void updateTextLayout(); + void moveCursor(int pos, bool mark = false); + void setText(const QString& txt, int pos = -1, bool edited = true); + int xToPos(int x, QTextLine::CursorPosition = QTextLine::CursorBetweenCharacters) const; + QRect cursorRect() const; + bool fixup(); + + QRect adjustedContentsRect() const; + +#ifndef QT_NO_DRAGANDDROP + // drag and drop + QPoint dndPos; + QBasicTimer dndTimer; + void drag(); +#endif + + void _q_clipboardChanged(); + void _q_handleWindowActivate(); + void _q_deleteSelected(); + bool userInput; + bool emitingEditingFinished; + +#ifdef QT_KEYPAD_NAVIGATION + QBasicTimer deleteAllTimer; // keypad navigation + QString origText; +#endif + + bool passwordEchoEditing; + void updatePasswordEchoEditing(bool editing); + +#ifndef QT_NO_COMPLETER + QPointer<QCompleter> completer; + void complete(int key = -1); + void _q_completionHighlighted(QString); + bool advanceToEnabledItem(int n); +#endif + + int leftTextMargin; + int topTextMargin; + int rightTextMargin; + int bottomTextMargin; +}; + +#endif // QT_NO_LINEEDIT + +QT_END_NAMESPACE + +#endif // QLINEEDIT_P_H diff --git a/src/gui/widgets/qmaccocoaviewcontainer_mac.h b/src/gui/widgets/qmaccocoaviewcontainer_mac.h new file mode 100644 index 0000000..19763ba --- /dev/null +++ b/src/gui/widgets/qmaccocoaviewcontainer_mac.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOAVIEWCONTAINER_H +#define QCOCOAVIEWCONTAINER_H + +#include <QtGui/QWidget> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QMacCocoaViewContainerPrivate; + +class Q_GUI_EXPORT QMacCocoaViewContainer : public QWidget +{ + Q_OBJECT +public: + QMacCocoaViewContainer(void *cocoaViewToWrap, QWidget *parent = 0); + virtual ~QMacCocoaViewContainer(); + + void setCocoaView(void *cocoaViewToWrap); + void *cocoaView() const; + +private: + Q_DECLARE_PRIVATE(QMacCocoaViewContainer) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOCOAVIEWCONTAINER_H diff --git a/src/gui/widgets/qmaccocoaviewcontainer_mac.mm b/src/gui/widgets/qmaccocoaviewcontainer_mac.mm new file mode 100644 index 0000000..710af6a --- /dev/null +++ b/src/gui/widgets/qmaccocoaviewcontainer_mac.mm @@ -0,0 +1,190 @@ +/**************************************************************************** + ** + ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + ** + ** This file is part of the QtGui module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + ** + ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + ** + ****************************************************************************/ + +#import <Cocoa/Cocoa.h> +#include <private/qwidget_p.h> +#include "qmaccocoaviewcontainer_mac.h" +#include <private/qt_mac_p.h> + +/*! + \class QMacCocoaViewContainer + \since 4.5 + + \brief The QMacCocoaViewContainer class provides a widget for Mac OS X that can be used to wrap arbitrary + Cocoa views (i.e., NSView subclasses) and insert them into Qt hierarchies. + + \ingroup advanced + + While Qt offers a lot of classes for writing your application, Apple's + Cocoa framework offers lots of functionality that is not currently in Qt or + may never end up in Qt. Using QMacCocoaViewContainer, it is possible to put an + arbitrary NSView-derived class from Cocoa and put it in a Qt hierarchy. + Depending on how comfortable you are with using objective-C, you can use + QMacCocoaViewContainer directly, or subclass it to wrap further functionality + of the underlying NSView. + + QMacCocoaViewContainer works regardless if Qt is built against Carbon or + Cocoa. However, QCocoaContainerView requires Mac OS X 10.5 or better to be + used with Carbon. + + It should be also noted that at the low level on Mac OS X, there is a + difference between windows (top-levels) and view (widgets that are inside a + window). For this reason, make sure that the NSView that you are wrapping + doesn't end up as a top-level. The best way to ensure this is to make sure + you always have a parent and not set the parent to 0. + + If you are using QMacCocoaViewContainer as a sub-class and are mixing and + matching objective-C with C++ (a.k.a. objective-C++). It is probably + simpler to have your file end with \tt{.mm} than \tt{.cpp}. Most Apple tools will + correctly identify the source as objective-C++. + + QMacCocoaViewContainer requires knowledge of how Cocoa works, especially in + regard to its reference counting (retain/release) nature. It is noted in + the functions below if there is any change in the reference count. Cocoa + views often generate temporary objects that are released by an autorelease + pool. If this is done outside of a running event loop, it is up to the + developer to provide the autorelease pool. + + The following is a snippet of subclassing QMacCocoaViewContainer to wrap a NSSearchField. + \snippet demos/macmainwindow/macmainwindow.mm 0 + +*/ + +QT_BEGIN_NAMESPACE + +class QMacCocoaViewContainerPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QMacCocoaViewContainer) +public: + NSView *nsview; +#ifndef QT_MAC_USE_COCOA + HIViewRef wrapperView; +#endif + QMacCocoaViewContainerPrivate(); + ~QMacCocoaViewContainerPrivate(); +}; + +QMacCocoaViewContainerPrivate::QMacCocoaViewContainerPrivate() + : nsview(0) +#ifndef QT_MAC_USE_COCOA + , wrapperView(0) +#endif +{ +} + +QMacCocoaViewContainerPrivate::~QMacCocoaViewContainerPrivate() +{ + [nsview release]; +#ifndef QT_MAC_USE_COCOA + if (wrapperView) + CFRelease(wrapperView); +#endif +} + +/*! + \fn QMacCocoaViewContainer::QMacCocoaViewContainer(void *cocoaViewToWrap, QWidget *parent) + + Create a new QMacCocoaViewContainer using the NSView pointer in \a + cocoaViewToWrap with parent, \a parent. QMacCocoaViewContainer will + retain \a cocoaViewToWrap. + + \a cocoaViewToWrap is a void pointer that allows the header to be included + with C++ source. +*/ +QMacCocoaViewContainer::QMacCocoaViewContainer(void *cocoaViewToWrap, QWidget *parent) + : QWidget(*new QMacCocoaViewContainerPrivate, parent, 0) +{ + if (cocoaViewToWrap) + setCocoaView(cocoaViewToWrap); +} + +/*! + Destroy the QMacCocoaViewContainer and release the wrapped view. +*/ +QMacCocoaViewContainer::~QMacCocoaViewContainer() +{ +} + +/*! + Returns the NSView that has been set on this container. The returned view + has been autoreleased, so you will need to retain it if you want to make + use of it. +*/ +void *QMacCocoaViewContainer::cocoaView() const +{ + Q_D(const QMacCocoaViewContainer); + return [[d->nsview retain] autorelease]; +} + +/*! + Sets the NSView to contain to be \a cocoaViewToWrap and retains it. If this + container already had a view set, it will release the previously set view. +*/ +void QMacCocoaViewContainer::setCocoaView(void *cocoaViewToWrap) +{ + Q_D(QMacCocoaViewContainer); + QMacCocoaAutoReleasePool pool; + NSView *view = static_cast<NSView *>(cocoaViewToWrap); + NSView *oldView = d->nsview; + destroy(true, true); + [view retain]; + d->nsview = view; +#ifndef QT_MAC_USE_COCOA + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5) { + qWarning("QMacCocoaViewContainer::setCocoaView: You cannot use this class with Carbon on versions of Mac OS X less than 10.5."); + return; + } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (d->wrapperView) + CFRelease(d->wrapperView); + HICocoaViewCreate(d->nsview, 0, &d->wrapperView); + create(WId(d->wrapperView), false, true); +#endif +#else + create(WId(d->nsview), false, true); +#endif + [oldView release]; +} + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qmacnativewidget_mac.h b/src/gui/widgets/qmacnativewidget_mac.h new file mode 100644 index 0000000..4db65e0 --- /dev/null +++ b/src/gui/widgets/qmacnativewidget_mac.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMACNATIVEWIDGET_H +#define QMACNATIVEWIDGET_H + +#include <QtGui/QWidget> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QMacNativeWidgetPrivate; +class Q_GUI_EXPORT QMacNativeWidget : public QWidget +{ + Q_OBJECT +public: + QMacNativeWidget(void *parentRef = 0); + ~QMacNativeWidget(); + + QSize sizeHint() const; + +protected: + bool event(QEvent *ev); + +private: + Q_DECLARE_PRIVATE_D(QWidget::d_ptr, QMacNativeWidget) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMACNATIVEWIDGET_H diff --git a/src/gui/widgets/qmacnativewidget_mac.mm b/src/gui/widgets/qmacnativewidget_mac.mm new file mode 100644 index 0000000..1bc0430 --- /dev/null +++ b/src/gui/widgets/qmacnativewidget_mac.mm @@ -0,0 +1,136 @@ +/**************************************************************************** + ** + ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + ** + ** This file is part of the QtGui module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + ** + ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + ** + ****************************************************************************/ + +#import <Cocoa/Cocoa.h> +#import <private/qcocoaview_mac_p.h> +#include "qmacnativewidget_mac.h" +#include <private/qwidget_p.h> + +/*! + \class QMacNativeWidget + \since 4.5 + \brief The QMacNativeWidget class provides a widget for Mac OS X that provides a way to put Qt widgets into Carbon + or Cocoa hierarchies depending on how Qt was configured. + + \ingroup advanced + + On Mac OS X, there is a difference between a window and view; + normally expressed as widgets in Qt. Qt makes assumptions about its + parent-child hierarchy that make it complex to put an arbitrary Qt widget + into a hierarchy of "normal" views from Apple frameworks. QMacNativeWidget + bridges the gap between views and windows and makes it possible to put a + hierarchy of Qt widgets into a non-Qt window or view. + + QMacNativeWidget pretends it is a window (i.e. isWindow() will return true), + but it cannot be shown on its own. It needs to be put into a window + when it is created or later through a native call. + + QMacNativeWidget works for either Carbon or Cocoa depending on how Qt was configured. If Qt is + using Carbon, QMacNativeWidget will embed into Carbon hierarchies. If Qt is + using Cocoa, QMacNativeWidget embeds into Cocoa hierarchies. + + Here is an example of putting a QPushButton into a NSWindow: + + \snippet doc/src/snippets/qmacnativewidget/main.mm 0 + + On Carbon, this would do the equivalent: + + \snippet doc/src/snippets/qmacnativewidget/main.mm 1 + + Note that QMacNativeWidget requires knowledge of Carbon or Cocoa. All it + does is get the Qt hierarchy into a window not owned by Qt. It is then up + to the programmer to ensure it is placed correctly in the window and + responds correctly to events. +*/ + +QT_BEGIN_NAMESPACE + +class QMacNativeWidgetPrivate : public QWidgetPrivate +{ +}; + +extern OSViewRef qt_mac_create_widget(QWidget *widget, QWidgetPrivate *widgetPrivate, OSViewRef parent); + + +/*! + Create a QMacNativeWidget with \a parentView as its "superview" (i.e., + parent). The \a parentView is either an HIViewRef if Qt is using Carbon or + a NSView pointer if Qt is using Cocoa. +*/ +QMacNativeWidget::QMacNativeWidget(void *parentView) + : QWidget(*new QMacNativeWidgetPrivate, 0, Qt::Window) +{ + Q_D(QMacNativeWidget); + OSViewRef myView = qt_mac_create_widget(this, d, OSViewRef(parentView)); + + d->topData()->embedded = true; + create(WId(myView), false, false); + setPalette(QPalette(Qt::transparent)); + setAttribute(Qt::WA_SetPalette, false); + setAttribute(Qt::WA_LayoutUsesWidgetRect); +} + +/*! + Destroy the QMacNativeWidget. +*/ +QMacNativeWidget::~QMacNativeWidget() +{ +} + +/*! + \reimp +*/ +QSize QMacNativeWidget::sizeHint() const +{ + return QSize(200, 200); +} +/*! + \reimp +*/ +bool QMacNativeWidget::event(QEvent *ev) +{ + return QWidget::event(ev); +} + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qmainwindow.cpp b/src/gui/widgets/qmainwindow.cpp new file mode 100644 index 0000000..46d6471 --- /dev/null +++ b/src/gui/widgets/qmainwindow.cpp @@ -0,0 +1,1591 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmainwindow.h" +#include "qmainwindowlayout_p.h" + +#ifndef QT_NO_MAINWINDOW + +#include "qdockwidget.h" +#include "qtoolbar.h" + +#include <qapplication.h> +#include <qmenubar.h> +#include <qstatusbar.h> +#include <qevent.h> +#include <qstyle.h> +#include <qdebug.h> +#include <qpainter.h> + +#include <private/qwidget_p.h> +#include "qtoolbar_p.h" +#include "qwidgetanimator_p.h" +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +QT_BEGIN_NAMESPACE +extern OSWindowRef qt_mac_window_for(const QWidget *); // qwidget_mac.cpp +QT_END_NAMESPACE +#endif + +QT_BEGIN_NAMESPACE + +class QMainWindowPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QMainWindow) +public: + inline QMainWindowPrivate() + : layout(0), toolButtonStyle(Qt::ToolButtonIconOnly) +#ifdef Q_WS_MAC + , useHIToolBar(false) +#endif +#if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_CURSOR) + , hasOldCursor(false) , cursorAdjusted(false) +#endif + { } + QMainWindowLayout *layout; + QSize iconSize; + bool explicitIconSize; + Qt::ToolButtonStyle toolButtonStyle; +#ifdef Q_WS_MAC + bool useHIToolBar; +#endif + void init(); + QList<int> hoverSeparator; + QPoint hoverPos; + +#if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_CURSOR) + QCursor separatorCursor(const QList<int> &path) const; + void adjustCursor(const QPoint &pos); + QCursor oldCursor; + uint hasOldCursor : 1; + uint cursorAdjusted : 1; +#endif +}; + +void QMainWindowPrivate::init() +{ + Q_Q(QMainWindow); + layout = new QMainWindowLayout(q); + const int metric = q->style()->pixelMetric(QStyle::PM_ToolBarIconSize, 0, q); + iconSize = QSize(metric, metric); + explicitIconSize = false; + + q->setAttribute(Qt::WA_Hover); +} + +/* + The Main Window: + + +----------------------------------------------------------+ + | Menu Bar | + +----------------------------------------------------------+ + | Tool Bar Area | + | +--------------------------------------------------+ | + | | Dock Window Area | | + | | +------------------------------------------+ | | + | | | | | | + | | | Central Widget | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + | | +------------------------------------------+ | | + | | | | + | +--------------------------------------------------+ | + | | + +----------------------------------------------------------+ + | Status Bar | + +----------------------------------------------------------+ + +*/ + +/*! + \class QMainWindow + \brief The QMainWindow class provides a main application + window. + \ingroup application + \mainclass + + \tableofcontents + + \section1 Qt Main Window Framework + + A main window provides a framework for building an + application's user interface. Qt has QMainWindow and its \l{Main + Window and Related Classes}{related classes} for main window + management. QMainWindow has its own layout to which you can add + \l{QToolBar}s, \l{QDockWidget}s, a + QMenuBar, and a QStatusBar. The layout has a center area that can + be occupied by any kind of widget. You can see an image of the + layout below. + + \image mainwindowlayout.png + + \note Creating a main window without a central widget is not supported. + You must have a central widget even if it is just a placeholder. + + \section1 Creating Main Window Components + + A central widget will typically be a standard Qt widget such + as a QTextEdit or a QGraphicsView. Custom widgets can also be + used for advanced applications. You set the central widget with \c + setCentralWidget(). + + Main windows have either a single (SDI) or multiple (MDI) + document interface. You create MDI applications in Qt by using a + QMdiArea as the central widget. + + We will now examine each of the other widgets that can be + added to a main window. We give examples on how to create and add + them. + + \section2 Creating Menus + + Qt implements menus in QMenu and QMainWindow keeps them in a + QMenuBar. \l{QAction}{QAction}s are added to the menus, which + display them as menu items. + + You can add new menus to the main window's menu bar by calling + \c menuBar(), which returns the QMenuBar for the window, and then + add a menu with QMenuBar::addMenu(). + + QMainWindow comes with a default menu bar, but you can also + set one yourself with \c setMenuBar(). If you wish to implement a + custom menu bar (i.e., not use the QMenuBar widget), you can set it + with \c setMenuWidget(). + + An example of how to create menus follows: + + \snippet examples/mainwindows/application/mainwindow.cpp 26 + + The \c createPopupMenu() function creates popup menus when the + main window receives context menu events. The default + implementation generates a menu with the checkable actions from + the dock widgets and toolbars. You can reimplement \c + createPopupMenu() for a custom menu. + + \section2 Creating Toolbars + + Toolbars are implemented in the QToolBar class. You add a + toolbar to a main window with \c addToolBar(). + + You control the initial position of toolbars by assigning them + to a specific Qt::ToolBarArea. You can split an area by inserting + a toolbar break - think of this as a line break in text editing - + with \c addToolBarBreak() or \c insertToolBarBreak(). You can also + restrict placement by the user with QToolBar::setAllowedAreas() + and QToolBar::setMovable(). + + The size of toolbar icons can be retrieved with \c iconSize(). + The sizes are platform dependent; you can set a fixed size with \c + setIconSize(). You can alter the appearance of all tool buttons in + the toolbars with \c setToolButtonStyle(). + + An example of toolbar creation follows: + + \snippet examples/mainwindows/application/mainwindow.cpp 29 + + \section2 Creating Dock Widgets + + Dock widgets are implemented in the QDockWidget class. A dock + widget is a window that can be docked into the main window. You + add dock widgets to a main window with \c addDockWidget(). + + There are four dock widget areas as given by the + Qt::DockWidgetArea enum: left, right, top, and bottom. You can + specify which dock widget area that should occupy the corners + where the areas overlap with \c setCorner(). By default + each area can only contain one row (vertical or horizontal) of + dock widgets, but if you enable nesting with \c + setDockNestingEnabled(), dock widgets can be added in either + direction. + + Two dock widgets may also be stacked on top of each other. A + QTabBar is then used to select which of the widgets that should be + displayed. + + We give an example of how to create and add dock widgets to a + main window: + + \snippet doc/src/snippets/mainwindowsnippet.cpp 0 + + \section2 The Status Bar + + You can set a status bar with \c setStatusBar(), but one is + created the first time \c statusBar() (which returns the main + window's status bar) is called. See QStatusBar for information on + how to use it. + + \section1 Storing State + + QMainWindow can store the state of its layout with \c + saveState(); it can later be retrieved with \c restoreState(). It + is the position and size (relative to the size of the main window) + of the toolbars and dock widgets that are stored. + + \sa QMenuBar, QToolBar, QStatusBar, QDockWidget, {Application + Example}, {Dock Widgets Example}, {MDI Example}, {SDI Example}, + {Menus Example} +*/ + +/*! + \fn void QMainWindow::iconSizeChanged(const QSize &iconSize) + + This signal is emitted when the size of the icons used in the + window is changed. The new icon size is passed in \a iconSize. + + You can connect this signal to other components to help maintain + a consistent appearance for your application. + + \sa setIconSize() +*/ + +/*! + \fn void QMainWindow::toolButtonStyleChanged(Qt::ToolButtonStyle toolButtonStyle) + + This signal is emitted when the style used for tool buttons in the + window is changed. The new style is passed in \a toolButtonStyle. + + You can connect this signal to other components to help maintain + a consistent appearance for your application. + + \sa setToolButtonStyle() +*/ + +/*! + Constructs a QMainWindow with the given \a parent and the specified + widget \a flags. + + QMainWindow sets the Qt::Window flag itself, and will hence + always be created as a top-level widget. + */ +QMainWindow::QMainWindow(QWidget *parent, Qt::WindowFlags flags) + : QWidget(*(new QMainWindowPrivate()), parent, flags | Qt::Window) +{ + d_func()->init(); +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + Constructs a QMainWindow with the given \a parent, \a name, and + with the specified widget \a flags. + */ +QMainWindow::QMainWindow(QWidget *parent, const char *name, Qt::WindowFlags flags) + : QWidget(*(new QMainWindowPrivate()), parent, flags | Qt::WType_TopLevel) +{ + setObjectName(QString::fromAscii(name)); + d_func()->init(); +} +#endif + +/*! + Destroys the main window. + */ +QMainWindow::~QMainWindow() +{ } + +/*! \property QMainWindow::iconSize + \brief size of toolbar icons in this mainwindow. + + The default is the default tool bar icon size of the GUI style. + Note that the icons used must be at least of this size as the + icons are only scaled down. +*/ + +/*! + \property QMainWindow::dockOptions + \brief the docking behavior of QMainWindow + \since 4.3 + + The default value is AnimatedDocks | AllowTabbedDocks. +*/ + +/*! + \enum QMainWindow::DockOption + \since 4.3 + + This enum contains flags that specify the docking behavior of QMainWindow. + + \value AnimatedDocks Identical to the \l animated property. + + \value AllowNestedDocks Identical to the \l dockNestingEnabled property. + + \value AllowTabbedDocks The user can drop one dock widget "on top" of + another. The two widgets are stacked and a tab + bar appears for selecting which one is visible. + + \value ForceTabbedDocks Each dock area contains a single stack of tabbed + dock widgets. In other words, dock widgets cannot + be placed next to each other in a dock area. If + this option is set, AllowNestedDocks has no effect. + + \value VerticalTabs The two vertical dock areas on the sides of the + main window show their tabs vertically. If this + option is not set, all dock areas show their tabs + at the bottom. Implies AllowTabbedDocks. See also + \l setTabPosition(). + + These options only control how dock widgets may be dropped in a QMainWindow. + They do not re-arrange the dock widgets to conform with the specified + options. For this reason they should be set before any dock widgets + are added to the main window. Exceptions to this are the AnimatedDocks and + VerticalTabs options, which may be set at any time. +*/ + +void QMainWindow::setDockOptions(DockOptions opt) +{ + Q_D(QMainWindow); + d->layout->setDockOptions(opt); +} + +QMainWindow::DockOptions QMainWindow::dockOptions() const +{ + Q_D(const QMainWindow); + return d->layout->dockOptions; +} + +QSize QMainWindow::iconSize() const +{ return d_func()->iconSize; } + +void QMainWindow::setIconSize(const QSize &iconSize) +{ + Q_D(QMainWindow); + QSize sz = iconSize; + if (!sz.isValid()) { + const int metric = style()->pixelMetric(QStyle::PM_ToolBarIconSize, 0, this); + sz = QSize(metric, metric); + } + if (d->iconSize != sz) { + d->iconSize = sz; + emit iconSizeChanged(d->iconSize); + } + d->explicitIconSize = iconSize.isValid(); +} + +/*! \property QMainWindow::toolButtonStyle + \brief style of toolbar buttons in this mainwindow. + + The default is Qt::ToolButtonIconOnly. +*/ + +Qt::ToolButtonStyle QMainWindow::toolButtonStyle() const +{ return d_func()->toolButtonStyle; } + +void QMainWindow::setToolButtonStyle(Qt::ToolButtonStyle toolButtonStyle) +{ + Q_D(QMainWindow); + if (d->toolButtonStyle == toolButtonStyle) + return; + d->toolButtonStyle = toolButtonStyle; + emit toolButtonStyleChanged(d->toolButtonStyle); +} + +#ifndef QT_NO_MENUBAR +/*! + Returns the menu bar for the main window. This function creates + and returns an empty menu bar if the menu bar does not exist. + + If you want all windows in a Mac application to share one menu + bar, don't use this function to create it, because the menu bar + created here will have this QMainWindow as its parent. Instead, + you must create a menu bar that does not have a parent, which you + can then share among all the Mac windows. Create a parent-less + menu bar this way: + + \snippet doc/src/snippets/code/src_gui_widgets_qmenubar.cpp 1 + + \sa setMenuBar() +*/ +QMenuBar *QMainWindow::menuBar() const +{ + QMenuBar *menuBar = qobject_cast<QMenuBar *>(d_func()->layout->menuBar()); + if (!menuBar) { + QMainWindow *self = const_cast<QMainWindow *>(this); + menuBar = new QMenuBar(self); + self->setMenuBar(menuBar); + } + return menuBar; +} + +/*! + Sets the menu bar for the main window to \a menuBar. + + Note: QMainWindow takes ownership of the \a menuBar pointer and + deletes it at the appropriate time. + + \sa menuBar() +*/ +void QMainWindow::setMenuBar(QMenuBar *menuBar) +{ + Q_D(QMainWindow); + if (d->layout->menuBar() && d->layout->menuBar() != menuBar) { + // Reparent corner widgets before we delete the old menu bar. + QMenuBar *oldMenuBar = qobject_cast<QMenuBar *>(d->layout->menuBar()); + if (menuBar) { + // TopLeftCorner widget. + QWidget *cornerWidget = oldMenuBar->cornerWidget(Qt::TopLeftCorner); + if (cornerWidget) + menuBar->setCornerWidget(cornerWidget, Qt::TopLeftCorner); + // TopRightCorner widget. + cornerWidget = oldMenuBar->cornerWidget(Qt::TopRightCorner); + if (cornerWidget) + menuBar->setCornerWidget(cornerWidget, Qt::TopRightCorner); + } + oldMenuBar->hide(); + oldMenuBar->deleteLater(); + } +#ifdef Q_OS_WINCE + if (menuBar->size().height() > 0) +#endif + d->layout->setMenuBar(menuBar); +} + +/*! + \since 4.2 + + Returns the menu bar for the main window. This function returns + null if a menu bar hasn't been constructed yet. +*/ +QWidget *QMainWindow::menuWidget() const +{ + QWidget *menuBar = d_func()->layout->menuBar(); + return menuBar; +} + +/*! + \since 4.2 + + Sets the menu bar for the main window to \a menuBar. + + QMainWindow takes ownership of the \a menuBar pointer and + deletes it at the appropriate time. +*/ +void QMainWindow::setMenuWidget(QWidget *menuBar) +{ + Q_D(QMainWindow); + if (d->layout->menuBar() && d->layout->menuBar() != menuBar) { + d->layout->menuBar()->hide(); + d->layout->menuBar()->deleteLater(); + } + d->layout->setMenuBar(menuBar); +} +#endif // QT_NO_MENUBAR + +#ifndef QT_NO_STATUSBAR +/*! + Returns the status bar for the main window. This function creates + and returns an empty status bar if the status bar does not exist. + + \sa setStatusBar() +*/ +QStatusBar *QMainWindow::statusBar() const +{ + QStatusBar *statusbar = d_func()->layout->statusBar(); + if (!statusbar) { + QMainWindow *self = const_cast<QMainWindow *>(this); + statusbar = new QStatusBar(self); + statusbar->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + self->setStatusBar(statusbar); + } + return statusbar; +} + +/*! + Sets the status bar for the main window to \a statusbar. + + Setting the status bar to 0 will remove it from the main window. + Note that QMainWindow takes ownership of the \a statusbar pointer + and deletes it at the appropriate time. + + \sa statusBar() +*/ +void QMainWindow::setStatusBar(QStatusBar *statusbar) +{ + Q_D(QMainWindow); + if (d->layout->statusBar() && d->layout->statusBar() != statusbar) { + d->layout->statusBar()->hide(); + d->layout->statusBar()->deleteLater(); + } + d->layout->setStatusBar(statusbar); +} +#endif // QT_NO_STATUSBAR + +/*! + Returns the central widget for the main window. This function + returns zero if the central widget has not been set. + + \sa setCentralWidget() +*/ +QWidget *QMainWindow::centralWidget() const +{ return d_func()->layout->centralWidget(); } + +/*! + Sets the given \a widget to be the main window's central widget. + + Note: QMainWindow takes ownership of the \a widget pointer and + deletes it at the appropriate time. + + \sa centralWidget() +*/ +void QMainWindow::setCentralWidget(QWidget *widget) +{ + Q_D(QMainWindow); + if (d->layout->centralWidget() && d->layout->centralWidget() != widget) { + d->layout->centralWidget()->hide(); + d->layout->centralWidget()->deleteLater(); + } + d->layout->setCentralWidget(widget); +} + +#ifndef QT_NO_DOCKWIDGET +/*! + Sets the given dock widget \a area to occupy the specified \a + corner. + + \sa corner() +*/ +void QMainWindow::setCorner(Qt::Corner corner, Qt::DockWidgetArea area) +{ + bool valid = false; + switch (corner) { + case Qt::TopLeftCorner: + valid = (area == Qt::TopDockWidgetArea || area == Qt::LeftDockWidgetArea); + break; + case Qt::TopRightCorner: + valid = (area == Qt::TopDockWidgetArea || area == Qt::RightDockWidgetArea); + break; + case Qt::BottomLeftCorner: + valid = (area == Qt::BottomDockWidgetArea || area == Qt::LeftDockWidgetArea); + break; + case Qt::BottomRightCorner: + valid = (area == Qt::BottomDockWidgetArea || area == Qt::RightDockWidgetArea); + break; + } + if (!valid) + qWarning("QMainWindow::setCorner(): 'area' is not valid for 'corner'"); + else + d_func()->layout->setCorner(corner, area); +} + +/*! + Returns the dock widget area that occupies the specified \a + corner. + + \sa setCorner() +*/ +Qt::DockWidgetArea QMainWindow::corner(Qt::Corner corner) const +{ return d_func()->layout->corner(corner); } +#endif + +#ifndef QT_NO_TOOLBAR + +static bool checkToolBarArea(Qt::ToolBarArea area, const char *where) +{ + switch (area) { + case Qt::LeftToolBarArea: + case Qt::RightToolBarArea: + case Qt::TopToolBarArea: + case Qt::BottomToolBarArea: + return true; + default: + break; + } + qWarning("%s: invalid 'area' argument", where); + return false; +} + +/*! + Adds a toolbar break to the given \a area after all the other + objects that are present. +*/ +void QMainWindow::addToolBarBreak(Qt::ToolBarArea area) +{ + if (!checkToolBarArea(area, "QMainWindow::addToolBarBreak")) + return; + d_func()->layout->addToolBarBreak(area); +} + +/*! + Inserts a toolbar break before the toolbar specified by \a before. +*/ +void QMainWindow::insertToolBarBreak(QToolBar *before) +{ d_func()->layout->insertToolBarBreak(before); } + +/*! + Removes a toolbar break previously inserted before the toolbar specified by \a before. +*/ + +void QMainWindow::removeToolBarBreak(QToolBar *before) +{ + Q_D(QMainWindow); + d->layout->removeToolBarBreak(before); +} + +/*! + Adds the \a toolbar into the specified \a area in this main + window. The \a toolbar is placed at the end of the current tool + bar block (i.e. line). If the main window already manages \a toolbar + then it will only move the toolbar to \a area. + + \sa insertToolBar() addToolBarBreak() insertToolBarBreak() +*/ +void QMainWindow::addToolBar(Qt::ToolBarArea area, QToolBar *toolbar) +{ + if (!checkToolBarArea(area, "QMainWindow::addToolBar")) + return; + + Q_D(QMainWindow); + + disconnect(this, SIGNAL(iconSizeChanged(QSize)), + toolbar, SLOT(_q_updateIconSize(QSize))); + disconnect(this, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), + toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle))); + + if(toolbar->d_func()->state && toolbar->d_func()->state->dragging) { + //removing a toolbar which is dragging will cause crash +#ifndef QT_NO_DOCKWIDGET + bool animated = isAnimated(); + setAnimated(false); +#endif + toolbar->d_func()->endDrag(); +#ifndef QT_NO_DOCKWIDGET + setAnimated(animated); +#endif + } + + if (!d->layout->usesHIToolBar(toolbar)) { + d->layout->removeWidget(toolbar); + } else { + d->layout->removeToolBar(toolbar); + } + + toolbar->d_func()->_q_updateIconSize(d->iconSize); + toolbar->d_func()->_q_updateToolButtonStyle(d->toolButtonStyle); + connect(this, SIGNAL(iconSizeChanged(QSize)), + toolbar, SLOT(_q_updateIconSize(QSize))); + connect(this, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), + toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle))); + + d->layout->addToolBar(area, toolbar); +} + +/*! \overload + Equivalent of calling addToolBar(Qt::TopToolBarArea, \a toolbar) +*/ +void QMainWindow::addToolBar(QToolBar *toolbar) +{ addToolBar(Qt::TopToolBarArea, toolbar); } + +/*! + \overload + + Creates a QToolBar object, setting its window title to \a title, + and inserts it into the top toolbar area. + + \sa setWindowTitle() +*/ +QToolBar *QMainWindow::addToolBar(const QString &title) +{ + QToolBar *toolBar = new QToolBar(this); + toolBar->setWindowTitle(title); + addToolBar(toolBar); + return toolBar; +} + +/*! + Inserts the \a toolbar into the area occupied by the \a before toolbar + so that it appears before it. For example, in normal left-to-right + layout operation, this means that \a toolbar will appear to the left + of the toolbar specified by \a before in a horizontal toolbar area. + + \sa insertToolBarBreak() addToolBar() addToolBarBreak() +*/ +void QMainWindow::insertToolBar(QToolBar *before, QToolBar *toolbar) +{ + Q_D(QMainWindow); + + d->layout->removeToolBar(toolbar); + + toolbar->d_func()->_q_updateIconSize(d->iconSize); + toolbar->d_func()->_q_updateToolButtonStyle(d->toolButtonStyle); + connect(this, SIGNAL(iconSizeChanged(QSize)), + toolbar, SLOT(_q_updateIconSize(QSize))); + connect(this, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), + toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle))); + + d->layout->insertToolBar(before, toolbar); +} + +/*! + Removes the \a toolbar from the main window layout and hides + it. Note that the \a toolbar is \e not deleted. +*/ +void QMainWindow::removeToolBar(QToolBar *toolbar) +{ + if (toolbar) { + d_func()->layout->removeToolBar(toolbar); + toolbar->hide(); + } +} + +/*! + Returns the Qt::ToolBarArea for \a toolbar. If \a toolbar has not + been added to the main window, this function returns \c + Qt::NoToolBarArea. + + \sa addToolBar() addToolBarBreak() Qt::ToolBarArea +*/ +Qt::ToolBarArea QMainWindow::toolBarArea(QToolBar *toolbar) const +{ return d_func()->layout->toolBarArea(toolbar); } + +/*! + + Returns whether there is a toolbar + break before the \a toolbar. + + \sa addToolBarBreak(), insertToolBarBreak() +*/ +bool QMainWindow::toolBarBreak(QToolBar *toolbar) const +{ + return d_func()->layout->toolBarBreak(toolbar); +} + +#endif // QT_NO_TOOLBAR + +#ifndef QT_NO_DOCKWIDGET + +/*! \property QMainWindow::animated + \brief whether manipulating dock widgets and tool bars is animated + \since 4.2 + + When a dock widget or tool bar is dragged over the + main window, the main window adjusts its contents + to indicate where the dock widget or tool bar will + be docked if it is dropped. Setting this property + causes QMainWindow to move its contents in a smooth + animation. Clearing this property causes the contents + to snap into their new positions. + + By default, this property is set. It may be cleared if + the main window contains widgets which are slow at resizing + or repainting themselves. + + Setting this property is identical to setting the AnimatedDocks + option using setDockOptions(). +*/ + +bool QMainWindow::isAnimated() const +{ + Q_D(const QMainWindow); + return d->layout->dockOptions & AnimatedDocks; +} + +void QMainWindow::setAnimated(bool enabled) +{ + Q_D(QMainWindow); + + DockOptions opts = d->layout->dockOptions; + if (enabled) + opts |= AnimatedDocks; + else + opts &= ~AnimatedDocks; + + d->layout->setDockOptions(opts); +} + +/*! \property QMainWindow::dockNestingEnabled + \brief whether docks can be nested + \since 4.2 + + If this property is false, dock areas can only contain a single row + (horizontal or vertical) of dock widgets. If this property is true, + the area occupied by a dock widget can be split in either direction to contain + more dock widgets. + + Dock nesting is only necessary in applications that contain a lot of + dock widgets. It gives the user greater freedom in organizing their + main window. However, dock nesting leads to more complex + (and less intuitive) behavior when a dock widget is dragged over the + main window, since there are more ways in which a dropped dock widget + may be placed in the dock area. + + Setting this property is identical to setting the AllowNestedDocks option + using setDockOptions(). +*/ + +bool QMainWindow::isDockNestingEnabled() const +{ + Q_D(const QMainWindow); + return d->layout->dockOptions & AllowNestedDocks; +} + +void QMainWindow::setDockNestingEnabled(bool enabled) +{ + Q_D(QMainWindow); + + DockOptions opts = d->layout->dockOptions; + if (enabled) + opts |= AllowNestedDocks; + else + opts &= ~AllowNestedDocks; + + d->layout->setDockOptions(opts); +} + +#if 0 +/*! \property QMainWindow::verticalTabsEnabled + \brief whether left and right dock areas use vertical tabs + \since 4.2 + + If this property is set to false, dock areas containing tabbed dock widgets + display horizontal tabs, simmilar to Visual Studio. + + If this property is set to true, then the right and left dock areas display vertical + tabs, simmilar to KDevelop. + + This property should be set before any dock widgets are added to the main window. +*/ + +bool QMainWindow::verticalTabsEnabled() const +{ + return d_func()->layout->verticalTabsEnabled(); +} + +void QMainWindow::setVerticalTabsEnabled(bool enabled) +{ + d_func()->layout->setVerticalTabsEnabled(enabled); +} +#endif + +static bool checkDockWidgetArea(Qt::DockWidgetArea area, const char *where) +{ + switch (area) { + case Qt::LeftDockWidgetArea: + case Qt::RightDockWidgetArea: + case Qt::TopDockWidgetArea: + case Qt::BottomDockWidgetArea: + return true; + default: + break; + } + qWarning("%s: invalid 'area' argument", where); + return false; +} + +#ifndef QT_NO_TABBAR +/*! + \property QMainWindow::documentMode + \brief whether the tab bar for tabbed dockwidgets is set to document mode. + \since 4.5 + + The default is false. + + \sa QTabBar::documentMode +*/ +bool QMainWindow::documentMode() const +{ + return d_func()->layout->documentMode(); +} + +void QMainWindow::setDocumentMode(bool enabled) +{ + d_func()->layout->setDocumentMode(enabled); +} +#endif // QT_NO_TABBAR + +#ifndef QT_NO_TABWIDGET +/*! + \property QMainWindow::tabShape + \brief the tab shape used for tabbed dock widgets. + \since 4.5 + + The default is \l QTabWidget::Rounded. + + \sa setTabPosition() +*/ +QTabWidget::TabShape QMainWindow::tabShape() const +{ + return d_func()->layout->tabShape(); +} + +void QMainWindow::setTabShape(QTabWidget::TabShape tabShape) +{ + d_func()->layout->setTabShape(tabShape); +} + +/*! + \since 4.5 + + Returns the tab position for \a area. + + \note The \l VerticalTabs dock option overrides the tab positions returned + by this function. + + \sa setTabPosition(), tabShape() +*/ +QTabWidget::TabPosition QMainWindow::tabPosition(Qt::DockWidgetArea area) const +{ + if (!checkDockWidgetArea(area, "QMainWindow::tabPosition")) + return QTabWidget::South; + return d_func()->layout->tabPosition(area); +} + +/*! + \since 4.5 + + Sets the tab position for the given dock widget \a areas to the specified + \a tabPosition. By default, all dock areas show their tabs at the bottom. + + \note The \l VerticalTabs dock option overrides the tab positions set by + this method. + + \sa tabPosition(), setTabShape() +*/ +void QMainWindow::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition) +{ + d_func()->layout->setTabPosition(areas, tabPosition); +} +#endif // QT_NO_TABWIDGET + +/*! + Adds the given \a dockwidget to the specified \a area. +*/ +void QMainWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget) +{ + if (!checkDockWidgetArea(area, "QMainWindow::addDockWidget")) + return; + + Qt::Orientation orientation = Qt::Vertical; + switch (area) { + case Qt::TopDockWidgetArea: + case Qt::BottomDockWidgetArea: + orientation = Qt::Horizontal; + break; + default: + break; + } + d_func()->layout->removeWidget(dockwidget); // in case it was already in here + addDockWidget(area, dockwidget, orientation); + +#ifdef Q_WS_MAC //drawer support + QMacCocoaAutoReleasePool pool; + extern bool qt_mac_is_macdrawer(const QWidget *); //qwidget_mac.cpp + if (qt_mac_is_macdrawer(dockwidget)) { + extern bool qt_mac_set_drawer_preferred_edge(QWidget *, Qt::DockWidgetArea); //qwidget_mac.cpp + window()->createWinId(); + dockwidget->window()->createWinId(); + qt_mac_set_drawer_preferred_edge(dockwidget, area); + if (dockwidget->isVisible()) { + dockwidget->hide(); + dockwidget->show(); + } + } +#endif +} + +/*! + Restores the state of \a dockwidget if it is created after the call + to restoreState(). Returns true if the state was restored; otherwise + returns false. +*/ + +bool QMainWindow::restoreDockWidget(QDockWidget *dockwidget) +{ + return d_func()->layout->restoreDockWidget(dockwidget); +} + +/*! + Adds \a dockwidget into the given \a area in the direction + specified by the \a orientation. +*/ +void QMainWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, + Qt::Orientation orientation) +{ + if (!checkDockWidgetArea(area, "QMainWindow::addDockWidget")) + return; + + // add a window to an area, placing done relative to the previous + d_func()->layout->addDockWidget(area, dockwidget, orientation); +} + +/*! + \fn void QMainWindow::splitDockWidget(QDockWidget *first, QDockWidget *second, Qt::Orientation orientation) + + Splits the space covered by the \a first dock widget into two parts, + moves the \a first dock widget into the first part, and moves the + \a second dock widget into the second part. + + The \a orientation specifies how the space is divided: A Qt::Horizontal + split places the second dock widget to the right of the first; a + Qt::Vertical split places the second dock widget below the first. + + \e Note: if \a first is currently in a tabbed docked area, \a second will + be added as a new tab, not as a neighbor of \a first. This is because a + single tab can contain only one dock widget. + + \e Note: The Qt::LayoutDirection influences the order of the dock widgets + in the two parts of the divided area. When right-to-left layout direction + is enabled, the placing of the dock widgets will be reversed. + + \sa tabifyDockWidget(), addDockWidget(), removeDockWidget() +*/ +void QMainWindow::splitDockWidget(QDockWidget *after, QDockWidget *dockwidget, + Qt::Orientation orientation) +{ + d_func()->layout->splitDockWidget(after, dockwidget, orientation); +} + +/*! + \fn void QMainWindow::tabifyDockWidget(QDockWidget *first, QDockWidget *second) + + Moves \a second dock widget on top of \a first dock widget, creating a tabbed + docked area in the main window. + + \sa tabifiedDockWidgets() +*/ +void QMainWindow::tabifyDockWidget(QDockWidget *first, QDockWidget *second) +{ + d_func()->layout->tabifyDockWidget(first, second); +} + + +/*! + \fn QList<QDockWidget*> QMainWindow::tabifiedDockWidgets(QDockWidget *dockwidget) const + + Returns the dock widgets that are tabified together with \a dockwidget. + + \since 4.5 + \sa tabifyDockWidget() +*/ + +QList<QDockWidget*> QMainWindow::tabifiedDockWidgets(QDockWidget *dockwidget) const +{ + QList<QDockWidget*> ret; +#if defined(QT_NO_TABBAR) + Q_UNUSED(dockwidget); +#else + const QDockAreaLayoutInfo *info = d_func()->layout->layoutState.dockAreaLayout.info(dockwidget); + if (info && info->tabbed && info->tabBar) { + for(int i = 0; i < info->item_list.count(); ++i) { + const QDockAreaLayoutItem &item = info->item_list.at(i); + if (item.widgetItem) { + if (QDockWidget *dock = qobject_cast<QDockWidget*>(item.widgetItem->widget())) { + if (dock != dockwidget) { + ret += dock; + } + } + } + } + } +#endif + return ret; +} + + +/*! + Removes the \a dockwidget from the main window layout and hides + it. Note that the \a dockwidget is \e not deleted. +*/ +void QMainWindow::removeDockWidget(QDockWidget *dockwidget) +{ + if (dockwidget) { + d_func()->layout->removeWidget(dockwidget); + dockwidget->hide(); + } +} + +/*! + Returns the Qt::DockWidgetArea for \a dockwidget. If \a dockwidget + has not been added to the main window, this function returns \c + Qt::NoDockWidgetArea. + + \sa addDockWidget() splitDockWidget() Qt::DockWidgetArea +*/ +Qt::DockWidgetArea QMainWindow::dockWidgetArea(QDockWidget *dockwidget) const +{ return d_func()->layout->dockWidgetArea(dockwidget); } + +#endif // QT_NO_DOCKWIDGET + +/*! + Saves the current state of this mainwindow's toolbars and + dockwidgets. The \a version number is stored as part of the data. + + The \link QObject::objectName objectName\endlink property is used + to identify each QToolBar and QDockWidget. You should make sure + that this property is unique for each QToolBar and QDockWidget you + add to the QMainWindow + + To restore the saved state, pass the return value and \a version + number to restoreState(). + + \sa restoreState(), QWidget::saveGeometry(), QWidget::restoreGeometry() +*/ +QByteArray QMainWindow::saveState(int version) const +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << QMainWindowLayout::VersionMarker; + stream << version; + d_func()->layout->saveState(stream); + return data; +} + +/*! + Restores the \a state of this mainwindow's toolbars and + dockwidgets. The \a version number is compared with that stored + in \a state. If they do not match, the mainwindow's state is left + unchanged, and this function returns \c false; otherwise, the state + is restored, and this function returns \c true. + + \sa saveState(), QWidget::saveGeometry(), QWidget::restoreGeometry() +*/ +bool QMainWindow::restoreState(const QByteArray &state, int version) +{ + if (state.isEmpty()) + return false; + QByteArray sd = state; + QDataStream stream(&sd, QIODevice::ReadOnly); + int marker, v; + stream >> marker; + stream >> v; + if (stream.status() != QDataStream::Ok || marker != QMainWindowLayout::VersionMarker || v != version) + return false; + bool restored = d_func()->layout->restoreState(stream); + return restored; +} + +#if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_CURSOR) +QCursor QMainWindowPrivate::separatorCursor(const QList<int> &path) const +{ + QDockAreaLayoutInfo *info = layout->layoutState.dockAreaLayout.info(path); + Q_ASSERT(info != 0); + if (path.size() == 1) { // is this the "top-level" separator which separates a dock area + // from the central widget? + switch (path.first()) { + case QInternal::LeftDock: + case QInternal::RightDock: + return Qt::SplitHCursor; + case QInternal::TopDock: + case QInternal::BottomDock: + return Qt::SplitVCursor; + default: + break; + } + } + + // no, it's a splitter inside a dock area, separating two dock widgets + + return info->o == Qt::Horizontal + ? Qt::SplitHCursor : Qt::SplitVCursor; +} + +void QMainWindowPrivate::adjustCursor(const QPoint &pos) +{ + Q_Q(QMainWindow); + + hoverPos = pos; + + if (pos == QPoint(0, 0)) { + if (!hoverSeparator.isEmpty()) + q->update(layout->layoutState.dockAreaLayout.separatorRect(hoverSeparator)); + hoverSeparator.clear(); + + if (cursorAdjusted) { + cursorAdjusted = false; + if (hasOldCursor) + q->setCursor(oldCursor); + else + q->unsetCursor(); + } + } else { + QList<int> pathToSeparator + = layout->layoutState.dockAreaLayout.findSeparator(pos); + + if (pathToSeparator != hoverSeparator) { + if (!hoverSeparator.isEmpty()) + q->update(layout->layoutState.dockAreaLayout.separatorRect(hoverSeparator)); + + hoverSeparator = pathToSeparator; + + if (hoverSeparator.isEmpty()) { + if (cursorAdjusted) { + cursorAdjusted = false; + if (hasOldCursor) + q->setCursor(oldCursor); + else + q->unsetCursor(); + } + } else { + q->update(layout->layoutState.dockAreaLayout.separatorRect(hoverSeparator)); + if (!cursorAdjusted) { + oldCursor = q->cursor(); + hasOldCursor = q->testAttribute(Qt::WA_SetCursor); + } + QCursor cursor = separatorCursor(hoverSeparator); + cursorAdjusted = false; //to not reset the oldCursor in event(CursorChange) + q->setCursor(cursor); + cursorAdjusted = true; + } + } + } +} +#endif + +/*! \reimp */ +bool QMainWindow::event(QEvent *event) +{ + Q_D(QMainWindow); + switch (event->type()) { + +#ifndef QT_NO_DOCKWIDGET + case QEvent::Paint: { + QPainter p(this); + QRegion r = static_cast<QPaintEvent*>(event)->region(); + d->layout->layoutState.dockAreaLayout.paintSeparators(&p, this, r, d->hoverPos); + break; + } + +#ifndef QT_NO_CURSOR + case QEvent::HoverMove: { + d->adjustCursor(static_cast<QHoverEvent*>(event)->pos()); + break; + } + + // We don't want QWidget to call update() on the entire QMainWindow + // on HoverEnter and HoverLeave, hence accept the event (return true). + case QEvent::HoverEnter: + return true; + case QEvent::HoverLeave: + d->adjustCursor(QPoint(0, 0)); + return true; + case QEvent::ShortcutOverride: // when a menu pops up + d->adjustCursor(QPoint(0, 0)); + break; +#endif // QT_NO_CURSOR + + case QEvent::MouseButtonPress: { + QMouseEvent *e = static_cast<QMouseEvent*>(event); + if (e->button() == Qt::LeftButton && d->layout->startSeparatorMove(e->pos())) { + // The click was on a separator, eat this event + e->accept(); + return true; + } + break; + } + + case QEvent::MouseMove: { + QMouseEvent *e = static_cast<QMouseEvent*>(event); + +#ifndef QT_NO_CURSOR + d->adjustCursor(e->pos()); +#endif + if (e->buttons() & Qt::LeftButton) { + if (d->layout->separatorMove(e->pos())) { + // We're moving a separator, eat this event + e->accept(); + return true; + } + } + + break; + } + + case QEvent::MouseButtonRelease: { + QMouseEvent *e = static_cast<QMouseEvent*>(event); + if (d->layout->endSeparatorMove(e->pos())) { + // We've released a separator, eat this event + e->accept(); + return true; + } + break; + } + +#endif + +#ifndef QT_NO_TOOLBAR + case QEvent::ToolBarChange: { + d->layout->toggleToolBarsVisible(); + return true; + } +#endif + +#ifndef QT_NO_STATUSTIP + case QEvent::StatusTip: +#ifndef QT_NO_STATUSBAR + if (QStatusBar *sb = d->layout->statusBar()) + sb->showMessage(static_cast<QStatusTipEvent*>(event)->tip()); + else +#endif + static_cast<QStatusTipEvent*>(event)->ignore(); + return true; +#endif // QT_NO_STATUSTIP + + case QEvent::StyleChange: + if (!d->explicitIconSize) + setIconSize(QSize()); + break; +#ifdef Q_WS_MAC + case QEvent::Show: + if (unifiedTitleAndToolBarOnMac()) + macWindowToolbarShow(this, true); + break; +# ifdef QT_MAC_USE_COCOA + case QEvent::WindowStateChange: + { + // We need to update the HIToolbar status when we go out of or into fullscreen. + QWindowStateChangeEvent *wce = static_cast<QWindowStateChangeEvent *>(event); + if ((windowState() & Qt::WindowFullScreen) || (wce->oldState() & Qt::WindowFullScreen)) { + d->layout->updateHIToolBarStatus(); + } + } + break; +# endif // Cocoa +#endif +#if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_CURSOR) + case QEvent::CursorChange: + if (d->cursorAdjusted) { + d->oldCursor = cursor(); + d->hasOldCursor = testAttribute(Qt::WA_SetCursor); + } + break; +#endif + + default: + break; + } + + return QWidget::event(event); +} + +#ifndef QT_NO_TOOLBAR + +/*! + \property QMainWindow::unifiedTitleAndToolBarOnMac + \brief whether the window uses the unified title and toolbar look on Mac OS X + \since 4.3 + + This property is false by default and only has any effect on Mac OS X 10.4 or higher. + + If set to true, then the top toolbar area is replaced with a Carbon + HIToolbar and all toolbars in the top toolbar area are moved to that. Any + toolbars added afterwards will also be added to the Carbon HIToolbar. This + means a couple of things. + + \list + \i QToolBars in this toolbar area are not movable and you cannot drag other + toolbars to it + \i Toolbar breaks are not respected or preserved + \i Any custom widgets in the toolbar will not be shown if the toolbar + becomes too small (only actions will be shown) + \i If you call showFullScreen() on the main window, the QToolbar will + disappear since it is considered to be part of the title bar. You can + work around this by turning off the unified toolbar before you call + showFullScreen() and restoring it after you call showNormal(). + \endlist + + Setting this back to false will remove these restrictions. + + The Qt::WA_MacBrushedMetal attribute takes precedence over this property. +*/ +void QMainWindow::setUnifiedTitleAndToolBarOnMac(bool set) +{ +#ifdef Q_WS_MAC + Q_D(QMainWindow); + if (!isWindow() || d->useHIToolBar == set || QSysInfo::MacintoshVersion < QSysInfo::MV_10_3) + return; + + // ### Disable the unified toolbar when using anything but the native graphics system. + if (windowSurface()) + return; + + d->useHIToolBar = set; + createWinId(); // We need the hiview for down below. + + d->layout->updateHIToolBarStatus(); + // Enabling the unified toolbar clears the opaque size grip setting, update it. + d->macUpdateOpaqueSizeGrip(); +#else + Q_UNUSED(set) +#endif +} + +bool QMainWindow::unifiedTitleAndToolBarOnMac() const +{ +#ifdef Q_WS_MAC + return d_func()->useHIToolBar && !testAttribute(Qt::WA_MacBrushedMetal) && !(windowFlags() & Qt::FramelessWindowHint); +#endif + return false; +} + +#endif // QT_NO_TOOLBAR + +/*! + \internal +*/ +bool QMainWindow::isSeparator(const QPoint &pos) const +{ +#ifndef QT_NO_DOCKWIDGET + Q_D(const QMainWindow); + return !d->layout->layoutState.dockAreaLayout.findSeparator(pos).isEmpty(); +#else + Q_UNUSED(pos); + return false; +#endif +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QMainWindow::contextMenuEvent(QContextMenuEvent *event) +{ + event->ignore(); + // only show the context menu for direct QDockWidget and QToolBar + // children and for the menu bar as well + QWidget *child = childAt(event->pos()); + while (child && child != this) { +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar *>(child)) { + if (mb->parentWidget() != this) + return; + break; + } +#endif +#ifndef QT_NO_DOCKWIDGET + if (QDockWidget *dw = qobject_cast<QDockWidget *>(child)) { + if (dw->parentWidget() != this) + return; + if (dw->widget() + && dw->widget()->geometry().contains(child->mapFrom(this, event->pos()))) { + // ignore the event if the mouse is over the QDockWidget contents + return; + } + break; + } +#endif // QT_NO_DOCKWIDGET +#ifndef QT_NO_TOOLBAR + if (QToolBar *tb = qobject_cast<QToolBar *>(child)) { + if (tb->parentWidget() != this) + return; + break; + } +#endif + child = child->parentWidget(); + } + if (child == this) + return; + +#ifndef QT_NO_MENU + QMenu *popup = createPopupMenu(); + if (popup && !popup->isEmpty()) { + popup->exec(event->globalPos()); + event->accept(); + } + delete popup; +#endif +} +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_MENU +/*! + Returns a popup menu containing checkable entries for the toolbars and + dock widgets present in the main window. If there are no toolbars and + dock widgets present, this function returns a null pointer. + + By default, this function is called by the main window when the user + activates a context menu, typically by right-clicking on a toolbar or a dock + widget. + + If you want to create a custom popup menu, reimplement this function and + return a newly-created popup menu. Ownership of the popup menu is transferred + to the caller. + + \sa addDockWidget(), addToolBar(), menuBar() +*/ +QMenu *QMainWindow::createPopupMenu() +{ + Q_D(QMainWindow); + QMenu *menu = 0; +#ifndef QT_NO_DOCKWIDGET + QList<QDockWidget *> dockwidgets = qFindChildren<QDockWidget *>(this); + if (dockwidgets.size()) { + menu = new QMenu(this); + for (int i = 0; i < dockwidgets.size(); ++i) { + QDockWidget *dockWidget = dockwidgets.at(i); + if (dockWidget->parentWidget() == this + && !d->layout->layoutState.dockAreaLayout.indexOf(dockWidget).isEmpty()) { + menu->addAction(dockwidgets.at(i)->toggleViewAction()); + } + } + menu->addSeparator(); + } +#endif // QT_NO_DOCKWIDGET +#ifndef QT_NO_TOOLBAR + QList<QToolBar *> toolbars = qFindChildren<QToolBar *>(this); + if (toolbars.size()) { + if (!menu) + menu = new QMenu(this); + for (int i = 0; i < toolbars.size(); ++i) { + QToolBar *toolBar = toolbars.at(i); + if (toolBar->parentWidget() == this + && (!d->layout->layoutState.toolBarAreaLayout.indexOf(toolBar).isEmpty() + || (unifiedTitleAndToolBarOnMac() + && toolBarArea(toolBar) == Qt::TopToolBarArea))) { + menu->addAction(toolbars.at(i)->toggleViewAction()); + } + } + } +#endif + Q_UNUSED(d); + return menu; +} +#endif // QT_NO_MENU + +QT_END_NAMESPACE + +#endif // QT_NO_MAINWINDOW diff --git a/src/gui/widgets/qmainwindow.h b/src/gui/widgets/qmainwindow.h new file mode 100644 index 0000000..9983c7a --- /dev/null +++ b/src/gui/widgets/qmainwindow.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICMAINWINDOW_H +#define QDYNAMICMAINWINDOW_H + +#include <QtGui/qwidget.h> +#include <QtGui/qtabwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MAINWINDOW + +class QDockWidget; +class QMainWindowPrivate; +class QMenuBar; +class QStatusBar; +class QToolBar; +class QMenu; + +class Q_GUI_EXPORT QMainWindow : public QWidget +{ + Q_OBJECT + + Q_ENUMS(DockOption) + Q_FLAGS(DockOptions) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + Q_PROPERTY(Qt::ToolButtonStyle toolButtonStyle READ toolButtonStyle WRITE setToolButtonStyle) +#ifndef QT_NO_DOCKWIDGET + Q_PROPERTY(bool animated READ isAnimated WRITE setAnimated) +#ifndef QT_NO_TABBAR + Q_PROPERTY(bool documentMode READ documentMode WRITE setDocumentMode) +#endif // QT_NO_TABBAR +#ifndef QT_NO_TABWIDGET + Q_PROPERTY(QTabWidget::TabShape tabShape READ tabShape WRITE setTabShape) +#endif // QT_NO_TABWIDGET + Q_PROPERTY(bool dockNestingEnabled READ isDockNestingEnabled WRITE setDockNestingEnabled) +#endif // QT_NO_DOCKWIDGET + Q_PROPERTY(DockOptions dockOptions READ dockOptions WRITE setDockOptions) +#ifndef QT_NO_TOOLBAR + Q_PROPERTY(bool unifiedTitleAndToolBarOnMac READ unifiedTitleAndToolBarOnMac WRITE setUnifiedTitleAndToolBarOnMac) +#endif + +public: + enum DockOption { + AnimatedDocks = 0x01, + AllowNestedDocks = 0x02, + AllowTabbedDocks = 0x04, + ForceTabbedDocks = 0x08, // implies AllowTabbedDocks, !AllowNestedDocks + VerticalTabs = 0x10 // implies AllowTabbedDocks + }; + Q_DECLARE_FLAGS(DockOptions, DockOption) + + explicit QMainWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QMainWindow(); + + QSize iconSize() const; + void setIconSize(const QSize &iconSize); + + Qt::ToolButtonStyle toolButtonStyle() const; + void setToolButtonStyle(Qt::ToolButtonStyle toolButtonStyle); + + bool isAnimated() const; + bool isDockNestingEnabled() const; + +#ifndef QT_NO_TABBAR + bool documentMode() const; + void setDocumentMode(bool enabled); +#endif + +#ifndef QT_NO_TABWIDGET + QTabWidget::TabShape tabShape() const; + void setTabShape(QTabWidget::TabShape tabShape); + QTabWidget::TabPosition tabPosition(Qt::DockWidgetArea area) const; + void setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition); +#endif // QT_NO_TABWIDGET + + void setDockOptions(DockOptions options); + DockOptions dockOptions() const; + + bool isSeparator(const QPoint &pos) const; + +#ifndef QT_NO_MENUBAR + QMenuBar *menuBar() const; + void setMenuBar(QMenuBar *menubar); + + QWidget *menuWidget() const; + void setMenuWidget(QWidget *menubar); +#endif + +#ifndef QT_NO_STATUSBAR + QStatusBar *statusBar() const; + void setStatusBar(QStatusBar *statusbar); +#endif + + QWidget *centralWidget() const; + void setCentralWidget(QWidget *widget); + +#ifndef QT_NO_DOCKWIDGET + void setCorner(Qt::Corner corner, Qt::DockWidgetArea area); + Qt::DockWidgetArea corner(Qt::Corner corner) const; +#endif + +#ifndef QT_NO_TOOLBAR + void addToolBarBreak(Qt::ToolBarArea area = Qt::TopToolBarArea); + void insertToolBarBreak(QToolBar *before); + + void addToolBar(Qt::ToolBarArea area, QToolBar *toolbar); + void addToolBar(QToolBar *toolbar); + QToolBar *addToolBar(const QString &title); + void insertToolBar(QToolBar *before, QToolBar *toolbar); + void removeToolBar(QToolBar *toolbar); + void removeToolBarBreak(QToolBar *before); + + void setUnifiedTitleAndToolBarOnMac(bool set); + bool unifiedTitleAndToolBarOnMac() const; + + Qt::ToolBarArea toolBarArea(QToolBar *toolbar) const; + bool toolBarBreak(QToolBar *toolbar) const; +#endif +#ifndef QT_NO_DOCKWIDGET + void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget); + void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, + Qt::Orientation orientation); + void splitDockWidget(QDockWidget *after, QDockWidget *dockwidget, + Qt::Orientation orientation); + void tabifyDockWidget(QDockWidget *first, QDockWidget *second); + QList<QDockWidget*> tabifiedDockWidgets(QDockWidget *dockwidget) const; + void removeDockWidget(QDockWidget *dockwidget); + bool restoreDockWidget(QDockWidget *dockwidget); + + Qt::DockWidgetArea dockWidgetArea(QDockWidget *dockwidget) const; +#endif // QT_NO_DOCKWIDGET + + QByteArray saveState(int version = 0) const; + bool restoreState(const QByteArray &state, int version = 0); + +#ifndef QT_NO_MENU + virtual QMenu *createPopupMenu(); +#endif + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QMainWindow(QWidget *parent, const char *name, Qt::WindowFlags flags = 0); +#endif + +#ifndef QT_NO_DOCKWIDGET +public Q_SLOTS: + void setAnimated(bool enabled); + void setDockNestingEnabled(bool enabled); +#endif + +Q_SIGNALS: + void iconSizeChanged(const QSize &iconSize); + void toolButtonStyleChanged(Qt::ToolButtonStyle toolButtonStyle); + +protected: +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *event); +#endif + bool event(QEvent *event); + +private: + Q_DECLARE_PRIVATE(QMainWindow) + Q_DISABLE_COPY(QMainWindow) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QMainWindow::DockOptions) + +#endif // QT_NO_MAINWINDOW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDYNAMICMAINWINDOW_H diff --git a/src/gui/widgets/qmainwindowlayout.cpp b/src/gui/widgets/qmainwindowlayout.cpp new file mode 100644 index 0000000..768446e --- /dev/null +++ b/src/gui/widgets/qmainwindowlayout.cpp @@ -0,0 +1,1986 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmainwindowlayout_p.h" +#include "qdockarealayout_p.h" + +#ifndef QT_NO_MAINWINDOW +#include "qdockwidget.h" +#include "qdockwidget_p.h" +#include "qtoolbar_p.h" +#include "qmainwindow.h" +#include "qmainwindowlayout_p.h" +#include "qtoolbar.h" +#include "qtoolbarlayout_p.h" +#include "qwidgetanimator_p.h" +#include "qrubberband.h" +#include "qdockwidget_p.h" +#include "qtabbar_p.h" + +#include <qapplication.h> +#include <qstatusbar.h> +#include <qstring.h> +#include <qstyle.h> +#include <qvarlengtharray.h> +#include <qstack.h> +#include <qmap.h> +#include <qtimer.h> + +#include <qdebug.h> + +#include <private/qapplication_p.h> +#include <private/qlayoutengine_p.h> +#ifdef Q_WS_MAC +# include <private/qcore_mac_p.h> +# include <private/qt_cocoa_helpers_mac_p.h> +#endif + +#ifdef Q_DEBUG_MAINWINDOW_LAYOUT +# include <QTextStream> +#endif + +QT_BEGIN_NAMESPACE + +/****************************************************************************** +** debug +*/ + +#if defined(Q_DEBUG_MAINWINDOW_LAYOUT) && !defined(QT_NO_DOCKWIDGET) + +static QTextStream qout(stderr, QIODevice::WriteOnly); + +static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QString indent); + +static void dumpLayout(QTextStream &qout, const QDockAreaLayoutItem &item, QString indent) +{ + qout << indent << "QDockAreaLayoutItem: " + << "pos: " << item.pos << " size:" << item.size + << " gap:" << (item.flags & QDockAreaLayoutItem::GapItem) + << " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize) << '\n'; + indent += QLatin1String(" "); + if (item.widgetItem != 0) { + qout << indent << "widget: " + << item.widgetItem->widget()->metaObject()->className() + << ' ' << item.widgetItem->widget()->windowTitle() << '\n'; + } else if (item.subinfo != 0) { + qout << indent << "subinfo:\n"; + dumpLayout(qout, *item.subinfo, indent + QLatin1String(" ")); + } else if (item.placeHolderItem != 0) { + QRect r = item.placeHolderItem->topLevelRect; + qout << indent << "placeHolder: " + << "pos: " << item.pos << " size:" << item.size + << " gap:" << (item.flags & QDockAreaLayoutItem::GapItem) + << " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize) + << " objectName:" << item.placeHolderItem->objectName + << " hidden:" << item.placeHolderItem->hidden + << " window:" << item.placeHolderItem->window + << " rect:" << r.x() << ',' << r.y() << ' ' + << r.width() << 'x' << r.height() << '\n'; + } + qout.flush(); +} + +static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QString indent) +{ + qout << indent << "QDockAreaLayoutInfo: " + << layout.rect.left() << ',' + << layout.rect.top() << ' ' + << layout.rect.width() << 'x' + << layout.rect.height() + << " orient:" << layout.o + << " tabbed:" << layout.tabbed + << " tbshape:" << layout.tabBarShape << '\n'; + + indent += QLatin1String(" "); + + for (int i = 0; i < layout.item_list.count(); ++i) { + qout << indent << "Item: " << i << '\n'; + dumpLayout(qout, layout.item_list.at(i), indent + QLatin1String(" ")); + } + qout.flush(); +}; + +static void dumpLayout(QTextStream &qout, const QDockAreaLayout &layout, QString indent) +{ + qout << indent << "QDockAreaLayout: " + << layout.rect.left() << ',' + << layout.rect.top() << ' ' + << layout.rect.width() << 'x' + << layout.rect.height() << '\n'; + + qout << indent << "TopDockArea:\n"; + dumpLayout(qout, layout.docks[QInternal::TopDock], indent + QLatin1String(" ")); + qout << indent << "LeftDockArea:\n"; + dumpLayout(qout, layout.docks[QInternal::LeftDock], indent + QLatin1String(" ")); + qout << indent << "RightDockArea:\n"; + dumpLayout(qout, layout.docks[QInternal::RightDock], indent + QLatin1String(" ")); + qout << indent << "BottomDockArea:\n"; + dumpLayout(qout, layout.docks[QInternal::BottomDock], indent + QLatin1String(" ")); + + qout.flush(); +}; + +void qt_dumpLayout(QTextStream &qout, QMainWindow *window) +{ + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(window->layout()); + dumpLayout(qout, layout->layoutState.dockAreaLayout, QString()); +} + +#endif // Q_DEBUG_MAINWINDOW_LAYOUT && !QT_NO_DOCKWIDGET + +/****************************************************************************** +** QMainWindowLayoutState +*/ + +// we deal with all the #ifndefferry here so QMainWindowLayout code is clean + +QMainWindowLayoutState::QMainWindowLayoutState(QMainWindow *win) + : +#ifndef QT_NO_TOOLBAR + toolBarAreaLayout(win), +#endif +#ifndef QT_NO_DOCKWIDGET + dockAreaLayout(win) +#else + centralWidgetItem(0) +#endif + +{ + mainWindow = win; +} + +QSize QMainWindowLayoutState::sizeHint() const +{ + + QSize result(0, 0); + +#ifndef QT_NO_DOCKWIDGET + result = dockAreaLayout.sizeHint(); +#else + if (centralWidgetItem != 0) + result = centralWidgetItem->sizeHint(); +#endif + +#ifndef QT_NO_TOOLBAR + result = toolBarAreaLayout.sizeHint(result); +#endif // QT_NO_TOOLBAR + + return result; +} + +QSize QMainWindowLayoutState::minimumSize() const +{ + QSize result(0, 0); + +#ifndef QT_NO_DOCKWIDGET + result = dockAreaLayout.minimumSize(); +#else + if (centralWidgetItem != 0) + result = centralWidgetItem->minimumSize(); +#endif + +#ifndef QT_NO_TOOLBAR + result = toolBarAreaLayout.minimumSize(result); +#endif // QT_NO_TOOLBAR + + return result; +} + +void QMainWindowLayoutState::apply(bool animated) +{ +#ifndef QT_NO_TOOLBAR + toolBarAreaLayout.apply(animated); +#endif + +#ifndef QT_NO_DOCKWIDGET +// dumpLayout(dockAreaLayout, QString()); + dockAreaLayout.apply(animated); +#else + if (centralWidgetItem != 0) { + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(mainWindow->layout()); + Q_ASSERT(layout != 0); + layout->widgetAnimator->animate(centralWidgetItem->widget(), centralWidgetRect, animated); + } +#endif +} + +void QMainWindowLayoutState::fitLayout() +{ + QRect r; +#ifdef QT_NO_TOOLBAR + r = rect; +#else + toolBarAreaLayout.rect = rect; + r = toolBarAreaLayout.fitLayout(); +#endif // QT_NO_TOOLBAR + +#ifndef QT_NO_DOCKWIDGET + dockAreaLayout.rect = r; + dockAreaLayout.fitLayout(); +#else + centralWidgetRect = r; +#endif +} + +void QMainWindowLayoutState::deleteAllLayoutItems() +{ +#ifndef QT_NO_TOOLBAR + toolBarAreaLayout.deleteAllLayoutItems(); +#endif + +#ifndef QT_NO_DOCKWIDGET + dockAreaLayout.deleteAllLayoutItems(); +#endif +} + +void QMainWindowLayoutState::deleteCentralWidgetItem() +{ +#ifndef QT_NO_DOCKWIDGET + delete dockAreaLayout.centralWidgetItem; + dockAreaLayout.centralWidgetItem = 0; +#else + delete centralWidgetItem; + centralWidgetItem = 0; +#endif +} + +QLayoutItem *QMainWindowLayoutState::itemAt(int index, int *x) const +{ +#ifndef QT_NO_TOOLBAR + if (QLayoutItem *ret = toolBarAreaLayout.itemAt(x, index)) + return ret; +#endif + +#ifndef QT_NO_DOCKWIDGET + if (QLayoutItem *ret = dockAreaLayout.itemAt(x, index)) + return ret; +#else + if (centralWidgetItem != 0 && (*x)++ == index) + return centralWidgetItem; +#endif + + return 0; +} + +QLayoutItem *QMainWindowLayoutState::takeAt(int index, int *x) +{ +#ifndef QT_NO_TOOLBAR + if (QLayoutItem *ret = toolBarAreaLayout.takeAt(x, index)) + return ret; +#endif + +#ifndef QT_NO_DOCKWIDGET + if (QLayoutItem *ret = dockAreaLayout.takeAt(x, index)) + return ret; +#else + if (centralWidgetItem != 0 && (*x)++ == index) { + QLayoutItem *ret = centralWidgetItem; + centralWidgetItem = 0; + return ret; + } +#endif + + return 0; +} + +QList<int> QMainWindowLayoutState::indexOf(QWidget *widget) const +{ + QList<int> result; + +#ifndef QT_NO_TOOLBAR + // is it a toolbar? + if (QToolBar *toolBar = qobject_cast<QToolBar*>(widget)) { + result = toolBarAreaLayout.indexOf(toolBar); + if (!result.isEmpty()) + result.prepend(0); + return result; + } +#endif + +#ifndef QT_NO_DOCKWIDGET + // is it a dock widget? + if (QDockWidget *dockWidget = qobject_cast<QDockWidget *>(widget)) { + result = dockAreaLayout.indexOf(dockWidget); + if (!result.isEmpty()) + result.prepend(1); + return result; + } +#endif //QT_NO_DOCKWIDGET + + return result; +} + +bool QMainWindowLayoutState::contains(QWidget *widget) const +{ +#ifndef QT_NO_DOCKWIDGET + if (dockAreaLayout.centralWidgetItem != 0 && dockAreaLayout.centralWidgetItem->widget() == widget) + return true; + if (!dockAreaLayout.indexOf(widget).isEmpty()) + return true; +#else + if (centralWidgetItem != 0 && centralWidgetItem->widget() == widget) + return true; +#endif + +#ifndef QT_NO_TOOLBAR + if (!toolBarAreaLayout.indexOf(widget).isEmpty()) + return true; +#endif + return false; +} + +void QMainWindowLayoutState::setCentralWidget(QWidget *widget) +{ + QLayoutItem *item = 0; + //make sure we remove the widget + deleteCentralWidgetItem(); + + if (widget != 0) + item = new QWidgetItemV2(widget); + +#ifndef QT_NO_DOCKWIDGET + dockAreaLayout.centralWidgetItem = item; +#else + centralWidgetItem = item; +#endif +} + +QWidget *QMainWindowLayoutState::centralWidget() const +{ + QLayoutItem *item = 0; + +#ifndef QT_NO_DOCKWIDGET + item = dockAreaLayout.centralWidgetItem; +#else + item = centralWidgetItem; +#endif + + if (item != 0) + return item->widget(); + return 0; +} + +QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget, + const QPoint &pos) const +{ + QList<int> result; + +#ifndef QT_NO_TOOLBAR + // is it a toolbar? + if (qobject_cast<QToolBar*>(widget) != 0) { + result = toolBarAreaLayout.gapIndex(pos); + if (!result.isEmpty()) + result.prepend(0); + return result; + } +#endif + +#ifndef QT_NO_DOCKWIDGET + // is it a dock widget? + if (qobject_cast<QDockWidget *>(widget) != 0) { + result = dockAreaLayout.gapIndex(pos); + if (!result.isEmpty()) + result.prepend(1); + return result; + } +#endif //QT_NO_DOCKWIDGET + + return result; +} + +bool QMainWindowLayoutState::insertGap(QList<int> path, QLayoutItem *item) +{ + if (path.isEmpty()) + return false; + + int i = path.takeFirst(); + +#ifndef QT_NO_TOOLBAR + if (i == 0) { + Q_ASSERT(qobject_cast<QToolBar*>(item->widget()) != 0); + return toolBarAreaLayout.insertGap(path, item); + } +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) { + Q_ASSERT(qobject_cast<QDockWidget*>(item->widget()) != 0); + return dockAreaLayout.insertGap(path, item); + } +#endif //QT_NO_DOCKWIDGET + + return false; +} + +void QMainWindowLayoutState::remove(QList<int> path) +{ + int i = path.takeFirst(); + +#ifndef QT_NO_TOOLBAR + if (i == 0) + toolBarAreaLayout.remove(path); +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) + dockAreaLayout.remove(path); +#endif //QT_NO_DOCKWIDGET +} + +void QMainWindowLayoutState::remove(QLayoutItem *item) +{ +#ifndef QT_NO_TOOLBAR + toolBarAreaLayout.remove(item); +#endif + +#ifndef QT_NO_DOCKWIDGET + // is it a dock widget? + if (QDockWidget *dockWidget = qobject_cast<QDockWidget *>(item->widget())) { + QList<int> path = dockAreaLayout.indexOf(dockWidget); + if (!path.isEmpty()) + dockAreaLayout.remove(path); + } +#endif //QT_NO_DOCKWIDGET +} + +void QMainWindowLayoutState::clear() +{ +#ifndef QT_NO_TOOLBAR + toolBarAreaLayout.clear(); +#endif + +#ifndef QT_NO_DOCKWIDGET + dockAreaLayout.clear(); +#else + centralWidgetRect = QRect(0, 0, -1, -1); +#endif + + rect = QRect(0, 0, -1, -1); +} + +bool QMainWindowLayoutState::isValid() const +{ + return rect.isValid(); +} + +QLayoutItem *QMainWindowLayoutState::item(QList<int> path) +{ + int i = path.takeFirst(); + +#ifndef QT_NO_TOOLBAR + if (i == 0) + return toolBarAreaLayout.item(path).widgetItem; +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) + return dockAreaLayout.item(path).widgetItem; +#endif //QT_NO_DOCKWIDGET + + return 0; +} + +QRect QMainWindowLayoutState::itemRect(QList<int> path) const +{ + int i = path.takeFirst(); + +#ifndef QT_NO_TOOLBAR + if (i == 0) + return toolBarAreaLayout.itemRect(path); +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) + return dockAreaLayout.itemRect(path); +#endif //QT_NO_DOCKWIDGET + + return QRect(); +} + +QRect QMainWindowLayoutState::gapRect(QList<int> path) const +{ + int i = path.takeFirst(); + +#ifndef QT_NO_TOOLBAR + if (i == 0) + return toolBarAreaLayout.itemRect(path); +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) + return dockAreaLayout.gapRect(path); +#endif //QT_NO_DOCKWIDGET + + return QRect(); +} + +QLayoutItem *QMainWindowLayoutState::plug(QList<int> path) +{ + int i = path.takeFirst(); + +#ifndef QT_NO_TOOLBAR + if (i == 0) + return toolBarAreaLayout.plug(path); +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) + return dockAreaLayout.plug(path); +#endif //QT_NO_DOCKWIDGET + + return 0; +} + +QLayoutItem *QMainWindowLayoutState::unplug(QList<int> path, QMainWindowLayoutState *other) +{ + int i = path.takeFirst(); + +#ifdef QT_NO_TOOLBAR + Q_UNUSED(other); +#else + if (i == 0) + return toolBarAreaLayout.unplug(path, other ? &other->toolBarAreaLayout : 0); +#endif + +#ifndef QT_NO_DOCKWIDGET + if (i == 1) + return dockAreaLayout.unplug(path); +#endif //QT_NO_DOCKWIDGET + + return 0; +} + +void QMainWindowLayoutState::saveState(QDataStream &stream) const +{ +#ifndef QT_NO_DOCKWIDGET + dockAreaLayout.saveState(stream); +#endif +#ifndef QT_NO_TOOLBAR + toolBarAreaLayout.saveState(stream); +#endif +} + +template <typename T> +static QList<T> findChildrenHelper(const QObject *o) +{ + const QObjectList &list = o->children(); + QList<T> result; + + for (int i=0; i < list.size(); ++i) { + if (T t = qobject_cast<T>(list[i])) { + result.append(t); + } + } + + return result; +} + +//pre4.3 tests the format that was used before 4.3 +bool QMainWindowLayoutState::checkFormat(QDataStream &stream, bool pre43) +{ +#ifdef QT_NO_TOOLBAR + Q_UNUSED(pre43); +#endif + while (!stream.atEnd()) { + uchar marker; + stream >> marker; + switch(marker) + { +#ifndef QT_NO_TOOLBAR + case QToolBarAreaLayout::ToolBarStateMarker: + case QToolBarAreaLayout::ToolBarStateMarkerEx: + { + QList<QToolBar *> toolBars = findChildrenHelper<QToolBar*>(mainWindow); + if (!toolBarAreaLayout.restoreState(stream, toolBars, marker, + pre43 /*testing 4.3 format*/, true /*testing*/)) { + return false; + } + } + break; +#endif // QT_NO_TOOLBAR + +#ifndef QT_NO_DOCKWIDGET + case QDockAreaLayout::DockWidgetStateMarker: + { + QList<QDockWidget *> dockWidgets = findChildrenHelper<QDockWidget*>(mainWindow); + if (!dockAreaLayout.restoreState(stream, dockWidgets, true /*testing*/)) { + return false; + } + } + break; +#endif + default: + //there was an error during the parsing + return false; + }// switch + } //while + + //everything went fine: it must be a pre-4.3 saved state + return true; +} + +bool QMainWindowLayoutState::restoreState(QDataStream &_stream, + const QMainWindowLayoutState &oldState) +{ + //make a copy of the data so that we can read it more than once + QByteArray copy; + while(!_stream.atEnd()) { + int length = 1024; + QByteArray ba(length, '\0'); + length = _stream.readRawData(ba.data(), ba.size()); + ba.resize(length); + copy += ba; + } + + QDataStream ds(copy); + const bool oldFormat = !checkFormat(ds, false); + if (oldFormat) { + //we should try with the old format + QDataStream ds2(copy); + if (!checkFormat(ds2, true)) { + return false; //format unknown + } + } + + QDataStream stream(copy); + + while (!stream.atEnd()) { + uchar marker; + stream >> marker; + switch(marker) + { +#ifndef QT_NO_DOCKWIDGET + case QDockAreaLayout::DockWidgetStateMarker: + { + QList<QDockWidget *> dockWidgets = findChildrenHelper<QDockWidget*>(mainWindow); + if (!dockAreaLayout.restoreState(stream, dockWidgets)) + return false; + + for (int i = 0; i < dockWidgets.size(); ++i) { + QDockWidget *w = dockWidgets.at(i); + QList<int> path = dockAreaLayout.indexOf(w); + if (path.isEmpty()) { + QList<int> oldPath = oldState.dockAreaLayout.indexOf(w); + if (oldPath.isEmpty()) { + continue; + } + QDockAreaLayoutInfo *info = dockAreaLayout.info(oldPath); + if (info == 0) { + continue; + } + info->item_list.append(QDockAreaLayoutItem(new QDockWidgetItem(w))); + } + } + } + break; +#endif // QT_NO_DOCKWIDGET + +#ifndef QT_NO_TOOLBAR + case QToolBarAreaLayout::ToolBarStateMarker: + case QToolBarAreaLayout::ToolBarStateMarkerEx: + { + QList<QToolBar *> toolBars = findChildrenHelper<QToolBar*>(mainWindow); + if (!toolBarAreaLayout.restoreState(stream, toolBars, marker, oldFormat)) + return false; + + for (int i = 0; i < toolBars.size(); ++i) { + QToolBar *w = toolBars.at(i); + QList<int> path = toolBarAreaLayout.indexOf(w); + if (path.isEmpty()) { + QList<int> oldPath = oldState.toolBarAreaLayout.indexOf(w); + if (oldPath.isEmpty()) { + continue; + } + toolBarAreaLayout.docks[oldPath.at(0)].insertToolBar(0, w); + } + } + } + break; +#endif //QT_NO_TOOLBAR + default: + return false; + }// switch + } //while + + + return true; +} + +/****************************************************************************** +** QMainWindowLayoutState - toolbars +*/ + +#ifndef QT_NO_TOOLBAR + +static inline void validateToolBarArea(Qt::ToolBarArea &area) +{ + switch (area) { + case Qt::LeftToolBarArea: + case Qt::RightToolBarArea: + case Qt::TopToolBarArea: + case Qt::BottomToolBarArea: + break; + default: + area = Qt::TopToolBarArea; + } +} + +static QInternal::DockPosition toDockPos(Qt::ToolBarArea area) +{ + switch (area) { + case Qt::LeftToolBarArea: return QInternal::LeftDock; + case Qt::RightToolBarArea: return QInternal::RightDock; + case Qt::TopToolBarArea: return QInternal::TopDock; + case Qt::BottomToolBarArea: return QInternal::BottomDock; + default: + break; + } + + return QInternal::DockCount; +} + +static Qt::ToolBarArea toToolBarArea(QInternal::DockPosition pos) +{ + switch (pos) { + case QInternal::LeftDock: return Qt::LeftToolBarArea; + case QInternal::RightDock: return Qt::RightToolBarArea; + case QInternal::TopDock: return Qt::TopToolBarArea; + case QInternal::BottomDock: return Qt::BottomToolBarArea; + default: break; + } + return Qt::NoToolBarArea; +} + +static inline Qt::ToolBarArea toToolBarArea(int pos) +{ + return toToolBarArea(static_cast<QInternal::DockPosition>(pos)); +} + +void QMainWindowLayout::addToolBarBreak(Qt::ToolBarArea area) +{ + validateToolBarArea(area); + + layoutState.toolBarAreaLayout.addToolBarBreak(toDockPos(area)); + if (savedState.isValid()) + savedState.toolBarAreaLayout.addToolBarBreak(toDockPos(area)); + + invalidate(); +} + +void QMainWindowLayout::insertToolBarBreak(QToolBar *before) +{ + layoutState.toolBarAreaLayout.insertToolBarBreak(before); + if (savedState.isValid()) + savedState.toolBarAreaLayout.insertToolBarBreak(before); + invalidate(); +} + +void QMainWindowLayout::removeToolBarBreak(QToolBar *before) +{ + layoutState.toolBarAreaLayout.removeToolBarBreak(before); + if (savedState.isValid()) + savedState.toolBarAreaLayout.removeToolBarBreak(before); + invalidate(); +} + +void QMainWindowLayout::moveToolBar(QToolBar *toolbar, int pos) +{ + layoutState.toolBarAreaLayout.moveToolBar(toolbar, pos); + if (savedState.isValid()) + savedState.toolBarAreaLayout.moveToolBar(toolbar, pos); + invalidate(); +} + +/* Removes the toolbar from the mainwindow so that it can be added again. Does not + explicitly hide the toolbar. */ +void QMainWindowLayout::removeToolBar(QToolBar *toolbar) +{ + if (toolbar) { + QObject::disconnect(parentWidget(), SIGNAL(iconSizeChanged(QSize)), + toolbar, SLOT(_q_updateIconSize(QSize))); + QObject::disconnect(parentWidget(), SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), + toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle))); + +#ifdef Q_WS_MAC + if (usesHIToolBar(toolbar)) { + removeFromMacToolbar(toolbar); + } else +#endif // Q_WS_MAC + { + removeWidget(toolbar); + } + } +} + +/*! + Adds \a toolbar to \a area, continuing the current line. +*/ +void QMainWindowLayout::addToolBar(Qt::ToolBarArea area, + QToolBar *toolbar, + bool) +{ + validateToolBarArea(area); +#ifdef Q_WS_MAC + if ((area == Qt::TopToolBarArea) + && layoutState.mainWindow->unifiedTitleAndToolBarOnMac()) { + insertIntoMacToolbar(0, toolbar); + } else +#endif + { + //let's add the toolbar to the layout + addChildWidget(toolbar); + QLayoutItem * item = layoutState.toolBarAreaLayout.addToolBar(toDockPos(area), toolbar); + if (savedState.isValid() && item) { + // copy the toolbar also in the saved state + savedState.toolBarAreaLayout.insertItem(toDockPos(area), item); + } + invalidate(); + + //this ensures that the toolbar has the right window flags (not floating any more) + toolbar->d_func()->updateWindowFlags(false /*floating*/); + } +} + +/*! + Adds \a toolbar before \a before +*/ +void QMainWindowLayout::insertToolBar(QToolBar *before, QToolBar *toolbar) +{ +#ifdef Q_WS_MAC + if (usesHIToolBar(before)) { + insertIntoMacToolbar(before, toolbar); + } else +#endif // Q_WS_MAC + { + addChildWidget(toolbar); + QLayoutItem * item = layoutState.toolBarAreaLayout.insertToolBar(before, toolbar); + if (savedState.isValid() && item) { + // copy the toolbar also in the saved state + savedState.toolBarAreaLayout.insertItem(before, item); + } + if (!currentGapPos.isEmpty() && currentGapPos.first() == 0) { + currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex(); + if (!currentGapPos.isEmpty()) { + currentGapPos.prepend(0); + currentGapRect = layoutState.itemRect(currentGapPos); + } + } + invalidate(); + } +} + +Qt::ToolBarArea QMainWindowLayout::toolBarArea(QToolBar *toolbar) const +{ + QInternal::DockPosition pos = layoutState.toolBarAreaLayout.findToolBar(toolbar); + switch (pos) { + case QInternal::LeftDock: return Qt::LeftToolBarArea; + case QInternal::RightDock: return Qt::RightToolBarArea; + case QInternal::TopDock: return Qt::TopToolBarArea; + case QInternal::BottomDock: return Qt::BottomToolBarArea; + default: break; + } +#ifdef Q_WS_MAC + if (pos == QInternal::DockCount) { + if (qtoolbarsInUnifiedToolbarList.contains(toolbar)) + return Qt::TopToolBarArea; + } +#endif + return Qt::NoToolBarArea; +} + +bool QMainWindowLayout::toolBarBreak(QToolBar *toolBar) const +{ + return layoutState.toolBarAreaLayout.toolBarBreak(toolBar); +} + +void QMainWindowLayout::getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const +{ + option->toolBarArea = toolBarArea(toolBar); + layoutState.toolBarAreaLayout.getStyleOptionInfo(option, toolBar); +} + +void QMainWindowLayout::toggleToolBarsVisible() +{ + layoutState.toolBarAreaLayout.visible = !layoutState.toolBarAreaLayout.visible; + if (!layoutState.mainWindow->isMaximized()){ + QPoint topLeft = parentWidget()->geometry().topLeft(); + QRect r = parentWidget()->geometry(); + r = layoutState.toolBarAreaLayout.rectHint(r); + r.moveTo(topLeft); + parentWidget()->setGeometry(r); +// widgetAnimator->animate(parentWidget(), r, true); + } else{ + update(); + } +} + +#endif // QT_NO_TOOLBAR + +/****************************************************************************** +** QMainWindowLayoutState - dock areas +*/ + +#ifndef QT_NO_DOCKWIDGET + +static inline void validateDockWidgetArea(Qt::DockWidgetArea &area) +{ + switch (area) { + case Qt::LeftDockWidgetArea: + case Qt::RightDockWidgetArea: + case Qt::TopDockWidgetArea: + case Qt::BottomDockWidgetArea: + break; + default: + area = Qt::LeftDockWidgetArea; + } +} + +static QInternal::DockPosition toDockPos(Qt::DockWidgetArea area) +{ + switch (area) { + case Qt::LeftDockWidgetArea: return QInternal::LeftDock; + case Qt::RightDockWidgetArea: return QInternal::RightDock; + case Qt::TopDockWidgetArea: return QInternal::TopDock; + case Qt::BottomDockWidgetArea: return QInternal::BottomDock; + default: + break; + } + + return QInternal::DockCount; +} + +static Qt::DockWidgetArea toDockWidgetArea(QInternal::DockPosition pos) +{ + switch (pos) { + case QInternal::LeftDock : return Qt::LeftDockWidgetArea; + case QInternal::RightDock : return Qt::RightDockWidgetArea; + case QInternal::TopDock : return Qt::TopDockWidgetArea; + case QInternal::BottomDock : return Qt::BottomDockWidgetArea; + default: + break; + } + + return Qt::NoDockWidgetArea; +} + +inline static Qt::DockWidgetArea toDockWidgetArea(int pos) +{ + return toDockWidgetArea(static_cast<QInternal::DockPosition>(pos)); +} + +void QMainWindowLayout::setCorner(Qt::Corner corner, Qt::DockWidgetArea area) +{ + if (layoutState.dockAreaLayout.corners[corner] == area) + return; + layoutState.dockAreaLayout.corners[corner] = area; + if (savedState.isValid()) + savedState.dockAreaLayout.corners[corner] = area; + invalidate(); +} + +Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const +{ + return layoutState.dockAreaLayout.corners[corner]; +} + +void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area, + QDockWidget *dockwidget, + Qt::Orientation orientation) +{ + addChildWidget(dockwidget); + + // If we are currently moving a separator, then we need to abort the move, since each + // time we move the mouse layoutState is replaced by savedState modified by the move. + if (!movingSeparator.isEmpty()) + endSeparatorMove(movingSeparatorPos); + + layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation); + emit dockwidget->dockLocationChanged(area); + invalidate(); +} + +void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second) +{ + addChildWidget(second); + layoutState.dockAreaLayout.tabifyDockWidget(first, second); + emit second->dockLocationChanged(dockWidgetArea(first)); + invalidate(); +} + +bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget) +{ + addChildWidget(dockwidget); + if (!layoutState.dockAreaLayout.restoreDockWidget(dockwidget)) + return false; + emit dockwidget->dockLocationChanged(dockWidgetArea(dockwidget)); + invalidate(); + return true; +} + +#ifndef QT_NO_TABBAR +bool QMainWindowLayout::documentMode() const +{ + return _documentMode; +} + +void QMainWindowLayout::setDocumentMode(bool enabled) +{ + if (_documentMode == enabled) + return; + + _documentMode = enabled; + + // Update the document mode for all tab bars + foreach (QTabBar *bar, usedTabBars) + bar->setDocumentMode(_documentMode); + foreach (QTabBar *bar, unusedTabBars) + bar->setDocumentMode(_documentMode); +} +#endif // QT_NO_TABBAR + +void QMainWindowLayout::setVerticalTabsEnabled(bool enabled) +{ +#ifdef QT_NO_TABBAR + Q_UNUSED(enabled); +#else + if (verticalTabsEnabled == enabled) + return; + + verticalTabsEnabled = enabled; + + updateTabBarShapes(); +#endif // QT_NO_TABBAR +} + +#ifndef QT_NO_TABWIDGET +QTabWidget::TabShape QMainWindowLayout::tabShape() const +{ + return _tabShape; +} + +void QMainWindowLayout::setTabShape(QTabWidget::TabShape tabShape) +{ + if (_tabShape == tabShape) + return; + + _tabShape = tabShape; + + updateTabBarShapes(); +} + +QTabWidget::TabPosition QMainWindowLayout::tabPosition(Qt::DockWidgetArea area) const +{ + return tabPositions[toDockPos(area)]; +} + +void QMainWindowLayout::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition) +{ + const Qt::DockWidgetArea dockWidgetAreas[] = { + Qt::TopDockWidgetArea, + Qt::LeftDockWidgetArea, + Qt::BottomDockWidgetArea, + Qt::RightDockWidgetArea + }; + const QInternal::DockPosition dockPositions[] = { + QInternal::TopDock, + QInternal::LeftDock, + QInternal::BottomDock, + QInternal::RightDock + }; + + for (int i = 0; i < QInternal::DockCount; ++i) + if (areas & dockWidgetAreas[i]) + tabPositions[dockPositions[i]] = tabPosition; + + updateTabBarShapes(); +} + +static inline QTabBar::Shape tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position) +{ + const bool rounded = (shape == QTabWidget::Rounded); + if (position == QTabWidget::North) + return rounded ? QTabBar::RoundedNorth : QTabBar::TriangularNorth; + if (position == QTabWidget::South) + return rounded ? QTabBar::RoundedSouth : QTabBar::TriangularSouth; + if (position == QTabWidget::East) + return rounded ? QTabBar::RoundedEast : QTabBar::TriangularEast; + if (position == QTabWidget::West) + return rounded ? QTabBar::RoundedWest : QTabBar::TriangularWest; + return QTabBar::RoundedNorth; +} +#endif // QT_NO_TABWIDGET + +#ifndef QT_NO_TABBAR +void QMainWindowLayout::updateTabBarShapes() +{ +#ifndef QT_NO_TABWIDGET + const QTabWidget::TabPosition vertical[] = { + QTabWidget::West, + QTabWidget::East, + QTabWidget::North, + QTabWidget::South + }; +#else + const QTabBar::Shape vertical[] = { + QTabBar::RoundedWest, + QTabBar::RoundedEast, + QTabBar::RoundedNorth, + QTabBar::RoundedSouth + }; +#endif + + QDockAreaLayout &layout = layoutState.dockAreaLayout; + + for (int i = 0; i < QInternal::DockCount; ++i) { +#ifndef QT_NO_TABWIDGET + QTabWidget::TabPosition pos = verticalTabsEnabled ? vertical[i] : tabPositions[i]; + QTabBar::Shape shape = tabBarShapeFrom(_tabShape, pos); +#else + QTabBar::Shape shape = verticalTabsEnabled ? vertical[i] : QTabBar::RoundedSouth; +#endif + layout.docks[i].setTabBarShape(shape); + } +} +#endif // QT_NO_TABBAR + +void QMainWindowLayout::splitDockWidget(QDockWidget *after, + QDockWidget *dockwidget, + Qt::Orientation orientation) +{ + addChildWidget(dockwidget); + layoutState.dockAreaLayout.splitDockWidget(after, dockwidget, orientation); + emit dockwidget->dockLocationChanged(dockWidgetArea(after)); + invalidate(); +} + +Qt::DockWidgetArea QMainWindowLayout::dockWidgetArea(QDockWidget *widget) const +{ + QList<int> pathToWidget = layoutState.dockAreaLayout.indexOf(widget); + if (pathToWidget.isEmpty()) + return Qt::NoDockWidgetArea; + return toDockWidgetArea(pathToWidget.first()); +} + +void QMainWindowLayout::keepSize(QDockWidget *w) +{ + layoutState.dockAreaLayout.keepSize(w); +} + +#ifndef QT_NO_TABBAR + +class QMainWindowTabBar : public QTabBar +{ +public: + QMainWindowTabBar(QWidget *parent); +protected: + bool event(QEvent *e); +}; + +QMainWindowTabBar::QMainWindowTabBar(QWidget *parent) + : QTabBar(parent) +{ + setExpanding(false); +} + +bool QMainWindowTabBar::event(QEvent *e) +{ + // show the tooltip if tab is too small to fit label + + if (e->type() != QEvent::ToolTip) + return QTabBar::event(e); + QSize size = this->size(); + QSize hint = sizeHint(); + if (shape() == QTabBar::RoundedWest || shape() == QTabBar::RoundedEast) { + size.transpose(); + hint.transpose(); + } + if (size.width() < hint.width()) + return QTabBar::event(e); + e->accept(); + return true; +} + +QTabBar *QMainWindowLayout::getTabBar() +{ + QTabBar *result = 0; + if (!unusedTabBars.isEmpty()) { + result = unusedTabBars.takeLast(); + } else { + result = new QMainWindowTabBar(parentWidget()); + result->setDrawBase(true); + result->setElideMode(Qt::ElideRight); + result->setDocumentMode(_documentMode); + connect(result, SIGNAL(currentChanged(int)), this, SLOT(tabChanged())); + } + + usedTabBars.insert(result); + return result; +} + +// Allocates a new separator widget if needed +QWidget *QMainWindowLayout::getSeparatorWidget() +{ + QWidget *result = 0; + if (!unusedSeparatorWidgets.isEmpty()) { + result = unusedSeparatorWidgets.takeLast(); + } else { + result = new QWidget(parentWidget()); + result->setAttribute(Qt::WA_MouseNoMask, true); + result->setAutoFillBackground(false); + result->setObjectName(QLatin1String("qt_qmainwindow_extended_splitter")); + } + usedSeparatorWidgets.insert(result); + return result; +} + +void QMainWindowLayout::tabChanged() +{ + QTabBar *tb = qobject_cast<QTabBar*>(sender()); + if (tb == 0) + return; + QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(tb); + if (info == 0) + return; + info->apply(false); + + if (QWidget *w = centralWidget()) + w->raise(); +} +#endif // QT_NO_TABBAR + +bool QMainWindowLayout::startSeparatorMove(const QPoint &pos) +{ + movingSeparator = layoutState.dockAreaLayout.findSeparator(pos); + + if (movingSeparator.isEmpty()) + return false; + + savedState = layoutState; + movingSeparatorPos = movingSeparatorOrigin = pos; + + return true; +} + +bool QMainWindowLayout::separatorMove(const QPoint &pos) +{ + if (movingSeparator.isEmpty()) + return false; + movingSeparatorPos = pos; + separatorMoveTimer->start(); + return true; +} + +void QMainWindowLayout::doSeparatorMove() +{ + if (movingSeparator.isEmpty()) + return; + if (movingSeparatorOrigin == movingSeparatorPos) + return; + + layoutState = savedState; + layoutState.dockAreaLayout.separatorMove(movingSeparator, movingSeparatorOrigin, + movingSeparatorPos, + &separatorMoveCache); + movingSeparatorPos = movingSeparatorOrigin; +} + +bool QMainWindowLayout::endSeparatorMove(const QPoint&) +{ + bool result = !movingSeparator.isEmpty(); + movingSeparator.clear(); + savedState.clear(); + separatorMoveCache.clear(); + return result; +} + +void QMainWindowLayout::raise(QDockWidget *widget) +{ + QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget); + if (info == 0) + return; +#ifndef QT_NO_TABBAR + if (!info->tabbed) + return; + info->setCurrentTab(widget); +#endif +} + +#endif // QT_NO_DOCKWIDGET + + +/****************************************************************************** +** QMainWindowLayoutState - layout interface +*/ + +int QMainWindowLayout::count() const +{ + qWarning("QMainWindowLayout::count: ?"); + return 0; //################################################# +} + +QLayoutItem *QMainWindowLayout::itemAt(int index) const +{ + int x = 0; + + if (QLayoutItem *ret = layoutState.itemAt(index, &x)) + return ret; + + if (statusbar && x++ == index) + return statusbar; + + return 0; +} + +QLayoutItem *QMainWindowLayout::takeAt(int index) +{ + int x = 0; + + if (QLayoutItem *ret = layoutState.takeAt(index, &x)) { + // the widget might in fact have been destroyed by now + if (QWidget *w = ret->widget()) { + widgetAnimator->abort(w); + if (w == pluggingWidget) + pluggingWidget = 0; + } + + if (savedState.isValid() ) { + //we need to remove the item also from the saved state to prevent crash + savedState.remove(ret); + //Also, the item may be contained several times as a gap item. + layoutState.remove(ret); + } + +#ifndef QT_NO_TOOLBAR + if (!currentGapPos.isEmpty() && currentGapPos.first() == 0) { + currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex(); + if (!currentGapPos.isEmpty()) { + currentGapPos.prepend(0); + currentGapRect = layoutState.itemRect(currentGapPos); + } + } +#endif + + return ret; + } + + if (statusbar && x++ == index) { + QLayoutItem *ret = statusbar; + statusbar = 0; + return ret; + } + + return 0; +} + +void QMainWindowLayout::setGeometry(const QRect &_r) +{ + if (savedState.isValid()) + return; + + QRect r = _r; + + QLayout::setGeometry(r); + + if (statusbar) { + QRect sbr(QPoint(0, 0), + QSize(r.width(), statusbar->heightForWidth(r.width())) + .expandedTo(statusbar->minimumSize())); + sbr.moveBottom(r.bottom()); + QRect vr = QStyle::visualRect(QApplication::layoutDirection(), _r, sbr); + statusbar->setGeometry(vr); + r.setBottom(sbr.top() - 1); + } + + layoutState.rect = r; + layoutState.fitLayout(); + applyState(layoutState, false); +} + +void QMainWindowLayout::addItem(QLayoutItem *) +{ qWarning("QMainWindowLayout::addItem: Please use the public QMainWindow API instead"); } + +QSize QMainWindowLayout::sizeHint() const +{ + if (!szHint.isValid()) { + szHint = layoutState.sizeHint(); + const QSize sbHint = statusbar ? statusbar->sizeHint() : QSize(0, 0); + szHint = QSize(qMax(sbHint.width(), szHint.width()), + sbHint.height() + szHint.height()); + } + return szHint; +} + +QSize QMainWindowLayout::minimumSize() const +{ + if (!minSize.isValid()) { + minSize = layoutState.minimumSize(); + const QSize sbMin = statusbar ? statusbar->minimumSize() : QSize(0, 0); + minSize = QSize(qMax(sbMin.width(), minSize.width()), + sbMin.height() + minSize.height()); +#ifdef Q_WS_MAC + const QSize storedSize = minSize; + int minWidth = 0; + foreach (QToolBar *toolbar, qtoolbarsInUnifiedToolbarList) { + minWidth += toolbar->sizeHint().width() + 20; + } + minSize = QSize(qMax(minWidth, storedSize.width()), storedSize.height()); +#endif + } + return minSize; +} + +void QMainWindowLayout::invalidate() +{ + QLayout::invalidate(); + minSize = szHint = QSize(); +} + +/****************************************************************************** +** QMainWindowLayout - remaining stuff +*/ + +static void fixToolBarOrientation(QLayoutItem *item, int dockPos) +{ +#ifndef QT_NO_TOOLBAR + QToolBar *toolBar = qobject_cast<QToolBar*>(item->widget()); + if (toolBar == 0) + return; + + QRect oldGeo = toolBar->geometry(); + + QInternal::DockPosition pos + = static_cast<QInternal::DockPosition>(dockPos); + Qt::Orientation o = pos == QInternal::TopDock || pos == QInternal::BottomDock + ? Qt::Horizontal : Qt::Vertical; + if (o != toolBar->orientation()) + toolBar->setOrientation(o); + + QSize hint = toolBar->sizeHint().boundedTo(toolBar->maximumSize()) + .expandedTo(toolBar->minimumSize()); + + if (toolBar->size() != hint) { + QRect newGeo(oldGeo.topLeft(), hint); + if (toolBar->layoutDirection() == Qt::RightToLeft) + newGeo.moveRight(oldGeo.right()); + toolBar->setGeometry(newGeo); + } + +#else + Q_UNUSED(item); + Q_UNUSED(dockPos); +#endif +} + +void QMainWindowLayout::revert(QLayoutItem *widgetItem) +{ + if (!savedState.isValid()) + return; + + QWidget *widget = widgetItem->widget(); + layoutState = savedState; + currentGapPos = layoutState.indexOf(widget); + fixToolBarOrientation(widgetItem, currentGapPos.at(1)); + layoutState.unplug(currentGapPos); + layoutState.fitLayout(); + currentGapRect = layoutState.itemRect(currentGapPos); + + plug(widgetItem); +} + +bool QMainWindowLayout::plug(QLayoutItem *widgetItem) +{ + if (!parentWidget()->isVisible() || parentWidget()->isMinimized() || currentGapPos.isEmpty()) + return false; + + fixToolBarOrientation(widgetItem, currentGapPos.at(1)); + + QWidget *widget = widgetItem->widget(); + + QList<int> previousPath = layoutState.indexOf(widget); + + QLayoutItem *it = layoutState.plug(currentGapPos); + Q_ASSERT(it == widgetItem); + Q_UNUSED(it); + if (!previousPath.isEmpty()) + layoutState.remove(previousPath); + + if (dockOptions & QMainWindow::AnimatedDocks) { + pluggingWidget = widget; + QRect globalRect = currentGapRect; + globalRect.moveTopLeft(parentWidget()->mapToGlobal(globalRect.topLeft())); +#ifndef QT_NO_DOCKWIDGET + if (qobject_cast<QDockWidget*>(widget) != 0) { + QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(widget->layout()); + if (layout->nativeWindowDeco()) { + globalRect.adjust(0, layout->titleHeight(), 0, 0); + } else { + int fw = widget->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, widget); + globalRect.adjust(-fw, -fw, fw, fw); + } + } +#endif + widgetAnimator->animate(widget, globalRect, + dockOptions & QMainWindow::AnimatedDocks); + } else { +#ifndef QT_NO_DOCKWIDGET + if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) + dw->d_func()->plug(currentGapRect); +#endif +#ifndef QT_NO_TOOLBAR + if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) + tb->d_func()->plug(currentGapRect); +#endif + applyState(layoutState); + savedState.clear(); +#ifndef QT_NO_DOCKWIDGET + parentWidget()->update(layoutState.dockAreaLayout.separatorRegion()); +#endif + currentGapPos.clear(); + updateGapIndicator(); + } + + return true; +} + +void QMainWindowLayout::allAnimationsFinished() +{ +#ifndef QT_NO_DOCKWIDGET + parentWidget()->update(layoutState.dockAreaLayout.separatorRegion()); + +#ifndef QT_NO_TABBAR + foreach (QTabBar *tab_bar, usedTabBars) + tab_bar->show(); +#endif // QT_NO_TABBAR +#endif // QT_NO_DOCKWIDGET + + updateGapIndicator(); +} + +void QMainWindowLayout::animationFinished(QWidget *widget) +{ + + /* This signal is delivered from QWidgetAnimator over a qeued connection. The problem is that + the widget can be deleted. This is handled as follows: + + The animator only ever animates widgets that have been added to this layout. If a widget + is deleted during animation, the widget's destructor removes the widget form this layout. + This in turn aborts the animation (see takeAt()) and this signal will never be delivered. + + If the widget is deleted after the animation is finished but before this qeued signal + is delivered, the widget is no longer in the layout and we catch it here. The key is that + QMainWindowLayoutState::contains() never dereferences the pointer. */ + + if (!layoutState.contains(widget)) + return; + +#ifndef QT_NO_TOOLBAR + if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) { + QToolBarLayout *tbl = qobject_cast<QToolBarLayout*>(tb->layout()); + if (tbl->animating) { + tbl->animating = false; + if (tbl->expanded) + tbl->layoutActions(tb->size()); + tb->update(); + } + } +#endif + + if (widget != pluggingWidget) + return; + +#ifndef QT_NO_DOCKWIDGET + if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) + dw->d_func()->plug(currentGapRect); +#endif +#ifndef QT_NO_TOOLBAR + if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) + tb->d_func()->plug(currentGapRect); +#endif + + applyState(layoutState, false); +#ifndef QT_NO_DOCKWIDGET +#ifndef QT_NO_TABBAR + if (qobject_cast<QDockWidget*>(widget) != 0) { + // info() might return null if the widget is destroyed while + // animating but before the animationFinished signal is received. + if (QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget)) + info->setCurrentTab(widget); + } +#endif +#endif + savedState.clear(); + currentGapPos.clear(); + pluggingWidget = 0; + updateGapIndicator(); +} + +void QMainWindowLayout::restore(bool keepSavedState) +{ + if (!savedState.isValid()) + return; + + layoutState = savedState; + applyState(layoutState); + if (!keepSavedState) + savedState.clear(); + currentGapPos.clear(); + pluggingWidget = 0; + updateGapIndicator(); +} + +QMainWindowLayout::QMainWindowLayout(QMainWindow *mainwindow) + : QLayout(mainwindow) + , layoutState(mainwindow) + , savedState(mainwindow) + , dockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowTabbedDocks) + , statusbar(0) +#ifndef QT_NO_DOCKWIDGET +#ifndef QT_NO_TABBAR + , _documentMode(false) + , verticalTabsEnabled(false) +#ifndef QT_NO_TABWIDGET + , _tabShape(QTabWidget::Rounded) +#endif +#endif +#endif // QT_NO_DOCKWIDGET +{ +#ifndef QT_NO_DOCKWIDGET +#ifndef QT_NO_TABBAR + sep = mainwindow->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, 0, mainwindow); +#endif + separatorMoveTimer = new QTimer(this); + separatorMoveTimer->setSingleShot(true); + separatorMoveTimer->setInterval(0); + connect(separatorMoveTimer, SIGNAL(timeout()), this, SLOT(doSeparatorMove())); + +#ifndef QT_NO_TABWIDGET + for (int i = 0; i < QInternal::DockCount; ++i) + tabPositions[i] = QTabWidget::South; +#endif +#endif // QT_NO_DOCKWIDGET + +#ifndef QT_NO_RUBBERBAND + gapIndicator = new QRubberBand(QRubberBand::Rectangle, mainwindow); + // For accessibility to identify this special widget. + gapIndicator->setObjectName(QLatin1String("qt_rubberband")); + + gapIndicator->hide(); +#endif + pluggingWidget = 0; + + setObjectName(mainwindow->objectName() + QLatin1String("_layout")); + widgetAnimator = new QWidgetAnimator(this); + connect(widgetAnimator, SIGNAL(finished(QWidget*)), + this, SLOT(animationFinished(QWidget*)), Qt::QueuedConnection); + connect(widgetAnimator, SIGNAL(finishedAll()), + this, SLOT(allAnimationsFinished())); +} + +QMainWindowLayout::~QMainWindowLayout() +{ + layoutState.deleteAllLayoutItems(); + layoutState.deleteCentralWidgetItem(); + +#ifdef Q_WS_MAC + cleanUpMacToolbarItems(); +#endif + + delete statusbar; +} + +void QMainWindowLayout::setDockOptions(QMainWindow::DockOptions opts) +{ + if (opts == dockOptions) + return; + + dockOptions = opts; + +#ifndef QT_NO_DOCKWIDGET + setVerticalTabsEnabled(opts & QMainWindow::VerticalTabs); +#endif + + invalidate(); +} + +#ifndef QT_NO_STATUSBAR +QStatusBar *QMainWindowLayout::statusBar() const +{ return statusbar ? qobject_cast<QStatusBar *>(statusbar->widget()) : 0; } + +void QMainWindowLayout::setStatusBar(QStatusBar *sb) +{ + if (sb) + addChildWidget(sb); + delete statusbar; + statusbar = sb ? new QWidgetItemV2(sb) : 0; + invalidate(); +} +#endif // QT_NO_STATUSBAR + +QWidget *QMainWindowLayout::centralWidget() const +{ + return layoutState.centralWidget(); +} + +void QMainWindowLayout::setCentralWidget(QWidget *widget) +{ + if (widget != 0) + addChildWidget(widget); + layoutState.setCentralWidget(widget); + if (savedState.isValid()) { +#ifndef QT_NO_DOCKWIDGET + savedState.dockAreaLayout.centralWidgetItem = layoutState.dockAreaLayout.centralWidgetItem; +#else + savedState.centralWidgetItem = layoutState.centralWidgetItem; +#endif + } + invalidate(); +} + +QLayoutItem *QMainWindowLayout::unplug(QWidget *widget) +{ + QList<int> path = layoutState.indexOf(widget); + if (path.isEmpty()) + return 0; + + QLayoutItem *item = layoutState.item(path); + if (widget->isWindow()) + return item; + + QRect r = layoutState.itemRect(path); + savedState = layoutState; + +#ifndef QT_NO_DOCKWIDGET + if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) { + dw->d_func()->unplug(r); + } +#endif +#ifndef QT_NO_TOOLBAR + if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) { + tb->d_func()->unplug(r); + } +#endif + + + layoutState.unplug(path ,&savedState); + savedState.fitLayout(); + currentGapPos = path; + currentGapRect = r; + updateGapIndicator(); + + fixToolBarOrientation(item, currentGapPos.at(1)); + + return item; +} + +void QMainWindowLayout::updateGapIndicator() +{ +#ifndef QT_NO_RUBBERBAND + if (widgetAnimator->animating() || currentGapPos.isEmpty()) { + gapIndicator->hide(); + } else { + if (gapIndicator->geometry() != currentGapRect) + gapIndicator->setGeometry(currentGapRect); + if (!gapIndicator->isVisible()) + gapIndicator->show(); + } +#endif +} + +QList<int> QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos) +{ + if (!parentWidget()->isVisible() || parentWidget()->isMinimized() + || pluggingWidget != 0 || widgetItem == 0) + return QList<int>(); + + QWidget *widget = widgetItem->widget(); + QPoint pos = parentWidget()->mapFromGlobal(mousePos); + + if (!savedState.isValid()) + savedState = layoutState; + + QList<int> path = savedState.gapIndex(widget, pos); + + if (!path.isEmpty()) { + bool allowed = false; + +#ifndef QT_NO_DOCKWIDGET + if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) + allowed = dw->isAreaAllowed(toDockWidgetArea(path.at(1))); +#endif +#ifndef QT_NO_TOOLBAR + if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) + allowed = tb->isAreaAllowed(toToolBarArea(path.at(1))); +#endif + + if (!allowed) + path.clear(); + } + + if (path == currentGapPos) + return currentGapPos; // the gap is already there + + currentGapPos = path; + if (path.isEmpty()) { + fixToolBarOrientation(widgetItem, 2); // 2 = top dock, ie. horizontal + restore(true); + return QList<int>(); + } + + fixToolBarOrientation(widgetItem, currentGapPos.at(1)); + + QMainWindowLayoutState newState = savedState; + + if (!newState.insertGap(path, widgetItem)) { + restore(true); // not enough space + return QList<int>(); + } + + QSize min = newState.minimumSize(); + QSize size = newState.rect.size(); + + if (min.width() > size.width() || min.height() > size.height()) { + restore(true); + return QList<int>(); + } + + newState.fitLayout(); + + currentGapRect = newState.gapRect(currentGapPos); + +#ifndef QT_NO_DOCKWIDGET + parentWidget()->update(layoutState.dockAreaLayout.separatorRegion()); +#endif + layoutState = newState; + applyState(layoutState); + + updateGapIndicator(); + + return path; +} + +void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animate) +{ +#ifndef QT_NO_DOCKWIDGET +#ifndef QT_NO_TABBAR + QSet<QTabBar*> used = newState.dockAreaLayout.usedTabBars(); + QSet<QTabBar*> retired = usedTabBars - used; + usedTabBars = used; + foreach (QTabBar *tab_bar, retired) { + tab_bar->hide(); + while (tab_bar->count() > 0) + tab_bar->removeTab(0); + unusedTabBars.append(tab_bar); + } + + if (sep == 1) { + QSet<QWidget*> usedSeps = newState.dockAreaLayout.usedSeparatorWidgets(); + QSet<QWidget*> retiredSeps = usedSeparatorWidgets - usedSeps; + usedSeparatorWidgets = usedSeps; + foreach (QWidget *sepWidget, retiredSeps) { + unusedSeparatorWidgets.append(sepWidget); + } + } + + +#endif // QT_NO_TABBAR +#endif // QT_NO_DOCKWIDGET + newState.apply(dockOptions & QMainWindow::AnimatedDocks && animate); +} + +void QMainWindowLayout::saveState(QDataStream &stream) const +{ + layoutState.saveState(stream); +} + +bool QMainWindowLayout::restoreState(QDataStream &stream) +{ + savedState = layoutState; + layoutState.clear(); + layoutState.rect = savedState.rect; + + if (!layoutState.restoreState(stream, savedState)) { + layoutState.deleteAllLayoutItems(); + layoutState = savedState; + if (parentWidget()->isVisible()) + applyState(layoutState, false); // hides tabBars allocated by newState + return false; + } + + if (parentWidget()->isVisible()) { + layoutState.fitLayout(); + applyState(layoutState, false); + } + + savedState.deleteAllLayoutItems(); + savedState.clear(); + +#ifndef QT_NO_DOCKWIDGET + if (parentWidget()->isVisible()) { +#ifndef QT_NO_TABBAR + foreach (QTabBar *tab_bar, usedTabBars) + tab_bar->show(); + +#endif + } +#endif // QT_NO_DOCKWIDGET + + return true; +} + + +// Returns if this toolbar *should* be using HIToolbar. Won't work for all in between cases +// for example, you have a toolbar in the top area and then you suddenly turn on +// HIToolbar. +bool QMainWindowLayout::usesHIToolBar(QToolBar *toolbar) const +{ +#ifndef Q_WS_MAC + Q_UNUSED(toolbar); + return false; +#else + return qtoolbarsInUnifiedToolbarList.contains(toolbar) + || ((toolBarArea(toolbar) == Qt::TopToolBarArea) + && layoutState.mainWindow->unifiedTitleAndToolBarOnMac()); +#endif +} + +QT_END_NAMESPACE + +#endif // QT_NO_MAINWINDOW diff --git a/src/gui/widgets/qmainwindowlayout_mac.mm b/src/gui/widgets/qmainwindowlayout_mac.mm new file mode 100644 index 0000000..950f758 --- /dev/null +++ b/src/gui/widgets/qmainwindowlayout_mac.mm @@ -0,0 +1,469 @@ +#include <private/qmainwindowlayout_p.h> +#include <qtoolbar.h> +#include <private/qtoolbarlayout_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> + +#ifndef QT_MAC_USE_COCOA +#include <Carbon/Carbon.h> +#else +#include <private/qcocoatoolbardelegate_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE +#ifdef QT_NAMESPACE + +// namespace up the stuff +#define SS(x) #x +#define S0(x) SS(x) +#define S "com.trolltech.qt-" S0(QT_NAMESPACE) ".qmainwindow.qtoolbarInHIToolbar" +#define SToolbar "com.trolltech.qt-" S0(QT_NAMESPACE) ".hitoolbar-qtoolbar" +#define SNSToolbar "com.trolltech.qt-" S0(QT_NAMESPACE) ".qtoolbarInNSToolbar" +#define MacToolbar "com.trolltech.qt-" S0(QT_NAMESPACE) ".qmainwindow.mactoolbar" + +#ifndef QT_MAC_USE_COCOA +static CFStringRef kQToolBarHIToolbarItemClassID = CFSTR(S); +static CFStringRef kQToolBarHIToolbarIdentifier = CFSTR(SToolbar); +#else +static NSString *kQToolBarNSToolbarIdentifier = @SNSToolbar; +#endif +static CFStringRef kQMainWindowMacToolbarID = CFSTR(MacToolbar); +#undef SS +#undef S0 +#undef S +#undef SToolbar +#undef SNSToolbar +#undef MacToolbar + +#else +#ifndef QT_MAC_USE_COCOA +static CFStringRef kQToolBarHIToolbarItemClassID = CFSTR("com.trolltech.qt.qmainwindow.qtoolbarInHIToolbar"); +static CFStringRef kQToolBarHIToolbarIdentifier = CFSTR("com.trolltech.qt.hitoolbar-qtoolbar"); +#else +static NSString *kQToolBarNSToolbarIdentifier = @"com.trolltech.qt.qmainwindow.qtoolbarInNSToolbar"; +#endif +static CFStringRef kQMainWindowMacToolbarID = CFSTR("com.trolltech.qt.qmainwindow.mactoolbar"); +#endif // QT_NAMESPACE + +#ifndef QT_MAC_USE_COCOA + +static const int kEventParamQToolBar = 'QTBR'; +static const int kEventParamQMainWindowLayout = 'QMWL'; + +const EventTypeSpec qtoolbarEvents[] = +{ + { kEventClassHIObject, kEventHIObjectConstruct }, + { kEventClassHIObject, kEventHIObjectDestruct }, + { kEventClassHIObject, kEventHIObjectInitialize }, + { kEventClassToolbarItem, kEventToolbarItemCreateCustomView } +}; + +struct QToolBarInHIToolbarInfo +{ + QToolBarInHIToolbarInfo(HIToolbarItemRef item) + : toolbarItem(item), mainWindowLayout(0) + {} + HIToolbarItemRef toolbarItem; + QMainWindowLayout *mainWindowLayout; +}; + +OSStatus QMainWindowLayout::qtoolbarInHIToolbarHandler(EventHandlerCallRef inCallRef, + EventRef event, void *data) +{ + OSStatus result = eventNotHandledErr; + QToolBarInHIToolbarInfo *object = static_cast<QToolBarInHIToolbarInfo *>(data); + + switch (GetEventClass(event)) { + case kEventClassHIObject: + switch (GetEventKind(event)) { + case kEventHIObjectConstruct: + { + HIObjectRef toolbarItem; + GetEventParameter(event, kEventParamHIObjectInstance, typeHIObjectRef, + 0, sizeof( HIObjectRef ), 0, &toolbarItem); + + QToolBarInHIToolbarInfo *item = new QToolBarInHIToolbarInfo(toolbarItem); + SetEventParameter(event, kEventParamHIObjectInstance, typeVoidPtr, + sizeof(void *), &item); + result = noErr; + } + break; + case kEventHIObjectInitialize: + result = CallNextEventHandler(inCallRef, event); + if (result == noErr) { + QToolBar *toolbar = 0; + QMainWindowLayout *layout = 0; + GetEventParameter(event, kEventParamQToolBar, typeVoidPtr, + 0, sizeof(void *), 0, &toolbar); + GetEventParameter(event, kEventParamQMainWindowLayout, typeVoidPtr, + 0, sizeof(void *), 0, &layout); + object->mainWindowLayout = layout; + object->mainWindowLayout->unifiedToolbarHash.insert(object->toolbarItem, toolbar); + HIToolbarItemChangeAttributes(object->toolbarItem, + kHIToolbarItemLabelDisabled, 0); + } + break; + + case kEventHIObjectDestruct: + delete object; + result = noErr; + break; + } + break; + + case kEventClassToolbarItem: + switch (GetEventKind(event)) + { + case kEventToolbarItemCreateCustomView: + { + QToolBar *toolbar + = object->mainWindowLayout->unifiedToolbarHash.value(object->toolbarItem); + if (toolbar) { + HIViewRef hiview = HIViewRef(toolbar->winId()); + SetEventParameter(event, kEventParamControlRef, typeControlRef, + sizeof(HIViewRef), &hiview); + result = noErr; + } + } + break; + } + break; + } + return result; +} + +void QMainWindowLayout::qtMacHIToolbarRegisterQToolBarInHIToolborItemClass() +{ + static bool registered = false; + + if (!registered) { + HIObjectRegisterSubclass( kQToolBarHIToolbarItemClassID, + kHIToolbarItemClassID, 0, QMainWindowLayout::qtoolbarInHIToolbarHandler, + GetEventTypeCount(qtoolbarEvents), qtoolbarEvents, 0, 0 ); + registered = true; + } +} + +static void GetToolbarAllowedItems(CFMutableArrayRef array) +{ + CFArrayAppendValue(array, kQToolBarHIToolbarIdentifier); +} + +HIToolbarItemRef QMainWindowLayout::createQToolBarInHIToolbarItem(QToolBar *toolbar, + QMainWindowLayout *layout) +{ + QMainWindowLayout::qtMacHIToolbarRegisterQToolBarInHIToolborItemClass(); + + EventRef event; + HIToolbarItemRef result = 0; + + CFStringRef identifier = kQToolBarHIToolbarIdentifier; + UInt32 options = kHIToolbarItemAllowDuplicates; + + CreateEvent(0, kEventClassHIObject, kEventHIObjectInitialize, + GetCurrentEventTime(), 0, &event); + SetEventParameter(event, kEventParamToolbarItemIdentifier, typeCFStringRef, + sizeof(CFStringRef), &identifier); + SetEventParameter(event, kEventParamAttributes, typeUInt32, sizeof(UInt32), &options); + SetEventParameter(event, kEventParamQToolBar, typeVoidPtr, sizeof(void *), &toolbar); + SetEventParameter(event, kEventParamQMainWindowLayout, typeVoidPtr, sizeof(void *), &layout); + + HIObjectCreate(kQToolBarHIToolbarItemClassID, event, + static_cast<HIObjectRef *>(&result)); + + ReleaseEvent(event); + return result; + +} + +HIToolbarItemRef QMainWindowLayout::CreateToolbarItemForIdentifier(CFStringRef identifier, + CFTypeRef data) +{ + HIToolbarItemRef item = 0; + if (CFStringCompare(kQToolBarHIToolbarIdentifier, identifier, + kCFCompareBackwards) == kCFCompareEqualTo) { + if (data && CFGetTypeID(data) == CFArrayGetTypeID()) { + CFArrayRef array = static_cast<CFArrayRef>(data); + QToolBar *toolbar = static_cast<QToolBar *>(const_cast<void *>(CFArrayGetValueAtIndex(array, 0))); + QMainWindowLayout *layout = static_cast<QMainWindowLayout *>(const_cast<void *>(CFArrayGetValueAtIndex(array, 1))); + item = createQToolBarInHIToolbarItem(toolbar, layout); + } + } + return item; +} + +static const EventTypeSpec kToolbarEvents[] = { +{ kEventClassToolbar, kEventToolbarGetDefaultIdentifiers }, +{ kEventClassToolbar, kEventToolbarGetAllowedIdentifiers }, +{ kEventClassToolbar, kEventToolbarCreateItemWithIdentifier }, +{ kEventClassToolbar, kEventToolbarItemAdded }, +{ kEventClassToolbar, kEventToolbarItemRemoved } +}; + +OSStatus QMainWindowLayout::qtmacToolbarDelegate(EventHandlerCallRef, EventRef event, void *data) +{ + QMainWindowLayout *mainWindowLayout = static_cast<QMainWindowLayout *>(data); + OSStatus result = eventNotHandledErr; + CFMutableArrayRef array; + CFStringRef identifier; + switch (GetEventKind(event)) { + case kEventToolbarGetDefaultIdentifiers: + case kEventToolbarGetAllowedIdentifiers: + GetEventParameter(event, kEventParamMutableArray, typeCFMutableArrayRef, 0, + sizeof(CFMutableArrayRef), 0, &array); + GetToolbarAllowedItems(array); + result = noErr; + break; + case kEventToolbarCreateItemWithIdentifier: { + HIToolbarItemRef item; + CFTypeRef data = 0; + OSStatus err = GetEventParameter(event, kEventParamToolbarItemIdentifier, typeCFStringRef, + 0, sizeof(CFStringRef), 0, &identifier); + err = GetEventParameter(event, kEventParamToolbarItemConfigData, typeCFTypeRef, + 0, sizeof(CFTypeRef), 0, &data); + item = CreateToolbarItemForIdentifier(identifier, data); + if (item) { + result = SetEventParameter(event, kEventParamToolbarItem, typeHIToolbarItemRef, + sizeof(HIToolbarItemRef), &item ); + } + break; + } + case kEventToolbarItemAdded: { + // Double check that our "view" of the toolbar is similar. + HIToolbarItemRef item; + CFIndex index; + if (GetEventParameter(event, kEventParamToolbarItem, typeHIToolbarItemRef, + 0, sizeof(HIToolbarItemRef), 0, &item) == noErr + && GetEventParameter(event, kEventParamIndex, typeCFIndex, 0, + sizeof(CFIndex), 0, &index) == noErr) { + CFRetain(item); // We will watch this until it's removed from the list (or bust). + mainWindowLayout->toolbarItemsCopy.insert(index, item); + QToolBar *toolbar = mainWindowLayout->unifiedToolbarHash.value(item); + if (toolbar) { + int toolbarIndex = mainWindowLayout->qtoolbarsInUnifiedToolbarList.indexOf(toolbar); + if (index != toolbarIndex) { + // Dang, we must be out of sync, rebuild it from the "toolbarItemsCopy" + mainWindowLayout->qtoolbarsInUnifiedToolbarList.clear(); + for (int i = 0; i < mainWindowLayout->toolbarItemsCopy.size(); ++i) { + // This will either append the correct toolbar or an + // null toolbar. This is fine because this list + // is really only kept to make sure that things are but in the right order. + mainWindowLayout->qtoolbarsInUnifiedToolbarList.append( + mainWindowLayout->unifiedToolbarHash.value(mainWindowLayout-> + toolbarItemsCopy.at(i))); + } + } + } + } + break; + } + case kEventToolbarItemRemoved: { + HIToolbarItemRef item; + if (GetEventParameter(event, kEventParamToolbarItem, typeHIToolbarItemRef, + 0, sizeof(HIToolbarItemRef), 0, &item) == noErr) { + mainWindowLayout->unifiedToolbarHash.remove(item); + for (int i = 0; i < mainWindowLayout->toolbarItemsCopy.size(); ++i) { + if (mainWindowLayout->toolbarItemsCopy.at(i) == item) { + // I know about it, so release it. + mainWindowLayout->toolbarItemsCopy.removeAt(i); + mainWindowLayout->qtoolbarsInUnifiedToolbarList.removeAt(i); + CFRelease(item); + break; + } + } + } + break; + } + } + return result; +} +#endif // ! QT_MAC_USE_COCOA + +#ifndef kWindowUnifiedTitleAndToolbarAttribute +#define kWindowUnifiedTitleAndToolbarAttribute (1 << 7) +#endif + +void QMainWindowLayout::updateHIToolBarStatus() +{ + bool useMacToolbar = layoutState.mainWindow->unifiedTitleAndToolBarOnMac(); + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { +#ifndef QT_MAC_USE_COCOA + if (useMacToolbar) { + ChangeWindowAttributes(qt_mac_window_for(layoutState.mainWindow), + kWindowUnifiedTitleAndToolbarAttribute, 0); + } else { + ChangeWindowAttributes(qt_mac_window_for(layoutState.mainWindow), + 0, kWindowUnifiedTitleAndToolbarAttribute); + } +#endif + macWindowToolbarShow(layoutState.mainWindow, useMacToolbar); + } + + layoutState.mainWindow->setUpdatesEnabled(false); // reduces a little bit of flicker, not all though + if (!useMacToolbar) { + OSWindowRef windowRef = qt_mac_window_for(parentWidget()); + macWindowToolbarShow(parentWidget(), false); + // Move everything out of the HIToolbar into the main toolbar. + while (!qtoolbarsInUnifiedToolbarList.isEmpty()) { + // Should shrink the list by one every time. + layoutState.mainWindow->addToolBar(Qt::TopToolBarArea, qtoolbarsInUnifiedToolbarList.first()); + } + macWindowToolbarSet(windowRef, NULL); + } else { + QList<QToolBar *> toolbars = layoutState.mainWindow->findChildren<QToolBar *>(); + for (int i = 0; i < toolbars.size(); ++i) { + QToolBar *toolbar = toolbars.at(i); + if (toolBarArea(toolbar) == Qt::TopToolBarArea) { + removeWidget(toolbar); // Do this here, because we are in an in-between state. + layoutState.mainWindow->addToolBar(Qt::TopToolBarArea, toolbar); + } + } + } + layoutState.mainWindow->setUpdatesEnabled(true); +} + +void QMainWindowLayout::insertIntoMacToolbar(QToolBar *before, QToolBar *toolbar) +{ + // This layering could go on to one more level, but I decided to stop here. + // The HIToolbar and NSToolbar APIs are fairly similar as you will see. + if (toolbar == 0) + return; + + + QToolBarLayout *toolbarLayout = static_cast<QToolBarLayout *>(toolbar->layout()); + toolbarSaveState.insert(toolbar, ToolBarSaveState(toolbar->isMovable(), + toolbar->maximumSize())); + + if (toolbarLayout->hasExpandFlag() == false) + toolbar->setMaximumSize(toolbar->sizeHint()); + + toolbar->setMovable(false); + toolbarLayout->setUsePopupMenu(true); + // Make the toolbar a child of the mainwindow to avoid creating a window. + toolbar->setParent(layoutState.mainWindow); + toolbar->createWinId(); // Now create the OSViewRef. + + layoutState.mainWindow->createWinId(); + + OSWindowRef window = qt_mac_window_for(layoutState.mainWindow); + int beforeIndex = qtoolbarsInUnifiedToolbarList.indexOf(before); + if (beforeIndex == -1) + beforeIndex = qtoolbarsInUnifiedToolbarList.size(); + + int toolbarIndex = qtoolbarsInUnifiedToolbarList.indexOf(toolbar); +#ifndef QT_MAC_USE_COCOA + HIToolbarRef macToolbar = NULL; + if ((GetWindowToolbar(window, &macToolbar) == noErr) && !macToolbar) { + HIToolbarCreate(kQMainWindowMacToolbarID, + kHIToolbarItemAllowDuplicates, &macToolbar); + InstallEventHandler(HIObjectGetEventTarget(static_cast<HIToolbarRef>(macToolbar)), + QMainWindowLayout::qtmacToolbarDelegate, GetEventTypeCount(kToolbarEvents), + kToolbarEvents, this, 0); + HIToolbarSetDisplaySize(macToolbar, kHIToolbarDisplaySizeNormal); + HIToolbarSetDisplayMode(macToolbar, kHIToolbarDisplayModeIconOnly); + macWindowToolbarSet(window, macToolbar); + if (layoutState.mainWindow->isVisible()) + macWindowToolbarShow(layoutState.mainWindow, true); + CFRelease(macToolbar); + } +#else + QMacCocoaAutoReleasePool pool; + NSToolbar *macToolbar = [window toolbar]; + if (macToolbar == nil) { + macToolbar = [[NSToolbar alloc] initWithIdentifier:(NSString *)kQMainWindowMacToolbarID]; + [macToolbar setDisplayMode:NSToolbarDisplayModeIconOnly]; + [macToolbar setSizeMode:NSToolbarSizeModeRegular]; + [macToolbar setDelegate:[[QCocoaToolBarDelegate alloc] initWithMainWindowLayout:this]]; + [window setToolbar:macToolbar]; + [macToolbar release]; + } +#endif + if (toolbarIndex != -1) { + qtoolbarsInUnifiedToolbarList.removeAt(toolbarIndex); +#ifndef QT_MAC_USE_COCOA + HIToolbarRemoveItemAtIndex(macToolbar, toolbarIndex); +#else + [macToolbar removeItemAtIndex:toolbarIndex]; +#endif + } + qtoolbarsInUnifiedToolbarList.insert(beforeIndex, toolbar); +#ifndef QT_MAC_USE_COCOA + QCFType<HIToolbarItemRef> outItem; + const QObject *stupidArray[] = { toolbar, this }; + QCFType<CFArrayRef> array = CFArrayCreate(0, reinterpret_cast<const void **>(&stupidArray), + 2, 0); + HIToolbarCreateItemWithIdentifier(macToolbar, kQToolBarHIToolbarIdentifier, + array, &outItem); + HIToolbarInsertItemAtIndex(macToolbar, outItem, beforeIndex); +#else + NSString *toolbarID = kQToolBarNSToolbarIdentifier; + toolbarID = [toolbarID stringByAppendingFormat:@"%p", toolbar]; + cocoaItemIDToToolbarHash.insert(QCFString::toQString(CFStringRef(toolbarID)), toolbar); + [macToolbar insertItemWithItemIdentifier:toolbarID atIndex:beforeIndex]; +#endif +} + +void QMainWindowLayout::removeFromMacToolbar(QToolBar *toolbar) +{ + QHash<void *, QToolBar *>::iterator it = unifiedToolbarHash.begin(); + while (it != unifiedToolbarHash.end()) { + if (it.value() == toolbar) { + // Rescue our HIView and set it on the mainWindow again. + bool saveVisible = !toolbar->isHidden(); + toolbar->setParent(0); + toolbar->setParent(parentWidget()); + toolbar->setVisible(saveVisible); + ToolBarSaveState saveState = toolbarSaveState.value(toolbar); + static_cast<QToolBarLayout *>(toolbar->layout())->setUsePopupMenu(false); + toolbar->setMovable(saveState.movable); + toolbar->setMaximumSize(saveState.maximumSize); + toolbarSaveState.remove(toolbar); +#ifndef QT_MAC_USE_COCOA + HIToolbarItemRef item = static_cast<HIToolbarItemRef>(it.key()); + HIToolbarRemoveItemAtIndex(HIToolbarItemGetToolbar(item), + toolbarItemsCopy.indexOf(item)); +#else + NSToolbarItem *item = static_cast<NSToolbarItem *>(it.key()); + [[qt_mac_window_for(layoutState.mainWindow->window()) toolbar] + removeItemAtIndex:toolbarItemsCopy.indexOf(item)]; + // In Carbon this hash and list gets emptied via events. In Cocoa, we have to do it ourselves here. + it = unifiedToolbarHash.erase(it); + qtoolbarsInUnifiedToolbarList.removeAll(toolbar); +#endif + break; + } + ++it; + } +} + +void QMainWindowLayout::cleanUpMacToolbarItems() +{ + for (int i = 0; i < toolbarItemsCopy.size(); ++i) + CFRelease(toolbarItemsCopy.at(i)); + toolbarItemsCopy.clear(); + unifiedToolbarHash.clear(); +} + +void QMainWindowLayout::fixSizeInUnifiedToolbar(QToolBar *tb) const +{ + QHash<void *, QToolBar *>::const_iterator it = unifiedToolbarHash.constBegin(); + NSToolbarItem *item = nil; + while (it != unifiedToolbarHash.constEnd()) { + if (tb == it.value()) { + item = static_cast<NSToolbarItem *>(it.key()); + break; + } + ++it; + } + if (item) { + QMacCocoaAutoReleasePool pool; + QWidgetItem layoutItem(tb); + QSize size = layoutItem.maximumSize(); + NSSize nssize = NSMakeSize(size.width(), size.height()); + [item setMaxSize:nssize]; + size = layoutItem.minimumSize(); + nssize.width = size.width(); + nssize.height = size.height(); + [item setMinSize:nssize]; + } +} +QT_END_NAMESPACE diff --git a/src/gui/widgets/qmainwindowlayout_p.h b/src/gui/widgets/qmainwindowlayout_p.h new file mode 100644 index 0000000..1159aac --- /dev/null +++ b/src/gui/widgets/qmainwindowlayout_p.h @@ -0,0 +1,374 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICMAINWINDOWLAYOUT_P_H +#define QDYNAMICMAINWINDOWLAYOUT_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 "qmainwindow.h" + +#ifndef QT_NO_MAINWINDOW + +#include "QtGui/qlayout.h" +#include "QtGui/qtabbar.h" +#include "QtCore/qvector.h" +#include "QtCore/qset.h" +#include "private/qlayoutengine_p.h" + +#include "qdockarealayout_p.h" +#include "qtoolbararealayout_p.h" + +//#define Q_DEBUG_MAINWINDOW_LAYOUT + +#ifdef Q_DEBUG_MAINWINDOW_LAYOUT +QT_BEGIN_NAMESPACE +class QTextStream; +Q_GUI_EXPORT void qt_dumpLayout(QTextStream &qout, QMainWindow *window); +QT_END_NAMESPACE +#endif // Q_DEBUG_MAINWINDOW_LAYOUT + +#ifdef Q_WS_MAC +// Forward defs to make avoid including Carbon.h (faster compile you know ;). +struct OpaqueHIObjectRef; +typedef struct OpaqueHIObjectRef* HIObjectRef; +typedef HIObjectRef HIToolbarItemRef; +typedef const void * CFTypeRef; +typedef const struct __CFString * CFStringRef; + +#endif + +QT_BEGIN_NAMESPACE + +class QToolBar; +class QWidgetAnimator; +class QRubberBand; + +/* This data structure represents the state of all the tool-bars and dock-widgets. It's value based + so it can be easilly copied into a temporary variable. All operations are performed without moving + any widgets. Only when we are sure we have the desired state, we call apply(), which moves the + widgets. +*/ + +class QMainWindowLayoutState +{ +public: + QRect rect; + QMainWindow *mainWindow; + + QMainWindowLayoutState(QMainWindow *win); + +#ifndef QT_NO_TOOLBAR + QToolBarAreaLayout toolBarAreaLayout; +#endif + +#ifndef QT_NO_DOCKWIDGET + QDockAreaLayout dockAreaLayout; +#else + QLayoutItem *centralWidgetItem; + QRect centralWidgetRect; +#endif + + void apply(bool animated); + void deleteAllLayoutItems(); + void deleteCentralWidgetItem(); + + QSize sizeHint() const; + QSize minimumSize() const; + void fitLayout(); + + QLayoutItem *itemAt(int index, int *x) const; + QLayoutItem *takeAt(int index, int *x); + QList<int> indexOf(QWidget *widget) const; + QLayoutItem *item(QList<int> path); + QRect itemRect(QList<int> path) const; + QRect gapRect(QList<int> path) const; // ### get rid of this, use itemRect() instead + + bool contains(QWidget *widget) const; + + void setCentralWidget(QWidget *widget); + QWidget *centralWidget() const; + + QList<int> gapIndex(QWidget *widget, const QPoint &pos) const; + bool insertGap(QList<int> path, QLayoutItem *item); + void remove(QList<int> path); + void remove(QLayoutItem *item); + void clear(); + bool isValid() const; + + QLayoutItem *plug(QList<int> path); + QLayoutItem *unplug(QList<int> path, QMainWindowLayoutState *savedState = 0); + + void saveState(QDataStream &stream) const; + bool checkFormat(QDataStream &stream, bool pre43); + bool restoreState(QDataStream &stream, const QMainWindowLayoutState &oldState); +}; + +class Q_AUTOTEST_EXPORT QMainWindowLayout : public QLayout +{ + Q_OBJECT + +public: + QMainWindowLayoutState layoutState, savedState; + + explicit QMainWindowLayout(QMainWindow *mainwindow); + ~QMainWindowLayout(); + + QMainWindow::DockOptions dockOptions; + void setDockOptions(QMainWindow::DockOptions opts); + bool usesHIToolBar(QToolBar *toolbar) const; + + // status bar + + QLayoutItem *statusbar; + +#ifndef QT_NO_STATUSBAR + QStatusBar *statusBar() const; + void setStatusBar(QStatusBar *sb); +#endif + + // central widget + + QWidget *centralWidget() const; + void setCentralWidget(QWidget *cw); + + // toolbars + +#ifndef QT_NO_TOOLBAR + void addToolBarBreak(Qt::ToolBarArea area); + void insertToolBarBreak(QToolBar *before); + void removeToolBarBreak(QToolBar *before); + + void addToolBar(Qt::ToolBarArea area, QToolBar *toolbar, bool needAddChildWidget = true); + void insertToolBar(QToolBar *before, QToolBar *toolbar); + Qt::ToolBarArea toolBarArea(QToolBar *toolbar) const; + bool toolBarBreak(QToolBar *toolBar) const; + void getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const; + void removeToolBar(QToolBar *toolbar); + void toggleToolBarsVisible(); + void moveToolBar(QToolBar *toolbar, int pos); +#endif + + // dock widgets + +#ifndef QT_NO_DOCKWIDGET + void setCorner(Qt::Corner corner, Qt::DockWidgetArea area); + Qt::DockWidgetArea corner(Qt::Corner corner) const; + void addDockWidget(Qt::DockWidgetArea area, + QDockWidget *dockwidget, + Qt::Orientation orientation); + void splitDockWidget(QDockWidget *after, + QDockWidget *dockwidget, + Qt::Orientation orientation); + void tabifyDockWidget(QDockWidget *first, QDockWidget *second); + Qt::DockWidgetArea dockWidgetArea(QDockWidget *dockwidget) const; + void raise(QDockWidget *widget); + void setVerticalTabsEnabled(bool enabled); + bool restoreDockWidget(QDockWidget *dockwidget); + +#ifndef QT_NO_TABBAR + bool _documentMode; + bool documentMode() const; + void setDocumentMode(bool enabled); + + QTabBar *getTabBar(); + QSet<QTabBar*> usedTabBars; + QList<QTabBar*> unusedTabBars; + bool verticalTabsEnabled; + + QWidget *getSeparatorWidget(); + QSet<QWidget*> usedSeparatorWidgets; + QList<QWidget*> unusedSeparatorWidgets; + int sep; // separator extent + +#ifndef QT_NO_TABWIDGET + QTabWidget::TabPosition tabPositions[4]; + QTabWidget::TabShape _tabShape; + + QTabWidget::TabShape tabShape() const; + void setTabShape(QTabWidget::TabShape tabShape); + QTabWidget::TabPosition tabPosition(Qt::DockWidgetArea area) const; + void setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition); +#endif // QT_NO_TABWIDGET +#endif // QT_NO_TABBAR + + // separators + + QList<int> movingSeparator; + QPoint movingSeparatorOrigin, movingSeparatorPos; + QTimer *separatorMoveTimer; + QVector<QLayoutStruct> separatorMoveCache; + + bool startSeparatorMove(const QPoint &pos); + bool separatorMove(const QPoint &pos); + bool endSeparatorMove(const QPoint &pos); + void keepSize(QDockWidget *w); +#endif // QT_NO_DOCKWIDGET + + // save/restore + + enum { // sentinel values used to validate state data + VersionMarker = 0xff + }; + void saveState(QDataStream &stream) const; + bool restoreState(QDataStream &stream); + + // QLayout interface + + void addItem(QLayoutItem *item); + void setGeometry(const QRect &r); + QLayoutItem *itemAt(int index) const; + QLayoutItem *takeAt(int index); + int count() const; + + QSize sizeHint() const; + QSize minimumSize() const; + mutable QSize szHint; + mutable QSize minSize; + void invalidate(); + + // animations + + QWidgetAnimator *widgetAnimator; + QList<int> currentGapPos; + QRect currentGapRect; + QWidget *pluggingWidget; +#ifndef QT_NO_RUBBERBAND + QRubberBand *gapIndicator; +#endif + + QList<int> hover(QLayoutItem *widgetItem, const QPoint &mousePos); + bool plug(QLayoutItem *widgetItem); + QLayoutItem *unplug(QWidget *widget); + void revert(QLayoutItem *widgetItem); + void updateGapIndicator(); + void paintDropIndicator(QPainter *p, QWidget *widget, const QRegion &clip); + void applyState(QMainWindowLayoutState &newState, bool animate = true); + void restore(bool keepSavedState = false); + void updateHIToolBarStatus(); + +private slots: + void animationFinished(QWidget *widget); + void allAnimationsFinished(); +#ifndef QT_NO_DOCKWIDGET + void doSeparatorMove(); +#ifndef QT_NO_TABBAR + void tabChanged(); +#endif +#endif +private: +#ifndef QT_NO_TABBAR + void updateTabBarShapes(); +#endif +#ifdef Q_WS_MAC +# ifndef QT_MAC_USE_COCOA + static OSStatus qtmacToolbarDelegate(EventHandlerCallRef, EventRef , void *); + static OSStatus qtoolbarInHIToolbarHandler(EventHandlerCallRef inCallRef, EventRef event, + void *data); + static void qtMacHIToolbarRegisterQToolBarInHIToolborItemClass(); + static HIToolbarItemRef CreateToolbarItemForIdentifier(CFStringRef identifier, CFTypeRef data); + static HIToolbarItemRef createQToolBarInHIToolbarItem(QToolBar *toolbar, + QMainWindowLayout *layout); +# endif +public: + struct ToolBarSaveState { + ToolBarSaveState() : movable(false) { } + ToolBarSaveState(bool newMovable, const QSize &newMax) + : movable(newMovable), maximumSize(newMax) { } + bool movable; + QSize maximumSize; + }; + QList<QToolBar *> qtoolbarsInUnifiedToolbarList; + QList<void *> toolbarItemsCopy; + QHash<void *, QToolBar *> unifiedToolbarHash; + QHash<QToolBar *, ToolBarSaveState> toolbarSaveState; + QHash<QString, QToolBar *> cocoaItemIDToToolbarHash; + void insertIntoMacToolbar(QToolBar *before, QToolBar *after); + void removeFromMacToolbar(QToolBar *toolbar); + void cleanUpMacToolbarItems(); + void fixSizeInUnifiedToolbar(QToolBar *tb) const; + bool useHIToolBar; +#endif +}; +QT_END_NAMESPACE + +#endif // QT_NO_MAINWINDOW + +QT_BEGIN_NAMESPACE +static inline int pick(Qt::Orientation o, const QPoint &pos) +{ return o == Qt::Horizontal ? pos.x() : pos.y(); } + +static inline int pick(Qt::Orientation o, const QSize &size) +{ return o == Qt::Horizontal ? size.width() : size.height(); } + +static inline int &rpick(Qt::Orientation o, QPoint &pos) +{ return o == Qt::Horizontal ? pos.rx() : pos.ry(); } + +static inline int &rpick(Qt::Orientation o, QSize &size) +{ return o == Qt::Horizontal ? size.rwidth() : size.rheight(); } + +static inline QSizePolicy::Policy pick(Qt::Orientation o, const QSizePolicy &policy) +{ return o == Qt::Horizontal ? policy.horizontalPolicy() : policy.verticalPolicy(); } + +static inline int perp(Qt::Orientation o, const QPoint &pos) +{ return o == Qt::Vertical ? pos.x() : pos.y(); } + +static inline int perp(Qt::Orientation o, const QSize &size) +{ return o == Qt::Vertical ? size.width() : size.height(); } + +static inline int &rperp(Qt::Orientation o, QPoint &pos) +{ return o == Qt::Vertical ? pos.rx() : pos.ry(); } + +static inline int &rperp(Qt::Orientation o, QSize &size) +{ return o == Qt::Vertical ? size.rwidth() : size.rheight(); } + +QT_END_NAMESPACE + +#endif // QDYNAMICMAINWINDOWLAYOUT_P_H diff --git a/src/gui/widgets/qmdiarea.cpp b/src/gui/widgets/qmdiarea.cpp new file mode 100644 index 0000000..598d3b5 --- /dev/null +++ b/src/gui/widgets/qmdiarea.cpp @@ -0,0 +1,2597 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QMdiArea + \brief The QMdiArea widget provides an area in which MDI windows are displayed. + \since 4.3 + \ingroup application + \mainclass + + QMdiArea functions, essentially, like a window manager for MDI + windows. For instance, it draws the windows it manages on itself + and arranges them in a cascading or tile pattern. QMdiArea is + commonly used as the center widget in a QMainWindow to create MDI + applications, but can also be placed in any layout. The following + code adds an area to a main window: + + \snippet doc/src/snippets/mdiareasnippets.cpp 0 + + Unlike the window managers for top-level windows, all window flags + (Qt::WindowFlags) are supported by QMdiArea as long as the flags + are supported by the current widget style. If a specific flag is + not supported by the style (e.g., the + \l{Qt::}{WindowShadeButtonHint}), you can still shade the window + with showShaded(). + + Subwindows in QMdiArea are instances of QMdiSubWindow. They + are added to an MDI area with addSubWindow(). It is common to pass + a QWidget, which is set as the internal widget, to this function, + but it is also possible to pass a QMdiSubWindow directly.The class + inherits QWidget, and you can use the same API as with a normal + top-level window when programming. QMdiSubWindow also has behavior + that is specific to MDI windows. See the QMdiSubWindow class + description for more details. + + A subwindow becomes active when it gets the keyboard focus, or + when setFocus() is called. The user activates a window by moving + focus in the usual ways. The MDI area emits the + subWindowActivated() signal when the active window changes, and + the activeSubWindow() function returns the active subwindow. + + The convenience function subWindowList() returns a list of all + subwindows. This information could be used in a popup menu + containing a list of windows, for example. + + The subwindows are sorted by the the current + \l{QMdiArea::}{WindowOrder}. This is used for the subWindowList() + and for activateNextSubWindow() and acivatePreviousSubWindow(). + Also, it is used when cascading or tiling the windows with + cascadeSubWindows() and tileSubWindows(). + + QMdiArea provides two built-in layout strategies for + subwindows: cascadeSubWindows() and tileSubWindows(). Both are + slots and are easily connected to menu entries. + + \table + \row \o \inlineimage mdi-cascade.png + \o \inlineimage mdi-tile.png + \endtable + + \note The default scroll bar property for QMdiArea is Qt::ScrollBarAlwaysOff. + + \sa QMdiSubWindow +*/ + +/*! + \fn QMdiArea::subWindowActivated(QMdiSubWindow *window) + + QMdiArea emits this signal after \a window has been activated. When \a + window is 0, QMdiArea has just deactivated its last active window, and + there are no active windows on the workspace. + + \sa QMdiArea::activeSubWindow() +*/ + +/*! + \enum QMdiArea::AreaOption + + This enum describes options that customize the behavior of the + QMdiArea. + + \value DontMaximizeSubWindowOnActivation When the active subwindow + is maximized, the default behavior is to maximize the next + subwindow that is activated. Set this option if you do not want + this behavior. +*/ + +/*! + \enum QMdiArea::WindowOrder + + Specifies the criteria to use for ordering the list of child windows + returned by subWindowList(). The functions cascadeSubWindows() and + tileSubWindows() follow this order when arranging the windows. + + \value CreationOrder The windows are returned in the order of + their creation. + + \value StackingOrder The windows are returned in the order in + which they are stacked, with the top-most window being last in + the list. + + \value ActivationHistoryOrder The windows are returned in the order in + which they were activated. + + \sa subWindowList() +*/ + +/*! + \enum QMdiArea::ViewMode + \since 4.4 + + This enum describes the view mode of the area; i.e. how sub-windows + will be displayed. + + \value SubWindowView Display sub-windows with window frames (default). + \value TabbedView Display sub-windows with tabs in a tab bar. + + \sa setViewMode() +*/ + +#include "qmdiarea_p.h" + +#ifndef QT_NO_MDIAREA + +#include <QApplication> +#include <QStyle> +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) +#include <QMacStyle> +#endif +#include <QChildEvent> +#include <QResizeEvent> +#include <QScrollBar> +#include <QtAlgorithms> +#include <QMutableListIterator> +#include <QPainter> +#include <QFontMetrics> +#include <QStyleOption> +#include <QDesktopWidget> +#include <QDebug> +#include <qmath.h> +#include <private/qlayoutengine_p.h> + +QT_BEGIN_NAMESPACE + +using namespace QMdi; + +// Asserts in debug mode, gives warning otherwise. +static bool sanityCheck(const QMdiSubWindow * const child, const char *where) +{ + if (!child) { + const char error[] = "null pointer"; + Q_ASSERT_X(false, where, error); + qWarning("%s:%s", where, error); + return false; + } + return true; +} + +static bool sanityCheck(const QList<QWidget *> &widgets, const int index, const char *where) +{ + if (index < 0 || index >= widgets.size()) { + const char error[] = "index out of range"; + Q_ASSERT_X(false, where, error); + qWarning("%s:%s", where, error); + return false; + } + if (!widgets.at(index)) { + const char error[] = "null pointer"; + Q_ASSERT_X(false, where, error); + qWarning("%s:%s", where, error); + return false; + } + return true; +} + +static void setIndex(int *index, int candidate, int min, int max, bool isIncreasing) +{ + if (!index) + return; + + if (isIncreasing) { + if (candidate > max) + *index = min; + else + *index = qMax(candidate, min); + } else { + if (candidate < min) + *index = max; + else + *index = qMin(candidate, max); + } + Q_ASSERT(*index >= min && *index <= max); +} + +static inline bool useScrollBar(const QRect &childrenRect, const QSize &maxViewportSize, + Qt::Orientation orientation) +{ + if (orientation == Qt::Horizontal) + return childrenRect.width() > maxViewportSize.width() + || childrenRect.left() < 0 + || childrenRect.right() >= maxViewportSize.width(); + else + return childrenRect.height() > maxViewportSize.height() + || childrenRect.top() < 0 + || childrenRect.bottom() >= maxViewportSize.height(); +} + +// Returns the closest mdi area containing the widget (if any). +static inline QMdiArea *mdiAreaParent(QWidget *widget) +{ + if (!widget) + return 0; + + QWidget *parent = widget->parentWidget(); + while (parent) { + if (QMdiArea *area = qobject_cast<QMdiArea *>(parent)) + return area; + parent = parent->parentWidget(); + } + return 0; +} + +#ifndef QT_NO_TABWIDGET +static inline QTabBar::Shape tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position) +{ + const bool rounded = (shape == QTabWidget::Rounded); + if (position == QTabWidget::North) + return rounded ? QTabBar::RoundedNorth : QTabBar::TriangularNorth; + if (position == QTabWidget::South) + return rounded ? QTabBar::RoundedSouth : QTabBar::TriangularSouth; + if (position == QTabWidget::East) + return rounded ? QTabBar::RoundedEast : QTabBar::TriangularEast; + if (position == QTabWidget::West) + return rounded ? QTabBar::RoundedWest : QTabBar::TriangularWest; + return QTabBar::RoundedNorth; +} +#endif // QT_NO_TABWIDGET + +static inline QString tabTextFor(QMdiSubWindow *subWindow) +{ + if (!subWindow) + return QString(); + + QString title = subWindow->windowTitle(); + if (subWindow->isWindowModified()) { + title.replace(QLatin1String("[*]"), QLatin1String("*")); + } else { + extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); + title = qt_setWindowTitle_helperHelper(title, subWindow); + } + + return title.isEmpty() ? QMdiArea::tr("(Untitled)") : title; +} + +/*! + \internal +*/ +void RegularTiler::rearrange(QList<QWidget *> &widgets, const QRect &domain) const +{ + if (widgets.isEmpty()) + return; + + const int n = widgets.size(); + const int ncols = qMax(qCeil(qSqrt(qreal(n))), 1); + const int nrows = qMax((n % ncols) ? (n / ncols + 1) : (n / ncols), 1); + const int nspecial = (n % ncols) ? (ncols - n % ncols) : 0; + const int dx = domain.width() / ncols; + const int dy = domain.height() / nrows; + + int i = 0; + for (int row = 0; row < nrows; ++row) { + const int y1 = int(row * (dy + 1)); + for (int col = 0; col < ncols; ++col) { + if (row == 1 && col < nspecial) + continue; + const int x1 = int(col * (dx + 1)); + int x2 = int(x1 + dx); + int y2 = int(y1 + dy); + if (row == 0 && col < nspecial) { + y2 *= 2; + if (nrows != 2) + y2 += 1; + else + y2 = domain.bottom(); + } + if (col == ncols - 1 && x2 != domain.right()) + x2 = domain.right(); + if (row == nrows - 1 && y2 != domain.bottom()) + y2 = domain.bottom(); + if (!sanityCheck(widgets, i, "RegularTiler")) + continue; + QWidget *widget = widgets.at(i++); + QRect newGeometry = QRect(QPoint(x1, y1), QPoint(x2, y2)); + widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry)); + } + } +} + +/*! + \internal +*/ +void SimpleCascader::rearrange(QList<QWidget *> &widgets, const QRect &domain) const +{ + if (widgets.isEmpty()) + return; + + // Tunables: + const int topOffset = 0; + const int bottomOffset = 50; + const int leftOffset = 0; + const int rightOffset = 100; + const int dx = 10; + + QStyleOptionTitleBar options; + options.initFrom(widgets.at(0)); + int titleBarHeight = widgets.at(0)->style()->pixelMetric(QStyle::PM_TitleBarHeight, &options, widgets.at(0)); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + // ### Remove this after the mac style has been fixed + if (qobject_cast<QMacStyle *>(widgets.at(0)->style())) + titleBarHeight -= 4; +#endif + const QFontMetrics fontMetrics = QFontMetrics(QApplication::font("QWorkspaceTitleBar")); + const int dy = qMax(titleBarHeight - (titleBarHeight - fontMetrics.height()) / 2, 1); + + const int n = widgets.size(); + const int nrows = qMax((domain.height() - (topOffset + bottomOffset)) / dy, 1); + const int ncols = qMax(n / nrows + ((n % nrows) ? 1 : 0), 1); + const int dcol = (domain.width() - (leftOffset + rightOffset)) / ncols; + + int i = 0; + for (int row = 0; row < nrows; ++row) { + for (int col = 0; col < ncols; ++col) { + const int x = leftOffset + row * dx + col * dcol; + const int y = topOffset + row * dy; + if (!sanityCheck(widgets, i, "SimpleCascader")) + continue; + QWidget *widget = widgets.at(i++); + QRect newGeometry = QRect(QPoint(x, y), widget->sizeHint()); + widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry)); + if (i == n) + return; + } + } +} + +/*! + \internal +*/ +void IconTiler::rearrange(QList<QWidget *> &widgets, const QRect &domain) const +{ + if (widgets.isEmpty() || !sanityCheck(widgets, 0, "IconTiler")) + return; + + const int n = widgets.size(); + const int width = widgets.at(0)->width(); + const int height = widgets.at(0)->height(); + const int ncols = qMax(domain.width() / width, 1); + const int nrows = n / ncols + ((n % ncols) ? 1 : 0); + + int i = 0; + for (int row = 0; row < nrows; ++row) { + for (int col = 0; col < ncols; ++col) { + const int x = col * width; + const int y = domain.height() - height - row * height; + if (!sanityCheck(widgets, i, "IconTiler")) + continue; + QWidget *widget = widgets.at(i++); + QPoint newPos(x, y); + QRect newGeometry = QRect(newPos.x(), newPos.y(), widget->width(), widget->height()); + widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry)); + if (i == n) + return; + } + } +} + +/*! + \internal + Calculates the accumulated overlap (intersection area) between 'source' and 'rects'. +*/ +int MinOverlapPlacer::accumulatedOverlap(const QRect &source, const QList<QRect> &rects) +{ + int accOverlap = 0; + foreach (const QRect &rect, rects) { + QRect intersection = source.intersected(rect); + accOverlap += intersection.width() * intersection.height(); + } + return accOverlap; +} + + +/*! + \internal + Finds among 'source' the rectangle with the minimum accumulated overlap with the + rectangles in 'rects'. +*/ +QRect MinOverlapPlacer::findMinOverlapRect(const QList<QRect> &source, const QList<QRect> &rects) +{ + int minAccOverlap = -1; + QRect minAccOverlapRect; + foreach (const QRect &srcRect, source) { + const int accOverlap = accumulatedOverlap(srcRect, rects); + if (accOverlap < minAccOverlap || minAccOverlap == -1) { + minAccOverlap = accOverlap; + minAccOverlapRect = srcRect; + } + } + return minAccOverlapRect; +} + +/*! + \internal + Gets candidates for the final placement. +*/ +void MinOverlapPlacer::getCandidatePlacements(const QSize &size, const QList<QRect> &rects, + const QRect &domain,QList<QRect> &candidates) +{ + QSet<int> xset; + QSet<int> yset; + xset << domain.left() << domain.right() - size.width() + 1; + yset << domain.top(); + if (domain.bottom() - size.height() + 1 >= 0) + yset << domain.bottom() - size.height() + 1; + foreach (const QRect &rect, rects) { + xset << rect.right() + 1; + yset << rect.bottom() + 1; + } + + QList<int> xlist = xset.values(); + qSort(xlist.begin(), xlist.end()); + QList<int> ylist = yset.values(); + qSort(ylist.begin(), ylist.end()); + + foreach (int y, ylist) + foreach (int x, xlist) + candidates << QRect(QPoint(x, y), size); +} + +/*! + \internal + Finds all rectangles in 'source' not completely inside 'domain'. The result is stored + in 'result' and also removed from 'source'. +*/ +void MinOverlapPlacer::findNonInsiders(const QRect &domain, QList<QRect> &source, + QList<QRect> &result) +{ + QMutableListIterator<QRect> it(source); + while (it.hasNext()) { + const QRect srcRect = it.next(); + if (!domain.contains(srcRect)) { + result << srcRect; + it.remove(); + } + } +} + +/*! + \internal + Finds all rectangles in 'source' that overlaps 'domain' by the maximum overlap area + between 'domain' and any rectangle in 'source'. The result is stored in 'result'. +*/ +void MinOverlapPlacer::findMaxOverlappers(const QRect &domain, const QList<QRect> &source, + QList<QRect> &result) +{ + int maxOverlap = -1; + foreach (const QRect &srcRect, source) { + QRect intersection = domain.intersected(srcRect); + const int overlap = intersection.width() * intersection.height(); + if (overlap >= maxOverlap || maxOverlap == -1) { + if (overlap > maxOverlap) { + maxOverlap = overlap; + result.clear(); + } + result << srcRect; + } + } +} + +/*! + \internal + Finds among the rectangles in 'source' the best placement. Here, 'best' means the + placement that overlaps the rectangles in 'rects' as little as possible while at the + same time being as much as possible inside 'domain'. +*/ +QPoint MinOverlapPlacer::findBestPlacement(const QRect &domain, const QList<QRect> &rects, + QList<QRect> &source) +{ + QList<QRect> nonInsiders; + findNonInsiders(domain, source, nonInsiders); + + if (!source.empty()) + return findMinOverlapRect(source, rects).topLeft(); + + QList<QRect> maxOverlappers; + findMaxOverlappers(domain, nonInsiders, maxOverlappers); + return findMinOverlapRect(maxOverlappers, rects).topLeft(); +} + + +/*! + \internal + Places the rectangle defined by 'size' relative to 'rects' and 'domain' so that it + overlaps 'rects' as little as possible and 'domain' as much as possible. + Returns the position of the resulting rectangle. +*/ +QPoint MinOverlapPlacer::place(const QSize &size, const QList<QRect> &rects, + const QRect &domain) const +{ + if (size.isEmpty() || !domain.isValid()) + return QPoint(); + foreach (const QRect &rect, rects) { + if (!rect.isValid()) + return QPoint(); + } + + QList<QRect> candidates; + getCandidatePlacements(size, rects, domain, candidates); + return findBestPlacement(domain, rects, candidates); +} + +#ifndef QT_NO_TABBAR +class QMdiAreaTabBar : public QTabBar +{ +public: + QMdiAreaTabBar(QWidget *parent) : QTabBar(parent) {} + +protected: + void mousePressEvent(QMouseEvent *event); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *event); +#endif + +private: + QMdiSubWindow *subWindowFromIndex(int index) const; +}; + +/*! + \internal +*/ +void QMdiAreaTabBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::MidButton) { + QTabBar::mousePressEvent(event); + return; + } + + QMdiSubWindow *subWindow = subWindowFromIndex(tabAt(event->pos())); + if (!subWindow) { + event->ignore(); + return; + } + + subWindow->close(); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \internal +*/ +void QMdiAreaTabBar::contextMenuEvent(QContextMenuEvent *event) +{ + QPointer<QMdiSubWindow> subWindow = subWindowFromIndex(tabAt(event->pos())); + if (!subWindow || subWindow->isHidden()) { + event->ignore(); + return; + } + +#ifndef QT_NO_MENU + QMdiSubWindowPrivate *subWindowPrivate = subWindow->d_func(); + if (!subWindowPrivate->systemMenu) { + event->ignore(); + return; + } + + QMdiSubWindow *currentSubWindow = subWindowFromIndex(currentIndex()); + Q_ASSERT(currentSubWindow); + + // We don't want these actions to show up in the system menu when the + // current sub-window is maximized, i.e. covers the entire viewport. + if (currentSubWindow->isMaximized()) { + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MoveAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::ResizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MinimizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MaximizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MaximizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::StayOnTopAction, false); + } + + // Show system menu. + subWindowPrivate->systemMenu->exec(event->globalPos()); + if (!subWindow) + return; + + // Restore action visibility. + subWindowPrivate->updateActions(); +#endif // QT_NO_MENU +} +#endif // QT_NO_CONTEXTMENU + +/*! + \internal +*/ +QMdiSubWindow *QMdiAreaTabBar::subWindowFromIndex(int index) const +{ + if (index < 0 || index >= count()) + return 0; + + QMdiArea *mdiArea = qobject_cast<QMdiArea *>(parentWidget()); + Q_ASSERT(mdiArea); + + const QList<QMdiSubWindow *> subWindows = mdiArea->subWindowList(); + Q_ASSERT(index < subWindows.size()); + + QMdiSubWindow *subWindow = mdiArea->subWindowList().at(index); + Q_ASSERT(subWindow); + + return subWindow; +} +#endif // QT_NO_TABBAR + +/*! + \internal +*/ +QMdiAreaPrivate::QMdiAreaPrivate() + : cascader(0), + regularTiler(0), + iconTiler(0), + placer(0), +#ifndef QT_NO_RUBBERBAND + rubberBand(0), +#endif +#ifndef QT_NO_TABBAR + tabBar(0), +#endif + activationOrder(QMdiArea::CreationOrder), + viewMode(QMdiArea::SubWindowView), +#ifndef QT_NO_TABBAR + documentMode(false), +#endif +#ifndef QT_NO_TABWIDGET + tabShape(QTabWidget::Rounded), + tabPosition(QTabWidget::North), +#endif + ignoreGeometryChange(false), + ignoreWindowStateChange(false), + isActivated(false), + isSubWindowsTiled(false), + showActiveWindowMaximized(false), + tileCalledFromResizeEvent(false), + updatesDisabledByUs(false), + inViewModeChange(false), + indexToNextWindow(-1), + indexToPreviousWindow(-1), + indexToHighlighted(-1), + indexToLastActiveTab(-1), + resizeTimerId(-1), + tabToPreviousTimerId(-1) +{ +} + +/*! + \internal +*/ +void QMdiAreaPrivate::_q_deactivateAllWindows(QMdiSubWindow *aboutToActivate) +{ + if (ignoreWindowStateChange) + return; + + Q_Q(QMdiArea); + if (!aboutToActivate) + aboutToBecomeActive = qobject_cast<QMdiSubWindow *>(q->sender()); + else + aboutToBecomeActive = aboutToActivate; + Q_ASSERT(aboutToBecomeActive); + + foreach (QMdiSubWindow *child, childWindows) { + if (!sanityCheck(child, "QMdiArea::deactivateAllWindows") || aboutToBecomeActive == child) + continue; + // We don't want to handle signals caused by child->showNormal(). + ignoreWindowStateChange = true; + if(!(options & QMdiArea::DontMaximizeSubWindowOnActivation) && !showActiveWindowMaximized) + showActiveWindowMaximized = child->isMaximized() && child->isVisible(); + if (showActiveWindowMaximized && child->isMaximized()) { + if (q->updatesEnabled()) { + updatesDisabledByUs = true; + q->setUpdatesEnabled(false); + } + child->showNormal(); + } + if (child->isMinimized() && !child->isShaded() && !windowStaysOnTop(child)) + child->lower(); + ignoreWindowStateChange = false; + child->d_func()->setActive(false); + } +} + +/*! + \internal +*/ +void QMdiAreaPrivate::_q_processWindowStateChanged(Qt::WindowStates oldState, + Qt::WindowStates newState) +{ + if (ignoreWindowStateChange) + return; + + Q_Q(QMdiArea); + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(q->sender()); + if (!child) + return; + + // windowActivated + if (!(oldState & Qt::WindowActive) && (newState & Qt::WindowActive)) + emitWindowActivated(child); + // windowDeactivated + else if ((oldState & Qt::WindowActive) && !(newState & Qt::WindowActive)) + resetActiveWindow(child); + + // windowMinimized + if (!(oldState & Qt::WindowMinimized) && (newState & Qt::WindowMinimized)) { + isSubWindowsTiled = false; + arrangeMinimizedSubWindows(); + // windowMaximized + } else if (!(oldState & Qt::WindowMaximized) && (newState & Qt::WindowMaximized)) { + internalRaise(child); + // windowRestored + } else if (!(newState & (Qt::WindowMaximized | Qt::WindowMinimized))) { + internalRaise(child); + if (oldState & Qt::WindowMinimized) + arrangeMinimizedSubWindows(); + } +} + +void QMdiAreaPrivate::_q_currentTabChanged(int index) +{ +#ifdef QT_NO_TABBAR + Q_UNUSED(index); +#else + if (!tabBar || index < 0) + return; + + // If the previous active sub-window was hidden, disable the tab. + if (indexToLastActiveTab >= 0 && indexToLastActiveTab < tabBar->count() + && indexToLastActiveTab < childWindows.count()) { + QMdiSubWindow *lastActive = childWindows.at(indexToLastActiveTab); + if (lastActive && lastActive->isHidden()) + tabBar->setTabEnabled(indexToLastActiveTab, false); + } + + indexToLastActiveTab = index; + Q_ASSERT(childWindows.size() > index); + QMdiSubWindow *subWindow = childWindows.at(index); + Q_ASSERT(subWindow); + activateWindow(subWindow); +#endif // QT_NO_TABBAR +} + +/*! + \internal +*/ +void QMdiAreaPrivate::appendChild(QMdiSubWindow *child) +{ + Q_Q(QMdiArea); + Q_ASSERT(child && childWindows.indexOf(child) == -1); + + if (child->parent() != q->viewport()) + child->setParent(q->viewport(), child->windowFlags()); + childWindows.append(QPointer<QMdiSubWindow>(child)); + + if (!child->testAttribute(Qt::WA_Resized) && q->isVisible()) { + QSize newSize(child->sizeHint().boundedTo(q->viewport()->size())); + child->resize(newSize.expandedTo(qSmartMinSize(child))); + } + + if (!placer) + placer = new MinOverlapPlacer; + place(placer, child); + + if (hbarpolicy != Qt::ScrollBarAlwaysOff) + child->setOption(QMdiSubWindow::AllowOutsideAreaHorizontally, true); + else + child->setOption(QMdiSubWindow::AllowOutsideAreaHorizontally, false); + + if (vbarpolicy != Qt::ScrollBarAlwaysOff) + child->setOption(QMdiSubWindow::AllowOutsideAreaVertically, true); + else + child->setOption(QMdiSubWindow::AllowOutsideAreaVertically, false); + + internalRaise(child); + indicesToActivatedChildren.prepend(childWindows.size() - 1); + Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size()); + +#ifndef QT_NO_TABBAR + if (tabBar) { + tabBar->addTab(child->windowIcon(), tabTextFor(child)); + updateTabBarGeometry(); + if (childWindows.count() == 1 && !(options & QMdiArea::DontMaximizeSubWindowOnActivation)) + showActiveWindowMaximized = true; + } +#endif + + if (!(child->windowFlags() & Qt::SubWindow)) + child->setWindowFlags(Qt::SubWindow); + child->installEventFilter(q); + + QObject::connect(child, SIGNAL(aboutToActivate()), q, SLOT(_q_deactivateAllWindows())); + QObject::connect(child, SIGNAL(windowStateChanged(Qt::WindowStates, Qt::WindowStates)), + q, SLOT(_q_processWindowStateChanged(Qt::WindowStates, Qt::WindowStates))); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::place(Placer *placer, QMdiSubWindow *child) +{ + if (!placer || !child) + return; + + Q_Q(QMdiArea); + if (!q->isVisible()) { + // The window is only laid out when it's added to QMdiArea, + // so there's no need to check that we don't have it in the + // list already. appendChild() ensures that. + pendingPlacements.append(child); + return; + } + + QList<QRect> rects; + QRect parentRect = q->rect(); + foreach (QMdiSubWindow *window, childWindows) { + if (!sanityCheck(window, "QMdiArea::place") || window == child || !window->isVisibleTo(q) + || !window->testAttribute(Qt::WA_Moved)) { + continue; + } + QRect occupiedGeometry; + if (window->isMaximized()) { + occupiedGeometry = QRect(window->d_func()->oldGeometry.topLeft(), + window->d_func()->restoreSize); + } else { + occupiedGeometry = window->geometry(); + } + rects.append(QStyle::visualRect(child->layoutDirection(), parentRect, occupiedGeometry)); + } + QPoint newPos = placer->place(child->size(), rects, parentRect); + QRect newGeometry = QRect(newPos.x(), newPos.y(), child->width(), child->height()); + child->setGeometry(QStyle::visualRect(child->layoutDirection(), parentRect, newGeometry)); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::rearrange(Rearranger *rearranger) +{ + if (!rearranger) + return; + + Q_Q(QMdiArea); + if (!q->isVisible()) { + // Compress if we already have the rearranger in the list. + int index = pendingRearrangements.indexOf(rearranger); + if (index != -1) + pendingRearrangements.move(index, pendingRearrangements.size() - 1); + else + pendingRearrangements.append(rearranger); + return; + } + + QList<QWidget *> widgets; + const bool reverseList = rearranger->type() == Rearranger::RegularTiler; + const QList<QMdiSubWindow *> subWindows = subWindowList(activationOrder, reverseList); + QSize minSubWindowSize; + foreach (QMdiSubWindow *child, subWindows) { + if (!sanityCheck(child, "QMdiArea::rearrange") || !child->isVisible()) + continue; + if (rearranger->type() == Rearranger::IconTiler) { + if (child->isMinimized() && !child->isShaded() && !(child->windowFlags() & Qt::FramelessWindowHint)) + widgets.append(child); + } else { + if (child->isMinimized() && !child->isShaded()) + continue; + if (child->isMaximized() || child->isShaded()) + child->showNormal(); + minSubWindowSize = minSubWindowSize.expandedTo(child->minimumSize()) + .expandedTo(child->d_func()->internalMinimumSize); + widgets.append(child); + } + } + + if (active && rearranger->type() == Rearranger::RegularTiler) { + // Move active window in front if necessary. That's the case if we + // have any windows with staysOnTopHint set. + int indexToActive = widgets.indexOf((QWidget *)active); + if (indexToActive > 0) + widgets.move(indexToActive, 0); + } + + QRect domain = q->viewport()->rect(); + if (rearranger->type() == Rearranger::RegularTiler && !widgets.isEmpty()) + domain = resizeToMinimumTileSize(minSubWindowSize, widgets.count()); + + rearranger->rearrange(widgets, domain); + + if (rearranger->type() == Rearranger::RegularTiler && !widgets.isEmpty()) { + isSubWindowsTiled = true; + updateScrollBars(); + } else if (rearranger->type() == Rearranger::SimpleCascader) { + isSubWindowsTiled = false; + } +} + +/*! + \internal + + Arranges all minimized windows at the bottom of the workspace. +*/ +void QMdiAreaPrivate::arrangeMinimizedSubWindows() +{ + if (!iconTiler) + iconTiler = new IconTiler; + rearrange(iconTiler); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::activateWindow(QMdiSubWindow *child) +{ + if (childWindows.isEmpty()) { + Q_ASSERT(!child); + Q_ASSERT(!active); + return; + } + + if (!child) { + if (active) { + Q_ASSERT(active->d_func()->isActive); + active->d_func()->setActive(false); + resetActiveWindow(); + } + return; + } + + if (child->isHidden() || child == active) + return; + child->d_func()->setActive(true); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::activateCurrentWindow() +{ + QMdiSubWindow *current = q_func()->currentSubWindow(); + if (current && !isExplicitlyDeactivated(current)) { + current->d_func()->activationEnabled = true; + current->d_func()->setActive(true, /*changeFocus=*/false); + } +} + +void QMdiAreaPrivate::activateHighlightedWindow() +{ + if (indexToHighlighted < 0) + return; + + Q_ASSERT(indexToHighlighted < childWindows.size()); + if (tabToPreviousTimerId != -1) + activateWindow(nextVisibleSubWindow(-1, QMdiArea::ActivationHistoryOrder)); + else + activateWindow(childWindows.at(indexToHighlighted)); +#ifndef QT_NO_RUBBERBAND + hideRubberBand(); +#endif +} + +/*! + \internal +*/ +void QMdiAreaPrivate::emitWindowActivated(QMdiSubWindow *activeWindow) +{ + Q_Q(QMdiArea); + Q_ASSERT(activeWindow); + if (activeWindow == active) + return; + Q_ASSERT(activeWindow->d_func()->isActive); + + if (!aboutToBecomeActive) + _q_deactivateAllWindows(activeWindow); + Q_ASSERT(aboutToBecomeActive); + + // This is true only if 'DontMaximizeSubWindowOnActivation' is disabled + // and the previous active window was maximized. + if (showActiveWindowMaximized) { + if (!activeWindow->isMaximized()) + activeWindow->showMaximized(); + showActiveWindowMaximized = false; + } + + // Put in front to update activation order. + const int indexToActiveWindow = childWindows.indexOf(activeWindow); + Q_ASSERT(indexToActiveWindow != -1); + const int index = indicesToActivatedChildren.indexOf(indexToActiveWindow); + Q_ASSERT(index != -1); + indicesToActivatedChildren.move(index, 0); + internalRaise(activeWindow); + + if (updatesDisabledByUs) { + q->setUpdatesEnabled(true); + updatesDisabledByUs = false; + } + + Q_ASSERT(aboutToBecomeActive == activeWindow); + active = activeWindow; + aboutToBecomeActive = 0; + Q_ASSERT(active->d_func()->isActive); + +#ifndef QT_NO_TABBAR + if (tabBar && tabBar->currentIndex() != indexToActiveWindow) + tabBar->setCurrentIndex(indexToActiveWindow); +#endif + + if (active->isMaximized() && scrollBarsEnabled()) + updateScrollBars(); + + emit q->subWindowActivated(active); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::resetActiveWindow(QMdiSubWindow *deactivatedWindow) +{ + Q_Q(QMdiArea); + if (deactivatedWindow) { + if (deactivatedWindow != active) + return; + active = 0; + if ((aboutToBecomeActive || isActivated || lastWindowAboutToBeDestroyed()) + && !isExplicitlyDeactivated(deactivatedWindow) && !q->window()->isMinimized()) { + return; + } + emit q->subWindowActivated(0); + return; + } + + if (aboutToBecomeActive) + return; + + active = 0; + emit q->subWindowActivated(0); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::updateActiveWindow(int removedIndex, bool activeRemoved) +{ + Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size()); + +#ifndef QT_NO_TABBAR + if (tabBar && removedIndex >= 0) { + tabBar->blockSignals(true); + tabBar->removeTab(removedIndex); + updateTabBarGeometry(); + tabBar->blockSignals(false); + } +#endif + + if (childWindows.isEmpty()) { + showActiveWindowMaximized = false; + resetActiveWindow(); + return; + } + + if (indexToHighlighted >= 0) { +#ifndef QT_NO_RUBBERBAND + // Hide rubber band if highlighted window is removed. + if (indexToHighlighted == removedIndex) + hideRubberBand(); + else +#endif + // or update index if necessary. + if (indexToHighlighted > removedIndex) + --indexToHighlighted; + } + + // Update indices list + for (int i = 0; i < indicesToActivatedChildren.size(); ++i) { + int *index = &indicesToActivatedChildren[i]; + if (*index > removedIndex) + --*index; + } + + if (!activeRemoved) + return; + + // Activate next window. + QMdiSubWindow *next = nextVisibleSubWindow(0, activationOrder, removedIndex); + if (next) + activateWindow(next); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::updateScrollBars() +{ + if (ignoreGeometryChange || !scrollBarsEnabled()) + return; + + Q_Q(QMdiArea); + QSize maxSize = q->maximumViewportSize(); + QSize hbarExtent = hbar->sizeHint(); + QSize vbarExtent = vbar->sizeHint(); + + if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, q)) { + const int doubleFrameWidth = frameWidth * 2; + if (hbarpolicy == Qt::ScrollBarAlwaysOn) + maxSize.rheight() -= doubleFrameWidth; + if (vbarpolicy == Qt::ScrollBarAlwaysOn) + maxSize.rwidth() -= doubleFrameWidth; + hbarExtent.rheight() += doubleFrameWidth; + vbarExtent.rwidth() += doubleFrameWidth; + } + + const QRect childrenRect = active && active->isMaximized() + ? active->geometry() : viewport->childrenRect(); + bool useHorizontalScrollBar = useScrollBar(childrenRect, maxSize, Qt::Horizontal); + bool useVerticalScrollBar = useScrollBar(childrenRect, maxSize, Qt::Vertical); + + if (useHorizontalScrollBar && !useVerticalScrollBar) { + const QSize max = maxSize - QSize(0, hbarExtent.height()); + useVerticalScrollBar = useScrollBar(childrenRect, max, Qt::Vertical); + } + + if (useVerticalScrollBar && !useHorizontalScrollBar) { + const QSize max = maxSize - QSize(vbarExtent.width(), 0); + useHorizontalScrollBar = useScrollBar(childrenRect, max, Qt::Horizontal); + } + + if (useHorizontalScrollBar && hbarpolicy != Qt::ScrollBarAlwaysOn) + maxSize.rheight() -= hbarExtent.height(); + if (useVerticalScrollBar && vbarpolicy != Qt::ScrollBarAlwaysOn) + maxSize.rwidth() -= vbarExtent.width(); + + QRect viewportRect(QPoint(0, 0), maxSize); + const int startX = q->isLeftToRight() ? childrenRect.left() : viewportRect.right() + - childrenRect.right(); + + // Horizontal scroll bar. + if (isSubWindowsTiled && hbar->value() != 0) + hbar->setValue(0); + const int xOffset = startX + hbar->value(); + hbar->setRange(qMin(0, xOffset), + qMax(0, xOffset + childrenRect.width() - viewportRect.width())); + hbar->setPageStep(childrenRect.width()); + hbar->setSingleStep(childrenRect.width() / 20); + + // Vertical scroll bar. + if (isSubWindowsTiled && vbar->value() != 0) + vbar->setValue(0); + const int yOffset = childrenRect.top() + vbar->value(); + vbar->setRange(qMin(0, yOffset), + qMax(0, yOffset + childrenRect.height() - viewportRect.height())); + vbar->setPageStep(childrenRect.height()); + vbar->setSingleStep(childrenRect.height() / 20); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::internalRaise(QMdiSubWindow *mdiChild) const +{ + if (!sanityCheck(mdiChild, "QMdiArea::internalRaise") || childWindows.size() < 2) + return; + + QMdiSubWindow *stackUnderChild = 0; + if (!windowStaysOnTop(mdiChild)) { + foreach (QObject *object, q_func()->viewport()->children()) { + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object); + if (!child || !childWindows.contains(child)) + continue; + if (!child->isHidden() && windowStaysOnTop(child)) { + if (stackUnderChild) + child->stackUnder(stackUnderChild); + else + child->raise(); + stackUnderChild = child; + } + } + } + + if (stackUnderChild) + mdiChild->stackUnder(stackUnderChild); + else + mdiChild->raise(); +} + +QRect QMdiAreaPrivate::resizeToMinimumTileSize(const QSize &minSubWindowSize, int subWindowCount) +{ + Q_Q(QMdiArea); + if (!minSubWindowSize.isValid() || subWindowCount <= 0) + return q->viewport()->rect(); + + // Calculate minimum size. + const int columns = qMax(qCeil(qSqrt(qreal(subWindowCount))), 1); + const int rows = qMax((subWindowCount % columns) ? (subWindowCount / columns + 1) + : (subWindowCount / columns), 1); + const int minWidth = minSubWindowSize.width() * columns; + const int minHeight = minSubWindowSize.height() * rows; + + // Increase area size if necessary. Scroll bars are provided if we're not able + // to resize to the minimum size. + if (!tileCalledFromResizeEvent) { + QWidget *topLevel = q; + // Find the topLevel for this area, either a real top-level or a sub-window. + while (topLevel && !topLevel->isWindow() && topLevel->windowType() != Qt::SubWindow) + topLevel = topLevel->parentWidget(); + // We don't want sub-subwindows to be placed at the edge, thus add 2 pixels. + int minAreaWidth = minWidth + left + right + 2; + int minAreaHeight = minHeight + top + bottom + 2; + if (q->horizontalScrollBar()->isVisible()) + minAreaHeight += q->horizontalScrollBar()->height(); + if (q->verticalScrollBar()->isVisible()) + minAreaWidth += q->verticalScrollBar()->width(); + if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, q)) { + const int frame = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, q); + minAreaWidth += 2 * frame; + minAreaHeight += 2 * frame; + } + const QSize diff = QSize(minAreaWidth, minAreaHeight).expandedTo(q->size()) - q->size(); + topLevel->resize(topLevel->size() + diff); + } + + QRect domain = q->viewport()->rect(); + + // Adjust domain width and provide horizontal scroll bar. + if (domain.width() < minWidth) { + domain.setWidth(minWidth); + if (q->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) + q->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + else if (q->horizontalScrollBar()->value() != 0) + q->horizontalScrollBar()->setValue(0); + } + // Adjust domain height and provide vertical scroll bar. + if (domain.height() < minHeight) { + domain.setHeight(minHeight); + if (q->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) + q->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + else if (q->verticalScrollBar()->value() != 0) + q->verticalScrollBar()->setValue(0); + } + return domain; +} + +/*! + \internal +*/ +bool QMdiAreaPrivate::scrollBarsEnabled() const +{ + return hbarpolicy != Qt::ScrollBarAlwaysOff || vbarpolicy != Qt::ScrollBarAlwaysOff; +} + +/*! + \internal +*/ +bool QMdiAreaPrivate::lastWindowAboutToBeDestroyed() const +{ + if (childWindows.count() != 1) + return false; + + QMdiSubWindow *last = childWindows.at(0); + if (!last) + return true; + + if (!last->testAttribute(Qt::WA_DeleteOnClose)) + return false; + + return last->d_func()->data.is_closing; +} + +/*! + \internal +*/ +void QMdiAreaPrivate::setChildActivationEnabled(bool enable, bool onlyNextActivationEvent) const +{ + foreach (QMdiSubWindow *subWindow, childWindows) { + if (!subWindow || !subWindow->isVisible()) + continue; + if (onlyNextActivationEvent) + subWindow->d_func()->ignoreNextActivationEvent = !enable; + else + subWindow->d_func()->activationEnabled = enable; + } +} + +/*! + \internal + \reimp +*/ +void QMdiAreaPrivate::scrollBarPolicyChanged(Qt::Orientation orientation, Qt::ScrollBarPolicy policy) +{ + if (childWindows.isEmpty()) + return; + + const QMdiSubWindow::SubWindowOption option = orientation == Qt::Horizontal ? + QMdiSubWindow::AllowOutsideAreaHorizontally : QMdiSubWindow::AllowOutsideAreaVertically; + const bool enable = policy != Qt::ScrollBarAlwaysOff; + foreach (QMdiSubWindow *child, childWindows) { + if (!sanityCheck(child, "QMdiArea::scrollBarPolicyChanged")) + continue; + child->setOption(option, enable); + } + updateScrollBars(); +} + +QList<QMdiSubWindow*> +QMdiAreaPrivate::subWindowList(QMdiArea::WindowOrder order, bool reversed) const +{ + QList<QMdiSubWindow *> list; + if (childWindows.isEmpty()) + return list; + + if (order == QMdiArea::CreationOrder) { + foreach (QMdiSubWindow *child, childWindows) { + if (!child) + continue; + if (!reversed) + list.append(child); + else + list.prepend(child); + } + } else if (order == QMdiArea::StackingOrder) { + foreach (QObject *object, viewport->children()) { + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object); + if (!child || !childWindows.contains(child)) + continue; + if (!reversed) + list.append(child); + else + list.prepend(child); + } + } else { // ActivationHistoryOrder + Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size()); + for (int i = indicesToActivatedChildren.count() - 1; i >= 0; --i) { + QMdiSubWindow *child = childWindows.at(indicesToActivatedChildren.at(i)); + if (!child) + continue; + if (!reversed) + list.append(child); + else + list.prepend(child); + } + } + return list; +} + +/*! + \internal +*/ +void QMdiAreaPrivate::disconnectSubWindow(QObject *subWindow) +{ + if (!subWindow) + return; + + Q_Q(QMdiArea); + QObject::disconnect(subWindow, 0, q, 0); + subWindow->removeEventFilter(q); +} + +/*! + \internal +*/ +QMdiSubWindow *QMdiAreaPrivate::nextVisibleSubWindow(int increaseFactor, QMdiArea::WindowOrder order, + int removedIndex, int fromIndex) const +{ + if (childWindows.isEmpty()) + return 0; + + Q_Q(const QMdiArea); + const QList<QMdiSubWindow *> subWindows = q->subWindowList(order); + QMdiSubWindow *current = 0; + + if (removedIndex < 0) { + if (fromIndex >= 0 && fromIndex < subWindows.size()) + current = childWindows.at(fromIndex); + else + current = q->currentSubWindow(); + } + + // There's no current sub-window (removed or deactivated), + // so we have to pick the last active or the next in creation order. + if (!current) { + if (removedIndex >= 0 && order == QMdiArea::CreationOrder) { + int candidateIndex = -1; + setIndex(&candidateIndex, removedIndex, 0, subWindows.size() - 1, true); + current = childWindows.at(candidateIndex); + } else { + current = subWindows.back(); + } + } + Q_ASSERT(current); + + // Find the index for the current sub-window in the given activation order + const int indexToCurrent = subWindows.indexOf(current); + const bool increasing = increaseFactor > 0 ? true : false; + + // and use that index + increseFactor as a candidate. + int index = -1; + setIndex(&index, indexToCurrent + increaseFactor, 0, subWindows.size() - 1, increasing); + Q_ASSERT(index != -1); + + // Try to find another window if the candidate is hidden. + while (subWindows.at(index)->isHidden()) { + setIndex(&index, index + increaseFactor, 0, subWindows.size() - 1, increasing); + if (index == indexToCurrent) + break; + } + + if (!subWindows.at(index)->isHidden()) + return subWindows.at(index); + return 0; +} + +/*! + \internal +*/ +void QMdiAreaPrivate::highlightNextSubWindow(int increaseFactor) +{ + if (childWindows.size() == 1) + return; + + Q_Q(QMdiArea); + // There's no highlighted sub-window atm, use current. + if (indexToHighlighted < 0) { + QMdiSubWindow *current = q->currentSubWindow(); + if (!current) + return; + indexToHighlighted = childWindows.indexOf(current); + } + + Q_ASSERT(indexToHighlighted >= 0); + Q_ASSERT(indexToHighlighted < childWindows.size()); + + QMdiSubWindow *highlight = nextVisibleSubWindow(increaseFactor, activationOrder, -1, indexToHighlighted); + if (!highlight) + return; + +#ifndef QT_NO_RUBBERBAND + if (!rubberBand) { + rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport); + // For accessibility to identify this special widget. + rubberBand->setObjectName(QLatin1String("qt_rubberband")); + rubberBand->setWindowFlags(rubberBand->windowFlags() | Qt::WindowStaysOnTopHint); + } +#endif + + // Only highlight if we're not switching back to the previously active window (Ctrl-Tab once). +#ifndef QT_NO_RUBBERBAND + if (tabToPreviousTimerId == -1) + showRubberBandFor(highlight); +#endif + + indexToHighlighted = childWindows.indexOf(highlight); + Q_ASSERT(indexToHighlighted >= 0); +} + +/*! + \internal + \since 4.4 +*/ +void QMdiAreaPrivate::setViewMode(QMdiArea::ViewMode mode) +{ + Q_Q(QMdiArea); + if (viewMode == mode || inViewModeChange) + return; + + // Just a guard since we cannot set viewMode = mode here. + inViewModeChange = true; + +#ifndef QT_NO_TABBAR + if (mode == QMdiArea::TabbedView) { + Q_ASSERT(!tabBar); + tabBar = new QMdiAreaTabBar(q); + tabBar->setDocumentMode(documentMode); +#ifndef QT_NO_TABWIDGET + tabBar->setShape(tabBarShapeFrom(tabShape, tabPosition)); +#endif + + isSubWindowsTiled = false; + + foreach (QMdiSubWindow *subWindow, childWindows) + tabBar->addTab(subWindow->windowIcon(), tabTextFor(subWindow)); + + QMdiSubWindow *current = q->currentSubWindow(); + if (current) { + tabBar->setCurrentIndex(childWindows.indexOf(current)); + // Restore sub-window (i.e. cleanup buttons in menu bar and window title). + if (current->isMaximized()) + current->showNormal(); + + viewMode = mode; + + // Now, maximize it. + if (!q->testOption(QMdiArea::DontMaximizeSubWindowOnActivation)) { + current->showMaximized(); + } + } else { + viewMode = mode; + } + + if (q->isVisible()) + tabBar->show(); + updateTabBarGeometry(); + + QObject::connect(tabBar, SIGNAL(currentChanged(int)), q, SLOT(_q_currentTabChanged(int))); + } else +#endif // QT_NO_TABBAR + { // SubWindowView +#ifndef QT_NO_TABBAR + delete tabBar; + tabBar = 0; +#endif // QT_NO_TABBAR + + viewMode = mode; + q->setViewportMargins(0, 0, 0, 0); + indexToLastActiveTab = -1; + + QMdiSubWindow *current = q->currentSubWindow(); + if (current && current->isMaximized()) + current->showNormal(); + } + + Q_ASSERT(viewMode == mode); + inViewModeChange = false; +} + +#ifndef QT_NO_TABBAR +/*! + \internal +*/ +void QMdiAreaPrivate::updateTabBarGeometry() +{ + if (!tabBar) + return; + + Q_Q(QMdiArea); +#ifndef QT_NO_TABWIDGET + Q_ASSERT(tabBarShapeFrom(tabShape, tabPosition) == tabBar->shape()); +#endif + const QSize tabBarSizeHint = tabBar->sizeHint(); + + int areaHeight = q->height(); + if (hbar && hbar->isVisible()) + areaHeight -= hbar->height(); + + int areaWidth = q->width(); + if (vbar && vbar->isVisible()) + areaWidth -= vbar->width(); + + QRect tabBarRect; +#ifndef QT_NO_TABWIDGET + switch (tabPosition) { + case QTabWidget::North: + q->setViewportMargins(0, tabBarSizeHint.height(), 0, 0); + tabBarRect = QRect(0, 0, areaWidth, tabBarSizeHint.height()); + break; + case QTabWidget::South: + q->setViewportMargins(0, 0, 0, tabBarSizeHint.height()); + tabBarRect = QRect(0, areaHeight - tabBarSizeHint.height(), areaWidth, tabBarSizeHint.height()); + break; + case QTabWidget::East: + if (q->layoutDirection() == Qt::LeftToRight) + q->setViewportMargins(0, 0, tabBarSizeHint.width(), 0); + else + q->setViewportMargins(tabBarSizeHint.width(), 0, 0, 0); + tabBarRect = QRect(areaWidth - tabBarSizeHint.width(), 0, tabBarSizeHint.width(), areaHeight); + break; + case QTabWidget::West: + if (q->layoutDirection() == Qt::LeftToRight) + q->setViewportMargins(tabBarSizeHint.width(), 0, 0, 0); + else + q->setViewportMargins(0, 0, tabBarSizeHint.width(), 0); + tabBarRect = QRect(0, 0, tabBarSizeHint.width(), areaHeight); + break; + default: + break; + } +#endif // QT_NO_TABWIDGET + + tabBar->setGeometry(QStyle::visualRect(q->layoutDirection(), q->contentsRect(), tabBarRect)); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::refreshTabBar() +{ + if (!tabBar) + return; + + tabBar->setDocumentMode(documentMode); +#ifndef QT_NO_TABWIDGET + tabBar->setShape(tabBarShapeFrom(tabShape, tabPosition)); +#endif + updateTabBarGeometry(); +} +#endif // QT_NO_TABBAR + +/*! + Constructs an empty mdi area. \a parent is passed to QWidget's + constructor. +*/ +QMdiArea::QMdiArea(QWidget *parent) + : QAbstractScrollArea(*new QMdiAreaPrivate, parent) +{ + setBackground(palette().brush(QPalette::Dark)); + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setViewport(0); + setFocusPolicy(Qt::NoFocus); + QApplication::instance()->installEventFilter(this); +} + +/*! + Destroys the MDI area. +*/ +QMdiArea::~QMdiArea() +{ + Q_D(QMdiArea); + delete d->cascader; + d->cascader = 0; + + delete d->regularTiler; + d->regularTiler = 0; + + delete d->iconTiler; + d->iconTiler = 0; + + delete d->placer; + d->placer = 0; +} + +/*! + \reimp +*/ +QSize QMdiArea::sizeHint() const +{ + // Calculate a proper scale factor for QDesktopWidget::size(). + // This also takes into account that we can have nested workspaces. + int nestedCount = 0; + QWidget *widget = this->parentWidget(); + while (widget) { + if (qobject_cast<QMdiArea *>(widget)) + ++nestedCount; + widget = widget->parentWidget(); + } + const int scaleFactor = 3 * (nestedCount + 1); + + QSize desktopSize = QApplication::desktop()->size(); + QSize size(desktopSize.width() * 2 / scaleFactor, desktopSize.height() * 2 / scaleFactor); + foreach (QMdiSubWindow *child, d_func()->childWindows) { + if (!sanityCheck(child, "QMdiArea::sizeHint")) + continue; + size = size.expandedTo(child->sizeHint()); + } + return size.expandedTo(QApplication::globalStrut()); +} + +/*! + \reimp +*/ +QSize QMdiArea::minimumSizeHint() const +{ + Q_D(const QMdiArea); + QSize size(style()->pixelMetric(QStyle::PM_MdiSubWindowMinimizedWidth, 0, this), + style()->pixelMetric(QStyle::PM_TitleBarHeight, 0, this)); + size = size.expandedTo(QAbstractScrollArea::minimumSizeHint()); + if (!d->scrollBarsEnabled()) { + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::sizeHint")) + continue; + size = size.expandedTo(child->minimumSizeHint()); + } + } + return size.expandedTo(QApplication::globalStrut()); +} + +/*! + Returns a pointer to the current subwindow, or 0 if there is + no current subwindow. + + This function will return the same as activeSubWindow() if + the QApplication containing QMdiArea is active. + + \sa activeSubWindow(), QApplication::activeWindow() +*/ +QMdiSubWindow *QMdiArea::currentSubWindow() const +{ + Q_D(const QMdiArea); + if (d->childWindows.isEmpty()) + return 0; + + if (d->active) + return d->active; + + if (d->isActivated && !window()->isMinimized()) + return 0; + + Q_ASSERT(d->indicesToActivatedChildren.count() > 0); + int index = d->indicesToActivatedChildren.at(0); + Q_ASSERT(index >= 0 && index < d->childWindows.size()); + QMdiSubWindow *current = d->childWindows.at(index); + Q_ASSERT(current); + return current; +} + +/*! + Returns a pointer to the current active subwindow. If no + window is currently active, 0 is returned. + + Subwindows are treated as top-level windows with respect to + window state, i.e., if a widget outside the MDI area is the active + window, no subwindow will be active. Note that if a widget in the + window in which the MDI area lives gains focus, the window will be + activated. + + \sa setActiveSubWindow(), Qt::WindowState +*/ +QMdiSubWindow *QMdiArea::activeSubWindow() const +{ + Q_D(const QMdiArea); + return d->active; +} + +/*! + Activates the subwindow \a window. If \a window is 0, any + current active window is deactivated. + + \sa activeSubWindow() +*/ +void QMdiArea::setActiveSubWindow(QMdiSubWindow *window) +{ + Q_D(QMdiArea); + if (!window) { + d->activateWindow(0); + return; + } + + if (d->childWindows.isEmpty()) { + qWarning("QMdiArea::setActiveSubWindow: workspace is empty"); + return; + } + + if (d->childWindows.indexOf(window) == -1) { + qWarning("QMdiArea::setActiveSubWindow: window is not inside workspace"); + return; + } + + d->activateWindow(window); +} + +/*! + Closes the active subwindow. + + \sa closeAllSubWindows() +*/ +void QMdiArea::closeActiveSubWindow() +{ + Q_D(QMdiArea); + if (d->active) + d->active->close(); +} + +/*! + Returns a list of all subwindows in the MDI area. If \a order is + CreationOrder (the default), the windows are sorted in the order + in which they were inserted into the workspace. If \a order is + StackingOrder, the windows are listed in their stacking order, + with the topmost window as the last item in the list. If \a order + is ActivationHistoryOrder, the windows are listed according to + their recent activation history. + + \sa WindowOrder +*/ +QList<QMdiSubWindow *> QMdiArea::subWindowList(WindowOrder order) const +{ + Q_D(const QMdiArea); + return d->subWindowList(order, false); +} + +/*! + Closes all subwindows by sending a QCloseEvent to each window. + You may receive subWindowActivated() signals from subwindows + before they are closed (if the MDI area activates the subwindow + when another is closing). + + Subwindows that ignore the close event will remain open. + + \sa closeActiveSubWindow() +*/ +void QMdiArea::closeAllSubWindows() +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + d->isSubWindowsTiled = false; + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::closeAllSubWindows")) + continue; + child->close(); + } + + d->updateScrollBars(); +} + +/*! + Gives the keyboard focus to the next window in the list of child + windows. The windows are activated in the order in which they are + created (CreationOrder). + + \sa activatePreviousSubWindow() +*/ +void QMdiArea::activateNextSubWindow() +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + QMdiSubWindow *next = d->nextVisibleSubWindow(1, d->activationOrder); + if (next) + d->activateWindow(next); +} + +/*! + Gives the keyboard focus to the previous window in the list of + child windows. The windows are activated in the order in which + they are created (CreationOrder). + + \sa activateNextSubWindow() +*/ +void QMdiArea::activatePreviousSubWindow() +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + QMdiSubWindow *previous = d->nextVisibleSubWindow(-1, d->activationOrder); + if (previous) + d->activateWindow(previous); +} + +/*! + Adds \a widget as a new subwindow to the MDI area. If \a + windowFlags are non-zero, they will override the flags set on the + widget. + + The \a widget can be either a QMdiSubWindow or another QWidget + (in which case the MDI area will create a subwindow and set the \a + widget as the internal widget). + + \note Once the subwindow has been added, its parent will be the + \e{viewport widget} of the QMdiArea. + + \snippet doc/src/snippets/mdiareasnippets.cpp 1 + + When you create your own subwindow, you must set the + Qt::WA_DeleteOnClose widget attribute if you want the window to be + deleted when closed in the MDI area. If not, the window will be + hidden and the MDI area will not activate the next subwindow. + + Returns the QMdiSubWindow that is added to the MDI area. + + \sa removeSubWindow() +*/ +QMdiSubWindow *QMdiArea::addSubWindow(QWidget *widget, Qt::WindowFlags windowFlags) +{ + if (!widget) { + qWarning("QMdiArea::addSubWindow: null pointer to widget"); + return 0; + } + + Q_D(QMdiArea); + // QWidget::setParent clears focusWidget so store it + QWidget *childFocus = widget->focusWidget(); + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(widget); + + // Widget is already a QMdiSubWindow + if (child) { + if (d->childWindows.indexOf(child) != -1) { + qWarning("QMdiArea::addSubWindow: window is already added"); + return child; + } + child->setParent(viewport(), windowFlags ? windowFlags : child->windowFlags()); + // Create a QMdiSubWindow + } else { + child = new QMdiSubWindow(viewport(), windowFlags); + child->setAttribute(Qt::WA_DeleteOnClose); + child->setWidget(widget); + Q_ASSERT(child->testAttribute(Qt::WA_DeleteOnClose)); + } + + if (childFocus) + childFocus->setFocus(); + d->appendChild(child); + return child; +} + +/*! + Removes \a widget from the MDI area. The \a widget must be + either a QMdiSubWindow or a widget that is the internal widget of + a subwindow. Note that the subwindow is not deleted by QMdiArea + and that its parent is set to 0. + + \sa addSubWindow() +*/ +void QMdiArea::removeSubWindow(QWidget *widget) +{ + if (!widget) { + qWarning("QMdiArea::removeSubWindow: null pointer to widget"); + return; + } + + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + if (QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(widget)) { + int index = d->childWindows.indexOf(child); + if (index == -1) { + qWarning("QMdiArea::removeSubWindow: window is not inside workspace"); + return; + } + d->disconnectSubWindow(child); + d->childWindows.removeAll(child); + d->indicesToActivatedChildren.removeAll(index); + d->updateActiveWindow(index, d->active == child); + child->setParent(0); + return; + } + + bool found = false; + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::removeSubWindow")) + continue; + if (child->widget() == widget) { + child->setWidget(0); + Q_ASSERT(!child->widget()); + found = true; + break; + } + } + + if (!found) + qWarning("QMdiArea::removeSubWindow: widget is not child of any window inside QMdiArea"); +} + +/*! + \property QMdiArea::background + \brief the background brush for the workspace + + This property sets the background brush for the workspace area + itself. By default, it is a gray color, but can be any brush + (e.g., colors, gradients or pixmaps). +*/ +QBrush QMdiArea::background() const +{ + return d_func()->background; +} + +void QMdiArea::setBackground(const QBrush &brush) +{ + Q_D(QMdiArea); + if (d->background != brush) { + d->background = brush; + d->viewport->setAttribute(Qt::WA_OpaquePaintEvent, brush.isOpaque()); + update(); + } +} + + +/*! + \property QMdiArea::activationOrder + \brief the ordering criteria for subwindow lists + \since 4.4 + + This property specifies the ordering criteria for the list of + subwindows returned by subWindowList(). By default, it is the window + creation order. + + \sa subWindowList() +*/ +QMdiArea::WindowOrder QMdiArea::activationOrder() const +{ + Q_D(const QMdiArea); + return d->activationOrder; +} + +void QMdiArea::setActivationOrder(WindowOrder order) +{ + Q_D(QMdiArea); + if (order != d->activationOrder) + d->activationOrder = order; +} + +/*! + If \a on is true, \a option is enabled on the MDI area; otherwise + it is disabled. See AreaOption for the effect of each option. + + \sa AreaOption, testOption() +*/ +void QMdiArea::setOption(AreaOption option, bool on) +{ + Q_D(QMdiArea); + if (on && !(d->options & option)) + d->options |= option; + else if (!on && (d->options & option)) + d->options &= ~option; +} + +/*! + Returns true if \a option is enabled; otherwise returns false. + + \sa AreaOption, setOption() +*/ +bool QMdiArea::testOption(AreaOption option) const +{ + return d_func()->options & option; +} + +/*! + \property QMdiArea::viewMode + \brief the way sub-windows are displayed in the QMdiArea. + \since 4.4 + + By default, the SubWindowView is used to display sub-windows. + + \sa ViewMode, setTabShape(), setTabPosition() +*/ +QMdiArea::ViewMode QMdiArea::viewMode() const +{ + Q_D(const QMdiArea); + return d->viewMode; +} + +void QMdiArea::setViewMode(ViewMode mode) +{ + Q_D(QMdiArea); + d->setViewMode(mode); +} + +#ifndef QT_NO_TABBAR +/*! + \property QMdiArea::documentMode + \brief whether the tab bar is set to document mode in tabbed view mode. + \since 4.5 + + Document mode is disabled by default. + + \sa QTabBar::documentMode, setViewMode() +*/ +bool QMdiArea::documentMode() const +{ + Q_D(const QMdiArea); + return d->documentMode; +} + +void QMdiArea::setDocumentMode(bool enabled) +{ + Q_D(QMdiArea); + if (d->documentMode == enabled) + return; + + d->documentMode = enabled; + d->refreshTabBar(); +} +#endif // QT_NO_TABBAR + +#ifndef QT_NO_TABWIDGET +/*! + \property QMdiArea::tabShape + \brief the shape of the tabs in tabbed view mode. + \since 4.4 + + Possible values for this property are QTabWidget::Rounded + (default) or QTabWidget::Triangular. + + \sa QTabWidget::TabShape, setViewMode() +*/ +QTabWidget::TabShape QMdiArea::tabShape() const +{ + Q_D(const QMdiArea); + return d->tabShape; +} + +void QMdiArea::setTabShape(QTabWidget::TabShape shape) +{ + Q_D(QMdiArea); + if (d->tabShape == shape) + return; + + d->tabShape = shape; + d->refreshTabBar(); +} + +/*! + \property QMdiArea::tabPosition + \brief the position of the tabs in tabbed view mode. + \since 4.4 + + Possible values for this property are described by the + QTabWidget::TabPosition enum. + + \sa QTabWidget::TabPosition, setViewMode() +*/ +QTabWidget::TabPosition QMdiArea::tabPosition() const +{ + Q_D(const QMdiArea); + return d->tabPosition; +} + +void QMdiArea::setTabPosition(QTabWidget::TabPosition position) +{ + Q_D(QMdiArea); + if (d->tabPosition == position) + return; + + d->tabPosition = position; + d->refreshTabBar(); +} +#endif // QT_NO_TABWIDGET + +/*! + \reimp +*/ +void QMdiArea::childEvent(QChildEvent *childEvent) +{ + Q_D(QMdiArea); + if (childEvent->type() == QEvent::ChildPolished) { + if (QMdiSubWindow *mdiChild = qobject_cast<QMdiSubWindow *>(childEvent->child())) { + if (d->childWindows.indexOf(mdiChild) == -1) + d->appendChild(mdiChild); + } + } +} + +/*! + \reimp +*/ +void QMdiArea::resizeEvent(QResizeEvent *resizeEvent) +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) { + resizeEvent->ignore(); + return; + } + +#ifndef QT_NO_TABBAR + d->updateTabBarGeometry(); +#endif + + // Re-tile the views if we're in tiled mode. Re-tile means we will change + // the geometry of the children, which in turn means 'isSubWindowsTiled' + // is set to false, so we have to update the state at the end. + if (d->isSubWindowsTiled) { + d->tileCalledFromResizeEvent = true; + tileSubWindows(); + d->tileCalledFromResizeEvent = false; + d->isSubWindowsTiled = true; + d->startResizeTimer(); + // We don't have scroll bars or any maximized views. + return; + } + + // Resize maximized views. + bool hasMaximizedSubWindow = false; + foreach (QMdiSubWindow *child, d->childWindows) { + if (sanityCheck(child, "QMdiArea::resizeEvent") && child->isMaximized() + && child->size() != resizeEvent->size()) { + child->resize(resizeEvent->size()); + if (!hasMaximizedSubWindow) + hasMaximizedSubWindow = true; + } + } + + d->updateScrollBars(); + + // Minimized views are stacked under maximized views so there's + // no need to re-arrange minimized views on-demand. Start a timer + // just to make things faster with subsequent resize events. + if (hasMaximizedSubWindow) + d->startResizeTimer(); + else + d->arrangeMinimizedSubWindows(); +} + +/*! + \reimp +*/ +void QMdiArea::timerEvent(QTimerEvent *timerEvent) +{ + Q_D(QMdiArea); + if (timerEvent->timerId() == d->resizeTimerId) { + killTimer(d->resizeTimerId); + d->resizeTimerId = -1; + d->arrangeMinimizedSubWindows(); + } else if (timerEvent->timerId() == d->tabToPreviousTimerId) { + killTimer(d->tabToPreviousTimerId); + d->tabToPreviousTimerId = -1; + if (d->indexToHighlighted < 0) + return; +#ifndef QT_NO_RUBBERBAND + // We're not doing a "quick switch" ... show rubber band. + Q_ASSERT(d->indexToHighlighted < d->childWindows.size()); + Q_ASSERT(d->rubberBand); + d->showRubberBandFor(d->childWindows.at(d->indexToHighlighted)); +#endif + } +} + +/*! + \reimp +*/ +void QMdiArea::showEvent(QShowEvent *showEvent) +{ + Q_D(QMdiArea); + if (!d->pendingRearrangements.isEmpty()) { + bool skipPlacement = false; + foreach (Rearranger *rearranger, d->pendingRearrangements) { + // If this is the case, we don't have to lay out pending child windows + // since the rearranger will find a placement for them. + if (rearranger->type() != Rearranger::IconTiler && !skipPlacement) + skipPlacement = true; + d->rearrange(rearranger); + } + d->pendingRearrangements.clear(); + + if (skipPlacement && !d->pendingPlacements.isEmpty()) + d->pendingPlacements.clear(); + } + + if (!d->pendingPlacements.isEmpty()) { + foreach (QMdiSubWindow *window, d->pendingPlacements) { + if (!window) + continue; + if (!window->testAttribute(Qt::WA_Resized)) { + QSize newSize(window->sizeHint().boundedTo(viewport()->size())); + window->resize(newSize.expandedTo(qSmartMinSize(window))); + } + if (!window->testAttribute(Qt::WA_Moved) && !window->isMinimized() + && !window->isMaximized()) { + d->place(d->placer, window); + } + } + d->pendingPlacements.clear(); + } + + d->setChildActivationEnabled(true); + d->activateCurrentWindow(); + + QAbstractScrollArea::showEvent(showEvent); +} + +/*! + \reimp +*/ +bool QMdiArea::viewportEvent(QEvent *event) +{ + Q_D(QMdiArea); + switch (event->type()) { + case QEvent::ChildRemoved: { + d->isSubWindowsTiled = false; + QObject *removedChild = static_cast<QChildEvent *>(event)->child(); + for (int i = 0; i < d->childWindows.size(); ++i) { + QObject *child = d->childWindows.at(i); + if (!child || child == removedChild || !child->parent() + || child->parent() != viewport()) { + if (!testOption(DontMaximizeSubWindowOnActivation)) { + // In this case we can only rely on the child being a QObject + // (or 0), but let's try and see if we can get more information. + QWidget *mdiChild = qobject_cast<QWidget *>(removedChild); + if (mdiChild && mdiChild->isMaximized()) + d->showActiveWindowMaximized = true; + } + d->disconnectSubWindow(child); + const bool activeRemoved = i == d->indicesToActivatedChildren.at(0); + d->childWindows.removeAt(i); + d->indicesToActivatedChildren.removeAll(i); + d->updateActiveWindow(i, activeRemoved); + d->arrangeMinimizedSubWindows(); + break; + } + } + d->updateScrollBars(); + break; + } + case QEvent::Destroy: + d->isSubWindowsTiled = false; + d->resetActiveWindow(); + d->childWindows.clear(); + qWarning("QMdiArea: Deleting the view port is undefined, use setViewport instead."); + break; + default: + break; + } + return QAbstractScrollArea::viewportEvent(event); +} + +/*! + \reimp +*/ +void QMdiArea::scrollContentsBy(int dx, int dy) +{ + Q_D(QMdiArea); + const bool wasSubWindowsTiled = d->isSubWindowsTiled; + d->ignoreGeometryChange = true; + viewport()->scroll(isLeftToRight() ? dx : -dx, dy); + d->arrangeMinimizedSubWindows(); + d->ignoreGeometryChange = false; + if (wasSubWindowsTiled) + d->isSubWindowsTiled = true; +} + +/*! + Arranges all child windows in a tile pattern. + + \sa cascadeSubWindows() +*/ +void QMdiArea::tileSubWindows() +{ + Q_D(QMdiArea); + if (!d->regularTiler) + d->regularTiler = new RegularTiler; + d->rearrange(d->regularTiler); +} + +/*! + Arranges all the child windows in a cascade pattern. + + \sa tileSubWindows() +*/ +void QMdiArea::cascadeSubWindows() +{ + Q_D(QMdiArea); + if (!d->cascader) + d->cascader = new SimpleCascader; + d->rearrange(d->cascader); +} + +/*! + \reimp +*/ +bool QMdiArea::event(QEvent *event) +{ + Q_D(QMdiArea); + switch (event->type()) { +#ifdef Q_WS_WIN + // QWidgetPrivate::hide_helper activates another sub-window when closing a + // modal dialog on Windows (see activateWindow() inside the the ifdef). + case QEvent::WindowUnblocked: + d->activateCurrentWindow(); + break; +#endif + case QEvent::WindowActivate: { + d->isActivated = true; + if (d->childWindows.isEmpty()) + break; + if (!d->active) + d->activateCurrentWindow(); + d->setChildActivationEnabled(false, true); + break; + } + case QEvent::WindowDeactivate: + d->isActivated = false; + d->setChildActivationEnabled(false, true); + break; + case QEvent::StyleChange: + // Re-tile the views if we're in tiled mode. Re-tile means we will change + // the geometry of the children, which in turn means 'isSubWindowsTiled' + // is set to false, so we have to update the state at the end. + if (d->isSubWindowsTiled) { + tileSubWindows(); + d->isSubWindowsTiled = true; + } + break; + case QEvent::WindowIconChange: + foreach (QMdiSubWindow *window, d->childWindows) { + if (sanityCheck(window, "QMdiArea::WindowIconChange")) + QApplication::sendEvent(window, event); + } + break; + case QEvent::Hide: + d->setActive(d->active, false, false); + d->setChildActivationEnabled(false); + break; +#ifndef QT_NO_TABBAR + case QEvent::LayoutDirectionChange: + d->updateTabBarGeometry(); + break; +#endif + default: + break; + } + return QAbstractScrollArea::event(event); +} + +/*! + \reimp +*/ +bool QMdiArea::eventFilter(QObject *object, QEvent *event) +{ + if (!object) + return QAbstractScrollArea::eventFilter(object, event); + + Q_D(QMdiArea); + // Global key events with Ctrl modifier. + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + // Ingore key events without a Ctrl modifier (except for press/release on the modifier itself). +#ifdef Q_WS_MAC + if (!(keyEvent->modifiers() & Qt::MetaModifier) && keyEvent->key() != Qt::Key_Meta) +#else + if (!(keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() != Qt::Key_Control) +#endif + return QAbstractScrollArea::eventFilter(object, event); + + // Find closest mdi area (in case we have a nested workspace). + QMdiArea *area = mdiAreaParent(static_cast<QWidget *>(object)); + if (!area) + return QAbstractScrollArea::eventFilter(object, event); + + const bool keyPress = (event->type() == QEvent::KeyPress) ? true : false; + + // 1) Ctrl-Tab once -> activate the previously active window. + // 2) Ctrl-Tab (Tab, Tab, ...) -> iterate through all windows (activateNextSubWindow()). + // 3) Ctrl-Shift-Tab (Tab, Tab, ...) -> iterate through all windows in the opposite + // direction (activatePreviousSubWindow()) + switch (keyEvent->key()) { +#ifdef Q_WS_MAC + case Qt::Key_Meta: +#else + case Qt::Key_Control: +#endif + if (keyPress) + area->d_func()->startTabToPreviousTimer(); + else + area->d_func()->activateHighlightedWindow(); + break; + case Qt::Key_Tab: + case Qt::Key_Backtab: + if (keyPress) + area->d_func()->highlightNextSubWindow(keyEvent->key() == Qt::Key_Tab ? 1 : -1); + return true; +#ifndef QT_NO_RUBBERBAND + case Qt::Key_Escape: + area->d_func()->hideRubberBand(); + break; +#endif + default: + break; + } + return QAbstractScrollArea::eventFilter(object, event); + } + + QMdiSubWindow *subWindow = qobject_cast<QMdiSubWindow *>(object); + + if (!subWindow) { + // QApplication events: + if (event->type() == QEvent::ApplicationActivate && !d->active + && isVisible() && !window()->isMinimized()) { + d->activateCurrentWindow(); + } else if (event->type() == QEvent::ApplicationDeactivate && d->active) { + d->setActive(d->active, false, false); + } + return QAbstractScrollArea::eventFilter(object, event); + } + + // QMdiSubWindow events: + switch (event->type()) { + case QEvent::Move: + case QEvent::Resize: + if (d->tileCalledFromResizeEvent) + break; + d->updateScrollBars(); + if (!subWindow->isMinimized()) + d->isSubWindowsTiled = false; + break; + case QEvent::Show: +#ifndef QT_NO_TABBAR + if (d->tabBar) { + const int tabIndex = d->childWindows.indexOf(subWindow); + if (!d->tabBar->isTabEnabled(tabIndex)) + d->tabBar->setTabEnabled(tabIndex, true); + } +#endif // QT_NO_TABBAR + // fall through + case QEvent::Hide: + d->isSubWindowsTiled = false; + break; +#ifndef QT_NO_RUBBERBAND + case QEvent::Close: + if (d->childWindows.indexOf(subWindow) == d->indexToHighlighted) + d->hideRubberBand(); + break; +#endif +#ifndef QT_NO_TABBAR + case QEvent::WindowTitleChange: + case QEvent::ModifiedChange: + if (d->tabBar) + d->tabBar->setTabText(d->childWindows.indexOf(subWindow), tabTextFor(subWindow)); + break; + case QEvent::WindowIconChange: + if (d->tabBar) + d->tabBar->setTabIcon(d->childWindows.indexOf(subWindow), subWindow->windowIcon()); + break; +#endif // QT_NO_TABBAR + default: + break; + } + return QAbstractScrollArea::eventFilter(object, event); +} + +/*! + \reimp +*/ +void QMdiArea::paintEvent(QPaintEvent *paintEvent) +{ + Q_D(QMdiArea); + QPainter painter(d->viewport); + const QVector<QRect> &exposedRects = paintEvent->region().rects(); + for (int i = 0; i < exposedRects.size(); ++i) + painter.fillRect(exposedRects.at(i), d->background); +} + +/*! + This slot is called by QAbstractScrollArea after setViewport() has been + called. Reimplement this function in a subclass of QMdiArea to + initialize the new \a viewport before it is used. + + \sa setViewport() +*/ +void QMdiArea::setupViewport(QWidget *viewport) +{ + Q_D(QMdiArea); + if (viewport) + viewport->setAttribute(Qt::WA_OpaquePaintEvent, d->background.isOpaque()); + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::setupViewport")) + continue; + child->setParent(viewport, child->windowFlags()); + } +} + +QT_END_NAMESPACE + +#include "moc_qmdiarea.cpp" + +#endif // QT_NO_MDIAREA diff --git a/src/gui/widgets/qmdiarea.h b/src/gui/widgets/qmdiarea.h new file mode 100644 index 0000000..8448c81 --- /dev/null +++ b/src/gui/widgets/qmdiarea.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMDIAREA_H +#define QMDIAREA_H + +#include <QtGui/qabstractscrollarea.h> +#include <QtGui/qtabwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MDIAREA + +class QMdiSubWindow; + +class QMdiAreaPrivate; +class Q_GUI_EXPORT QMdiArea : public QAbstractScrollArea +{ + Q_OBJECT + Q_ENUMS(ViewMode) + Q_PROPERTY(QBrush background READ background WRITE setBackground) + Q_PROPERTY(WindowOrder activationOrder READ activationOrder WRITE setActivationOrder) + Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode) +#ifndef QT_NO_TABBAR + Q_PROPERTY(bool documentMode READ documentMode WRITE setDocumentMode) +#endif +#ifndef QT_NO_TABWIDGET + Q_PROPERTY(QTabWidget::TabShape tabShape READ tabShape WRITE setTabShape) + Q_PROPERTY(QTabWidget::TabPosition tabPosition READ tabPosition WRITE setTabPosition) +#endif + Q_ENUMS(WindowOrder) +public: + enum AreaOption { + DontMaximizeSubWindowOnActivation = 0x1 + }; + Q_DECLARE_FLAGS(AreaOptions, AreaOption) + + enum WindowOrder { + CreationOrder, + StackingOrder, + ActivationHistoryOrder + }; + + enum ViewMode { + SubWindowView, + TabbedView + }; + + QMdiArea(QWidget *parent = 0); + ~QMdiArea(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + QMdiSubWindow *currentSubWindow() const; + QMdiSubWindow *activeSubWindow() const; + QList<QMdiSubWindow *> subWindowList(WindowOrder order = CreationOrder) const; + + QMdiSubWindow *addSubWindow(QWidget *widget, Qt::WindowFlags flags = 0); + void removeSubWindow(QWidget *widget); + + QBrush background() const; + void setBackground(const QBrush &background); + + WindowOrder activationOrder() const; + void setActivationOrder(WindowOrder order); + + void setOption(AreaOption option, bool on = true); + bool testOption(AreaOption opton) const; + + void setViewMode(ViewMode mode); + ViewMode viewMode() const; + +#ifndef QT_NO_TABBAR + bool documentMode() const; + void setDocumentMode(bool enabled); +#endif +#ifndef QT_NO_TABWIDGET + void setTabShape(QTabWidget::TabShape shape); + QTabWidget::TabShape tabShape() const; + + void setTabPosition(QTabWidget::TabPosition position); + QTabWidget::TabPosition tabPosition() const; +#endif + +Q_SIGNALS: + void subWindowActivated(QMdiSubWindow *); + +public Q_SLOTS: + void setActiveSubWindow(QMdiSubWindow *window); + void tileSubWindows(); + void cascadeSubWindows(); + void closeActiveSubWindow(); + void closeAllSubWindows(); + void activateNextSubWindow(); + void activatePreviousSubWindow(); + +protected Q_SLOTS: + void setupViewport(QWidget *viewport); + +protected: + bool event(QEvent *event); + bool eventFilter(QObject *object, QEvent *event); + void paintEvent(QPaintEvent *paintEvent); + void childEvent(QChildEvent *childEvent); + void resizeEvent(QResizeEvent *resizeEvent); + void timerEvent(QTimerEvent *timerEvent); + void showEvent(QShowEvent *showEvent); + bool viewportEvent(QEvent *event); + void scrollContentsBy(int dx, int dy); + +private: + Q_DISABLE_COPY(QMdiArea) + Q_DECLARE_PRIVATE(QMdiArea) + Q_PRIVATE_SLOT(d_func(), void _q_deactivateAllWindows()) + Q_PRIVATE_SLOT(d_func(), void _q_processWindowStateChanged(Qt::WindowStates, Qt::WindowStates)) + Q_PRIVATE_SLOT(d_func(), void _q_currentTabChanged(int index)) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QMdiArea::AreaOptions) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_MDIAREA +#endif // QMDIAREA_H diff --git a/src/gui/widgets/qmdiarea_p.h b/src/gui/widgets/qmdiarea_p.h new file mode 100644 index 0000000..645f0cc --- /dev/null +++ b/src/gui/widgets/qmdiarea_p.h @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMDIAREA_P_H +#define QMDIAREA_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 "qmdiarea.h" +#include "qmdisubwindow.h" + +#ifndef QT_NO_MDIAREA + +#include <QList> +#include <QRect> +#include <QPoint> +#include <QtGui/qapplication.h> +#include <private/qmdisubwindow_p.h> +#include <private/qabstractscrollarea_p.h> + +QT_BEGIN_NAMESPACE + +namespace QMdi { +class Rearranger +{ +public: + enum Type { + RegularTiler, + SimpleCascader, + IconTiler + }; + + // Rearranges widgets relative to domain. + virtual void rearrange(QList<QWidget *> &widgets, const QRect &domain) const = 0; + virtual Type type() const = 0; + virtual ~Rearranger() {} +}; + +class RegularTiler : public Rearranger +{ + // Rearranges widgets according to a regular tiling pattern + // covering the entire domain. + // Both positions and sizes may change. + void rearrange(QList<QWidget *> &widgets, const QRect &domain) const; + inline Type type() const { return Rearranger::RegularTiler; } +}; + +class SimpleCascader : public Rearranger +{ + // Rearranges widgets according to a simple, regular cascading pattern. + // Widgets are resized to minimumSize. + // Both positions and sizes may change. + void rearrange(QList<QWidget *> &widgets, const QRect &domain) const; + inline Type type() const { return Rearranger::SimpleCascader; } +}; + +class IconTiler : public Rearranger +{ + // Rearranges icons (assumed to be the same size) according to a regular + // tiling pattern filling up the domain from the bottom. + // Only positions may change. + void rearrange(QList<QWidget *> &widgets, const QRect &domain) const; + inline Type type() const { return Rearranger::IconTiler; } +}; + +class Placer +{ +public: + // Places the rectangle defined by 'size' relative to 'rects' and 'domain'. + // Returns the position of the resulting rectangle. + virtual QPoint place( + const QSize &size, const QList<QRect> &rects, const QRect &domain) const = 0; + virtual ~Placer() {} +}; + +class MinOverlapPlacer : public Placer +{ + QPoint place(const QSize &size, const QList<QRect> &rects, const QRect &domain) const; + static int accumulatedOverlap(const QRect &source, const QList<QRect> &rects); + static QRect findMinOverlapRect(const QList<QRect> &source, const QList<QRect> &rects); + static void getCandidatePlacements( + const QSize &size, const QList<QRect> &rects, const QRect &domain, + QList<QRect> &candidates); + static QPoint findBestPlacement( + const QRect &domain, const QList<QRect> &rects, QList<QRect> &source); + static void findNonInsiders( + const QRect &domain, QList<QRect> &source, QList<QRect> &result); + static void findMaxOverlappers( + const QRect &domain, const QList<QRect> &source, QList<QRect> &result); +}; +} // namespace QMdi + +class QMdiAreaTabBar; +class QMdiAreaPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QMdiArea) +public: + QMdiAreaPrivate(); + + // Variables. + QMdi::Rearranger *cascader; + QMdi::Rearranger *regularTiler; + QMdi::Rearranger *iconTiler; + QMdi::Placer *placer; +#ifndef QT_NO_RUBBERBAND + QRubberBand *rubberBand; +#endif + QMdiAreaTabBar *tabBar; + QList<QMdi::Rearranger *> pendingRearrangements; + QList< QPointer<QMdiSubWindow> > pendingPlacements; + QList< QPointer<QMdiSubWindow> > childWindows; + QList<int> indicesToActivatedChildren; + QPointer<QMdiSubWindow> active; + QPointer<QMdiSubWindow> aboutToBecomeActive; + QBrush background; + QMdiArea::WindowOrder activationOrder; + QMdiArea::AreaOptions options; + QMdiArea::ViewMode viewMode; +#ifndef QT_NO_TABBAR + bool documentMode; +#endif +#ifndef QT_NO_TABWIDGET + QTabWidget::TabShape tabShape; + QTabWidget::TabPosition tabPosition; +#endif + bool ignoreGeometryChange; + bool ignoreWindowStateChange; + bool isActivated; + bool isSubWindowsTiled; + bool showActiveWindowMaximized; + bool tileCalledFromResizeEvent; + bool updatesDisabledByUs; + bool inViewModeChange; + int indexToNextWindow; + int indexToPreviousWindow; + int indexToHighlighted; + int indexToLastActiveTab; + int resizeTimerId; + int tabToPreviousTimerId; + + // Slots. + void _q_deactivateAllWindows(QMdiSubWindow *aboutToActivate = 0); + void _q_processWindowStateChanged(Qt::WindowStates oldState, Qt::WindowStates newState); + void _q_currentTabChanged(int index); + + // Functions. + void appendChild(QMdiSubWindow *child); + void place(QMdi::Placer *placer, QMdiSubWindow *child); + void rearrange(QMdi::Rearranger *rearranger); + void arrangeMinimizedSubWindows(); + void activateWindow(QMdiSubWindow *child); + void activateCurrentWindow(); + void activateHighlightedWindow(); + void emitWindowActivated(QMdiSubWindow *child); + void resetActiveWindow(QMdiSubWindow *child = 0); + void updateActiveWindow(int removedIndex, bool activeRemoved); + void updateScrollBars(); + void internalRaise(QMdiSubWindow *child) const; + bool scrollBarsEnabled() const; + bool lastWindowAboutToBeDestroyed() const; + void setChildActivationEnabled(bool enable = true, bool onlyNextActivationEvent = false) const; + QRect resizeToMinimumTileSize(const QSize &minSubWindowSize, int subWindowCount); + void scrollBarPolicyChanged(Qt::Orientation, Qt::ScrollBarPolicy); // reimp + QMdiSubWindow *nextVisibleSubWindow(int increaseFactor, QMdiArea::WindowOrder, + int removed = -1, int fromIndex = -1) const; + void highlightNextSubWindow(int increaseFactor); + QList<QMdiSubWindow *> subWindowList(QMdiArea::WindowOrder, bool reversed = false) const; + void disconnectSubWindow(QObject *subWindow); + void setViewMode(QMdiArea::ViewMode mode); +#ifndef QT_NO_TABBAR + void updateTabBarGeometry(); + void refreshTabBar(); +#endif + + inline void startResizeTimer() + { + Q_Q(QMdiArea); + if (resizeTimerId > 0) + q->killTimer(resizeTimerId); + resizeTimerId = q->startTimer(200); + } + + inline void startTabToPreviousTimer() + { + Q_Q(QMdiArea); + if (tabToPreviousTimerId > 0) + q->killTimer(tabToPreviousTimerId); + tabToPreviousTimerId = q->startTimer(QApplication::keyboardInputInterval()); + } + + inline bool windowStaysOnTop(QMdiSubWindow *subWindow) const + { + if (!subWindow) + return false; + return subWindow->windowFlags() & Qt::WindowStaysOnTopHint; + } + + inline bool isExplicitlyDeactivated(QMdiSubWindow *subWindow) const + { + if (!subWindow) + return true; + return subWindow->d_func()->isExplicitlyDeactivated; + } + + inline void setActive(QMdiSubWindow *subWindow, bool active = true, bool changeFocus = true) const + { + if (subWindow) + subWindow->d_func()->setActive(active, changeFocus); + } + +#ifndef QT_NO_RUBBERBAND + inline void showRubberBandFor(QMdiSubWindow *subWindow) + { + if (!subWindow || !rubberBand) + return; + rubberBand->setGeometry(subWindow->geometry()); + rubberBand->raise(); + rubberBand->show(); + } + + inline void hideRubberBand() + { + if (rubberBand && rubberBand->isVisible()) + rubberBand->hide(); + indexToHighlighted = -1; + } +#endif // QT_NO_RUBBERBAND +}; + +#endif // QT_NO_MDIAREA + +QT_END_NAMESPACE + +#endif // QMDIAREA_P_H diff --git a/src/gui/widgets/qmdisubwindow.cpp b/src/gui/widgets/qmdisubwindow.cpp new file mode 100644 index 0000000..6bf7633 --- /dev/null +++ b/src/gui/widgets/qmdisubwindow.cpp @@ -0,0 +1,3552 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QMdiSubWindow + \brief The QMdiSubWindow class provides a subwindow class for + QMdiArea. + \since 4.3 + \ingroup application + \mainclass + + QMdiSubWindow represents a top-level window in a QMdiArea, and consists + of a title bar with window decorations, an internal widget, and + (depending on the current style) a window frame and a size + grip. QMdiSubWindow has its own layout, which consists of the + title bar and a center area for the internal widget. + + \image qmdisubwindowlayout.png + + The most common way to construct a QMdiSubWindow is to call + QMdiArea::addSubWindow() with the internal widget as the argument. + You can also create a subwindow yourself, and set an internal + widget by calling setWidget(). + + You use the same API when programming with subwindows as with + regular top-level windows (e.g., you can call functions such as + show(), hide(), showMaximized(), and setWindowTitle()). + + \section1 Subwindow Handling + + QMdiSubWindow also supports behavior specific to subwindows in + an MDI area. + + By default, each QMdiSubWindow is visible inside the MDI area + viewport when moved around, but it is also possible to specify + transparent window movement and resizing behavior, where only + the outline of a subwindow is updated during these operations. + The setOption() function is used to enable this behavior. + + The isShaded() function detects whether the subwindow is + currently shaded (i.e., the window is collapsed so that only the + title bar is visible). To enter shaded mode, call showShaded(). + QMdiSubWindow emits the windowStateChanged() signal whenever the + window state has changed (e.g., when the window becomes minimized, + or is restored). It also emits aboutToActivate() before it is + activated. + + In keyboard-interactive mode, the windows are moved and resized + with the keyboard. You can enter this mode through the system menu + of the window. The keyboardSingleStep and keyboardPageStep + properties control the distance the widget is moved or resized for + each keypress event. When shift is pressed down page step is used; + otherwise single step is used. + + You can also change the active window with the keyboard. By + pressing the control and tab keys at the same time, the next + (using the current \l{QMdiArea::}{WindowOrder}) subwindow will be + activated. By pressing control, shift, and tab, you will activate + the previous window. This is equivalent to calling + \l{QMdiArea::}{activateNextSubWindow()} and + \l{QMdiArea::}{activatePreviousSubWindow()}. Note that these + shortcuts overrides global shortcuts, but not the \l{QMdiArea}s + shortcuts. + + \sa QMdiArea +*/ + +/*! + \enum QMdiSubWindow::SubWindowOption + + This enum describes options that customize the behavior + of QMdiSubWindow. + + \omitvalue AllowOutsideAreaHorizontally + \omitvalue AllowOutsideAreaVertically + + \value RubberBandResize If you enable this option, a rubber band + control is used to represent the subwindow's outline, and the user + resizes this instead of the subwindow itself. + As a result, the subwindow maintains its original position and size + until the resize operation has been completed, at which time it will + receive a single QResizeEvent. + By default, this option is disabled. + + \value RubberBandMove If you enable this option, a rubber band + control is used to represent the subwindow's outline, and the user + moves this instead of the subwindow itself. + As a result, the subwindow remains in its original position until + the move operation has completed, at which time a QMoveEvent is + sent to the window. By default, this option is disabled. +*/ + +/*! + \fn QMdiSubWindow::windowStateChanged(Qt::WindowStates oldState, Qt::WindowStates newState) + + QMdiSubWindow emits this signal after the window state changes. \a + oldState is the window state before it changed, and \a newState is the + new, current state. +*/ + +/*! + \fn QMdiSubWindow::aboutToActivate() + + QMdiSubWindow emits this signal immediately before it is + activated. After the subwindow has been activated, the QMdiArea that + manages the subwindow will also emit the + \l{QMdiArea::}{subWindowActivated()} signal. + + \sa QMdiArea::subWindowActivated() +*/ + +#include "qmdisubwindow_p.h" + +#ifndef QT_NO_MDIAREA + +#include <QApplication> +#include <QStylePainter> +#include <QVBoxLayout> +#include <QMouseEvent> +#include <QWhatsThis> +#include <QToolTip> +#include <QMainWindow> +#include <QScrollBar> +#include <QDebug> +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) +#include <QMacStyle> +#endif +#include <QMdiArea> + +QT_BEGIN_NAMESPACE + +using namespace QMdi; + +static const QStyle::SubControl SubControls[] = +{ + QStyle::SC_TitleBarLabel, // 1 + QStyle::SC_TitleBarSysMenu, // 2 + QStyle::SC_TitleBarMinButton, // 3 + QStyle::SC_TitleBarMaxButton, // 4 + QStyle::SC_TitleBarShadeButton, // 5 + QStyle::SC_TitleBarCloseButton, // 6 + QStyle::SC_TitleBarNormalButton, // 7 + QStyle::SC_TitleBarUnshadeButton, // 8 + QStyle::SC_TitleBarContextHelpButton // 9 +}; +static const int NumSubControls = sizeof(SubControls) / sizeof(SubControls[0]); + +static const QStyle::StandardPixmap ButtonPixmaps[] = +{ + QStyle::SP_TitleBarMinButton, + QStyle::SP_TitleBarNormalButton, + QStyle::SP_TitleBarCloseButton +}; +static const int NumButtonPixmaps = sizeof(ButtonPixmaps) / sizeof(ButtonPixmaps[0]); + +static const Qt::WindowFlags CustomizeWindowFlags = + Qt::FramelessWindowHint + | Qt::CustomizeWindowHint + | Qt::WindowTitleHint + | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowMinMaxButtonsHint; + + +static const int BoundaryMargin = 5; + +static inline int getMoveDeltaComponent(uint cflags, uint moveFlag, uint resizeFlag, + int delta, int maxDelta, int minDelta) +{ + if (cflags & moveFlag) { + if (delta > 0) + return (cflags & resizeFlag) ? qMin(delta, maxDelta) : delta; + return (cflags & resizeFlag) ? qMax(delta, minDelta) : delta; + } + return 0; +} + +static inline int getResizeDeltaComponent(uint cflags, uint resizeFlag, + uint resizeReverseFlag, int delta) +{ + if (cflags & resizeFlag) { + if (cflags & resizeReverseFlag) + return -delta; + return delta; + } + return 0; +} + +static inline bool isChildOfQMdiSubWindow(const QWidget *child) +{ + Q_ASSERT(child); + QWidget *parent = child->parentWidget(); + while (parent) { + if (qobject_cast<QMdiSubWindow *>(parent)) + return true; + parent = parent->parentWidget(); + } + return false; +} + +static inline bool isChildOfTabbedQMdiArea(const QMdiSubWindow *child) +{ + Q_ASSERT(child); + if (QMdiArea *mdiArea = child->mdiArea()) { + if (mdiArea->viewMode() == QMdiArea::TabbedView) + return true; + } + return false; +} + +template<typename T> +static inline ControlElement<T> *ptr(QWidget *widget) +{ + if (widget && widget->qt_metacast("ControlElement") + && strcmp(widget->metaObject()->className(), T::staticMetaObject.className()) == 0) { + return static_cast<ControlElement<T> *>(widget); + } + return 0; +} + +QString QMdiSubWindowPrivate::originalWindowTitle() +{ + Q_Q(QMdiSubWindow); + if (originalTitle.isNull()) { + originalTitle = q->window()->windowTitle(); + if (originalTitle.isNull()) + originalTitle = QLatin1String(""); + } + return originalTitle; +} + +void QMdiSubWindowPrivate::setNewWindowTitle() +{ + Q_Q(QMdiSubWindow); + QString childTitle = q->windowTitle(); + if (childTitle.isEmpty()) + return; + QString original = originalWindowTitle(); + if (!original.isEmpty()) { + if (!original.contains(QMdiSubWindow::tr("- [%1]").arg(childTitle))) + q->window()->setWindowTitle(QMdiSubWindow::tr("%1 - [%2]").arg(original, childTitle)); + + } else { + q->window()->setWindowTitle(childTitle); + } +} + +static inline bool isHoverControl(QStyle::SubControl control) +{ + return control != QStyle::SC_None && control != QStyle::SC_TitleBarLabel; +} + +#if defined(Q_WS_WIN) +static inline QRgb colorref2qrgb(COLORREF col) +{ + return qRgb(GetRValue(col),GetGValue(col),GetBValue(col)); +} +#endif + +#ifndef QT_NO_TOOLTIP +static void showToolTip(QHelpEvent *helpEvent, QWidget *widget, const QStyleOptionComplex &opt, + QStyle::ComplexControl complexControl, QStyle::SubControl subControl) +{ + Q_ASSERT(helpEvent); + Q_ASSERT(helpEvent->type() == QEvent::ToolTip); + Q_ASSERT(widget); + +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + // Native Mac windows don't show tool tip. + if (qobject_cast<QMacStyle *>(widget->style())) + return; +#endif + + // Convert CC_MdiControls to CC_TitleBar. Sub controls of different complex + // controls cannot be in the same switch as they might have the same value. + if (complexControl == QStyle::CC_MdiControls) { + if (subControl == QStyle::SC_MdiMinButton) + subControl = QStyle::SC_TitleBarMinButton; + else if (subControl == QStyle::SC_MdiCloseButton) + subControl = QStyle::SC_TitleBarCloseButton; + else if (subControl == QStyle::SC_MdiNormalButton) + subControl = QStyle::SC_TitleBarNormalButton; + else + subControl = QStyle::SC_None; + } + + // Don't change the tooltip for the base widget itself. + if (subControl == QStyle::SC_None) + return; + + QString toolTip; + + switch (subControl) { + case QStyle::SC_TitleBarMinButton: + toolTip = QMdiSubWindow::tr("Minimize"); + break; + case QStyle::SC_TitleBarMaxButton: + toolTip = QMdiSubWindow::tr("Maximize"); + break; + case QStyle::SC_TitleBarUnshadeButton: + toolTip = QMdiSubWindow::tr("Unshade"); + break; + case QStyle::SC_TitleBarShadeButton: + toolTip = QMdiSubWindow::tr("Shade"); + break; + case QStyle::SC_TitleBarNormalButton: + if (widget->isMaximized() || !qobject_cast<QMdiSubWindow *>(widget)) + toolTip = QMdiSubWindow::tr("Restore Down"); + else + toolTip = QMdiSubWindow::tr("Restore"); + break; + case QStyle::SC_TitleBarCloseButton: + toolTip = QMdiSubWindow::tr("Close"); + break; + case QStyle::SC_TitleBarContextHelpButton: + toolTip = QMdiSubWindow::tr("Help"); + break; + case QStyle::SC_TitleBarSysMenu: + toolTip = QMdiSubWindow::tr("Menu"); + break; + default: + break; + } + + const QRect rect = widget->style()->subControlRect(complexControl, &opt, subControl, widget); + QToolTip::showText(helpEvent->globalPos(), toolTip, widget, rect); +} +#endif // QT_NO_TOOLTIP + +namespace QMdi { +/* + \class ControlLabel + \internal +*/ +class ControlLabel : public QWidget +{ + Q_OBJECT +public: + ControlLabel(QMdiSubWindow *subWindow, QWidget *parent = 0); + + QSize sizeHint() const; + +signals: + void _q_clicked(); + void _q_doubleClicked(); + +protected: + bool event(QEvent *event); + void paintEvent(QPaintEvent *paintEvent); + void mousePressEvent(QMouseEvent *mouseEvent); + void mouseDoubleClickEvent(QMouseEvent *mouseEvent); + void mouseReleaseEvent(QMouseEvent *mouseEvent); + +private: + QPixmap label; + bool isPressed; + void updateWindowIcon(); +}; +} // namespace QMdi + +ControlLabel::ControlLabel(QMdiSubWindow *subWindow, QWidget *parent) + : QWidget(parent), isPressed(false) +{ + Q_UNUSED(subWindow); + setFocusPolicy(Qt::NoFocus); + updateWindowIcon(); + setFixedSize(label.size()); +} + +/* + \internal +*/ +QSize ControlLabel::sizeHint() const +{ + return label.size(); +} + +/* + \internal +*/ +bool ControlLabel::event(QEvent *event) +{ + if (event->type() == QEvent::WindowIconChange) + updateWindowIcon(); +#ifndef QT_NO_TOOLTIP + else if (event->type() == QEvent::ToolTip) { + QStyleOptionTitleBar options; + options.initFrom(this); + showToolTip(static_cast<QHelpEvent *>(event), this, options, + QStyle::CC_TitleBar, QStyle::SC_TitleBarSysMenu); + } +#endif + return QWidget::event(event); +} + +/* + \internal +*/ +void ControlLabel::paintEvent(QPaintEvent * /*paintEvent*/) +{ + QPainter painter(this); + painter.drawPixmap(0, 0, label); +} + +/* + \internal +*/ +void ControlLabel::mousePressEvent(QMouseEvent *mouseEvent) +{ + if (mouseEvent->button() != Qt::LeftButton) { + mouseEvent->ignore(); + return; + } + isPressed = true; +} + +/* + \internal +*/ +void ControlLabel::mouseDoubleClickEvent(QMouseEvent *mouseEvent) +{ + if (mouseEvent->button() != Qt::LeftButton) { + mouseEvent->ignore(); + return; + } + isPressed = false; + emit _q_doubleClicked(); +} + +/* + \internal +*/ +void ControlLabel::mouseReleaseEvent(QMouseEvent *mouseEvent) +{ + if (mouseEvent->button() != Qt::LeftButton) { + mouseEvent->ignore(); + return; + } + if (isPressed) { + isPressed = false; + emit _q_clicked(); + } +} + +/* + \internal +*/ +void ControlLabel::updateWindowIcon() +{ + QIcon menuIcon = windowIcon(); + if (menuIcon.isNull()) + menuIcon = style()->standardIcon(QStyle::SP_TitleBarMenuButton, 0, parentWidget()); + label = menuIcon.pixmap(16, 16); + update(); +} + +namespace QMdi { +/* + \class ControllerWidget + \internal +*/ +class ControllerWidget : public QWidget +{ + Q_OBJECT +public: + ControllerWidget(QMdiSubWindow *subWindow, QWidget *parent = 0); + QSize sizeHint() const; + void setControlVisible(QMdiSubWindowPrivate::WindowStateAction action, bool visible); + inline bool hasVisibleControls() const + { + return (visibleControls & QStyle::SC_MdiMinButton) + || (visibleControls & QStyle::SC_MdiNormalButton) + || (visibleControls & QStyle::SC_MdiCloseButton); + } + +signals: + void _q_minimize(); + void _q_restore(); + void _q_close(); + +protected: + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void leaveEvent(QEvent *event); + bool event(QEvent *event); + +private: + QStyle::SubControl activeControl; + QStyle::SubControl hoverControl; + QStyle::SubControls visibleControls; + void initStyleOption(QStyleOptionComplex *option) const; + QMdiArea *mdiArea; + inline QStyle::SubControl getSubControl(const QPoint &pos) const + { + QStyleOptionComplex opt; + initStyleOption(&opt); + return style()->hitTestComplexControl(QStyle::CC_MdiControls, &opt, pos, mdiArea); + } +}; +} // namespace QMdi + +/* + \internal +*/ +ControllerWidget::ControllerWidget(QMdiSubWindow *subWindow, QWidget *parent) + : QWidget(parent), + activeControl(QStyle::SC_None), + hoverControl(QStyle::SC_None), + visibleControls(QStyle::SC_None), + mdiArea(0) +{ + if (subWindow->parentWidget()) + mdiArea = qobject_cast<QMdiArea *>(subWindow->parentWidget()->parentWidget()); + setFocusPolicy(Qt::NoFocus); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setMouseTracking(true); +} + +/* + \internal +*/ +QSize ControllerWidget::sizeHint() const +{ + ensurePolished(); + QStyleOptionComplex opt; + initStyleOption(&opt); + QSize size(48, 16); + return style()->sizeFromContents(QStyle::CT_MdiControls, &opt, size, mdiArea); +} + +void ControllerWidget::setControlVisible(QMdiSubWindowPrivate::WindowStateAction action, bool visible) +{ + QStyle::SubControl subControl = QStyle::SC_None; + + // Map action from QMdiSubWindowPrivate::WindowStateAction to QStyle::SubControl. + if (action == QMdiSubWindowPrivate::MaximizeAction) + subControl = QStyle::SC_MdiNormalButton; + else if (action == QMdiSubWindowPrivate::CloseAction) + subControl = QStyle::SC_MdiCloseButton; + else if (action == QMdiSubWindowPrivate::MinimizeAction) + subControl = QStyle::SC_MdiMinButton; + + if (subControl == QStyle::SC_None) + return; + + if (visible && !(visibleControls & subControl)) + visibleControls |= subControl; + else if (!visible && (visibleControls & subControl)) + visibleControls &= ~subControl; +} + +/* + \internal +*/ +void ControllerWidget::paintEvent(QPaintEvent * /*paintEvent*/) +{ + QStyleOptionComplex opt; + initStyleOption(&opt); + if (activeControl == hoverControl) { + opt.activeSubControls = activeControl; + opt.state |= QStyle::State_Sunken; + } else if (hoverControl != QStyle::SC_None && (activeControl == QStyle::SC_None)) { + opt.activeSubControls = hoverControl; + opt.state |= QStyle::State_MouseOver; + } + QPainter painter(this); + style()->drawComplexControl(QStyle::CC_MdiControls, &opt, &painter, mdiArea); +} + +/* + \internal +*/ +void ControllerWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + activeControl = getSubControl(event->pos()); + update(); +} + +/* + \internal +*/ +void ControllerWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + + QStyle::SubControl under_mouse = getSubControl(event->pos()); + if (under_mouse == activeControl) { + switch (activeControl) { + case QStyle::SC_MdiCloseButton: + emit _q_close(); + break; + case QStyle::SC_MdiNormalButton: + emit _q_restore(); + break; + case QStyle::SC_MdiMinButton: + emit _q_minimize(); + break; + default: + break; + } + } + + activeControl = QStyle::SC_None; + update(); +} + +/* + \internal +*/ +void ControllerWidget::mouseMoveEvent(QMouseEvent *event) +{ + QStyle::SubControl under_mouse = getSubControl(event->pos()); + //test if hover state changes + if (hoverControl != under_mouse) { + hoverControl = under_mouse; + update(); + } +} + +/* + \internal +*/ +void ControllerWidget::leaveEvent(QEvent * /*event*/) +{ + hoverControl = QStyle::SC_None; + update(); +} + +/* + \internal +*/ +bool ControllerWidget::event(QEvent *event) +{ +#ifndef QT_NO_TOOLTIP + if (event->type() == QEvent::ToolTip) { + QStyleOptionComplex opt; + initStyleOption(&opt); + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + showToolTip(helpEvent, this, opt, QStyle::CC_MdiControls, getSubControl(helpEvent->pos())); + } +#endif // QT_NO_TOOLTIP + return QWidget::event(event); +} + +/* + \internal +*/ +void ControllerWidget::initStyleOption(QStyleOptionComplex *option) const +{ + option->initFrom(this); + option->subControls = visibleControls; + option->activeSubControls = QStyle::SC_None; +} + +/* + \internal +*/ +ControlContainer::ControlContainer(QMdiSubWindow *mdiChild) + : QObject(mdiChild), + previousLeft(0), + previousRight(0), +#ifndef QT_NO_MENUBAR + m_menuBar(0), +#endif + mdiChild(mdiChild) +{ + Q_ASSERT(mdiChild); + + m_controllerWidget = new ControlElement<ControllerWidget>(mdiChild); + connect(m_controllerWidget, SIGNAL(_q_close()), mdiChild, SLOT(close())); + connect(m_controllerWidget, SIGNAL(_q_restore()), mdiChild, SLOT(showNormal())); + connect(m_controllerWidget, SIGNAL(_q_minimize()), mdiChild, SLOT(showMinimized())); + + m_menuLabel = new ControlElement<ControlLabel>(mdiChild); + m_menuLabel->setWindowIcon(mdiChild->windowIcon()); +#ifndef QT_NO_MENU + connect(m_menuLabel, SIGNAL(_q_clicked()), mdiChild, SLOT(showSystemMenu())); +#endif + connect(m_menuLabel, SIGNAL(_q_doubleClicked()), mdiChild, SLOT(close())); +} + +ControlContainer::~ControlContainer() +{ +#ifndef QT_NO_MENUBAR + removeButtonsFromMenuBar(); +#endif + delete m_menuLabel; + m_menuLabel = 0; + delete m_controllerWidget; + m_controllerWidget = 0; +} + +#ifndef QT_NO_MENUBAR +/* + \internal +*/ +QMenuBar *QMdiSubWindowPrivate::menuBar() const +{ +#if defined(QT_NO_MAINWINDOW) + return 0; +#else + Q_Q(const QMdiSubWindow); + if (!q->isMaximized() || drawTitleBarWhenMaximized() || isChildOfTabbedQMdiArea(q)) + return 0; + + if (QMainWindow *mainWindow = qobject_cast<QMainWindow *>(q->window())) + return mainWindow->menuBar(); + + return 0; +#endif +} + +/* + \internal +*/ +void ControlContainer::showButtonsInMenuBar(QMenuBar *menuBar) +{ + if (!menuBar || !mdiChild || mdiChild->windowFlags() & Qt::FramelessWindowHint) + return; + m_menuBar = menuBar; + + if (m_menuLabel && mdiChild->windowFlags() & Qt::WindowSystemMenuHint) { + QWidget *currentLeft = menuBar->cornerWidget(Qt::TopLeftCorner); + if (currentLeft) + currentLeft->hide(); + if (currentLeft != m_menuLabel) { + menuBar->setCornerWidget(m_menuLabel, Qt::TopLeftCorner); + previousLeft = currentLeft; + } + m_menuLabel->show(); + } + ControllerWidget *controllerWidget = qobject_cast<ControllerWidget *>(m_controllerWidget); + if (controllerWidget && controllerWidget->hasVisibleControls()) { + QWidget *currentRight = menuBar->cornerWidget(Qt::TopRightCorner); + if (currentRight) + currentRight->hide(); + if (currentRight != m_controllerWidget) { + menuBar->setCornerWidget(m_controllerWidget, Qt::TopRightCorner); + previousRight = currentRight; + } + m_controllerWidget->show(); + } + mdiChild->d_func()->setNewWindowTitle(); +} + +/* + \internal +*/ +void ControlContainer::removeButtonsFromMenuBar(QMenuBar *menuBar) +{ + if (menuBar && menuBar != m_menuBar) { + // m_menubar was deleted while sub-window was maximized + previousRight = 0; + previousLeft = 0; + m_menuBar = menuBar; + } + + if (!m_menuBar || !mdiChild || qt_widget_private(mdiChild->window())->data.in_destructor) + return; + + QMdiSubWindow *child = 0; + if (m_controllerWidget) { + QWidget *currentRight = m_menuBar->cornerWidget(Qt::TopRightCorner); + if (currentRight == m_controllerWidget) { + if (ControlElement<ControllerWidget> *ce = ptr<ControllerWidget>(previousRight)) { + if (!ce->mdiChild || !ce->mdiChild->isMaximized()) + previousRight = 0; + else + child = ce->mdiChild; + } + m_menuBar->setCornerWidget(previousRight, Qt::TopRightCorner); + if (previousRight) { + previousRight->show(); + previousRight = 0; + } + } + m_controllerWidget->hide(); + m_controllerWidget->setParent(0); + } + if (m_menuLabel) { + QWidget *currentLeft = m_menuBar->cornerWidget(Qt::TopLeftCorner); + if (currentLeft == m_menuLabel) { + if (ControlElement<ControlLabel> *ce = ptr<ControlLabel>(previousLeft)) { + if (!ce->mdiChild || !ce->mdiChild->isMaximized()) + previousLeft = 0; + else if (!child) + child = mdiChild; + } + m_menuBar->setCornerWidget(previousLeft, Qt::TopLeftCorner); + if (previousLeft) { + previousLeft->show(); + previousLeft = 0; + } + } + m_menuLabel->hide(); + m_menuLabel->setParent(0); + } + m_menuBar->update(); + if (child) + child->d_func()->setNewWindowTitle(); + else if (mdiChild) + mdiChild->window()->setWindowTitle(mdiChild->d_func()->originalWindowTitle()); +} + +#endif // QT_NO_MENUBAR + +void ControlContainer::updateWindowIcon(const QIcon &windowIcon) +{ + if (m_menuLabel) + m_menuLabel->setWindowIcon(windowIcon); +} + +/*! + \internal +*/ +QMdiSubWindowPrivate::QMdiSubWindowPrivate() + : baseWidget(0), + restoreFocusWidget(0), + controlContainer(0), +#ifndef QT_NO_SIZEGRIP + sizeGrip(0), +#endif +#ifndef QT_NO_RUBBERBAND + rubberBand(0), +#endif + userMinimumSize(0,0), + resizeEnabled(true), + moveEnabled(true), + isInInteractiveMode(false), +#ifndef QT_NO_RUBBERBAND + isInRubberBandMode(false), +#endif + isShadeMode(false), + ignoreWindowTitleChange(false), + ignoreNextActivationEvent(false), + activationEnabled(true), + isShadeRequestFromMinimizeMode(false), + isMaximizeMode(false), + isWidgetHiddenByUs(false), + isActive(false), + isExplicitlyDeactivated(false), + keyboardSingleStep(5), + keyboardPageStep(20), + resizeTimerId(-1), + currentOperation(None), + hoveredSubControl(QStyle::SC_None), + activeSubControl(QStyle::SC_None), + focusInReason(Qt::ActiveWindowFocusReason) +{ + initOperationMap(); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::_q_updateStaysOnTopHint() +{ +#ifndef QT_NO_ACTION + Q_Q(QMdiSubWindow); + if (QAction *senderAction = qobject_cast<QAction *>(q->sender())) { + if (senderAction->isChecked()) { + q->setWindowFlags(q->windowFlags() | Qt::WindowStaysOnTopHint); + q->raise(); + } else { + q->setWindowFlags(q->windowFlags() & ~Qt::WindowStaysOnTopHint); + q->lower(); + } + } +#endif // QT_NO_ACTION +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::_q_enterInteractiveMode() +{ +#ifndef QT_NO_ACTION + Q_Q(QMdiSubWindow); + QAction *action = qobject_cast<QAction *>(q->sender()); + if (!action) + return; + + QPoint pressPos; + if (actions[MoveAction] && actions[MoveAction] == action) { + currentOperation = Move; + pressPos = QPoint(q->width() / 2, titleBarHeight() - 1); + } else if (actions[ResizeAction] && actions[ResizeAction] == action) { + currentOperation = q->isLeftToRight() ? BottomRightResize : BottomLeftResize; + int offset = q->style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, q) / 2; + int x = q->isLeftToRight() ? q->width() - offset : offset; + pressPos = QPoint(x, q->height() - offset); + } else { + return; + } + + updateCursor(); +#ifndef QT_NO_CURSOR + q->cursor().setPos(q->mapToGlobal(pressPos)); +#endif + mousePressPosition = q->mapToParent(pressPos); + oldGeometry = q->geometry(); + isInInteractiveMode = true; + q->setFocus(); +#ifndef QT_NO_RUBBERBAND + if ((q->testOption(QMdiSubWindow::RubberBandResize) + && (currentOperation == BottomRightResize || currentOperation == BottomLeftResize)) + || (q->testOption(QMdiSubWindow::RubberBandMove) && currentOperation == Move)) { + enterRubberBandMode(); + } else +#endif // QT_NO_RUBBERBAND + { + q->grabMouse(); + } +#endif // QT_NO_ACTION +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::_q_processFocusChanged(QWidget *old, QWidget *now) +{ + Q_UNUSED(old); + Q_Q(QMdiSubWindow); + if (now && (now == q || q->isAncestorOf(now))) { + if (now == q && !isInInteractiveMode) + setFocusWidget(); + setActive(true); + } +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::leaveInteractiveMode() +{ + Q_Q(QMdiSubWindow); +#ifndef QT_NO_RUBBERBAND + if (isInRubberBandMode) + leaveRubberBandMode(); + else +#endif + q->releaseMouse(); + isInInteractiveMode = false; + currentOperation = None; + updateDirtyRegions(); + updateCursor(); + if (baseWidget && baseWidget->focusWidget()) + baseWidget->focusWidget()->setFocus(); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::removeBaseWidget() +{ + if (!baseWidget) + return; + + Q_Q(QMdiSubWindow); + baseWidget->removeEventFilter(q); + if (QLayout *layout = q->layout()) + layout->removeWidget(baseWidget); + if (baseWidget->windowTitle() == q->windowTitle()) { + ignoreWindowTitleChange = true; + q->setWindowTitle(QString()); + ignoreWindowTitleChange = false; + q->setWindowModified(false); + } + lastChildWindowTitle.clear(); + baseWidget->setParent(0); + baseWidget = 0; + isWidgetHiddenByUs = false; +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::initOperationMap() +{ + operationMap.insert(Move, OperationInfo(HMove | VMove, Qt::ArrowCursor, false)); + operationMap.insert(TopResize, OperationInfo(VMove | VResize | VResizeReverse, Qt::SizeVerCursor)); + operationMap.insert(BottomResize, OperationInfo(VResize, Qt::SizeVerCursor)); + operationMap.insert(LeftResize, OperationInfo(HMove | HResize | HResizeReverse, Qt::SizeHorCursor)); + operationMap.insert(RightResize, OperationInfo(HResize, Qt::SizeHorCursor)); + operationMap.insert(TopLeftResize, OperationInfo(HMove | VMove | HResize | VResize | VResizeReverse + | HResizeReverse, Qt::SizeFDiagCursor)); + operationMap.insert(TopRightResize, OperationInfo(VMove | HResize | VResize + | VResizeReverse, Qt::SizeBDiagCursor)); + operationMap.insert(BottomLeftResize, OperationInfo(HMove | HResize | VResize | HResizeReverse, + Qt::SizeBDiagCursor)); + operationMap.insert(BottomRightResize, OperationInfo(HResize | VResize, Qt::SizeFDiagCursor)); +} + +#ifndef QT_NO_MENU + +/*! + \internal +*/ +void QMdiSubWindowPrivate::createSystemMenu() +{ + Q_Q(QMdiSubWindow); + Q_ASSERT_X(q, "QMdiSubWindowPrivate::createSystemMenu", + "You can NOT call this function before QMdiSubWindow's ctor"); + systemMenu = new QMenu(q); + const QStyle *style = q->style(); + addToSystemMenu(RestoreAction, QMdiSubWindow::tr("&Restore"), SLOT(showNormal())); + actions[RestoreAction]->setIcon(style->standardIcon(QStyle::SP_TitleBarNormalButton, 0, q)); + actions[RestoreAction]->setEnabled(false); + addToSystemMenu(MoveAction, QMdiSubWindow::tr("&Move"), SLOT(_q_enterInteractiveMode())); + addToSystemMenu(ResizeAction, QMdiSubWindow::tr("&Size"), SLOT(_q_enterInteractiveMode())); + addToSystemMenu(MinimizeAction, QMdiSubWindow::tr("Mi&nimize"), SLOT(showMinimized())); + actions[MinimizeAction]->setIcon(style->standardIcon(QStyle::SP_TitleBarMinButton, 0, q)); + addToSystemMenu(MaximizeAction, QMdiSubWindow::tr("Ma&ximize"), SLOT(showMaximized())); + actions[MaximizeAction]->setIcon(style->standardIcon(QStyle::SP_TitleBarMaxButton, 0, q)); + addToSystemMenu(StayOnTopAction, QMdiSubWindow::tr("Stay on &Top"), SLOT(_q_updateStaysOnTopHint())); + actions[StayOnTopAction]->setCheckable(true); + systemMenu->addSeparator(); + addToSystemMenu(CloseAction, QMdiSubWindow::tr("&Close"), SLOT(close())); + actions[CloseAction]->setIcon(style->standardIcon(QStyle::SP_TitleBarCloseButton, 0, q)); +#if !defined(QT_NO_SHORTCUT) + actions[CloseAction]->setShortcut(QKeySequence::Close); +#endif + updateActions(); +} +#endif + +/*! + \internal +*/ +void QMdiSubWindowPrivate::updateCursor() +{ +#ifndef QT_NO_CURSOR + Q_Q(QMdiSubWindow); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(q->style())) + return; +#endif + + if (currentOperation == None) { + q->unsetCursor(); + return; + } + + if (currentOperation == Move || operationMap.find(currentOperation).value().hover) { + q->setCursor(operationMap.find(currentOperation).value().cursorShape); + return; + } +#endif +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::updateDirtyRegions() +{ + // No update necessary + if (!q_func()->parent()) + return; + + foreach (Operation operation, operationMap.keys()) + operationMap.find(operation).value().region = getRegion(operation); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::updateGeometryConstraints() +{ + Q_Q(QMdiSubWindow); + if (!q->parent()) + return; + + internalMinimumSize = (!q->isMinimized() && !q->minimumSize().isNull()) + ? q->minimumSize() : q->minimumSizeHint(); + int margin, minWidth; + sizeParameters(&margin, &minWidth); + q->setContentsMargins(margin, titleBarHeight(), margin, margin); + if (q->isMaximized() || (q->isMinimized() && !q->isShaded())) { + moveEnabled = false; + resizeEnabled = false; + } else { + moveEnabled = true; + if ((q->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) || q->isShaded()) + resizeEnabled = false; + else + resizeEnabled = true; + } + updateDirtyRegions(); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::updateMask() +{ + Q_Q(QMdiSubWindow); + if (!q->mask().isEmpty()) + q->clearMask(); + + if (!q->parent()) + return; + + if ((q->isMaximized() && !drawTitleBarWhenMaximized()) + || q->windowFlags() & Qt::FramelessWindowHint) + return; + + if (resizeTimerId == -1) + cachedStyleOptions = titleBarOptions(); + cachedStyleOptions.rect = q->rect(); + QStyleHintReturnMask frameMask; + q->style()->styleHint(QStyle::SH_WindowFrame_Mask, &cachedStyleOptions, q, &frameMask); + if (!frameMask.region.isEmpty()) + q->setMask(frameMask.region); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setNewGeometry(const QPoint &pos) +{ + Q_Q(QMdiSubWindow); + Q_ASSERT(currentOperation != None); + Q_ASSERT(q->parent()); + + uint cflags = operationMap.find(currentOperation).value().changeFlags; + int posX = pos.x(); + int posY = pos.y(); + + const bool restrictHorizontal = !q->testOption(QMdiSubWindow::AllowOutsideAreaHorizontally); + const bool restrictVertical = !q->testOption(QMdiSubWindow::AllowOutsideAreaVertically); + + if (restrictHorizontal || restrictVertical) { + QRect parentRect = q->parentWidget()->rect(); + if (restrictVertical && (cflags & VResizeReverse || currentOperation == Move)) { + posY = qMin(qMax(mousePressPosition.y() - oldGeometry.y(), posY), + parentRect.height() - BoundaryMargin); + } + if (currentOperation == Move) { + if (restrictHorizontal) + posX = qMin(qMax(BoundaryMargin, posX), parentRect.width() - BoundaryMargin); + if (restrictVertical) + posY = qMin(posY, parentRect.height() - BoundaryMargin); + } else { + if (restrictHorizontal) { + if (cflags & HResizeReverse) + posX = qMax(mousePressPosition.x() - oldGeometry.x(), posX); + else + posX = qMin(parentRect.width() - (oldGeometry.x() + oldGeometry.width() + - mousePressPosition.x()), posX); + } + if (restrictVertical && !(cflags & VResizeReverse)) { + posY = qMin(parentRect.height() - (oldGeometry.y() + oldGeometry.height() + - mousePressPosition.y()), posY); + } + } + } + + QRect geometry; + if (cflags & (HMove | VMove)) { + int dx = getMoveDeltaComponent(cflags, HMove, HResize, posX - mousePressPosition.x(), + oldGeometry.width() - internalMinimumSize.width(), + oldGeometry.width() - q->maximumWidth()); + int dy = getMoveDeltaComponent(cflags, VMove, VResize, posY - mousePressPosition.y(), + oldGeometry.height() - internalMinimumSize.height(), + oldGeometry.height() - q->maximumHeight()); + geometry.setTopLeft(oldGeometry.topLeft() + QPoint(dx, dy)); + } else { + geometry.setTopLeft(q->geometry().topLeft()); + } + + if (cflags & (HResize | VResize)) { + int dx = getResizeDeltaComponent(cflags, HResize, HResizeReverse, + posX - mousePressPosition.x()); + int dy = getResizeDeltaComponent(cflags, VResize, VResizeReverse, + posY - mousePressPosition.y()); + geometry.setSize(oldGeometry.size() + QSize(dx, dy)); + } else { + geometry.setSize(q->geometry().size()); + } + + setNewGeometry(&geometry); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setMinimizeMode() +{ + Q_Q(QMdiSubWindow); + Q_ASSERT(q->parent()); + + ensureWindowState(Qt::WindowMinimized); + isShadeRequestFromMinimizeMode = true; + q->showShaded(); + isShadeRequestFromMinimizeMode = false; + + moveEnabled = false; +#ifndef QT_NO_ACTION + setEnabled(MoveAction, moveEnabled); +#endif + + Q_ASSERT(q->windowState() & Qt::WindowMinimized); + Q_ASSERT(!(q->windowState() & Qt::WindowMaximized)); + // This should be a valid assert, but people can actually re-implement + // setVisible and do crazy stuff, so we're not guaranteed that + // the widget is hidden after calling hide(). + // Q_ASSERT(baseWidget ? baseWidget->isHidden() : true); + + setActive(true); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setNormalMode() +{ + Q_Q(QMdiSubWindow); + Q_ASSERT(q->parent()); + + isShadeMode = false; + isMaximizeMode = false; + + ensureWindowState(Qt::WindowNoState); +#ifndef QT_NO_MENUBAR + removeButtonsFromMenuBar(); +#endif + + // Hide the window before we change the geometry to avoid multiple resize + // events and wrong window state. + const bool wasVisible = q->isVisible(); + if (wasVisible) + q->setVisible(false); + + // Restore minimum size if set by user. + if (!userMinimumSize.isNull()) { + q->setMinimumSize(userMinimumSize); + userMinimumSize = QSize(0, 0); + } + + // Show the internal widget if it was hidden by us, + if (baseWidget && isWidgetHiddenByUs) { + baseWidget->show(); + isWidgetHiddenByUs = false; + } + + updateGeometryConstraints(); + QRect newGeometry = oldGeometry; + newGeometry.setSize(restoreSize.expandedTo(internalMinimumSize)); + q->setGeometry(newGeometry); + + if (wasVisible) + q->setVisible(true); + + // Invalidate the restore size. + restoreSize.setWidth(-1); + restoreSize.setHeight(-1); + +#ifndef QT_NO_SIZEGRIP + setSizeGripVisible(true); +#endif + +#ifndef QT_NO_ACTION + setEnabled(MoveAction, true); + setEnabled(MaximizeAction, true); + setEnabled(MinimizeAction, true); + setEnabled(RestoreAction, false); + setEnabled(ResizeAction, resizeEnabled); +#endif // QT_NO_ACTION + + Q_ASSERT(!(q_func()->windowState() & Qt::WindowMinimized)); + // This sub-window can be maximized when shown above if not the + // QMdiArea::DontMaximizeSubWindowOnActionvation is set. Make sure + // the Qt::WindowMaximized flag is set accordingly. + Q_ASSERT((isMaximizeMode && q_func()->windowState() & Qt::WindowMaximized) + || (!isMaximizeMode && !(q_func()->windowState() & Qt::WindowMaximized))); + Q_ASSERT(!isShadeMode); + + setActive(true); + restoreFocus(); + updateMask(); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setMaximizeMode() +{ + Q_Q(QMdiSubWindow); + Q_ASSERT(q->parent()); + + ensureWindowState(Qt::WindowMaximized); + isShadeMode = false; + isMaximizeMode = true; + + if (!restoreFocusWidget && q->isAncestorOf(QApplication::focusWidget())) + restoreFocusWidget = QApplication::focusWidget(); + +#ifndef QT_NO_SIZEGRIP + setSizeGripVisible(false); +#endif + + // Store old geometry and set restore size if not already set. + if (!restoreSize.isValid()) { + oldGeometry = q->geometry(); + restoreSize.setWidth(oldGeometry.width()); + restoreSize.setHeight(oldGeometry.height()); + } + + // Hide the window before we change the geometry to avoid multiple resize + // events and wrong window state. + const bool wasVisible = q->isVisible(); + if (wasVisible) + q->setVisible(false); + + // Show the internal widget if it was hidden by us. + if (baseWidget && isWidgetHiddenByUs) { + baseWidget->show(); + isWidgetHiddenByUs = false; + } + + updateGeometryConstraints(); + + if (wasVisible) { +#ifndef QT_NO_MENUBAR + if (QMenuBar *mBar = menuBar()) + showButtonsInMenuBar(mBar); + else +#endif + if (!controlContainer) + controlContainer = new ControlContainer(q); + } + + QWidget *parent = q->parentWidget(); + QRect availableRect = parent->contentsRect(); + + // Adjust geometry if the sub-window is inside a scroll area. + QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea *>(parent->parentWidget()); + if (scrollArea && scrollArea->viewport() == parent) { + QScrollBar *hbar = scrollArea->horizontalScrollBar(); + QScrollBar *vbar = scrollArea->verticalScrollBar(); + const int xOffset = hbar ? hbar->value() : 0; + const int yOffset = vbar ? vbar->value() : 0; + availableRect.adjust(-xOffset, -yOffset, -xOffset, -yOffset); + oldGeometry.adjust(xOffset, yOffset, xOffset, yOffset); + } + + setNewGeometry(&availableRect); + // QWidget::setGeometry will reset Qt::WindowMaximized so we have to update it here. + ensureWindowState(Qt::WindowMaximized); + + if (wasVisible) + q->setVisible(true); + + resizeEnabled = false; + moveEnabled = false; + +#ifndef QT_NO_ACTION + setEnabled(MoveAction, moveEnabled); + setEnabled(MaximizeAction, false); + setEnabled(MinimizeAction, true); + setEnabled(RestoreAction, true); + setEnabled(ResizeAction, resizeEnabled); +#endif // QT_NO_ACTION + + Q_ASSERT(q->windowState() & Qt::WindowMaximized); + Q_ASSERT(!(q->windowState() & Qt::WindowMinimized)); + + restoreFocus(); + updateMask(); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setActive(bool activate, bool changeFocus) +{ + Q_Q(QMdiSubWindow); + if (!q->parent() || !activationEnabled) + return; + + if (activate && !isActive && q->isEnabled()) { + isActive = true; + isExplicitlyDeactivated = false; + Qt::WindowStates oldWindowState = q->windowState(); + ensureWindowState(Qt::WindowActive); + emit q->aboutToActivate(); +#ifndef QT_NO_MENUBAR + if (QMenuBar *mBar = menuBar()) + showButtonsInMenuBar(mBar); +#endif + Q_ASSERT(isActive); + emit q->windowStateChanged(oldWindowState, q->windowState()); + } else if (!activate && isActive) { + isActive = false; + Qt::WindowStates oldWindowState = q->windowState(); + q->overrideWindowState(q->windowState() & ~Qt::WindowActive); + if (changeFocus) { + QWidget *focusWidget = QApplication::focusWidget(); + if (focusWidget && (focusWidget == q || q->isAncestorOf(focusWidget))) + focusWidget->clearFocus(); + } + if (baseWidget) + baseWidget->overrideWindowState(baseWidget->windowState() & ~Qt::WindowActive); + Q_ASSERT(!isActive); + emit q->windowStateChanged(oldWindowState, q->windowState()); + } + + if (activate && isActive && q->isEnabled() && !q->hasFocus() + && !q->isAncestorOf(QApplication::focusWidget())) { + if (changeFocus) + setFocusWidget(); + ensureWindowState(Qt::WindowActive); + } + + int frameWidth = q->style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, q); + int titleBarHeight = this->titleBarHeight(); + QRegion windowDecoration = QRegion(0, 0, q->width(), q->height()); + windowDecoration -= QRegion(frameWidth, titleBarHeight, q->width() - 2 * frameWidth, + q->height() - titleBarHeight - frameWidth); + + // Make sure we don't use cached style options if we get + // resize events right before activation/deactivation. + if (resizeTimerId != -1) { + q->killTimer(resizeTimerId); + resizeTimerId = -1; + updateDirtyRegions(); + } + + q->update(windowDecoration); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::processClickedSubControl() +{ + Q_Q(QMdiSubWindow); + switch (activeSubControl) { + case QStyle::SC_TitleBarContextHelpButton: +#ifndef QT_NO_WHATSTHIS + QWhatsThis::enterWhatsThisMode(); +#endif + break; + case QStyle::SC_TitleBarShadeButton: + q->showShaded(); + hoveredSubControl = QStyle::SC_TitleBarUnshadeButton; + break; + case QStyle::SC_TitleBarUnshadeButton: + if (q->isShaded()) + hoveredSubControl = QStyle::SC_TitleBarShadeButton; + q->showNormal(); + break; + case QStyle::SC_TitleBarMinButton: +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(q->style())) { + if (q->isMinimized()) + q->showNormal(); + else + q->showMinimized(); + break; + } +#endif + q->showMinimized(); + break; + case QStyle::SC_TitleBarNormalButton: + if (q->isShaded()) + hoveredSubControl = QStyle::SC_TitleBarMinButton; + q->showNormal(); + break; + case QStyle::SC_TitleBarMaxButton: +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(q->style())) { + if (q->isMaximized()) + q->showNormal(); + else + q->showMaximized(); + break; + } +#endif + q->showMaximized(); + break; + case QStyle::SC_TitleBarCloseButton: + q->close(); + break; + default: + break; + } +} + +/*! + \internal +*/ +QRegion QMdiSubWindowPrivate::getRegion(Operation operation) const +{ + Q_Q(const QMdiSubWindow); + int width = q->width(); + int height = q->height(); + int titleBarHeight = this->titleBarHeight(); + int frameWidth = q->style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, q); + int cornerConst = titleBarHeight - frameWidth; + int titleBarConst = 2 * titleBarHeight; + + if (operation == Move) { + QStyleOptionTitleBar titleBarOptions = this->titleBarOptions(); + QRegion move(frameWidth, frameWidth, width - 2 * frameWidth, cornerConst); + // Depending on which window flags are set, activated sub controllers will + // be subtracted from the 'move' region. + for (int i = 0; i < NumSubControls; ++i) { + if (SubControls[i] == QStyle::SC_TitleBarLabel) + continue; + move -= QRegion(q->style()->subControlRect(QStyle::CC_TitleBar, &titleBarOptions, + SubControls[i])); + } + return move; + } + + QRegion region; +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(q->style())) + return region; +#endif + + switch (operation) { + case TopResize: + region = QRegion(titleBarHeight, 0, width - titleBarConst, frameWidth); + break; + case BottomResize: + region = QRegion(titleBarHeight, height - frameWidth, width - titleBarConst, frameWidth); + break; + case LeftResize: + region = QRegion(0, titleBarHeight, frameWidth, height - titleBarConst); + break; + case RightResize: + region = QRegion(width - frameWidth, titleBarHeight, frameWidth, height - titleBarConst); + break; + case TopLeftResize: + region = QRegion(0, 0, titleBarHeight, titleBarHeight) + - QRegion(frameWidth, frameWidth, cornerConst, cornerConst); + break; + case TopRightResize: + region = QRegion(width - titleBarHeight, 0, titleBarHeight, titleBarHeight) + - QRegion(width - titleBarHeight, frameWidth, cornerConst, cornerConst); + break; + case BottomLeftResize: + region = QRegion(0, height - titleBarHeight, titleBarHeight, titleBarHeight) + - QRegion(frameWidth, height - titleBarHeight, cornerConst, cornerConst); + break; + case BottomRightResize: + region = QRegion(width - titleBarHeight, height - titleBarHeight, titleBarHeight, titleBarHeight) + - QRegion(width - titleBarHeight, height - titleBarHeight, cornerConst, cornerConst); + break; + default: + break; + } + + return region; +} + +/*! + \internal +*/ +QMdiSubWindowPrivate::Operation QMdiSubWindowPrivate::getOperation(const QPoint &pos) const +{ + OperationInfoMap::const_iterator it; + for (it = operationMap.constBegin(); it != operationMap.constEnd(); ++it) + if (it.value().region.contains(pos)) + return it.key(); + return None; +} + +extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); + +/*! + \internal +*/ +QStyleOptionTitleBar QMdiSubWindowPrivate::titleBarOptions() const +{ + Q_Q(const QMdiSubWindow); + QStyleOptionTitleBar titleBarOptions; + titleBarOptions.initFrom(q); + if (activeSubControl != QStyle::SC_None) { + if (hoveredSubControl == activeSubControl) { + titleBarOptions.state |= QStyle::State_Sunken; + titleBarOptions.activeSubControls = activeSubControl; + } + } else if (autoRaise() && hoveredSubControl != QStyle::SC_None + && hoveredSubControl != QStyle::SC_TitleBarLabel) { + titleBarOptions.state |= QStyle::State_MouseOver; + titleBarOptions.activeSubControls = hoveredSubControl; + } else { + titleBarOptions.state &= ~QStyle::State_MouseOver; + titleBarOptions.activeSubControls = QStyle::SC_None; + } + + titleBarOptions.subControls = QStyle::SC_All; + titleBarOptions.titleBarFlags = q->windowFlags(); + titleBarOptions.titleBarState = q->windowState(); + titleBarOptions.palette = titleBarPalette; + titleBarOptions.icon = menuIcon; + + if (isActive) { + titleBarOptions.state |= QStyle::State_Active; + titleBarOptions.titleBarState |= QStyle::State_Active; + titleBarOptions.palette.setCurrentColorGroup(QPalette::Active); + } else { + titleBarOptions.state &= ~QStyle::State_Active; + titleBarOptions.palette.setCurrentColorGroup(QPalette::Inactive); + } + + int border = hasBorder(titleBarOptions) ? 4 : 0; + int paintHeight = titleBarHeight(titleBarOptions); + paintHeight -= q->isMinimized() ? 2 * border : border; + titleBarOptions.rect = QRect(border, border, q->width() - 2 * border, paintHeight); + + if (!windowTitle.isEmpty()) { + // Set the text here before asking for the width of the title bar label + // in case people uses the actual text to calculate the width. + titleBarOptions.text = windowTitle; + titleBarOptions.fontMetrics = QFontMetrics(font); + int width = q->style()->subControlRect(QStyle::CC_TitleBar, &titleBarOptions, + QStyle::SC_TitleBarLabel, q).width(); + // Set elided text if we don't have enough space for the entire title. + titleBarOptions.text = titleBarOptions.fontMetrics.elidedText(windowTitle, Qt::ElideRight, width); + } + return titleBarOptions; +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::ensureWindowState(Qt::WindowState state) +{ + Q_Q(QMdiSubWindow); + Qt::WindowStates windowStates = q->windowState() | state; + switch (state) { + case Qt::WindowMinimized: + windowStates &= ~Qt::WindowMaximized; + windowStates &= ~Qt::WindowNoState; + break; + case Qt::WindowMaximized: + windowStates &= ~Qt::WindowMinimized; + windowStates &= ~Qt::WindowNoState; + break; + case Qt::WindowNoState: + windowStates &= ~Qt::WindowMinimized; + windowStates &= ~Qt::WindowMaximized; + break; + default: + break; + } + if (baseWidget) { + if (!(baseWidget->windowState() & Qt::WindowActive) && windowStates & Qt::WindowActive) + baseWidget->overrideWindowState(windowStates & ~Qt::WindowActive); + else + baseWidget->overrideWindowState(windowStates); + } + q->overrideWindowState(windowStates); +} + +/*! + \internal +*/ +int QMdiSubWindowPrivate::titleBarHeight(const QStyleOptionTitleBar &options) const +{ + Q_Q(const QMdiSubWindow); + if (!q->parent() || q->windowFlags() & Qt::FramelessWindowHint + || (q->isMaximized() && !drawTitleBarWhenMaximized())) { + return 0; + } + + int height = q->style()->pixelMetric(QStyle::PM_TitleBarHeight, &options, q); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + // ### Fix mac style, the +4 pixels hack is not necessary anymore + if (qobject_cast<QMacStyle *>(q->style())) + height -= 4; +#endif + if (hasBorder(options)) + height += q->isMinimized() ? 8 : 4; + return height; +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::sizeParameters(int *margin, int *minWidth) const +{ + Q_Q(const QMdiSubWindow); + Qt::WindowFlags flags = q->windowFlags(); + if (!q->parent() || flags & Qt::FramelessWindowHint) { + *margin = 0; + *minWidth = 0; + return; + } + + if (q->isMaximized() && !drawTitleBarWhenMaximized()) + *margin = 0; + else + *margin = q->style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, q); + + QStyleOptionTitleBar opt = this->titleBarOptions(); + int tempWidth = 0; + for (int i = 0; i < NumSubControls; ++i) { + if (SubControls[i] == QStyle::SC_TitleBarLabel) { + tempWidth += 30; + continue; + } + QRect rect = q->style()->subControlRect(QStyle::CC_TitleBar, &opt, SubControls[i], q); + if (!rect.isValid()) + continue; + tempWidth += rect.width(); + } + *minWidth = tempWidth; +} + +/*! + \internal +*/ +bool QMdiSubWindowPrivate::drawTitleBarWhenMaximized() const +{ + Q_Q(const QMdiSubWindow); + if (q->window()->testAttribute(Qt::WA_CanHostQMdiSubWindowTitleBar)) + return false; + + if (isChildOfTabbedQMdiArea(q)) + return false; + +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) || defined(Q_OS_WINCE_WM) + return true; +#else + if (q->style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, q)) + return true; +#if defined(QT_NO_MENUBAR) || defined(QT_NO_MAINWINDOW) + return true; +#else + QMainWindow *mainWindow = qobject_cast<QMainWindow *>(q->window()); + if (!mainWindow || !qobject_cast<QMenuBar *>(mainWindow->menuWidget()) + || mainWindow->menuWidget()->isHidden()) + return true; + + return isChildOfQMdiSubWindow(q); +#endif +#endif +} + +#ifndef QT_NO_MENUBAR + +/*! + \internal +*/ +void QMdiSubWindowPrivate::showButtonsInMenuBar(QMenuBar *menuBar) +{ + Q_Q(QMdiSubWindow); + Q_ASSERT(q->isMaximized() && !drawTitleBarWhenMaximized()); + + if (isChildOfTabbedQMdiArea(q)) + return; + + removeButtonsFromMenuBar(); + if (!controlContainer) + controlContainer = new ControlContainer(q); + + ignoreWindowTitleChange = true; + controlContainer->showButtonsInMenuBar(menuBar); + ignoreWindowTitleChange = false; + + QWidget *topLevelWindow = q->window(); + topLevelWindow->setWindowModified(q->isWindowModified()); + topLevelWindow->installEventFilter(q); + + int buttonHeight = 0; + if (controlContainer->controllerWidget()) + buttonHeight = controlContainer->controllerWidget()->height(); + else if (controlContainer->systemMenuLabel()) + buttonHeight = controlContainer->systemMenuLabel()->height(); + + // This will rarely happen. + if (menuBar && menuBar->height() < buttonHeight + && topLevelWindow->layout()) { + // Make sure topLevelWindow->contentsRect returns correct geometry. + // topLevelWidget->updateGeoemtry will not do the trick here since it will post the event. + QEvent event(QEvent::LayoutRequest); + QApplication::sendEvent(topLevelWindow, &event); + } +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::removeButtonsFromMenuBar() +{ + Q_Q(QMdiSubWindow); + + if (!controlContainer || isChildOfTabbedQMdiArea(q)) + return; + + QMenuBar *currentMenuBar = 0; +#ifndef QT_NO_MAINWINDOW + if (QMainWindow *mainWindow = qobject_cast<QMainWindow *>(q->window())) { + // NB! We can't use menuBar() here because that one will actually create + // a menubar for us if not set. That's not what we want :-) + currentMenuBar = qobject_cast<QMenuBar *>(mainWindow->menuWidget()); + } +#endif + + ignoreWindowTitleChange = true; + controlContainer->removeButtonsFromMenuBar(currentMenuBar); + ignoreWindowTitleChange = false; + + QWidget *topLevelWindow = q->window(); + topLevelWindow->removeEventFilter(q); + if (baseWidget && !drawTitleBarWhenMaximized()) + topLevelWindow->setWindowModified(false); + originalTitle = QString::null; +} + +#endif // QT_NO_MENUBAR + +void QMdiSubWindowPrivate::updateWindowTitle(bool isRequestFromChild) +{ + Q_Q(QMdiSubWindow); + if (isRequestFromChild && !q->windowTitle().isEmpty() && !lastChildWindowTitle.isEmpty() + && lastChildWindowTitle != q->windowTitle()) { + return; + } + + QWidget *titleWidget = 0; + if (isRequestFromChild) + titleWidget = baseWidget; + else + titleWidget = q; + if (!titleWidget || titleWidget->windowTitle().isEmpty()) + return; + + ignoreWindowTitleChange = true; + q->setWindowTitle(titleWidget->windowTitle()); + if (q->maximizedButtonsWidget()) + setNewWindowTitle(); + ignoreWindowTitleChange = false; +} + +#ifndef QT_NO_RUBBERBAND +void QMdiSubWindowPrivate::enterRubberBandMode() +{ + Q_Q(QMdiSubWindow); + if (q->isMaximized()) + return; + Q_ASSERT(oldGeometry.isValid()); + Q_ASSERT(q->parent()); + if (!rubberBand) { + rubberBand = new QRubberBand(QRubberBand::Rectangle, q->parentWidget()); + // For accessibility to identify this special widget. + rubberBand->setObjectName(QLatin1String("qt_rubberband")); + } + QPoint rubberBandPos = q->mapToParent(QPoint(0, 0)); + rubberBand->setGeometry(rubberBandPos.x(), rubberBandPos.y(), + oldGeometry.width(), oldGeometry.height()); + rubberBand->show(); + isInRubberBandMode = true; + q->grabMouse(); +} + +void QMdiSubWindowPrivate::leaveRubberBandMode() +{ + Q_Q(QMdiSubWindow); + Q_ASSERT(rubberBand); + Q_ASSERT(isInRubberBandMode); + q->releaseMouse(); + isInRubberBandMode = false; + q->setGeometry(rubberBand->geometry()); + rubberBand->hide(); + currentOperation = None; +} +#endif // QT_NO_RUBBERBAND + +// Taken from the old QWorkspace (::readColors()) +QPalette QMdiSubWindowPrivate::desktopPalette() const +{ + Q_Q(const QMdiSubWindow); + QPalette newPalette = q->palette(); + + bool colorsInitialized = false; +#ifdef Q_WS_WIN // ask system properties on windows +#ifndef SPI_GETGRADIENTCAPTIONS +#define SPI_GETGRADIENTCAPTIONS 0x1008 +#endif +#ifndef COLOR_GRADIENTACTIVECAPTION +#define COLOR_GRADIENTACTIVECAPTION 27 +#endif +#ifndef COLOR_GRADIENTINACTIVECAPTION +#define COLOR_GRADIENTINACTIVECAPTION 28 +#endif + if (QApplication::desktopSettingsAware()) { + newPalette.setColor(QPalette::Active, QPalette::Highlight, + colorref2qrgb(GetSysColor(COLOR_ACTIVECAPTION))); + newPalette.setColor(QPalette::Inactive, QPalette::Highlight, + colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTION))); + newPalette.setColor(QPalette::Active, QPalette::HighlightedText, + colorref2qrgb(GetSysColor(COLOR_CAPTIONTEXT))); + newPalette.setColor(QPalette::Inactive, QPalette::HighlightedText, + colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTIONTEXT))); + if (QSysInfo::WindowsVersion != QSysInfo::WV_95 + && QSysInfo::WindowsVersion != QSysInfo::WV_NT) { + colorsInitialized = true; + BOOL hasGradient; + QT_WA({ + SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &hasGradient, 0); + } , { + SystemParametersInfoA(SPI_GETGRADIENTCAPTIONS, 0, &hasGradient, 0); + }); + if (hasGradient) { + newPalette.setColor(QPalette::Active, QPalette::Base, + colorref2qrgb(GetSysColor(COLOR_GRADIENTACTIVECAPTION))); + newPalette.setColor(QPalette::Inactive, QPalette::Base, + colorref2qrgb(GetSysColor(COLOR_GRADIENTINACTIVECAPTION))); + } else { + newPalette.setColor(QPalette::Active, QPalette::Base, + newPalette.color(QPalette::Active, QPalette::Highlight)); + newPalette.setColor(QPalette::Inactive, QPalette::Base, + newPalette.color(QPalette::Inactive, QPalette::Highlight)); + } + } + } +#endif // Q_WS_WIN + if (!colorsInitialized) { + newPalette.setColor(QPalette::Active, QPalette::Highlight, + newPalette.color(QPalette::Active, QPalette::Highlight)); + newPalette.setColor(QPalette::Active, QPalette::Base, + newPalette.color(QPalette::Active, QPalette::Highlight)); + newPalette.setColor(QPalette::Inactive, QPalette::Highlight, + newPalette.color(QPalette::Inactive, QPalette::Dark)); + newPalette.setColor(QPalette::Inactive, QPalette::Base, + newPalette.color(QPalette::Inactive, QPalette::Dark)); + newPalette.setColor(QPalette::Inactive, QPalette::HighlightedText, + newPalette.color(QPalette::Inactive, QPalette::Window)); + } + + return newPalette; +} + +void QMdiSubWindowPrivate::updateActions() +{ + Qt::WindowFlags windowFlags = q_func()->windowFlags(); + // Hide all + for (int i = 0; i < NumWindowStateActions; ++i) + setVisible(WindowStateAction(i), false); + + if (windowFlags & Qt::FramelessWindowHint) + return; + + setVisible(StayOnTopAction, true); + setVisible(MoveAction, moveEnabled); + setVisible(ResizeAction, resizeEnabled); + + // CloseAction + if (windowFlags & Qt::WindowSystemMenuHint) + setVisible(CloseAction, true); + + // RestoreAction + if (windowFlags & (Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint)) + setVisible(RestoreAction, true); + + // MinimizeAction + if (windowFlags & Qt::WindowMinimizeButtonHint) + setVisible(MinimizeAction, true); + + // MaximizeAction + if (windowFlags & Qt::WindowMaximizeButtonHint) + setVisible(MaximizeAction, true); +} + +void QMdiSubWindowPrivate::setFocusWidget() +{ + Q_Q(QMdiSubWindow); + if (!baseWidget) { + q->setFocus(); + return; + } + + // This will give focus to the next child if possible, otherwise + // do nothing, hence it's not possible to tab between windows with + // just hitting tab (unless Qt::TabFocus is removed from the focus policy). + if (focusInReason == Qt::TabFocusReason) { + q->focusNextChild(); + return; + } + + // Same as above, but gives focus to the previous child. + if (focusInReason == Qt::BacktabFocusReason) { + q->focusPreviousChild(); + return; + } + + if (QWidget *focusWidget = baseWidget->focusWidget()) { + if (!focusWidget->hasFocus() && q->isAncestorOf(focusWidget) + && focusWidget->isVisible() && !q->isMinimized() + && focusWidget->focusPolicy() != Qt::NoFocus) { + focusWidget->setFocus(); + } else { + q->setFocus(); + } + return; + } + + QWidget *focusWidget = q->nextInFocusChain(); + while (focusWidget && focusWidget != q && focusWidget->focusPolicy() == Qt::NoFocus) + focusWidget = focusWidget->nextInFocusChain(); + if (focusWidget && q->isAncestorOf(focusWidget)) + focusWidget->setFocus(); + else if (baseWidget->focusPolicy() != Qt::NoFocus) + baseWidget->setFocus(); + else if (!q->hasFocus()) + q->setFocus(); +} + +void QMdiSubWindowPrivate::restoreFocus() +{ + if (!restoreFocusWidget) + return; + if (!restoreFocusWidget->hasFocus() && q_func()->isAncestorOf(restoreFocusWidget) + && restoreFocusWidget->isVisible() + && restoreFocusWidget->focusPolicy() != Qt::NoFocus) { + restoreFocusWidget->setFocus(); + } + restoreFocusWidget = 0; +} + +/*! + \internal + ### Please add QEvent::WindowFlagsChange event +*/ +void QMdiSubWindowPrivate::setWindowFlags(Qt::WindowFlags windowFlags) +{ + Q_Q(QMdiSubWindow); + if (!q->parent()) { + q->setWindowFlags(windowFlags); + return; + } + + Qt::WindowFlags windowType = windowFlags & Qt::WindowType_Mask; + if (windowType == Qt::Dialog || windowFlags & Qt::MSWindowsFixedSizeDialogHint) + windowFlags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint; + + // Set standard flags if none of the customize flags are set + if (!(windowFlags & CustomizeWindowFlags)) + windowFlags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint; + else if (windowFlags & Qt::FramelessWindowHint && windowFlags & Qt::WindowStaysOnTopHint) + windowFlags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint; + else if (windowFlags & Qt::FramelessWindowHint) + windowFlags = Qt::FramelessWindowHint; + + windowFlags &= ~windowType; + windowFlags |= Qt::SubWindow; + +#ifndef QT_NO_ACTION + if (QAction *stayOnTopAction = actions[QMdiSubWindowPrivate::StayOnTopAction]) { + if (windowFlags & Qt::WindowStaysOnTopHint) + stayOnTopAction->setChecked(true); + else + stayOnTopAction->setChecked(false); + } +#endif + +#ifndef QT_NO_SIZEGRIP + if ((windowFlags & Qt::FramelessWindowHint) && sizeGrip) + delete sizeGrip; +#endif + + q->setWindowFlags(windowFlags); + updateGeometryConstraints(); + updateActions(); + QSize currentSize = q->size(); + if (q->isVisible() && (currentSize.width() < internalMinimumSize.width() + || currentSize.height() < internalMinimumSize.height())) { + q->resize(currentSize.expandedTo(internalMinimumSize)); + } +} + +void QMdiSubWindowPrivate::setVisible(WindowStateAction action, bool visible) +{ +#ifndef QT_NO_ACTION + if (actions[action]) + actions[action]->setVisible(visible); +#endif + + Q_Q(QMdiSubWindow); + if (!controlContainer) + controlContainer = new ControlContainer(q); + + if (ControllerWidget *ctrlWidget = qobject_cast<ControllerWidget *> + (controlContainer->controllerWidget())) { + ctrlWidget->setControlVisible(action, visible); + } +} + +#ifndef QT_NO_ACTION +void QMdiSubWindowPrivate::setEnabled(WindowStateAction action, bool enable) +{ + if (actions[action]) + actions[action]->setEnabled(enable); +} + +#ifndef QT_NO_MENU +void QMdiSubWindowPrivate::addToSystemMenu(WindowStateAction action, const QString &text, + const char *slot) +{ + if (!systemMenu) + return; + actions[action] = systemMenu->addAction(text, q_func(), slot); +} +#endif +#endif // QT_NO_ACTION + +/*! + \internal +*/ +QSize QMdiSubWindowPrivate::iconSize() const +{ + Q_Q(const QMdiSubWindow); + if (!q->parent() || q->windowFlags() & Qt::FramelessWindowHint) + return QSize(-1, -1); + return QSize(q->style()->pixelMetric(QStyle::PM_MdiSubWindowMinimizedWidth, 0, q), titleBarHeight()); +} + +#ifndef QT_NO_SIZEGRIP + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setSizeGrip(QSizeGrip *newSizeGrip) +{ + Q_Q(QMdiSubWindow); + if (!newSizeGrip || sizeGrip || q->windowFlags() & Qt::FramelessWindowHint) + return; + + if (q->layout() && q->layout()->indexOf(newSizeGrip) != -1) + return; + newSizeGrip->setFixedSize(newSizeGrip->sizeHint()); + bool putSizeGripInLayout = q->layout() ? true : false; +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(q->style())) + putSizeGripInLayout = false; +#endif + if (putSizeGripInLayout) { + q->layout()->addWidget(newSizeGrip); + q->layout()->setAlignment(newSizeGrip, Qt::AlignBottom | Qt::AlignRight); + } else { + newSizeGrip->setParent(q); + newSizeGrip->move(q->isLeftToRight() ? q->width() - newSizeGrip->width() : 0, + q->height() - newSizeGrip->height()); + sizeGrip = newSizeGrip; + } + newSizeGrip->raise(); + updateGeometryConstraints(); + newSizeGrip->installEventFilter(q); +} + +/*! + \internal +*/ +void QMdiSubWindowPrivate::setSizeGripVisible(bool visible) const +{ + // See if we can find any size grips + QList<QSizeGrip *> sizeGrips = qFindChildren<QSizeGrip *>(q_func()); + foreach (QSizeGrip *grip, sizeGrips) + grip->setVisible(visible); +} + +#endif // QT_NO_SIZEGRIP + +/*! + \internal +*/ +void QMdiSubWindowPrivate::updateInternalWindowTitle() +{ + Q_Q(QMdiSubWindow); + if (q->isWindowModified()) { + windowTitle = q->windowTitle(); + windowTitle.replace(QLatin1String("[*]"), QLatin1String("*")); + } else { + windowTitle = qt_setWindowTitle_helperHelper(q->windowTitle(), q); + } + q->update(0, 0, q->width(), titleBarHeight()); +} + +/*! + Constructs a new QMdiSubWindow widget. The \a parent and \a + flags arguments are passed to QWidget's constructor. + + Instead of using addSubWindow(), it is also simply possible to + use setParent() when you add the subwindow to a QMdiArea. + + Note that only \l{QMdiSubWindow}s can be set as children of + QMdiArea; you cannot, for instance, write: + + \badcode + QMdiArea mdiArea; + QTextEdit editor(&mdiArea); // invalid child widget + \endcode + + \sa QMdiArea::addSubWindow() +*/ +QMdiSubWindow::QMdiSubWindow(QWidget *parent, Qt::WindowFlags flags) + : QWidget(*new QMdiSubWindowPrivate, parent, 0) +{ + Q_D(QMdiSubWindow); +#ifndef QT_NO_MENU + d->createSystemMenu(); + addActions(d->systemMenu->actions()); +#endif + d->setWindowFlags(flags); + setBackgroundRole(QPalette::Window); + setAutoFillBackground(true); + setMouseTracking(true); + setLayout(new QVBoxLayout); + setFocusPolicy(Qt::StrongFocus); + layout()->setMargin(0); + d->updateGeometryConstraints(); + setAttribute(Qt::WA_Resized, false); + d->titleBarPalette = d->desktopPalette(); + d->font = QApplication::font("QWorkspaceTitleBar"); + // We don't want the menu icon by default on mac. +#ifndef Q_WS_MAC + if (windowIcon().isNull()) + d->menuIcon = style()->standardIcon(QStyle::SP_TitleBarMenuButton, 0, this); + else + d->menuIcon = windowIcon(); +#endif + connect(qApp, SIGNAL(focusChanged(QWidget *, QWidget *)), + this, SLOT(_q_processFocusChanged(QWidget *, QWidget *))); +} + +/*! + Destroys the subwindow. + + \sa QMdiArea::removeSubWindow() +*/ +QMdiSubWindow::~QMdiSubWindow() +{ + Q_D(QMdiSubWindow); +#ifndef QT_NO_MENUBAR + d->removeButtonsFromMenuBar(); +#endif + d->setActive(false); +} + +/*! + Sets \a widget as the internal widget of this subwindow. The + internal widget is displayed in the center of the subwindow + beneath the title bar. + + QMdiSubWindow takes temporary ownership of \a widget; you do + not have to delete it. Any existing internal widget will be + removed and reparented to the root window. + + \sa widget() +*/ +void QMdiSubWindow::setWidget(QWidget *widget) +{ + Q_D(QMdiSubWindow); + if (!widget) { + d->removeBaseWidget(); + return; + } + + if (widget == d->baseWidget) { + qWarning("QMdiSubWindow::setWidget: widget is already set"); + return; + } + + bool wasResized = testAttribute(Qt::WA_Resized); + d->removeBaseWidget(); + + if (QLayout *layout = this->layout()) + layout->addWidget(widget); + else + widget->setParent(this); + +#ifndef QT_NO_SIZEGRIP + QSizeGrip *sizeGrip = qFindChild<QSizeGrip *>(widget); + if (sizeGrip) + sizeGrip->installEventFilter(this); + if (d->sizeGrip) + d->sizeGrip->raise(); +#endif + + d->baseWidget = widget; + d->baseWidget->installEventFilter(this); + + d->ignoreWindowTitleChange = true; + bool isWindowModified = this->isWindowModified(); + if (windowTitle().isEmpty()) { + d->updateWindowTitle(true); + isWindowModified = d->baseWidget->isWindowModified(); + } + if (!this->isWindowModified() && isWindowModified + && windowTitle().contains(QLatin1String("[*]"))) { + setWindowModified(isWindowModified); + } + d->lastChildWindowTitle = d->baseWidget->windowTitle(); + d->ignoreWindowTitleChange = false; + + if (windowIcon().isNull() && !d->baseWidget->windowIcon().isNull()) + setWindowIcon(d->baseWidget->windowIcon()); + + d->updateGeometryConstraints(); + if (!wasResized && testAttribute(Qt::WA_Resized)) + setAttribute(Qt::WA_Resized, false); +} + +/*! + Returns the current internal widget. + + \sa setWidget() +*/ +QWidget *QMdiSubWindow::widget() const +{ + return d_func()->baseWidget; +} + + +/*! + \internal +*/ +QWidget *QMdiSubWindow::maximizedButtonsWidget() const +{ + Q_D(const QMdiSubWindow); + if (isVisible() && d->controlContainer && isMaximized() && !d->drawTitleBarWhenMaximized() + && !isChildOfTabbedQMdiArea(this)) { + return d->controlContainer->controllerWidget(); + } + return 0; +} + +/*! + \internal +*/ +QWidget *QMdiSubWindow::maximizedSystemMenuIconWidget() const +{ + Q_D(const QMdiSubWindow); + if (isVisible() && d->controlContainer && isMaximized() && !d->drawTitleBarWhenMaximized() + && !isChildOfTabbedQMdiArea(this)) { + return d->controlContainer->systemMenuLabel(); + } + return 0; +} + +/*! + Returns true if this window is shaded; otherwise returns false. + + A window is shaded if it is collapsed so that only the title bar is + visible. +*/ +bool QMdiSubWindow::isShaded() const +{ + return d_func()->isShadeMode; +} + +/*! + If \a on is true, \a option is enabled on the subwindow; otherwise it is + disabled. See SubWindowOption for the effect of each option. + + \sa SubWindowOption, testOption() +*/ +void QMdiSubWindow::setOption(SubWindowOption option, bool on) +{ + Q_D(QMdiSubWindow); + if (on && !(d->options & option)) + d->options |= option; + else if (!on && (d->options & option)) + d->options &= ~option; + +#ifndef QT_NO_RUBBERBAND + if ((option & (RubberBandResize | RubberBandMove)) && !on && d->isInRubberBandMode) + d->leaveRubberBandMode(); +#endif +} + +/*! + Returns true if \a option is enabled; otherwise returns false. + + \sa SubWindowOption, setOption() +*/ +bool QMdiSubWindow::testOption(SubWindowOption option) const +{ + return d_func()->options & option; +} + +/*! + \property QMdiSubWindow::keyboardSingleStep + \brief sets how far a widget should move or resize when using the + keyboard arrow keys. + + When in keyboard-interactive mode, you can use the arrow and page keys to + either move or resize the window. This property controls the arrow keys. + The common way to enter keyboard interactive mode is to enter the + subwindow menu, and select either "resize" or "move". + + The default keyboard single step value is 5 pixels. + + \sa keyboardPageStep +*/ +int QMdiSubWindow::keyboardSingleStep() const +{ + return d_func()->keyboardSingleStep; +} + +void QMdiSubWindow::setKeyboardSingleStep(int step) +{ + // Haven't done any boundary check here since negative step only + // means inverted behavior, which is OK if the user want it. + // A step equal to zero means "do nothing". + d_func()->keyboardSingleStep = step; +} + +/*! + \property QMdiSubWindow::keyboardPageStep + \brief sets how far a widget should move or resize when using the + keyboard page keys. + + When in keyboard-interactive mode, you can use the arrow and page keys to + either move or resize the window. This property controls the page + keys. The common way to enter keyboard interactive mode is to enter the + subwindow menu, and select either "resize" or "move". + + The default keyboard page step value is 20 pixels. + + \sa keyboardSingleStep +*/ +int QMdiSubWindow::keyboardPageStep() const +{ + return d_func()->keyboardPageStep; +} + +void QMdiSubWindow::setKeyboardPageStep(int step) +{ + // Haven't done any boundary check here since negative step only + // means inverted behavior, which is OK if the user want it. + // A step equal to zero means "do nothing". + d_func()->keyboardPageStep = step; +} + +#ifndef QT_NO_MENU +/*! + Sets \a systemMenu as the current system menu for this subwindow. + + By default, each QMdiSubWindow has a standard system menu. + + QActions for the system menu created by QMdiSubWindow will + automatically be updated depending on the current window state; + e.g., the minimize action will be disabled after the window is + minimized. + + QActions added by the user are not updated by QMdiSubWindow. + + QMdiSubWindow takes ownership of \a systemMenu; you do not have to + delete it. Any existing menus will be deleted. + + \sa systemMenu(), showSystemMenu() +*/ +void QMdiSubWindow::setSystemMenu(QMenu *systemMenu) +{ + Q_D(QMdiSubWindow); + if (systemMenu && systemMenu == d->systemMenu) { + qWarning("QMdiSubWindow::setSystemMenu: system menu is already set"); + return; + } + + if (d->systemMenu) { + delete d->systemMenu; + d->systemMenu = 0; + } + + if (!systemMenu) + return; + + if (systemMenu->parent() != this) + systemMenu->setParent(this); + d->systemMenu = systemMenu; +} + +/*! + Returns a pointer to the current system menu, or zero if no system + menu is set. QMdiSubWindow provides a default system menu, but you can + also set the menu with setSystemMenu(). + + \sa setSystemMenu(), showSystemMenu() +*/ +QMenu *QMdiSubWindow::systemMenu() const +{ + return d_func()->systemMenu; +} + +/*! + Shows the system menu below the system menu icon in the title bar. + + \sa setSystemMenu(), systemMenu() +*/ +void QMdiSubWindow::showSystemMenu() +{ + Q_D(QMdiSubWindow); + if (!d->systemMenu) + return; + + QPoint globalPopupPos; + if (QWidget *icon = maximizedSystemMenuIconWidget()) { + if (isLeftToRight()) + globalPopupPos = icon->mapToGlobal(QPoint(0, icon->y() + icon->height())); + else + globalPopupPos = icon->mapToGlobal(QPoint(icon->width(), icon->y() + icon->height())); + } else { + if (isLeftToRight()) + globalPopupPos = mapToGlobal(contentsRect().topLeft()); + else // + QPoint(1, 0) because topRight() == QPoint(left() + width() -1, top()) + globalPopupPos = mapToGlobal(contentsRect().topRight()) + QPoint(1, 0); + } + + // Adjust x() with -menuwidth in reverse mode. + if (isRightToLeft()) + globalPopupPos -= QPoint(d->systemMenu->sizeHint().width(), 0); + d->systemMenu->installEventFilter(this); + d->systemMenu->popup(globalPopupPos); +} +#endif // QT_NO_MENU + +/*! + \since 4.4 + + Returns the area containing this sub-window, or 0 if there is none. + + \sa QMdiArea::addSubWindow() +*/ +QMdiArea *QMdiSubWindow::mdiArea() const +{ + QWidget *parent = parentWidget(); + while (parent) { + if (QMdiArea *area = qobject_cast<QMdiArea *>(parent)) { + if (area->viewport() == parentWidget()) + return area; + } + parent = parent->parentWidget(); + } + return 0; +} + +/*! + Calling this function makes the subwindow enter the shaded mode. + When the subwindow is shaded, only the title bar is visible. + + Although shading is not supported by all styles, this function will + still show the subwindow as shaded, regardless of whether support + for shading is available. However, when used with styles without + shading support, the user will be unable to return from shaded mode + through the user interface (e.g., through a shade button in the title + bar). + + \sa isShaded() +*/ +void QMdiSubWindow::showShaded() +{ + if (!parent()) + return; + + Q_D(QMdiSubWindow); + // setMinimizeMode uses this function. + if (!d->isShadeRequestFromMinimizeMode && isShaded()) + return; + + d->isMaximizeMode = false; + + QWidget *currentFocusWidget = QApplication::focusWidget(); + if (!d->restoreFocusWidget && isAncestorOf(currentFocusWidget)) + d->restoreFocusWidget = currentFocusWidget; + + if (!d->isShadeRequestFromMinimizeMode) { + d->isShadeMode = true; + d->ensureWindowState(Qt::WindowMinimized); + } + +#ifndef QT_NO_MENUBAR + d->removeButtonsFromMenuBar(); +#endif + + // showMinimized() will reset Qt::WindowActive, which makes sense + // for top level widgets, but in MDI it makes sense to have an + // active window which is minimized. + if (hasFocus() || isAncestorOf(currentFocusWidget)) + d->ensureWindowState(Qt::WindowActive); + +#ifndef QT_NO_SIZEGRIP + d->setSizeGripVisible(false); +#endif + + if (!d->restoreSize.isValid() || d->isShadeMode) { + d->oldGeometry = geometry(); + d->restoreSize.setWidth(d->oldGeometry.width()); + d->restoreSize.setHeight(d->oldGeometry.height()); + } + + // Hide the window before we change the geometry to avoid multiple resize + // events and wrong window state. + const bool wasVisible = isVisible(); + if (wasVisible) + setVisible(false); + + d->updateGeometryConstraints(); + // Update minimum size to internalMinimumSize if set by user. + if (!minimumSize().isNull()) { + d->userMinimumSize = minimumSize(); + setMinimumSize(d->internalMinimumSize); + } + resize(d->internalMinimumSize); + + // Hide the internal widget if not already hidden by the user. + if (d->baseWidget && !d->baseWidget->isHidden()) { + d->baseWidget->hide(); + d->isWidgetHiddenByUs = true; + } + + if (wasVisible) + setVisible(true); + + d->setFocusWidget(); + d->resizeEnabled = false; + d->moveEnabled = true; + d->updateDirtyRegions(); + d->updateMask(); + +#ifndef QT_NO_ACTION + d->setEnabled(QMdiSubWindowPrivate::MinimizeAction, false); + d->setEnabled(QMdiSubWindowPrivate::ResizeAction, d->resizeEnabled); + d->setEnabled(QMdiSubWindowPrivate::MaximizeAction, true); + d->setEnabled(QMdiSubWindowPrivate::RestoreAction, true); + d->setEnabled(QMdiSubWindowPrivate::MoveAction, d->moveEnabled); +#endif +} + +/*! + \reimp +*/ +bool QMdiSubWindow::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QMdiSubWindow); + if (!object) + return QWidget::eventFilter(object, event); + +#ifndef QT_NO_MENU + // System menu events. + if (d->systemMenu && d->systemMenu == object) { + if (event->type() == QEvent::MouseButtonDblClick) { + close(); + } else if (event->type() == QEvent::MouseMove) { + QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); + d->hoveredSubControl = d->getSubControl(mapFromGlobal(mouseEvent->globalPos())); + } else if (event->type() == QEvent::Hide) { + d->systemMenu->removeEventFilter(this); + d->activeSubControl = QStyle::SC_None; + update(QRegion(0, 0, width(), d->titleBarHeight())); + } + return QWidget::eventFilter(object, event); + } +#endif + +#ifndef QT_NO_SIZEGRIP + if (object != d->baseWidget && parent() && qobject_cast<QSizeGrip *>(object)) { + if (event->type() != QEvent::MouseButtonPress || !testOption(QMdiSubWindow::RubberBandResize)) + return QWidget::eventFilter(object, event); + const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); + d->mousePressPosition = parentWidget()->mapFromGlobal(mouseEvent->globalPos()); + d->oldGeometry = geometry(); + d->currentOperation = isLeftToRight() ? QMdiSubWindowPrivate::BottomRightResize + : QMdiSubWindowPrivate::BottomLeftResize; +#ifndef QT_NO_RUBBERBAND + d->enterRubberBandMode(); +#endif + return true; + } +#endif + + if (object != d->baseWidget && event->type() != QEvent::WindowTitleChange) + return QWidget::eventFilter(object, event); + + switch (event->type()) { + case QEvent::Show: + d->setActive(true); + break; + case QEvent::ShowToParent: + if (!d->isWidgetHiddenByUs) + show(); + break; + case QEvent::WindowStateChange: { + QWindowStateChangeEvent *changeEvent = static_cast<QWindowStateChangeEvent*>(event); + if (changeEvent->isOverride()) + break; + Qt::WindowStates oldState = changeEvent->oldState(); + Qt::WindowStates newState = d->baseWidget->windowState(); + if (!(oldState & Qt::WindowMinimized) && (newState & Qt::WindowMinimized)) + showMinimized(); + else if (!(oldState & Qt::WindowMaximized) && (newState & Qt::WindowMaximized)) + showMaximized(); + else if (!(newState & (Qt::WindowMaximized | Qt::WindowMinimized))) + showNormal(); + break; + } + case QEvent::Enter: + d->currentOperation = QMdiSubWindowPrivate::None; + d->updateCursor(); + break; + case QEvent::LayoutRequest: + d->updateGeometryConstraints(); + break; + case QEvent::WindowTitleChange: + if (d->ignoreWindowTitleChange) + break; + if (object == d->baseWidget) { + d->updateWindowTitle(true); + d->lastChildWindowTitle = d->baseWidget->windowTitle(); +#ifndef QT_NO_MENUBAR + } else if (maximizedButtonsWidget() && d->controlContainer->menuBar() && d->controlContainer->menuBar() + ->cornerWidget(Qt::TopRightCorner) == maximizedButtonsWidget()) { + d->originalTitle = QString::null; + if (d->baseWidget && d->baseWidget->windowTitle() == windowTitle()) + d->updateWindowTitle(true); + else + d->updateWindowTitle(false); +#endif + } + break; + case QEvent::ModifiedChange: { + if (object != d->baseWidget) + break; + bool windowModified = d->baseWidget->isWindowModified(); + if (!windowModified && d->baseWidget->windowTitle() != windowTitle()) + break; + if (windowTitle().contains(QLatin1String("[*]"))) + setWindowModified(windowModified); + break; + } + default: + break; + } + return QWidget::eventFilter(object, event); +} + +/*! + \reimp +*/ +bool QMdiSubWindow::event(QEvent *event) +{ + Q_D(QMdiSubWindow); + switch (event->type()) { + case QEvent::StyleChange: { + bool wasShaded = isShaded(); + bool wasMinimized = isMinimized(); + bool wasMaximized = isMaximized(); + ensurePolished(); + setContentsMargins(0, 0, 0, 0); + if (wasMinimized || wasMaximized || wasShaded) + showNormal(); + d->updateGeometryConstraints(); + resize(d->internalMinimumSize.expandedTo(size())); + d->updateMask(); + d->updateDirtyRegions(); + if (wasShaded) + showShaded(); + else if (wasMinimized) + showMinimized(); + else if (wasMaximized) + showMaximized(); + break; + } + case QEvent::ParentAboutToChange: + d->setActive(false); + break; + case QEvent::ParentChange: { + bool wasResized = testAttribute(Qt::WA_Resized); +#ifndef QT_NO_MENUBAR + d->removeButtonsFromMenuBar(); +#endif + d->currentOperation = QMdiSubWindowPrivate::None; + d->activeSubControl = QStyle::SC_None; + d->hoveredSubControl = QStyle::SC_None; +#ifndef QT_NO_RUBBERBAND + if (d->isInRubberBandMode) + d->leaveRubberBandMode(); +#endif + d->isShadeMode = false; + d->isMaximizeMode = false; + d->isWidgetHiddenByUs = false; + if (!parent()) { +#if !defined(QT_NO_SIZEGRIP) && defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(style())) + delete d->sizeGrip; +#endif + setOption(RubberBandResize, false); + setOption(RubberBandMove, false); + } else { + d->setWindowFlags(windowFlags()); + } + setContentsMargins(0, 0, 0, 0); + d->updateGeometryConstraints(); + d->updateCursor(); + d->updateMask(); + d->updateDirtyRegions(); + d->updateActions(); + if (!wasResized && testAttribute(Qt::WA_Resized)) + setAttribute(Qt::WA_Resized, false); + break; + } + case QEvent::WindowActivate: + if (d->ignoreNextActivationEvent) { + d->ignoreNextActivationEvent = false; + break; + } + d->isExplicitlyDeactivated = false; + d->setActive(true); + break; + case QEvent::WindowDeactivate: + if (d->ignoreNextActivationEvent) { + d->ignoreNextActivationEvent = false; + break; + } + d->isExplicitlyDeactivated = true; + d->setActive(false); + break; + case QEvent::WindowTitleChange: + if (!d->ignoreWindowTitleChange) + d->updateWindowTitle(false); + d->updateInternalWindowTitle(); + break; + case QEvent::ModifiedChange: + if (!windowTitle().contains(QLatin1String("[*]"))) + break; +#ifndef QT_NO_MENUBAR + if (maximizedButtonsWidget() && d->controlContainer->menuBar() && d->controlContainer->menuBar() + ->cornerWidget(Qt::TopRightCorner) == maximizedButtonsWidget()) { + window()->setWindowModified(isWindowModified()); + } +#endif // QT_NO_MENUBAR + d->updateInternalWindowTitle(); + break; + case QEvent::LayoutDirectionChange: + d->updateDirtyRegions(); + break; + case QEvent::LayoutRequest: + d->updateGeometryConstraints(); + break; + case QEvent::WindowIconChange: + d->menuIcon = windowIcon(); + if (d->menuIcon.isNull()) + d->menuIcon = style()->standardIcon(QStyle::SP_TitleBarMenuButton, 0, this); + if (d->controlContainer) + d->controlContainer->updateWindowIcon(d->menuIcon); + if (!maximizedSystemMenuIconWidget()) + update(0, 0, width(), d->titleBarHeight()); + break; + case QEvent::PaletteChange: + d->titleBarPalette = d->desktopPalette(); + break; + case QEvent::FontChange: + d->font = font(); + break; +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: + showToolTip(static_cast<QHelpEvent *>(event), this, d->titleBarOptions(), + QStyle::CC_TitleBar, d->hoveredSubControl); + break; +#endif + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +void QMdiSubWindow::showEvent(QShowEvent *showEvent) +{ + Q_D(QMdiSubWindow); + if (!parent()) { + QWidget::showEvent(showEvent); + return; + } + +#if !defined(QT_NO_SIZEGRIP) && defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(style()) && !d->sizeGrip + && !(windowFlags() & Qt::FramelessWindowHint)) { + d->setSizeGrip(new QSizeGrip(0)); + Q_ASSERT(d->sizeGrip); + if (isMinimized()) + d->setSizeGripVisible(false); + else + d->setSizeGripVisible(true); + resize(size().expandedTo(d->internalMinimumSize)); + } +#endif + + d->updateDirtyRegions(); + // Show buttons in the menu bar if they're already not there. + // We want to do this when QMdiSubWindow becomes visible after being hidden. +#ifndef QT_NO_MENUBAR + if (d->controlContainer) { + if (QMenuBar *menuBar = d->menuBar()) { + if (menuBar->cornerWidget(Qt::TopRightCorner) != maximizedButtonsWidget()) + d->showButtonsInMenuBar(menuBar); + } + } +#endif + d->setActive(true); +} + +/*! + \reimp +*/ +void QMdiSubWindow::hideEvent(QHideEvent * /*hideEvent*/) +{ +#ifndef QT_NO_MENUBAR + d_func()->removeButtonsFromMenuBar(); +#endif +} + +/*! + \reimp +*/ +void QMdiSubWindow::changeEvent(QEvent *changeEvent) +{ + if (!parent()) { + QWidget::changeEvent(changeEvent); + return; + } + + if (changeEvent->type() != QEvent::WindowStateChange) { + QWidget::changeEvent(changeEvent); + return; + } + + QWindowStateChangeEvent *event = static_cast<QWindowStateChangeEvent *>(changeEvent); + if (event->isOverride()) { + event->ignore(); + return; + } + + Qt::WindowStates oldState = event->oldState(); + Qt::WindowStates newState = windowState(); + if (oldState == newState) { + changeEvent->ignore(); + return; + } + + // QWidget ensures that the widget is visible _after_ setWindowState(), + // but we need to ensure that the widget is visible _before_ + // setWindowState() returns. + Q_D(QMdiSubWindow); + if (!isVisible()) { + d->ensureWindowState(Qt::WindowNoState); + setVisible(true); + } + + if (!d->oldGeometry.isValid()) + d->oldGeometry = geometry(); + + if ((oldState & Qt::WindowActive) && (newState & Qt::WindowActive)) + d->currentOperation = QMdiSubWindowPrivate::None; + + if (!(oldState & Qt::WindowMinimized) && (newState & Qt::WindowMinimized)) + d->setMinimizeMode(); + else if (!(oldState & Qt::WindowMaximized) && (newState & Qt::WindowMaximized)) + d->setMaximizeMode(); + else if (!(newState & (Qt::WindowMaximized | Qt::WindowMinimized))) + d->setNormalMode(); + + if (d->isActive) + d->ensureWindowState(Qt::WindowActive); + emit windowStateChanged(oldState, windowState()); +} + +/*! + \reimp +*/ +void QMdiSubWindow::closeEvent(QCloseEvent *closeEvent) +{ + Q_D(QMdiSubWindow); + bool acceptClose = true; + if (d->baseWidget) + acceptClose = d->baseWidget->close(); + if (!acceptClose) { + closeEvent->ignore(); + return; + } +#ifndef QT_NO_MENUBAR + d->removeButtonsFromMenuBar(); +#endif + d->setActive(false); + if (parentWidget() && testAttribute(Qt::WA_DeleteOnClose)) { + QChildEvent childRemoved(QEvent::ChildRemoved, this); + QApplication::sendEvent(parentWidget(), &childRemoved); + } + closeEvent->accept(); +} + +/*! + \reimp +*/ +void QMdiSubWindow::leaveEvent(QEvent * /*leaveEvent*/) +{ + Q_D(QMdiSubWindow); + if (d->hoveredSubControl != QStyle::SC_None) { + d->hoveredSubControl = QStyle::SC_None; + update(QRegion(0, 0, width(), d->titleBarHeight())); + } +} + +/*! + \reimp +*/ +void QMdiSubWindow::resizeEvent(QResizeEvent *resizeEvent) +{ + Q_D(QMdiSubWindow); +#ifndef QT_NO_SIZEGRIP + if (d->sizeGrip) { + d->sizeGrip->move(isLeftToRight() ? width() - d->sizeGrip->width() : 0, + height() - d->sizeGrip->height()); + } +#endif + + if (!parent()) { + QWidget::resizeEvent(resizeEvent); + return; + } + + if (d->isMaximizeMode) + d->ensureWindowState(Qt::WindowMaximized); + + d->updateMask(); + if (!isVisible()) + return; + + if (d->resizeTimerId <= 0) + d->cachedStyleOptions = d->titleBarOptions(); + else + killTimer(d->resizeTimerId); + d->resizeTimerId = startTimer(200); +} + +/*! + \reimp +*/ +void QMdiSubWindow::timerEvent(QTimerEvent *timerEvent) +{ + Q_D(QMdiSubWindow); + if (timerEvent->timerId() == d->resizeTimerId) { + killTimer(d->resizeTimerId); + d->resizeTimerId = -1; + d->updateDirtyRegions(); + } +} + +/*! + \reimp +*/ +void QMdiSubWindow::moveEvent(QMoveEvent *moveEvent) +{ + if (!parent()) { + QWidget::moveEvent(moveEvent); + return; + } + + Q_D(QMdiSubWindow); + if (d->isMaximizeMode) + d->ensureWindowState(Qt::WindowMaximized); +} + +/*! + \reimp +*/ +void QMdiSubWindow::paintEvent(QPaintEvent *paintEvent) +{ + if (!parent() || (windowFlags() & Qt::FramelessWindowHint)) { + QWidget::paintEvent(paintEvent); + return; + } + + Q_D(QMdiSubWindow); + if (isMaximized() && !d->drawTitleBarWhenMaximized()) + return; + + if (d->resizeTimerId != -1) { + // Only update the style option rect and the window title. + int border = d->hasBorder(d->cachedStyleOptions) ? 4 : 0; + int titleBarHeight = d->titleBarHeight(d->cachedStyleOptions); + titleBarHeight -= isMinimized() ? 2 * border : border; + d->cachedStyleOptions.rect = QRect(border, border, width() - 2 * border, titleBarHeight); + if (!d->windowTitle.isEmpty()) { + int width = style()->subControlRect(QStyle::CC_TitleBar, &d->cachedStyleOptions, + QStyle::SC_TitleBarLabel, this).width(); + d->cachedStyleOptions.text = d->cachedStyleOptions.fontMetrics + .elidedText(d->windowTitle, Qt::ElideRight, width); + } + } else { + // Force full update. + d->cachedStyleOptions = d->titleBarOptions(); + } + + QStylePainter painter(this); + if (!d->windowTitle.isEmpty()) + painter.setFont(d->font); + painter.drawComplexControl(QStyle::CC_TitleBar, d->cachedStyleOptions); + + if (isMinimized() && !d->hasBorder(d->cachedStyleOptions)) + return; + + QStyleOptionFrame frameOptions; + frameOptions.initFrom(this); + frameOptions.lineWidth = style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, this); + if (d->isActive) + frameOptions.state |= QStyle::State_Active; + else + frameOptions.state &= ~QStyle::State_Active; + + // ### Ensure that we do not require setting the cliprect for 4.4 + if (!isMinimized() && !d->hasBorder(d->cachedStyleOptions)) + painter.setClipRect(rect().adjusted(0, d->titleBarHeight(d->cachedStyleOptions), 0, 0)); + if (!isMinimized() || d->hasBorder(d->cachedStyleOptions)) + painter.drawPrimitive(QStyle::PE_FrameWindow, frameOptions); +} + +/*! + \reimp +*/ +void QMdiSubWindow::mousePressEvent(QMouseEvent *mouseEvent) +{ + if (!parent()) { + QWidget::mousePressEvent(mouseEvent); + return; + } + + Q_D(QMdiSubWindow); + if (d->isInInteractiveMode) + d->leaveInteractiveMode(); +#ifndef QT_NO_RUBBERBAND + if (d->isInRubberBandMode) + d->leaveRubberBandMode(); +#endif + + if (mouseEvent->button() != Qt::LeftButton) { + mouseEvent->ignore(); + return; + } + + if (d->currentOperation != QMdiSubWindowPrivate::None) { + d->updateCursor(); + d->mousePressPosition = mapToParent(mouseEvent->pos()); + if (d->resizeEnabled || d->moveEnabled) + d->oldGeometry = geometry(); +#ifndef QT_NO_RUBBERBAND + if ((testOption(QMdiSubWindow::RubberBandResize) && d->isResizeOperation()) + || (testOption(QMdiSubWindow::RubberBandMove) && d->isMoveOperation())) { + d->enterRubberBandMode(); + } +#endif + return; + } + + d->activeSubControl = d->hoveredSubControl; +#ifndef QT_NO_MENU + if (d->activeSubControl == QStyle::SC_TitleBarSysMenu) + showSystemMenu(); + else +#endif + update(QRegion(0, 0, width(), d->titleBarHeight())); +} + +/*! + \reimp +*/ +void QMdiSubWindow::mouseDoubleClickEvent(QMouseEvent *mouseEvent) +{ + if (!parent()) { + QWidget::mouseDoubleClickEvent(mouseEvent); + return; + } + + if (mouseEvent->button() != Qt::LeftButton) { + mouseEvent->ignore(); + return; + } + + Q_D(QMdiSubWindow); + if (!d->isMoveOperation()) { +#ifndef QT_NO_MENU + if (d->hoveredSubControl == QStyle::SC_TitleBarSysMenu) + close(); +#endif + return; + } + + Qt::WindowFlags flags = windowFlags(); + if (isMinimized()) { + if ((isShaded() && (flags & Qt::WindowShadeButtonHint)) + || (flags & Qt::WindowMinimizeButtonHint)) { + showNormal(); + } + return; + } + + if (isMaximized()) { + if (flags & Qt::WindowMaximizeButtonHint) + showNormal(); + return; + } + + if (flags & Qt::WindowShadeButtonHint) + showShaded(); + else if (flags & Qt::WindowMaximizeButtonHint) + showMaximized(); +} + +/*! + \reimp +*/ +void QMdiSubWindow::mouseReleaseEvent(QMouseEvent *mouseEvent) +{ + if (!parent()) { + QWidget::mouseReleaseEvent(mouseEvent); + return; + } + + if (mouseEvent->button() != Qt::LeftButton) { + mouseEvent->ignore(); + return; + } + + Q_D(QMdiSubWindow); + if (d->currentOperation != QMdiSubWindowPrivate::None) { +#ifndef QT_NO_RUBBERBAND + if (d->isInRubberBandMode && !d->isInInteractiveMode) + d->leaveRubberBandMode(); +#endif + if (d->resizeEnabled || d->moveEnabled) + d->oldGeometry = geometry(); + } + + d->currentOperation = d->getOperation(mouseEvent->pos()); + d->updateCursor(); + + d->hoveredSubControl = d->getSubControl(mouseEvent->pos()); + if (d->activeSubControl != QStyle::SC_None + && d->activeSubControl == d->hoveredSubControl) { + d->processClickedSubControl(); + } + d->activeSubControl = QStyle::SC_None; + update(QRegion(0, 0, width(), d->titleBarHeight())); +} + +/*! + \reimp +*/ +void QMdiSubWindow::mouseMoveEvent(QMouseEvent *mouseEvent) +{ + if (!parent()) { + QWidget::mouseMoveEvent(mouseEvent); + return; + } + + Q_D(QMdiSubWindow); + // No update needed if we're in a move/resize operation. + if (!d->isMoveOperation() && !d->isResizeOperation()) { + // Find previous and current hover region. + const QStyleOptionTitleBar options = d->titleBarOptions(); + QStyle::SubControl oldHover = d->hoveredSubControl; + d->hoveredSubControl = d->getSubControl(mouseEvent->pos()); + QRegion hoverRegion; + if (isHoverControl(oldHover) && oldHover != d->hoveredSubControl) + hoverRegion += style()->subControlRect(QStyle::CC_TitleBar, &options, oldHover, this); + if (isHoverControl(d->hoveredSubControl) && d->hoveredSubControl != oldHover) { + hoverRegion += style()->subControlRect(QStyle::CC_TitleBar, &options, + d->hoveredSubControl, this); + } +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast<QMacStyle *>(style()) && !hoverRegion.isEmpty()) + hoverRegion += QRegion(0, 0, width(), d->titleBarHeight(options)); +#endif + if (!hoverRegion.isEmpty()) + update(hoverRegion); + } + + if ((mouseEvent->buttons() & Qt::LeftButton) || d->isInInteractiveMode) { + if ((d->isResizeOperation() && d->resizeEnabled) || (d->isMoveOperation() && d->moveEnabled)) + d->setNewGeometry(mapToParent(mouseEvent->pos())); + return; + } + + // Do not resize/move if not allowed. + d->currentOperation = d->getOperation(mouseEvent->pos()); + if ((d->isResizeOperation() && !d->resizeEnabled) || (d->isMoveOperation() && !d->moveEnabled)) + d->currentOperation = QMdiSubWindowPrivate::None; + d->updateCursor(); +} + +/*! + \reimp +*/ +void QMdiSubWindow::keyPressEvent(QKeyEvent *keyEvent) +{ + Q_D(QMdiSubWindow); + if (!d->isInInteractiveMode || !parent()) { + keyEvent->ignore(); + return; + } + + QPoint delta; + switch (keyEvent->key()) { + case Qt::Key_Right: + if (keyEvent->modifiers() & Qt::ShiftModifier) + delta = QPoint(d->keyboardPageStep, 0); + else + delta = QPoint(d->keyboardSingleStep, 0); + break; + case Qt::Key_Up: + if (keyEvent->modifiers() & Qt::ShiftModifier) + delta = QPoint(0, -d->keyboardPageStep); + else + delta = QPoint(0, -d->keyboardSingleStep); + break; + case Qt::Key_Left: + if (keyEvent->modifiers() & Qt::ShiftModifier) + delta = QPoint(-d->keyboardPageStep, 0); + else + delta = QPoint(-d->keyboardSingleStep, 0); + break; + case Qt::Key_Down: + if (keyEvent->modifiers() & Qt::ShiftModifier) + delta = QPoint(0, d->keyboardPageStep); + else + delta = QPoint(0, d->keyboardSingleStep); + break; + case Qt::Key_Escape: + case Qt::Key_Return: + case Qt::Key_Enter: + d->leaveInteractiveMode(); + return; + default: + keyEvent->ignore(); + return; + } + +#ifndef QT_NO_CURSOR + QPoint newPosition = parentWidget()->mapFromGlobal(cursor().pos() + delta); + QRect oldGeometry = +#ifndef QT_NO_RUBBERBAND + d->isInRubberBandMode ? d->rubberBand->geometry() : +#endif + geometry(); + d->setNewGeometry(newPosition); + QRect currentGeometry = +#ifndef QT_NO_RUBBERBAND + d->isInRubberBandMode ? d->rubberBand->geometry() : +#endif + geometry(); + if (currentGeometry == oldGeometry) + return; + + // Update cursor position + + QPoint actualDelta; + if (d->isMoveOperation()) { + actualDelta = QPoint(currentGeometry.x() - oldGeometry.x(), + currentGeometry.y() - oldGeometry.y()); + } else { + int dx = isLeftToRight() ? currentGeometry.width() - oldGeometry.width() + : currentGeometry.x() - oldGeometry.x(); + actualDelta = QPoint(dx, currentGeometry.height() - oldGeometry.height()); + } + + // Adjust in case we weren't able to move as long as wanted. + if (actualDelta != delta) + newPosition += (actualDelta - delta); + cursor().setPos(parentWidget()->mapToGlobal(newPosition)); +#endif +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QMdiSubWindow::contextMenuEvent(QContextMenuEvent *contextMenuEvent) +{ + Q_D(QMdiSubWindow); + if (!d->systemMenu) { + contextMenuEvent->ignore(); + return; + } + + if (d->hoveredSubControl == QStyle::SC_TitleBarSysMenu + || d->getRegion(QMdiSubWindowPrivate::Move).contains(contextMenuEvent->pos())) { + d->systemMenu->exec(contextMenuEvent->globalPos()); + } else { + contextMenuEvent->ignore(); + } +} +#endif // QT_NO_CONTEXTMENU + +/*! + \reimp +*/ +void QMdiSubWindow::focusInEvent(QFocusEvent *focusInEvent) +{ + d_func()->focusInReason = focusInEvent->reason(); +} + +/*! + \reimp +*/ +void QMdiSubWindow::focusOutEvent(QFocusEvent * /*focusOutEvent*/) +{ + // To avoid update() in QWidget::focusOutEvent. +} + +/*! + \reimp +*/ +void QMdiSubWindow::childEvent(QChildEvent *childEvent) +{ + if (childEvent->type() != QEvent::ChildPolished) + return; +#ifndef QT_NO_SIZEGRIP + if (QSizeGrip *sizeGrip = qobject_cast<QSizeGrip *>(childEvent->child())) + d_func()->setSizeGrip(sizeGrip); +#endif +} + +/*! + \reimp +*/ +QSize QMdiSubWindow::sizeHint() const +{ + Q_D(const QMdiSubWindow); + int margin, minWidth; + d->sizeParameters(&margin, &minWidth); + QSize size(2 * margin, d->titleBarHeight() + margin); + if (d->baseWidget && d->baseWidget->sizeHint().isValid()) + size += d->baseWidget->sizeHint(); + return size.expandedTo(minimumSizeHint()); +} + +/*! + \reimp +*/ +QSize QMdiSubWindow::minimumSizeHint() const +{ + Q_D(const QMdiSubWindow); + if (isVisible()) + ensurePolished(); + + // Minimized window. + if (parent() && isMinimized() && !isShaded()) + return d->iconSize(); + + // Calculate window decoration. + int margin, minWidth; + d->sizeParameters(&margin, &minWidth); + int decorationHeight = margin + d->titleBarHeight(); + int minHeight = decorationHeight; + + // Shaded window. + if (parent() && isShaded()) + return QSize(qMax(minWidth, width()), d->titleBarHeight()); + + // Content + if (layout()) { + QSize minLayoutSize = layout()->minimumSize(); + if (minLayoutSize.isValid()) { + minWidth = qMax(minWidth, minLayoutSize.width() + 2 * margin); + minHeight += minLayoutSize.height(); + } + } else if (d->baseWidget && d->baseWidget->isVisible()) { + QSize minBaseWidgetSize = d->baseWidget->minimumSizeHint(); + if (minBaseWidgetSize.isValid()) { + minWidth = qMax(minWidth, minBaseWidgetSize.width() + 2 * margin); + minHeight += minBaseWidgetSize.height(); + } + } + +#ifndef QT_NO_SIZEGRIP + // SizeGrip + int sizeGripHeight = 0; + if (d->sizeGrip && d->sizeGrip->isVisibleTo(const_cast<QMdiSubWindow *>(this))) + sizeGripHeight = d->sizeGrip->height(); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + else if (parent() && qobject_cast<QMacStyle *>(style()) && !d->sizeGrip) + sizeGripHeight = style()->pixelMetric(QStyle::PM_SizeGripSize, 0, this); +#endif + minHeight = qMax(minHeight, decorationHeight + sizeGripHeight); +#endif + + return QSize(minWidth, minHeight).expandedTo(QApplication::globalStrut()); +} + +QT_END_NAMESPACE + +#include "moc_qmdisubwindow.cpp" +#include "qmdisubwindow.moc" + +#endif //QT_NO_MDIAREA diff --git a/src/gui/widgets/qmdisubwindow.h b/src/gui/widgets/qmdisubwindow.h new file mode 100644 index 0000000..3dcb1bb --- /dev/null +++ b/src/gui/widgets/qmdisubwindow.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMDISUBWINDOW_H +#define QMDISUBWINDOW_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MDIAREA + +class QMenu; +class QMdiArea; + +namespace QMdi { class ControlContainer; } +class QMdiSubWindowPrivate; +class Q_GUI_EXPORT QMdiSubWindow : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int keyboardSingleStep READ keyboardSingleStep WRITE setKeyboardSingleStep) + Q_PROPERTY(int keyboardPageStep READ keyboardPageStep WRITE setKeyboardPageStep) +public: + enum SubWindowOption { + AllowOutsideAreaHorizontally = 0x1, // internal + AllowOutsideAreaVertically = 0x2, // internal + RubberBandResize = 0x4, + RubberBandMove = 0x8 + }; + Q_DECLARE_FLAGS(SubWindowOptions, SubWindowOption) + + QMdiSubWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QMdiSubWindow(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setWidget(QWidget *widget); + QWidget *widget() const; + + QWidget *maximizedButtonsWidget() const; // internal + QWidget *maximizedSystemMenuIconWidget() const; // internal + + bool isShaded() const; + + void setOption(SubWindowOption option, bool on = true); + bool testOption(SubWindowOption) const; + + void setKeyboardSingleStep(int step); + int keyboardSingleStep() const; + + void setKeyboardPageStep(int step); + int keyboardPageStep() const; + +#ifndef QT_NO_MENU + void setSystemMenu(QMenu *systemMenu); + QMenu *systemMenu() const; +#endif + + QMdiArea *mdiArea() const; + +Q_SIGNALS: + void windowStateChanged(Qt::WindowStates oldState, Qt::WindowStates newState); + void aboutToActivate(); + +public Q_SLOTS: +#ifndef QT_NO_MENU + void showSystemMenu(); +#endif + void showShaded(); + +protected: + bool eventFilter(QObject *object, QEvent *event); + bool event(QEvent *event); + void showEvent(QShowEvent *showEvent); + void hideEvent(QHideEvent *hideEvent); + void changeEvent(QEvent *changeEvent); + void closeEvent(QCloseEvent *closeEvent); + void leaveEvent(QEvent *leaveEvent); + void resizeEvent(QResizeEvent *resizeEvent); + void timerEvent(QTimerEvent *timerEvent); + void moveEvent(QMoveEvent *moveEvent); + void paintEvent(QPaintEvent *paintEvent); + void mousePressEvent(QMouseEvent *mouseEvent); + void mouseDoubleClickEvent(QMouseEvent *mouseEvent); + void mouseReleaseEvent(QMouseEvent *mouseEvent); + void mouseMoveEvent(QMouseEvent *mouseEvent); + void keyPressEvent(QKeyEvent *keyEvent); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *contextMenuEvent); +#endif + void focusInEvent(QFocusEvent *focusInEvent); + void focusOutEvent(QFocusEvent *focusOutEvent); + void childEvent(QChildEvent *childEvent); + +private: + Q_DISABLE_COPY(QMdiSubWindow) + Q_DECLARE_PRIVATE(QMdiSubWindow) + Q_PRIVATE_SLOT(d_func(), void _q_updateStaysOnTopHint()) + Q_PRIVATE_SLOT(d_func(), void _q_enterInteractiveMode()) + Q_PRIVATE_SLOT(d_func(), void _q_processFocusChanged(QWidget *, QWidget *)) + friend class QMdiAreaPrivate; +#ifndef QT_NO_TABBAR + friend class QMdiAreaTabBar; +#endif + friend class QMdi::ControlContainer; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QMdiSubWindow::SubWindowOptions) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_MDIAREA + +#endif // QMDISUBWINDOW_H diff --git a/src/gui/widgets/qmdisubwindow_p.h b/src/gui/widgets/qmdisubwindow_p.h new file mode 100644 index 0000000..2e672d6 --- /dev/null +++ b/src/gui/widgets/qmdisubwindow_p.h @@ -0,0 +1,348 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMDISUBWINDOW_P_H +#define QMDISUBWINDOW_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 "qmdisubwindow.h" + +#ifndef QT_NO_MDIAREA + +#include <QStyle> +#include <QStyleOptionTitleBar> +#include <QMenuBar> +#include <QSizeGrip> +#include <QPointer> +#include <QDebug> +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +class QVBoxLayout; +class QMouseEvent; + +namespace QMdi { +template<typename T> +class ControlElement : public T +{ +public: + ControlElement(QMdiSubWindow *child) : T(child, 0) + { + Q_ASSERT(child); + mdiChild = child; + } + + void *qt_metacast(const char *classname) + { + if (classname && strcmp(classname, "ControlElement") == 0) + return this; + return 0; + } + + QPointer<QMdiSubWindow> mdiChild; +}; + +class ControlContainer : public QObject +{ +public: + ControlContainer(QMdiSubWindow *mdiChild); + ~ControlContainer(); + +#ifndef QT_NO_MENUBAR + void showButtonsInMenuBar(QMenuBar *menuBar); + void removeButtonsFromMenuBar(QMenuBar *menuBar = 0); + QMenuBar *menuBar() const { return m_menuBar; } +#endif + void updateWindowIcon(const QIcon &windowIcon); + QWidget *controllerWidget() const { return m_controllerWidget; } + QWidget *systemMenuLabel() const { return m_menuLabel; } + +private: + QPointer<QWidget> previousLeft; + QPointer<QWidget> previousRight; +#ifndef QT_NO_MENUBAR + QPointer<QMenuBar> m_menuBar; +#endif + QPointer<QWidget> m_controllerWidget; + QPointer<QWidget> m_menuLabel; + QPointer<QMdiSubWindow> mdiChild; +}; +} // namespace QMdi + +class QMdiSubWindowPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QMdiSubWindow) +public: + // Enums and typedefs. + enum Operation { + None, + Move, + TopResize, + BottomResize, + LeftResize, + RightResize, + TopLeftResize, + TopRightResize, + BottomLeftResize, + BottomRightResize + }; + + enum ChangeFlag { + HMove = 0x01, + VMove = 0x02, + HResize = 0x04, + VResize = 0x08, + HResizeReverse = 0x10, + VResizeReverse = 0x20 + }; + + enum WindowStateAction { + RestoreAction, + MoveAction, + ResizeAction, + MinimizeAction, + MaximizeAction, + StayOnTopAction, + CloseAction, + /* Add new states _above_ this line! */ + NumWindowStateActions + }; + + struct OperationInfo { + uint changeFlags; + Qt::CursorShape cursorShape; + QRegion region; + bool hover; + OperationInfo(uint changeFlags, Qt::CursorShape cursorShape, bool hover = true) + : changeFlags(changeFlags), + cursorShape(cursorShape), + hover(hover) + {} + }; + + typedef QMap<Operation, OperationInfo> OperationInfoMap; + + QMdiSubWindowPrivate(); + + // Variables. + QPointer<QWidget> baseWidget; + QPointer<QWidget> restoreFocusWidget; + QPointer<QMdi::ControlContainer> controlContainer; +#ifndef QT_NO_SIZEGRIP + QPointer<QSizeGrip> sizeGrip; +#endif +#ifndef QT_NO_RUBBERBAND + QRubberBand *rubberBand; +#endif + QPoint mousePressPosition; + QRect oldGeometry; + QSize internalMinimumSize; + QSize userMinimumSize; + QSize restoreSize; + bool resizeEnabled; + bool moveEnabled; + bool isInInteractiveMode; +#ifndef QT_NO_RUBBERBAND + bool isInRubberBandMode; +#endif + bool isShadeMode; + bool ignoreWindowTitleChange; + bool ignoreNextActivationEvent; + bool activationEnabled; + bool isShadeRequestFromMinimizeMode; + bool isMaximizeMode; + bool isWidgetHiddenByUs; + bool isActive; + bool isExplicitlyDeactivated; + int keyboardSingleStep; + int keyboardPageStep; + int resizeTimerId; + Operation currentOperation; + QStyle::SubControl hoveredSubControl; + QStyle::SubControl activeSubControl; + Qt::FocusReason focusInReason; + OperationInfoMap operationMap; + QPointer<QMenu> systemMenu; +#ifndef QT_NO_ACTIONS + QPointer<QAction> actions[NumWindowStateActions]; +#endif + QMdiSubWindow::SubWindowOptions options; + QString lastChildWindowTitle; + QPalette titleBarPalette; + QString windowTitle; + QFont font; + QIcon menuIcon; + QStyleOptionTitleBar cachedStyleOptions; + QString originalTitle; + + // Slots. + void _q_updateStaysOnTopHint(); + void _q_enterInteractiveMode(); + void _q_processFocusChanged(QWidget *old, QWidget *now); + + // Functions. + void leaveInteractiveMode(); + void removeBaseWidget(); + void initOperationMap(); +#ifndef QT_NO_MENU + void createSystemMenu(); +#endif + void updateCursor(); + void updateDirtyRegions(); + void updateGeometryConstraints(); + void updateMask(); + void setNewGeometry(const QPoint &pos); + void setMinimizeMode(); + void setNormalMode(); + void setMaximizeMode(); + void setActive(bool activate, bool changeFocus = true); + void processClickedSubControl(); + QRegion getRegion(Operation operation) const; + Operation getOperation(const QPoint &pos) const; + QStyleOptionTitleBar titleBarOptions() const; + void ensureWindowState(Qt::WindowState state); + int titleBarHeight(const QStyleOptionTitleBar &options) const; + void sizeParameters(int *margin, int *minWidth) const; + bool drawTitleBarWhenMaximized() const; +#ifndef QT_NO_MENUBAR + QMenuBar *menuBar() const; + void showButtonsInMenuBar(QMenuBar *menuBar); + void removeButtonsFromMenuBar(); +#endif + void updateWindowTitle(bool requestFromChild); +#ifndef QT_NO_RUBBERBAND + void enterRubberBandMode(); + void leaveRubberBandMode(); +#endif + QPalette desktopPalette() const; + void updateActions(); + void setFocusWidget(); + void restoreFocus(); + void setWindowFlags(Qt::WindowFlags windowFlags); + void setVisible(WindowStateAction, bool visible = true); +#ifndef QT_NO_ACTION + void setEnabled(WindowStateAction, bool enable = true); +#ifndef QT_NO_MENU + void addToSystemMenu(WindowStateAction, const QString &text, const char *slot); +#endif +#endif // QT_NO_ACTION + QSize iconSize() const; +#ifndef QT_NO_SIZEGRIP + void setSizeGrip(QSizeGrip *sizeGrip); + void setSizeGripVisible(bool visible = true) const; +#endif + void updateInternalWindowTitle(); + QString originalWindowTitle(); + void setNewWindowTitle(); + + inline int titleBarHeight() const + { + Q_Q(const QMdiSubWindow); + if (!q->parent() || q->windowFlags() & Qt::FramelessWindowHint + || (q->isMaximized() && !drawTitleBarWhenMaximized())) { + return 0; + } + QStyleOptionTitleBar options = titleBarOptions(); + int height = options.rect.height(); + if (hasBorder(options)) + height += q->isMinimized() ? 8 : 4; + return height; + } + + inline QStyle::SubControl getSubControl(const QPoint &pos) const + { + Q_Q(const QMdiSubWindow); + QStyleOptionTitleBar titleBarOptions = this->titleBarOptions(); + return q->style()->hitTestComplexControl(QStyle::CC_TitleBar, &titleBarOptions, pos, q); + } + + inline void setNewGeometry(QRect *geometry) + { + Q_Q(QMdiSubWindow); + Q_ASSERT(q->parent()); + geometry->setSize(geometry->size().expandedTo(internalMinimumSize)); +#ifndef QT_NO_RUBBERBAND + if (isInRubberBandMode) + rubberBand->setGeometry(*geometry); + else +#endif + q->setGeometry(*geometry); + } + + inline bool hasBorder(const QStyleOptionTitleBar &options) const + { + Q_Q(const QMdiSubWindow); + return !q->style()->styleHint(QStyle::SH_TitleBar_NoBorder, &options, q); + } + + inline bool autoRaise() const + { + Q_Q(const QMdiSubWindow); + return q->style()->styleHint(QStyle::SH_TitleBar_AutoRaise, 0, q); + } + + inline bool isResizeOperation() const + { + return currentOperation != None && currentOperation != Move; + } + + inline bool isMoveOperation() const + { + return currentOperation == Move; + } +}; + +#endif // QT_NO_MDIAREA + +QT_END_NAMESPACE + +#endif // QMDISUBWINDOW_P_H diff --git a/src/gui/widgets/qmenu.cpp b/src/gui/widgets/qmenu.cpp new file mode 100644 index 0000000..e96856a --- /dev/null +++ b/src/gui/widgets/qmenu.cpp @@ -0,0 +1,3466 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmenu.h" + +#ifndef QT_NO_MENU + +#include "qdebug.h" +#include "qstyle.h" +#include "qevent.h" +#include "qtimer.h" +#include "qlayout.h" +#include "qpainter.h" +#include "qapplication.h" +#include "qdesktopwidget.h" +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif +#ifndef QT_NO_EFFECTS +# include <private/qeffects_p.h> +#endif +#ifndef QT_NO_WHATSTHIS +# include <qwhatsthis.h> +#endif + +#include "qmenu_p.h" +#include "qmenubar_p.h" +#include "qwidgetaction.h" +#include "qtoolbutton.h" +#include <private/qaction_p.h> +#ifdef QT3_SUPPORT +#include <qmenudata.h> +#endif // QT3_SUPPORT + +#ifdef Q_WS_X11 +# include <private/qt_x11_p.h> +#endif + +#if defined(Q_WS_MAC) && !defined(QT_NO_EFFECTS) +# include <private/qcore_mac_p.h> +# include <private/qt_cocoa_helpers_mac_p.h> +#endif + + +QT_BEGIN_NAMESPACE + +QPointer<QMenu> QMenuPrivate::mouseDown; +QBasicTimer QMenuPrivate::menuDelayTimer; +QBasicTimer QMenuPrivate::sloppyDelayTimer; + +/* QMenu code */ +// internal class used for the torn off popup +class QTornOffMenu : public QMenu +{ + Q_OBJECT + class QTornOffMenuPrivate : public QMenuPrivate + { + Q_DECLARE_PUBLIC(QMenu) + public: + QTornOffMenuPrivate(QMenu *p) : causedMenu(p) { + tornoff = 1; + causedPopup.widget = 0; + causedPopup.action = ((QTornOffMenu*)p)->d_func()->causedPopup.action; + causedStack = ((QTornOffMenu*)p)->d_func()->calcCausedStack(); + } + QList<QPointer<QWidget> > calcCausedStack() const { return causedStack; } + QPointer<QMenu> causedMenu; + QList<QPointer<QWidget> > causedStack; + }; +public: + QTornOffMenu(QMenu *p) : QMenu(*(new QTornOffMenuPrivate(p))) + { + Q_D(QTornOffMenu); + // make the torn-off menu a sibling of p (instead of a child) + QWidget *parentWidget = d->causedStack.isEmpty() ? p : d->causedStack.last(); + if (parentWidget->parentWidget()) + parentWidget = parentWidget->parentWidget(); + setParent(parentWidget, Qt::Window | Qt::Tool); + setAttribute(Qt::WA_DeleteOnClose, true); + setAttribute(Qt::WA_X11NetWmWindowTypeMenu, true); + setWindowTitle(p->windowTitle()); + setEnabled(p->isEnabled()); + //QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(onTrigger(QAction*))); + //QObject::connect(this, SIGNAL(hovered(QAction*)), this, SLOT(onHovered(QAction*))); + QList<QAction*> items = p->actions(); + for(int i = 0; i < items.count(); i++) + addAction(items.at(i)); + } + void syncWithMenu(QMenu *menu, QActionEvent *act) + { + Q_D(QTornOffMenu); + if(menu != d->causedMenu) + return; + if (act->type() == QEvent::ActionAdded) { + insertAction(act->before(), act->action()); + } else if (act->type() == QEvent::ActionRemoved) + removeAction(act->action()); + } + void actionEvent(QActionEvent *e) + { + QMenu::actionEvent(e); + setFixedSize(sizeHint()); + } +public slots: + void onTrigger(QAction *action) { d_func()->activateAction(action, QAction::Trigger, false); } + void onHovered(QAction *action) { d_func()->activateAction(action, QAction::Hover, false); } +private: + Q_DECLARE_PRIVATE(QTornOffMenu) + friend class QMenuPrivate; +}; + +void QMenuPrivate::init() +{ + Q_Q(QMenu); + activationRecursionGuard = false; +#ifndef QT_NO_WHATSTHIS + q->setAttribute(Qt::WA_CustomWhatsThis); +#endif + q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu); + defaultMenuAction = menuAction = new QAction(q); + menuAction->d_func()->menu = q; + q->setMouseTracking(q->style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, q)); + if (q->style()->styleHint(QStyle::SH_Menu_Scrollable, 0, q)) { + scroll = new QMenuPrivate::QMenuScroller; + scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; + } +} + +//Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't +QRect QMenuPrivate::popupGeometry(int screen) const +{ +#ifdef Q_WS_WIN + return QApplication::desktop()->screenGeometry(screen); +#elif defined Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE) + return QApplication::desktop()->screenGeometry(screen); + else + return QApplication::desktop()->availableGeometry(screen); +#else + return QApplication::desktop()->availableGeometry(screen); +#endif +} + +QList<QPointer<QWidget> > QMenuPrivate::calcCausedStack() const +{ + QList<QPointer<QWidget> > ret; + for(QWidget *widget = causedPopup.widget; widget; ) { + ret.append(widget); + if (QTornOffMenu *qtmenu = qobject_cast<QTornOffMenu*>(widget)) + ret += qtmenu->d_func()->causedStack; + if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) + widget = qmenu->d_func()->causedPopup.widget; + else + break; + } + return ret; +} + +void QMenuPrivate::calcActionRects(QMap<QAction*, QRect> &actionRects, QList<QAction*> &actionList) const +{ + Q_Q(const QMenu); + if (!itemsDirty) { + actionRects = this->actionRects; + actionList = this->actionList; + return; + } + + actionRects.clear(); + actionList.clear(); + QList<QAction*> items = filterActions(q->actions()); + int max_column_width = 0, + dh = popupGeometry(QApplication::desktop()->screenNumber(q)).height(), + ncols = 1, + y = 0; + const int hmargin = q->style()->pixelMetric(QStyle::PM_MenuHMargin, 0, q), + vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q), + icone = q->style()->pixelMetric(QStyle::PM_SmallIconSize, 0, q); + + //for compatability now - will have to refactor this away.. + tabWidth = 0; + maxIconWidth = 0; + hasCheckableItems = false; + for(int i = 0; i < items.count(); i++) { + QAction *action = items.at(i); + if (widgetItems.value(action)) + continue; + hasCheckableItems |= action->isCheckable(); + QIcon is = action->icon(); + if (!is.isNull()) { + uint miw = maxIconWidth; + maxIconWidth = qMax<uint>(miw, icone + 4); + } + } + + //calculate size + QFontMetrics qfm = q->fontMetrics(); + for(int i = 0; i < items.count(); i++) { + QAction *action = items.at(i); + + QFontMetrics fm(action->font().resolve(q->font())); + QSize sz; + + //let the style modify the above size.. + QStyleOptionMenuItem opt; + q->initStyleOption(&opt, action); + opt.rect = q->rect(); + + if (QWidget *w = widgetItems.value(action)) { + sz=w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize()); + } else { + //calc what I think the size is.. + if (action->isSeparator()) { + sz = QSize(2, 2); + } else { + QString s = action->text(); + int t = s.indexOf(QLatin1Char('\t')); + if (t != -1) { + tabWidth = qMax(int(tabWidth), qfm.width(s.mid(t+1))); + s = s.left(t); + #ifndef QT_NO_SHORTCUT + } else { + QKeySequence seq = action->shortcut(); + if (!seq.isEmpty()) + tabWidth = qMax(int(tabWidth), qfm.width(seq)); + #endif + } + int w = fm.boundingRect(QRect(), Qt::TextSingleLine, s).width(); + w -= s.count(QLatin1Char('&')) * fm.width(QLatin1Char('&')); + w += s.count(QLatin1String("&&")) * fm.width(QLatin1Char('&')); + sz.setWidth(w); + sz.setHeight(qMax(fm.height(), qfm.height())); + + QIcon is = action->icon(); + if (!is.isNull()) { + QSize is_sz = QSize(icone, icone); + if (is_sz.height() > sz.height()) + sz.setHeight(is_sz.height()); + } + } + sz = q->style()->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, q); + } + + + if (!sz.isEmpty()) { + max_column_width = qMax(max_column_width, sz.width()); + //wrapping + if (!scroll && + y+sz.height()+vmargin > dh - (q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q) * 2)) { + ncols++; + y = vmargin; + } + y += sz.height(); + //append item + actionRects.insert(action, QRect(0, 0, sz.width(), sz.height())); + actionList.append(action); + } + } + + if (tabWidth) + max_column_width += tabWidth; //finally add in the tab width + + //calculate position + int x = hmargin; + y = vmargin; + + for(int i = 0; i < actionList.count(); i++) { + QAction *action = actionList.at(i); + QRect &rect = actionRects[action]; + if (rect.isNull()) + continue; + if (!scroll && + y+rect.height() > dh - (q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q) * 2)) { + ncols--; + if (ncols < 0) + qWarning("QMenu: Column calculation mismatch (%d)", ncols); + x += max_column_width + hmargin; + y = vmargin; + } + rect.translate(x, y); //move + rect.setWidth(max_column_width); //uniform width + y += rect.height(); + } +} + +void QMenuPrivate::updateActions() +{ + Q_Q(const QMenu); + if (!itemsDirty) + return; + sloppyAction = 0; + calcActionRects(actionRects, actionList); + for (QHash<QAction *, QWidget *>::ConstIterator item = widgetItems.constBegin(), + end = widgetItems.constEnd(); item != end; ++item) { + QAction *action = item.key(); + QWidget *widget = item.value(); + widget->setGeometry(actionRect(action)); + widget->setVisible(action->isVisible()); + } + ncols = 1; + int last_left = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q); + if (!scroll) { + for(int i = 0; i < actionList.count(); i++) { + int left = actionRects.value(actionList.at(i)).left(); + if (left > last_left) { + last_left = left; + ncols++; + } + } + } + itemsDirty = 0; +} + +QList<QAction *> QMenuPrivate::filterActions(const QList<QAction *> &actions) const +{ + QList<QAction *> visibleActions; + int i = 0; + while (i < actions.count()) { + QAction *action = actions.at(i); + if (!action->isVisible()) { + ++i; + continue; + } + if (!action->isSeparator() || !collapsibleSeparators) { + visibleActions.append(action); + ++i; + continue; + } + + // no leading separators + if (!visibleActions.isEmpty()) + visibleActions.append(action); + + // skip double/tripple/etc. separators + while (i < actions.count() + && (!actions.at(i)->isVisible() || actions.at(i)->isSeparator())) + ++i; + } + + if (collapsibleSeparators) { + // remove trailing separators + while (!visibleActions.isEmpty() && visibleActions.last()->isSeparator()) + visibleActions.removeLast(); + } + + return visibleActions; +} + +QRect QMenuPrivate::actionRect(QAction *act) const +{ + Q_Q(const QMenu); + QRect ret = actionRects.value(act); + if (ret.isNull()) + return ret; + if (scroll) + ret.translate(0, scroll->scrollOffset); + if (tearoff) + ret.translate(0, q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q)); + const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q); + ret.translate(fw+leftmargin, fw+topmargin); + return ret; +} + +void QMenuPrivate::hideUpToMenuBar() +{ + Q_Q(QMenu); + if (!tornoff) { + QWidget *caused = causedPopup.widget; + hideMenu(q); //hide after getting causedPopup + while(caused) { +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) { + mb->d_func()->setCurrentAction(0); + mb->d_func()->setKeyboardMode(false); + caused = 0; + } else +#endif + if (QMenu *m = qobject_cast<QMenu*>(caused)) { + caused = m->d_func()->causedPopup.widget; + if (!m->d_func()->tornoff) + hideMenu(m); + m->d_func()->setCurrentAction(0); + } else { +#ifndef QT_NO_TOOLBUTTON + if (qobject_cast<QToolButton*>(caused) == 0) +#endif + qWarning("QMenu: Internal error"); + caused = 0; + } + } + } + setCurrentAction(0); +} + +void QMenuPrivate::hideMenu(QMenu *menu) +{ + if (!menu) + return; + +#if !defined(QT_NO_EFFECTS) + menu->blockSignals(true); + aboutToHide = true; + // Flash item which is about to trigger (if any). + if (menu->style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem) + && currentAction && currentAction == actionAboutToTrigger) { + + QEventLoop eventLoop; + QAction *activeAction = currentAction; + + // Deselect and wait 60 ms. + menu->setActiveAction(0); + QTimer::singleShot(60, &eventLoop, SLOT(quit())); + eventLoop.exec(); + + // Select and wait 20 ms. + menu->setActiveAction(activeAction); + QTimer::singleShot(20, &eventLoop, SLOT(quit())); + eventLoop.exec(); + } + + // Fade out. + if (menu->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide)) { + // ### Qt 4.4: + // Should be something like: q->transitionWindow(Qt::FadeOutTransition, 150); + // Hopefully we'll integrate qt/research/windowtransitions into main before 4.4. + // Talk to Richard, Trenton or Bjoern. +#if defined(Q_WS_MAC) + macWindowFade(qt_mac_window_for(menu)); // FIXME - what is the default duration for view animations + + // Wait for the transition to complete. + QEventLoop eventLoop; + QTimer::singleShot(150, &eventLoop, SLOT(quit())); + eventLoop.exec(); +#endif // Q_WS_MAC + } + aboutToHide = false; + menu->blockSignals(false); +#endif // QT_NO_EFFECTS + menu->hide(); +} + +void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst) +{ + Q_Q(QMenu); + if (action && action->isEnabled()) { + if (!delay) + q->internalDelayedPopup(); + else + QMenuPrivate::menuDelayTimer.start(delay, q); + if (activateFirst && action->menu()) + action->menu()->d_func()->setFirstActionActive(); + } else if (QMenu *menu = activeMenu) { //hide the current item + activeMenu = 0; + hideMenu(menu); + } +} + +void QMenuPrivate::setSyncAction() +{ + Q_Q(QMenu); + QAction *current = currentAction; + if(current && (!current->isEnabled() || current->menu() || current->isSeparator())) + current = 0; + for(QWidget *caused = q; caused;) { + if (QMenu *m = qobject_cast<QMenu*>(caused)) { + caused = m->d_func()->causedPopup.widget; + if (m->d_func()->eventLoop) + m->d_func()->syncAction = current; // synchronous operation + } else { + break; + } + } +} + + +void QMenuPrivate::setFirstActionActive() +{ + Q_Q(QMenu); + const int scrollerHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q); + for(int i = 0, saccum = 0; i < actionList.count(); i++) { + QAction *act = actionList[i]; + if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) { + saccum -= actionRects.value(act).height(); + if (saccum > scroll->scrollOffset-scrollerHeight) + continue; + } + if (!act->isSeparator() && + (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q) + || act->isEnabled())) { + setCurrentAction(act); + break; + } + } +} + +// popup == -1 means do not popup, 0 means immediately, others mean use a timer +void QMenuPrivate::setCurrentAction(QAction *action, int popup, SelectionReason reason, bool activateFirst) +{ + Q_Q(QMenu); + tearoffHighlighted = 0; + if (action == currentAction && !(action && action->menu() && action->menu() != activeMenu)) { + if(QMenu *menu = qobject_cast<QMenu*>(causedPopup.widget)) { + if(causedPopup.action && menu->d_func()->activeMenu == q) + menu->d_func()->setCurrentAction(causedPopup.action, 0, reason, false); + } + return; + } + if (currentAction) + q->update(actionRect(currentAction)); + + sloppyAction = 0; + if (!sloppyRegion.isEmpty()) + sloppyRegion = QRegion(); + QMenu *hideActiveMenu = activeMenu; +#ifndef QT_NO_STATUSTIP + QAction *previousAction = currentAction; +#endif +#ifdef QT3_SUPPORT + emitHighlighted = (action && action != currentAction); +#endif + currentAction = action; + if (action) { + if (!action->isSeparator()) { + activateAction(action, QAction::Hover); + if (popup != -1) { + hideActiveMenu = 0; //will be done "later" + // if the menu is visible then activate the required action, + // otherwise we just mark the action as currentAction + // and activate it when the menu will be popuped. + if (q->isVisible()) + popupAction(currentAction, popup, activateFirst); + } + q->update(actionRect(action)); + QWidget *widget = widgetItems.value(action); + + if (reason == SelectedFromKeyboard) { + if (widget) { + if (widget->focusPolicy() != Qt::NoFocus) + widget->setFocus(Qt::TabFocusReason); + } else { + //when the action has no QWidget, the QMenu itself should + // get the focus + // Since the menu is a pop-up, it uses the popup reason. + q->setFocus(Qt::PopupFocusReason); + } + } + } else { //action is a separator + if (popup != -1) + hideActiveMenu = 0; //will be done "later" + } +#ifndef QT_NO_STATUSTIP + } else if (previousAction) { + QWidget *w = causedPopup.widget; + while (QMenu *m = qobject_cast<QMenu*>(w)) + w = m->d_func()->causedPopup.widget; + if (w) { + QString empty; + QStatusTipEvent tip(empty); + QApplication::sendEvent(w, &tip); + } +#endif + } + if (hideActiveMenu) { + activeMenu = 0; +#ifndef QT_NO_EFFECTS + // kill any running effect + qFadeEffect(0); + qScrollEffect(0); +#endif + hideMenu(hideActiveMenu); + } +} + +QAction *QMenuPrivate::actionAt(QPoint p) const +{ + if (!q_func()->rect().contains(p)) //sanity check + return 0; + + for(int i = 0; i < actionList.count(); i++) { + QAction *act = actionList[i]; + if (actionRect(act).contains(p)) + return act; + } + return 0; +} + +void QMenuPrivate::setOverrideMenuAction(QAction *a) +{ + Q_Q(QMenu); + QObject::disconnect(menuAction, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed())); + if (a) { + menuAction = a; + QObject::connect(a, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed())); + } else { //we revert back to the default action created by the QMenu itself + menuAction = defaultMenuAction; + } +} + +void QMenuPrivate::_q_overrideMenuActionDestroyed() +{ + menuAction=defaultMenuAction; +} + +/*! + Returns the action associated with this menu. +*/ +QAction *QMenu::menuAction() const +{ + return d_func()->menuAction; +} + +/*! + \property QMenu::title + \brief The title of the menu + + This is equivalent to the QAction::text property of the menuAction(). + + By default, this property contains an empty string. +*/ +QString QMenu::title() const +{ + return d_func()->menuAction->text(); +} + +void QMenu::setTitle(const QString &text) +{ + d_func()->menuAction->setText(text); +} + +/*! + \property QMenu::icon + + \brief The icon of the menu + + This is equivalent to the QAction::icon property of the menuAction(). + + By default, if no icon is explicitly set, this property contains a null icon. +*/ +QIcon QMenu::icon() const +{ + return d_func()->menuAction->icon(); +} + +void QMenu::setIcon(const QIcon &icon) +{ + d_func()->menuAction->setIcon(icon); +} + + +//actually performs the scrolling +void QMenuPrivate::scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active) +{ + Q_Q(QMenu); + if (!scroll || !scroll->scrollFlags) + return; + int newOffset = 0; + const int scrollHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q); + const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollHeight : 0; + const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollHeight : 0; + const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q); + const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q); + + if (location == QMenuScroller::ScrollTop) { + for(int i = 0, saccum = 0; i < actionList.count(); i++) { + QAction *act = actionList.at(i); + if (act == action) { + newOffset = topScroll - saccum; + break; + } + saccum += actionRects.value(act).height(); + } + } else { + for(int i = 0, saccum = 0; i < actionList.count(); i++) { + QAction *act = actionList.at(i); + saccum += actionRects.value(act).height(); + if (act == action) { + if (location == QMenuScroller::ScrollCenter) + newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll); + else + newOffset = (q->height() - botScroll) - saccum; + break; + } + } + if(newOffset) + newOffset -= fw*2; + } + + //figure out which scroll flags + uint newScrollFlags = QMenuScroller::ScrollNone; + if (newOffset < 0) //easy and cheap one + newScrollFlags |= QMenuScroller::ScrollUp; + int saccum = newOffset; + for(int i = 0; i < actionList.count(); i++) { + saccum += actionRects.value(actionList.at(i)).height(); + if (saccum > q->height()) { + newScrollFlags |= QMenuScroller::ScrollDown; + break; + } + } + + if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) { + newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin; //last item at bottom + } + + if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) { + newOffset = 0; //first item at top + } + + if (newScrollFlags & QMenuScroller::ScrollUp) + newOffset -= vmargin; + + QRect screen = popupGeometry(QApplication::desktop()->screenNumber(q)); + const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q); + if (q->height() < screen.height()-(desktopFrame*2)-1) { + QRect geom = q->geometry(); + if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up + const int newHeight = geom.height()-(newOffset-scroll->scrollOffset); + if(newHeight > geom.height()) + geom.setHeight(newHeight); + } else if(scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) { + int newTop = geom.top() + (newOffset-scroll->scrollOffset); + if (newTop < desktopFrame+screen.top()) + newTop = desktopFrame+screen.top(); + if (newTop < geom.top()) { + geom.setTop(newTop); + newOffset = 0; + newScrollFlags &= ~QMenuScroller::ScrollUp; + } + } + if (geom.bottom() > screen.bottom() - desktopFrame) + geom.setBottom(screen.bottom() - desktopFrame); + if (geom.top() < desktopFrame+screen.top()) + geom.setTop(desktopFrame+screen.top()); + if (geom != q->geometry()) { +#if 0 + if (newScrollFlags & QMenuScroller::ScrollDown && + q->geometry().top() - geom.top() >= -newOffset) + newScrollFlags &= ~QMenuScroller::ScrollDown; +#endif + q->setGeometry(geom); + } + } + + //actually update flags + scroll->scrollOffset = newOffset; + if (scroll->scrollOffset > 0) + scroll->scrollOffset = 0; + scroll->scrollFlags = newScrollFlags; + if (active) + setCurrentAction(action); + + q->update(); //issue an update so we see all the new state.. +} + +void QMenuPrivate::scrollMenu(QMenuScroller::ScrollLocation location, bool active) +{ + Q_Q(QMenu); + if(location == QMenuScroller::ScrollBottom) { + for(int i = actionList.size()-1; i >= 0; --i) { + QAction *act = actionList.at(i); + if (!act->isSeparator() && + (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q) + || act->isEnabled())) { + if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) + scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollBottom, active); + else if(active) + setCurrentAction(act, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard); + break; + } + } + } else if(location == QMenuScroller::ScrollTop) { + for(int i = 0; i < actionList.size(); ++i) { + QAction *act = actionList.at(i); + if (!act->isSeparator() && + (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q) + || act->isEnabled())) { + if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) + scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollTop, active); + else if(active) + setCurrentAction(act, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard); + break; + } + } + } +} + +//only directional +void QMenuPrivate::scrollMenu(QMenuScroller::ScrollDirection direction, bool page, bool active) +{ + Q_Q(QMenu); + if (!scroll || !(scroll->scrollFlags & direction)) //not really possible... + return; + const int scrollHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q); + const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollHeight : 0; + const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollHeight : 0; + const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q); + const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q); + const int offset = topScroll ? topScroll-vmargin : 0; + if (direction == QMenuScroller::ScrollUp) { + for(int i = 0, saccum = 0; i < actionList.count(); i++) { + QAction *act = actionList.at(i); + const int iHeight = actionRects.value(act).height(); + saccum -= iHeight; + if (saccum <= scroll->scrollOffset-offset) { + scrollMenu(act, page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active); + break; + } + } + } else if (direction == QMenuScroller::ScrollDown) { + bool scrolled = false; + for(int i = 0, saccum = 0; i < actionList.count(); i++) { + QAction *act = actionList.at(i); + const int iHeight = actionRects.value(act).height(); + saccum -= iHeight; + if (saccum <= scroll->scrollOffset-offset) { + const int scrollerArea = q->height() - botScroll - fw*2; + int visible = (scroll->scrollOffset-offset) - saccum; + for(i++ ; i < actionList.count(); i++) { + act = actionList.at(i); + const int iHeight = actionRects.value(act).height(); + visible += iHeight; + if (visible > scrollerArea - topScroll) { + scrolled = true; + scrollMenu(act, page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active); + break; + } + } + break; + } + } + if(!scrolled) { + scroll->scrollFlags &= ~QMenuScroller::ScrollDown; + q->update(); + } + } +} + +/* This is poor-mans eventfilters. This avoids the use of + eventFilter (which can be nasty for users of QMenuBar's). */ +bool QMenuPrivate::mouseEventTaken(QMouseEvent *e) +{ + Q_Q(QMenu); + QPoint pos = q->mapFromGlobal(e->globalPos()); + if (scroll && !activeMenu) { //let the scroller "steal" the event + bool isScroll = false; + if (pos.x() >= 0 && pos.x() < q->width()) { + const int scrollerHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q); + for(int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) { + if (scroll->scrollFlags & dir) { + if (dir == QMenuScroller::ScrollUp) + isScroll = (pos.y() <= scrollerHeight); + else if (dir == QMenuScroller::ScrollDown) + isScroll = (pos.y() >= q->height()-scrollerHeight); + if (isScroll) { + scroll->scrollDirection = dir; + break; + } + } + } + } + if (isScroll) { + if (!scroll->scrollTimer) + scroll->scrollTimer = new QBasicTimer; + scroll->scrollTimer->start(50, q); + return true; + } else if (scroll->scrollTimer && scroll->scrollTimer->isActive()) { + scroll->scrollTimer->stop(); + } + } + + if (tearoff) { //let the tear off thingie "steal" the event.. + QRect tearRect(0, 0, q->width(), q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q)); + if (scroll && scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) + tearRect.translate(0, q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q)); + q->update(tearRect); + if (tearRect.contains(pos) && hasMouseMoved(e->globalPos())) { + setCurrentAction(0); + tearoffHighlighted = 1; + if (e->type() == QEvent::MouseButtonRelease) { + if (!tornPopup) + tornPopup = new QTornOffMenu(q); + tornPopup->setGeometry(q->geometry()); + tornPopup->show(); + hideUpToMenuBar(); + } + return true; + } + tearoffHighlighted = 0; + } + + if (q->frameGeometry().contains(e->globalPos())) //otherwise if the event is in our rect we want it.. + return false; + + for(QWidget *caused = causedPopup.widget; caused;) { + bool passOnEvent = false; + QWidget *next_widget = 0; + QPoint cpos = caused->mapFromGlobal(e->globalPos()); +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) { + passOnEvent = mb->rect().contains(cpos); + } else +#endif + if (QMenu *m = qobject_cast<QMenu*>(caused)) { + passOnEvent = m->rect().contains(cpos); + next_widget = m->d_func()->causedPopup.widget; + } + if (passOnEvent) { + if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) { + QMouseEvent new_e(e->type(), cpos, e->button(), e->buttons(), e->modifiers()); + QApplication::sendEvent(caused, &new_e); + return true; + } + } + if (!next_widget) + break; + caused = next_widget; + } + return false; +} + +void QMenuPrivate::activateCausedStack(const QList<QPointer<QWidget> > &causedStack, QAction *action, QAction::ActionEvent action_e, bool self) +{ + Q_ASSERT(!activationRecursionGuard); + activationRecursionGuard = true; +#ifdef QT3_SUPPORT + const int actionId = q_func()->findIdForAction(action); +#endif + if(self) + action->activate(action_e); + + for(int i = 0; i < causedStack.size(); ++i) { + QPointer<QWidget> widget = causedStack.at(i); + if (!widget) + continue; + //fire + if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) { + widget = qmenu->d_func()->causedPopup.widget; + if (action_e == QAction::Trigger) { + emit qmenu->triggered(action); + } else if (action_e == QAction::Hover) { + emit qmenu->hovered(action); +#ifdef QT3_SUPPORT + if (emitHighlighted) { + emit qmenu->highlighted(actionId); + emitHighlighted = false; + } +#endif + } +#ifndef QT_NO_MENUBAR + } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) { + if (action_e == QAction::Trigger) { + emit qmenubar->triggered(action); +#ifdef QT3_SUPPORT + emit qmenubar->activated(actionId); +#endif + } else if (action_e == QAction::Hover) { + emit qmenubar->hovered(action); +#ifdef QT3_SUPPORT + if (emitHighlighted) { + emit qmenubar->highlighted(actionId); + emitHighlighted = false; + } +#endif + } + break; //nothing more.. +#endif + } + } + activationRecursionGuard = false; +} + +void QMenuPrivate::activateAction(QAction *action, QAction::ActionEvent action_e, bool self) +{ + Q_Q(QMenu); +#ifndef QT_NO_WHATSTHIS + bool inWhatsThisMode = QWhatsThis::inWhatsThisMode(); +#endif + if (!action || !q->isEnabled() + || (action_e == QAction::Trigger +#ifndef QT_NO_WHATSTHIS + && !inWhatsThisMode +#endif + && (action->isSeparator() ||!action->isEnabled()))) + return; + + /* I have to save the caused stack here because it will be undone after popup execution (ie in the hide). + Then I iterate over the list to actually send the events. --Sam + */ + const QList<QPointer<QWidget> > causedStack = calcCausedStack(); + if (action_e == QAction::Trigger) { +#ifndef QT_NO_WHATSTHIS + if (!inWhatsThisMode) + actionAboutToTrigger = action; +#endif + + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + hideUpToMenuBar(); + } else { + for(QWidget *widget = qApp->activePopupWidget(); widget; ) { + if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) { + if(qmenu == q) + hideUpToMenuBar(); + widget = qmenu->d_func()->causedPopup.widget; + } else { + break; + } + } + } + +#ifndef QT_NO_WHATSTHIS + if (inWhatsThisMode) { + QString s = action->whatsThis(); + if (s.isEmpty()) + s = whatsThis; + QWhatsThis::showText(q->mapToGlobal(actionRect(action).center()), s, q); + return; + } +#endif + } + + + activateCausedStack(causedStack, action, action_e, self); + + + if (action_e == QAction::Hover) { +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + int actionIndex = indexOf(action) + 1; + QAccessible::updateAccessibility(q, actionIndex, QAccessible::Focus); + QAccessible::updateAccessibility(q, actionIndex, QAccessible::Selection); + } +#endif + QWidget *w = causedPopup.widget; + while (QMenu *m = qobject_cast<QMenu*>(w)) + w = m->d_func()->causedPopup.widget; + action->showStatusText(w); + } else { + actionAboutToTrigger = 0; + } +} + +void QMenuPrivate::_q_actionTriggered() +{ + Q_Q(QMenu); + if (QAction *action = qobject_cast<QAction *>(q->sender())) { +#ifdef QT3_SUPPORT + //we store it here because the action might be deleted/changed by connected slots + const int id = q->findIdForAction(action); +#endif + emit q->triggered(action); +#ifdef QT3_SUPPORT + emit q->activated(id); +#endif + + if (!activationRecursionGuard) { + //in case the action has not been activated by the mouse + //we check the parent hierarchy + QList< QPointer<QWidget> > list; + for(QWidget *widget = q->parentWidget(); widget; ) { + if (qobject_cast<QMenu*>(widget) +#ifndef QT_NO_MENUBAR + || qobject_cast<QMenuBar*>(widget) +#endif + ) { + list.append(widget); + widget = widget->parentWidget(); + } else { + break; + } + } + activateCausedStack(list, action, QAction::Trigger, false); + } + } +} + +void QMenuPrivate::_q_actionHovered() +{ + Q_Q(QMenu); + if (QAction * action = qobject_cast<QAction *>(q->sender())) { +#ifdef QT3_SUPPORT + //we store it here because the action might be deleted/changed by connected slots + const int id = q->findIdForAction(action); +#endif + emit q->hovered(action); +#ifdef QT3_SUPPORT + if (emitHighlighted) { + emit q->highlighted(id); + emitHighlighted = false; + } +#endif + } +} + +bool QMenuPrivate::hasMouseMoved(const QPoint &globalPos) +{ + //determines if the mouse has moved (ie its intial position has + //changed by more than QApplication::startDragDistance() + //or if there were at least 6 mouse motions) + return motions > 6 || + QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength(); +} + + +/*! + Initialize \a option with the values from this menu and information from \a action. This method + is useful for subclasses when they need a QStyleOptionMenuItem, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() QMenuBar::initStyleOption() +*/ +void QMenu::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const +{ + if (!option || !action) + return; + + Q_D(const QMenu); + option->initFrom(this); + option->palette = palette(); + option->state = QStyle::State_None; + + if (window()->isActiveWindow()) + option->state |= QStyle::State_Active; + if (isEnabled() && action->isEnabled() + && (!action->menu() || action->menu()->isEnabled())) + option->state |= QStyle::State_Enabled; + else + option->palette.setCurrentColorGroup(QPalette::Disabled); + + option->font = action->font(); + + if (d->currentAction && d->currentAction == action && !d->currentAction->isSeparator()) { + option->state |= QStyle::State_Selected + | (d->mouseDown ? QStyle::State_Sunken : QStyle::State_None); + } + + option->menuHasCheckableItems = d->hasCheckableItems; + if (!action->isCheckable()) { + option->checkType = QStyleOptionMenuItem::NotCheckable; + } else { + option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive()) + ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive; + option->checked = action->isChecked(); + } + if (action->menu()) + option->menuItemType = QStyleOptionMenuItem::SubMenu; + else if (action->isSeparator()) + option->menuItemType = QStyleOptionMenuItem::Separator; + else if (d->defaultAction == action) + option->menuItemType = QStyleOptionMenuItem::DefaultItem; + else + option->menuItemType = QStyleOptionMenuItem::Normal; + if (action->isIconVisibleInMenu()) + option->icon = action->icon(); + QString textAndAccel = action->text(); +#ifndef QT_NO_SHORTCUT + if (textAndAccel.indexOf(QLatin1Char('\t')) == -1) { + QKeySequence seq = action->shortcut(); + if (!seq.isEmpty()) + textAndAccel += QLatin1Char('\t') + QString(seq); + } +#endif + option->text = textAndAccel; + option->tabWidth = d->tabWidth; + option->maxIconWidth = d->maxIconWidth; + option->menuRect = rect(); +} + +/*! + \class QMenu + \brief The QMenu class provides a menu widget for use in menu + bars, context menus, and other popup menus. + + \ingroup application + \ingroup basicwidgets + \mainclass + + A menu widget is a selection menu. It can be either a pull-down + menu in a menu bar or a standalone context menu. Pull-down menus + are shown by the menu bar when the user clicks on the respective + item or presses the specified shortcut key. Use + QMenuBar::addMenu() to insert a menu into a menu bar. Context + menus are usually invoked by some special keyboard key or by + right-clicking. They can be executed either asynchronously with + popup() or synchronously with exec(). Menus can also be invoked in + response to button presses; these are just like context menus + except for how they are invoked. + + \raw HTML + <table align="center" cellpadding="0"> + <tr> + <td> + \endraw + \inlineimage plastique-menu.png + \raw HTML + </td> + <td> + \endraw + \inlineimage windowsxp-menu.png + \raw HTML + </td> + <td> + \endraw + \inlineimage macintosh-menu.png + \raw HTML + </td> + + </tr> + <tr> + <td colspan="3"> + \endraw + A menu shown in \l{Plastique Style Widget Gallery}{Plastique widget style}, + \l{Windows XP Style Widget Gallery}{Windows XP widget style}, + and \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \raw HTML + </td> + </tr> + </table> + \endraw + + \section1 Actions + + A menu consists of a list of action items. Actions are added with + the addAction(), addActions() and insertAction() functions. An action + is represented vertically and rendered by QStyle. In addition, actions + can have a text label, an optional icon drawn on the very left side, + and shortcut key sequence such as "Ctrl+X". + + The existing actions held by a menu can be found with actions(). + + There are four kinds of action items: separators, actions that + show a submenu, widgets, and actions that perform an action. + Separators are inserted with addSeparator(), submenus with addMenu(), + and all other items are considered action items. + + When inserting action items you usually specify a receiver and a + slot. The receiver will be notifed whenever the item is + \l{QAction::triggered()}{triggered()}. In addition, QMenu provides + two signals, activated() and highlighted(), which signal the + QAction that was triggered from the menu. + + You clear a menu with clear() and remove individual action items + with removeAction(). + + A QMenu can also provide a tear-off menu. A tear-off menu is a + top-level window that contains a copy of the menu. This makes it + possible for the user to "tear off" frequently used menus and + position them in a convenient place on the screen. If you want + this functionality for a particular menu, insert a tear-off handle + with setTearOffEnabled(). When using tear-off menus, bear in mind + that the concept isn't typically used on Microsoft Windows so + some users may not be familiar with it. Consider using a QToolBar + instead. + + Widgets can be inserted into menus with the QWidgetAction class. + Instances of this class are used to hold widgets, and are inserted + into menus with the addAction() overload that takes a QAction. + + Conversely, actions can be added to widgets with the addAction(), + addActions() and insertAction() functions. + + \section1 QMenu on Qt for Windows CE + + If a menu is integrated into the native menubar on Windows Mobile we + do not support the signals: aboutToHide (), aboutToShow () and hovered (). + It is not possible to display an icon in a native menu on Windows Mobile. + + See the \l{mainwindows/menus}{Menus} example for an example of how + to use QMenuBar and QMenu in your application. + + \bold{Important inherited functions:} addAction(), removeAction(), clear(), + addSeparator(), and addMenu(). + + \sa QMenuBar, {fowler}{GUI Design Handbook: Menu, Drop-Down and Pop-Up}, + {Application Example}, {Menus Example}, {Recent Files Example} +*/ + + +/*! + Constructs a menu with parent \a parent. + + Although a popup menu is always a top-level widget, if a parent is + passed the popup menu will be deleted when that parent is + destroyed (as with any other QObject). +*/ +QMenu::QMenu(QWidget *parent) + : QWidget(*new QMenuPrivate, parent, Qt::Popup) +{ + Q_D(QMenu); + d->init(); +} + +/*! + Constructs a menu with a \a title and a \a parent. + + Although a popup menu is always a top-level widget, if a parent is + passed the popup menu will be deleted when that parent is + destroyed (as with any other QObject). + + \sa title +*/ +QMenu::QMenu(const QString &title, QWidget *parent) + : QWidget(*new QMenuPrivate, parent, Qt::Popup) +{ + Q_D(QMenu); + d->init(); + d->menuAction->setText(title); +} + +/*! \internal + */ +QMenu::QMenu(QMenuPrivate &dd, QWidget *parent) + : QWidget(dd, parent, Qt::Popup) +{ + Q_D(QMenu); + d->init(); +} + +/*! + Destroys the menu. +*/ +QMenu::~QMenu() +{ + Q_D(QMenu); + for (QHash<QAction *, QWidget *>::ConstIterator item = d->widgetItems.constBegin(), + end = d->widgetItems.constEnd(); item != end; ++item) { + QWidgetAction *action = static_cast<QWidgetAction *>(item.key()); + QWidget *widget = item.value(); + if (action && widget) + action->releaseWidget(widget); + } + d->widgetItems.clear(); + + if (d->eventLoop) + d->eventLoop->exit(); + if (d->tornPopup) + d->tornPopup->close(); +} + +/*! + \overload + + This convenience function creates a new action with \a text. + The function adds the newly created action to the menu's + list of actions, and returns it. + + \sa QWidget::addAction() +*/ +QAction *QMenu::addAction(const QString &text) +{ + QAction *ret = new QAction(text, this); + addAction(ret); + return ret; +} + +/*! + \overload + + This convenience function creates a new action with an \a icon + and some \a text. The function adds the newly created action to + the menu's list of actions, and returns it. + + \sa QWidget::addAction() +*/ +QAction *QMenu::addAction(const QIcon &icon, const QString &text) +{ + QAction *ret = new QAction(icon, text, this); + addAction(ret); + return ret; +} + +/*! + \overload + + This convenience function creates a new action with the text \a + text and an optional shortcut \a shortcut. The action's + \l{QAction::triggered()}{triggered()} signal is connected to the + \a receiver's \a member slot. The function adds the newly created + action to the menu's list of actions and returns it. + + \sa QWidget::addAction() +*/ +QAction *QMenu::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut) +{ + QAction *action = new QAction(text, this); +#ifdef QT_NO_SHORTCUT + Q_UNUSED(shortcut); +#else + action->setShortcut(shortcut); +#endif + QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); + addAction(action); + return action; +} + +/*! + \overload + + This convenience function creates a new action with an \a icon and + some \a text and an optional shortcut \a shortcut. The action's + \l{QAction::triggered()}{triggered()} signal is connected to the + \a member slot of the \a receiver object. The function adds the + newly created action to the menu's list of actions, and returns it. + + \sa QWidget::addAction() +*/ +QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *receiver, + const char* member, const QKeySequence &shortcut) +{ + QAction *action = new QAction(icon, text, this); +#ifdef QT_NO_SHORTCUT + Q_UNUSED(shortcut); +#else + action->setShortcut(shortcut); +#endif + QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); + addAction(action); + return action; +} + +/*! + This convenience function adds \a menu as a submenu to this menu. + It returns \a menu's menuAction(). This menu does not take + ownership of \a menu. + + \sa QWidget::addAction() QMenu::menuAction() +*/ +QAction *QMenu::addMenu(QMenu *menu) +{ + QAction *action = menu->menuAction(); + addAction(action); + return action; +} + +/*! + Appends a new QMenu with \a title to the menu. The menu + takes ownership of the menu. Returns the new menu. + + \sa QWidget::addAction() QMenu::menuAction() +*/ +QMenu *QMenu::addMenu(const QString &title) +{ + QMenu *menu = new QMenu(title, this); + addAction(menu->menuAction()); + return menu; +} + +/*! + Appends a new QMenu with \a icon and \a title to the menu. The menu + takes ownership of the menu. Returns the new menu. + + \sa QWidget::addAction() QMenu::menuAction() +*/ +QMenu *QMenu::addMenu(const QIcon &icon, const QString &title) +{ + QMenu *menu = new QMenu(title, this); + menu->setIcon(icon); + addAction(menu->menuAction()); + return menu; +} + +/*! + This convenience function creates a new separator action, i.e. an + action with QAction::isSeparator() returning true, and adds the new + action to this menu's list of actions. It returns the newly + created action. + + \sa QWidget::addAction() +*/ +QAction *QMenu::addSeparator() +{ + QAction *action = new QAction(this); + action->setSeparator(true); + addAction(action); + return action; +} + +/*! + This convenience function inserts \a menu before action \a before + and returns the menus menuAction(). + + \sa QWidget::insertAction(), addMenu() +*/ +QAction *QMenu::insertMenu(QAction *before, QMenu *menu) +{ + QAction *action = menu->menuAction(); + insertAction(before, action); + return action; +} + +/*! + This convenience function creates a new separator action, i.e. an + action with QAction::isSeparator() returning true. The function inserts + the newly created action into this menu's list of actions before + action \a before and returns it. + + \sa QWidget::insertAction(), addSeparator() +*/ +QAction *QMenu::insertSeparator(QAction *before) +{ + QAction *action = new QAction(this); + action->setSeparator(true); + insertAction(before, action); + return action; +} + +/*! + This will set the default action to \a act. The default action may + have a visual queue depending on the current QStyle. A default + action is usually meant to indicate what will defaultly happen on a + drop, as shown in a context menu. + + \sa defaultAction() +*/ +void QMenu::setDefaultAction(QAction *act) +{ + d_func()->defaultAction = act; +} + +/*! + Returns the current default action. + + \sa setDefaultAction() +*/ +QAction *QMenu::defaultAction() const +{ + return d_func()->defaultAction; +} + +/*! + \property QMenu::tearOffEnabled + \brief whether the menu supports being torn off + + When true, the menu contains a special tear-off item (often shown as a dashed + line at the top of the menu) that creates a copy of the menu when it is + triggered. + + This "torn-off" copy lives in a separate window. It contains the same menu + items as the original menu, with the exception of the tear-off handle. + + By default, this property is false. +*/ +void QMenu::setTearOffEnabled(bool b) +{ + Q_D(QMenu); + if (d->tearoff == b) + return; + if (!b && d->tornPopup) + d->tornPopup->close(); + d->tearoff = b; + + d->itemsDirty = true; + if (isVisible()) + resize(sizeHint()); +} + +bool QMenu::isTearOffEnabled() const +{ + return d_func()->tearoff; +} + +/*! + When a menu is torn off a second menu is shown to display the menu + contents in a new window. When the menu is in this mode and the menu + is visible returns true; otherwise false. + + \sa hideTearOffMenu() isTearOffEnabled() +*/ +bool QMenu::isTearOffMenuVisible() const +{ + if (d_func()->tornPopup) + return d_func()->tornPopup->isVisible(); + return false; +} + +/*! + This function will forcibly hide the torn off menu making it + disappear from the users desktop. + + \sa isTearOffMenuVisible() isTearOffEnabled() +*/ +void QMenu::hideTearOffMenu() +{ + if (d_func()->tornPopup) + d_func()->tornPopup->close(); +} + + +/*! + Sets the currently highlighted action to \a act. +*/ +void QMenu::setActiveAction(QAction *act) +{ + Q_D(QMenu); + d->setCurrentAction(act, 0); + if (d->scroll) + d->scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollCenter); +} + + +/*! + Returns the currently highlighted action, or 0 if no + action is currently highlighted. +*/ +QAction *QMenu::activeAction() const +{ + return d_func()->currentAction; +} + +/*! + \since 4.2 + + Returns true if there are no visible actions inserted into the menu, false + otherwise. + + \sa QWidget::actions() +*/ + +bool QMenu::isEmpty() const +{ + bool ret = true; + for(int i = 0; ret && i < actions().count(); ++i) { + const QAction *action = actions().at(i); + if (!action->isSeparator() && action->isVisible()) { + ret = false; + } + } + return ret; +} + +/*! + Removes all the menu's actions. Actions owned by the menu and not + shown in any other widget are deleted. + + \sa removeAction() +*/ +void QMenu::clear() +{ + QList<QAction*> acts = actions(); + for(int i = 0; i < acts.size(); i++) { + removeAction(acts[i]); + if (acts[i]->parent() == this && acts[i]->d_func()->widgets.isEmpty()) + delete acts[i]; + } +} + +/*! + If a menu does not fit on the screen it lays itself out so that it + does fit. It is style dependent what layout means (for example, on + Windows it will use multiple columns). + + This functions returns the number of columns necessary. +*/ +int QMenu::columnCount() const +{ + return d_func()->ncols; +} + +/*! + Returns the item at \a pt; returns 0 if there is no item there. +*/ +QAction *QMenu::actionAt(const QPoint &pt) const +{ + if (QAction *ret = d_func()->actionAt(pt)) + return ret; + return 0; +} + +/*! + Returns the geometry of action \a act. +*/ +QRect QMenu::actionGeometry(QAction *act) const +{ + return d_func()->actionRect(act); +} + +/*! + \reimp +*/ +QSize QMenu::sizeHint() const +{ + Q_D(const QMenu); + ensurePolished(); + QMap<QAction*, QRect> actionRects; + QList<QAction*> actionList; + d->calcActionRects(actionRects, actionList); + + QSize s; + QStyleOption opt(0); + opt.rect = rect(); + opt.palette = palette(); + opt.state = QStyle::State_None; + for (QMap<QAction*, QRect>::const_iterator i = actionRects.constBegin(); + i != actionRects.constEnd(); ++i) { + if (i.value().bottom() > s.height()) + s.setHeight(i.value().y()+i.value().height()); + if (i.value().right() > s.width()) + s.setWidth(i.value().right()); + } + if (d->tearoff) + s.rheight() += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, this); + if (const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, this)) { + s.rwidth() += fw*2; + s.rheight() += fw*2; + } + // Note that the action rects calculated above already include + // the top and left margins, so we only need to add margins for + // the bottom and right. + s.rwidth() += style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this); + s.rheight() += style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, this); + + s += QSize(d->leftmargin + d->rightmargin, d->topmargin + d->bottommargin); + + return style()->sizeFromContents(QStyle::CT_Menu, &opt, + s.expandedTo(QApplication::globalStrut()), this); +} + +/*! + Displays the menu so that the action \a atAction will be at the + specified \e global position \a p. To translate a widget's local + coordinates into global coordinates, use QWidget::mapToGlobal(). + + When positioning a menu with exec() or popup(), bear in mind that + you cannot rely on the menu's current size(). For performance + reasons, the menu adapts its size only when necessary, so in many + cases, the size before and after the show is different. Instead, + use sizeHint() which calculates the proper size depending on the + menu's current contents. + + \sa QWidget::mapToGlobal(), exec() +*/ +void QMenu::popup(const QPoint &p, QAction *atAction) +{ + Q_D(QMenu); + if (d->scroll) { //reset scroll state from last popup + d->scroll->scrollOffset = 0; + d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; + } + d->tearoffHighlighted = 0; + d->motions = 0; + d->doChildEffects = true; + +#ifndef QT_NO_MENUBAR + // if this menu is part of a chain attached to a QMenuBar, set the + // _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type + QWidget* top = this; + while (QMenu* m = qobject_cast<QMenu *>(top)) + top = m->d_func()->causedPopup.widget; + setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(top) != 0); +#endif + + ensurePolished(); // Get the right font + emit aboutToShow(); + d->updateActions(); + QPoint pos = p; + QSize size = sizeHint(); + QRect screen = d->popupGeometry(QApplication::desktop()->screenNumber(p)); + const int desktopFrame = style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, this); + bool adjustToDesktop = !window()->testAttribute(Qt::WA_DontShowOnScreen); + if (d->ncols > 1) { + pos.setY(screen.top()+desktopFrame); + } else if (atAction) { + for(int i=0, above_height=0; i<(int)d->actionList.count(); i++) { + QAction *action = d->actionList.at(i); + if (action == atAction) { + int newY = pos.y()-above_height; + if (d->scroll && newY < desktopFrame) { + d->scroll->scrollFlags = d->scroll->scrollFlags + | QMenuPrivate::QMenuScroller::ScrollUp; + d->scroll->scrollOffset = newY; + newY = desktopFrame; + } + pos.setY(newY); + + if (d->scroll && d->scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone + && !style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, 0, this)) { + int below_height = above_height + d->scroll->scrollOffset; + for(int i2 = i; i2 < (int)d->actionList.count(); i2++) + below_height += d->actionRects.value(d->actionList.at(i2)).height(); + size.setHeight(below_height); + } + break; + } else { + above_height += d->actionRects.value(action).height(); + } + } + } + + QPoint mouse = QCursor::pos(); + d->mousePopupPos = mouse; + const bool snapToMouse = (QRect(p.x()-3, p.y()-3, 6, 6).contains(mouse)); + + if (adjustToDesktop) { + //handle popup falling "off screen" + if (qApp->layoutDirection() == Qt::RightToLeft) { + if(snapToMouse) //position flowing left from the mouse + pos.setX(mouse.x()-size.width()); + + if (pos.x() < screen.left()+desktopFrame) + pos.setX(qMax(p.x(), screen.left()+desktopFrame)); + if (pos.x()+size.width()-1 > screen.right()-desktopFrame) + pos.setX(qMax(p.x()-size.width(), screen.right()-desktopFrame-size.width()+1)); + } else { + if (pos.x()+size.width()-1 > screen.right()-desktopFrame) + pos.setX(qMin(p.x()+size.width(), screen.right()-desktopFrame-size.width()+1)); + if (pos.x() < screen.left()+desktopFrame) + pos.setX(qMax(p.x(), screen.left() + desktopFrame)); + } + if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) { + if(snapToMouse) + pos.setY(qMin(mouse.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1)); + else + pos.setY(qMax(p.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1)); + } else if (pos.y() < screen.top() + desktopFrame) { + pos.setY(screen.top() + desktopFrame); + } + + if (pos.y() < screen.top() + desktopFrame) + pos.setY(screen.top() + desktopFrame); + if (pos.y()+size.height()-1 > screen.bottom() - desktopFrame) { + if (d->scroll) { + d->scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown); + int y = qMax(screen.y(),pos.y()); + size.setHeight(screen.bottom()-(desktopFrame*2)-y); + } else { + // Too big for screen, bias to see bottom of menu (for some reason) + pos.setY(screen.bottom()-size.height()+1); + } + } + } + setGeometry(QRect(pos, size)); +#ifndef QT_NO_EFFECTS + int hGuess = qApp->layoutDirection() == Qt::RightToLeft ? QEffects::LeftScroll : QEffects::RightScroll; + int vGuess = QEffects::DownScroll; + if (qApp->layoutDirection() == Qt::RightToLeft) { + if ((snapToMouse && (pos.x() + size.width()/2 > mouse.x())) || + (qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width()/2 > d->causedPopup.widget->x())) + hGuess = QEffects::RightScroll; + } else { + if ((snapToMouse && (pos.x() + size.width()/2 < mouse.x())) || + (qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width()/2 < d->causedPopup.widget->x())) + hGuess = QEffects::LeftScroll; + } + +#ifndef QT_NO_MENUBAR + if ((snapToMouse && (pos.y() + size.height()/2 < mouse.y())) || + (qobject_cast<QMenuBar*>(d->causedPopup.widget) && + pos.y() + size.width()/2 < d->causedPopup.widget->mapToGlobal(d->causedPopup.widget->pos()).y())) + vGuess = QEffects::UpScroll; +#endif + if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) { + bool doChildEffects = true; +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget)) { + doChildEffects = mb->d_func()->doChildEffects; + mb->d_func()->doChildEffects = false; + } else +#endif + if (QMenu *m = qobject_cast<QMenu*>(d->causedPopup.widget)) { + doChildEffects = m->d_func()->doChildEffects; + m->d_func()->doChildEffects = false; + } + + if (doChildEffects) { + if (QApplication::isEffectEnabled(Qt::UI_FadeMenu)) + qFadeEffect(this); + else if (d->causedPopup.widget) + qScrollEffect(this, qobject_cast<QMenu*>(d->causedPopup.widget) ? hGuess : vGuess); + else + qScrollEffect(this, hGuess | vGuess); + } else { + // kill any running effect + qFadeEffect(0); + qScrollEffect(0); + + show(); + } + } else +#endif + { + show(); + } + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::PopupMenuStart); +#endif +} + +/*! + Executes this menu synchronously. + + This is equivalent to \c{exec(pos())}. + + This returns the triggered QAction in either the popup menu or one + of its submenus, or 0 if no item was triggered (normally because + the user pressed Esc). + + In most situations you'll want to specify the position yourself, + for example, the current mouse position: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 0 + or aligned to a widget: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 1 + or in reaction to a QMouseEvent *e: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 2 +*/ +QAction *QMenu::exec() +{ + return exec(pos()); +} + + +/*! + \overload + + Executes this menu synchronously. + + Pops up the menu so that the action \a action will be at the + specified \e global position \a p. To translate a widget's local + coordinates into global coordinates, use QWidget::mapToGlobal(). + + This returns the triggered QAction in either the popup menu or one + of its submenus, or 0 if no item was triggered (normally because + the user pressed Esc). + + Note that all signals are emitted as usual. If you connect a + QAction to a slot and call the menu's exec(), you get the result + both via the signal-slot connection and in the return value of + exec(). + + Common usage is to position the menu at the current mouse + position: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 3 + or aligned to a widget: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 4 + or in reaction to a QMouseEvent *e: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 5 + + When positioning a menu with exec() or popup(), bear in mind that + you cannot rely on the menu's current size(). For performance + reasons, the menu adapts its size only when necessary. So in many + cases, the size before and after the show is different. Instead, + use sizeHint() which calculates the proper size depending on the + menu's current contents. + + \sa popup(), QWidget::mapToGlobal() +*/ +QAction *QMenu::exec(const QPoint &p, QAction *action) +{ + Q_D(QMenu); + createWinId(); + QEventLoop eventLoop; + d->eventLoop = &eventLoop; + popup(p, action); + + QPointer<QObject> guard = this; + (void) eventLoop.exec(); + if (guard.isNull()) + return 0; + + action = d->syncAction; + d->syncAction = 0; + d->eventLoop = 0; + return action; +} + +/*! + \overload + + Executes a menu synchronously. + + The menu's actions are specified by the list of \a actions. The menu will + pop up so that the specified action, \a at, appears at global position \a + pos. If \a at is not specified then the menu appears at position \a + pos. \a parent is the menu's parent widget; specifying the parent will + provide context when \a pos alone is not enough to decide where the menu + should go (e.g., with multiple desktops or when the parent is embedded in + QGraphicsView). + + The function returns the triggered QAction in either the popup + menu or one of its submenus, or 0 if no item was triggered + (normally because the user pressed Esc). + + This is equivalent to: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 6 + + \sa popup(), QWidget::mapToGlobal() +*/ +QAction *QMenu::exec(QList<QAction*> actions, const QPoint &pos, QAction *at, QWidget *parent) +{ + QMenu menu(parent); + for(QList<QAction*>::ConstIterator it = actions.constBegin(); it != actions.constEnd(); ++it) + menu.addAction((*it)); + return menu.exec(pos, at); +} + +/*! + \overload + + Executes a menu synchronously. + + The menu's actions are specified by the list of \a actions. The menu + will pop up so that the specified action, \a at, appears at global + position \a pos. If \a at is not specified then the menu appears + at position \a pos. + + The function returns the triggered QAction in either the popup + menu or one of its submenus, or 0 if no item was triggered + (normally because the user pressed Esc). + + This is equivalent to: + \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 6 + + \sa popup(), QWidget::mapToGlobal() +*/ +QAction *QMenu::exec(QList<QAction*> actions, const QPoint &pos, QAction *at) +{ + // ### Qt 5: merge + return exec(actions, pos, at, 0); +} + +/*! + \reimp +*/ +void QMenu::hideEvent(QHideEvent *) +{ + Q_D(QMenu); + emit aboutToHide(); + if (d->eventLoop) + d->eventLoop->exit(); + d->setCurrentAction(0); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::PopupMenuEnd); +#endif +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget)) + mb->d_func()->setCurrentAction(0); +#endif + d->mouseDown = 0; + d->hasHadMouse = false; + d->causedPopup.widget = 0; + d->causedPopup.action = 0; +} + +/*! + \reimp +*/ +void QMenu::paintEvent(QPaintEvent *e) +{ + Q_D(QMenu); + QPainter p(this); + QRegion emptyArea = QRegion(rect()); + + QStyleOptionMenuItem menuOpt; + menuOpt.initFrom(this); + menuOpt.state = QStyle::State_None; + menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; + menuOpt.maxIconWidth = 0; + menuOpt.tabWidth = 0; + style()->drawPrimitive(QStyle::PE_PanelMenu, &menuOpt, &p, this); + + //draw the items that need updating.. + for (int i = 0; i < d->actionList.count(); ++i) { + QAction *action = d->actionList.at(i); + QRect adjustedActionRect = d->actionRect(action); + if (!e->rect().intersects(adjustedActionRect) + || d->widgetItems.value(action)) + continue; + //set the clip region to be extra safe (and adjust for the scrollers) + QRegion adjustedActionReg(adjustedActionRect); + emptyArea -= adjustedActionReg; + p.setClipRegion(adjustedActionReg); + + QStyleOptionMenuItem opt; + initStyleOption(&opt, action); + opt.rect = adjustedActionRect; + style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this); + } + + const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, this); + //draw the scroller regions.. + if (d->scroll) { + const int scrollerHeight = style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, this); + menuOpt.menuItemType = QStyleOptionMenuItem::Scroller; + menuOpt.state |= QStyle::State_Enabled; + if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) { + menuOpt.rect.setRect(fw, fw, width() - (fw * 2), scrollerHeight); + emptyArea -= QRegion(menuOpt.rect); + p.setClipRect(menuOpt.rect); + style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this); + } + if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) { + menuOpt.rect.setRect(fw, height() - scrollerHeight - fw, width() - (fw * 2), + scrollerHeight); + emptyArea -= QRegion(menuOpt.rect); + menuOpt.state |= QStyle::State_DownArrow; + p.setClipRect(menuOpt.rect); + style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this); + } + } + //paint the tear off.. + if (d->tearoff) { + menuOpt.menuItemType = QStyleOptionMenuItem::TearOff; + menuOpt.rect.setRect(fw, fw, width() - (fw * 2), + style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this)); + if (d->scroll && d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) + menuOpt.rect.translate(0, style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, this)); + emptyArea -= QRegion(menuOpt.rect); + p.setClipRect(menuOpt.rect); + menuOpt.state = QStyle::State_None; + if (d->tearoffHighlighted) + menuOpt.state |= QStyle::State_Selected; + style()->drawControl(QStyle::CE_MenuTearoff, &menuOpt, &p, this); + } + //draw border + if (fw) { + QRegion borderReg; + borderReg += QRect(0, 0, fw, height()); //left + borderReg += QRect(width()-fw, 0, fw, height()); //right + borderReg += QRect(0, 0, width(), fw); //top + borderReg += QRect(0, height()-fw, width(), fw); //bottom + p.setClipRegion(borderReg); + emptyArea -= borderReg; + QStyleOptionFrame frame; + frame.rect = rect(); + frame.palette = palette(); + frame.state = QStyle::State_None; + frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth); + frame.midLineWidth = 0; + style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, &p, this); + } + + //finally the rest of the space + p.setClipRegion(emptyArea); + menuOpt.state = QStyle::State_None; + menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea; + menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; + menuOpt.rect = rect(); + menuOpt.menuRect = rect(); + style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this); +} + +#ifndef QT_NO_WHEELEVENT +/*! + \reimp +*/ +void QMenu::wheelEvent(QWheelEvent *e) +{ + Q_D(QMenu); + if (d->scroll && rect().contains(e->pos())) + d->scrollMenu(e->delta() > 0 ? + QMenuPrivate::QMenuScroller::ScrollUp : QMenuPrivate::QMenuScroller::ScrollDown); +} +#endif + +/*! + \reimp +*/ +void QMenu::mousePressEvent(QMouseEvent *e) +{ + Q_D(QMenu); + if (d->aboutToHide || d->mouseEventTaken(e)) + return; + if (!rect().contains(e->pos())) { + if (d->noReplayFor + && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos())) + setAttribute(Qt::WA_NoMouseReplay); + if (d->eventLoop) // synchronous operation + d->syncAction = 0; + d->hideUpToMenuBar(); + return; + } + d->mouseDown = this; + + QAction *action = d->actionAt(e->pos()); + d->setCurrentAction(action, 20); + update(); +} + +/*! + \reimp +*/ +void QMenu::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QMenu); + if (d->aboutToHide || d->mouseEventTaken(e)) + return; + if(d->mouseDown != this) { + d->mouseDown = 0; + return; + } + + d->mouseDown = 0; + d->setSyncAction(); + QAction *action = d->actionAt(e->pos()); + + if (action && action == d->currentAction) { + if (action->menu()) + action->menu()->d_func()->setFirstActionActive(); + else { +#if defined(Q_WS_WIN) && !defined(QT_NO_MENUBAR) + //On Windows only context menus can be activated with the right button + bool isContextMenu = true; + const QWidget *cause = d->causedPopup.widget; + while (cause) { + //if the popup was caused by either QMenuBar or a QToolButton, it is not a context menu + if (qobject_cast<const QMenuBar *>(cause) || qobject_cast<const QToolButton *>(cause)) { + isContextMenu = false; + break; + } else if (const QMenu *menu = qobject_cast<const QMenu *>(cause)) { + cause = menu->d_func()->causedPopup.widget; + } else { + break; + } + } + if (e->button() == Qt::LeftButton || (e->button() == Qt::RightButton && isContextMenu)) +#endif + d->activateAction(action, QAction::Trigger); + } + } else if (d->hasMouseMoved(e->globalPos())) { + d->hideUpToMenuBar(); + } +} + +/*! + \reimp +*/ +void QMenu::changeEvent(QEvent *e) +{ + Q_D(QMenu); + if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange || + e->type() == QEvent::LayoutDirectionChange) { + d->itemsDirty = 1; + setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, this)); + if (isVisible()) + resize(sizeHint()); + if (!style()->styleHint(QStyle::SH_Menu_Scrollable, 0, this)) { + delete d->scroll; + d->scroll = 0; + } else if (!d->scroll) { + d->scroll = new QMenuPrivate::QMenuScroller; + d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone; + } + } else if (e->type() == QEvent::EnabledChange) { + if (d->tornPopup) // torn-off menu + d->tornPopup->setEnabled(isEnabled()); + d->menuAction->setEnabled(isEnabled()); +#ifdef Q_WS_MAC + if (d->mac_menu) + d->setMacMenuEnabled(isEnabled()); +#endif + } + QWidget::changeEvent(e); +} + + +/*! + \reimp +*/ +bool +QMenu::event(QEvent *e) +{ + Q_D(QMenu); + switch (e->type()) { + case QEvent::ShortcutOverride: { + QKeyEvent *kev = static_cast<QKeyEvent*>(e); + if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down + || kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right + || kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return + || kev->key() == Qt::Key_Escape) { + e->accept(); + return true; + } + } + break; + case QEvent::KeyPress: { + QKeyEvent *ke = (QKeyEvent*)e; + if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { + keyPressEvent(ke); + return true; + } + } break; + case QEvent::ContextMenu: + if(QMenuPrivate::menuDelayTimer.isActive()) { + QMenuPrivate::menuDelayTimer.stop(); + internalDelayedPopup(); + } + break; + case QEvent::Resize: { + QStyleHintReturnMask menuMask; + QStyleOption option; + option.initFrom(this); + if (style()->styleHint(QStyle::SH_Menu_Mask, &option, this, &menuMask)) { + setMask(menuMask.region); + } + d->itemsDirty = 1; + d->updateActions(); + break; } + case QEvent::Show: + d->mouseDown = 0; + d->updateActions(); + if (d->currentAction) + d->popupAction(d->currentAction, 0, false); + break; +#ifndef QT_NO_WHATSTHIS + case QEvent::QueryWhatsThis: + e->setAccepted(d->whatsThis.size()); + if (QAction *action = d->actionAt(static_cast<QHelpEvent*>(e)->pos())) { + if (action->whatsThis().size() || action->menu()) + e->accept(); + } + return true; +#endif + default: + break; + } + return QWidget::event(e); +} + +/*! + \reimp +*/ +bool QMenu::focusNextPrevChild(bool next) +{ + setFocus(); + QKeyEvent ev(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); + keyPressEvent(&ev); + return true; +} + +/*! + \reimp +*/ +void QMenu::keyPressEvent(QKeyEvent *e) +{ + Q_D(QMenu); + int key = e->key(); + if (isRightToLeft()) { // in reverse mode open/close key for submenues are reversed + if (key == Qt::Key_Left) + key = Qt::Key_Right; + else if (key == Qt::Key_Right) + key = Qt::Key_Left; + } +#ifndef Q_WS_MAC + if (key == Qt::Key_Tab) //means down + key = Qt::Key_Down; + if (key == Qt::Key_Backtab) //means up + key = Qt::Key_Up; +#endif + + bool key_consumed = false; + switch(key) { + case Qt::Key_Home: + key_consumed = true; + if (d->scroll) + d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollTop, true); + break; + case Qt::Key_End: + key_consumed = true; + if (d->scroll) + d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollBottom, true); + break; + case Qt::Key_PageUp: + key_consumed = true; + if (d->currentAction && d->scroll) { + if(d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) + d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollUp, true, true); + else + d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollTop, true); + } + break; + case Qt::Key_PageDown: + key_consumed = true; + if (d->currentAction && d->scroll) { + if(d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) + d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollDown, true, true); + else + d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollBottom, true); + } + break; + case Qt::Key_Up: + case Qt::Key_Down: { + key_consumed = true; + QAction *nextAction = 0; + QMenuPrivate::QMenuScroller::ScrollLocation scroll_loc = QMenuPrivate::QMenuScroller::ScrollStay; + if (!d->currentAction) { + if(key == Qt::Key_Down) { + for(int i = 0; i < d->actionList.size(); ++i) { + QAction *act = d->actionList.at(i); + if (!act->isSeparator() && + (style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this) + || act->isEnabled())) { + nextAction = act; + break; + } + } + } else { + for(int i = d->actionList.size()-1; i >= 0; --i) { + QAction *act = d->actionList.at(i); + if (!act->isSeparator() && + (style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this) + || act->isEnabled())) { + nextAction = act; + break; + } + } + } + } else { + for(int i=0, y=0; !nextAction && i < (int)d->actionList.count(); i++) { + QAction *act = d->actionList.at(i); + if (act == d->currentAction) { + if (key == Qt::Key_Up) { + for(int next_i = i-1; true; next_i--) { + if (next_i == -1) { + if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this)) + break; + if (d->scroll) + scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom; + next_i = d->actionList.count()-1; + } + QAction *next = d->actionList.at(next_i); + if (next == d->currentAction) + break; + if (next->isSeparator() || + (!next->isEnabled() && + !style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this))) + continue; + nextAction = next; + if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)) { + int topVisible = style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, this); + if (d->tearoff) + topVisible += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this); + if (((y + d->scroll->scrollOffset) - topVisible) <= d->actionRects.value(nextAction).height()) + scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop; + } + break; + } + if (!nextAction && d->tearoff) + d->tearoffHighlighted = 1; + } else { + y += d->actionRects.value(act).height(); + for(int next_i = i+1; true; next_i++) { + if (next_i == d->actionList.count()) { + if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this)) + break; + if (d->scroll) + scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop; + next_i = 0; + } + QAction *next = d->actionList.at(next_i); + if (next == d->currentAction) + break; + if (next->isSeparator() || + (!next->isEnabled() && + !style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this))) + continue; + nextAction = next; + if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)) { + const int scrollerHeight = style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, this); + int bottomVisible = height()-scrollerHeight; + if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) + bottomVisible -= scrollerHeight; + if (d->tearoff) + bottomVisible -= style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this); + if ((y + d->scroll->scrollOffset + d->actionRects.value(nextAction).height()) > bottomVisible) + scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom; + } + break; + } + } + break; + } + y += d->actionRects.value(act).height(); + } + } + if (nextAction) { + if (d->scroll && scroll_loc != QMenuPrivate::QMenuScroller::ScrollStay) { + if (d->scroll->scrollTimer) + d->scroll->scrollTimer->stop(); + d->scrollMenu(nextAction, scroll_loc); + } + d->setCurrentAction(nextAction, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard); + } + break; } + + case Qt::Key_Right: + if (d->currentAction && d->currentAction->isEnabled() && d->currentAction->menu()) { + d->popupAction(d->currentAction, 0, true); + key_consumed = true; + break; + } + //FALL THROUGH + case Qt::Key_Left: { + if (d->currentAction && !d->scroll) { + QAction *nextAction = 0; + if (key == Qt::Key_Left) { + QRect actionR = d->actionRect(d->currentAction); + for(int x = actionR.left()-1; !nextAction && x >= 0; x--) + nextAction = d->actionAt(QPoint(x, actionR.center().y())); + } else { + QRect actionR = d->actionRect(d->currentAction); + for(int x = actionR.right()+1; !nextAction && x < width(); x++) + nextAction = d->actionAt(QPoint(x, actionR.center().y())); + } + if (nextAction) { + d->setCurrentAction(nextAction, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard); + key_consumed = true; + } + } + if (!key_consumed && key == Qt::Key_Left && qobject_cast<QMenu*>(d->causedPopup.widget)) { + QPointer<QWidget> caused = d->causedPopup.widget; + d->hideMenu(this); + if (caused) + caused->setFocus(); + key_consumed = true; + } + break; } + + case Qt::Key_Alt: + if (d->tornoff) + break; + + key_consumed = true; + if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this)) + { + d->hideMenu(this); +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(qApp->focusWidget())) { + mb->d_func()->setKeyboardMode(false); + } +#endif + } + break; + + case Qt::Key_Escape: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Back: +#endif + key_consumed = true; + if (d->tornoff) { + close(); + return; + } + { + QPointer<QWidget> caused = d->causedPopup.widget; + d->hideMenu(this); // hide after getting causedPopup +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) { + mb->d_func()->setCurrentAction(d->menuAction); + mb->d_func()->setKeyboardMode(true); + } +#endif + } + break; + + case Qt::Key_Space: + if (!style()->styleHint(QStyle::SH_Menu_SpaceActivatesItem, 0, this)) + break; + // for motif, fall through +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: +#endif + case Qt::Key_Return: + case Qt::Key_Enter: { + if (!d->currentAction) { + d->setFirstActionActive(); + key_consumed = true; + break; + } + + d->setSyncAction(); + + if (d->currentAction->menu()) + d->popupAction(d->currentAction, 0, true); + else + d->activateAction(d->currentAction, QAction::Trigger); + key_consumed = true; + break; } + +#ifndef QT_NO_WHATSTHIS + case Qt::Key_F1: + if (!d->currentAction || d->currentAction->whatsThis().isNull()) + break; + QWhatsThis::enterWhatsThisMode(); + d->activateAction(d->currentAction, QAction::Trigger); + return; +#endif + default: + key_consumed = false; + } + + if (!key_consumed) { // send to menu bar + if ((!e->modifiers() || e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ShiftModifier) && + e->text().length()==1) { + bool activateAction = false; + QAction *nextAction = 0; + if (style()->styleHint(QStyle::SH_Menu_KeyboardSearch, 0, this) && !e->modifiers()) { + int best_match_count = 0; + d->searchBufferTimer.start(2000, this); + d->searchBuffer += e->text(); + for(int i = 0; i < d->actionList.size(); ++i) { + int match_count = 0; + register QAction *act = d->actionList.at(i); + const QString act_text = act->text(); + for(int c = 0; c < d->searchBuffer.size(); ++c) { + if(act_text.indexOf(d->searchBuffer.at(c), 0, Qt::CaseInsensitive) != -1) + ++match_count; + } + if(match_count > best_match_count) { + best_match_count = match_count; + nextAction = act; + } + } + } +#ifndef QT_NO_SHORTCUT + else { + int clashCount = 0; + QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0; + QChar c = e->text().at(0).toUpper(); + for(int i = 0; i < d->actionList.size(); ++i) { + register QAction *act = d->actionList.at(i); + QKeySequence sequence = QKeySequence::mnemonic(act->text()); + int key = sequence[0] & 0xffff; + if (key == c.unicode()) { + clashCount++; + if (!first) + first = act; + if (act == d->currentAction) + currentSelected = act; + else if (!firstAfterCurrent && currentSelected) + firstAfterCurrent = act; + } + } + if (clashCount == 1) + activateAction = true; + if (clashCount >= 1) { + if (clashCount == 1 || !currentSelected || !firstAfterCurrent) + nextAction = first; + else + nextAction = firstAfterCurrent; + } + } +#endif + if (nextAction) { + key_consumed = true; + if(d->scroll) + d->scrollMenu(nextAction, QMenuPrivate::QMenuScroller::ScrollCenter, false); + d->setCurrentAction(nextAction, 20, QMenuPrivate::SelectedFromElsewhere, true); + if (!nextAction->menu() && activateAction) { + d->setSyncAction(); + d->activateAction(nextAction, QAction::Trigger); + } + } + } + if (!key_consumed) { + if (QWidget *caused = d->causedPopup.widget) { + while(QMenu *m = qobject_cast<QMenu*>(caused)) + caused = m->d_func()->causedPopup.widget; +#ifndef QT_NO_MENUBAR + if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) { + QAction *oldAct = mb->d_func()->currentAction; + QApplication::sendEvent(mb, e); + if (mb->d_func()->currentAction != oldAct) + key_consumed = true; + } +#endif + } + } + +#ifdef Q_OS_WIN32 + if (key_consumed && (e->key() == Qt::Key_Control || e->key() == Qt::Key_Shift || e->key() == Qt::Key_Meta)) + qApp->beep(); +#endif // Q_OS_WIN32 + } + if (key_consumed) + e->accept(); + else + e->ignore(); +} + +/*! + \reimp +*/ +void QMenu::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QMenu); + if (!isVisible() || d->aboutToHide || d->mouseEventTaken(e)) + return; + d->motions++; + if (d->motions == 0) // ignore first mouse move event (see enterEvent()) + return; + d->hasHadMouse |= rect().contains(e->pos()); + + QAction *action = d->actionAt(e->pos()); + if (!action) { + if (d->hasHadMouse && !rect().contains(e->pos())) + d->setCurrentAction(0); + return; + } else if(e->buttons() & (Qt::LeftButton | Qt::RightButton)) { + d->mouseDown = this; + } + if (d->sloppyRegion.contains(e->pos())) { + d->sloppyAction = action; + QMenuPrivate::sloppyDelayTimer.start(style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)*6, this); + } else { + d->setCurrentAction(action, style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)); + } +} + +/*! + \reimp +*/ +void QMenu::enterEvent(QEvent *) +{ + d_func()->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent() +} + +/*! + \reimp +*/ +void QMenu::leaveEvent(QEvent *) +{ + Q_D(QMenu); + d->sloppyAction = 0; + if (!d->sloppyRegion.isEmpty()) + d->sloppyRegion = QRegion(); +} + +/*! + \reimp +*/ +void +QMenu::timerEvent(QTimerEvent *e) +{ + Q_D(QMenu); + if (d->scroll && d->scroll->scrollTimer && d->scroll->scrollTimer->timerId() == e->timerId()) { + d->scrollMenu((QMenuPrivate::QMenuScroller::ScrollDirection)d->scroll->scrollDirection); + if (d->scroll->scrollFlags == QMenuPrivate::QMenuScroller::ScrollNone) + d->scroll->scrollTimer->stop(); + } else if(QMenuPrivate::menuDelayTimer.timerId() == e->timerId()) { + QMenuPrivate::menuDelayTimer.stop(); + internalDelayedPopup(); + } else if(QMenuPrivate::sloppyDelayTimer.timerId() == e->timerId()) { + QMenuPrivate::sloppyDelayTimer.stop(); + internalSetSloppyAction(); + } else if(d->searchBufferTimer.timerId() == e->timerId()) { + d->searchBuffer.clear(); + } +} + +/*! + \reimp +*/ +void QMenu::actionEvent(QActionEvent *e) +{ + Q_D(QMenu); + d->itemsDirty = 1; + setAttribute(Qt::WA_Resized, false); + if (d->tornPopup) + d->tornPopup->syncWithMenu(this, e); + if (e->type() == QEvent::ActionAdded) { + if(!d->tornoff) { + connect(e->action(), SIGNAL(triggered()), this, SLOT(_q_actionTriggered())); + connect(e->action(), SIGNAL(hovered()), this, SLOT(_q_actionHovered())); + } + + if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) { + QWidget *widget = wa->requestWidget(this); + if (widget) + d->widgetItems.insert(wa, widget); + } + } else if (e->type() == QEvent::ActionRemoved) { + d->actionRects.clear(); + d->actionList.clear(); + e->action()->disconnect(this); + if (e->action() == d->currentAction) + d->currentAction = 0; + if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) { + QWidget *widget = d->widgetItems.take(wa); + if (widget) + wa->releaseWidget(widget); + } else { + // If this is called from the QAction destructor, the + // previous call to qobject_cast will fail because the + // QWidgetAction has been destroyed already. We need to + // remove it from the hash anyway or it might crash later + // the widget itself has been already destroyed in + // ~QWidgetAction + d->widgetItems.remove(e->action()); + } + } + +#ifdef Q_WS_MAC + if (d->mac_menu) { + if (e->type() == QEvent::ActionAdded) + d->mac_menu->addAction(e->action(), d->mac_menu->findAction(e->before()), d); + else if (e->type() == QEvent::ActionRemoved) + d->mac_menu->removeAction(e->action()); + else if (e->type() == QEvent::ActionChanged) + d->mac_menu->syncAction(e->action()); + } +#endif + +#if defined(Q_OS_WINCE) && !defined(QT_NO_MENUBAR) + if (!d->wce_menu) + d->wce_menu = new QMenuPrivate::QWceMenuPrivate; + if (e->type() == QEvent::ActionAdded) + d->wce_menu->addAction(e->action(), d->wce_menu->findAction(e->before())); + else if (e->type() == QEvent::ActionRemoved) + d->wce_menu->removeAction(e->action()); + else if (e->type() == QEvent::ActionChanged) + d->wce_menu->syncAction(e->action()); +#endif + + if (isVisible()) { + d->updateActions(); + resize(sizeHint()); + update(); + } +} + +/*! + \internal +*/ +void QMenu::internalSetSloppyAction() +{ + if (d_func()->sloppyAction) + d_func()->setCurrentAction(d_func()->sloppyAction, 0); +} + +/*! + \internal +*/ +void QMenu::internalDelayedPopup() +{ + Q_D(QMenu); + + //hide the current item + if (QMenu *menu = d->activeMenu) { + d->activeMenu = 0; + d->hideMenu(menu); + } + + if (!d->currentAction || !d->currentAction->isEnabled() || !d->currentAction->menu() || + !d->currentAction->menu()->isEnabled() || d->currentAction->menu()->isVisible()) + return; + + //setup + d->activeMenu = d->currentAction->menu(); + d->activeMenu->d_func()->causedPopup.widget = this; + d->activeMenu->d_func()->causedPopup.action = d->currentAction; + + int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, 0, this); + const QRect actionRect(d->actionRect(d->currentAction)); + const QSize menuSize(d->activeMenu->sizeHint()); + const QPoint rightPos(mapToGlobal(QPoint(rect().right() + subMenuOffset + 1, actionRect.top()))); + const QPoint leftPos(mapToGlobal(QPoint(rect().left() - subMenuOffset - menuSize.width(), actionRect.top()))); + + QPoint pos(rightPos); + QMenu *caused = qobject_cast<QMenu*>(d->activeMenu->d_func()->causedPopup.widget); + + const QRect availGeometry(d->popupGeometry(QApplication::desktop()->screenNumber(caused))); + if (isRightToLeft()) { + pos = leftPos; + if ((caused && caused->x() < x()) || pos.x() < availGeometry.left()) { + if(rightPos.x() + menuSize.width() < availGeometry.right()) + pos = rightPos; + else + pos.rx() = availGeometry.left(); + } + } else { + if ((caused && caused->x() > x()) || pos.x() + menuSize.width() > availGeometry.right()) { + if(leftPos.x() < availGeometry.left()) + pos.rx() = availGeometry.right() - menuSize.width(); + else + pos = leftPos; + } + } + + //calc sloppy focus buffer + if (style()->styleHint(QStyle::SH_Menu_SloppySubMenus, 0, this)) { + QPoint cur = QCursor::pos(); + if (actionRect.contains(mapFromGlobal(cur))) { + QPoint pts[4]; + pts[0] = QPoint(cur.x(), cur.y() - 2); + pts[3] = QPoint(cur.x(), cur.y() + 2); + if (pos.x() >= cur.x()) { + pts[1] = QPoint(geometry().right(), pos.y()); + pts[2] = QPoint(geometry().right(), pos.y() + menuSize.height()); + } else { + pts[1] = QPoint(pos.x() + menuSize.width(), pos.y()); + pts[2] = QPoint(pos.x() + menuSize.width(), pos.y() + menuSize.height()); + } + QPolygon points(4); + for(int i = 0; i < 4; i++) + points.setPoint(i, mapFromGlobal(pts[i])); + d->sloppyRegion = QRegion(points); + } + } + + //do the popup + d->activeMenu->popup(pos); +} + +/*! + \fn void QMenu::addAction(QAction *action) + \overload + + Appends the action \a action to the menu's list of actions. + + \sa QMenuBar::addAction(), QWidget::addAction() +*/ + +/*! + \fn void QMenu::aboutToHide() + \since 4.2 + + This signal is emitted just before the menu is hidden from the user. + + \sa aboutToShow(), hide() +*/ + +/*! + \fn void QMenu::aboutToShow() + + This signal is emitted just before the menu is shown to the user. + + \sa aboutToHide(), show() +*/ + +/*! + \fn void QMenu::triggered(QAction *action) + + This signal is emitted when an action in this menu is triggered. + + \a action is the action that caused the signal to be emitted. + + Normally, you connect each menu action's \l{QAction::}{triggered()} signal + to its own custom slot, but sometimes you will want to connect several + actions to a single slot, for example, when you have a group of closely + related actions, such as "left justify", "center", "right justify". + + \note This signal is emitted for the main parent menu in a hierarchy. + Hence, only the parent menu needs to be connected to a slot; sub-menus need + not be connected. + + \sa hovered(), QAction::triggered() +*/ + +/*! + \fn void QMenu::hovered(QAction *action) + + This signal is emitted when a menu action is highlighted; \a action + is the action that caused the signal to be emitted. + + Often this is used to update status information. + + \sa triggered(), QAction::hovered() +*/ + + +/*!\internal +*/ +void QMenu::setNoReplayFor(QWidget *noReplayFor) +{ +#ifdef Q_WS_WIN + d_func()->noReplayFor = noReplayFor; +#else + Q_UNUSED(noReplayFor); +#endif +} + +/*! + \property QMenu::separatorsCollapsible + \since 4.2 + + \brief whether consecutive separators should be collapsed + + This property specifies whether consecutive separators in the menu + should be visually collapsed to a single one. Separators at the + beginning or the end of the menu are also hidden. + + By default, this property is true. +*/ +bool QMenu::separatorsCollapsible() const +{ + Q_D(const QMenu); + return d->collapsibleSeparators; +} + +void QMenu::setSeparatorsCollapsible(bool collapse) +{ + Q_D(QMenu); + d->collapsibleSeparators = collapse; + d->itemsDirty = 1; + if (isVisible()) { + d->updateActions(); + update(); + } +} + +#ifdef QT3_SUPPORT + +int QMenu::insertAny(const QIcon *icon, const QString *text, const QObject *receiver, const char *member, + const QKeySequence *shortcut, const QMenu *popup, int id, int index) +{ + QAction *act = popup ? popup->menuAction() : new QAction(this); + if (id != -1) + static_cast<QMenuItem*>(act)->setId(id); + if (icon) + act->setIcon(*icon); + if (text) + act->setText(*text); + if (shortcut) + act->setShortcut(*shortcut); + if (receiver && member) + QObject::connect(act, SIGNAL(activated(int)), receiver, member); + if (index == -1 || index >= actions().count()) + addAction(act); + else + insertAction(actions().value(index), act); + return findIdForAction(act); +} + +/*! + Use insertAction() or one of the addAction() overloads instead. +*/ +int QMenu::insertItem(QMenuItem *item, int id, int index) +{ + if (index == -1 || index >= actions().count()) + addAction(item); + else + insertAction(actions().value(index), item); + if (id > -1) + item->d_func()->id = id; + return findIdForAction(item); +} + +/*! + Use the insertSeparator() overload that takes a QAction * + parameter instead. +*/ +int QMenu::insertSeparator(int index) +{ + QAction *act = new QAction(this); + act->setSeparator(true); + if (index == -1 || index >= actions().count()) + addAction(act); + else + insertAction(actions().value(index), act); + return findIdForAction(act); +} + +QAction *QMenu::findActionForId(int id) const +{ + QList<QAction *> list = actions(); + for (int i = 0; i < list.size(); ++i) { + QAction *act = list.at(i); + if (findIdForAction(act)== id) + return act; + } + return 0; +} + +/*! + Use QAction and actions() instead. +*/ +QMenuItem *QMenu::findPopup( QMenu *popup, int *index ) +{ + QList<QAction *> list = actions(); + for (int i = 0; i < list.size(); ++i) { + QAction *act = list.at(i); + if (act->menu() == popup) { + QMenuItem *item = static_cast<QMenuItem *>(act); + if (index) + *index = act->d_func()->id; + return item; + } + } + return 0; +} + + +/*! + Use QAction::setData() instead. +*/ +bool QMenu::setItemParameter(int id, int param) +{ + if (QAction *act = findActionForId(id)) { + act->d_func()->param = param; + return true; + } + return false; +} + +/*! + Use QAction::data() instead. +*/ +int QMenu::itemParameter(int id) const +{ + if (QAction *act = findActionForId(id)) + return act->d_func()->param; + return id; +} + +/*! + Use actions instead. +*/ +void QMenu::setId(int index, int id) +{ + if(QAction *act = actions().value(index)) + act->d_func()->id = id; +} + +/*! + Use style()->pixelMetric(QStyle::PM_MenuPanelWidth, this) instead. +*/ +int QMenu::frameWidth() const +{ + return style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, this); +} + +int QMenu::findIdForAction(QAction *act) const +{ + if (!act) + return -1; + return act->d_func()->id; +} +#endif // QT3_SUPPORT + +/*! + \fn uint QMenu::count() const + + Use actions().count() instead. +*/ + +/*! + \fn int QMenu::insertItem(const QString &text, const QObject *receiver, const char* member, const QKeySequence& shortcut, int id, int index) + + Use insertAction() or one of the addAction() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QIcon& icon, const QString &text, const QObject *receiver, const char* member, const QKeySequence& shortcut, int id, int index) + + Use insertAction() or one of the addAction() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QPixmap &pixmap, const QObject *receiver, const char* member, const QKeySequence& shortcut, int id, int index) + + Use insertAction() or one of the addAction() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QString &text, int id, int index) + + Use insertAction() or one of the addAction() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QIcon& icon, const QString &text, int id, int index) + + Use insertAction() or one of the addAction() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QString &text, QMenu *popup, int id, int index) + + Use insertMenu() or one of the addMenu() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QIcon& icon, const QString &text, QMenu *popup, int id, int index) + + Use insertMenu() or one of the addMenu() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QPixmap &pixmap, int id, int index) + + Use insertAction() or one of the addAction() overloads instead. +*/ + +/*! + \fn int QMenu::insertItem(const QPixmap &pixmap, QMenu *popup, int id, int index) + + Use insertMenu() or one of the addMenu() overloads instead. +*/ + +/*! + \fn void QMenu::removeItem(int id) + + Use removeAction() instead. +*/ + +/*! + \fn void QMenu::removeItemAt(int index) + + Use removeAction() instead. +*/ + +/*! + \fn QKeySequence QMenu::accel(int id) const + + Use shortcut() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::setAccel(const QKeySequence& key, int id) + + Use setShortcut() on the relevant QAction instead. +*/ + +/*! + \fn QIcon QMenu::iconSet(int id) const + + Use icon() on the relevant QAction instead. +*/ + +/*! + \fn QString QMenu::text(int id) const + + Use text() on the relevant QAction instead. +*/ + +/*! + \fn QPixmap QMenu::pixmap(int id) const + + Use QPixmap(icon()) on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::setWhatsThis(int id, const QString &w) + + Use setWhatsThis() on the relevant QAction instead. +*/ + +/*! + \fn QString QMenu::whatsThis(int id) const + + Use whatsThis() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::changeItem(int id, const QString &text) + + Use setText() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::changeItem(int id, const QPixmap &pixmap) + + Use setText() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::changeItem(int id, const QIcon &icon, const QString &text) + + Use setIcon() and setText() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenu::isItemActive(int id) const + + Use activeAction() instead. +*/ + +/*! + \fn bool QMenu::isItemEnabled(int id) const + + Use isEnabled() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::setItemEnabled(int id, bool enable) + + Use setEnabled() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenu::isItemChecked(int id) const + + Use isChecked() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::setItemChecked(int id, bool check) + + Use setChecked() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenu::isItemVisible(int id) const + + Use isVisible() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::setItemVisible(int id, bool visible) + + Use setVisible() on the relevant QAction instead. +*/ + +/*! + \fn QRect QMenu::itemGeometry(int index) + + Use actionGeometry() on the relevant QAction instead. +*/ + +/*! + \fn QFont QMenu::itemFont(int id) const + + Use font() on the relevant QAction instead. +*/ + +/*! + \fn void QMenu::setItemFont(int id, const QFont &font) + + Use setFont() on the relevant QAction instead. +*/ + +/*! + \fn int QMenu::indexOf(int id) const + + Use actions().indexOf(action) on the relevant QAction instead. +*/ + +/*! + \fn int QMenu::idAt(int index) const + + Use actions instead. +*/ + +/*! + \fn void QMenu::activateItemAt(int index) + + Use activate() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenu::connectItem(int id, const QObject *receiver, const char* member) + + Use connect() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenu::disconnectItem(int id,const QObject *receiver, const char* member) + Use disconnect() on the relevant QAction instead. + +*/ + +/*! + \fn QMenuItem *QMenu::findItem(int id) const + + Use actions instead. +*/ + +/*! + \fn void QMenu::popup(const QPoint & pos, int indexAtPoint) + + Use popup() on the relevant QAction instead. +*/ + +/*! + \fn int QMenu::insertTearOffHandle(int a, int b) + + Use setTearOffEnabled() instead. +*/ + +/*! + \fn int QMenu::itemAtPos(const QPoint &p, bool ignoreSeparator) + + Use actions instead. +*/ + +/*! + \fn int QMenu::columns() const + + Use columnCount() instead. +*/ + +/*! + \fn int QMenu::itemHeight(int index) + + Use actionGeometry(actions().value(index)).height() instead. +*/ + +/*! + \fn int QMenu::itemHeight(QMenuItem *mi) + + Use actionGeometry() instead. +*/ + +/*! + \fn void QMenu::activated(int itemId); + + Use triggered() instead. +*/ + +/*! + \fn void QMenu::highlighted(int itemId); + + Use hovered() instead. +*/ + +/*! + \fn void QMenu::setCheckable(bool checkable) + + Not necessary anymore. The \a checkable parameter is ignored. +*/ + +/*! + \fn bool QMenu::isCheckable() const + + Not necessary anymore. Always returns true. +*/ + +/*! + \fn void QMenu::setActiveItem(int id) + + Use setActiveAction() instead. +*/ + +QT_END_NAMESPACE + +// for private slots +#include "moc_qmenu.cpp" +#include "qmenu.moc" + +#endif // QT_NO_MENU diff --git a/src/gui/widgets/qmenu.h b/src/gui/widgets/qmenu.h new file mode 100644 index 0000000..867baee --- /dev/null +++ b/src/gui/widgets/qmenu.h @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMENU_H +#define QMENU_H + +#include <QtGui/qwidget.h> +#include <QtCore/qstring.h> +#include <QtGui/qicon.h> +#include <QtGui/qaction.h> + +#ifdef QT3_SUPPORT +#include <QtGui/qpixmap.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MENU + +class QMenuPrivate; +class QStyleOptionMenuItem; +#ifdef QT3_SUPPORT +class QMenuItem; +#endif + +class Q_GUI_EXPORT QMenu : public QWidget +{ +private: + Q_OBJECT + Q_DECLARE_PRIVATE(QMenu) + + Q_PROPERTY(bool tearOffEnabled READ isTearOffEnabled WRITE setTearOffEnabled) + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) + Q_PROPERTY(bool separatorsCollapsible READ separatorsCollapsible WRITE setSeparatorsCollapsible) + +public: + explicit QMenu(QWidget *parent = 0); + explicit QMenu(const QString &title, QWidget *parent = 0); + ~QMenu(); + +#ifdef Q_NO_USING_KEYWORD + inline void addAction(QAction *action) { QWidget::addAction(action); } +#else + using QWidget::addAction; +#endif + QAction *addAction(const QString &text); + QAction *addAction(const QIcon &icon, const QString &text); + QAction *addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut = 0); + QAction *addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut = 0); + + QAction *addMenu(QMenu *menu); + QMenu *addMenu(const QString &title); + QMenu *addMenu(const QIcon &icon, const QString &title); + + QAction *addSeparator(); + + QAction *insertMenu(QAction *before, QMenu *menu); + QAction *insertSeparator(QAction *before); + + bool isEmpty() const; + void clear(); + + void setTearOffEnabled(bool); + bool isTearOffEnabled() const; + + bool isTearOffMenuVisible() const; + void hideTearOffMenu(); + + void setDefaultAction(QAction *); + QAction *defaultAction() const; + + void setActiveAction(QAction *act); + QAction *activeAction() const; + + void popup(const QPoint &pos, QAction *at=0); + QAction *exec(); + QAction *exec(const QPoint &pos, QAction *at=0); + + // ### Qt 5: merge + static QAction *exec(QList<QAction*> actions, const QPoint &pos, QAction *at=0); + static QAction *exec(QList<QAction*> actions, const QPoint &pos, QAction *at, QWidget *parent); + + QSize sizeHint() const; + + QRect actionGeometry(QAction *) const; + QAction *actionAt(const QPoint &) const; + + QAction *menuAction() const; + + QString title() const; + void setTitle(const QString &title); + + QIcon icon() const; + void setIcon(const QIcon &icon); + + void setNoReplayFor(QWidget *widget); +#ifdef Q_WS_MAC + OSMenuRef macMenu(OSMenuRef merge=0); +#endif + +#ifdef Q_OS_WINCE + HMENU wceMenu(bool create = false); +#endif + + + bool separatorsCollapsible() const; + void setSeparatorsCollapsible(bool collapse); + +Q_SIGNALS: + void aboutToShow(); + void aboutToHide(); + void triggered(QAction *action); + void hovered(QAction *action); + +protected: + int columnCount() const; + + void changeEvent(QEvent *); + void keyPressEvent(QKeyEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void wheelEvent(QWheelEvent *); + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + void hideEvent(QHideEvent *); + void paintEvent(QPaintEvent *); + void actionEvent(QActionEvent *); + void timerEvent(QTimerEvent *); + bool event(QEvent *); + bool focusNextPrevChild(bool next); + void initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const; + +#ifdef Q_OS_WINCE + QAction* wceCommands(uint command); +#endif + +private Q_SLOTS: + void internalSetSloppyAction(); + void internalDelayedPopup(); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_actionTriggered()) + Q_PRIVATE_SLOT(d_func(), void _q_actionHovered()) + Q_PRIVATE_SLOT(d_func(), void _q_overrideMenuActionDestroyed()) + +#ifdef QT3_SUPPORT +public: + //menudata + inline QT3_SUPPORT uint count() const { return actions().count(); } + inline QT3_SUPPORT int insertItem(const QString &text, const QObject *receiver, const char* member, + const QKeySequence& shortcut = 0, int id = -1, int index = -1) { + return insertAny(0, &text, receiver, member, &shortcut, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QIcon& icon, const QString &text, + const QObject *receiver, const char* member, + const QKeySequence& shortcut = 0, int id = -1, int index = -1) { + return insertAny(&icon, &text, receiver, member, &shortcut, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QPixmap &pixmap, const QObject *receiver, const char* member, + const QKeySequence& shortcut = 0, int id = -1, int index = -1) { + QIcon icon(pixmap); + return insertAny(&icon, 0, receiver, member, &shortcut, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QString &text, int id=-1, int index=-1) { + return insertAny(0, &text, 0, 0, 0, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QIcon& icon, const QString &text, int id=-1, int index=-1) { + return insertAny(&icon, &text, 0, 0, 0, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QString &text, QMenu *popup, int id=-1, int index=-1) { + return insertAny(0, &text, 0, 0, 0, popup, id, index); + } + inline QT3_SUPPORT int insertItem(const QIcon& icon, const QString &text, QMenu *popup, int id=-1, int index=-1) { + return insertAny(&icon, &text, 0, 0, 0, popup, id, index); + } + inline QT3_SUPPORT int insertItem(const QPixmap &pixmap, int id=-1, int index=-1) { + QIcon icon(pixmap); + return insertAny(&icon, 0, 0, 0, 0, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QPixmap &pixmap, QMenu *popup, int id=-1, int index=-1) { + QIcon icon(pixmap); + return insertAny(&icon, 0, 0, 0, 0, popup, id, index); + } + QT3_SUPPORT int insertItem(QMenuItem *item, int id=-1, int index=-1); + QT3_SUPPORT int insertSeparator(int index=-1); + inline QT3_SUPPORT void removeItem(int id) { + if(QAction *act = findActionForId(id)) + removeAction(act); } + inline QT3_SUPPORT void removeItemAt(int index) { + if(QAction *act = actions().value(index)) + removeAction(act); } +#ifndef QT_NO_SHORTCUT + inline QT3_SUPPORT QKeySequence accel(int id) const { + if(QAction *act = findActionForId(id)) + return act->shortcut(); + return QKeySequence(); } + inline QT3_SUPPORT void setAccel(const QKeySequence& key, int id) { + if(QAction *act = findActionForId(id)) + act->setShortcut(key); + } +#endif + inline QT3_SUPPORT QIcon iconSet(int id) const { + if(QAction *act = findActionForId(id)) + return act->icon(); + return QIcon(); } + inline QT3_SUPPORT QString text(int id) const { + if(QAction *act = findActionForId(id)) + return act->text(); + return QString(); } + inline QT3_SUPPORT QPixmap pixmap(int id) const { + if(QAction *act = findActionForId(id)) + return act->icon().pixmap(QSize(22, 22)); + return QPixmap(); } + inline QT3_SUPPORT void setWhatsThis(int id, const QString &w) { + if(QAction *act = findActionForId(id)) + act->setWhatsThis(w); } + inline QT3_SUPPORT QString whatsThis(int id) const { + if(QAction *act = findActionForId(id)) + return act->whatsThis(); + return QString(); } + + inline QT3_SUPPORT void changeItem(int id, const QString &text) { + if(QAction *act = findActionForId(id)) + act->setText(text); } + inline QT3_SUPPORT void changeItem(int id, const QPixmap &pixmap) { + if(QAction *act = findActionForId(id)) + act->setIcon(QIcon(pixmap)); } + inline QT3_SUPPORT void changeItem(int id, const QIcon &icon, const QString &text) { + if(QAction *act = findActionForId(id)) { + act->setIcon(icon); + act->setText(text); + } + } + inline QT3_SUPPORT void setActiveItem(int id) { + setActiveAction(findActionForId(id)); + } + inline QT3_SUPPORT bool isItemActive(int id) const { + return findActionForId(id) == activeAction(); + } + inline QT3_SUPPORT bool isItemEnabled(int id) const { + if(QAction *act = findActionForId(id)) + return act->isEnabled(); + return false; } + inline QT3_SUPPORT void setItemEnabled(int id, bool enable) { + if(QAction *act = findActionForId(id)) + act->setEnabled(enable); + } + inline QT3_SUPPORT bool isItemChecked(int id) const { + if(QAction *act = findActionForId(id)) + return act->isChecked(); + return false; + } + inline QT3_SUPPORT void setItemChecked(int id, bool check) { + if(QAction *act = findActionForId(id)) { + act->setCheckable(true); + act->setChecked(check); + } + } + inline QT3_SUPPORT bool isItemVisible(int id) const { + if(QAction *act = findActionForId(id)) + return act->isVisible(); + return false; + } + inline QT3_SUPPORT void setItemVisible(int id, bool visible) { + if(QAction *act = findActionForId(id)) + act->setVisible(visible); + } + inline QT3_SUPPORT QRect itemGeometry(int index) { + if(QAction *act = actions().value(index)) + return actionGeometry(act); + return QRect(); + } + inline QT3_SUPPORT QFont itemFont(int id) const { + if(QAction *act = findActionForId(id)) + return act->font(); + return QFont(); + } + inline QT3_SUPPORT void setItemFont(int id, const QFont &font) { + if(QAction *act = findActionForId(id)) + act->setFont(font); + } + inline QT3_SUPPORT int indexOf(int id) const { + return actions().indexOf(findActionForId(id)); + } + inline QT3_SUPPORT int idAt(int index) const { + return findIdForAction(actions().value(index)); + } + QT3_SUPPORT void setId (int index, int id); + inline QT3_SUPPORT void activateItemAt(int index) { + if(QAction *ret = actions().value(index)) + ret->activate(QAction::Trigger); + } + inline QT3_SUPPORT bool connectItem(int id, const QObject *receiver, const char* member) { + if(QAction *act = findActionForId(id)) { + QObject::connect(act, SIGNAL(activated(int)), receiver, member); + return true; + } + return false; + } + inline QT3_SUPPORT bool disconnectItem(int id,const QObject *receiver, const char* member) { + if(QAction *act = findActionForId(id)) { + QObject::disconnect(act, SIGNAL(triggered()), receiver, member); + return true; + } + return false; + } + inline QT3_SUPPORT QMenuItem *findItem(int id) const { + return reinterpret_cast<QMenuItem*>(findActionForId(id)); + } + + inline QT3_SUPPORT void setCheckable(bool){} + inline QT3_SUPPORT bool isCheckable() const {return true;} + + QT3_SUPPORT QMenuItem *findPopup( QMenu *popup, int *index ); + + QT3_SUPPORT bool setItemParameter(int id, int param); + QT3_SUPPORT int itemParameter(int id) const; + + //frame + QT3_SUPPORT int frameWidth() const; + + //popupmenu + inline QT3_SUPPORT void popup(const QPoint & pos, int indexAtPoint) { popup(pos, actions().value(indexAtPoint)); } + inline QT3_SUPPORT int insertTearOffHandle(int = 0, int = 0) { + setTearOffEnabled(true); + return -1; + } + +protected: + inline QT3_SUPPORT int itemAtPos(const QPoint &p, bool ignoreSeparator = true) { + QAction *ret = actionAt(p); + if(ignoreSeparator && ret && ret->isSeparator()) + return -1; + return findIdForAction(ret); + } + inline QT3_SUPPORT int columns() const { return columnCount(); } + inline QT3_SUPPORT int itemHeight(int index) { + return actionGeometry(actions().value(index)).height(); + } + inline QT3_SUPPORT int itemHeight(QMenuItem *mi) { + return actionGeometry(reinterpret_cast<QAction *>(mi)).height(); + } + +Q_SIGNALS: + QT_MOC_COMPAT void activated(int itemId); + QT_MOC_COMPAT void highlighted(int itemId); + +private: + int insertAny(const QIcon *icon, const QString *text, const QObject *receiver, const char *member, + const QKeySequence *shorcut, const QMenu *popup, int id, int index); + QAction *findActionForId(int id) const; + int findIdForAction(QAction*) const; +#endif + +protected: + QMenu(QMenuPrivate &dd, QWidget* parent = 0); + +private: + Q_DISABLE_COPY(QMenu) + + friend class QMenuBar; + friend class QMenuBarPrivate; + friend class QTornOffMenu; + friend class Q3PopupMenu; + friend class QComboBox; + friend class QAction; + friend class QToolButtonPrivate; + +#ifdef Q_WS_MAC + friend void qt_mac_trayicon_activate_action(QMenu *, QAction *action); + friend bool qt_mac_watchingAboutToShow(QMenu *); + friend OSStatus qt_mac_menu_event(EventHandlerCallRef, EventRef, void *); + friend bool qt_mac_activate_action(OSMenuRef, uint, QAction::ActionEvent, bool); + friend void qt_mac_emit_menuSignals(QMenu *, bool); +#endif +}; + +#endif // QT_NO_MENU + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMENU_H diff --git a/src/gui/widgets/qmenu_mac.mm b/src/gui/widgets/qmenu_mac.mm new file mode 100644 index 0000000..ad848c9 --- /dev/null +++ b/src/gui/widgets/qmenu_mac.mm @@ -0,0 +1,2038 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmenu.h" +#include "qhash.h" +#include <qdebug.h> +#include "qapplication.h" +#include <private/qt_mac_p.h> +#include "qregexp.h" +#include "qmainwindow.h" +#include "qdockwidget.h" +#include "qtoolbar.h" +#include "qevent.h" +#include "qstyle.h" +#include "qwidgetaction.h" +#include "qmacnativewidget_mac.h" + +#include <private/qapplication_p.h> +#include <private/qcocoaapplication_mac_p.h> +#include <private/qmenu_p.h> +#include <private/qmenubar_p.h> +#include <private/qcocoamenuloader_mac_p.h> +#include <private/qcocoamenu_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <Cocoa/Cocoa.h> + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + QMenu debug facilities + *****************************************************************************/ + +/***************************************************************************** + QMenu globals + *****************************************************************************/ +bool qt_mac_no_native_menubar = false; +bool qt_mac_no_menubar_merge = false; +bool qt_mac_quit_menu_item_enabled = true; +int qt_mac_menus_open_count = 0; + +static OSMenuRef qt_mac_create_menu(QWidget *w); + +#ifndef QT_MAC_USE_COCOA +static uint qt_mac_menu_static_cmd_id = 'QT00'; +const UInt32 kMenuCreatorQt = 'cute'; +enum { + kMenuPropertyQAction = 'QAcT', + kMenuPropertyQWidget = 'QWId', + kMenuPropertyCausedQWidget = 'QCAU', + kMenuPropertyMergeMenu = 'QApP', + kMenuPropertyMergeList = 'QAmL', + kMenuPropertyWidgetActionWidget = 'QWid', + kMenuPropertyWidgetMenu = 'QWMe', + + kHICommandAboutQt = 'AOQT', + kHICommandCustomMerge = 'AQt0' +}; +#endif + +static struct { + QPointer<QMenuBar> qmenubar; + bool modal; +} qt_mac_current_menubar = { 0, false }; + + + + +/***************************************************************************** + Externals + *****************************************************************************/ +extern OSViewRef qt_mac_hiview_for(const QWidget *w); //qwidget_mac.cpp +extern HIViewRef qt_mac_hiview_for(OSWindowRef w); //qwidget_mac.cpp +extern IconRef qt_mac_create_iconref(const QPixmap &px); //qpixmap_mac.cpp +extern QWidget * mac_keyboard_grabber; //qwidget_mac.cpp +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_xxx.cpp +RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp +void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp + +/***************************************************************************** + QMenu utility functions + *****************************************************************************/ +bool qt_mac_watchingAboutToShow(QMenu *menu) +{ + return menu && menu->receivers(SIGNAL(aboutToShow())); +} + +static int qt_mac_CountMenuItems(OSMenuRef menu) +{ + if (menu) { +#ifndef QT_MAC_USE_COCOA + int ret = 0; + const int items = CountMenuItems(menu); + for(int i = 0; i < items; i++) { + MenuItemAttributes attr; + if (GetMenuItemAttributes(menu, i+1, &attr) == noErr && + attr & kMenuItemAttrHidden) + continue; + ++ret; + } + return ret; +#else + return [menu numberOfItems]; +#endif + } + return 0; +} + +static bool actualMenuItemVisibility(const QMenuBarPrivate::QMacMenuBarPrivate *mbp, + const QMacMenuAction *action) +{ + bool visible = action->action->isVisible(); + if (visible && action->action->text() == QString(QChar(0x14))) + return false; + if (visible && action->action->menu() && !action->action->menu()->actions().isEmpty() && + !qt_mac_CountMenuItems(action->action->menu()->macMenu(mbp->apple_menu)) && + !qt_mac_watchingAboutToShow(action->action->menu())) { + return false; + } + return visible; +} + +#ifndef QT_MAC_USE_COCOA +bool qt_mac_activate_action(MenuRef menu, uint command, QAction::ActionEvent action_e, bool by_accel) +{ + //fire event + QMacMenuAction *action = 0; + if (GetMenuCommandProperty(menu, command, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action), 0, &action) != noErr) { + QMenuMergeList *list = 0; + GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list); + if (!list && qt_mac_current_menubar.qmenubar) { + MenuRef apple_menu = qt_mac_current_menubar.qmenubar->d_func()->mac_menubar->apple_menu; + GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, sizeof(list), 0, &list); + if (list) + menu = apple_menu; + } + if (list) { + for(int i = 0; i < list->size(); ++i) { + QMenuMergeItem item = list->at(i); + if (item.command == command && item.action) { + action = item.action; + break; + } + } + } + if (!action) + return false; + } + + if (action_e == QAction::Trigger && by_accel && action->ignore_accel) //no, not a real accel (ie tab) + return false; + + // Unhighlight the highlighted menu item before triggering the action to + // prevent items from staying highlighted while a modal dialog is shown. + // This also fixed the problem that parentless modal dialogs leave + // the menu item highlighted (since the menu bar is cleared for these types of dialogs). + if (action_e == QAction::Trigger) + HiliteMenu(0); + + action->action->activate(action_e); + + //now walk up firing for each "caused" widget (like in the platform independent menu) + QWidget *caused = 0; + if (GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), 0, &caused) == noErr) { + MenuRef caused_menu = 0; + if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused)) + caused_menu = qmenu2->macMenu(); + else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused)) + caused_menu = qmenubar2->macMenu(); + else + caused_menu = 0; + while(caused_menu) { + //fire + QWidget *widget = 0; + GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget); + if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) { + if (action_e == QAction::Trigger) { + emit qmenu->triggered(action->action); + } else if (action_e == QAction::Hover) { + action->action->showStatusText(widget); + emit qmenu->hovered(action->action); + } + } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) { + if (action_e == QAction::Trigger) { + emit qmenubar->triggered(action->action); + } else if (action_e == QAction::Hover) { + action->action->showStatusText(widget); + emit qmenubar->hovered(action->action); + } + break; //nothing more.. + } + + //walk up + if (GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, + sizeof(caused), 0, &caused) != noErr) + break; + if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused)) + caused_menu = qmenu2->macMenu(); + else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused)) + caused_menu = qmenubar2->macMenu(); + else + caused_menu = 0; + } + } + return true; +} + +//lookup a QMacMenuAction in a menu +static int qt_mac_menu_find_action(MenuRef menu, MenuCommand cmd) +{ + MenuItemIndex ret_idx; + MenuRef ret_menu; + if (GetIndMenuItemWithCommandID(menu, cmd, 1, &ret_menu, &ret_idx) == noErr) { + if (ret_menu == menu) + return (int)ret_idx; + } + return -1; +} +static int qt_mac_menu_find_action(MenuRef menu, QMacMenuAction *action) +{ + return qt_mac_menu_find_action(menu, action->command); +} + +typedef QMultiHash<OSMenuRef, EventHandlerRef> EventHandlerHash; +Q_GLOBAL_STATIC(EventHandlerHash, menu_eventHandlers_hash) + +static EventTypeSpec widget_in_menu_events[] = { + { kEventClassMenu, kEventMenuMeasureItemWidth }, + { kEventClassMenu, kEventMenuMeasureItemHeight }, + { kEventClassMenu, kEventMenuDrawItem }, + { kEventClassMenu, kEventMenuCalculateSize } +}; + +static OSStatus qt_mac_widget_in_menu_eventHandler(EventHandlerCallRef er, EventRef event, void *) +{ + UInt32 ekind = GetEventKind(event); + UInt32 eclass = GetEventClass(event); + OSStatus result = eventNotHandledErr; + switch (eclass) { + case kEventClassMenu: + switch (ekind) { + default: + break; + case kEventMenuMeasureItemWidth: { + MenuItemIndex item; + GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex, + 0, sizeof(item), 0, &item); + OSMenuRef menu; + GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu); + QWidget *widget; + if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, + sizeof(widget), 0, &widget) == noErr) { + short width = short(widget->sizeHint().width()); + SetEventParameter(event, kEventParamMenuItemWidth, typeSInt16, + sizeof(short), &width); + result = noErr; + } + break; } + case kEventMenuMeasureItemHeight: { + MenuItemIndex item; + GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex, + 0, sizeof(item), 0, &item); + OSMenuRef menu; + GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu); + QWidget *widget; + if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, + sizeof(widget), 0, &widget) == noErr && widget) { + short height = short(widget->sizeHint().height()); + SetEventParameter(event, kEventParamMenuItemHeight, typeSInt16, + sizeof(short), &height); + result = noErr; + } + break; } + case kEventMenuDrawItem: + result = noErr; + break; + case kEventMenuCalculateSize: { + result = CallNextEventHandler(er, event); + if (result == noErr) { + OSMenuRef menu; + GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu); + HIViewRef content; + HIMenuGetContentView(menu, kThemeMenuTypePullDown, &content); + UInt16 count = CountMenuItems(menu); + for (MenuItemIndex i = 1; i <= count; ++i) { + QWidget *widget; + if (GetMenuItemProperty(menu, i, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, + sizeof(widget), 0, &widget) == noErr && widget) { + RgnHandle itemRgn = qt_mac_get_rgn(); + GetControlRegion(content, i, itemRgn); + + Rect bounds; + GetRegionBounds( itemRgn, &bounds ); + qt_mac_dispose_rgn(itemRgn); + widget->setGeometry(bounds.left, bounds.top, + bounds.right - bounds.left, bounds.bottom - bounds.top); + } + } + } + break; } + } + } + return result; +} + +//handling of events for menurefs created by Qt.. +static EventTypeSpec menu_events[] = { + { kEventClassCommand, kEventCommandProcess }, + { kEventClassMenu, kEventMenuTargetItem }, + { kEventClassMenu, kEventMenuOpening }, + { kEventClassMenu, kEventMenuClosed } +}; + +// Special case for kEventMenuMatchKey, see qt_mac_create_menu below. +static EventTypeSpec menu_menu_events[] = { + { kEventClassMenu, kEventMenuMatchKey } +}; + +OSStatus qt_mac_menu_event(EventHandlerCallRef er, EventRef event, void *) +{ + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + + bool handled_event = true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassCommand: + if (ekind == kEventCommandProcess) { + UInt32 context; + GetEventParameter(event, kEventParamMenuContext, typeUInt32, + 0, sizeof(context), 0, &context); + HICommand cmd; + GetEventParameter(event, kEventParamDirectObject, typeHICommand, + 0, sizeof(cmd), 0, &cmd); + if (!mac_keyboard_grabber && (context & kMenuContextKeyMatching)) { + QMacMenuAction *action = 0; + if (GetMenuCommandProperty(cmd.menu.menuRef, cmd.commandID, kMenuCreatorQt, + kMenuPropertyQAction, sizeof(action), 0, &action) == noErr) { + QWidget *widget = 0; + if (qApp->activePopupWidget()) + widget = (qApp->activePopupWidget()->focusWidget() ? + qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget()); + else if (QApplicationPrivate::focus_widget) + widget = QApplicationPrivate::focus_widget; + if (widget) { + int key = action->action->shortcut(); + QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)), + Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)); + accel_ev.ignore(); + qt_sendSpontaneousEvent(widget, &accel_ev); + if (accel_ev.isAccepted()) { + handled_event = false; + break; + } + } + } + } + handled_event = qt_mac_activate_action(cmd.menu.menuRef, cmd.commandID, + QAction::Trigger, context & kMenuContextKeyMatching); + } + break; + case kEventClassMenu: { + MenuRef menu; + GetEventParameter(event, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menu), NULL, &menu); + if (ekind == kEventMenuMatchKey) { + // Don't activate any actions if we are showing a native modal dialog, + // the key events should go to the dialog in this case. + if (QApplicationPrivate::native_modal_dialog_active) + return menuItemNotFoundErr; + + handled_event = false; + } else if (ekind == kEventMenuTargetItem) { + MenuCommand command; + GetEventParameter(event, kEventParamMenuCommand, typeMenuCommand, + 0, sizeof(command), 0, &command); + handled_event = qt_mac_activate_action(menu, command, QAction::Hover, false); + } else if (ekind == kEventMenuOpening || ekind == kEventMenuClosed) { + qt_mac_menus_open_count += (ekind == kEventMenuOpening) ? 1 : -1; + MenuRef mr; + GetEventParameter(event, kEventParamDirectObject, typeMenuRef, + 0, sizeof(mr), 0, &mr); + + QWidget *widget = 0; + if (GetMenuItemProperty(mr, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget) == noErr) { + if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) { + handled_event = true; + if (ekind == kEventMenuOpening) { + emit qmenu->aboutToShow(); + + int merged = 0; + const QMenuPrivate::QMacMenuPrivate *mac_menu = qmenu->d_func()->mac_menu; + for(int i = 0; i < mac_menu->actionItems.size(); ++i) { + QMacMenuAction *action = mac_menu->actionItems.at(i); + if (action->action->isSeparator()) { + bool hide = false; + if(!action->action->isVisible()) { + hide = true; + } else if (merged && merged == i) { + hide = true; + } else { + for(int l = i+1; l < mac_menu->actionItems.size(); ++l) { + QMacMenuAction *action = mac_menu->actionItems.at(l); + if (action->merged) { + hide = true; + } else if (action->action->isSeparator()) { + if (hide) + break; + } else if (!action->merged) { + hide = false; + break; + } + } + } + + const int index = qt_mac_menu_find_action(mr, action); + if (hide) { + ++merged; + ChangeMenuItemAttributes(mr, index, kMenuItemAttrHidden, 0); + } else { + ChangeMenuItemAttributes(mr, index, 0, kMenuItemAttrHidden); + } + } else if (action->merged) { + ++merged; + } + } + } else { + emit qmenu->aboutToHide(); + } + } + } + } else { + handled_event = false; + } + break; } + default: + handled_event = false; + break; + } + if (!handled_event) //let the event go through + return CallNextEventHandler(er, event); + return noErr; //we eat the event +} +static EventHandlerRef mac_menu_event_handler = 0; +static EventHandlerUPP mac_menu_eventUPP = 0; +static void qt_mac_cleanup_menu_event() +{ + if (mac_menu_event_handler) { + RemoveEventHandler(mac_menu_event_handler); + mac_menu_event_handler = 0; + } + if (mac_menu_eventUPP) { + DisposeEventHandlerUPP(mac_menu_eventUPP); + mac_menu_eventUPP = 0; + } +} +static inline void qt_mac_create_menu_event_handler() +{ + if (!mac_menu_event_handler) { + mac_menu_eventUPP = NewEventHandlerUPP(qt_mac_menu_event); + InstallEventHandler(GetApplicationEventTarget(), mac_menu_eventUPP, + GetEventTypeCount(menu_events), menu_events, 0, + &mac_menu_event_handler); + qAddPostRoutine(qt_mac_cleanup_menu_event); + } +} + + +//enabling of commands +static void qt_mac_command_set_enabled(MenuRef menu, UInt32 cmd, bool b) +{ + if (cmd == kHICommandQuit) + qt_mac_quit_menu_item_enabled = b; + + if (b) { + EnableMenuCommand(menu, cmd); + if (MenuRef dock_menu = GetApplicationDockTileMenu()) + EnableMenuCommand(dock_menu, cmd); + } else { + DisableMenuCommand(menu, cmd); + if (MenuRef dock_menu = GetApplicationDockTileMenu()) + DisableMenuCommand(dock_menu, cmd); + } +} + +static bool qt_mac_auto_apple_menu(MenuCommand cmd) +{ + return (cmd == kHICommandPreferences || cmd == kHICommandQuit); +} + +static void qt_mac_get_accel(quint32 accel_key, quint32 *modif, quint32 *key) { + if (modif) { + *modif = 0; + if ((accel_key & Qt::CTRL) != Qt::CTRL) + *modif |= kMenuNoCommandModifier; + if ((accel_key & Qt::META) == Qt::META) + *modif |= kMenuControlModifier; + if ((accel_key & Qt::ALT) == Qt::ALT) + *modif |= kMenuOptionModifier; + if ((accel_key & Qt::SHIFT) == Qt::SHIFT) + *modif |= kMenuShiftModifier; + } + + accel_key &= ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL); + if (key) { + *key = 0; + if (accel_key == Qt::Key_Return) + *key = kMenuReturnGlyph; + else if (accel_key == Qt::Key_Enter) + *key = kMenuEnterGlyph; + else if (accel_key == Qt::Key_Tab) + *key = kMenuTabRightGlyph; + else if (accel_key == Qt::Key_Backspace) + *key = kMenuDeleteLeftGlyph; + else if (accel_key == Qt::Key_Delete) + *key = kMenuDeleteRightGlyph; + else if (accel_key == Qt::Key_Escape) + *key = kMenuEscapeGlyph; + else if (accel_key == Qt::Key_PageUp) + *key = kMenuPageUpGlyph; + else if (accel_key == Qt::Key_PageDown) + *key = kMenuPageDownGlyph; + else if (accel_key == Qt::Key_Up) + *key = kMenuUpArrowGlyph; + else if (accel_key == Qt::Key_Down) + *key = kMenuDownArrowGlyph; + else if (accel_key == Qt::Key_Left) + *key = kMenuLeftArrowGlyph; + else if (accel_key == Qt::Key_Right) + *key = kMenuRightArrowGlyph; + else if (accel_key == Qt::Key_CapsLock) + *key = kMenuCapsLockGlyph; + else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15) + *key = (accel_key - Qt::Key_F1) + kMenuF1Glyph; + else if (accel_key == Qt::Key_Home) + *key = kMenuNorthwestArrowGlyph; + else if (accel_key == Qt::Key_End) + *key = kMenuSoutheastArrowGlyph; + } +} +#else // Cocoa +static inline void syncNSMenuItemVisiblity(NSMenuItem *menuItem, bool actionVisibility) +{ + [menuItem setHidden:NO]; + [menuItem setHidden:YES]; + [menuItem setHidden:!actionVisibility]; +} + +static inline void syncMenuBarItemsVisiblity(const QMenuBarPrivate::QMacMenuBarPrivate *mac_menubar) +{ + const QList<QMacMenuAction *> &menubarActions = mac_menubar->actionItems; + for (int i = 0; i < menubarActions.size(); ++i) { + const QMacMenuAction *action = menubarActions.at(i); + syncNSMenuItemVisiblity(action->menuItem, actualMenuItemVisibility(mac_menubar, action)); + } +} + +static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader() +{ + return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)]; +} + +static NSMenuItem *createNSMenuItem(const QString &title) +{ + NSMenuItem *item = [[NSMenuItem alloc] + initWithTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(title))) + action:@selector(qtDispatcherToQAction:) keyEquivalent:@""]; + [item setTarget:getMenuLoader()]; + return item; +} +#endif + + + +// helper that recurses into a menu structure and en/dis-ables them +void qt_mac_set_modal_state_helper_recursive(OSMenuRef menu, OSMenuRef merge, bool on) +{ +#ifndef QT_MAC_USE_COCOA + for (int i = 0; i < CountMenuItems(menu); i++) { + OSMenuRef submenu; + GetMenuItemHierarchicalMenu(menu, i+1, &submenu); + if (submenu != merge) { + if (submenu) + qt_mac_set_modal_state_helper_recursive(submenu, merge, on); + if (on) + DisableMenuItem(submenu, 0); + else + EnableMenuItem(submenu, 0); + } + } +#else + for (NSMenuItem *item in [menu itemArray]) { + OSMenuRef submenu = [item submenu]; + if (submenu != merge) { + if (submenu) + qt_mac_set_modal_state_helper_recursive(submenu, merge, on); + if (!on) { + // The item should follow what the QAction has. + if ([item tag]) { + QAction *action = reinterpret_cast<QAction *>([item tag]); + [item setEnabled:action->isEnabled()]; + } else { + [item setEnabled:YES]; + } + } else { + [item setEnabled:NO]; + } + } + } +#endif +} + +//toggling of modal state +static void qt_mac_set_modal_state(OSMenuRef menu, bool on) +{ +#ifndef QT_MAC_USE_COCOA + OSMenuRef merge = 0; + GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, + sizeof(merge), 0, &merge); + + qt_mac_set_modal_state_helper_recursive(menu, merge, on); + + UInt32 commands[] = { kHICommandQuit, kHICommandPreferences, kHICommandAbout, kHICommandAboutQt, 0 }; + for(int c = 0; commands[c]; c++) { + bool enabled = !on; + if (enabled) { + QMacMenuAction *action = 0; + GetMenuCommandProperty(menu, commands[c], kMenuCreatorQt, kMenuPropertyQAction, + sizeof(action), 0, &action); + if (!action && merge) { + GetMenuCommandProperty(merge, commands[c], kMenuCreatorQt, kMenuPropertyQAction, + sizeof(action), 0, &action); + if (!action) { + QMenuMergeList *list = 0; + GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list); + for(int i = 0; list && i < list->size(); ++i) { + QMenuMergeItem item = list->at(i); + if (item.command == commands[c] && item.action) { + action = item.action; + break; + } + } + } + } + + if (!action) { + if (commands[c] != kHICommandQuit) + enabled = false; + } else { + enabled = action->action ? action->action->isEnabled() : 0; + } + } + qt_mac_command_set_enabled(menu, commands[c], enabled); + } +#else + OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu); + qt_mac_set_modal_state_helper_recursive(menu, merge, on); + // I'm ignoring the special items now, since they should get handled via a syncAction() +#endif +} + +bool qt_mac_menubar_is_open() +{ + return qt_mac_menus_open_count > 0; +} + +void qt_mac_clear_menubar() +{ +#ifndef QT_MAC_USE_COCOA + MenuRef clear_menu = 0; + if (CreateNewMenu(0, 0, &clear_menu) == noErr) { + SetRootMenu(clear_menu); + ReleaseMenu(clear_menu); + } else { + qWarning("QMenu: Internal error at %s:%d", __FILE__, __LINE__); + } + ClearMenuBar(); + qt_mac_command_set_enabled(0, kHICommandPreferences, false); + InvalMenuBar(); +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + NSMenu *menu = [loader menu]; + [loader ensureAppMenuInMenu:menu]; + [NSApp setMainMenu:menu]; +#endif +} + + +QMacMenuAction::~QMacMenuAction() +{ +#ifdef QT_MAC_USE_COCOA + [menu release]; + [menuItem setTag:nil]; + [menuItem release]; +#endif +} + +#ifndef QT_MAC_USE_COCOA +static MenuCommand qt_mac_menu_merge_action(MenuRef merge, QMacMenuAction *action) +#else +static NSMenuItem *qt_mac_menu_merge_action(OSMenuRef merge, QMacMenuAction *action) +#endif +{ + if (qt_mac_no_menubar_merge || action->action->menu() || action->action->isSeparator() + || action->action->menuRole() == QAction::NoRole) + return 0; + + QString t = qt_mac_removeMnemonics(action->action->text().toLower()); + int st = t.lastIndexOf(QLatin1Char('\t')); + if (st != -1) + t.remove(st, t.length()-st); + t.replace(QRegExp(QString::fromLatin1("\\.*$")), QLatin1String("")); //no ellipses + //now the fun part +#ifndef QT_MAC_USE_COCOA + MenuCommand ret = 0; +#else + NSMenuItem *ret = 0; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); +#endif + switch (action->action->menuRole()) { + case QAction::NoRole: + ret = 0; + break; + case QAction::ApplicationSpecificRole: +#ifndef QT_MAC_USE_COCOA + { + QMenuMergeList *list = 0; + if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list) == noErr && list) { + MenuCommand lastCustom = kHICommandCustomMerge; + for(int i = 0; i < list->size(); ++i) { + QMenuMergeItem item = list->at(i); + if (item.command == lastCustom) + ++lastCustom; + } + ret = lastCustom; + } else { + // The list hasn't been created, so, must be the first one. + ret = kHICommandCustomMerge; + } + } +#else + ret = [loader appSpecificMenuItem]; +#endif + break; + case QAction::AboutRole: +#ifndef QT_MAC_USE_COCOA + ret = kHICommandAbout; +#else + ret = [loader aboutMenuItem]; +#endif + break; + case QAction::AboutQtRole: +#ifndef QT_MAC_USE_COCOA + ret = kHICommandAboutQt; +#else + ret = [loader aboutQtMenuItem]; +#endif + break; + case QAction::QuitRole: +#ifndef QT_MAC_USE_COCOA + ret = kHICommandQuit; +#else + ret = [loader quitMenuItem]; +#endif + break; + case QAction::PreferencesRole: +#ifndef QT_MAC_USE_COCOA + ret = kHICommandPreferences; +#else + ret = [loader preferencesMenuItem]; +#endif + break; + case QAction::TextHeuristicRole: { + QString aboutString = QMenuBar::tr("About").toLower(); + if (t.startsWith(aboutString) || t.endsWith(aboutString)) { + if (t.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) { +#ifndef QT_MAC_USE_COCOA + ret = kHICommandAbout; +#else + ret = [loader aboutMenuItem]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + ret = kHICommandAboutQt; +#else + ret = [loader aboutQtMenuItem]; +#endif + } + } else if (t.startsWith(QMenuBar::tr("Config").toLower()) + || t.startsWith(QMenuBar::tr("Preference").toLower()) + || t.startsWith(QMenuBar::tr("Options").toLower()) + || t.startsWith(QMenuBar::tr("Setting").toLower()) + || t.startsWith(QMenuBar::tr("Setup").toLower())) { +#ifndef QT_MAC_USE_COCOA + ret = kHICommandPreferences; +#else + ret = [loader preferencesMenuItem]; +#endif + } else if (t.startsWith(QMenuBar::tr("Quit").toLower()) + || t.startsWith(QMenuBar::tr("Exit").toLower())) { +#ifndef QT_MAC_USE_COCOA + ret = kHICommandQuit; +#else + ret = [loader quitMenuItem]; +#endif + } + } + break; + } + +#ifndef QT_MAC_USE_COCOA + QMenuMergeList *list = 0; + if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list) == noErr && list) { + for(int i = 0; i < list->size(); ++i) { + QMenuMergeItem item = list->at(i); + if (item.command == ret && item.action) + return 0; + } + } + + QAction *cmd_action = 0; + if (GetMenuCommandProperty(merge, ret, kMenuCreatorQt, kMenuPropertyQAction, + sizeof(cmd_action), 0, &cmd_action) == noErr && cmd_action) + return 0; //already taken +#else + if (QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge)) { + for(int i = 0; i < list->size(); ++i) { + const QMenuMergeItem &item = list->at(i); + if (item.menuItem == ret && item.action) + return 0; + } + } + + if ([ret tag] != 0) + ret = 0; // already taken +#endif + return ret; +} + +static QString qt_mac_menu_merge_text(QMacMenuAction *action) +{ + QString ret; +#ifdef QT_MAC_USE_COCOA + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); +#endif + if (action->action->menuRole() == QAction::ApplicationSpecificRole) + ret = action->action->text(); +#ifndef QT_MAC_USE_COCOA + else if (action->command == kHICommandAbout) + ret = QMenuBar::tr("About %1").arg(qAppName()); + else if (action->command == kHICommandAboutQt) + ret = QMenuBar::tr("About Qt"); + else if (action->command == kHICommandPreferences) + ret = QMenuBar::tr("Preferences"); + else if (action->command == kHICommandQuit) + ret = QMenuBar::tr("Quit %1").arg(qAppName()); +#else + else if (action->menuItem == [loader aboutMenuItem]) + ret = QMenuBar::tr("About %1").arg(qAppName()); + else if (action->menuItem == [loader aboutQtMenuItem]) + ret = QMenuBar::tr("About Qt"); + else if (action->menuItem == [loader preferencesMenuItem]) + ret = QMenuBar::tr("Preferences"); + else if (action->menuItem == [loader quitMenuItem]) + ret = QMenuBar::tr("Quit %1").arg(qAppName()); +#endif + return ret; +} + +static QKeySequence qt_mac_menu_merge_accel(QMacMenuAction *action) +{ + QKeySequence ret; +#ifdef QT_MAC_USE_COCOA + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); +#endif + if (action->action->menuRole() == QAction::ApplicationSpecificRole) + ret = action->action->shortcut(); +#ifndef QT_MAC_USE_COCOA + else if (action->command == kHICommandPreferences) + ret = QKeySequence(Qt::CTRL+Qt::Key_Comma); + else if (action->command == kHICommandQuit) + ret = QKeySequence(Qt::CTRL+Qt::Key_Q); +#else + else if (action->menuItem == [loader preferencesMenuItem]) + ret = QKeySequence(Qt::CTRL+Qt::Key_Comma); + else if (action->menuItem == [loader quitMenuItem]) + ret = QKeySequence(Qt::CTRL+Qt::Key_Q); +#endif + return ret; +} + +void Q_GUI_EXPORT qt_mac_set_menubar_icons(bool b) +{ QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, !b); } +void Q_GUI_EXPORT qt_mac_set_native_menubar(bool b) { qt_mac_no_native_menubar = !b; } +void Q_GUI_EXPORT qt_mac_set_menubar_merge(bool b) { qt_mac_no_menubar_merge = !b; } + +/***************************************************************************** + QMenu bindings + *****************************************************************************/ +QMenuPrivate::QMacMenuPrivate::QMacMenuPrivate() : menu(0) +{ +} + +QMenuPrivate::QMacMenuPrivate::~QMacMenuPrivate() +{ +#ifndef QT_MAC_USE_COCOA + for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) { + QMacMenuAction *action = (*it); + RemoveMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction); + if (action->merged) { + QMenuMergeList *list = 0; + GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list); + for(int i = 0; list && i < list->size(); ) { + QMenuMergeItem item = list->at(i); + if (item.action == action) + list->removeAt(i); + else + ++i; + } + } + delete action; + } + if (menu) { + EventHandlerHash::iterator it = menu_eventHandlers_hash()->find(menu); + while (it != menu_eventHandlers_hash()->end() && it.key() == menu) { + RemoveEventHandler(it.value()); + ++it; + } + menu_eventHandlers_hash()->remove(menu); + ReleaseMenu(menu); + } +#else + QMacCocoaAutoReleasePool pool; + while (actionItems.size()) { + QMacMenuAction *action = actionItems.takeFirst(); + if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) { + int i = 0; + while (i < list->size()) { + const QMenuMergeItem &item = list->at(i); + if (item.action == action) + list->removeAt(i); + else + ++i; + } + } + delete action; + } + mergeMenuHash.remove(menu); + mergeMenuItemsHash.remove(menu); + [menu release]; +#endif +} + +void +QMenuPrivate::QMacMenuPrivate::addAction(QAction *a, QMacMenuAction *before, QMenuPrivate *qmenu) +{ + QMacMenuAction *action = new QMacMenuAction; + action->action = a; + action->ignore_accel = 0; + action->merged = 0; + action->menu = 0; +#ifndef QT_MAC_USE_COCOA + action->command = qt_mac_menu_static_cmd_id++; +#endif + addAction(action, before, qmenu); +} + +void +QMenuPrivate::QMacMenuPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before, QMenuPrivate *qmenu) +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + Q_UNUSED(qmenu); +#endif + if (!action) + return; + int before_index = actionItems.indexOf(before); + if (before_index < 0) { + before = 0; + before_index = actionItems.size(); + } + actionItems.insert(before_index, action); + +#ifndef QT_MAC_USE_COCOA + int index = qt_mac_menu_find_action(menu, action); +#else + [menu retain]; + [action->menu release]; +#endif + action->menu = menu; + + /* When the action is considered a mergable action it + will stay that way, until removed.. */ + if (!qt_mac_no_menubar_merge) { +#ifndef QT_MAC_USE_COCOA + MenuRef merge = 0; + GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, + sizeof(merge), 0, &merge); +#else + OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu); +#endif + if (merge) { +#ifndef QT_MAC_USE_COCOA + if (MenuCommand cmd = qt_mac_menu_merge_action(merge, action)) { + action->merged = 1; + action->menu = merge; + action->command = cmd; + if (qt_mac_auto_apple_menu(cmd)) + index = 0; //no need + + QMenuMergeList *list = 0; + if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list) != noErr || !list) { + list = new QMenuMergeList; + SetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), &list); + } + list->append(QMenuMergeItem(cmd, action)); + } +#else + if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) { + action->merged = 1; + [merge retain]; + [action->menu release]; + action->menu = merge; + [cmd retain]; + [cmd setAction:@selector(qtDispatcherToQAction:)]; + [cmd setTarget:getMenuLoader()]; + [action->menuItem release]; + action->menuItem = cmd; + QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge); + if (!list) { + list = new QMenuMergeList; + QMenuPrivate::mergeMenuItemsHash.insert(merge, list); + } + list->append(QMenuMergeItem(cmd, action)); + } +#endif + } + } + +#ifdef QT_MAC_USE_COCOA + NSMenuItem *newItem = action->menuItem; +#endif + if ( +#ifndef QT_MAC_USE_COCOA + index == -1 +#else + newItem == 0 +#endif + ) { +#ifndef QT_MAC_USE_COCOA + index = before_index; + MenuItemAttributes attr = kMenuItemAttrAutoRepeat; +#else + newItem = createNSMenuItem(action->action->text()); + action->menuItem = newItem; +#endif + if (before) { +#ifndef QT_MAC_USE_COCOA + InsertMenuItemTextWithCFString(action->menu, 0, qMax(before_index, 0), attr, action->command); +#else + [menu insertItem:newItem atIndex:qMax(before_index, 0)]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + // Append the menu item to the menu. If it is a kHICommandAbout or a kHICommandAboutQt append + // a separator also (to get a separator above "Preferences"), but make sure that we don't + // add separators between two "about" items. + + // Build a set of all commands that could possibly be before the separator. + QSet<MenuCommand> mergedItems; + mergedItems.insert(kHICommandAbout); + mergedItems.insert(kHICommandAboutQt); + mergedItems.insert(kHICommandCustomMerge); + + QMenuMergeList *list = 0; + if (GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list) == noErr && list) { + for (int i = 0; i < list->size(); ++i) { + MenuCommand command = list->at(i).command; + if (command > kHICommandCustomMerge) { + mergedItems.insert(command); + } + } + } + + const int itemCount = CountMenuItems(action->menu); + MenuItemAttributes testattr; + GetMenuItemAttributes(action->menu, itemCount , &testattr); + if (mergedItems.contains(action->command) + && (testattr & kMenuItemAttrSeparator)) { + InsertMenuItemTextWithCFString(action->menu, 0, qMax(itemCount - 1, 0), attr, action->command); + index = itemCount; + } else { + MenuItemIndex tmpIndex; + AppendMenuItemTextWithCFString(action->menu, 0, attr, action->command, &tmpIndex); + index = tmpIndex; + if (mergedItems.contains(action->command)) + AppendMenuItemTextWithCFString(action->menu, 0, kMenuItemAttrSeparator, 0, &tmpIndex); + } +#else + [menu addItem:newItem]; +#endif + } + + QWidget *widget = qmenu ? qmenu->widgetItems.value(action->action) : 0; + if (widget) { +#ifndef QT_MAC_USE_COCOA + ChangeMenuAttributes(action->menu, kMenuAttrDoNotCacheImage, 0); + attr = kMenuItemAttrCustomDraw; + SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, + sizeof(QWidget *), &widget); + HIViewRef content; + HIMenuGetContentView(action->menu, kThemeMenuTypePullDown, &content); + + EventHandlerRef eventHandlerRef; + InstallMenuEventHandler(action->menu, qt_mac_widget_in_menu_eventHandler, + GetEventTypeCount(widget_in_menu_events), + widget_in_menu_events, 0, &eventHandlerRef); + menu_eventHandlers_hash()->insert(action->menu, eventHandlerRef); + + QWidget *menuWidget = 0; + GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu, + sizeof(menuWidget), 0, &menuWidget); + if(!menuWidget) { + menuWidget = new QMacNativeWidget(content); + SetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu, + sizeof(menuWidget), &menuWidget); + menuWidget->show(); + } + widget->setParent(menuWidget); +#else + QMacNativeWidget *container = new QMacNativeWidget(0); + container->resize(widget->sizeHint()); + widget->setAttribute(Qt::WA_LayoutUsesWidgetRect); + widget->setParent(container); + + NSView *containerView = qt_mac_nativeview_for(container); + [containerView setAutoresizesSubviews:YES]; + [containerView setAutoresizingMask:NSViewWidthSizable]; + [qt_mac_nativeview_for(widget) setAutoresizingMask:NSViewWidthSizable]; + + [newItem setView:containerView]; + container->show(); +#endif + widget->show(); + } + + } else { +#ifndef QT_MAC_USE_COCOA + qt_mac_command_set_enabled(action->menu, action->command, !QApplicationPrivate::modalState()); +#else + [newItem setEnabled:!QApplicationPrivate::modalState()]; +#endif + } +#ifndef QT_MAC_USE_COCOA + SetMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction, + sizeof(action), &action); +#else + [newItem setTag:long(static_cast<QAction *>(action->action))]; +#endif + syncAction(action); +} + +// return an autoreleased string given a QKeySequence (currently only looks at the first one). +NSString *keySequenceToKeyEqivalent(const QKeySequence &accel) +{ + quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL)); + unichar keyEquiv[1] = { 0 }; + if (accel_key == Qt::Key_Return) + keyEquiv[0] = kReturnCharCode; + else if (accel_key == Qt::Key_Enter) + keyEquiv[0] = kEnterCharCode; + else if (accel_key == Qt::Key_Tab) + keyEquiv[0] = kTabCharCode; + else if (accel_key == Qt::Key_Backspace) + keyEquiv[0] = kBackspaceCharCode; + else if (accel_key == Qt::Key_Delete) + keyEquiv[0] = NSDeleteFunctionKey; + else if (accel_key == Qt::Key_Escape) + keyEquiv[0] = kEscapeCharCode; + else if (accel_key == Qt::Key_PageUp) + keyEquiv[0] = NSPageUpFunctionKey; + else if (accel_key == Qt::Key_PageDown) + keyEquiv[0] = NSPageDownFunctionKey; + else if (accel_key == Qt::Key_Up) + keyEquiv[0] = NSUpArrowFunctionKey; + else if (accel_key == Qt::Key_Down) + keyEquiv[0] = NSDownArrowFunctionKey; + else if (accel_key == Qt::Key_Left) + keyEquiv[0] = NSLeftArrowFunctionKey; + else if (accel_key == Qt::Key_Right) + keyEquiv[0] = NSRightArrowFunctionKey; + else if (accel_key == Qt::Key_CapsLock) + keyEquiv[0] = kMenuCapsLockGlyph; // ### Cocoa has no equivalent + else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15) + keyEquiv[0] = (accel_key - Qt::Key_F1) + NSF1FunctionKey; + else if (accel_key == Qt::Key_Home) + keyEquiv[0] = NSHomeFunctionKey; + else if (accel_key == Qt::Key_End) + keyEquiv[0] = NSEndFunctionKey; + else + keyEquiv[0] = unichar(QChar(accel_key).toLower().unicode()); + return [NSString stringWithCharacters:keyEquiv length:1]; +} + +// return the cocoa modifier mask for the QKeySequence (currently only looks at the first one). +NSUInteger keySequenceModifierMask(const QKeySequence &accel) +{ + NSUInteger ret = 0; + quint32 accel_key = accel[0]; + if ((accel_key & Qt::CTRL) == Qt::CTRL) + ret |= NSCommandKeyMask; + if ((accel_key & Qt::META) == Qt::META) + ret |= NSControlKeyMask; + if ((accel_key & Qt::ALT) == Qt::ALT) + ret |= NSAlternateKeyMask; + if ((accel_key & Qt::SHIFT) == Qt::SHIFT) + ret |= NSShiftKeyMask; + return ret; +} + +void +QMenuPrivate::QMacMenuPrivate::syncAction(QMacMenuAction *action) +{ + if (!action) + return; + +#ifndef QT_MAC_USE_COCOA + const int index = qt_mac_menu_find_action(action->menu, action); + if (index == -1) + return; +#else + NSMenuItem *item = action->menuItem; + if (!item) + return; +#endif + +#ifndef QT_MAC_USE_COCOA + if (!action->action->isVisible()) { + ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrHidden, 0); + return; + } + ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrHidden); +#else + QMacCocoaAutoReleasePool pool; + NSMenu *menu = [item menu]; + bool actionVisible = action->action->isVisible(); + [item setHidden:!actionVisible]; + if (!actionVisible) + return; +#endif + +#ifndef QT_MAC_USE_COCOA + if (action->action->isSeparator()) { + ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrSeparator, 0); + return; + } + ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrSeparator); +#else + int itemIndex = [menu indexOfItem:item]; + Q_ASSERT(itemIndex != -1); + if (action->action->isSeparator()) { + action->menuItem = [NSMenuItem separatorItem]; + [action->menuItem retain]; + [menu insertItem: action->menuItem atIndex:itemIndex]; + [menu removeItem:item]; + [item release]; + item = action->menuItem; + return; + } else if ([item isSeparatorItem]) { + // I'm no longer a separator... + action->menuItem = createNSMenuItem(action->action->text()); + [menu insertItem:action->menuItem atIndex:itemIndex]; + [menu removeItem:item]; + [item release]; + item = action->menuItem; + } +#endif + + //find text (and accel) + action->ignore_accel = 0; + QString text = action->action->text(); + QKeySequence accel = action->action->shortcut(); + { + int st = text.lastIndexOf(QLatin1Char('\t')); + if (st != -1) { + action->ignore_accel = 1; + accel = QKeySequence(text.right(text.length()-(st+1))); + text.remove(st, text.length()-st); + } + } + { + QString cmd_text = qt_mac_menu_merge_text(action); + if (!cmd_text.isEmpty()) { + text = cmd_text; + accel = qt_mac_menu_merge_accel(action); + } + } + if (accel.count() > 1) + text += QLatin1String(" (****)"); //just to denote a multi stroke shortcut + + QString finalString = qt_mac_removeMnemonics(text); + +#ifndef QT_MAC_USE_COCOA + MenuItemDataRec data; + memset(&data, '\0', sizeof(data)); + + //Carbon text + data.whichData |= kMenuItemDataCFString; + QCFString cfstring(finalString); // Hold the reference to the end of the function. + data.cfText = cfstring; + + // Carbon enabled + data.whichData |= kMenuItemDataEnabled; + data.enabled = action->action->isEnabled(); + // Carbon icon + data.whichData |= kMenuItemDataIconHandle; + if (!action->action->icon().isNull() + && action->action->isIconVisibleInMenu()) { + data.iconType = kMenuIconRefType; + data.iconHandle = (Handle)qt_mac_create_iconref(action->action->icon().pixmap(22, QIcon::Normal)); + } else { + data.iconType = kMenuNoIcon; + } + if (action->action->font().resolve()) { // Carbon font + if (action->action->font().bold()) + data.style |= bold; + if (action->action->font().underline()) + data.style |= underline; + if (action->action->font().italic()) + data.style |= italic; + if (data.style) + data.whichData |= kMenuItemDataStyle; + data.whichData |= kMenuItemDataFontID; + data.fontID = action->action->font().macFontID(); + } +#else + // Cocoa Font and title + if (action->action->font().resolve()) { + const QFont &actionFont = action->action->font(); + NSFont *customMenuFont = [NSFont fontWithName:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(actionFont.family()))) + size:actionFont.pointSize()]; + NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil]; + NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil]; + NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; + NSAttributedString *str = [[[NSAttributedString alloc] initWithString:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(finalString))) + attributes:attributes] autorelease]; + [item setAttributedTitle: str]; + } else { + [item setTitle: reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(finalString)))]; + } + [item setTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(qt_mac_removeMnemonics(text))))]; + + // Cocoa Enabled + [item setEnabled: action->action->isEnabled()]; + + // Cocoa icon + NSImage *nsimage = 0; + if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) { + nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(22, QIcon::Normal))); + } + [item setImage:nsimage]; + [nsimage release]; +#endif + + if (action->action->menu()) { //submenu +#ifndef QT_MAC_USE_COCOA + data.whichData |= kMenuItemDataSubmenuHandle; + data.submenuHandle = action->action->menu()->macMenu(); + QWidget *caused = 0; + GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused); + SetMenuItemProperty(data.submenuHandle, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused); +#else + [item setSubmenu:static_cast<NSMenu *>(action->action->menu()->macMenu())]; +#endif + } else { //respect some other items +#ifndef QT_MAC_USE_COCOA + //shortcuts (say we are setting them all so that we can also clear them). + data.whichData |= kMenuItemDataCmdKey; + data.whichData |= kMenuItemDataCmdKeyModifiers; + data.whichData |= kMenuItemDataCmdKeyGlyph; + if (!accel.isEmpty()) { + qt_mac_get_accel(accel[0], (quint32*)&data.cmdKeyModifiers, (quint32*)&data.cmdKeyGlyph); + if (data.cmdKeyGlyph == 0) + data.cmdKey = (UniChar)accel[0]; + } +#else + [item setSubmenu:0]; + if (!accel.isEmpty()) { + [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)]; + [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)]; + } else { + [item setKeyEquivalent:@""]; + [item setKeyEquivalentModifierMask:NSCommandKeyMask]; + } +#endif + } +#ifndef QT_MAC_USE_COCOA + //mark glyph + data.whichData |= kMenuItemDataMark; + if (action->action->isChecked()) { +#if 0 + if (action->action->actionGroup() && + action->action->actionGroup()->isExclusive()) + data.mark = diamondMark; + else +#endif + data.mark = checkMark; + } else { + data.mark = noMark; + } + + //actually set it + SetMenuItemData(action->menu, action->command, true, &data); + + // Free up memory + if (data.iconHandle) + ReleaseIconRef(IconRef(data.iconHandle)); +#else + //mark glyph + [item setState:action->action->isChecked() ? NSOnState : NSOffState]; +#endif +} + +void +QMenuPrivate::QMacMenuPrivate::removeAction(QMacMenuAction *action) +{ + if (!action) + return; +#ifndef QT_MAC_USE_COCOA + if (action->command == kHICommandQuit || action->command == kHICommandPreferences) + qt_mac_command_set_enabled(action->menu, action->command, false); + else + DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action)); +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + if (action->menuItem == [loader quitMenuItem] || action->menuItem == [loader preferencesMenuItem]) + [action->menuItem setEnabled:false]; + else + [[action->menuItem menu] removeItem:action->menuItem]; +#endif + actionItems.removeAll(action); +} + +OSMenuRef +QMenuPrivate::macMenu(OSMenuRef merge) +{ + Q_UNUSED(merge); + Q_Q(QMenu); + if (mac_menu && mac_menu->menu) + return mac_menu->menu; + if (!mac_menu) + mac_menu = new QMacMenuPrivate; + mac_menu->menu = qt_mac_create_menu(q); + if (merge) { +#ifndef QT_MAC_USE_COCOA + SetMenuItemProperty(mac_menu->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, sizeof(merge), &merge); +#else + mergeMenuHash.insert(mac_menu->menu, merge); +#endif + } + QList<QAction*> items = q->actions(); + for(int i = 0; i < items.count(); i++) + mac_menu->addAction(items[i], 0, this); + return mac_menu->menu; +} + +/*! + \internal +*/ +void QMenuPrivate::setMacMenuEnabled(bool enable) +{ + if (!macMenu(0)) + return; + + QMacCocoaAutoReleasePool pool; + if (enable) { + for (int i = 0; i < mac_menu->actionItems.count(); ++i) { + QMacMenuAction *menuItem = mac_menu->actionItems.at(i); + if (menuItem && menuItem->action && menuItem->action->isEnabled()) { +#ifndef QT_MAC_USE_COCOA + // Only enable those items which contains an enabled QAction. + // i == 0 -> the menu itself, hence i + 1 for items. + EnableMenuItem(mac_menu->menu, i + 1); +#else + [menuItem->menuItem setEnabled:true]; +#endif + } + } + } else { +#ifndef QT_MAC_USE_COCOA + DisableAllMenuItems(mac_menu->menu); +#else + NSMenu *menu = mac_menu->menu; + for (NSMenuItem *item in [menu itemArray]) { + [item setEnabled:false]; + } +#endif + } +} + +/*! + \internal + + This function will return the OSMenuRef used to create the native menu bar + bindings. + + If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used + with Carbon's Menu Manager API. + + If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer. + + \warning This function is not portable. + + \sa QMenuBar::macMenu() +*/ +OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); } + +/***************************************************************************** + QMenuBar bindings + *****************************************************************************/ +typedef QHash<QWidget *, QMenuBar *> MenuBarHash; +Q_GLOBAL_STATIC(MenuBarHash, menubars) +static QMenuBar *fallback = 0; +QMenuBarPrivate::QMacMenuBarPrivate::QMacMenuBarPrivate() : menu(0), apple_menu(0) +{ +} + +QMenuBarPrivate::QMacMenuBarPrivate::~QMacMenuBarPrivate() +{ + for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) + delete (*it); +#ifndef QT_MAC_USE_COCOA + if (apple_menu) { + QMenuMergeList *list = 0; + GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list); + if (list) { + RemoveMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList); + delete list; + } + ReleaseMenu(apple_menu); + } + if (menu) + ReleaseMenu(menu); +#else + [apple_menu release]; + [menu release]; +#endif +} + +void +QMenuBarPrivate::QMacMenuBarPrivate::addAction(QAction *a, QMacMenuAction *before) +{ + if (a->isSeparator() || !menu) + return; + QMacMenuAction *action = new QMacMenuAction; + action->action = a; + action->ignore_accel = 1; +#ifndef QT_MAC_USE_COCOA + action->command = qt_mac_menu_static_cmd_id++; +#endif + addAction(action, before); +} + +void +QMenuBarPrivate::QMacMenuBarPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before) +{ + if (!action || !menu) + return; + + int before_index = actionItems.indexOf(before); + if (before_index < 0) { + before = 0; + before_index = actionItems.size(); + } + actionItems.insert(before_index, action); + + MenuItemIndex index = actionItems.size()-1; + + action->menu = menu; +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + [action->menu retain]; + NSMenuItem *newItem = createNSMenuItem(action->action->text()); + action->menuItem = newItem; +#endif + if (before) { +#ifndef QT_MAC_USE_COCOA + InsertMenuItemTextWithCFString(action->menu, 0, qMax(1, before_index+1), 0, action->command); +#else + [menu insertItem:newItem atIndex:qMax(1, before_index + 1)]; +#endif + index = before_index; + } else { +#ifndef QT_MAC_USE_COCOA + AppendMenuItemTextWithCFString(action->menu, 0, 0, action->command, &index); +#else + [menu addItem:newItem]; +#endif + } +#ifndef QT_MAC_USE_COCOA + SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action), + &action); +#else + [newItem setTag:long(static_cast<QAction *>(action->action))]; +#endif + syncAction(action); +} + +void +QMenuBarPrivate::QMacMenuBarPrivate::syncAction(QMacMenuAction *action) +{ + if (!action || !menu) + return; +#ifndef QT_MAC_USE_COCOA + const int index = qt_mac_menu_find_action(action->menu, action); +#else + QMacCocoaAutoReleasePool pool; + NSMenuItem *item = action->menuItem; +#endif + + OSMenuRef submenu = 0; + bool release_submenu = false; + if (action->action->menu()) { + if ((submenu = action->action->menu()->macMenu(apple_menu))) { +#ifndef QT_MAC_USE_COCOA + QWidget *caused = 0; + GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused); + SetMenuItemProperty(submenu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused); +#else + [item setSubmenu:submenu]; +#endif + } +#ifndef QT_MAC_USE_COCOA + } else { // create a submenu to act as menu + release_submenu = true; + CreateNewMenu(0, 0, &submenu); +#endif + } + + if (submenu) { + bool visible = actualMenuItemVisibility(this, action); +#ifndef QT_MAC_USE_COCOA + SetMenuItemHierarchicalMenu(action->menu, index, submenu); + SetMenuTitleWithCFString(submenu, QCFString(qt_mac_removeMnemonics(action->action->text()))); + if (visible) + ChangeMenuAttributes(submenu, 0, kMenuAttrHidden); + else + ChangeMenuAttributes(submenu, kMenuAttrHidden, 0); +#else + [item setSubmenu: submenu]; + [submenu setTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(qt_mac_removeMnemonics(action->action->text()))))]; + syncNSMenuItemVisiblity(item, visible); +#endif + if (release_submenu) { //no pointers to it +#ifndef QT_MAC_USE_COCOA + ReleaseMenu(submenu); +#else + [submenu release]; +#endif + } + } else { + qWarning("QMenu: No OSMenuRef created for popup menu"); + } +} + +void +QMenuBarPrivate::QMacMenuBarPrivate::removeAction(QMacMenuAction *action) +{ + if (!action || !menu) + return; +#ifndef QT_MAC_USE_COCOA + DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action)); +#else + QMacCocoaAutoReleasePool pool; + [action->menu removeItem:action->menuItem]; +#endif + actionItems.removeAll(action); +} + +void +QMenuBarPrivate::macCreateMenuBar(QWidget *parent) +{ + Q_Q(QMenuBar); + static int checkEnv = -1; + if (qt_mac_no_native_menubar == false && checkEnv < 0) { + checkEnv = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty(); + qt_mac_no_native_menubar = checkEnv; + } + if (!qt_mac_no_native_menubar) { + extern void qt_event_request_menubarupdate(); //qapplication_mac.cpp + qt_event_request_menubarupdate(); + if (!parent && !fallback) { + fallback = q; + mac_menubar = new QMacMenuBarPrivate; + } else if (parent && parent->isWindow()) { + menubars()->insert(q->window(), q); + mac_menubar = new QMacMenuBarPrivate; + } + } +} + +void QMenuBarPrivate::macDestroyMenuBar() +{ + Q_Q(QMenuBar); + QMacCocoaAutoReleasePool pool; + if (fallback == q) + fallback = 0; + delete mac_menubar; + QWidget *tlw = q->window(); + menubars()->remove(tlw); + mac_menubar = 0; + + if (qt_mac_current_menubar.qmenubar == q) { + extern void qt_event_request_menubarupdate(); //qapplication_mac.cpp + qt_event_request_menubarupdate(); + } +} + +OSMenuRef QMenuBarPrivate::macMenu() +{ + Q_Q(QMenuBar); + if (!mac_menubar) { + return 0; + } else if (!mac_menubar->menu) { + mac_menubar->menu = qt_mac_create_menu(q); + ProcessSerialNumber mine, front; + if (GetCurrentProcess(&mine) == noErr && GetFrontProcess(&front) == noErr) { + if (!qt_mac_no_menubar_merge && !mac_menubar->apple_menu) { + mac_menubar->apple_menu = qt_mac_create_menu(q); +#ifndef QT_MAC_USE_COCOA + MenuItemIndex index; + AppendMenuItemTextWithCFString(mac_menubar->menu, 0, 0, 0, &index); + + SetMenuTitleWithCFString(mac_menubar->apple_menu, QCFString(QString(QChar(0x14)))); + SetMenuItemHierarchicalMenu(mac_menubar->menu, index, mac_menubar->apple_menu); + SetMenuItemProperty(mac_menubar->apple_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(q), &q); +#else + [mac_menubar->apple_menu setTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(QString(QChar(0x14)))))]; + NSMenuItem *apple_menuItem = [[NSMenuItem alloc] init]; + [apple_menuItem setSubmenu:mac_menubar->menu]; + [mac_menubar->apple_menu addItem:apple_menuItem]; + [apple_menuItem release]; +#endif + } + if (mac_menubar->apple_menu) { +#ifndef QT_MAC_USE_COCOA + SetMenuItemProperty(mac_menubar->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, + sizeof(mac_menubar->apple_menu), &mac_menubar->apple_menu); +#else + QMenuPrivate::mergeMenuHash.insert(mac_menubar->menu, mac_menubar->apple_menu); +#endif + } + QList<QAction*> items = q->actions(); + for(int i = 0; i < items.count(); i++) + mac_menubar->addAction(items[i]); + } + } + return mac_menubar->menu; +} + +/*! + \internal + + This function will return the OSMenuRef used to create the native menu bar + bindings. This OSMenuRef is then set as the root menu for the Menu + Manager. + + \warning This function is not portable. + + \sa QMenu::macMenu() +*/ +OSMenuRef QMenuBar::macMenu() { return d_func()->macMenu(); } + +/* ! + \internal + Ancestor function that crosses windows (QWidget::isAncestorOf + only considers widgets within the same window). +*/ +static bool qt_mac_is_ancestor(QWidget* possibleAncestor, QWidget *child) +{ + QWidget * current = child->parentWidget(); + while (current != 0) { + if (current == possibleAncestor) + return true; + current = current->parentWidget(); + } + return false; +} + +/* ! + \internal + Returns true if the entries of menuBar should be disabled, + based on the modality type of modalWidget. +*/ +static bool qt_mac_should_disable_menu(QMenuBar *menuBar, QWidget *modalWidget) +{ + if (modalWidget == 0 || menuBar == 0) + return false; + const Qt::WindowModality modality = modalWidget->windowModality(); + if (modality == Qt::ApplicationModal) { + return true; + } else if (modality == Qt::WindowModal) { + QWidget * parent = menuBar->parentWidget(); + + // Special case for the global menu bar: It's not associated + // with a window so don't disable it. + if (parent == 0) + return false; + + // Disable menu entries in menu bars that belong to ancestors of + // the modal widget, leave entries in unrelated menu bars enabled. + return qt_mac_is_ancestor(parent, modalWidget); + } + return false; // modality == NonModal +} + +static void cancelAllMenuTracking() +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSMenu *mainMenu = [NSApp mainMenu]; + [mainMenu cancelTracking]; + for (NSMenuItem *item in [mainMenu itemArray]) { + if ([item submenu]) { + [[item submenu] cancelTracking]; + } + } +#endif +} + +/*! + \internal + + This function will update the current menu bar and set it as the + active menu bar in the Menu Manager. + + \warning This function is not portable. + + \sa QMenu::macMenu(), QMenuBar::macMenu() +*/ +bool QMenuBar::macUpdateMenuBar() +{ + if (qt_mac_no_native_menubar) //nothing to be done.. + return true; + + cancelAllMenuTracking(); + QMenuBar *mb = 0; + //find a menu bar + QWidget *w = qApp->activeWindow(); + + if (!w) { + QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); ++i) { + QWidget *tlw = tlws.at(i); + if ((tlw->isVisible() && tlw->windowType() != Qt::Tool && + tlw->windowType() != Qt::Popup)) { + w = tlw; + break; + } + } + } + if (w) { + mb = menubars()->value(w); +#ifndef QT_NO_MAINWINDOW + QDockWidget *dw = qobject_cast<QDockWidget *>(w); + if (!mb && dw) { + QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget()); + if (mw && (mb = menubars()->value(mw))) + w = mw; + } +#endif + while(w && !mb) + mb = menubars()->value((w = w->parentWidget())); + } + if (!mb) + mb = fallback; + //now set it + bool ret = false; + if (mb) { +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#endif + if (OSMenuRef menu = mb->macMenu()) { +#ifndef QT_MAC_USE_COCOA + SetRootMenu(menu); +#else + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + [loader ensureAppMenuInMenu:menu]; + [NSApp setMainMenu:menu]; + syncMenuBarItemsVisiblity(mb->d_func()->mac_menubar); +#endif + QWidget *modalWidget = qApp->activeModalWidget(); + if (mb != menubars()->value(modalWidget)) { + qt_mac_set_modal_state(menu, qt_mac_should_disable_menu(mb, modalWidget)); + } + } + qt_mac_current_menubar.qmenubar = mb; + qt_mac_current_menubar.modal = QApplicationPrivate::modalState(); + ret = true; + } else if (qt_mac_current_menubar.qmenubar) { + const bool modal = QApplicationPrivate::modalState(); + if (modal != qt_mac_current_menubar.modal) { + ret = true; + if (OSMenuRef menu = qt_mac_current_menubar.qmenubar->macMenu()) { +#ifndef QT_MAC_USE_COCOA + SetRootMenu(menu); +#else + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + [loader ensureAppMenuInMenu:menu]; + [NSApp setMainMenu:menu]; + syncMenuBarItemsVisiblity(qt_mac_current_menubar.qmenubar->d_func()->mac_menubar); +#endif + QWidget *modalWidget = qApp->activeModalWidget(); + if (qt_mac_current_menubar.qmenubar != menubars()->value(modalWidget)) { + qt_mac_set_modal_state(menu, qt_mac_should_disable_menu(mb, modalWidget)); + } + } + qt_mac_current_menubar.modal = modal; + } + } + if(!ret) + qt_mac_clear_menubar(); + return ret; +} + +QHash<OSMenuRef, OSMenuRef> QMenuPrivate::mergeMenuHash; +QHash<OSMenuRef, QMenuMergeList*> QMenuPrivate::mergeMenuItemsHash; + +bool QMenuPrivate::QMacMenuPrivate::merged(const QAction *action) const +{ +#ifndef QT_MAC_USE_COCOA + MenuRef merge = 0; + GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, + sizeof(merge), 0, &merge); + if (merge) { + QMenuMergeList *list = 0; + if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, + sizeof(list), 0, &list) == noErr && list) { + for(int i = 0; i < list->size(); ++i) { + QMenuMergeItem item = list->at(i); + if (item.action->action == action) + return true; + } + } + } +#else + if (OSMenuRef merge = mergeMenuHash.value(menu)) { + if (QMenuMergeList *list = mergeMenuItemsHash.value(merge)) { + for(int i = 0; i < list->size(); ++i) { + const QMenuMergeItem &item = list->at(i); + if (item.action->action == action) + return true; + } + } + } +#endif + return false; +} + +//creation of the OSMenuRef +static OSMenuRef qt_mac_create_menu(QWidget *w) +{ + OSMenuRef ret; +#ifndef QT_MAC_USE_COCOA + ret = 0; + if (CreateNewMenu(0, 0, &ret) == noErr) { + qt_mac_create_menu_event_handler(); + SetMenuItemProperty(ret, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(w), &w); + + // kEventMenuMatchKey is only sent to the menu itself and not to + // the application, install a separate handler for that event. + EventHandlerRef eventHandlerRef; + InstallMenuEventHandler(ret, qt_mac_menu_event, + GetEventTypeCount(menu_menu_events), + menu_menu_events, 0, &eventHandlerRef); + menu_eventHandlers_hash()->insert(ret, eventHandlerRef); + } else { + qWarning("QMenu: Internal error"); + } +#else + if (QMenu *qmenu = qobject_cast<QMenu *>(w)){ + ret = [[QT_MANGLE_NAMESPACE(QCocoaMenu) alloc] initWithQMenu:qmenu]; + } else { + ret = [[NSMenu alloc] init]; + } +#endif + return ret; +} + + + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qmenu_p.h b/src/gui/widgets/qmenu_p.h new file mode 100644 index 0000000..e3c4890 --- /dev/null +++ b/src/gui/widgets/qmenu_p.h @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMENU_P_H +#define QMENU_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 "QtGui/qmenubar.h" +#include "QtGui/qstyleoption.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qmap.h" +#include "QtCore/qhash.h" +#include "QtCore/qbasictimer.h" +#include "private/qwidget_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_MENU + +class QTornOffMenu; +class QEventLoop; + +#ifdef Q_WS_MAC +# ifdef __OBJC__ +QT_END_NAMESPACE +@class NSMenuItem; +QT_BEGIN_NAMESPACE +# else +typedef void NSMenuItem; +# endif //__OBJC__ +struct QMacMenuAction { + QMacMenuAction() +#ifndef QT_MAC_USE_COCOA + : command(0) +#else + : menuItem(0) +#endif + , ignore_accel(0), merged(0), menu(0) + { + } + ~QMacMenuAction(); +#ifndef QT_MAC_USE_COCOA + uint command; +#else + NSMenuItem *menuItem; +#endif + uchar ignore_accel : 1; + uchar merged : 1; + QPointer<QAction> action; + OSMenuRef menu; +}; + +struct QMenuMergeItem +{ +#ifndef QT_MAC_USE_COCOA + inline QMenuMergeItem(MenuCommand c, QMacMenuAction *a) : command(c), action(a) { } + MenuCommand command; +#else + inline QMenuMergeItem(NSMenuItem *c, QMacMenuAction *a) : menuItem(c), action(a) { } + NSMenuItem *menuItem; +#endif + QMacMenuAction *action; +}; +typedef QList<QMenuMergeItem> QMenuMergeList; +#endif + +#ifdef Q_OS_WINCE +struct QWceMenuAction { + uint command; + QPointer<QAction> action; + HMENU menuHandle; + QWceMenuAction() : menuHandle(0), command(0) {} +}; +#endif + +class QMenuPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QMenu) +public: + QMenuPrivate() : itemsDirty(0), maxIconWidth(0), tabWidth(0), ncols(0), + collapsibleSeparators(true), hasHadMouse(0), aboutToHide(0), motions(0), + currentAction(0), scroll(0), eventLoop(0), tearoff(0), tornoff(0), tearoffHighlighted(0), + hasCheckableItems(0), sloppyAction(0) +#ifdef Q_WS_MAC + ,mac_menu(0) +#endif +#if defined(Q_OS_WINCE) && !defined(QT_NO_MENUBAR) + ,wce_menu(0) +#endif +#ifdef QT3_SUPPORT + ,emitHighlighted(false) +#endif + { } + ~QMenuPrivate() + { + delete scroll; +#ifdef Q_WS_MAC + delete mac_menu; +#endif +#if defined(Q_OS_WINCE) && !defined(QT_NO_MENUBAR) + delete wce_menu; +#endif + } + void init(); + + //item calculations + mutable uint itemsDirty : 1; + mutable uint maxIconWidth, tabWidth; + QRect actionRect(QAction *) const; + mutable QMap<QAction*, QRect> actionRects; + mutable QList<QAction*> actionList; + mutable QHash<QAction *, QWidget *> widgetItems; + void calcActionRects(QMap<QAction*, QRect> &actionRects, QList<QAction*> &actionList) const; + void updateActions(); + QRect popupGeometry(int screen=-1) const; + QList<QAction *> filterActions(const QList<QAction *> &actions) const; + uint ncols : 4; //4 bits is probably plenty + uint collapsibleSeparators : 1; + + uint activationRecursionGuard : 1; + + //selection + static QPointer<QMenu> mouseDown; + QPoint mousePopupPos; + uint hasHadMouse : 1; + uint aboutToHide : 1; + int motions; + QAction *currentAction; + static QBasicTimer menuDelayTimer; + enum SelectionReason { + SelectedFromKeyboard, + SelectedFromElsewhere + }; + QAction *actionAt(QPoint p) const; + void setFirstActionActive(); + void setCurrentAction(QAction *, int popup = -1, SelectionReason reason = SelectedFromElsewhere, bool activateFirst = false); + void popupAction(QAction *, int, bool); + void setSyncAction(); + + //scrolling support + struct QMenuScroller { + enum ScrollLocation { ScrollStay, ScrollBottom, ScrollTop, ScrollCenter }; + enum ScrollDirection { ScrollNone=0, ScrollUp=0x01, ScrollDown=0x02 }; + uint scrollFlags : 2, scrollDirection : 2; + int scrollOffset; + QBasicTimer *scrollTimer; + + QMenuScroller() : scrollFlags(ScrollNone), scrollDirection(ScrollNone), scrollOffset(0), scrollTimer(0) { } + ~QMenuScroller() { delete scrollTimer; } + } *scroll; + void scrollMenu(QMenuScroller::ScrollLocation location, bool active=false); + void scrollMenu(QMenuScroller::ScrollDirection direction, bool page=false, bool active=false); + void scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active=false); + + //synchronous operation (ie exec()) + QEventLoop *eventLoop; + QPointer<QAction> syncAction; + + //search buffer + QString searchBuffer; + QBasicTimer searchBufferTimer; + + //passing of mouse events up the parent heirarchy + QPointer<QMenu> activeMenu; + bool mouseEventTaken(QMouseEvent *); + + //used to walk up the popup list + struct QMenuCaused { + QPointer<QWidget> widget; + QPointer<QAction> action; + }; + virtual QList<QPointer<QWidget> > calcCausedStack() const; + QMenuCaused causedPopup; + void hideUpToMenuBar(); + void hideMenu(QMenu *menu); + + //index mappings + inline QAction *actionAt(int i) const { return q_func()->actions().at(i); } + inline int indexOf(QAction *act) const { return q_func()->actions().indexOf(act); } + + //tear off support + uint tearoff : 1, tornoff : 1, tearoffHighlighted : 1; + QPointer<QTornOffMenu> tornPopup; + + mutable bool hasCheckableItems; + + //sloppy selection + static QBasicTimer sloppyDelayTimer; + QAction *sloppyAction; + QRegion sloppyRegion; + + //default action + QPointer<QAction> defaultAction; + + QAction *menuAction; + QAction *defaultMenuAction; + + void setOverrideMenuAction(QAction *); + void _q_overrideMenuActionDestroyed(); + + //firing of events + void activateAction(QAction *, QAction::ActionEvent, bool self=true); + void activateCausedStack(const QList<QPointer<QWidget> > &, QAction *, QAction::ActionEvent, bool); + + void _q_actionTriggered(); + void _q_actionHovered(); + + bool hasMouseMoved(const QPoint &globalPos); + + //menu fading/scrolling effects + bool doChildEffects; + +#ifdef Q_WS_MAC + //mac menu binding + struct QMacMenuPrivate { + QList<QMacMenuAction*> actionItems; + OSMenuRef menu; + QMacMenuPrivate(); + ~QMacMenuPrivate(); + + bool merged(const QAction *action) const; + void addAction(QAction *, QMacMenuAction* =0, QMenuPrivate *qmenu = 0); + void addAction(QMacMenuAction *, QMacMenuAction* =0, QMenuPrivate *qmenu = 0); + void syncAction(QMacMenuAction *); + inline void syncAction(QAction *a) { syncAction(findAction(a)); } + void removeAction(QMacMenuAction *); + inline void removeAction(QAction *a) { removeAction(findAction(a)); } + inline QMacMenuAction *findAction(QAction *a) { + for(int i = 0; i < actionItems.size(); i++) { + QMacMenuAction *act = actionItems[i]; + if(a == act->action) + return act; + } + return 0; + } + } *mac_menu; + OSMenuRef macMenu(OSMenuRef merge); + void setMacMenuEnabled(bool enable = true); + static QHash<OSMenuRef, OSMenuRef> mergeMenuHash; + static QHash<OSMenuRef, QMenuMergeList*> mergeMenuItemsHash; +#endif + + QPointer<QAction> actionAboutToTrigger; +#ifdef QT3_SUPPORT + bool emitHighlighted; +#endif + +#if defined(Q_OS_WINCE) && !defined(QT_NO_MENUBAR) + struct QWceMenuPrivate { + QList<QWceMenuAction*> actionItems; + HMENU menuHandle; + QWceMenuPrivate(); + ~QWceMenuPrivate(); + void addAction(QAction *, QWceMenuAction* =0); + void addAction(QWceMenuAction *, QWceMenuAction* =0); + void syncAction(QWceMenuAction *); + inline void syncAction(QAction *a) { syncAction(findAction(a)); } + void removeAction(QWceMenuAction *); + void rebuild(bool reCreate = false); + inline void removeAction(QAction *a) { removeAction(findAction(a)); } + inline QWceMenuAction *findAction(QAction *a) { + for(int i = 0; i < actionItems.size(); i++) { + QWceMenuAction *act = actionItems[i]; + if(a == act->action) + return act; + } + return 0; + } + } *wce_menu; + HMENU wceMenu(bool create = false); + QAction* wceCommands(uint command); +#endif + + QPointer<QWidget> noReplayFor; +}; + +#endif // QT_NO_MENU + +QT_END_NAMESPACE + +#endif // QMENU_P_H diff --git a/src/gui/widgets/qmenu_wince.cpp b/src/gui/widgets/qmenu_wince.cpp new file mode 100644 index 0000000..ea58d46 --- /dev/null +++ b/src/gui/widgets/qmenu_wince.cpp @@ -0,0 +1,608 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//Native menubars are only supported for Windows Mobile not the standard SDK/generic WinCE +#ifdef Q_OS_WINCE +#include "qmenu.h" +#include "qt_windows.h" +#include "qapplication.h" +#include "qmainwindow.h" +#include "qtoolbar.h" +#include "qevent.h" +#include "qstyle.h" +#include "qdebug.h" +#include "qwidgetaction.h" +#include <private/qapplication_p.h> +#include <private/qmenu_p.h> +#include <private/qmenubar_p.h> + +#include "qmenu_wince_resource_p.h" + +#include <QtCore/qlibrary.h> +#include <commctrl.h> + +#include "qguifunctions_wince.h" + +#ifndef QT_NO_MENUBAR + +#ifndef SHCMBF_EMPTYBAR +#define SHCMBF_EMPTYBAR 0x0001 +#endif + +#ifndef SHCMBM_GETSUBMENU +#define SHCMBM_GETSUBMENU (WM_USER + 401) +#endif + +extern bool qt_wince_is_smartphone();//defined in qguifunctions_wce.cpp +extern bool qt_wince_is_pocket_pc(); //defined in qguifunctions_wce.cpp + +QT_BEGIN_NAMESPACE + +static uint qt_wce_menu_static_cmd_id = 200; +static QList<QMenuBar*> nativeMenuBars; + +struct qt_SHMENUBARINFO +{ + DWORD cbSize; + HWND hwndParent; + DWORD dwFlags; + UINT nToolBarId; + HINSTANCE hInstRes; + int nBmpId; + int cBmpImages; + HWND hwndMB; + COLORREF clrBk; +}; + +typedef int (WINAPI *superfunc)(int, int); +typedef BOOL (WINAPI *AygCreateMenuBar)(qt_SHMENUBARINFO*); +typedef HRESULT (WINAPI *AygEnableSoftKey)(HWND,UINT,BOOL,BOOL); + +static bool aygResolved = false; +static AygCreateMenuBar ptrCreateMenuBar = 0; +static AygEnableSoftKey ptrEnableSoftKey = 0; + +static void resolveAygLibs() +{ + if (!aygResolved) { + aygResolved = true; + QLibrary aygLib(QLatin1String("aygshell")); + if (!aygLib.load()) + return; + ptrCreateMenuBar = (AygCreateMenuBar) aygLib.resolve("SHCreateMenuBar"); + ptrEnableSoftKey = (AygEnableSoftKey) aygLib.resolve("SHEnableSoftkey"); + } +} + +static void qt_wce_enable_soft_key(HWND handle, uint command) +{ + resolveAygLibs(); + if (ptrEnableSoftKey) + ptrEnableSoftKey(handle, command, false, true); +} +static void qt_wce_disable_soft_key(HWND handle, uint command) +{ + resolveAygLibs(); + if (ptrEnableSoftKey) + ptrEnableSoftKey(handle, command, false, false); +} + +static void qt_wce_delete_action_list(QList<QWceMenuAction*> *list) { + for(QList<QWceMenuAction*>::Iterator it = list->begin(); it != list->end(); ++it) { + QWceMenuAction *action = (*it); + delete action; + action = 0; + } + list->clear(); +} + +//search for first QuitRole in QMenuBar +static QAction* qt_wce_get_quit_action(QList<QAction *> actionItems) { + QAction *returnAction = 0; + for (int i = 0; i < actionItems.size(); ++i) { + QAction *action = actionItems.at(i); + if (action->menuRole() == QAction::QuitRole) + returnAction = action; + else + if (action->menu()) + returnAction = qt_wce_get_quit_action(action->menu()->actions()); + if (returnAction) + return returnAction; //return first action found + } + return 0; //nothing found; +} + +static QAction* qt_wce_get_quit_action(QList<QWceMenuAction*> actionItems) { + for (int i = 0; i < actionItems.size(); ++i) { + if (actionItems.at(i)->action->menuRole() == QAction::QuitRole) + return actionItems.at(i)->action; + else if (actionItems.at(i)->action->menu()) { + QAction *returnAction = qt_wce_get_quit_action(actionItems.at(i)->action->menu()->actions()); + if (returnAction) + return returnAction; + } + } + return 0; +} + +static HMODULE qt_wce_get_module_handle() { + HMODULE module = 0; //handle to resources + if (!(module = GetModuleHandle(L"QtGui4"))) //release dynamic + if (!(module = GetModuleHandle(L"QtGuid4"))) //debug dynamic + module = (HINSTANCE)qWinAppInst(); //static + Q_ASSERT_X(module, "qt_wce_get_module_handle()", "cannot get handle to module?"); + return module; +} + +static void qt_wce_change_command(HWND menuHandle, int item, int command) { +TBBUTTONINFOA tbbi; + memset(&tbbi,0,sizeof(tbbi)); + tbbi.cbSize = sizeof(tbbi); + tbbi.dwMask = TBIF_COMMAND; + tbbi.idCommand = command; + SendMessage(menuHandle, TB_SETBUTTONINFO, item, (LPARAM)&tbbi); +} + +static void qt_wce_rename_menu_item(HWND menuHandle, int item, const QString &newText) { + TBBUTTONINFOA tbbi; + memset(&tbbi,0,sizeof(tbbi)); + tbbi.cbSize = sizeof(tbbi); + tbbi.dwMask = TBIF_TEXT; + QString text = newText; + text.remove(QChar::fromLatin1('&')); + tbbi.pszText = (LPSTR) text.utf16(); + SendMessage(menuHandle, TB_SETBUTTONINFO, item, (LPARAM)&tbbi); +} + +static HWND qt_wce_create_menubar(HWND parentHandle, HINSTANCE resourceHandle, int toolbarID, int flags = 0) { + resolveAygLibs(); + + if (ptrCreateMenuBar) { + qt_SHMENUBARINFO mbi; + memset(&mbi, 0, sizeof(qt_SHMENUBARINFO)); + mbi.cbSize = sizeof(qt_SHMENUBARINFO); + mbi.hwndParent = parentHandle; + mbi.hInstRes = resourceHandle; + mbi.dwFlags = flags; + mbi.nToolBarId = toolbarID; + + if (ptrCreateMenuBar(&mbi)) + return mbi.hwndMB; + } + return 0; +} + +static void qt_wce_insert_action(HMENU menu, QWceMenuAction *action, bool created) { + + Q_ASSERT_X(menu, "AppendMenu", "menu is 0"); + if (action->action->isVisible()) { + int flags; + action->action->isEnabled() ? flags = MF_ENABLED : flags = MF_GRAYED; + + QString text = action->action->iconText(); + text.remove(QChar::fromLatin1('&')); + if (action->action->isSeparator()) { + AppendMenu (menu, MF_SEPARATOR , 0, 0); + } + else if (action->action->menu()) { + text.remove(QChar::fromLatin1('&')); + AppendMenu (menu, MF_STRING | flags | MF_POPUP, + (UINT) action->action->menu()->wceMenu(created), reinterpret_cast<const wchar_t *> (text.utf16())); + } + else { + AppendMenu (menu, MF_STRING | flags, action->command, reinterpret_cast<const wchar_t *> (text.utf16())); + } + if (action->action->isCheckable()) + if (action->action->isChecked()) + CheckMenuItem(menu, action->command, MF_BYCOMMAND | MF_CHECKED); + else + CheckMenuItem(menu, action->command, MF_BYCOMMAND | MF_UNCHECKED); + } +} + +/*! + \internal + + This function refreshes the native Windows CE menu. +*/ + +void QMenuBar::wceRefresh() { + for (int i = 0; i < nativeMenuBars.size(); ++i) + nativeMenuBars.at(i)->d_func()->wceRefresh(); +} + +void QMenuBarPrivate::wceRefresh() { + DrawMenuBar(wce_menubar->menubarHandle); +} + +/*! + \internal + + This function sends native Windows CE commands to Qt menus. +*/ + +QAction* QMenu::wceCommands(uint command) { + Q_D(QMenu); + return d->wceCommands(command); +} + +/*! + \internal + + This function sends native Windows CE commands to Qt menu bars + and all their child menus. +*/ + +void QMenuBar::wceCommands(uint command, HWND) { + for (int i = 0; i < nativeMenuBars.size(); ++i) + nativeMenuBars.at(i)->d_func()->wceCommands(command); +} + +bool QMenuBarPrivate::wceEmitSignals(QList<QWceMenuAction*> actions, uint command) { + QAction *foundAction = 0; + for (int i = 0; i < actions.size(); ++i) { + if (foundAction) + break; + QWceMenuAction *action = actions.at(i); + if (action->action->menu()) { + foundAction = action->action->menu()->wceCommands(command); + } + else if (action->command == command) { + emit q_func()->triggered(action->action); + action->action->activate(QAction::Trigger); + return true; + } + } + if (foundAction) { + emit q_func()->triggered(foundAction); + return true; + } + return false; +} + +void QMenuBarPrivate::wceCommands(uint command) { + if (wceClassicMenu) { + for (int i = 0; i < wce_menubar->actionItemsClassic.size(); ++i) + wceEmitSignals(wce_menubar->actionItemsClassic.at(i), command); + } else { + if (wceEmitSignals(wce_menubar->actionItems, command)) { + return ; + } + else if (wce_menubar->leftButtonIsMenu) {//check if command is on the left quick button + wceEmitSignals(wce_menubar->actionItemsLeftButton, command); + } + else if ((wce_menubar->leftButtonAction) && (command == wce_menubar->leftButtonCommand)) { + emit q_func()->triggered(wce_menubar->leftButtonAction); + wce_menubar->leftButtonAction->activate(QAction::Trigger); + } + } +} + +QAction *QMenuPrivate::wceCommands(uint command) { + QAction *foundAction = 0; + for (int i = 0; i < wce_menu->actionItems.size(); ++i) { + if (foundAction) + break; + QWceMenuAction *action = wce_menu->actionItems.at(i); + if (action->action->menu()) { + foundAction = action->action->menu()->d_func()->wceCommands(command); + } + else if (action->command == command) { + action->action->activate(QAction::Trigger); + return action->action; + } + } + if (foundAction) + emit q_func()->triggered(foundAction); + return foundAction; +} + +void QMenuBarPrivate::wceCreateMenuBar(QWidget *parent) { + + Q_Q(QMenuBar); + wce_menubar = new QWceMenuBarPrivate(this); + + wce_menubar->parentWindowHandle = parent ? parent->winId() : q->winId(); + wce_menubar->leftButtonAction = defaultAction; + + wce_menubar->menubarHandle = qt_wce_create_menubar(wce_menubar->parentWindowHandle, (HINSTANCE)qWinAppInst(), 0, SHCMBF_EMPTYBAR); + Q_ASSERT_X(wce_menubar->menubarHandle, "wceCreateMenuBar", "cannot create empty menu bar"); + DrawMenuBar(wce_menubar->menubarHandle); + nativeMenuBars.append(q); + wceClassicMenu = (!qt_wince_is_smartphone() && !qt_wince_is_pocket_pc()); +} + +void QMenuBarPrivate::wceDestroyMenuBar() { + Q_Q(QMenuBar); + int index = nativeMenuBars.indexOf(q); + nativeMenuBars.removeAt(index); + if (wce_menubar) + delete wce_menubar; + wce_menubar = 0; +} + +QMenuBarPrivate::QWceMenuBarPrivate::QWceMenuBarPrivate(QMenuBarPrivate *menubar) : + menubarHandle(0), menuHandle(0),leftButtonMenuHandle(0) , + leftButtonAction(0), leftButtonIsMenu(false), d(menubar) { +} + +QMenuBarPrivate::QWceMenuBarPrivate::~QWceMenuBarPrivate() { + if (menubarHandle) + DestroyWindow(menubarHandle); + qt_wce_delete_action_list(&actionItems); + qt_wce_delete_action_list(&actionItemsLeftButton); + + for (int i=0; i<actionItemsClassic.size(); ++i) + if (!actionItemsClassic.value(i).empty()) + qt_wce_delete_action_list(&actionItemsClassic[i]); + actionItemsClassic.clear(); + + menubarHandle = 0; + menuHandle = 0; + leftButtonMenuHandle = 0; + leftButtonCommand = 0; + QMenuBar::wceRefresh(); +} + +QMenuPrivate::QWceMenuPrivate::QWceMenuPrivate() { + menuHandle = 0; +} + +QMenuPrivate::QWceMenuPrivate::~QWceMenuPrivate() { + qt_wce_delete_action_list(&actionItems); + menuHandle = 0; +} + +void QMenuPrivate::QWceMenuPrivate::addAction(QAction *a, QWceMenuAction *before) { + QWceMenuAction *action = new QWceMenuAction; + action->action = a; + action->command = qt_wce_menu_static_cmd_id++; + addAction(action, before); +} + +void QMenuPrivate::QWceMenuPrivate::addAction(QWceMenuAction *action, QWceMenuAction *before) { + if (!action) + return; + int before_index = actionItems.indexOf(before); + if (before_index < 0) { + before = 0; + before_index = actionItems.size(); + } + actionItems.insert(before_index, action); + rebuild(); +} + +/*! + \internal + + This function will return the HMENU used to create the native + Windows CE menu bar bindings. +*/ + +HMENU QMenu::wceMenu(bool create) { return d_func()->wceMenu(create); } + +HMENU QMenuPrivate::wceMenu(bool create) { + if (!wce_menu) + wce_menu = new QWceMenuPrivate; + if (!wce_menu->menuHandle || create) + wce_menu->rebuild(create); + return wce_menu->menuHandle; +} + +void QMenuPrivate::QWceMenuPrivate::rebuild(bool reCreate) { + if (menuHandle && !reCreate) + DestroyMenu(menuHandle); + menuHandle = CreatePopupMenu(); + for (int i = 0; i < actionItems.size(); ++i) { + QWceMenuAction *action = actionItems.at(i); + action->menuHandle = menuHandle; + qt_wce_insert_action(menuHandle, action, true); + } + QMenuBar::wceRefresh(); +} + +void QMenuPrivate::QWceMenuPrivate::syncAction(QWceMenuAction *) { + rebuild(); +} + +void QMenuPrivate::QWceMenuPrivate::removeAction(QWceMenuAction *action) { + actionItems.removeAll(action); + delete action; + action = 0; + rebuild(); +} + +void QMenuBarPrivate::QWceMenuBarPrivate::addAction(QAction *a, QWceMenuAction *before) { + QWceMenuAction *action = new QWceMenuAction; + action->action = a; + action->command = qt_wce_menu_static_cmd_id++; + addAction(action, before); +} + +void QMenuBarPrivate::QWceMenuBarPrivate::addAction(QWceMenuAction *action, QWceMenuAction *before) { + if (!action) + return; + int before_index = actionItems.indexOf(before); + if (before_index < 0) { + before = 0; + before_index = actionItems.size(); + } + actionItems.insert(before_index, action); + rebuild(); +} + +void QMenuBarPrivate::QWceMenuBarPrivate::syncAction(QWceMenuAction*) { + QMenuBar::wceRefresh(); + rebuild(); +} + +void QMenuBarPrivate::QWceMenuBarPrivate::removeAction(QWceMenuAction *action) { + actionItems.removeAll(action); + delete action; + action = 0; + rebuild(); +} + +void QMenuBarPrivate::_q_updateDefaultAction() { + if (wce_menubar) + wce_menubar->rebuild(); +} + +void QMenuBarPrivate::QWceMenuBarPrivate::rebuild() { + + d->q_func()->resize(0,0); + parentWindowHandle = d->q_func()->parentWidget() ? d->q_func()->parentWidget()->winId() : d->q_func()->winId(); + if (d->wceClassicMenu) { + QList<QAction*> actions = d->actions; + int maxEntries; + int resourceHandle; + if (actions.size() < 5) { + maxEntries = 4; + resourceHandle = IDR_MAIN_MENU3; + } else if (actions.size() < 7) { + maxEntries = 6; + resourceHandle = IDR_MAIN_MENU4; + } + else { + maxEntries = 8; + resourceHandle = IDR_MAIN_MENU5; + } + Q_ASSERT_X(menubarHandle, "rebuild !created", "menubar already deleted"); + DestroyWindow(menubarHandle); + menubarHandle = qt_wce_create_menubar(parentWindowHandle, qt_wce_get_module_handle(), resourceHandle); + Q_ASSERT_X(menubarHandle, "rebuild classic menu", "cannot create menubar from resource"); + DrawMenuBar(menubarHandle); + QList<int> menu_ids; + QList<int> item_ids; + menu_ids << IDM_MENU1 << IDM_MENU2 << IDM_MENU3 << IDM_MENU4 << IDM_MENU5 << IDM_MENU6 << IDM_MENU7 << IDM_MENU8; + item_ids << IDM_ITEM1 << IDM_ITEM2 << IDM_ITEM3 << IDM_ITEM4 << IDM_ITEM5 << IDM_ITEM6 << IDM_ITEM7 << IDM_ITEM8; + + for (int i = 0; i < actionItemsClassic.size(); ++i) + if (!actionItemsClassic.value(i).empty()) + qt_wce_delete_action_list(&actionItemsClassic[i]); + actionItemsClassic.clear(); + + for (int i = 0; i < actions.size(); ++i) { + qt_wce_rename_menu_item(menubarHandle, menu_ids.at(i), actions.at(i)->text()); + QList<QAction *> subActions = actions.at(i)->menu()->actions(); + HMENU subMenuHandle = (HMENU) SendMessage(menubarHandle, SHCMBM_GETSUBMENU,0 , menu_ids.at(i)); + DeleteMenu(subMenuHandle, item_ids.at(i), MF_BYCOMMAND); + for (int c = 0; c < subActions.size(); ++c) { + QList<QWceMenuAction*> list; + actionItemsClassic.append(list); + QWceMenuAction *action = new QWceMenuAction; + action->action = subActions.at(c); + action->command = qt_wce_menu_static_cmd_id++; + action->menuHandle = subMenuHandle; + actionItemsClassic.last().append(action); + qt_wce_insert_action(subMenuHandle, action, true); + } + } + for (int i = actions.size();i<maxEntries;++i) { + qt_wce_rename_menu_item(menubarHandle, menu_ids.at(i), QString()); + qt_wce_disable_soft_key(menubarHandle, menu_ids.at(i)); + } + } else { + leftButtonAction = d->defaultAction; + if (!leftButtonAction) + leftButtonAction = qt_wce_get_quit_action(actionItems); + + leftButtonIsMenu = (leftButtonAction && leftButtonAction->menu()); + Q_ASSERT_X(menubarHandle, "rebuild !created", "menubar already deleted"); + DestroyWindow(menubarHandle); + if (leftButtonIsMenu) { + menubarHandle = qt_wce_create_menubar(parentWindowHandle, qt_wce_get_module_handle(), IDR_MAIN_MENU2); + Q_ASSERT_X(menubarHandle, "rebuild !created left menubar", "cannot create menubar from resource"); + menuHandle = (HMENU) SendMessage(menubarHandle, SHCMBM_GETSUBMENU,0,IDM_MENU); + Q_ASSERT_X(menuHandle, "rebuild !created", "IDM_MENU not found - invalid resource?"); + DeleteMenu(menuHandle, IDM_ABOUT, MF_BYCOMMAND); + leftButtonMenuHandle = (HMENU) SendMessage(menubarHandle, SHCMBM_GETSUBMENU,0,IDM_LEFTMENU); + Q_ASSERT_X(leftButtonMenuHandle, "rebuild !created", "IDM_LEFTMENU not found - invalid resource?"); + DeleteMenu(leftButtonMenuHandle, IDM_VIEW, MF_BYCOMMAND); + } else { + menubarHandle = qt_wce_create_menubar(parentWindowHandle, qt_wce_get_module_handle(), IDR_MAIN_MENU); + Q_ASSERT_X(menubarHandle, "rebuild !created no left menubar", "cannot create menubar from resource"); + menuHandle = (HMENU) SendMessage(menubarHandle, SHCMBM_GETSUBMENU,0,IDM_MENU); + Q_ASSERT_X(menuHandle, "rebuild !created", "IDM_MENU not found - invalid resource?"); + DeleteMenu(menuHandle, IDM_ABOUT, MF_BYCOMMAND); + leftButtonMenuHandle = 0; + leftButtonCommand = qt_wce_menu_static_cmd_id++; + qt_wce_change_command(menubarHandle, IDM_EXIT, leftButtonCommand); + } + + if (actionItems.size() == 0) { + qt_wce_rename_menu_item(menubarHandle, IDM_MENU, QLatin1String("")); + qt_wce_disable_soft_key(menubarHandle, IDM_MENU); + } + for (int i = 0; i < actionItems.size(); ++i) { + QWceMenuAction *action = actionItems.at(i); + action->menuHandle = menuHandle; + qt_wce_insert_action(menuHandle, action, true); + } + if (!leftButtonIsMenu) { + if (leftButtonAction) { + qt_wce_rename_menu_item(menubarHandle, leftButtonCommand, leftButtonAction->text()); + qt_wce_enable_soft_key(menubarHandle, leftButtonCommand); + } else { + qt_wce_rename_menu_item(menubarHandle, leftButtonCommand, QLatin1String("")); + qt_wce_disable_soft_key(menubarHandle, leftButtonCommand); + } + } else { + qt_wce_rename_menu_item(menubarHandle, IDM_LEFTMENU, leftButtonAction->text()); + QList<QAction *> actions = leftButtonAction->menu()->actions(); + qt_wce_delete_action_list(&actionItemsLeftButton); + for (int i=0; i<actions.size(); ++i) { + QWceMenuAction *action = new QWceMenuAction; + action->action = actions.at(i); + action->command = qt_wce_menu_static_cmd_id++; + action->menuHandle = leftButtonMenuHandle; + actionItemsLeftButton.append(action); + qt_wce_insert_action(leftButtonMenuHandle, action, true); + } + } + } + DrawMenuBar(menubarHandle); +} + +QT_END_NAMESPACE + +#endif //QT_NO_MENUBAR +#endif //Q_OS_WINCE diff --git a/src/gui/widgets/qmenu_wince.rc b/src/gui/widgets/qmenu_wince.rc new file mode 100644 index 0000000..2540d9f --- /dev/null +++ b/src/gui/widgets/qmenu_wince.rc @@ -0,0 +1,231 @@ +#include "qmenu_wince_resource_p.h" + +#include <commctrl.h> +#include "winuser.h" + +#if defined (_DEBUG) && defined(QT_DLL) +#include "QtGuid_resource.rc" +#elif defined(QT_DLL) +#include "QtGui_resource.rc" +#endif + +#define DIALOGEX DIALOG DISCARDABLE +#define SHMENUBAR RCDATA +#define I_IMAGENONE (-2) +#define NOMENU 0xFFFF + +IDR_MAIN_MENU MENU DISCARDABLE +BEGIN + POPUP "Menu" + BEGIN + MENUITEM "About", IDM_ABOUT + END +END + +IDR_MAIN_MENU2 MENU DISCARDABLE +BEGIN + POPUP "Menu" + BEGIN + MENUITEM "About", IDM_ABOUT + END + POPUP "Display" + BEGIN + MENUITEM "View", IDM_VIEW + END +END + + +IDR_MAIN_MENU3 MENU DISCARDABLE +BEGIN + POPUP "Menu1" + BEGIN + MENUITEM "Item1", IDM_ITEM1 + END + POPUP "Menu2" + BEGIN + MENUITEM "Item2", IDM_ITEM2 + END + POPUP "Menu3" + BEGIN + MENUITEM "Item3", IDM_ITEM3 + END + POPUP "Menu4" + BEGIN + MENUITEM "Item4", IDM_ITEM4 + END +END + +IDR_MAIN_MENU4 MENU DISCARDABLE +BEGIN + POPUP "Menu1" + BEGIN + MENUITEM "Item1", IDM_ITEM1 + END + POPUP "Menu2" + BEGIN + MENUITEM "Item2", IDM_ITEM2 + END + POPUP "Menu3" + BEGIN + MENUITEM "Item3", IDM_ITEM3 + END + POPUP "Menu4" + BEGIN + MENUITEM "Item4", IDM_ITEM4 + END + POPUP "Menu5" + BEGIN + MENUITEM "Item5", IDM_ITEM5 + END + POPUP "Menu6" + BEGIN + MENUITEM "Item6", IDM_ITEM6 + END +END + +IDR_MAIN_MENU5 MENU DISCARDABLE +BEGIN + POPUP "Menu1" + BEGIN + MENUITEM "Item1", IDM_ITEM1 + END + POPUP "Menu2" + BEGIN + MENUITEM "Item2", IDM_ITEM2 + END + POPUP "Menu3" + BEGIN + MENUITEM "Item3", IDM_ITEM3 + END + POPUP "Menu4" + BEGIN + MENUITEM "Item4", IDM_ITEM4 + END + POPUP "Menu5" + BEGIN + MENUITEM "Item5", IDM_ITEM5 + END + POPUP "Menu6" + BEGIN + MENUITEM "Item6", IDM_ITEM6 + END + POPUP "Menu7" + BEGIN + MENUITEM "Item7", IDM_ITEM7 + END + POPUP "Menu8" + BEGIN + MENUITEM "Item8", IDM_ITEM8 + END +END + +STRINGTABLE +BEGIN + IDS_EXIT "Exit" + IDS_MENU "Menu" + IDS_LEFTMENU "Display" + IDS_MENU1 "Menu__1" + IDS_MENU2 "Menu__2" + IDS_MENU3 "Menu__3" + IDS_MENU4 "Menu__4" + IDS_MENU5 "Menu__5" + IDS_MENU6 "Menu__6" + IDS_MENU7 "Menu__7" + IDS_MENU8 "Menu__8" +END + +IDR_MAIN_MENU SHMENUBAR DISCARDABLE +BEGIN + IDR_MAIN_MENU, + 2, + + I_IMAGENONE, IDM_EXIT, TBSTATE_ENABLED, TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, + IDS_EXIT, 0, NOMENU, + + I_IMAGENONE, IDM_MENU, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU, 0, 0, +END + +IDR_MAIN_MENU2 SHMENUBAR DISCARDABLE +BEGIN + IDR_MAIN_MENU2, + 2, + + I_IMAGENONE, IDM_LEFTMENU, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_LEFTMENU, 0, 1, + + I_IMAGENONE, IDM_MENU, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU, 0, 0, +END + +IDR_MAIN_MENU3 SHMENUBAR DISCARDABLE +BEGIN + IDR_MAIN_MENU3, + 4, + + I_IMAGENONE, IDM_MENU1, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU1, 0, 0, + + I_IMAGENONE, IDM_MENU2, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU2, 0, 1, + + I_IMAGENONE, IDM_MENU3, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU3, 0, 2, + + I_IMAGENONE, IDM_MENU4, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU4, 0, 3, +END + +IDR_MAIN_MENU4 SHMENUBAR DISCARDABLE +BEGIN + IDR_MAIN_MENU4, + 6, + + I_IMAGENONE, IDM_MENU1, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU1, 0, 0, + + I_IMAGENONE, IDM_MENU2, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU2, 0, 1, + + I_IMAGENONE, IDM_MENU3, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU3, 0, 2, + + I_IMAGENONE, IDM_MENU4, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU4, 0, 3, + + I_IMAGENONE, IDM_MENU5, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU5, 0, 4, + + I_IMAGENONE, IDM_MENU6, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU6, 0, 5, +END + +IDR_MAIN_MENU5 SHMENUBAR DISCARDABLE +BEGIN + IDR_MAIN_MENU5, + 8, + + I_IMAGENONE, IDM_MENU1, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU1, 0, 0, + + I_IMAGENONE, IDM_MENU2, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU2, 0, 1, + + I_IMAGENONE, IDM_MENU3, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU3, 0, 2, + + I_IMAGENONE, IDM_MENU4, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU4, 0, 3, + + I_IMAGENONE, IDM_MENU5, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU5, 0, 4, + + I_IMAGENONE, IDM_MENU6, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU6, 0, 5, + + I_IMAGENONE, IDM_MENU7, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU7, 0, 6, + + I_IMAGENONE, IDM_MENU8, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, + IDS_MENU8, 0, 7, +END diff --git a/src/gui/widgets/qmenu_wince_resource_p.h b/src/gui/widgets/qmenu_wince_resource_p.h new file mode 100644 index 0000000..cc944f4 --- /dev/null +++ b/src/gui/widgets/qmenu_wince_resource_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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. +// + +QT_BEGIN_HEADER + +#define IDR_MAIN_MENU 102 +#define IDR_MAIN_MENU2 103 +#define IDR_MAIN_MENU3 104 +#define IDS_EXIT 105 +#define IDS_MENU 106 +#define IDS_LEFTMENU 107 +#define IDM_ABOUT 108 +#define IDM_VIEW 109 +#define IDM_ITEM1 108 +#define IDM_ITEM2 109 +#define IDM_ITEM3 110 +#define IDM_ITEM4 111 +#define IDM_ITEM5 112 +#define IDM_ITEM6 113 +#define IDM_ITEM7 114 +#define IDM_ITEM8 115 +#define IDS_MENU1 116 +#define IDS_MENU2 117 +#define IDS_MENU3 118 +#define IDS_MENU4 119 +#define IDS_MENU5 120 +#define IDS_MENU6 121 +#define IDS_MENU7 122 +#define IDS_MENU8 123 +#define IDR_MAIN_MENU4 124 +#define IDR_MAIN_MENU5 125 +#define IDM_EXIT 40000 +#define IDM_MENU 40001 +#define IDM_LEFTMENU 40002 +#define IDM_MENU1 40003 +#define IDM_MENU2 40004 +#define IDM_MENU3 40005 +#define IDM_MENU4 40006 +#define IDM_MENU5 40007 +#define IDM_MENU6 40008 +#define IDM_MENU7 40009 +#define IDM_MENU8 40010 + +QT_END_HEADER + diff --git a/src/gui/widgets/qmenubar.cpp b/src/gui/widgets/qmenubar.cpp new file mode 100644 index 0000000..ccf37db --- /dev/null +++ b/src/gui/widgets/qmenubar.cpp @@ -0,0 +1,2405 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qmenubar.h> + +#include <qstyle.h> +#include <qlayout.h> +#include <qapplication.h> +#include <qdesktopwidget.h> +#ifndef QT_NO_ACCESSIBILITY +# include <qaccessible.h> +#endif +#include <qpainter.h> +#include <qstylepainter.h> +#include <qevent.h> +#include <qmainwindow.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qwhatsthis.h> + +#ifndef QT_NO_MENUBAR + +#ifdef QT3_SUPPORT +#include <private/qaction_p.h> +#include <qmenudata.h> +#endif + +#include "qmenu_p.h" +#include "qmenubar_p.h" +#include "qdebug.h" + +#ifdef Q_OS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wce.cpp +#endif + +QT_BEGIN_NAMESPACE + +class QMenuBarExtension : public QToolButton +{ +public: + explicit QMenuBarExtension(QWidget *parent); + + QSize sizeHint() const; + void paintEvent(QPaintEvent *); +}; + +QMenuBarExtension::QMenuBarExtension(QWidget *parent) + : QToolButton(parent) +{ + setObjectName(QLatin1String("qt_menubar_ext_button")); + setAutoRaise(true); +#ifndef QT_NO_MENU + setPopupMode(QToolButton::InstantPopup); +#endif + setIcon(style()->standardIcon(QStyle::SP_ToolBarHorizontalExtensionButton, 0, parentWidget())); +} + +void QMenuBarExtension::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + // We do not need to draw both extention arrows + opt.features &= ~QStyleOptionToolButton::HasMenu; + p.drawComplexControl(QStyle::CC_ToolButton, opt); +} + + +QSize QMenuBarExtension::sizeHint() const +{ + int ext = style()->pixelMetric(QStyle::PM_ToolBarExtensionExtent, 0, parentWidget()); + return QSize(ext, ext); +} + + +/*! + \internal +*/ +QAction *QMenuBarPrivate::actionAt(QPoint p) const +{ + Q_Q(const QMenuBar); + QList<QAction*> items = q->actions(); + for(int i = 0; i < items.size(); ++i) { + if(actionRect(items.at(i)).contains(p)) + return items.at(i); + } + return 0; +} + +QRect QMenuBarPrivate::menuRect(bool extVisible) const +{ + Q_Q(const QMenuBar); + + int hmargin = q->style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, q); + QRect result = q->rect(); + result.adjust(hmargin, 0, -hmargin, 0); + + if (extVisible) { + if (q->layoutDirection() == Qt::RightToLeft) + result.setLeft(result.left() + extension->sizeHint().width()); + else + result.setWidth(result.width() - extension->sizeHint().width()); + } + + if (leftWidget && leftWidget->isVisible()) { + QSize sz = leftWidget->sizeHint(); + if (q->layoutDirection() == Qt::RightToLeft) + result.setRight(result.right() - sz.width()); + else + result.setLeft(result.left() + sz.width()); + } + + if (rightWidget && rightWidget->isVisible()) { + QSize sz = rightWidget->sizeHint(); + if (q->layoutDirection() == Qt::RightToLeft) + result.setLeft(result.left() + sz.width()); + else + result.setRight(result.right() - sz.width()); + } + + return result; +} + +bool QMenuBarPrivate::isVisible(QAction *action) +{ + return !hiddenActions.contains(action); +} + +void QMenuBarPrivate::updateGeometries() +{ + Q_Q(QMenuBar); + if(!itemsDirty) + return; + int q_width = q->width()-(q->style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, q)*2); + int q_start = -1; + if(leftWidget || rightWidget) { + int vmargin = q->style()->pixelMetric(QStyle::PM_MenuBarVMargin, 0, q) + + q->style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, q); + int hmargin = q->style()->pixelMetric(QStyle::PM_MenuBarHMargin, 0, q) + + q->style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, q); + if (leftWidget && leftWidget->isVisible()) { + QSize sz = leftWidget->sizeHint(); + q_width -= sz.width(); + q_start = sz.width(); + QPoint pos(hmargin, (q->height() - leftWidget->height()) / 2); + QRect vRect = QStyle::visualRect(q->layoutDirection(), q->rect(), QRect(pos, sz)); + leftWidget->setGeometry(vRect); + } + if (rightWidget && rightWidget->isVisible()) { + QSize sz = rightWidget->sizeHint(); + q_width -= sz.width(); + QPoint pos(q->width() - sz.width() - hmargin, vmargin); + QRect vRect = QStyle::visualRect(q->layoutDirection(), q->rect(), QRect(pos, sz)); + rightWidget->setGeometry(vRect); + } + } + +#ifdef Q_WS_MAC + if(mac_menubar) {//nothing to see here folks, move along.. + itemsDirty = false; + return; + } +#endif + calcActionRects(q_width, q_start, actionRects, actionList); + itemsWidth = q_width; + itemsStart = q_start; + currentAction = 0; +#ifndef QT_NO_SHORTCUT + if(itemsDirty) { + for(int j = 0; j < shortcutIndexMap.size(); ++j) + q->releaseShortcut(shortcutIndexMap.value(j)); + shortcutIndexMap.resize(0); // faster than clear + for(int i = 0; i < actionList.count(); i++) + shortcutIndexMap.append(q->grabShortcut(QKeySequence::mnemonic(actionList.at(i)->text()))); + } +#endif + itemsDirty = false; + + hiddenActions.clear(); + //this is the menu rectangle without any extension + QRect menuRect = this->menuRect(false); + + //we try to see if the actions will fit there + bool hasHiddenActions = false; + foreach(QAction *action, actionList) { + if (!menuRect.contains(actionRect(action))) { + hasHiddenActions = true; + break; + } + } + + //...and if not, determine the ones that fit on the menu with the extension visible + if (hasHiddenActions) { + menuRect = this->menuRect(true); + foreach(QAction *action, actionList) { + if (!menuRect.contains(actionRect(action))) { + hiddenActions.append(action); + } + } + } + + if (hiddenActions.count() > 0) { + QMenu *pop = extension->menu(); + if (!pop) { + pop = new QMenu(q); + extension->setMenu(pop); + } + pop->clear(); + pop->addActions(hiddenActions); + + int vmargin = q->style()->pixelMetric(QStyle::PM_MenuBarVMargin, 0, q); + int x = q->layoutDirection() == Qt::RightToLeft + ? menuRect.left() - extension->sizeHint().width() + 1 + : menuRect.right(); + extension->setGeometry(x, vmargin, extension->sizeHint().width(), menuRect.height() - vmargin*2); + extension->show(); + } else { + extension->hide(); + } + q->updateGeometry(); +#ifdef QT3_SUPPORT + if (q->parentWidget() != 0) { + QMenubarUpdatedEvent menubarUpdated(q); + QApplication::sendEvent(q->parentWidget(), &menubarUpdated); + } +#endif +} + +QRect QMenuBarPrivate::actionRect(QAction *act) const +{ + Q_Q(const QMenuBar); + QRect ret = actionRects.value(act); + const int fw = q->style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, q); + ret.translate(fw, fw); + return QStyle::visualRect(q->layoutDirection(), q->rect(), ret); +} + +void QMenuBarPrivate::setKeyboardMode(bool b) +{ + Q_Q(QMenuBar); + if (b && !q->style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, q)) { + setCurrentAction(0); + return; + } + keyboardState = b; + if(b) { + QWidget *fw = qApp->focusWidget(); + if (fw != q) + keyboardFocusWidget = fw; + if(!currentAction && !actionList.isEmpty()) + setCurrentAction(actionList.first()); + q->setFocus(Qt::MenuBarFocusReason); + } else { + if(!popupState) + setCurrentAction(0); + if(keyboardFocusWidget) { + if (qApp->focusWidget() == q) + keyboardFocusWidget->setFocus(Qt::MenuBarFocusReason); + keyboardFocusWidget = 0; + } + } + q->update(); +} + +void QMenuBarPrivate::popupAction(QAction *action, bool activateFirst) +{ + Q_Q(QMenuBar); + if(!action || !action->menu() || closePopupMode) + return; + popupState = true; + if (action->isEnabled() && action->menu()->isEnabled()) { + closePopupMode = 0; + activeMenu = action->menu(); + activeMenu->d_func()->causedPopup.widget = q; + activeMenu->d_func()->causedPopup.action = action; + + QRect adjustedActionRect = actionRect(action); + QPoint pos(q->mapToGlobal(QPoint(adjustedActionRect.left(), adjustedActionRect.bottom() + 1))); + QSize popup_size = activeMenu->sizeHint(); + + QRect screenRect = QApplication::desktop()->screenGeometry(pos); + + const bool fitUp = (q->mapToGlobal(adjustedActionRect.topLeft()).y() >= popup_size.height()); + const bool fitDown = (pos.y() + popup_size.height() <= screenRect.bottom()); + const int actionWidth = adjustedActionRect.width(); + + if (!fitUp && !fitDown) { //we should shift the menu + bool shouldShiftToRight = !q->isRightToLeft(); + if (q->isRightToLeft() && popup_size.width() > pos.x()) + shouldShiftToRight = true; + else if (actionWidth + popup_size.width() + pos.x() > screenRect.right()) + shouldShiftToRight = false; + + if (shouldShiftToRight) + pos.rx() += actionWidth; + else + pos.rx() -= popup_size.width(); + } else if (q->isRightToLeft()) { + pos.setX(pos.x()-(popup_size.width() - actionWidth)); + } + + if(pos.x() < screenRect.x()) { + pos.setX(screenRect.x()); + } else { + const int off = pos.x()+popup_size.width() - screenRect.right(); + if(off > 0) + pos.setX(qMax(screenRect.x(), pos.x()-off)); + + } + + if(!defaultPopDown || (fitUp && !fitDown)) + pos.setY(qMax(screenRect.y(), q->mapToGlobal(QPoint(0, adjustedActionRect.top()-popup_size.height())).y())); + activeMenu->popup(pos); + if(activateFirst) + activeMenu->d_func()->setFirstActionActive(); + } + q->update(actionRect(action)); +} + +void QMenuBarPrivate::setCurrentAction(QAction *action, bool popup, bool activateFirst) +{ + if(currentAction == action && popup == popupState) + return; + + autoReleaseTimer.stop(); + + doChildEffects = (popup && !activeMenu); + Q_Q(QMenuBar); + QWidget *fw = 0; + if(QMenu *menu = activeMenu) { + activeMenu = 0; + if (popup) { + fw = q->window()->focusWidget(); + q->setFocus(Qt::NoFocusReason); + } + menu->hide(); + } + + if(currentAction) + q->update(actionRect(currentAction)); + + popupState = popup; +#ifndef QT_NO_STATUSTIP + QAction *previousAction = currentAction; +#endif + currentAction = action; + if (action) { + activateAction(action, QAction::Hover); + if(popup) + popupAction(action, activateFirst); + q->update(actionRect(action)); +#ifndef QT_NO_STATUSTIP + } else if (previousAction) { + QString empty; + QStatusTipEvent tip(empty); + QApplication::sendEvent(q, &tip); +#endif + } + if (fw) + fw->setFocus(Qt::NoFocusReason); +} + +void QMenuBarPrivate::calcActionRects(int max_width, int start, QMap<QAction*, QRect> &actionRects, QList<QAction*> &actionList) const +{ + Q_Q(const QMenuBar); + + if(!itemsDirty && itemsWidth == max_width && itemsStart == start) { + actionRects = actionRects; + actionList = actionList; + return; + } + actionRects.clear(); + actionList.clear(); + const int itemSpacing = q->style()->pixelMetric(QStyle::PM_MenuBarItemSpacing, 0, q); + int max_item_height = 0, separator = -1, separator_start = 0, separator_len = 0; + QList<QAction*> items = q->actions(); + + //calculate size + const QFontMetrics fm = q->fontMetrics(); + const int hmargin = q->style()->pixelMetric(QStyle::PM_MenuBarHMargin, 0, q), + vmargin = q->style()->pixelMetric(QStyle::PM_MenuBarVMargin, 0, q), + icone = q->style()->pixelMetric(QStyle::PM_SmallIconSize, 0, q); + for(int i = 0; i < items.count(); i++) { + QAction *action = items.at(i); + if(!action->isVisible()) + continue; + + QSize sz; + + //calc what I think the size is.. + if(action->isSeparator()) { + if (q->style()->styleHint(QStyle::SH_DrawMenuBarSeparator, 0, q)) + separator = actionRects.count(); + continue; //we don't really position these! + } else { + QString s = action->text(); + if(!s.isEmpty()) { + int w = fm.width(s); + w -= s.count(QLatin1Char('&')) * fm.width(QLatin1Char('&')); + w += s.count(QLatin1String("&&")) * fm.width(QLatin1Char('&')); + sz = QSize(w, fm.height()); + } + + QIcon is = action->icon(); + if (!is.isNull()) { + QSize is_sz = QSize(icone, icone); + if (is_sz.height() > sz.height()) + sz.setHeight(is_sz.height()); + if (is_sz.width() > sz.width()) + sz.setWidth(is_sz.width()); + } + } + + //let the style modify the above size.. + QStyleOptionMenuItem opt; + q->initStyleOption(&opt, action); + sz = q->style()->sizeFromContents(QStyle::CT_MenuBarItem, &opt, sz, q); + + if(!sz.isEmpty()) { + { //update the separator state + int iWidth = sz.width(); + iWidth += itemSpacing; + if(separator == -1) + separator_start += iWidth; + else + separator_len += iWidth; + } + //maximum height + max_item_height = qMax(max_item_height, sz.height()); + //append + actionRects.insert(action, QRect(0, 0, sz.width(), sz.height())); + actionList.append(action); + } + } + + //calculate position + int x = ((start == -1) ? hmargin : start) + itemSpacing; + int y = vmargin; + for(int i = 0; i < actionList.count(); i++) { + QAction *action = actionList.at(i); + QRect &rect = actionRects[action]; + //resize + rect.setHeight(max_item_height); + + //move + if(separator != -1 && i >= separator) { //after the separator + int left = (max_width - separator_len - hmargin - itemSpacing) + (x - separator_start - hmargin); + if(left < separator_start) { //wrap + separator_start = x = hmargin; + y += max_item_height; + } + rect.moveLeft(left); + } else { + rect.moveLeft(x); + } + rect.moveTop(y); + + //keep moving along.. + x += rect.width() + itemSpacing; + } +} + +void QMenuBarPrivate::activateAction(QAction *action, QAction::ActionEvent action_e) +{ + Q_Q(QMenuBar); + if (!action || !action->isEnabled()) + return; + action->activate(action_e); + if (action_e == QAction::Hover) + action->showStatusText(q); + +// if(action_e == QAction::Trigger) +// emit q->activated(action); +// else if(action_e == QAction::Hover) +// emit q->highlighted(action); +} + + +void QMenuBarPrivate::_q_actionTriggered() +{ + Q_Q(QMenuBar); + if (QAction *action = qobject_cast<QAction *>(q->sender())) { + emit q->triggered(action); +#ifdef QT3_SUPPORT + emit q->activated(q->findIdForAction(action)); +#endif + } +} + +void QMenuBarPrivate::_q_actionHovered() +{ + Q_Q(QMenuBar); + if (QAction *action = qobject_cast<QAction *>(q->sender())) { + emit q->hovered(action); +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + QList<QAction*> actions = q->actions(); + int actionIndex = actions.indexOf(action); + ++actionIndex; + QAccessible::updateAccessibility(q, actionIndex, QAccessible::Focus); + QAccessible::updateAccessibility(q, actionIndex, QAccessible::Selection); + } +#endif //QT_NO_ACCESSIBILITY +#ifdef QT3_SUPPORT + emit q->highlighted(q->findIdForAction(action)); +#endif + } +} + +/*! + Initialize \a option with the values from the menu bar and information from \a action. This method + is useful for subclasses when they need a QStyleOptionMenuItem, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() QMenu::initStyleOption() +*/ +void QMenuBar::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const +{ + if (!option || !action) + return; + Q_D(const QMenuBar); + option->palette = palette(); + option->state = QStyle::State_None; + if (isEnabled() && action->isEnabled()) + option->state |= QStyle::State_Enabled; + else + option->palette.setCurrentColorGroup(QPalette::Disabled); + option->fontMetrics = fontMetrics(); + if (d->currentAction && d->currentAction == action) { + option->state |= QStyle::State_Selected; + if (d->popupState && !d->closePopupMode) + option->state |= QStyle::State_Sunken; + } + if (hasFocus() || d->currentAction) + option->state |= QStyle::State_HasFocus; + option->menuRect = rect(); + option->menuItemType = QStyleOptionMenuItem::Normal; + option->checkType = QStyleOptionMenuItem::NotCheckable; + option->text = action->text(); + option->icon = action->icon(); +} + +/*! + \class QMenuBar + \brief The QMenuBar class provides a horizontal menu bar. + + \ingroup application + \mainclass + + A menu bar consists of a list of pull-down menu items. You add + menu items with addMenu(). For example, asuming that \c menubar + is a pointer to a QMenuBar and \c fileMenu is a pointer to a + QMenu, the following statement inserts the menu into the menu bar: + \snippet doc/src/snippets/code/src_gui_widgets_qmenubar.cpp 0 + + The ampersand in the menu item's text sets Alt+F as a shortcut for + this menu. (You can use "\&\&" to get a real ampersand in the menu + bar.) + + There is no need to lay out a menu bar. It automatically sets its + own geometry to the top of the parent widget and changes it + appropriately whenever the parent is resized. + + \section1 Usage + + In most main window style applications you would use the + \l{QMainWindow::}{menuBar()} function provided in QMainWindow, + adding \l{QMenu}s to the menu bar and adding \l{QAction}s to the + pop-up menus. + + Example (from the \l{mainwindows/menus}{Menus} example): + + \snippet examples/mainwindows/menus/mainwindow.cpp 9 + + Menu items may be removed with removeAction(). + + Widgets can be added to menus by using instances of the QWidgetAction + class to hold them. These actions can then be inserted into menus + in the usual way; see the QMenu documentation for more details. + + \section1 Platform Dependent Look and Feel + + Different platforms have different requirements for the appearance + of menu bars and their behavior when the user interacts with them. + For example, Windows systems are often configured so that the + underlined character mnemonics that indicate keyboard shortcuts + for items in the menu bar are only shown when the \gui{Alt} key is + pressed. + + \table + + \row \o \inlineimage plastique-menubar.png A menu bar shown in the + Plastique widget style. + + \o The \l{QPlastiqueStyle}{Plastique widget style}, like most + other styles, handles the \gui{Help} menu in the same way as it + handles any other menu. + + \row \o \inlineimage motif-menubar.png A menu bar shown in the + Motif widget style. + + \o The \l{QMotifStyle}{Motif widget style} treats \gui{Help} menus + in a special way, placing them at right-hand end of the menu bar. + + \endtable + + \section1 QMenuBar on Mac OS X + + QMenuBar on Mac OS X is a wrapper for using the system-wide menu bar. + If you have multiple menu bars in one dialog the outermost menu bar + (normally inside a widget with widget flag Qt::Window) will + be used for the system-wide menu bar. + + Qt for Mac OS X also provides a menu bar merging feature to make + QMenuBar conform more closely to accepted Mac OS X menu bar layout. + The merging functionality is based on string matching the title of + a QMenu entry. These strings are translated (using QObject::tr()) + in the "QMenuBar" context. If an entry is moved its slots will still + fire as if it was in the original place. The table below outlines + the strings looked for and where the entry is placed if matched: + + \table + \header \i String matches \i Placement \i Notes + \row \i about.* + \i Application Menu | About <application name> + \i If this entry is not found no About item will appear in + the Application Menu + \row \i config, options, setup, settings or preferences + \i Application Menu | Preferences + \i If this entry is not found the Settings item will be disabled + \row \i quit or exit + \i Application Menu | Quit <application name> + \i If this entry is not found a default Quit item will be + created to call QApplication::quit() + \endtable + + You can override this behavior by using the QAction::menuRole() + property. + + If you want all windows in a Mac application to share one menu + bar, you must create a menu bar that does not have a parent. + Create a parent-less menu bar this way: + + \snippet doc/src/snippets/code/src_gui_widgets_qmenubar.cpp 1 + + \bold{Note:} Do \e{not} call QMainWindow::menuBar() to create the + shared menu bar, because that menu bar will have the QMainWindow + as its parent. That menu bar would only be displayed for the + parent QMainWindow. + + \bold{Note:} The text used for the application name in the menu + bar is obtained from the value set in the \c{Info.plist} file in + the application's bundle. See \l{Deploying an Application on + Mac OS X} for more information. + + \section1 QMenuBar on Windows CE + + QMenuBar on Windows CE is a wrapper for using the system-wide menu bar, + similar to the Mac. This feature is activated for Windows Mobile + and integrates QMenuBar with the native soft keys. The left soft + key can be controlled with QMenuBar::setDefaultAction() and the + right soft key can be used to access the menu bar. + + The hovered() signal is not supported for the native menu + integration. Also, it is not possible to display an icon in a + native menu on Windows Mobile. + + \section1 Examples + + The \l{mainwindows/menus}{Menus} example shows how to use QMenuBar + and QMenu. The other \l{Qt Examples#Main Windows}{main window + application examples} also provide menus using these classes. + + \sa QMenu, QShortcut, QAction, + {http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/index.html}{Introduction to Apple Human Interface Guidelines}, + {fowler}{GUI Design Handbook: Menu Bar}, {Menus Example} +*/ + + +void QMenuBarPrivate::init() +{ + Q_Q(QMenuBar); + q->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + q->setAttribute(Qt::WA_CustomWhatsThis); +#ifdef Q_WS_MAC + macCreateMenuBar(q->parentWidget()); + if(mac_menubar) + q->hide(); +#endif +#ifdef Q_OS_WINCE + if (qt_wince_is_mobile()) { + wceCreateMenuBar(q->parentWidget()); + if(wce_menubar) + q->hide(); + } +#endif + q->setBackgroundRole(QPalette::Button); + oldWindow = oldParent = 0; +#ifdef QT3_SUPPORT + doAutoResize = false; +#endif + handleReparent(); + q->setMouseTracking(q->style()->styleHint(QStyle::SH_MenuBar_MouseTracking, 0, q)); + + extension = new QMenuBarExtension(q); + extension->setFocusPolicy(Qt::NoFocus); + extension->hide(); +} + +/*! + Constructs a menu bar with parent \a parent. +*/ +QMenuBar::QMenuBar(QWidget *parent) : QWidget(*new QMenuBarPrivate, parent, 0) +{ + Q_D(QMenuBar); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QMenuBar::QMenuBar(QWidget *parent, const char *name) : QWidget(*new QMenuBarPrivate, parent, 0) +{ + Q_D(QMenuBar); + d->init(); + setObjectName(QString::fromAscii(name)); +} +#endif + +/*! + Destroys the menu bar. +*/ +QMenuBar::~QMenuBar() +{ +#ifdef Q_WS_MAC + Q_D(QMenuBar); + d->macDestroyMenuBar(); +#endif +#ifdef Q_OS_WINCE + Q_D(QMenuBar); + if (qt_wince_is_mobile()) + d->wceDestroyMenuBar(); +#endif +} + +/*! + \overload + + This convenience function creates a new action with \a text. + The function adds the newly created action to the menu's + list of actions, and returns it. + + \sa QWidget::addAction(), QWidget::actions() +*/ +QAction *QMenuBar::addAction(const QString &text) +{ + QAction *ret = new QAction(text, this); + addAction(ret); + return ret; +} + +/*! + \overload + + This convenience function creates a new action with the given \a + text. The action's triggered() signal is connected to the \a + receiver's \a member slot. The function adds the newly created + action to the menu's list of actions and returns it. + + \sa QWidget::addAction(), QWidget::actions() +*/ +QAction *QMenuBar::addAction(const QString &text, const QObject *receiver, const char* member) +{ + QAction *ret = new QAction(text, this); + QObject::connect(ret, SIGNAL(triggered(bool)), receiver, member); + addAction(ret); + return ret; +} + +/*! + Appends a new QMenu with \a title to the menu bar. The menu bar + takes ownership of the menu. Returns the new menu. + + \sa QWidget::addAction() QMenu::menuAction() +*/ +QMenu *QMenuBar::addMenu(const QString &title) +{ + QMenu *menu = new QMenu(title, this); + addAction(menu->menuAction()); + return menu; +} + +/*! + Appends a new QMenu with \a icon and \a title to the menu bar. The menu bar + takes ownership of the menu. Returns the new menu. + + \sa QWidget::addAction() QMenu::menuAction() +*/ +QMenu *QMenuBar::addMenu(const QIcon &icon, const QString &title) +{ + QMenu *menu = new QMenu(title, this); + menu->setIcon(icon); + addAction(menu->menuAction()); + return menu; +} + +/*! + Appends \a menu to the menu bar. Returns the menu's menuAction(). + + \note The returned QAction object can be used to hide the corresponding + menu. + + \sa QWidget::addAction() QMenu::menuAction() +*/ +QAction *QMenuBar::addMenu(QMenu *menu) +{ + QAction *action = menu->menuAction(); + addAction(action); + return action; +} + +/*! + Appends a separator to the menu. +*/ +QAction *QMenuBar::addSeparator() +{ + QAction *ret = new QAction(this); + ret->setSeparator(true); + addAction(ret); + return ret; +} + +/*! + This convenience function creates a new separator action, i.e. an + action with QAction::isSeparator() returning true. The function inserts + the newly created action into this menu bar's list of actions before + action \a before and returns it. + + \sa QWidget::insertAction(), addSeparator() +*/ +QAction *QMenuBar::insertSeparator(QAction *before) +{ + QAction *action = new QAction(this); + action->setSeparator(true); + insertAction(before, action); + return action; +} + +/*! + This convenience function inserts \a menu before action \a before + and returns the menus menuAction(). + + \sa QWidget::insertAction() addMenu() +*/ +QAction *QMenuBar::insertMenu(QAction *before, QMenu *menu) +{ + QAction *action = menu->menuAction(); + insertAction(before, action); + return action; +} + +/*! + Returns the QAction that is currently highlighted. A null pointer + will be returned if no action is currently selected. +*/ +QAction *QMenuBar::activeAction() const +{ + Q_D(const QMenuBar); + return d->currentAction; +} + +/*! + \since 4.1 + + Sets the currently highlighted action to \a act. +*/ +void QMenuBar::setActiveAction(QAction *act) +{ + Q_D(QMenuBar); + d->setCurrentAction(act, true, false); +} + + +/*! + Removes all the actions from the menu bar. + + \sa removeAction() +*/ +void QMenuBar::clear() +{ + QList<QAction*> acts = actions(); + for(int i = 0; i < acts.size(); i++) + removeAction(acts[i]); +} + +/*! + \property QMenuBar::defaultUp + \brief the popup orientation + + The default popup orientation. By default, menus pop "down" the + screen. By setting the property to true, the menu will pop "up". + You might call this for menus that are \e below the document to + which they refer. + + If the menu would not fit on the screen, the other direction is + used automatically. +*/ +void QMenuBar::setDefaultUp(bool b) +{ + Q_D(QMenuBar); + d->defaultPopDown = !b; +} + +bool QMenuBar::isDefaultUp() const +{ + Q_D(const QMenuBar); + return !d->defaultPopDown; +} + +/*! + \reimp +*/ +void QMenuBar::resizeEvent(QResizeEvent *) +{ + Q_D(QMenuBar); + d->itemsDirty = true; + d->updateGeometries(); +} + +/*! + \reimp +*/ +void QMenuBar::paintEvent(QPaintEvent *e) +{ + Q_D(QMenuBar); + QPainter p(this); + QRegion emptyArea(rect()); + + //draw the items + for (int i = 0; i < d->actionList.count(); ++i) { + QAction *action = d->actionList.at(i); + QRect adjustedActionRect = d->actionRect(action); + if (adjustedActionRect.isEmpty() || !d->isVisible(action)) + continue; + if(!e->rect().intersects(adjustedActionRect)) + continue; + + emptyArea -= adjustedActionRect; + QStyleOptionMenuItem opt; + initStyleOption(&opt, action); + opt.rect = adjustedActionRect; + p.setClipRect(adjustedActionRect); + style()->drawControl(QStyle::CE_MenuBarItem, &opt, &p, this); + } + //draw border + if(int fw = style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, this)) { + QRegion borderReg; + borderReg += QRect(0, 0, fw, height()); //left + borderReg += QRect(width()-fw, 0, fw, height()); //right + borderReg += QRect(0, 0, width(), fw); //top + borderReg += QRect(0, height()-fw, width(), fw); //bottom + p.setClipRegion(borderReg); + emptyArea -= borderReg; + QStyleOptionFrame frame; + frame.rect = rect(); + frame.palette = palette(); + frame.state = QStyle::State_None; + frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuBarPanelWidth); + frame.midLineWidth = 0; + style()->drawPrimitive(QStyle::PE_PanelMenuBar, &frame, &p, this); + } + p.setClipRegion(emptyArea); + QStyleOptionMenuItem menuOpt; + menuOpt.palette = palette(); + menuOpt.state = QStyle::State_None; + menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea; + menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; + menuOpt.rect = rect(); + menuOpt.menuRect = rect(); + style()->drawControl(QStyle::CE_MenuBarEmptyArea, &menuOpt, &p, this); +} + +/*! + \reimp +*/ +void QMenuBar::setVisible(bool visible) +{ +#ifdef Q_WS_MAC + Q_D(QMenuBar); + if(d->mac_menubar) + return; +#endif +#ifdef Q_OS_WINCE + Q_D(QMenuBar); + if(d->wce_menubar) + return; +#endif + QWidget::setVisible(visible); +} + +/*! + \reimp +*/ +void QMenuBar::mousePressEvent(QMouseEvent *e) +{ + Q_D(QMenuBar); + if(e->button() != Qt::LeftButton) + return; + + QAction *action = d->actionAt(e->pos()); + if (!action || !d->isVisible(action)) { + d->setCurrentAction(0); +#ifndef QT_NO_WHATSTHIS + if (QWhatsThis::inWhatsThisMode()) + QWhatsThis::showText(e->globalPos(), d->whatsThis, this); +#endif + return; + } + + d->mouseDown = true; + + if(d->currentAction == action && d->popupState) { + if(QMenu *menu = d->activeMenu) { + d->activeMenu = 0; + menu->hide(); + } +#ifdef Q_WS_WIN + if((d->closePopupMode = style()->styleHint(QStyle::SH_MenuBar_DismissOnSecondClick))) + update(d->actionRect(action)); +#endif + } else { + d->setCurrentAction(action, true); + } +} + +/*! + \reimp +*/ +void QMenuBar::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QMenuBar); + if(e->button() != Qt::LeftButton || !d->mouseDown) + return; + + d->mouseDown = false; + QAction *action = d->actionAt(e->pos()); + if((d->closePopupMode && action == d->currentAction) || !action || !action->menu()) { + //we set the current action before activating + //so that we let the leave event set the current back to 0 + d->setCurrentAction(action, false); + if(action) + d->activateAction(action, QAction::Trigger); + } + d->closePopupMode = 0; +} + +/*! + \reimp +*/ +void QMenuBar::keyPressEvent(QKeyEvent *e) +{ + Q_D(QMenuBar); + int key = e->key(); + if(isRightToLeft()) { // in reverse mode open/close key for submenues are reversed + if(key == Qt::Key_Left) + key = Qt::Key_Right; + else if(key == Qt::Key_Right) + key = Qt::Key_Left; + } + if(key == Qt::Key_Tab) //means right + key = Qt::Key_Right; + else if(key == Qt::Key_Backtab) //means left + key = Qt::Key_Left; + + bool key_consumed = false; + switch(key) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Enter: + case Qt::Key_Space: + case Qt::Key_Return: { + if(!style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this) || !d->currentAction) + break; + if(d->currentAction->menu()) { + d->popupAction(d->currentAction, true); + } else if(key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Space) { + d->activateAction(d->currentAction, QAction::Trigger); + d->setCurrentAction(d->currentAction, false); + d->setKeyboardMode(false); + } + key_consumed = true; + break; } + + case Qt::Key_Right: + case Qt::Key_Left: { + if(d->currentAction) { + QAction *nextAction = 0; + bool allowActiveAndDisabled = + style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this); + + for(int i=0; i<(int)d->actionList.count(); i++) { + if(d->actionList.at(i) == (QAction*)d->currentAction) { + if (key == Qt::Key_Left) { + while (i > 0) { + i--; + if (allowActiveAndDisabled || d->actionList[i]->isEnabled()) { + nextAction = d->actionList.at(i); + break; + } + } + } else { + while (i < d->actionList.count()-1) { + i++; + if (allowActiveAndDisabled || d->actionList[i]->isEnabled()) { + nextAction = d->actionList.at(i); + break; + } + } + } + break; + + } + } + + if(!nextAction) { + if (key == Qt::Key_Left) { + for (int i = d->actionList.size() - 1 ; i >= 0 ; --i) { + if (allowActiveAndDisabled || d->actionList[i]->isEnabled()) { + nextAction = d->actionList.at(i); + i--; + break; + } + } + } else { + for (int i = 0 ; i < d->actionList.count() ; ++i) { + if (allowActiveAndDisabled || d->actionList[i]->isEnabled()) { + nextAction = d->actionList.at(i); + i++; + break; + } + } + } + } + if(nextAction) { + d->setCurrentAction(nextAction, d->popupState, true); + key_consumed = true; + } + } + break; } + + case Qt::Key_Escape: + d->setCurrentAction(0); + d->setKeyboardMode(false); + key_consumed = true; + break; + + default: + key_consumed = false; + } + + if(!key_consumed && + (!e->modifiers() || + (e->modifiers()&(Qt::MetaModifier|Qt::AltModifier))) && e->text().length()==1 && !d->popupState) { + int clashCount = 0; + QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0; + { + QChar c = e->text()[0].toUpper(); + for(int i = 0; i < d->actionList.size(); ++i) { + register QAction *act = d->actionList.at(i); + QString s = act->text(); + if(!s.isEmpty()) { + int ampersand = s.indexOf(QLatin1Char('&')); + if(ampersand >= 0) { + if(s[ampersand+1].toUpper() == c) { + clashCount++; + if(!first) + first = act; + if(act == d->currentAction) + currentSelected = act; + else if (!firstAfterCurrent && currentSelected) + firstAfterCurrent = act; + } + } + } + } + } + QAction *next_action = 0; + if(clashCount >= 1) { + if(clashCount == 1 || !d->currentAction || (currentSelected && !firstAfterCurrent)) + next_action = first; + else + next_action = firstAfterCurrent; + } + if(next_action) { + key_consumed = true; + d->setCurrentAction(next_action, true, true); + } + } + if(key_consumed) + e->accept(); + else + e->ignore(); +} + +/*! + \reimp +*/ +void QMenuBar::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QMenuBar); + d->mouseDown = e->buttons() & Qt::LeftButton; + QAction *action = d->actionAt(e->pos()); + bool popupState = d->popupState || d->mouseDown; + if ((action && d->isVisible(action)) || !popupState) + d->setCurrentAction(action, popupState); +} + +/*! + \reimp +*/ +void QMenuBar::leaveEvent(QEvent *) +{ + Q_D(QMenuBar); + if(!hasFocus() && !d->popupState) + d->setCurrentAction(0); +} + +/*! + \reimp +*/ +void QMenuBar::actionEvent(QActionEvent *e) +{ + Q_D(QMenuBar); + d->itemsDirty = true; +#ifdef Q_WS_MAC + if(d->mac_menubar) { + if(e->type() == QEvent::ActionAdded) + d->mac_menubar->addAction(e->action(), d->mac_menubar->findAction(e->before())); + else if(e->type() == QEvent::ActionRemoved) + d->mac_menubar->removeAction(e->action()); + else if(e->type() == QEvent::ActionChanged) + d->mac_menubar->syncAction(e->action()); + } +#endif +#ifdef Q_OS_WINCE + if(d->wce_menubar) { + if(e->type() == QEvent::ActionAdded) + d->wce_menubar->addAction(e->action(), d->wce_menubar->findAction(e->before())); + else if(e->type() == QEvent::ActionRemoved) + d->wce_menubar->removeAction(e->action()); + else if(e->type() == QEvent::ActionChanged) + d->wce_menubar->syncAction(e->action()); + } +#endif + if(e->type() == QEvent::ActionAdded) { + connect(e->action(), SIGNAL(triggered()), this, SLOT(_q_actionTriggered())); + connect(e->action(), SIGNAL(hovered()), this, SLOT(_q_actionHovered())); + } else if(e->type() == QEvent::ActionRemoved) { + e->action()->disconnect(this); + } + if (isVisible()) { + d->updateGeometries(); + update(); + } +} + +/*! + \reimp +*/ +void QMenuBar::focusInEvent(QFocusEvent *) +{ + Q_D(QMenuBar); + if(d->keyboardState && !d->currentAction && !d->actionList.isEmpty()) + d->setCurrentAction(d->actionList.first()); +} + +/*! + \reimp +*/ +void QMenuBar::focusOutEvent(QFocusEvent *) +{ + Q_D(QMenuBar); + if(!d->popupState) { + d->setCurrentAction(0); + d->setKeyboardMode(false); + } +} + +/*! + \reimp + */ +void QMenuBar::timerEvent (QTimerEvent *e) +{ + Q_D(QMenuBar); + if (e->timerId() == d->autoReleaseTimer.timerId()) { + d->autoReleaseTimer.stop(); + d->setCurrentAction(0); + } + QWidget::timerEvent(e); +} + + +void QMenuBarPrivate::handleReparent() +{ + Q_Q(QMenuBar); + QWidget *newParent = q->parentWidget(); + //Note: if parent is reparented, then window may change even if parent doesn't + + // we need to install an event filter on parent, and remove the old one + + if (oldParent != newParent) { + if (oldParent) + oldParent->removeEventFilter(q); + if (newParent) + newParent->installEventFilter(q); + } + + //we also need event filter on top-level (for shortcuts) + QWidget *newWindow = newParent ? newParent->window() : 0; + + if (oldWindow != newWindow) { + if (oldParent && oldParent != oldWindow) + oldWindow->removeEventFilter(q); + + if (newParent && newParent != newWindow) + newWindow->installEventFilter(q); + } + + oldParent = newParent; + oldWindow = newWindow; + +#ifdef Q_WS_MAC + macDestroyMenuBar(); + macCreateMenuBar(newParent); +#endif + +#ifdef Q_OS_WINCE + if (qt_wince_is_mobile() && wce_menubar) + wce_menubar->rebuild(); +#endif +} + +#ifdef QT3_SUPPORT +/*! + Sets whether the menu bar should automatically resize itself + when its parent widget is resized. + + This feature is provided to help porting to Qt 4. We recommend + against using it in new code. + + \sa autoGeometry() +*/ +void QMenuBar::setAutoGeometry(bool b) +{ + Q_D(QMenuBar); + d->doAutoResize = b; +} + +/*! + Returns true if the menu bar automatically resizes itself + when its parent widget is resized; otherwise returns false. + + This feature is provided to help porting to Qt 4. We recommend + against using it in new code. + + \sa setAutoGeometry() +*/ +bool QMenuBar::autoGeometry() const +{ + Q_D(const QMenuBar); + return d->doAutoResize; +} +#endif + +/*! + \reimp +*/ +void QMenuBar::changeEvent(QEvent *e) +{ + Q_D(QMenuBar); + if(e->type() == QEvent::StyleChange) { + d->itemsDirty = true; + setMouseTracking(style()->styleHint(QStyle::SH_MenuBar_MouseTracking, 0, this)); + if(parentWidget()) + resize(parentWidget()->width(), heightForWidth(parentWidget()->width())); + d->updateGeometries(); + } else if (e->type() == QEvent::ParentChange) { + d->handleReparent(); + } else if (e->type() == QEvent::FontChange + || e->type() == QEvent::ApplicationFontChange) { + d->itemsDirty = true; + d->updateGeometries(); + } + QWidget::changeEvent(e); +} + +/*! + \reimp +*/ +bool QMenuBar::event(QEvent *e) +{ + Q_D(QMenuBar); + switch (e->type()) { + case QEvent::KeyPress: { + QKeyEvent *ke = (QKeyEvent*)e; +#if 0 + if(!d->keyboardState) { //all keypresses.. + d->setCurrentAction(0); + return ; + } +#endif + if(ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { + keyPressEvent(ke); + return true; + } + + } break; +#ifndef QT_NO_SHORTCUT + case QEvent::Shortcut: { + QShortcutEvent *se = static_cast<QShortcutEvent *>(e); + int shortcutId = se->shortcutId(); + for(int j = 0; j < d->shortcutIndexMap.size(); ++j) { + if (shortcutId == d->shortcutIndexMap.value(j)) + d->_q_internalShortcutActivated(j); + } + } break; +#endif + case QEvent::Show: +#ifdef QT3_SUPPORT + if(QWidget *p = parentWidget()) { + // If itemsDirty == true, updateGeometries sends the MenubarUpdated event. + if (!d->itemsDirty) { + QMenubarUpdatedEvent menubarUpdated(this); + QApplication::sendEvent(p, &menubarUpdated); + } + } +#endif + d->_q_updateLayout(); + break; + case QEvent::ShortcutOverride: { + QKeyEvent *kev = static_cast<QKeyEvent*>(e); + if (kev->key() == Qt::Key_Escape) { + e->accept(); + return true; + } + } + break; + +#ifdef QT3_SUPPORT + case QEvent::Hide: { + if(QWidget *p = parentWidget()) { + QMenubarUpdatedEvent menubarUpdated(this); + QApplication::sendEvent(p, &menubarUpdated); + } + } break; +#endif + +#ifndef QT_NO_WHATSTHIS + case QEvent::QueryWhatsThis: + e->setAccepted(d->whatsThis.size()); + if (QAction *action = d->actionAt(static_cast<QHelpEvent*>(e)->pos())) { + if (action->whatsThis().size() || action->menu()) + e->accept(); + } + return true; +#endif + case QEvent::LayoutDirectionChange: + d->_q_updateLayout(); + break; + default: + break; + } + return QWidget::event(e); +} + +/*! + \reimp +*/ +bool QMenuBar::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QMenuBar); + if (object == parent() && object) { +#ifdef QT3_SUPPORT + if (d->doAutoResize && event->type() == QEvent::Resize) { + QResizeEvent *e = (QResizeEvent *)event; + int w = e->size().width(); + setGeometry(0, y(), w, heightForWidth(w)); + return false; + } +#endif + if (event->type() == QEvent::ParentChange) //GrandparentChange + d->handleReparent(); + } + if (object == d->leftWidget || object == d->rightWidget) { + switch (event->type()) { + case QEvent::ShowToParent: + case QEvent::HideToParent: + d->_q_updateLayout(); + break; + default: + break; + } + } + + if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this)) { + if (d->altPressed) { + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + QKeyEvent *kev = static_cast<QKeyEvent*>(event); + if (kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta) { + if (event->type() == QEvent::KeyPress) // Alt-press does not interest us, we have the shortcut-override event + break; + d->setKeyboardMode(!d->keyboardState); + } + } + // fall through + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::ActivationChange: + d->altPressed = false; + qApp->removeEventFilter(this); + break; + default: + break; + } + } else if (isVisible()) { + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent *kev = static_cast<QKeyEvent*>(event); + if ((kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta) + && kev->modifiers() == Qt::AltModifier) { + d->altPressed = true; + qApp->installEventFilter(this); + } + } + } + } + + return false; +} + +/*! + \internal + + Return the item at \a pt, or 0 if there is no item there or if it is + a separator item. +*/ +QAction *QMenuBar::actionAt(const QPoint &pt) const +{ + Q_D(const QMenuBar); + return d->actionAt(pt); +} + +/*! + \internal + + Returns the geometry of action \a act. +*/ +QRect QMenuBar::actionGeometry(QAction *act) const +{ + Q_D(const QMenuBar); + return d->actionRect(act); +} + +/*! + \reimp +*/ +QSize QMenuBar::minimumSizeHint() const +{ + Q_D(const QMenuBar); +#ifdef Q_WS_MAC + const bool as_gui_menubar = !d->mac_menubar; +#elif defined (Q_OS_WINCE) + const bool as_gui_menubar = !d->wce_menubar; +#else + const bool as_gui_menubar = true; +#endif + + ensurePolished(); + QSize ret(0, 0); + const int hmargin = style()->pixelMetric(QStyle::PM_MenuBarHMargin, 0, this); + const int vmargin = style()->pixelMetric(QStyle::PM_MenuBarVMargin, 0, this); + int fw = style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, this); + int spaceBelowMenuBar = style()->styleHint(QStyle::SH_MainWindow_SpaceBelowMenuBar, 0, this); + if(as_gui_menubar) { + QMap<QAction*, QRect> actionRects; + QList<QAction*> actionList; + int w = parentWidget() ? parentWidget()->width() : QApplication::desktop()->width(); + d->calcActionRects(w - (2 * fw), 0, actionRects, actionList); + if (d->actionList.count() > 0) { + ret = d->actionRect(d->actionList.at(0)).size(); + if (!d->extension->isHidden()) + ret += QSize(d->extension->sizeHint().width(), 0); + } + ret += QSize(2*fw + hmargin, 2*fw + vmargin); + } + int margin = 2*vmargin + 2*fw + spaceBelowMenuBar; + if(d->leftWidget) { + QSize sz = d->leftWidget->minimumSizeHint(); + ret.setWidth(ret.width() + sz.width()); + if(sz.height() + margin > ret.height()) + ret.setHeight(sz.height() + margin); + } + if(d->rightWidget) { + QSize sz = d->rightWidget->minimumSizeHint(); + ret.setWidth(ret.width() + sz.width()); + if(sz.height() + margin > ret.height()) + ret.setHeight(sz.height() + margin); + } + if(as_gui_menubar) { + QStyleOptionMenuItem opt; + opt.rect = rect(); + opt.menuRect = rect(); + opt.state = QStyle::State_None; + opt.menuItemType = QStyleOptionMenuItem::Normal; + opt.checkType = QStyleOptionMenuItem::NotCheckable; + opt.palette = palette(); + return (style()->sizeFromContents(QStyle::CT_MenuBar, &opt, + ret.expandedTo(QApplication::globalStrut()), + this)); + } + return ret; +} + +/*! + \reimp +*/ +QSize QMenuBar::sizeHint() const +{ + Q_D(const QMenuBar); +#ifdef Q_WS_MAC + const bool as_gui_menubar = !d->mac_menubar; +#elif defined (Q_OS_WINCE) + const bool as_gui_menubar = !d->wce_menubar; +#else + const bool as_gui_menubar = true; +#endif + + ensurePolished(); + QSize ret(0, 0); + const int hmargin = style()->pixelMetric(QStyle::PM_MenuBarHMargin, 0, this); + const int vmargin = style()->pixelMetric(QStyle::PM_MenuBarVMargin, 0, this); + int fw = style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, this); + int spaceBelowMenuBar = style()->styleHint(QStyle::SH_MainWindow_SpaceBelowMenuBar, 0, this); + if(as_gui_menubar) { + QMap<QAction*, QRect> actionRects; + QList<QAction*> actionList; + const int w = parentWidget() ? parentWidget()->width() : QApplication::desktop()->width(); + d->calcActionRects(w - (2 * fw), 0, actionRects, actionList); + for (QMap<QAction*, QRect>::const_iterator i = actionRects.constBegin(); + i != actionRects.constEnd(); ++i) { + QRect actionRect(i.value()); + if(actionRect.x() + actionRect.width() > ret.width()) + ret.setWidth(actionRect.x() + actionRect.width()); + if(actionRect.y() + actionRect.height() > ret.height()) + ret.setHeight(actionRect.y() + actionRect.height()); + } + ret += QSize(2*fw + 2*hmargin, 2*fw + 2*vmargin); + } + int margin = 2*vmargin + 2*fw + spaceBelowMenuBar; + if(d->leftWidget) { + QSize sz = d->leftWidget->sizeHint(); + ret.setWidth(ret.width() + sz.width()); + if(sz.height() + margin > ret.height()) + ret.setHeight(sz.height() + margin); + } + if(d->rightWidget) { + QSize sz = d->rightWidget->sizeHint(); + ret.setWidth(ret.width() + sz.width()); + if(sz.height() + margin > ret.height()) + ret.setHeight(sz.height() + margin); + } + if(as_gui_menubar) { + QStyleOptionMenuItem opt; + opt.rect = rect(); + opt.menuRect = rect(); + opt.state = QStyle::State_None; + opt.menuItemType = QStyleOptionMenuItem::Normal; + opt.checkType = QStyleOptionMenuItem::NotCheckable; + opt.palette = palette(); + return (style()->sizeFromContents(QStyle::CT_MenuBar, &opt, + ret.expandedTo(QApplication::globalStrut()), + this)); + } + return ret; +} + +/*! + \reimp +*/ +int QMenuBar::heightForWidth(int) const +{ + Q_D(const QMenuBar); +#ifdef Q_WS_MAC + const bool as_gui_menubar = !d->mac_menubar; +#elif defined (Q_OS_WINCE) + const bool as_gui_menubar = !d->wce_menubar; +#else + const bool as_gui_menubar = true; +#endif + int height = 0; + const int vmargin = style()->pixelMetric(QStyle::PM_MenuBarVMargin, 0, this); + int fw = style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, this); + int spaceBelowMenuBar = style()->styleHint(QStyle::SH_MainWindow_SpaceBelowMenuBar, 0, this); + if(as_gui_menubar) { + if (d->actionList.count()) { + // assume all actionrects have the same height + height = d->actionRect(d->actionList.first()).height(); + height += spaceBelowMenuBar; + } + height += 2*fw; + height += 2*vmargin; + } + int margin = 2*vmargin + 2*fw + spaceBelowMenuBar; + if(d->leftWidget) + height = qMax(d->leftWidget->sizeHint().height() + margin, height); + if(d->rightWidget) + height = qMax(d->rightWidget->sizeHint().height() + margin, height); + if(as_gui_menubar) { + QStyleOptionMenuItem opt; + opt.init(this); + opt.menuRect = rect(); + opt.state = QStyle::State_None; + opt.menuItemType = QStyleOptionMenuItem::Normal; + opt.checkType = QStyleOptionMenuItem::NotCheckable; + return style()->sizeFromContents(QStyle::CT_MenuBar, &opt, QSize(0, height), this).height(); //not pretty.. + } + return height; +} + +/*! + \internal +*/ +void QMenuBarPrivate::_q_internalShortcutActivated(int id) +{ + Q_Q(QMenuBar); + QAction *act = actionList.at(id); + setCurrentAction(act, true, true); + if (act && !act->menu()) { + activateAction(act, QAction::Trigger); + //100 is the same as the default value in QPushButton::animateClick + autoReleaseTimer.start(100, q); + } +} + +void QMenuBarPrivate::_q_updateLayout() +{ + Q_Q(QMenuBar); + itemsDirty = true; + if (q->isVisible()) { + updateGeometries(); + q->update(); + } +} + +/*! + \internal + + This sets widget \a w to be shown directly on the left of the first or + the right of the last menu item, depending on \a corner. +*/ +void QMenuBar::setCornerWidget(QWidget *w, Qt::Corner corner) +{ + Q_D(QMenuBar); + switch (corner) { + case Qt::TopLeftCorner: + if (d->leftWidget) + d->leftWidget->removeEventFilter(this); + d->leftWidget = w; + break; + case Qt::TopRightCorner: + if (d->rightWidget) + d->rightWidget->removeEventFilter(this); + d->rightWidget = w; + break; + default: + qWarning("QMenuBar::setCornerWidget: Only TopLeftCorner and TopRightCorner are supported"); + return; + } + + if (w) { + w->setParent(this); + w->installEventFilter(this); + } + + d->_q_updateLayout(); +} + +/*! + \internal + + Returns the widget in the left of the first or the right of the last menu + item, depending on \a corner. +*/ +QWidget *QMenuBar::cornerWidget(Qt::Corner corner) const +{ + Q_D(const QMenuBar); + QWidget *w = 0; + switch(corner) { + case Qt::TopLeftCorner: + w = d->leftWidget; + break; + case Qt::TopRightCorner: + w = d->rightWidget; + break; + default: + qWarning("QMenuBar::cornerWidget: Only TopLeftCorner and TopRightCorner are supported"); + break; + } + + return w; +} + +/*! + \since 4.4 + + Sets the default action to \a act. + + The default action is assigned to the left soft key. The menu is assigned + to the right soft key. + + Currently there is only support for the default action on Windows + Mobile. All other platforms ignore the default action. + + \sa defaultAction() +*/ + +#ifdef Q_OS_WINCE +void QMenuBar::setDefaultAction(QAction *act) +{ + Q_D(QMenuBar); + if (d->defaultAction == act) + return; +#ifdef Q_OS_WINCE + if (qt_wince_is_mobile()) + if (d->defaultAction) { + disconnect(d->defaultAction, SIGNAL(changed()), this, SLOT(_q_updateDefaultAction())); + disconnect(d->defaultAction, SIGNAL(destroyed ()), this, SLOT(_q_updateDefaultAction())); + } +#endif + d->defaultAction = act; +#ifdef Q_OS_WINCE + if (qt_wince_is_mobile()) + if (d->defaultAction) { + connect(d->defaultAction, SIGNAL(changed()), this, SLOT(_q_updateDefaultAction())); + connect(d->defaultAction, SIGNAL(destroyed()), this, SLOT(_q_updateDefaultAction())); + } + if (d->wce_menubar) { + d->wce_menubar->rebuild(); + } +#endif +} + +/*! + \since 4.4 + + Returns the current default action. + + \sa setDefaultAction() +*/ +QAction *QMenuBar::defaultAction() const +{ + return d_func()->defaultAction; +} +#endif + +/*! + \fn void QMenuBar::triggered(QAction *action) + + This signal is emitted when an action in a menu belonging to this menubar + is triggered as a result of a mouse click; \a action is the action that + caused the signal to be emitted. + + Normally, you connect each menu action to a single slot using + QAction::triggered(), but sometimes you will want to connect + several items to a single slot (most often if the user selects + from an array). This signal is useful in such cases. + + \sa hovered(), QAction::triggered() +*/ + +/*! + \fn void QMenuBar::hovered(QAction *action) + + This signal is emitted when a menu action is highlighted; \a action + is the action that caused the event to be sent. + + Often this is used to update status information. + + \sa triggered(), QAction::hovered() +*/ + + +#ifdef QT3_SUPPORT +/*! + Use style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, this) + instead. +*/ +int QMenuBar::frameWidth() const +{ + return style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, this); +} + +int QMenuBar::insertAny(const QIcon *icon, const QString *text, const QObject *receiver, const char *member, + const QKeySequence *shortcut, const QMenu *popup, int id, int index) +{ + QAction *act = popup ? popup->menuAction() : new QAction(this); + if(id != -1) + static_cast<QMenuItem*>(act)->setId(id); + if(icon) + act->setIcon(*icon); + if(text) + act->setText(*text); + if(shortcut) + act->setShortcut(*shortcut); + if(receiver && member) + QObject::connect(act, SIGNAL(triggered(bool)), receiver, member); + if(index == -1 || index >= actions().count()) + addAction(act); + else + insertAction(actions().value(index), act); + return findIdForAction(act); +} + +/*! + \since 4.2 + + Use addSeparator() or insertAction() instead. + + \oldcode + menuBar->insertSeparator(); + \newcode + menuBar->addSeparator(); + \endcode +*/ +int QMenuBar::insertSeparator(int index) +{ + QAction *act = new QAction(this); + act->setSeparator(true); + if(index == -1 || index >= actions().count()) + addAction(act); + else + insertAction(actions().value(index), act); + return findIdForAction(act); +} + +/*! + Use QAction::setData() instead. +*/ +bool QMenuBar::setItemParameter(int id, int param) +{ + if(QAction *act = findActionForId(id)) { + act->d_func()->param = param; + return true; + } + return false; +} + +/*! + Use QAction::data() instead. +*/ +int QMenuBar::itemParameter(int id) const +{ + if(QAction *act = findActionForId(id)) + return act->d_func()->param; + return id; +} + +QAction *QMenuBar::findActionForId(int id) const +{ + QList<QAction *> list = actions(); + for (int i = 0; i < list.size(); ++i) { + QAction *act = list.at(i); + if (findIdForAction(act) == id) + return act; + } + return 0; +} + +int QMenuBar::findIdForAction(QAction *act) const +{ + Q_ASSERT(act); + return act->d_func()->id; +} +#endif + +/*! + \enum QMenuBar::Separator + + \compat + + \value Never + \value InWindowsStyle + +*/ + +/*! + \fn void QMenuBar::addAction(QAction *action) + \overload + + Appends the action \a action to the menu bar's list of actions. + + \sa QMenu::addAction(), QWidget::addAction(), QWidget::actions() +*/ + +/*! + \fn uint QMenuBar::count() const + + Use actions().count() instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QString &text, const QObject *receiver, const char* member, const QKeySequence& shortcut, int id, int index) + + Use one of the insertAction() or addAction() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QIcon& icon, const QString &text, const QObject *receiver, const char* member, const QKeySequence& shortcut, int id, int index) + + Use one of the insertAction() or addAction() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QPixmap &pixmap, const QObject *receiver, const char* member, const QKeySequence& shortcut, int id, int index) + + Use one of the insertAction(), addAction(), insertMenu(), or + addMenu() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QString &text, int id, int index) + + Use one of the insertAction() or addAction() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QIcon& icon, const QString &text, int id, int index) + + Use one of the insertAction(), addAction(), insertMenu(), or + addMenu() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QString &text, QMenu *popup, int id, int index) + + Use one of the insertMenu(), or addMenu() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QIcon& icon, const QString &text, QMenu *popup, int id, int index) + + Use one of the insertMenu(), or addMenu() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QPixmap &pixmap, int id, int index) + + Use one of the insertAction(), addAction(), insertMenu(), or + addMenu() overloads instead. +*/ + +/*! + \fn int QMenuBar::insertItem(const QPixmap &pixmap, QMenu *popup, int id, int index) + + Use one of the insertMenu(), or addMenu() overloads instead. +*/ + +/*! + \fn void QMenuBar::removeItem(int id) + + Use removeAction() instead. +*/ + +/*! + \fn void QMenuBar::removeItemAt(int index) + + Use removeAction() instead. +*/ + +/*! + \fn QKeySequence QMenuBar::accel(int id) const + + Use shortcut() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::setAccel(const QKeySequence& key, int id) + + Use setShortcut() on the relevant QAction instead. +*/ + +/*! + \fn QIcon QMenuBar::iconSet(int id) const + + Use icon() on the relevant QAction instead. +*/ + +/*! + \fn QString QMenuBar::text(int id) const + + Use text() on the relevant QAction instead. +*/ + +/*! + \fn QPixmap QMenuBar::pixmap(int id) const + + Use QPixmap(icon()) on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::setWhatsThis(int id, const QString &w) + + Use setWhatsThis() on the relevant QAction instead. +*/ + +/*! + \fn QString QMenuBar::whatsThis(int id) const + + Use whatsThis() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::changeItem(int id, const QString &text) + + Use setText() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::changeItem(int id, const QPixmap &pixmap) + + Use setText() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::changeItem(int id, const QIcon &icon, const QString &text) + + Use setIcon() and setText() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenuBar::isItemActive(int id) const + + Use activeAction() instead. +*/ + +/*! + \fn bool QMenuBar::isItemEnabled(int id) const + + Use isEnabled() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::setItemEnabled(int id, bool enable) + + Use setEnabled() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenuBar::isItemChecked(int id) const + + Use isChecked() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::setItemChecked(int id, bool check) + + Use setChecked() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenuBar::isItemVisible(int id) const + + Use isVisible() on the relevant QAction instead. +*/ + +/*! + \fn void QMenuBar::setItemVisible(int id, bool visible) + + Use setVisible() on the relevant QAction instead. +*/ + +/*! + \fn int QMenuBar::indexOf(int id) const + + Use actions().indexOf(action) on the relevant QAction instead. +*/ + +/*! + \fn int QMenuBar::idAt(int index) const + + Use actions instead. +*/ + +/*! + \fn void QMenuBar::activateItemAt(int index) + + Use activate() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenuBar::connectItem(int id, const QObject *receiver, const char* member) + + Use connect() on the relevant QAction instead. +*/ + +/*! + \fn bool QMenuBar::disconnectItem(int id,const QObject *receiver, const char* member) + + Use disconnect() on the relevant QAction instead. +*/ + +/*! + \fn QMenuItem *QMenuBar::findItem(int id) const + + Use actions instead. +*/ + +/*! + \fn Separator QMenuBar::separator() const + + This function is provided only to make old code compile. +*/ + +/*! + \fn void QMenuBar::setSeparator(Separator sep) + + This function is provided only to make old code compile. +*/ + +/*! + \fn QRect QMenuBar::itemRect(int index) + + Use actionGeometry() on the relevant QAction instead. +*/ + +/*! + \fn int QMenuBar::itemAtPos(const QPoint &p) + + There is no equivalent way to achieve this in Qt 4. +*/ + +/*! + \fn void QMenuBar::activated(int itemId); + + Use triggered() instead. +*/ + +/*! + \fn void QMenuBar::highlighted(int itemId); + + Use hovered() instead. +*/ + +/*! + \fn void QMenuBar::setFrameRect(QRect) + \internal +*/ + +/*! + \fn QRect QMenuBar::frameRect() const + \internal +*/ +/*! + \enum QMenuBar::DummyFrame + \internal + + \value Box + \value Sunken + \value Plain + \value Raised + \value MShadow + \value NoFrame + \value Panel + \value StyledPanel + \value HLine + \value VLine + \value GroupBoxPanel + \value WinPanel + \value ToolBarPanel + \value MenuBarPanel + \value PopupPanel + \value LineEditPanel + \value TabWidgetPanel + \value MShape +*/ + +/*! + \fn void QMenuBar::setFrameShadow(DummyFrame) + \internal +*/ + +/*! + \fn DummyFrame QMenuBar::frameShadow() const + \internal +*/ + +/*! + \fn void QMenuBar::setFrameShape(DummyFrame) + \internal +*/ + +/*! + \fn DummyFrame QMenuBar::frameShape() const + \internal +*/ + +/*! + \fn void QMenuBar::setFrameStyle(int) + \internal +*/ + +/*! + \fn int QMenuBar::frameStyle() const + \internal +*/ + +/*! + \fn void QMenuBar::setLineWidth(int) + \internal +*/ + +/*! + \fn int QMenuBar::lineWidth() const + \internal +*/ + +/*! + \fn void QMenuBar::setMargin(int margin) + Sets the width of the margin around the contents of the widget to \a margin. + + Use QWidget::setContentsMargins() instead. + \sa margin(), QWidget::setContentsMargins() +*/ + +/*! + \fn int QMenuBar::margin() const + Returns the with of the the margin around the contents of the widget. + + Use QWidget::getContentsMargins() instead. + \sa setMargin(), QWidget::getContentsMargins() +*/ + +/*! + \fn void QMenuBar::setMidLineWidth(int) + \internal +*/ + +/*! + \fn int QMenuBar::midLineWidth() const + \internal +*/ + +// for private slots + + +QT_END_NAMESPACE + +#include <moc_qmenubar.cpp> + +#endif // QT_NO_MENUBAR diff --git a/src/gui/widgets/qmenubar.h b/src/gui/widgets/qmenubar.h new file mode 100644 index 0000000..42f0c0c --- /dev/null +++ b/src/gui/widgets/qmenubar.h @@ -0,0 +1,363 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMENUBAR_H +#define QMENUBAR_H + +#include <QtGui/qmenu.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MENUBAR + +class QMenuBarPrivate; +class QStyleOptionMenuItem; +class QWindowsStyle; +#ifdef QT3_SUPPORT +class QMenuItem; +#endif + +class Q_GUI_EXPORT QMenuBar : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool defaultUp READ isDefaultUp WRITE setDefaultUp) + +public: + explicit QMenuBar(QWidget *parent = 0); + ~QMenuBar(); + +#ifdef Q_NO_USING_KEYWORD + void addAction(QAction *action) { QWidget::addAction(action); } +#else + using QWidget::addAction; +#endif + QAction *addAction(const QString &text); + QAction *addAction(const QString &text, const QObject *receiver, const char* member); + + QAction *addMenu(QMenu *menu); + QMenu *addMenu(const QString &title); + QMenu *addMenu(const QIcon &icon, const QString &title); + + + QAction *addSeparator(); + QAction *insertSeparator(QAction *before); + + QAction *insertMenu(QAction *before, QMenu *menu); + + void clear(); + + QAction *activeAction() const; + void setActiveAction(QAction *action); + + void setDefaultUp(bool); + bool isDefaultUp() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + int heightForWidth(int) const; + + QRect actionGeometry(QAction *) const; + QAction *actionAt(const QPoint &) const; + + void setCornerWidget(QWidget *w, Qt::Corner corner = Qt::TopRightCorner); + QWidget *cornerWidget(Qt::Corner corner = Qt::TopRightCorner) const; + +#ifdef Q_WS_MAC + OSMenuRef macMenu(); + static bool macUpdateMenuBar(); +#endif + +#ifdef Q_OS_WINCE + void setDefaultAction(QAction *); + QAction *defaultAction() const; + + static void wceCommands(uint command, HWND controlHandle); + static void wceRefresh(); +#endif + +public Q_SLOTS: + virtual void setVisible(bool visible); + +Q_SIGNALS: + void triggered(QAction *action); + void hovered(QAction *action); + +protected: + void changeEvent(QEvent *); + void keyPressEvent(QKeyEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void leaveEvent(QEvent *); + void paintEvent(QPaintEvent *); + void resizeEvent(QResizeEvent *); + void actionEvent(QActionEvent *); + void focusOutEvent(QFocusEvent *); + void focusInEvent(QFocusEvent *); + void timerEvent(QTimerEvent *); + bool eventFilter(QObject *, QEvent *); + bool event(QEvent *); + void initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QMenuBar(QWidget *parent, const char *name); + inline QT3_SUPPORT uint count() const { return actions().count(); } + inline QT3_SUPPORT int insertItem(const QString &text, const QObject *receiver, const char* member, + const QKeySequence& shortcut = 0, int id = -1, int index = -1) { + return insertAny(0, &text, receiver, member, &shortcut, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QIcon& icon, const QString &text, + const QObject *receiver, const char* member, + const QKeySequence& shortcut = 0, int id = -1, int index = -1) { + return insertAny(&icon, &text, receiver, member, &shortcut, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QPixmap &pixmap, const QObject *receiver, const char* member, + const QKeySequence& shortcut = 0, int id = -1, int index = -1) { + QIcon icon(pixmap); + return insertAny(&icon, 0, receiver, member, &shortcut, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QString &text, int id=-1, int index=-1) { + return insertAny(0, &text, 0, 0, 0, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QIcon& icon, const QString &text, int id=-1, int index=-1) { + return insertAny(&icon, &text, 0, 0, 0, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QString &text, QMenu *popup, int id=-1, int index=-1) { + return insertAny(0, &text, 0, 0, 0, popup, id, index); + } + inline QT3_SUPPORT int insertItem(const QIcon& icon, const QString &text, QMenu *popup, int id=-1, int index=-1) { + return insertAny(&icon, &text, 0, 0, 0, popup, id, index); + } + inline QT3_SUPPORT int insertItem(const QPixmap &pixmap, int id=-1, int index=-1) { + QIcon icon(pixmap); + return insertAny(&icon, 0, 0, 0, 0, 0, id, index); + } + inline QT3_SUPPORT int insertItem(const QPixmap &pixmap, QMenu *popup, int id=-1, int index=-1) { + QIcon icon(pixmap); + return insertAny(&icon, 0, 0, 0, 0, popup, id, index); + } + QT3_SUPPORT int insertSeparator(int index=-1); + inline QT3_SUPPORT void removeItem(int id) { + if(QAction *act = findActionForId(id)) + removeAction(act); } + inline QT3_SUPPORT void removeItemAt(int index) { + if(QAction *act = actions().value(index)) + removeAction(act); } +#ifndef QT_NO_SHORTCUT + inline QT3_SUPPORT QKeySequence accel(int id) const { + if(QAction *act = findActionForId(id)) + return act->shortcut(); + return QKeySequence(); } + inline QT3_SUPPORT void setAccel(const QKeySequence& key, int id) { + if(QAction *act = findActionForId(id)) + act->setShortcut(key); + } +#endif + inline QT3_SUPPORT QIcon iconSet(int id) const { + if(QAction *act = findActionForId(id)) + return act->icon(); + return QIcon(); } + inline QT3_SUPPORT QString text(int id) const { + if(QAction *act = findActionForId(id)) + return act->text(); + return QString(); } + inline QT3_SUPPORT QPixmap pixmap(int id) const { + if(QAction *act = findActionForId(id)) + return act->icon().pixmap(QSize(22,22)); + return QPixmap(); } + inline QT3_SUPPORT void setWhatsThis(int id, const QString &w) { + if(QAction *act = findActionForId(id)) + act->setWhatsThis(w); } + inline QT3_SUPPORT QString whatsThis(int id) const { + if(QAction *act = findActionForId(id)) + return act->whatsThis(); + return QString(); } + + inline QT3_SUPPORT void changeItem(int id, const QString &text) { + if(QAction *act = findActionForId(id)) + act->setText(text); } + inline QT3_SUPPORT void changeItem(int id, const QPixmap &pixmap) { + if(QAction *act = findActionForId(id)) + act->setIcon(QIcon(pixmap)); } + inline QT3_SUPPORT void changeItem(int id, const QIcon &icon, const QString &text) { + if(QAction *act = findActionForId(id)) { + act->setIcon(icon); + act->setText(text); + } + } + inline QT3_SUPPORT bool isItemActive(int id) const { return findActionForId(id) == activeAction(); } + inline QT3_SUPPORT bool isItemEnabled(int id) const { + if(QAction *act = findActionForId(id)) + return act->isEnabled(); + return false; } + inline QT3_SUPPORT void setItemEnabled(int id, bool enable) { + if(QAction *act = findActionForId(id)) + act->setEnabled(enable); } + inline QT3_SUPPORT bool isItemChecked(int id) const { + if(QAction *act = findActionForId(id)) + return act->isChecked(); + return false; } + inline QT3_SUPPORT void setItemChecked(int id, bool check) { + if(QAction *act = findActionForId(id)) + act->setChecked(check); } + inline QT3_SUPPORT bool isItemVisible(int id) const { + if(QAction *act = findActionForId(id)) + return act->isVisible(); + return false; } + inline QT3_SUPPORT void setItemVisible(int id, bool visible) { + if(QAction *act = findActionForId(id)) + act->setVisible(visible); } + inline QT3_SUPPORT int indexOf(int id) const { return actions().indexOf(findActionForId(id)); } + inline QT3_SUPPORT int idAt(int index) const { + return index >= 0 && index < actions().size() + ? findIdForAction(actions().at(index)) + : -1; + } + inline QT3_SUPPORT void activateItemAt(int index) { + if(QAction *ret = actions().value(index)) + setActiveAction(ret); + } + inline QT3_SUPPORT bool connectItem(int id, const QObject *receiver, const char* member) { + if(QAction *act = findActionForId(id)) { + QObject::connect(act, SIGNAL(triggered()), receiver, member); + return true; + } + return false; + } + inline QT3_SUPPORT bool disconnectItem(int id,const QObject *receiver, const char* member) { + if(QAction *act = findActionForId(id)) { + QObject::disconnect(act, SIGNAL(triggered()), receiver, member); + return true; + } + return false; + } + inline QT3_SUPPORT QMenuItem *findItem(int id) const { + return (QMenuItem*)findActionForId(id); + } + QT3_SUPPORT bool setItemParameter(int id, int param); + QT3_SUPPORT int itemParameter(int id) const; + + //frame + QT3_SUPPORT int frameWidth() const; + + QT3_SUPPORT void setFrameRect(QRect) {} + QT3_SUPPORT QRect frameRect() const { return QRect(); } + enum DummyFrame { Box, Sunken, Plain, Raised, MShadow, NoFrame, Panel, StyledPanel, + HLine, VLine, GroupBoxPanel, WinPanel, ToolBarPanel, MenuBarPanel, + PopupPanel, LineEditPanel, TabWidgetPanel, MShape }; + QT3_SUPPORT void setFrameShadow(DummyFrame) {} + QT3_SUPPORT DummyFrame frameShadow() const { return Plain; } + QT3_SUPPORT void setFrameShape(DummyFrame) {} + QT3_SUPPORT DummyFrame frameShape() const { return NoFrame; } + QT3_SUPPORT void setFrameStyle(int) {} + QT3_SUPPORT int frameStyle() const { return 0; } + QT3_SUPPORT void setLineWidth(int) {} + QT3_SUPPORT int lineWidth() const { return 0; } + QT3_SUPPORT void setMargin(int margin) { setContentsMargins(margin, margin, margin, margin); } + QT3_SUPPORT int margin() const + { int margin; int dummy; getContentsMargins(&margin, &dummy, &dummy, &dummy); return margin; } + QT3_SUPPORT void setMidLineWidth(int) {} + QT3_SUPPORT int midLineWidth() const { return 0; } + + //menubar + enum Separator { Never=0, InWindowsStyle=1 }; + inline QT3_SUPPORT Separator separator() const { return InWindowsStyle; } + inline QT3_SUPPORT void setSeparator(Separator) { } + + QT3_SUPPORT void setAutoGeometry(bool); + QT3_SUPPORT bool autoGeometry() const; + +Q_SIGNALS: + QT_MOC_COMPAT void activated(int itemId); + QT_MOC_COMPAT void highlighted(int itemId); + +protected: + inline QT3_SUPPORT QRect itemRect(int index) { + if(QAction *act = actions().value(index)) + return actionGeometry(act); + return QRect(); + } + inline QT3_SUPPORT int itemAtPos(const QPoint &p) { + return findIdForAction(actionAt(p)); + } +private: + QAction *findActionForId(int id) const; + int insertAny(const QIcon *icon, const QString *text, const QObject *receiver, const char *member, + const QKeySequence *shorcut, const QMenu *popup, int id, int index); + int findIdForAction(QAction*) const; +#endif + +private: + Q_DECLARE_PRIVATE(QMenuBar) + Q_DISABLE_COPY(QMenuBar) + Q_PRIVATE_SLOT(d_func(), void _q_actionTriggered()) + Q_PRIVATE_SLOT(d_func(), void _q_actionHovered()) + Q_PRIVATE_SLOT(d_func(), void _q_internalShortcutActivated(int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateLayout()) + +#ifdef Q_OS_WINCE + Q_PRIVATE_SLOT(d_func(), void _q_updateDefaultAction()) +#endif + + friend class QMenu; + friend class QMenuPrivate; + friend class QWindowsStyle; + +#ifdef Q_WS_MAC + friend class QApplicationPrivate; + friend class QWidgetPrivate; + friend bool qt_mac_activate_action(MenuRef, uint, QAction::ActionEvent, bool); +#endif +}; + +#endif // QT_NO_MENUBAR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMENUBAR_H diff --git a/src/gui/widgets/qmenubar_p.h b/src/gui/widgets/qmenubar_p.h new file mode 100644 index 0000000..223346b --- /dev/null +++ b/src/gui/widgets/qmenubar_p.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMENUBAR_P_H +#define QMENUBAR_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 QMAC_Q3MENUBAR_CPP_FILE +#include "QtGui/qstyleoption.h" +#include <private/qmenu_p.h> // Mac needs what in this file! + +#ifdef Q_OS_WINCE +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_MENUBAR +class QMenuBarExtension; +class QMenuBarPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QMenuBar) +public: + QMenuBarPrivate() : itemsDirty(0), itemsWidth(0), itemsStart(-1), currentAction(0), mouseDown(0), + closePopupMode(0), defaultPopDown(1), popupState(0), keyboardState(0), altPressed(0) +#ifdef Q_WS_MAC + , mac_menubar(0) +#endif + +#ifdef Q_OS_WINCE + , wce_menubar(0), wceClassicMenu(false) +#endif + { } + ~QMenuBarPrivate() + { +#ifdef Q_WS_MAC + delete mac_menubar; +#endif +#ifdef Q_OS_WINCE + delete wce_menubar; +#endif + } + + void init(); + QStyleOptionMenuItem getStyleOption(const QAction *action) const; + + //item calculations + uint itemsDirty : 1; + int itemsWidth, itemsStart; + + QVector<int> shortcutIndexMap; + mutable QMap<QAction*, QRect> actionRects; + mutable QList<QAction*> actionList; + void calcActionRects(int max_width, int start, QMap<QAction*, QRect> &actionRects, QList<QAction*> &actionList) const; + QRect actionRect(QAction *) const; + void updateGeometries(); + + //selection + QPointer<QAction>currentAction; + uint mouseDown : 1, closePopupMode : 1, defaultPopDown; + QAction *actionAt(QPoint p) const; + void setCurrentAction(QAction *, bool =false, bool =false); + void popupAction(QAction *, bool); + + //active popup state + uint popupState : 1; + QPointer<QMenu> activeMenu; + + //keyboard mode for keyboard navigation + void setKeyboardMode(bool); + uint keyboardState : 1, altPressed : 1; + QPointer<QWidget> keyboardFocusWidget; + + //firing of events + void activateAction(QAction *, QAction::ActionEvent); + + void _q_actionTriggered(); + void _q_actionHovered(); + void _q_internalShortcutActivated(int); + void _q_updateLayout(); + +#ifdef Q_OS_WINCE + void _q_updateDefaultAction(); +#endif + + //extra widgets in the menubar + QPointer<QWidget> leftWidget, rightWidget; + QMenuBarExtension *extension; + bool isVisible(QAction *action); + + //menu fading/scrolling effects + bool doChildEffects; + + QRect menuRect(bool) const; + + // reparenting + void handleReparent(); + QWidget *oldParent; + QWidget *oldWindow; + + QList<QAction*> hiddenActions; + //default action + QPointer<QAction> defaultAction; + + QBasicTimer autoReleaseTimer; +#ifdef QT3_SUPPORT + bool doAutoResize; +#endif +#ifdef Q_WS_MAC + //mac menubar binding + struct QMacMenuBarPrivate { + QList<QMacMenuAction*> actionItems; + OSMenuRef menu, apple_menu; + QMacMenuBarPrivate(); + ~QMacMenuBarPrivate(); + + void addAction(QAction *, QMacMenuAction* =0); + void addAction(QMacMenuAction *, QMacMenuAction* =0); + void syncAction(QMacMenuAction *); + inline void syncAction(QAction *a) { syncAction(findAction(a)); } + void removeAction(QMacMenuAction *); + inline void removeAction(QAction *a) { removeAction(findAction(a)); } + inline QMacMenuAction *findAction(QAction *a) { + for(int i = 0; i < actionItems.size(); i++) { + QMacMenuAction *act = actionItems[i]; + if(a == act->action) + return act; + } + return 0; + } + } *mac_menubar; + void macCreateMenuBar(QWidget *); + void macDestroyMenuBar(); + OSMenuRef macMenu(); +#endif +#ifdef Q_OS_WINCE + void wceCreateMenuBar(QWidget *); + void wceDestroyMenuBar(); + struct QWceMenuBarPrivate { + QList<QWceMenuAction*> actionItems; + QList<QWceMenuAction*> actionItemsLeftButton; + QList<QList<QWceMenuAction*>> actionItemsClassic; + HMENU menuHandle; + HMENU leftButtonMenuHandle; + HWND menubarHandle; + HWND parentWindowHandle; + bool leftButtonIsMenu; + QPointer<QAction> leftButtonAction; + QMenuBarPrivate *d; + int leftButtonCommand; + + QWceMenuBarPrivate(QMenuBarPrivate *menubar); + ~QWceMenuBarPrivate(); + void addAction(QAction *, QWceMenuAction* =0); + void addAction(QWceMenuAction *, QWceMenuAction* =0); + void syncAction(QWceMenuAction *); + inline void syncAction(QAction *a) { syncAction(findAction(a)); } + void removeAction(QWceMenuAction *); + void rebuild(); + inline void removeAction(QAction *a) { removeAction(findAction(a)); } + inline QWceMenuAction *findAction(QAction *a) { + for(int i = 0; i < actionItems.size(); i++) { + QWceMenuAction *act = actionItems[i]; + if(a == act->action) + return act; + } + return 0; + } + } *wce_menubar; + bool wceClassicMenu; + void wceCommands(uint command); + void wceRefresh(); + bool wceEmitSignals(QList<QWceMenuAction*> actions, uint command); +#endif +}; +#endif + +#endif // QT_NO_MENUBAR + +QT_END_NAMESPACE + +#endif // QMENUBAR_P_H diff --git a/src/gui/widgets/qmenudata.cpp b/src/gui/widgets/qmenudata.cpp new file mode 100644 index 0000000..568b67f --- /dev/null +++ b/src/gui/widgets/qmenudata.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qmenudata.h" + +#ifdef QT3_SUPPORT +#include <qaction.h> +#include <private/qaction_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QMenuItem + \brief The QMenuItem class represents an item in a menu. + + \compat + + Use QAction instead. +*/ + +/*! + \compat + Constructs a new menu item. +*/ +QMenuItem::QMenuItem() : QAction((QWidget*)0) +{ +} + +void QMenuItem::setId(int id) +{ + d_func()->param = d_func()->id = id; +} + +/*! + \compat + Returns the menu item's ID. +*/ +int QMenuItem::id() const +{ + return d_func()->id; +} + +void QMenuItem::setSignalValue(int param) +{ + d_func()->param = param; +} + +/*! + \compat + Returns the signal value for the menu item. +*/ +int QMenuItem::signalValue() const +{ + return d_func()->param; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/widgets/qmenudata.h b/src/gui/widgets/qmenudata.h new file mode 100644 index 0000000..24d960a --- /dev/null +++ b/src/gui/widgets/qmenudata.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMENUDATA_H +#define QMENUDATA_H + +#include <QtCore/qglobal.h> + +#ifdef QT3_SUPPORT +#include <QtGui/qaction.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QMenuItem : public QAction +{ + Q_OBJECT + +public: + QMenuItem(); + + QT3_SUPPORT int id() const; + QT3_SUPPORT int signalValue() const; +private: + friend class QMenu; + friend class QMenuBar; + void setId(int); + void setSignalValue(int); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + +#endif // QMENUDATA_H diff --git a/src/gui/widgets/qplaintextedit.cpp b/src/gui/widgets/qplaintextedit.cpp new file mode 100644 index 0000000..2e9201d --- /dev/null +++ b/src/gui/widgets/qplaintextedit.cpp @@ -0,0 +1,2893 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaintextedit_p.h" + + +#include <qfont.h> +#include <qpainter.h> +#include <qevent.h> +#include <qdebug.h> +#include <qmime.h> +#include <qdrag.h> +#include <qclipboard.h> +#include <qmenu.h> +#include <qstyle.h> +#include <qtimer.h> +#include "private/qtextdocumentlayout_p.h" +#include "private/qabstracttextdocumentlayout_p.h" +#include "qtextdocument.h" +#include "private/qtextdocument_p.h" +#include "qtextlist.h" +#include "private/qtextcontrol_p.h" + +#include <qtextformat.h> +#include <qdatetime.h> +#include <qapplication.h> +#include <limits.h> +#include <qtexttable.h> +#include <qvariant.h> + +#include <qinputcontext.h> + +#ifndef QT_NO_TEXTEDIT + +QT_BEGIN_NAMESPACE + +class QPlainTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate +{ + Q_DECLARE_PUBLIC(QPlainTextDocumentLayout) +public: + QPlainTextDocumentLayoutPrivate() { + mainViewPrivate = 0; + width = 0; + maximumWidth = 0; + maximumWidthBlockNumber = 0; + blockCount = 1; + blockUpdate = blockDocumentSizeChanged = false; + cursorWidth = 1; + textLayoutFlags = 0; + } + + qreal width; + qreal maximumWidth; + int maximumWidthBlockNumber; + int blockCount; + QPlainTextEditPrivate *mainViewPrivate; + bool blockUpdate; + bool blockDocumentSizeChanged; + int cursorWidth; + int textLayoutFlags; + + void layoutBlock(const QTextBlock &block); + qreal blockWidth(const QTextBlock &block); + + void relayout(); +}; + + + +/*! \class QPlainTextDocumentLayout + \since 4.4 + \brief The QPlainTextDocumentLayout class implements a plain text layout for QTextDocument + + \ingroup text + + + A QPlainTextDocumentLayout is required for text documents that can + be display or edited in a QPlainTextEdit. See + QTextDocument::setDocumentLayout(). + + QPlainTextDocumentLayout uses the QAbstractTextDocumentLayout API + that QTextDocument requires, but redefines it partially in order to + support plain text better. For instances, it does not operate on + vertical pixels, but on paragraphs (called blocks) instead. The + height of a document is identical to the number of paragraphs it + contains. The layout also doesn't support tables or nested frames, + or any sort of advanced text layout that goes beyond a list of + paragraphs with syntax highlighting. + +*/ + + + +/*! + Constructs a plain text document layout for the text \a document. + */ +QPlainTextDocumentLayout::QPlainTextDocumentLayout(QTextDocument *document) + :QAbstractTextDocumentLayout(* new QPlainTextDocumentLayoutPrivate, document) { +} +/*! + Destructs a plain text document layout. + */ +QPlainTextDocumentLayout::~QPlainTextDocumentLayout() {} + + +/*! + \reimp + */ +void QPlainTextDocumentLayout::draw(QPainter *, const PaintContext &) +{ +} + +/*! + \reimp + */ +int QPlainTextDocumentLayout::hitTest(const QPointF &, Qt::HitTestAccuracy ) const +{ +// this function is used from +// QAbstractTextDocumentLayout::anchorAt(), but is not +// implementable in a plain text document layout, because the +// layout depends on the top block and top line which depends on +// the view + return -1; +} + +/*! + \reimp + */ +int QPlainTextDocumentLayout::pageCount() const +{ return 1; } + +/*! + \reimp + */ +QSizeF QPlainTextDocumentLayout::documentSize() const +{ + Q_D(const QPlainTextDocumentLayout); + return QSizeF(d->maximumWidth, document()->lineCount()); +} + +/*! + \reimp + */ +QRectF QPlainTextDocumentLayout::frameBoundingRect(QTextFrame *) const +{ + Q_D(const QPlainTextDocumentLayout); + return QRectF(0, 0, qMax(d->width, d->maximumWidth), qreal(INT_MAX)); +} + +/*! + \reimp + */ +QRectF QPlainTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const +{ + if (!block.isValid()) { return QRectF(); } + QTextLayout *tl = block.layout(); + if (!tl->lineCount()) + const_cast<QPlainTextDocumentLayout*>(this)->layoutBlock(block); + QRectF br; + if (block.isVisible()) { + br = QRectF(QPointF(0, 0), tl->boundingRect().bottomRight()); + if (tl->lineCount() == 1) + br.setWidth(qMax(br.width(), tl->lineAt(0).naturalTextWidth())); + qreal margin = document()->documentMargin(); + br.adjust(0, 0, margin, 0); + if (!block.next().isValid()) + br.adjust(0, 0, 0, margin); + } + return br; + +} + +/*! + Ensures that \a block has a valid layout + */ +void QPlainTextDocumentLayout::ensureBlockLayout(const QTextBlock &block) const +{ + if (!block.isValid()) + return; + QTextLayout *tl = block.layout(); + if (!tl->lineCount()) + const_cast<QPlainTextDocumentLayout*>(this)->layoutBlock(block); +} + + +/*! \property QPlainTextDocumentLayout::cursorWidth + + This property specifies the width of the cursor in pixels. The default value is 1. +*/ +void QPlainTextDocumentLayout::setCursorWidth(int width) +{ + Q_D(QPlainTextDocumentLayout); + d->cursorWidth = width; +} + +int QPlainTextDocumentLayout::cursorWidth() const +{ + Q_D(const QPlainTextDocumentLayout); + return d->cursorWidth; +} + +QPlainTextDocumentLayoutPrivate *QPlainTextDocumentLayout::priv() const +{ + Q_D(const QPlainTextDocumentLayout); + return const_cast<QPlainTextDocumentLayoutPrivate*>(d); +} + + +/*! + + Requests a complete update on all views. + */ +void QPlainTextDocumentLayout::requestUpdate() +{ + emit update(QRectF(0., -4., 1000000000., 1000000000.)); +} + + +void QPlainTextDocumentLayout::setTextWidth(qreal newWidth) +{ + Q_D(QPlainTextDocumentLayout); + d->width = d->maximumWidth = newWidth; + d->relayout(); +} + +qreal QPlainTextDocumentLayout::textWidth() const +{ + Q_D(const QPlainTextDocumentLayout); + return d->width; +} + +void QPlainTextDocumentLayoutPrivate::relayout() +{ + Q_Q(QPlainTextDocumentLayout); + QTextBlock block = q->document()->firstBlock(); + while (block.isValid()) { + block.layout()->clearLayout(); + block.setLineCount(block.isVisible() ? 1 : 0); + block = block.next(); + } + emit q->update(); +} + + +/*! \reimp + */ +void QPlainTextDocumentLayout::documentChanged(int from, int /*charsRemoved*/, int charsAdded) +{ + Q_D(QPlainTextDocumentLayout); + QTextDocument *doc = document(); + int newBlockCount = doc->blockCount(); + + QTextBlock changeStartBlock = doc->findBlock(from); + QTextBlock changeEndBlock = doc->findBlock(qMax(0, from + charsAdded - 1)); + + if (changeStartBlock == changeEndBlock && newBlockCount == d->blockCount) { + QTextBlock block = changeStartBlock; + int blockLineCount = block.layout()->lineCount(); + if (block.isValid() && blockLineCount) { + QRectF oldBr = blockBoundingRect(block); + layoutBlock(block); + QRectF newBr = blockBoundingRect(block); + if (newBr.height() == oldBr.height()) { + if (!d->blockUpdate) + emit updateBlock(block); + return; + } + } + } else { + QTextBlock block = changeStartBlock; + do { + block.clearLayout(); + if (block == changeEndBlock) + break; + block = block.next(); + } while(block.isValid()); + } + + if (newBlockCount != d->blockCount) { + + int changeEnd = changeEndBlock.blockNumber(); + int blockDiff = newBlockCount - d->blockCount; + int oldChangeEnd = changeEnd - blockDiff; + + if (d->maximumWidthBlockNumber > oldChangeEnd) + d->maximumWidthBlockNumber += blockDiff; + + d->blockCount = newBlockCount; + if (d->blockCount == 1) + d->maximumWidth = blockWidth(doc->firstBlock()); + + if (!d->blockDocumentSizeChanged) + emit documentSizeChanged(documentSize()); + + if (blockDiff == 1 && changeEnd == newBlockCount -1 ) { + if (!d->blockUpdate) { + QTextBlock b = changeStartBlock; + for(;;) { + emit updateBlock(b); + if (b == changeEndBlock) + break; + b = b.next(); + } + } + return; + } + } + + if (!d->blockUpdate) + emit update(); // optimization potential + +} + + +void QPlainTextDocumentLayout::layoutBlock(const QTextBlock &block) +{ + Q_D(QPlainTextDocumentLayout); + QTextDocument *doc = document(); + qreal margin = doc->documentMargin(); + QFontMetrics fm(doc->defaultFont()); + qreal blockMaximumWidth = 0; + + int leading = qMax(0, fm.leading()); + qreal height = 0; + QTextLayout *tl = block.layout(); + QTextOption option = doc->defaultTextOption(); + tl->setTextOption(option); + + int extraMargin = 0; + if (option.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) { + QFontMetrics fm(block.charFormat().font()); + extraMargin += fm.width(QChar(0x21B5)); + } + tl->beginLayout(); + while (1) { + QTextLine line = tl->createLine(); + if (!line.isValid()) + break; + line.setLineWidth(d->width - 2*margin - extraMargin); + + height += leading; + line.setPosition(QPointF(margin, height)); + height += line.height(); + blockMaximumWidth = qMax(blockMaximumWidth, line.naturalTextWidth() + 2*margin); + } + tl->endLayout(); + + int previousLineCount = doc->lineCount(); + const_cast<QTextBlock&>(block).setLineCount(block.isVisible() ? tl->lineCount() : 0); + int lineCount = doc->lineCount(); + + bool emitDocumentSizeChanged = previousLineCount != lineCount; + if (blockMaximumWidth > d->maximumWidth) { + // new longest line + d->maximumWidth = blockMaximumWidth; + d->maximumWidthBlockNumber = block.blockNumber(); + emitDocumentSizeChanged = true; + } else if (block.blockNumber() == d->maximumWidthBlockNumber && blockMaximumWidth < d->maximumWidth) { + // longest line shrinking + QTextBlock b = doc->firstBlock(); + d->maximumWidth = 0; + QTextBlock maximumBlock; + while (b.isValid()) { + qreal blockMaximumWidth = blockWidth(b); + if (blockMaximumWidth > d->maximumWidth) { + d->maximumWidth = blockMaximumWidth; + maximumBlock = b; + } + b = b.next(); + } + if (maximumBlock.isValid()) { + d->maximumWidthBlockNumber = maximumBlock.blockNumber(); + emitDocumentSizeChanged = true; + } + } + if (emitDocumentSizeChanged && !d->blockDocumentSizeChanged) + emit documentSizeChanged(documentSize()); +} + +qreal QPlainTextDocumentLayout::blockWidth(const QTextBlock &block) +{ + QTextLayout *layout = block.layout(); + if (!layout->lineCount()) + return 0; // only for layouted blocks + qreal blockWidth = 0; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + blockWidth = qMax(line.naturalTextWidth() + 8, blockWidth); + } + return blockWidth; +} + + +QPlainTextEditControl::QPlainTextEditControl(QPlainTextEdit *parent) + : QTextControl(parent), textEdit(parent), + topBlock(0) +{ + setAcceptRichText(false); +} + +void QPlainTextEditPrivate::_q_cursorPositionChanged() +{ + pageUpDownLastCursorYIsValid = false; +}; + +void QPlainTextEditPrivate::_q_verticalScrollbarActionTriggered(int action) { + if (action == QAbstractSlider::SliderPageStepAdd) { + pageUpDown(QTextCursor::Down, QTextCursor::MoveAnchor, false); + } else if (action == QAbstractSlider::SliderPageStepSub) { + pageUpDown(QTextCursor::Up, QTextCursor::MoveAnchor, false); + } +} + +QMimeData *QPlainTextEditControl::createMimeDataFromSelection() const { + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(parent()); + if (!ed) + return QTextControl::createMimeDataFromSelection(); + return ed->createMimeDataFromSelection(); + } +bool QPlainTextEditControl::canInsertFromMimeData(const QMimeData *source) const { + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(parent()); + if (!ed) + return QTextControl::canInsertFromMimeData(source); + return ed->canInsertFromMimeData(source); +} +void QPlainTextEditControl::insertFromMimeData(const QMimeData *source) { + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(parent()); + if (!ed) + QTextControl::insertFromMimeData(source); + else + ed->insertFromMimeData(source); +} + +int QPlainTextEditPrivate::verticalOffset(int topBlock, int topLine) const +{ + qreal offset = 0; + QTextDocument *doc = control->document(); + + if (topLine) { + QTextBlock currentBlock = doc->findBlockByNumber(topBlock); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + QRectF r = documentLayout->blockBoundingRect(currentBlock); + QTextLayout *layout = currentBlock.layout(); + if (layout && topLine <= layout->lineCount()) { + QTextLine line = layout->lineAt(topLine - 1); + const QRectF lr = line.naturalTextRect(); + offset = lr.bottom(); + } + } + if (topBlock == 0 && topLine == 0) + offset -= doc->documentMargin(); // top margin + return (int)offset; +} + + +int QPlainTextEditPrivate::verticalOffset() const { + return verticalOffset(control->topBlock, topLine); +} + + +QTextBlock QPlainTextEditControl::firstVisibleBlock() const +{ + return document()->findBlockByNumber(topBlock); +} + + + +int QPlainTextEditControl::hitTest(const QPointF &point, Qt::HitTestAccuracy ) const { + int currentBlockNumber = topBlock; + QTextBlock currentBlock = document()->findBlockByNumber(currentBlockNumber); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + + QPointF offset; + QRectF r = documentLayout->blockBoundingRect(currentBlock); + while (currentBlock.next().isValid() && r.bottom() + offset.y() <= point.y()) { + offset.ry() += r.height(); + currentBlock = currentBlock.next(); + ++currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + } + while (currentBlock.previous().isValid() && r.top() + offset.y() > point.y()) { + offset.ry() -= r.height(); + currentBlock = currentBlock.previous(); + --currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + } + + + if (!currentBlock.isValid()) + return -1; + QTextLayout *layout = currentBlock.layout(); + int off = 0; + QPointF pos = point - offset; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + const QRectF lr = line.naturalTextRect(); + if (lr.top() > pos.y()) { + off = qMin(off, line.textStart()); + } else if (lr.bottom() <= pos.y()) { + off = qMax(off, line.textStart() + line.textLength()); + } else { + off = line.xToCursor(pos.x(), overwriteMode() ? + QTextLine::CursorOnCharacter : QTextLine::CursorBetweenCharacters); + break; + } + } + + return currentBlock.position() + off; +} + +QRectF QPlainTextEditControl::blockBoundingRect(const QTextBlock &block) const { + int currentBlockNumber = topBlock; + int blockNumber = block.blockNumber(); + QTextBlock currentBlock = document()->findBlockByNumber(currentBlockNumber); + if (!currentBlock.isValid()) + return QRectF(); + Q_ASSERT(currentBlock.blockNumber() == currentBlockNumber); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + + QPointF offset; + if (!block.isValid()) + return QRectF(); + QRectF r = documentLayout->blockBoundingRect(currentBlock); + while (currentBlockNumber < blockNumber && offset.y() <= 2* textEdit->viewport()->height()) { + offset.ry() += r.height(); + currentBlock = currentBlock.next(); + ++currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + } + while (currentBlockNumber > blockNumber && offset.y() >= -textEdit->viewport()->height()) { + currentBlock = currentBlock.previous(); + if (!currentBlock.isValid()) + break; + --currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + offset.ry() -= r.height(); + } + + if (currentBlockNumber != blockNumber) { + // fallback for blocks out of reach. Give it some geometry at + // least, and ensure the layout is up to date. + r = documentLayout->blockBoundingRect(block); + if (currentBlockNumber > blockNumber) + offset.ry() -= r.height(); + } + r.translate(offset); + return r; +} + + +void QPlainTextEditPrivate::setTopLine(int visualTopLine, int dx) +{ + QTextDocument *doc = control->document(); + QTextBlock block = doc->findBlockByLineNumber(visualTopLine); + int blockNumber = block.blockNumber(); + int lineNumber = visualTopLine - block.firstLineNumber(); + setTopBlock(blockNumber, lineNumber, dx); +} + +void QPlainTextEditPrivate::setTopBlock(int blockNumber, int lineNumber, int dx) +{ + Q_Q(QPlainTextEdit); + blockNumber = qMax(0, blockNumber); + lineNumber = qMax(0, lineNumber); + QTextDocument *doc = control->document(); + QTextBlock block = doc->findBlockByNumber(blockNumber); + + int newTopLine = block.firstLineNumber() + lineNumber; + int maxTopLine = vbar->maximum(); + + if (newTopLine > maxTopLine) { + block = doc->findBlockByLineNumber(maxTopLine); + blockNumber = block.blockNumber(); + lineNumber = maxTopLine - block.firstLineNumber(); + } + + bool vbarSignalsBlocked = vbar->blockSignals(true); + vbar->setValue(newTopLine); + vbar->blockSignals(vbarSignalsBlocked); + + if (!dx && blockNumber == control->topBlock && lineNumber == topLine) + return; + + if (viewport->updatesEnabled() && viewport->isVisible()) { + int dy = 0; + if (doc->findBlockByLineNumber(control->topBlock).isValid()) { + dy = (int)(-q->blockBoundingGeometry(block).y()) + + verticalOffset() - verticalOffset(blockNumber, lineNumber); + } + control->topBlock = blockNumber; + topLine = lineNumber; + if (dx || dy) + viewport->scroll(q->isRightToLeft() ? -dx : dx, dy); + else + viewport->update(); + emit q->updateRequest(viewport->rect(), dy); + } else { + control->topBlock = blockNumber; + topLine = lineNumber; + } + +} + + + +void QPlainTextEditPrivate::ensureVisible(int position, bool center, bool forceCenter) { + Q_Q(QPlainTextEdit); + QRectF visible = QRectF(viewport->rect()).translated(-q->contentOffset()); + QTextBlock block = control->document()->findBlock(position); + if (!block.isValid()) + return; + QRectF br = control->blockBoundingRect(block); + if (!br.isValid()) + return; + QRectF lr = br; + QTextLine line = block.layout()->lineForTextPosition(position - block.position()); + Q_ASSERT(line.isValid()); + lr = line.naturalTextRect().translated(br.topLeft()); + + if (lr.bottom() >= visible.bottom() || (center && lr.top() < visible.top()) || forceCenter){ + + qreal height = visible.height(); + if (center) + height /= 2; + + qreal h = center ? line.naturalTextRect().center().y() : line.naturalTextRect().bottom(); + + while (h < height && block.previous().isValid()) { + block = block.previous(); + h += q->blockBoundingRect(block).height(); + } + + int l = 0; + int lineCount = block.layout()->lineCount(); + int voffset = verticalOffset(block.blockNumber(), 0); + while (l < lineCount) { + QRectF lineRect = block.layout()->lineAt(l).naturalTextRect(); + if (h - voffset - lineRect.top() <= height) + break; + ++l; + } + + if (block.next().isValid() && l >= lineCount) { + block = block.next(); + l = 0; + } + setTopBlock(block.blockNumber(), l); + } else if (lr.top() < visible.top()) { + setTopBlock(block.blockNumber(), line.lineNumber()); + } + +} + + +void QPlainTextEditPrivate::updateViewport() +{ + Q_Q(QPlainTextEdit); + viewport->update(); + emit q->updateRequest(viewport->rect(), 0); +} + +QPlainTextEditPrivate::QPlainTextEditPrivate() + : control(0), + tabChangesFocus(false), + lineWrap(QPlainTextEdit::WidgetWidth), + wordWrap(QTextOption::WrapAtWordBoundaryOrAnywhere), + topLine(0), pageUpDownLastCursorYIsValid(false) +{ + showCursorOnInitialShow = true; + backgroundVisible = false; + centerOnScroll = false; + inDrag = false; +} + + +void QPlainTextEditPrivate::init(const QString &txt) +{ + Q_Q(QPlainTextEdit); + control = new QPlainTextEditControl(q); + + QTextDocument *doc = new QTextDocument(control); + QAbstractTextDocumentLayout *layout = new QPlainTextDocumentLayout(doc); + doc->setDocumentLayout(layout); + control->setDocument(doc); + + control->setPalette(q->palette()); + + QObject::connect(vbar, SIGNAL(actionTriggered(int)), q, SLOT(_q_verticalScrollbarActionTriggered(int))); + + QObject::connect(control, SIGNAL(microFocusChanged()), q, SLOT(updateMicroFocus())); + QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), q, SLOT(_q_adjustScrollbars())); + QObject::connect(control, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); + QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(_q_repaintContents(QRectF))); + QObject::connect(control, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); + + QObject::connect(control, SIGNAL(textChanged()), q, SIGNAL(textChanged())); + QObject::connect(control, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); + QObject::connect(control, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); + QObject::connect(control, SIGNAL(copyAvailable(bool)), q, SIGNAL(copyAvailable(bool))); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(_q_cursorPositionChanged())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged())); + + + // set a null page size initially to avoid any relayouting until the textedit + // is shown. relayoutDocument() will take care of setting the page size to the + // viewport dimensions later. + doc->setTextWidth(0); + doc->documentLayout()->setPaintDevice(viewport); + doc->setDefaultFont(q->font()); + + + if (!txt.isEmpty()) + control->setPlainText(txt); + + hbar->setSingleStep(20); + vbar->setSingleStep(1); + + viewport->setBackgroundRole(QPalette::Base); + q->setAcceptDrops(true); + q->setFocusPolicy(Qt::WheelFocus); + q->setAttribute(Qt::WA_KeyCompression); + q->setAttribute(Qt::WA_InputMethodEnabled); + +#ifndef QT_NO_CURSOR + viewport->setCursor(Qt::IBeamCursor); +#endif +} + +void QPlainTextEditPrivate::_q_repaintContents(const QRectF &contentsRect) +{ + Q_Q(QPlainTextEdit); + if (!contentsRect.isValid()) { + updateViewport(); + return; + } + const int xOffset = horizontalOffset(); + const int yOffset = verticalOffset(); + const QRect visibleRect(xOffset, yOffset, viewport->width(), viewport->height()); + + QRect r = contentsRect.adjusted(-1, -1, 1, 1).intersected(visibleRect).toAlignedRect(); + if (r.isEmpty()) + return; + + r.translate(-xOffset, -yOffset); + viewport->update(r); + emit q->updateRequest(r, 0); +} + +void QPlainTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode, bool moveCursor) +{ + + Q_Q(QPlainTextEdit); + + QTextCursor cursor = control->textCursor(); + if (moveCursor) { + ensureCursorVisible(); + if (!pageUpDownLastCursorYIsValid) + pageUpDownLastCursorY = control->cursorRect(cursor).top() - verticalOffset(); + } + + qreal lastY = pageUpDownLastCursorY; + + + if (op == QTextCursor::Down) { + QRectF visible = QRectF(viewport->rect()).translated(-q->contentOffset()); + QTextBlock firstVisibleBlock = q->firstVisibleBlock(); + QTextBlock block = firstVisibleBlock; + QRectF br = q->blockBoundingRect(block); + qreal h = 0; + int atEnd = false; + while (h + br.height() <= visible.bottom()) { + if (!block.next().isValid()) { + atEnd = true; + lastY = visible.bottom(); // set cursor to last line + break; + } + h += br.height(); + block = block.next(); + br = q->blockBoundingRect(block); + } + + if (!atEnd) { + int line = 0; + qreal diff = visible.bottom() - h; + int lineCount = block.layout()->lineCount(); + while (line < lineCount - 1) { + if (block.layout()->lineAt(line).naturalTextRect().bottom() > diff) { + // the first line that did not completely fit the screen + break; + } + ++line; + } + setTopBlock(block.blockNumber(), line); + } + + if (moveCursor) { + // move using movePosition to keep the cursor's x + lastY += verticalOffset(); + bool moved = false; + do { + moved = cursor.movePosition(op, moveMode); + } while (moved && control->cursorRect(cursor).top() < lastY); + } + + } else if (op == QTextCursor::Up) { + + QRectF visible = QRectF(viewport->rect()).translated(-q->contentOffset()); + visible.translate(0, -visible.height()); // previous page + QTextBlock block = q->firstVisibleBlock(); + qreal h = 0; + while (h >= visible.top()) { + if (!block.previous().isValid()) { + if (control->topBlock == 0 && topLine == 0) { + lastY = 0; // set cursor to first line + } + break; + } + block = block.previous(); + QRectF br = q->blockBoundingRect(block); + h -= br.height(); + } + + int line = 0; + if (block.isValid()) { + qreal diff = visible.top() - h; + int lineCount = block.layout()->lineCount(); + while (line < lineCount) { + if (block.layout()->lineAt(line).naturalTextRect().top() >= diff) + break; + ++line; + } + if (line == lineCount) { + if (block.next().isValid() && block.next() != q->firstVisibleBlock()) { + block = block.next(); + line = 0; + } else { + --line; + } + } + } + setTopBlock(block.blockNumber(), line); + + if (moveCursor) { + // move using movePosition to keep the cursor's x + lastY += verticalOffset(); + bool moved = false; + do { + moved = cursor.movePosition(op, moveMode); + } while (moved && control->cursorRect(cursor).top() > lastY); + } + } + + if (moveCursor) { + control->setTextCursor(cursor); + pageUpDownLastCursorYIsValid = true; + } +} + +#ifndef QT_NO_SCROLLBAR + +void QPlainTextEditPrivate::_q_adjustScrollbars() +{ + Q_Q(QPlainTextEdit); + QTextDocument *doc = control->document(); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + bool documentSizeChangedBlocked = documentLayout->priv()->blockDocumentSizeChanged; + documentLayout->priv()->blockDocumentSizeChanged = true; + qreal margin = doc->documentMargin(); + + int vmax = 0; + + int vSliderLength = 0; + if (!centerOnScroll && q->isVisible()) { + QTextBlock block = doc->lastBlock(); + const int visible = static_cast<int>(viewport->rect().height() - margin - 1); + int y = 0; + int visibleFromBottom = 0; + + while (block.isValid()) { + if (!block.isVisible()) { + block = block.previous(); + continue; + } + y += int(documentLayout->blockBoundingRect(block).height()); + + QTextLayout *layout = block.layout(); + int layoutLineCount = layout->lineCount(); + if (y > visible) { + int lineNumber = 0; + while (lineNumber < layoutLineCount) { + QTextLine line = layout->lineAt(lineNumber); + const QRectF lr = line.naturalTextRect(); + if (int(lr.top()) >= y - visible) + break; + ++lineNumber; + } + if (lineNumber < layoutLineCount) + visibleFromBottom += (layoutLineCount - lineNumber - 1); + break; + + } + visibleFromBottom += layoutLineCount; + block = block.previous(); + } + vmax = qMax(0, doc->lineCount() - visibleFromBottom); + vSliderLength = visibleFromBottom; + + } else { + vmax = qMax(0, doc->lineCount() - 1); + vSliderLength = viewport->height() / q->fontMetrics().lineSpacing(); + } + + + + QSizeF documentSize = documentLayout->documentSize(); + vbar->setRange(0, qMax(0, vmax)); + vbar->setPageStep(vSliderLength); + int visualTopLine = vmax; + QTextBlock firstVisibleBlock = q->firstVisibleBlock(); + if (firstVisibleBlock.isValid()) + visualTopLine = firstVisibleBlock.firstLineNumber() + topLine; + bool vbarSignalsBlocked = vbar->blockSignals(true); + vbar->setValue(visualTopLine); + vbar->blockSignals(vbarSignalsBlocked); + + hbar->setRange(0, (int)documentSize.width() - viewport->width()); + hbar->setPageStep(viewport->width()); + documentLayout->priv()->blockDocumentSizeChanged = documentSizeChangedBlocked; + setTopLine(vbar->value()); +} + +#endif + + +void QPlainTextEditPrivate::ensureViewportLayouted() +{ +} + +/*! + \class QPlainTextEdit + \since 4.4 + \brief The QPlainTextEdit class provides a widget that is used to edit and display + plain text. + + \ingroup text + \mainclass + + \tableofcontents + + \section1 Introduction and Concepts + + QPlainTextEdit is an advanced viewer/editor supporting plain + text. It is optimized to handle large documents and to respond + quickly to user input. + + QPlainText uses very much the same technology and concepts as + QTextEdit, but is optimized for plain text handling. + + QPlainTextEdit works on paragraphs and characters. A paragraph is a + formatted string which is word-wrapped to fit into the width of + the widget. By default when reading plain text, one newline + signifies a paragraph. A document consists of zero or more + paragraphs. The words in the paragraph are aligned in accordance + with the paragraph's alignment. Paragraphs are separated by hard + line breaks. Each character within a paragraph has its own + attributes, for example, font and color. + + The shape of the mouse cursor on a QPlainTextEdit is + Qt::IBeamCursor by default. It can be changed through the + viewport()'s cursor property. + + \section1 Using QPlainTextEdit as a Display Widget + + The text is set or replaced using setPlainText() which deletes any + existing text and replaces it with the text passed in the + setPlainText() call. + + Text itself can be inserted using the QTextCursor class or using + the convenience functins insertPlainText(), appendPlainText() or + paste(). + + By default the text edit wraps words at whitespace to fit within + the text edit widget. The setLineWrapMode() function is used to + specify the kind of line wrap you want, \l WidgetWidth or \l + NoWrap if you don't want any wrapping. If you use word wrap to + the widget's width \l WidgetWidth, you can specify whether to + break on whitespace or anywhere with setWordWrapMode(). + + The find() function can be used to find and select a given string + within the text. + + If you want to limit the total number of paragraphs in a + QPlainTextEdit, as it is for example useful in a log viewer, then + you can use the maximumBlockCount property. The combination of + setMaximumBlockCount() and appendPlainText() turns QPlainTextEdit + into an efficient viewer for log text. The scrolling can be + reduced with the centerOnScroll() property, making the log viewer + even faster. Text can be formatted in a limited way, either using + a syntax highlighter (see below), or by appending html-formatted + text with appendHtml(). While QPlainTextEdit does not support + complex rich text rendering with tables and floats, it does + support limited paragraph-based formatting that you may need in a + log viewer. + + \section2 Read-only Key Bindings + + When QPlainTextEdit is used read-only the key bindings are limited to + navigation, and text may only be selected with the mouse: + \table + \header \i Keypresses \i Action + \row \i Qt::UpArrow \i Moves one line up. + \row \i Qt::DownArrow \i Moves one line down. + \row \i Qt::LeftArrow \i Moves one character to the left. + \row \i Qt::RightArrow \i Moves one character to the right. + \row \i PageUp \i Moves one (viewport) page up. + \row \i PageDown \i Moves one (viewport) page down. + \row \i Home \i Moves to the beginning of the text. + \row \i End \i Moves to the end of the text. + \row \i Alt+Wheel + \i Scrolls the page horizontally (the Wheel is the mouse wheel). + \row \i Ctrl+Wheel \i Zooms the text. + \row \i Ctrl+A \i Selects all text. + \endtable + + + \section1 Using QPlainTextEdit as an Editor + + All the information about using QPlainTextEdit as a display widget also + applies here. + + Selection of text is handled by the QTextCursor class, which provides + functionality for creating selections, retrieving the text contents or + deleting selections. You can retrieve the object that corresponds with + the user-visible cursor using the textCursor() method. If you want to set + a selection in QPlainTextEdit just create one on a QTextCursor object and + then make that cursor the visible cursor using setCursor(). The selection + can be copied to the clipboard with copy(), or cut to the clipboard with + cut(). The entire text can be selected using selectAll(). + + QPlainTextEdit holds a QTextDocument object which can be retrieved using the + document() method. You can also set your own document object using setDocument(). + QTextDocument emits a textChanged() signal if the text changes and it also + provides a isModified() function which will return true if the text has been + modified since it was either loaded or since the last call to setModified + with false as argument. In addition it provides methods for undo and redo. + + \section2 Syntax Highlighting + + Just like QTextEdit, QPlainTextEdit works together with + QSyntaxHighlighter. + + \section2 Editing Key Bindings + + The list of key bindings which are implemented for editing: + \table + \header \i Keypresses \i Action + \row \i Backspace \i Deletes the character to the left of the cursor. + \row \i Delete \i Deletes the character to the right of the cursor. + \row \i Ctrl+C \i Copy the selected text to the clipboard. + \row \i Ctrl+Insert \i Copy the selected text to the clipboard. + \row \i Ctrl+K \i Deletes to the end of the line. + \row \i Ctrl+V \i Pastes the clipboard text into text edit. + \row \i Shift+Insert \i Pastes the clipboard text into text edit. + \row \i Ctrl+X \i Deletes the selected text and copies it to the clipboard. + \row \i Shift+Delete \i Deletes the selected text and copies it to the clipboard. + \row \i Ctrl+Z \i Undoes the last operation. + \row \i Ctrl+Y \i Redoes the last operation. + \row \i LeftArrow \i Moves the cursor one character to the left. + \row \i Ctrl+LeftArrow \i Moves the cursor one word to the left. + \row \i RightArrow \i Moves the cursor one character to the right. + \row \i Ctrl+RightArrow \i Moves the cursor one word to the right. + \row \i UpArrow \i Moves the cursor one line up. + \row \i Ctrl+UpArrow \i Moves the cursor one word up. + \row \i DownArrow \i Moves the cursor one line down. + \row \i Ctrl+Down Arrow \i Moves the cursor one word down. + \row \i PageUp \i Moves the cursor one page up. + \row \i PageDown \i Moves the cursor one page down. + \row \i Home \i Moves the cursor to the beginning of the line. + \row \i Ctrl+Home \i Moves the cursor to the beginning of the text. + \row \i End \i Moves the cursor to the end of the line. + \row \i Ctrl+End \i Moves the cursor to the end of the text. + \row \i Alt+Wheel \i Scrolls the page horizontally (the Wheel is the mouse wheel). + \row \i Ctrl+Wheel \i Zooms the text. + \endtable + + To select (mark) text hold down the Shift key whilst pressing one + of the movement keystrokes, for example, \e{Shift+Right Arrow} + will select the character to the right, and \e{Shift+Ctrl+Right + Arrow} will select the word to the right, etc. + + \section1 Differences to QTextEdit + + QPlainTextEdit is a thin class, implemented by using most of the + technology that is behind QTextEdit and QTextDocument. Its + performance benefits over QTextEdit stem mostly from using a + different and simplified text layout called + QPlainTextDocumentLayout on the text document (see + QTextDocument::setDocumentLayout()). The plain text document layout + does not support tables nor embedded frames, and \e{replaces a + pixel-exact height calculation with a line-by-line respectively + paragraph-by-paragraph scrolling approach}. This makes it possible + to handle significantly larger documents, and still resize the + editor with line wrap enabled in real time. It also makes for a + fast log viewer (see setMaximumBlockCount()). + + + \sa QTextDocument, QTextCursor, {Application Example}, + {Syntax Highlighter Example}, {Rich Text Processing} + +*/ + +/*! + \property QPlainTextEdit::plainText + + This property gets and sets the plain text editor's contents. The previous + contents are removed and undo/redo history is reset when this property is set. + + By default, for an editor with no contents, this property contains an empty string. +*/ + +/*! + \property QPlainTextEdit::undoRedoEnabled + \brief whether undo and redo are enabled + + Users are only able to undo or redo actions if this property is + true, and if there is an action that can be undone (or redone). + + By default, this property is true. +*/ + +/*! + \enum QPlainTextEdit::LineWrapMode + + \value NoWrap + \value WidgetWidth +*/ + + +/*! + Constructs an empty QPlainTextEdit with parent \a + parent. +*/ +QPlainTextEdit::QPlainTextEdit(QWidget *parent) + : QAbstractScrollArea(*new QPlainTextEditPrivate, parent) +{ + Q_D(QPlainTextEdit); + d->init(); +} + +/*! + \internal +*/ +QPlainTextEdit::QPlainTextEdit(QPlainTextEditPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + Q_D(QPlainTextEdit); + d->init(); +} + +/*! + Constructs a QPlainTextEdit with parent \a parent. The text edit will display + the plain text \a text. +*/ +QPlainTextEdit::QPlainTextEdit(const QString &text, QWidget *parent) + : QAbstractScrollArea(*new QPlainTextEditPrivate, parent) +{ + Q_D(QPlainTextEdit); + d->init(text); +} + + +/*! + Destructor. +*/ +QPlainTextEdit::~QPlainTextEdit() +{ + Q_D(QPlainTextEdit); + if (d->documentLayoutPtr) { + if (d->documentLayoutPtr->priv()->mainViewPrivate == d) + d->documentLayoutPtr->priv()->mainViewPrivate = 0; + } +} + +/*! + Makes \a document the new document of the text editor. + + The parent QObject of the provided document remains the owner + of the object. If the current document is a child of the text + editor, then it is deleted. + + The document must have a document layout that inherits + QPlainTextDocumentLayout (see QTextDocument::setDocumentLayout()). + + \sa document() +*/ +void QPlainTextEdit::setDocument(QTextDocument *document) +{ + Q_D(QPlainTextEdit); + QPlainTextDocumentLayout *documentLayout = 0; + + if (!document) { + document = new QTextDocument(d->control); + documentLayout = new QPlainTextDocumentLayout(document); + document->setDocumentLayout(documentLayout); + } else { + documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document->documentLayout()); + if (!documentLayout) { + qWarning("QPlainTextEdit::setDocument: Document set does not support QPlainTextDocumentLayout"); + return; + } + } + d->control->setDocument(document); + if (!documentLayout->priv()->mainViewPrivate) + documentLayout->priv()->mainViewPrivate = d; + d->documentLayoutPtr = documentLayout; + d->updateDefaultTextOption(); + d->relayoutDocument(); + d->_q_adjustScrollbars(); +} + +/*! + Returns a pointer to the underlying document. + + \sa setDocument() +*/ +QTextDocument *QPlainTextEdit::document() const +{ + Q_D(const QPlainTextEdit); + return d->control->document(); +} + +/*! + Sets the visible \a cursor. +*/ +void QPlainTextEdit::setTextCursor(const QTextCursor &cursor) +{ + Q_D(QPlainTextEdit); + d->control->setTextCursor(cursor); +} + +/*! + Returns a copy of the QTextCursor that represents the currently visible cursor. + Note that changes on the returned cursor do not affect QPlainTextEdit's cursor; use + setTextCursor() to update the visible cursor. + */ +QTextCursor QPlainTextEdit::textCursor() const +{ + Q_D(const QPlainTextEdit); + return d->control->textCursor(); +} + + +/*! + Undoes the last operation. + + If there is no operation to undo, i.e. there is no undo step in + the undo/redo history, nothing happens. + + \sa redo() +*/ +void QPlainTextEdit::undo() +{ + Q_D(QPlainTextEdit); + d->control->undo(); +} + +void QPlainTextEdit::redo() +{ + Q_D(QPlainTextEdit); + d->control->redo(); +} + +/*! + \fn void QPlainTextEdit::redo() + + Redoes the last operation. + + If there is no operation to redo, i.e. there is no redo step in + the undo/redo history, nothing happens. + + \sa undo() +*/ + +#ifndef QT_NO_CLIPBOARD +/*! + Copies the selected text to the clipboard and deletes it from + the text edit. + + If there is no selected text nothing happens. + + \sa copy() paste() +*/ + +void QPlainTextEdit::cut() +{ + Q_D(QPlainTextEdit); + d->control->cut(); +} + +/*! + Copies any selected text to the clipboard. + + \sa copyAvailable() +*/ + +void QPlainTextEdit::copy() +{ + Q_D(QPlainTextEdit); + d->control->copy(); +} + +/*! + Pastes the text from the clipboard into the text edit at the + current cursor position. + + If there is no text in the clipboard nothing happens. + + To change the behavior of this function, i.e. to modify what + QPlainTextEdit can paste and how it is being pasted, reimplement the + virtual canInsertFromMimeData() and insertFromMimeData() + functions. + + \sa cut() copy() +*/ + +void QPlainTextEdit::paste() +{ + Q_D(QPlainTextEdit); + d->control->paste(); +} +#endif + +/*! + Deletes all the text in the text edit. + + Note that the undo/redo history is cleared by this function. + + \sa cut() setPlainText() +*/ +void QPlainTextEdit::clear() +{ + Q_D(QPlainTextEdit); + // clears and sets empty content + d->control->topBlock = d->topLine = 0; + d->control->clear(); +} + + +/*! + Selects all text. + + \sa copy() cut() textCursor() + */ +void QPlainTextEdit::selectAll() +{ + Q_D(QPlainTextEdit); + d->control->selectAll(); +} + +/*! \internal +*/ +bool QPlainTextEdit::event(QEvent *e) +{ + Q_D(QPlainTextEdit); + +#ifndef QT_NO_CONTEXTMENU + if (e->type() == QEvent::ContextMenu + && static_cast<QContextMenuEvent *>(e)->reason() == QContextMenuEvent::Keyboard) { + ensureCursorVisible(); + const QPoint cursorPos = cursorRect().center(); + QContextMenuEvent ce(QContextMenuEvent::Keyboard, cursorPos, d->viewport->mapToGlobal(cursorPos)); + ce.setAccepted(e->isAccepted()); + const bool result = QAbstractScrollArea::event(&ce); + e->setAccepted(ce.isAccepted()); + return result; + } +#endif // QT_NO_CONTEXTMENU + if (e->type() == QEvent::ShortcutOverride + || e->type() == QEvent::ToolTip) { + d->sendControlEvent(e); + } +#ifdef QT_KEYPAD_NAVIGATION + else if (e->type() == QEvent::EnterEditFocus || e->type() == QEvent::LeaveEditFocus) { + if (QApplication::keypadNavigationEnabled()) + d->sendControlEvent(e); + } +#endif + return QAbstractScrollArea::event(e); +} + +/*! \internal +*/ + +void QPlainTextEdit::timerEvent(QTimerEvent *e) +{ + Q_D(QPlainTextEdit); + if (e->timerId() == d->autoScrollTimer.timerId()) { + QRect visible = d->viewport->rect(); + QPoint pos; + if (d->inDrag) { + pos = d->autoScrollDragPos; + visible.adjust(qMin(visible.width()/3,20), qMin(visible.height()/3,20), + -qMin(visible.width()/3,20), -qMin(visible.height()/3,20)); + } else { + const QPoint globalPos = QCursor::pos(); + pos = d->viewport->mapFromGlobal(globalPos); + QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mouseMoveEvent(&ev); + } + int deltaY = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height(); + int deltaX = qMax(pos.x() - visible.left(), visible.right() - pos.x()) - visible.width(); + int delta = qMax(deltaX, deltaY); + if (delta >= 0) { + if (delta < 7) + delta = 7; + int timeout = 4900 / (delta * delta); + d->autoScrollTimer.start(timeout, this); + + if (deltaY > 0) + d->vbar->triggerAction(pos.y() < visible.center().y() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + if (deltaX > 0) + d->hbar->triggerAction(pos.x() < visible.center().x() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + } + } +#ifdef QT_KEYPAD_NAVIGATION + else if (e->timerId() == d->deleteAllTimer.timerId()) { + d->deleteAllTimer.stop(); + clear(); + } +#endif +} + +/*! + Changes the text of the text edit to the string \a text. + Any previous text is removed. + + \a text is interpreted as plain text. + + Note that the undo/redo history is cleared by this function. + + \sa toText() +*/ + +void QPlainTextEdit::setPlainText(const QString &text) +{ + Q_D(QPlainTextEdit); + d->control->setPlainText(text); +} + +/*! + \fn QString QPlainTextEdit::toPlainText() const + + Returns the text of the text edit as plain text. + + \sa QPlainTextEdit::setPlainText() + */ + +/*! \reimp +*/ +void QPlainTextEdit::keyPressEvent(QKeyEvent *e) +{ + Q_D(QPlainTextEdit); + +#ifdef QT_KEYPAD_NAVIGATION + switch (e->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (!(d->control->textInteractionFlags() & Qt::LinksAccessibleByKeyboard)) + setEditFocus(!hasEditFocus()); + else { + if (!hasEditFocus()) + setEditFocus(true); + else { + QTextCursor cursor = d->control->textCursor(); + QTextCharFormat charFmt = cursor.charFormat(); + if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) { + setEditFocus(false); + } + } + } + } + break; + case Qt::Key_Back: + case Qt::Key_No: + if (!QApplication::keypadNavigationEnabled() + || (QApplication::keypadNavigationEnabled() && !hasEditFocus())) { + e->ignore(); + return; + } + break; + default: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus() && !(e->modifiers() & Qt::ControlModifier)) { + if (e->text()[0].isPrint()) { + setEditFocus(true); + clear(); + } else { + e->ignore(); + return; + } + } + } + break; + } +#endif + + if (!(d->control->textInteractionFlags() & Qt::TextEditable)) { + switch (e->key()) { + case Qt::Key_Space: + e->accept(); + if (e->modifiers() & Qt::ShiftModifier) + d->vbar->triggerAction(QAbstractSlider::SliderPageStepSub); + else + d->vbar->triggerAction(QAbstractSlider::SliderPageStepAdd); + break; + default: + d->sendControlEvent(e); + if (!e->isAccepted() && e->modifiers() == Qt::NoModifier) { + if (e->key() == Qt::Key_Home) { + d->vbar->triggerAction(QAbstractSlider::SliderToMinimum); + e->accept(); + } else if (e->key() == Qt::Key_End) { + d->vbar->triggerAction(QAbstractSlider::SliderToMaximum); + e->accept(); + } + } + if (!e->isAccepted()) { + QAbstractScrollArea::keyPressEvent(e); + } + } + return; + } + +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::MoveToPreviousPage) { + e->accept(); + d->pageUpDown(QTextCursor::Up, QTextCursor::MoveAnchor); + return; + } else if (e == QKeySequence::MoveToNextPage) { + e->accept(); + d->pageUpDown(QTextCursor::Down, QTextCursor::MoveAnchor); + return; + } else if (e == QKeySequence::SelectPreviousPage) { + e->accept(); + d->pageUpDown(QTextCursor::Up, QTextCursor::KeepAnchor); + return; + } else if (e ==QKeySequence::SelectNextPage) { + e->accept(); + d->pageUpDown(QTextCursor::Down, QTextCursor::KeepAnchor); + return; + } +#endif // QT_NO_SHORTCUT + + + d->sendControlEvent(e); +#ifdef QT_KEYPAD_NAVIGATION + if (!e->isAccepted()) { + switch (e->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + if (QApplication::keypadNavigationEnabled()) { + // Cursor position didn't change, so we want to leave + // these keys to change focus. + e->ignore(); + return; + } + break; + case Qt::Key_Back: + if (!e->isAutoRepeat()) { + if (QApplication::keypadNavigationEnabled()) { + if (document()->isEmpty()) { + setEditFocus(false); + e->accept(); + } else if (!d->deleteAllTimer.isActive()) { + e->accept(); + d->deleteAllTimer.start(750, this); + } + } else { + e->ignore(); + return; + } + } + break; + default: break; + } + } +#endif +} + +/*! \reimp +*/ +void QPlainTextEdit::keyReleaseEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + Q_D(QPlainTextEdit); + if (QApplication::keypadNavigationEnabled()) { + if (!e->isAutoRepeat() && e->key() == Qt::Key_Back + && d->deleteAllTimer.isActive()) { + d->deleteAllTimer.stop(); + QTextCursor cursor = d->control->textCursor(); + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + if (list && cursor.atBlockStart()) { + list->remove(cursor.block()); + } else if (cursor.atBlockStart() && blockFmt.indent() > 0) { + blockFmt.setIndent(blockFmt.indent() - 1); + cursor.setBlockFormat(blockFmt); + } else { + cursor.deletePreviousChar(); + } + setTextCursor(cursor); + } + } +#else + Q_UNUSED(e); +#endif +} + +/*! + Loads the resource specified by the given \a type and \a name. + + This function is an extension of QTextDocument::loadResource(). + + \sa QTextDocument::loadResource() +*/ +QVariant QPlainTextEdit::loadResource(int type, const QUrl &name) +{ + Q_UNUSED(type); + Q_UNUSED(name); + return QVariant(); +} + +/*! \reimp +*/ +void QPlainTextEdit::resizeEvent(QResizeEvent *e) +{ + Q_D(QPlainTextEdit); + if (e->oldSize().width() != e->size().width()) + d->relayoutDocument(); + d->_q_adjustScrollbars(); +} + +void QPlainTextEditPrivate::relayoutDocument() +{ + QTextDocument *doc = control->document(); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + documentLayoutPtr = documentLayout; + + int width = viewport->width(); + + if (documentLayout->priv()->mainViewPrivate == 0 + || documentLayout->priv()->mainViewPrivate == this + || width > documentLayout->textWidth()) { + documentLayout->priv()->mainViewPrivate = this; + documentLayout->setTextWidth(width); + } +} + +static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, QRectF gradientRect = QRectF()) +{ + p->save(); + if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) { + if (!gradientRect.isNull()) { + QTransform m; + m.translate(gradientRect.left(), gradientRect.top()); + m.scale(gradientRect.width(), gradientRect.height()); + brush.setTransform(m); + const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode); + } + } else { + p->setBrushOrigin(rect.topLeft()); + } + p->fillRect(rect, brush); + p->restore(); +} + + + +/*! \reimp +*/ +void QPlainTextEdit::paintEvent(QPaintEvent *e) +{ + QPainter painter(viewport()); + Q_ASSERT(qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout())); + + QPointF offset(contentOffset()); + + QRect er = e->rect(); + QRect viewportRect = viewport()->rect(); + + bool editable = !isReadOnly(); + + QTextBlock block = firstVisibleBlock(); + qreal maximumWidth = document()->documentLayout()->documentSize().width(); + + // keep right margin clean from full-width selection + int maxX = offset.x() + qMax((qreal)viewportRect.width(), maximumWidth) + - document()->documentMargin(); + er.setRight(qMin(er.right(), maxX)); + painter.setClipRect(er); + + + QAbstractTextDocumentLayout::PaintContext context = getPaintContext(); + + while (block.isValid()) { + + QRectF r = blockBoundingRect(block).translated(offset); + QTextLayout *layout = block.layout(); + + if (!block.isVisible()) { + offset.ry() += r.height(); + block = block.next(); + continue; + } + + if (r.bottom() >= er.top() && r.top() <= er.bottom()) { + + QTextBlockFormat blockFormat = block.blockFormat(); + + QBrush bg = blockFormat.background(); + if (bg != Qt::NoBrush) { + QRectF contentsRect = r; + contentsRect.setWidth(qMax(r.width(), maximumWidth)); + fillBackground(&painter, contentsRect, bg); + } + + + QVector<QTextLayout::FormatRange> selections; + int blpos = block.position(); + int bllen = block.length(); + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i); + const int selStart = range.cursor.selectionStart() - blpos; + const int selEnd = range.cursor.selectionEnd() - blpos; + if (selStart < bllen && selEnd > 0 + && selEnd > selStart) { + QTextLayout::FormatRange o; + o.start = selStart; + o.length = selEnd - selStart; + o.format = range.format; + selections.append(o); + } else if (!range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection) + && block.contains(range.cursor.position())) { + // for full width selections we don't require an actual selection, just + // a position to specify the line. that's more convenience in usage. + QTextLayout::FormatRange o; + QTextLine l = layout->lineForTextPosition(range.cursor.position() - blpos); + o.start = l.textStart(); + o.length = l.textLength(); + if (o.start + o.length == bllen - 1) + ++o.length; // include newline + o.format = range.format; + selections.append(o); + } + } + + bool drawCursor = (editable + && context.cursorPosition >= blpos + && context.cursorPosition < blpos + bllen); + + bool drawCursorAsBlock = drawCursor && overwriteMode() ; + + if (drawCursorAsBlock) { + if (context.cursorPosition == blpos + bllen - 1) { + drawCursorAsBlock = false; + } else { + QTextLayout::FormatRange o; + o.start = context.cursorPosition - blpos; + o.length = 1; + o.format.setForeground(palette().base()); + o.format.setBackground(palette().text()); + selections.append(o); + } + } + + + layout->draw(&painter, offset, selections, er); + if ((drawCursor && !drawCursorAsBlock) + || (editable && context.cursorPosition < -1 + && !layout->preeditAreaText().isEmpty())) { + int cpos = context.cursorPosition; + if (cpos < -1) + cpos = layout->preeditAreaPosition() - (cpos + 2); + else + cpos -= blpos; + layout->drawCursor(&painter, offset, cpos, cursorWidth()); + } + } + + offset.ry() += r.height(); + if (offset.y() > viewportRect.height()) + break; + block = block.next(); + } + + if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom() + && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) { + painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background()); + } +} + + +void QPlainTextEditPrivate::updateDefaultTextOption() +{ + QTextDocument *doc = control->document(); + + QTextOption opt = doc->defaultTextOption(); + QTextOption::WrapMode oldWrapMode = opt.wrapMode(); + + if (lineWrap == QPlainTextEdit::NoWrap) + opt.setWrapMode(QTextOption::NoWrap); + else + opt.setWrapMode(wordWrap); + + if (opt.wrapMode() != oldWrapMode) + doc->setDefaultTextOption(opt); +} + + +/*! \reimp +*/ +void QPlainTextEdit::mousePressEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) + setEditFocus(true); +#endif + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = false; // paranoia + const QPoint pos = e->pos(); + d->sendControlEvent(e); + if (!(e->buttons() & Qt::LeftButton)) + return; + QRect visible = d->viewport->rect(); + if (visible.contains(pos)) + d->autoScrollTimer.stop(); + else if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); +} + +/*! \reimp +*/ +void QPlainTextEdit::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); + d->sendControlEvent(e); + if (d->autoScrollTimer.isActive()) { + d->autoScrollTimer.stop(); + d->ensureCursorVisible(); + } +} + +/*! \reimp +*/ +void QPlainTextEdit::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +bool QPlainTextEdit::focusNextPrevChild(bool next) +{ + Q_D(const QPlainTextEdit); + if (!d->tabChangesFocus && d->control->textInteractionFlags() & Qt::TextEditable) + return false; + return QAbstractScrollArea::focusNextPrevChild(next); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \fn void QPlainTextEdit::contextMenuEvent(QContextMenuEvent *event) + + Shows the standard context menu created with createStandardContextMenu(). + + If you do not want the text edit to have a context menu, you can set + its \l contextMenuPolicy to Qt::NoContextMenu. If you want to + customize the context menu, reimplement this function. If you want + to extend the standard context menu, reimplement this function, call + createStandardContextMenu() and extend the menu returned. + + Information about the event is passed in the \a event object. + + \snippet doc/src/snippets/code/src_gui_widgets_qplaintextedit.cpp 0 +*/ +void QPlainTextEdit::contextMenuEvent(QContextMenuEvent *e) +{ + Q_D(QPlainTextEdit); + d->sendControlEvent(e); +} +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp +*/ +void QPlainTextEdit::dragEnterEvent(QDragEnterEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = true; + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::dragLeaveEvent(QDragLeaveEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = false; + d->autoScrollTimer.stop(); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::dragMoveEvent(QDragMoveEvent *e) +{ + Q_D(QPlainTextEdit); + d->autoScrollDragPos = e->pos(); + if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::dropEvent(QDropEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = false; + d->autoScrollTimer.stop(); + d->sendControlEvent(e); +} + +#endif // QT_NO_DRAGANDDROP + +/*! \reimp + */ +void QPlainTextEdit::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QPlainTextEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (d->control->textInteractionFlags() & Qt::TextEditable + && QApplication::keypadNavigationEnabled() + && !hasEditFocus()) { + setEditFocus(true); + selectAll(); // so text is replaced rather than appended to + } +#endif + d->sendControlEvent(e); +} + +/*!\reimp +*/ +void QPlainTextEdit::scrollContentsBy(int dx, int /*dy*/) +{ + Q_D(QPlainTextEdit); + d->setTopLine(d->vbar->value(), dx); +} + +/*!\reimp +*/ +QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QPlainTextEdit); + QVariant v = d->control->inputMethodQuery(property); + const QPoint offset(-d->horizontalOffset(), -0); + if (v.type() == QVariant::RectF) + v = v.toRectF().toRect().translated(offset); + else if (v.type() == QVariant::PointF) + v = v.toPointF().toPoint() + offset; + else if (v.type() == QVariant::Rect) + v = v.toRect().translated(offset); + else if (v.type() == QVariant::Point) + v = v.toPoint() + offset; + return v; +} + +/*! \reimp +*/ +void QPlainTextEdit::focusInEvent(QFocusEvent *e) +{ + Q_D(QPlainTextEdit); + QAbstractScrollArea::focusInEvent(e); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::focusOutEvent(QFocusEvent *e) +{ + Q_D(QPlainTextEdit); + QAbstractScrollArea::focusOutEvent(e); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::showEvent(QShowEvent *) +{ + Q_D(QPlainTextEdit); + if (d->showCursorOnInitialShow) { + d->showCursorOnInitialShow = false; + ensureCursorVisible(); + } +} + +/*! \reimp +*/ +void QPlainTextEdit::changeEvent(QEvent *e) +{ + Q_D(QPlainTextEdit); + QAbstractScrollArea::changeEvent(e); + if (e->type() == QEvent::ApplicationFontChange + || e->type() == QEvent::FontChange) { + d->control->document()->setDefaultFont(font()); + } else if(e->type() == QEvent::ActivationChange) { + if (!isActiveWindow()) + d->autoScrollTimer.stop(); + } else if (e->type() == QEvent::EnabledChange) { + e->setAccepted(isEnabled()); + d->sendControlEvent(e); + } else if (e->type() == QEvent::PaletteChange) { + d->control->setPalette(palette()); + } else if (e->type() == QEvent::LayoutDirectionChange) { + d->sendControlEvent(e); + } +} + +/*! \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QPlainTextEdit::wheelEvent(QWheelEvent *e) +{ + QAbstractScrollArea::wheelEvent(e); + updateMicroFocus(); +} +#endif + +#ifndef QT_NO_CONTEXTMENU +/*! This function creates the standard context menu which is shown + when the user clicks on the line edit with the right mouse + button. It is called from the default contextMenuEvent() handler. + The popup menu's ownership is transferred to the caller. +*/ + +QMenu *QPlainTextEdit::createStandardContextMenu() +{ + Q_D(QPlainTextEdit); + return d->control->createStandardContextMenu(QPointF(), this); +} +#endif // QT_NO_CONTEXTMENU + +/*! + returns a QTextCursor at position \a pos (in viewport coordinates). +*/ +QTextCursor QPlainTextEdit::cursorForPosition(const QPoint &pos) const +{ + Q_D(const QPlainTextEdit); + return d->control->cursorForPosition(d->mapToContents(pos)); +} + +/*! + returns a rectangle (in viewport coordinates) that includes the + \a cursor. + */ +QRect QPlainTextEdit::cursorRect(const QTextCursor &cursor) const +{ + Q_D(const QPlainTextEdit); + if (cursor.isNull()) + return QRect(); + + QRect r = d->control->cursorRect(cursor).toRect(); + r.translate(-d->horizontalOffset(),-d->verticalOffset()); + return r; +} + +/*! + returns a rectangle (in viewport coordinates) that includes the + cursor of the text edit. + */ +QRect QPlainTextEdit::cursorRect() const +{ + Q_D(const QPlainTextEdit); + QRect r = d->control->cursorRect().toRect(); + r.translate(-d->horizontalOffset(),-d->verticalOffset()); + return r; +} + + +/*! + \property QPlainTextEdit::overwriteMode + \brief whether text entered by the user will overwrite existing text + + As with many text editors, the plain text editor widget can be configured + to insert or overwrite existing text with new text entered by the user. + + If this property is true, existing text is overwritten, character-for-character + by new text; otherwise, text is inserted at the cursor position, displacing + existing text. + + By default, this property is false (new text does not overwrite existing text). +*/ + +bool QPlainTextEdit::overwriteMode() const +{ + Q_D(const QPlainTextEdit); + return d->control->overwriteMode(); +} + +void QPlainTextEdit::setOverwriteMode(bool overwrite) +{ + Q_D(QPlainTextEdit); + d->control->setOverwriteMode(overwrite); +} + +/*! + \property QPlainTextEdit::tabStopWidth + \brief the tab stop width in pixels + + By default, this property contains a value of 80. +*/ + +int QPlainTextEdit::tabStopWidth() const +{ + Q_D(const QPlainTextEdit); + return qRound(d->control->document()->defaultTextOption().tabStop()); +} + +void QPlainTextEdit::setTabStopWidth(int width) +{ + Q_D(QPlainTextEdit); + QTextOption opt = d->control->document()->defaultTextOption(); + if (opt.tabStop() == width || width < 0) + return; + opt.setTabStop(width); + d->control->document()->setDefaultTextOption(opt); +} + +/*! + \property QPlainTextEdit::cursorWidth + + This property specifies the width of the cursor in pixels. The default value is 1. +*/ +int QPlainTextEdit::cursorWidth() const +{ + Q_D(const QPlainTextEdit); + return d->control->cursorWidth(); +} + +void QPlainTextEdit::setCursorWidth(int width) +{ + Q_D(QPlainTextEdit); + d->control->setCursorWidth(width); +} + + + +/*! + This function allows temporarily marking certain regions in the document + with a given color, specified as \a selections. This can be useful for + example in a programming editor to mark a whole line of text with a given + background color to indicate the existence of a breakpoint. + + \sa QTextEdit::ExtraSelection, extraSelections() +*/ +void QPlainTextEdit::setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections) +{ + Q_D(QPlainTextEdit); + d->control->setExtraSelections(selections); +} + +/*! + Returns previously set extra selections. + + \sa setExtraSelections() +*/ +QList<QTextEdit::ExtraSelection> QPlainTextEdit::extraSelections() const +{ + Q_D(const QPlainTextEdit); + return d->control->extraSelections(); +} + +/*! + This function returns a new MIME data object to represent the contents + of the text edit's current selection. It is called when the selection needs + to be encapsulated into a new QMimeData object; for example, when a drag + and drop operation is started, or when data is copied to the clipboard. + + If you reimplement this function, note that the ownership of the returned + QMimeData object is passed to the caller. The selection can be retrieved + by using the textCursor() function. +*/ +QMimeData *QPlainTextEdit::createMimeDataFromSelection() const +{ + Q_D(const QPlainTextEdit); + return d->control->QTextControl::createMimeDataFromSelection(); +} + +/*! + This function returns true if the contents of the MIME data object, specified + by \a source, can be decoded and inserted into the document. It is called + for example when during a drag operation the mouse enters this widget and it + is necessary to determine whether it is possible to accept the drag. + */ +bool QPlainTextEdit::canInsertFromMimeData(const QMimeData *source) const +{ + Q_D(const QPlainTextEdit); + return d->control->QTextControl::canInsertFromMimeData(source); +} + +/*! + This function inserts the contents of the MIME data object, specified + by \a source, into the text edit at the current cursor position. It is + called whenever text is inserted as the result of a clipboard paste + operation, or when the text edit accepts data from a drag and drop + operation. +*/ +void QPlainTextEdit::insertFromMimeData(const QMimeData *source) +{ + Q_D(QPlainTextEdit); + d->control->QTextControl::insertFromMimeData(source); +} + +/*! + \property QPlainTextEdit::readOnly + \brief whether the text edit is read-only + + In a read-only text edit the user can only navigate through the + text and select text; modifying the text is not possible. + + This property's default is false. +*/ + +bool QPlainTextEdit::isReadOnly() const +{ + Q_D(const QPlainTextEdit); + return !(d->control->textInteractionFlags() & Qt::TextEditable); +} + +void QPlainTextEdit::setReadOnly(bool ro) +{ + Q_D(QPlainTextEdit); + Qt::TextInteractionFlags flags = Qt::NoTextInteraction; + if (ro) { + flags = Qt::TextSelectableByMouse; + } else { + flags = Qt::TextEditorInteraction; + } + setAttribute(Qt::WA_InputMethodEnabled, !ro); + d->control->setTextInteractionFlags(flags); +} + +/*! + \property QPlainTextEdit::textInteractionFlags + + Specifies how the label should interact with user input if it displays text. + + If the flags contain either Qt::LinksAccessibleByKeyboard or Qt::TextSelectableByKeyboard + then the focus policy is also automatically set to Qt::ClickFocus. + + The default value depends on whether the QPlainTextEdit is read-only + or editable, and whether it is a QTextBrowser or not. +*/ + +void QPlainTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QPlainTextEdit); + d->control->setTextInteractionFlags(flags); +} + +Qt::TextInteractionFlags QPlainTextEdit::textInteractionFlags() const +{ + Q_D(const QPlainTextEdit); + return d->control->textInteractionFlags(); +} + +/*! + Merges the properties specified in \a modifier into the current character + format by calling QTextCursor::mergeCharFormat on the editor's cursor. + If the editor has a selection then the properties of \a modifier are + directly applied to the selection. + + \sa QTextCursor::mergeCharFormat() + */ +void QPlainTextEdit::mergeCurrentCharFormat(const QTextCharFormat &modifier) +{ + Q_D(QPlainTextEdit); + d->control->mergeCurrentCharFormat(modifier); +} + +/*! + Sets the char format that is be used when inserting new text to \a + format by calling QTextCursor::setCharFormat() on the editor's + cursor. If the editor has a selection then the char format is + directly applied to the selection. + */ +void QPlainTextEdit::setCurrentCharFormat(const QTextCharFormat &format) +{ + Q_D(QPlainTextEdit); + d->control->setCurrentCharFormat(format); +} + +/*! + Returns the char format that is used when inserting new text. + */ +QTextCharFormat QPlainTextEdit::currentCharFormat() const +{ + Q_D(const QPlainTextEdit); + return d->control->currentCharFormat(); +} + + + +/*! + Convenience slot that inserts \a text at the current + cursor position. + + It is equivalent to + + \snippet doc/src/snippets/code/src_gui_widgets_qplaintextedit.cpp 1 + */ +void QPlainTextEdit::insertPlainText(const QString &text) +{ + Q_D(QPlainTextEdit); + d->control->insertPlainText(text); +} + + +/*! + Moves the cursor by performing the given \a operation. + + If \a mode is QTextCursor::KeepAnchor, the cursor selects the text it moves over. + This is the same effect that the user achieves when they hold down the Shift key + and move the cursor with the cursor keys. + + \sa QTextCursor::movePosition() +*/ +void QPlainTextEdit::moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode) +{ + Q_D(QPlainTextEdit); + d->control->moveCursor(operation, mode); +} + +/*! + Returns whether text can be pasted from the clipboard into the textedit. +*/ +bool QPlainTextEdit::canPaste() const +{ + Q_D(const QPlainTextEdit); + return d->control->canPaste(); +} + +#ifndef QT_NO_PRINTER +/*! + Convenience function to print the text edit's document to the given \a printer. This + is equivalent to calling the print method on the document directly except that this + function also supports QPrinter::Selection as print range. + + \sa QTextDocument::print() +*/ +void QPlainTextEdit::print(QPrinter *printer) const +{ + Q_D(const QPlainTextEdit); + d->control->print(printer); +} +#endif // QT _NO_PRINTER + +/*! \property QPlainTextEdit::tabChangesFocus + \brief whether \gui Tab changes focus or is accepted as input + + In some occasions text edits should not allow the user to input + tabulators or change indentation using the \gui Tab key, as this breaks + the focus chain. The default is false. + +*/ + +bool QPlainTextEdit::tabChangesFocus() const +{ + Q_D(const QPlainTextEdit); + return d->tabChangesFocus; +} + +void QPlainTextEdit::setTabChangesFocus(bool b) +{ + Q_D(QPlainTextEdit); + d->tabChangesFocus = b; +} + +/*! + \property QPlainTextEdit::documentTitle + \brief the title of the document parsed from the text. + + By default, this property contains an empty string. +*/ + +/*! + \property QPlainTextEdit::lineWrapMode + \brief the line wrap mode + + The default mode is WidgetWidth which causes words to be + wrapped at the right edge of the text edit. Wrapping occurs at + whitespace, keeping whole words intact. If you want wrapping to + occur within words use setWordWrapMode(). +*/ + +QPlainTextEdit::LineWrapMode QPlainTextEdit::lineWrapMode() const +{ + Q_D(const QPlainTextEdit); + return d->lineWrap; +} + +void QPlainTextEdit::setLineWrapMode(LineWrapMode wrap) +{ + Q_D(QPlainTextEdit); + if (d->lineWrap == wrap) + return; + d->lineWrap = wrap; + d->updateDefaultTextOption(); + d->relayoutDocument(); + d->_q_adjustScrollbars(); + ensureCursorVisible(); +} + +/*! + \property QPlainTextEdit::wordWrapMode + \brief the mode QPlainTextEdit will use when wrapping text by words + + By default, this property is set to QTextOption::WrapAtWordBoundaryOrAnywhere. + + \sa QTextOption::WrapMode +*/ + +QTextOption::WrapMode QPlainTextEdit::wordWrapMode() const +{ + Q_D(const QPlainTextEdit); + return d->wordWrap; +} + +void QPlainTextEdit::setWordWrapMode(QTextOption::WrapMode mode) +{ + Q_D(QPlainTextEdit); + if (mode == d->wordWrap) + return; + d->wordWrap = mode; + d->updateDefaultTextOption(); +} + +/*! + \property QPlainTextEdit::backgroundVisible + \brief whether the palette background is visible outside the document area + + If set to true, the plain text edit paints the palette background + on the viewport area not covered by the text document. Otherwise, + if set to false, it won't. The feature makes it possible for + the user to visually distinguish between the area of the document, + painted with the base color of the palette, and the empty + area not covered by any document. + + The default is false. +*/ + +bool QPlainTextEdit::backgroundVisible() const +{ + Q_D(const QPlainTextEdit); + return d->backgroundVisible; +} + +void QPlainTextEdit::setBackgroundVisible(bool visible) +{ + Q_D(QPlainTextEdit); + if (visible == d->backgroundVisible) + return; + d->backgroundVisible = visible; + d->updateViewport(); +} + +/*! + \property QPlainTextEdit::centerOnScroll + \brief whether the cursor should be centered on screen + + If set to true, the plain text edit scrolls the document + vertically to make the cursor visible at the center of the + viewport. This also allows the text edit to scroll below the end + of the document. Otherwise, if set to false, the plain text edit + scrolls the smallest amount possible to ensure the cursor is + visible. The same algorithm is applied to any new line appended + through appendPlainText(). + + The default is false. + + \sa centerCursor(), ensureCursorVisible() +*/ + +bool QPlainTextEdit::centerOnScroll() const +{ + Q_D(const QPlainTextEdit); + return d->centerOnScroll; +} + +void QPlainTextEdit::setCenterOnScroll(bool enabled) +{ + Q_D(QPlainTextEdit); + if (enabled == d->centerOnScroll) + return; + d->centerOnScroll = enabled; +} + + + +/*! + Finds the next occurrence of the string, \a exp, using the given + \a options. Returns true if \a exp was found and changes the + cursor to select the match; otherwise returns false. +*/ +bool QPlainTextEdit::find(const QString &exp, QTextDocument::FindFlags options) +{ + Q_D(QPlainTextEdit); + return d->control->find(exp, options); +} + +/*! + \fn void QPlainTextEdit::copyAvailable(bool yes) + + This signal is emitted when text is selected or de-selected in the + text edit. + + When text is selected this signal will be emitted with \a yes set + to true. If no text has been selected or if the selected text is + de-selected this signal is emitted with \a yes set to false. + + If \a yes is true then copy() can be used to copy the selection to + the clipboard. If \a yes is false then copy() does nothing. + + \sa selectionChanged() +*/ + + +/*! + \fn void QPlainTextEdit::selectionChanged() + + This signal is emitted whenever the selection changes. + + \sa copyAvailable() +*/ + +/*! + \fn void QPlainTextEdit::cursorPositionChanged() + + This signal is emitted whenever the position of the + cursor changed. +*/ + + + +/*! + \fn void QPlainTextEdit::updateRequest(const QRect &rect, int dy) + + This signal is emitted when the text document needs an update of + the specified \a rect. If the text is scrolled, \a rect will cover + the entire viewport area. If the text is scrolled vertically, \a + dy carries the amount of pixels the viewport was scrolled. + + The purpose of the signal is to support extra widgets in plain + text edit subclasses that e.g. show line numbers, breakpoints, or + other extra information. +*/ + +/*! \fn void QPlainTextEdit::blockCountChanged(int newBlockCount); + + This signal is emitted whenever the block count changes. The new + block count is passed in \a newBlockCount. +*/ + +/*! \fn void QPlainTextEdit::modificationChanged(bool changed); + + This signal is emitted whenever the content of the document + changes in a way that affects the modification state. If \a + changed is true, the document has been modified; otherwise it is + false. + + For example, calling setModified(false) on a document and then + inserting text causes the signal to get emitted. If you undo that + operation, causing the document to return to its original + unmodified state, the signal will get emitted again. +*/ + + + + +void QPlainTextEditPrivate::append(const QString &text, Qt::TextFormat format) +{ + Q_Q(QPlainTextEdit); + + QTextDocument *document = control->document(); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document->documentLayout()); + Q_ASSERT(documentLayout); + + int maximumBlockCount = document->maximumBlockCount(); + if (maximumBlockCount) + document->setMaximumBlockCount(0); + + const bool atBottom = q->isVisible() + && (control->blockBoundingRect(document->lastBlock()).bottom() - verticalOffset() + <= viewport->rect().bottom()); + + if (!q->isVisible()) + showCursorOnInitialShow = true; + + bool documentSizeChangedBlocked = documentLayout->priv()->blockDocumentSizeChanged; + documentLayout->priv()->blockDocumentSizeChanged = true; + + if (format == Qt::RichText) + control->appendHtml(text); + else if (format == Qt::PlainText) + control->appendPlainText(text); + else + control->append(text); + + if (maximumBlockCount > 0) { + if (document->blockCount() > maximumBlockCount) { + bool blockUpdate = false; + if (control->topBlock) { + control->topBlock--; + blockUpdate = true; + emit q->updateRequest(viewport->rect(), 0); + } + + bool updatesBlocked = documentLayout->priv()->blockUpdate; + documentLayout->priv()->blockUpdate = blockUpdate; + QTextCursor cursor(document); + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + documentLayout->priv()->blockUpdate = updatesBlocked; + } + document->setMaximumBlockCount(maximumBlockCount); + } + + documentLayout->priv()->blockDocumentSizeChanged = documentSizeChangedBlocked; + _q_adjustScrollbars(); + + + if (atBottom) { + const bool needScroll = !centerOnScroll + || control->blockBoundingRect(document->lastBlock()).bottom() - verticalOffset() + > viewport->rect().bottom(); + if (needScroll) + vbar->setValue(vbar->maximum()); + } +} + + +/*! + Appends a new paragraph with \a text to the end of the text edit. + + \sa appendHtml() +*/ + +void QPlainTextEdit::appendPlainText(const QString &text) +{ + Q_D(QPlainTextEdit); + d->append(text, Qt::PlainText); +} + +/*! + Appends a new paragraph with \a html to the end of the text edit. + + appendPlainText() +*/ + +void QPlainTextEdit::appendHtml(const QString &html) +{ + Q_D(QPlainTextEdit); + d->append(html, Qt::RichText); +} + +void QPlainTextEditPrivate::ensureCursorVisible(bool center) +{ + Q_Q(QPlainTextEdit); + QRect visible = viewport->rect(); + QRect cr = q->cursorRect(); + if (cr.top() < visible.top() || cr.bottom() > visible.bottom()) { + ensureVisible(control->textCursor().position(), center); + } + + const bool rtl = q->isRightToLeft(); + if (cr.left() < visible.left() || cr.right() > visible.right()) { + int x = cr.center().x() + horizontalOffset() - visible.width()/2; + hbar->setValue(rtl ? hbar->maximum() - x : x); + } +} + +/*! + Ensures that the cursor is visible by scrolling the text edit if + necessary. + + \sa centerCursor(), centerOnScroll +*/ +void QPlainTextEdit::ensureCursorVisible() +{ + Q_D(QPlainTextEdit); + d->ensureCursorVisible(d->centerOnScroll); +} + + +/*! Scrolls the document in order to center the cursor vertically. + +\sa ensureCursorVisible(), centerOnScroll + */ +void QPlainTextEdit::centerCursor() +{ + Q_D(QPlainTextEdit); + d->ensureVisible(textCursor().position(), true, true); +} + +/*! + Returns the first visible block. + + \sa blockBoundingRect() + */ +QTextBlock QPlainTextEdit::firstVisibleBlock() const +{ + Q_D(const QPlainTextEdit); + return d->control->firstVisibleBlock(); +} + +/*! Returns the content's origin in viewport coordinates. + + The origin of the content of a plain text edit is always the top + left corner of the first visible text block. The content offset + is different from (0,0) when the text has been scrolled + horizontally, or when the first visible block has been scrolled + partially off the screen, i.e. the visible text does not start + with the first line of the first visible block, or when the first + visible block is the very first block and the editor displays a + margin. + + \sa firstVisibleBlock(), horizontalScrollBar(), verticalScrollBar() + */ +QPointF QPlainTextEdit::contentOffset() const +{ + Q_D(const QPlainTextEdit); + return QPointF(-d->horizontalOffset(), -d->verticalOffset()); +} + + +/*! Returns the bounding rectangle of the text \a block in content + coordinates. Translate the rectangle with the contentOffset() to get + visual coordinates on the viewport. + + \sa firstVisibleBlock(), blockBoundingRect() + */ +QRectF QPlainTextEdit::blockBoundingGeometry(const QTextBlock &block) const +{ + Q_D(const QPlainTextEdit); + return d->control->blockBoundingRect(block); +} + +/*! + Returns the bounding rectangle of the text \a block in the block's own coordinates. + + \sa blockBoundingGeometry() + */ +QRectF QPlainTextEdit::blockBoundingRect(const QTextBlock &block) const +{ + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + return documentLayout->blockBoundingRect(block); +} + +/*! + \property QPlainTextEdit::blockCount + \brief the number of text blocks in the document. + + By default, in an empty document, this property contains a value of 1. +*/ +int QPlainTextEdit::blockCount() const +{ + return document()->blockCount(); +} + +/*! Returns the paint context for the viewport(), useful only when + reimplementing paintEvent(). + */ +QAbstractTextDocumentLayout::PaintContext QPlainTextEdit::getPaintContext() const +{ + Q_D(const QPlainTextEdit); + return d->control->getPaintContext(d->viewport); +} + +/*! + \property QPlainTextEdit::maximumBlockCount + \brief the limit for blocks in the document. + + Specifies the maximum number of blocks the document may have. If there are + more blocks in the document that specified with this property blocks are removed + from the beginning of the document. + + A negative or zero value specifies that the document may contain an unlimited + amount of blocks. + + The default value is 0. + + Note that setting this property will apply the limit immediately to the document + contents. Setting this property also disables the undo redo history. + +*/ + + +/*! + \fn void QPlainTextEdit::textChanged() + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. +*/ + +/*! + \fn void QPlainTextEdit::undoAvailable(bool available) + + This signal is emitted whenever undo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn void QPlainTextEdit::redoAvailable(bool available) + + This signal is emitted whenever redo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +QT_END_NAMESPACE + +#include "moc_qplaintextedit.cpp" +#include "moc_qplaintextedit_p.cpp" + +#endif // QT_NO_TEXTEDIT diff --git a/src/gui/widgets/qplaintextedit.h b/src/gui/widgets/qplaintextedit.h new file mode 100644 index 0000000..c00ff4e --- /dev/null +++ b/src/gui/widgets/qplaintextedit.h @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLAINTEXTEDIT_H +#define QPLAINTEXTEDIT_H + +#include <QtGui/qtextedit.h> + +#include <QtGui/qabstractscrollarea.h> +#include <QtGui/qtextdocument.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qtextformat.h> +#include <QtGui/qabstracttextdocumentlayout.h> + +#ifndef QT_NO_TEXTEDIT + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyleSheet; +class QTextDocument; +class QMenu; +class QPlainTextEditPrivate; +class QMimeData; + + +class Q_GUI_EXPORT QPlainTextEdit : public QAbstractScrollArea +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPlainTextEdit) + Q_ENUMS(LineWrapMode) + Q_PROPERTY(bool tabChangesFocus READ tabChangesFocus WRITE setTabChangesFocus) + Q_PROPERTY(QString documentTitle READ documentTitle WRITE setDocumentTitle) + Q_PROPERTY(bool undoRedoEnabled READ isUndoRedoEnabled WRITE setUndoRedoEnabled) + Q_PROPERTY(LineWrapMode lineWrapMode READ lineWrapMode WRITE setLineWrapMode) + QDOC_PROPERTY(QTextOption::WrapMode wordWrapMode READ wordWrapMode WRITE setWordWrapMode) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(QString plainText READ toPlainText WRITE setPlainText NOTIFY textChanged USER true) + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + Q_PROPERTY(int tabStopWidth READ tabStopWidth WRITE setTabStopWidth) + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags WRITE setTextInteractionFlags) + Q_PROPERTY(int blockCount READ blockCount) + Q_PROPERTY(int maximumBlockCount READ maximumBlockCount WRITE setMaximumBlockCount) + Q_PROPERTY(bool backgroundVisible READ backgroundVisible WRITE setBackgroundVisible) + Q_PROPERTY(bool centerOnScroll READ centerOnScroll WRITE setCenterOnScroll) +public: + enum LineWrapMode { + NoWrap, + WidgetWidth + }; + + explicit QPlainTextEdit(QWidget *parent = 0); + explicit QPlainTextEdit(const QString &text, QWidget *parent = 0); + virtual ~QPlainTextEdit(); + + void setDocument(QTextDocument *document); + QTextDocument *document() const; + + void setTextCursor(const QTextCursor &cursor); + QTextCursor textCursor() const; + + bool isReadOnly() const; + void setReadOnly(bool ro); + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + void mergeCurrentCharFormat(const QTextCharFormat &modifier); + void setCurrentCharFormat(const QTextCharFormat &format); + QTextCharFormat currentCharFormat() const; + + bool tabChangesFocus() const; + void setTabChangesFocus(bool b); + + inline void setDocumentTitle(const QString &title) + { document()->setMetaInformation(QTextDocument::DocumentTitle, title); } + inline QString documentTitle() const + { return document()->metaInformation(QTextDocument::DocumentTitle); } + + inline bool isUndoRedoEnabled() const + { return document()->isUndoRedoEnabled(); } + inline void setUndoRedoEnabled(bool enable) + { document()->setUndoRedoEnabled(enable); } + + inline void setMaximumBlockCount(int maximum) + { document()->setMaximumBlockCount(maximum); } + inline int maximumBlockCount() const + { return document()->maximumBlockCount(); } + + + LineWrapMode lineWrapMode() const; + void setLineWrapMode(LineWrapMode mode); + + QTextOption::WrapMode wordWrapMode() const; + void setWordWrapMode(QTextOption::WrapMode policy); + + void setBackgroundVisible(bool visible); + bool backgroundVisible() const; + + void setCenterOnScroll(bool enabled); + bool centerOnScroll() const; + + bool find(const QString &exp, QTextDocument::FindFlags options = 0); + + inline QString toPlainText() const + { return document()->toPlainText(); } + + void ensureCursorVisible(); + + virtual QVariant loadResource(int type, const QUrl &name); +#ifndef QT_NO_CONTEXTMENU + QMenu *createStandardContextMenu(); +#endif + + QTextCursor cursorForPosition(const QPoint &pos) const; + QRect cursorRect(const QTextCursor &cursor) const; + QRect cursorRect() const; + + bool overwriteMode() const; + void setOverwriteMode(bool overwrite); + + int tabStopWidth() const; + void setTabStopWidth(int width); + + int cursorWidth() const; + void setCursorWidth(int width); + + void setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections); + QList<QTextEdit::ExtraSelection> extraSelections() const; + + void moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + bool canPaste() const; + +#ifndef QT_NO_PRINTER + void print(QPrinter *printer) const; +#endif + + int blockCount() const; + +public Q_SLOTS: + + void setPlainText(const QString &text); + +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy(); + void paste(); +#endif + + void undo(); + void redo(); + + void clear(); + void selectAll(); + + void insertPlainText(const QString &text); + + void appendPlainText(const QString &text); + void appendHtml(const QString &html); + + void centerCursor(); + +Q_SIGNALS: + void textChanged(); + void undoAvailable(bool b); + void redoAvailable(bool b); + void copyAvailable(bool b); + void selectionChanged(); + void cursorPositionChanged(); + + void updateRequest(const QRect &rect, int dy); + void blockCountChanged(int newBlockCount); + void modificationChanged(bool); + +protected: + virtual bool event(QEvent *e); + virtual void timerEvent(QTimerEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + virtual void keyReleaseEvent(QKeyEvent *e); + virtual void resizeEvent(QResizeEvent *e); + virtual void paintEvent(QPaintEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual bool focusNextPrevChild(bool next); +#ifndef QT_NO_CONTEXTMENU + virtual void contextMenuEvent(QContextMenuEvent *e); +#endif +#ifndef QT_NO_DRAGANDDROP + virtual void dragEnterEvent(QDragEnterEvent *e); + virtual void dragLeaveEvent(QDragLeaveEvent *e); + virtual void dragMoveEvent(QDragMoveEvent *e); + virtual void dropEvent(QDropEvent *e); +#endif + virtual void focusInEvent(QFocusEvent *e); + virtual void focusOutEvent(QFocusEvent *e); + virtual void showEvent(QShowEvent *); + virtual void changeEvent(QEvent *e); +#ifndef QT_NO_WHEELEVENT + virtual void wheelEvent(QWheelEvent *e); +#endif + + virtual QMimeData *createMimeDataFromSelection() const; + virtual bool canInsertFromMimeData(const QMimeData *source) const; + virtual void insertFromMimeData(const QMimeData *source); + + virtual void inputMethodEvent(QInputMethodEvent *); + QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + + QPlainTextEdit(QPlainTextEditPrivate &dd, QWidget *parent); + + virtual void scrollContentsBy(int dx, int dy); + + QTextBlock firstVisibleBlock() const; + QPointF contentOffset() const; + QRectF blockBoundingRect(const QTextBlock &block) const; + QRectF blockBoundingGeometry(const QTextBlock &block) const; + QAbstractTextDocumentLayout::PaintContext getPaintContext() const; + + +private: + Q_DISABLE_COPY(QPlainTextEdit) + Q_PRIVATE_SLOT(d_func(), void _q_repaintContents(const QRectF &r)) + Q_PRIVATE_SLOT(d_func(), void _q_adjustScrollbars()) + Q_PRIVATE_SLOT(d_func(), void _q_verticalScrollbarActionTriggered(int)) + Q_PRIVATE_SLOT(d_func(), void _q_cursorPositionChanged()) + friend class QPlainTextEditControl; +}; + + +class QPlainTextDocumentLayoutPrivate; +class Q_GUI_EXPORT QPlainTextDocumentLayout : public QAbstractTextDocumentLayout +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPlainTextDocumentLayout) + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + +public: + QPlainTextDocumentLayout(QTextDocument *document); + ~QPlainTextDocumentLayout(); + + void draw(QPainter *, const PaintContext &); + int hitTest(const QPointF &, Qt::HitTestAccuracy ) const; + + int pageCount() const; + QSizeF documentSize() const; + + QRectF frameBoundingRect(QTextFrame *) const; + QRectF blockBoundingRect(const QTextBlock &block) const; + + void ensureBlockLayout(const QTextBlock &block) const; + + void setCursorWidth(int width); + int cursorWidth() const; + + void requestUpdate(); + +protected: + void documentChanged(int from, int /*charsRemoved*/, int charsAdded); + + +private: + void setTextWidth(qreal newWidth); + qreal textWidth() const; + void layoutBlock(const QTextBlock &block); + qreal blockWidth(const QTextBlock &block); + + QPlainTextDocumentLayoutPrivate *priv() const; + + friend class QPlainTextEdit; + friend class QPlainTextEditPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + + +#endif // QT_NO_TEXTEDIT + +#endif // QPLAINTEXTEDIT_H diff --git a/src/gui/widgets/qplaintextedit_p.h b/src/gui/widgets/qplaintextedit_p.h new file mode 100644 index 0000000..0739d53 --- /dev/null +++ b/src/gui/widgets/qplaintextedit_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLAINTEXTEDIT_P_H +#define QPLAINTEXTEDIT_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/qabstractscrollarea_p.h" +#include "QtGui/qtextdocumentfragment.h" +#include "QtGui/qscrollbar.h" +#include "QtGui/qtextcursor.h" +#include "QtGui/qtextformat.h" +#include "QtGui/qmenu.h" +#include "QtGui/qabstracttextdocumentlayout.h" +#include "QtCore/qbasictimer.h" +#include "private/qtextcontrol_p.h" +#include "qplaintextedit.h" + +#ifndef QT_NO_TEXTEDIT + +QT_BEGIN_NAMESPACE + +class QMimeData; + +class QPlainTextEdit; +class ExtraArea; + +class QPlainTextEditControl : public QTextControl +{ + Q_OBJECT +public: + QPlainTextEditControl(QPlainTextEdit *parent); + + + QMimeData *createMimeDataFromSelection() const; + bool canInsertFromMimeData(const QMimeData *source) const; + void insertFromMimeData(const QMimeData *source); + int hitTest(const QPointF &point, Qt::HitTestAccuracy = Qt::FuzzyHit) const; + QRectF blockBoundingRect(const QTextBlock &block) const; + inline QRectF cursorRect(const QTextCursor &cursor) const { + QRectF r = QTextControl::cursorRect(cursor); + r.setLeft(qMax(r.left(), (qreal) 0.)); + return r; + } + inline QRectF cursorRect() { return cursorRect(textCursor()); } + void ensureCursorVisible() { textEdit->ensureCursorVisible(); } + + + QPlainTextEdit *textEdit; + int topBlock; + QTextBlock firstVisibleBlock() const; + + QVariant loadResource(int type, const QUrl &name) { + return textEdit->loadResource(type, name); + } + +}; + + +class QPlainTextEditPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QPlainTextEdit) +public: + QPlainTextEditPrivate(); + + void init(const QString &txt = QString()); + void _q_repaintContents(const QRectF &contentsRect); + + inline QPoint mapToContents(const QPoint &point) const + { return QPoint(point.x() + horizontalOffset(), point.y() + verticalOffset()); } + + void _q_adjustScrollbars(); + void _q_verticalScrollbarActionTriggered(int action); + void ensureViewportLayouted(); + void relayoutDocument(); + + void pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode, bool moveCursor = true); + + inline int horizontalOffset() const + { return (q_func()->isRightToLeft() ? (hbar->maximum() - hbar->value()) : hbar->value()); } + int verticalOffset(int topBlock, int topLine) const; + int verticalOffset() const; + + inline void sendControlEvent(QEvent *e) + { control->processEvent(e, QPointF(horizontalOffset(), verticalOffset()), viewport); } + + void updateDefaultTextOption(); + + QPlainTextEditControl *control; + + bool tabChangesFocus; + + QBasicTimer autoScrollTimer; + QPoint autoScrollDragPos; + + QPlainTextEdit::LineWrapMode lineWrap; + QTextOption::WrapMode wordWrap; + + uint showCursorOnInitialShow : 1; + uint backgroundVisible : 1; + uint centerOnScroll : 1; + uint inDrag : 1; + + int topLine; + + void setTopLine(int visualTopLine, int dx = 0); + void setTopBlock(int newTopBlock, int newTopLine, int dx = 0); + + void ensureVisible(int position, bool center, bool forceCenter = false); + void ensureCursorVisible(bool center = false); + void updateViewport(); + + QPointer<QPlainTextDocumentLayout> documentLayoutPtr; + + void append(const QString &text, Qt::TextFormat format = Qt::AutoText); + + qreal pageUpDownLastCursorY; + bool pageUpDownLastCursorYIsValid; + + +#ifdef QT_KEYPAD_NAVIGATION + QBasicTimer deleteAllTimer; +#endif + + void _q_cursorPositionChanged(); + + void _q_modificationChanged(bool); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TEXTEDIT + +#endif // QPLAINTEXTEDIT_P_H diff --git a/src/gui/widgets/qprintpreviewwidget.cpp b/src/gui/widgets/qprintpreviewwidget.cpp new file mode 100644 index 0000000..16334b8 --- /dev/null +++ b/src/gui/widgets/qprintpreviewwidget.cpp @@ -0,0 +1,829 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprintpreviewwidget.h" +#include <private/qprinter_p.h> + +#include <QtCore/qmath.h> +#include <QtGui/qboxlayout.h> +#include <QtGui/qgraphicsitem.h> +#include <QtGui/qgraphicsview.h> +#include <QtGui/qscrollbar.h> +#include <QtGui/qstyleoption.h> + +#ifndef QT_NO_PRINTPREVIEWWIDGET + +QT_BEGIN_NAMESPACE + +namespace { +class PageItem : public QGraphicsItem +{ +public: + PageItem(int _pageNum, const QPicture* _pagePicture, QSize _paperSize, QRect _pageRect) + : pageNum(_pageNum), pagePicture(_pagePicture), + paperSize(_paperSize), pageRect(_pageRect) + { + qreal border = qMax(paperSize.height(), paperSize.width()) / 25; + brect = QRectF(QPointF(-border, -border), + QSizeF(paperSize)+QSizeF(2*border, 2*border)); + setCacheMode(DeviceCoordinateCache); + } + + inline QRectF boundingRect() const + { return brect; } + + inline int pageNumber() const + { return pageNum; } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *item, QWidget *widget); + +private: + int pageNum; + const QPicture* pagePicture; + QSize paperSize; + QRect pageRect; + QRectF brect; +}; + +void PageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + +#if 0 + // Draw item bounding rect, for debugging + painter->save(); + painter->setPen(QPen(Qt::red, 0)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(QRectF(-border()+1.0, -border()+1.0, boundingRect().width()-2, boundingRect().height()-2)); + painter->restore(); +#endif + + QRectF paperRect(0,0, paperSize.width(), paperSize.height()); + + painter->setClipRect(paperRect & option->exposedRect); + painter->fillRect(paperRect, Qt::white); + if (!pagePicture) + return; + painter->drawPicture(pageRect.topLeft(), *pagePicture); + + // Effect: make anything drawn in the margins look washed out. + QPainterPath path; + path.addRect(paperRect); + path.addRect(pageRect); + painter->setPen(QPen(Qt::NoPen)); + painter->setBrush(QColor(255, 255, 255, 180)); + painter->drawPath(path); + + painter->setClipRect(option->exposedRect); +#if 0 + // Draw frame around paper. + painter->setPen(QPen(Qt::black, 0)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(paperRect); +#endif + + // Draw shadow + qreal shWidth = paperRect.width()/100; + QRectF rshadow(paperRect.topRight() + QPointF(0, shWidth), + paperRect.bottomRight() + QPointF(shWidth, 0)); + QLinearGradient rgrad(rshadow.topLeft(), rshadow.topRight()); + rgrad.setColorAt(0.0, QColor(0,0,0,255)); + rgrad.setColorAt(1.0, QColor(0,0,0,0)); + painter->fillRect(rshadow, QBrush(rgrad)); + QRectF bshadow(paperRect.bottomLeft() + QPointF(shWidth, 0), + paperRect.bottomRight() + QPointF(0, shWidth)); + QLinearGradient bgrad(bshadow.topLeft(), bshadow.bottomLeft()); + bgrad.setColorAt(0.0, QColor(0,0,0,255)); + bgrad.setColorAt(1.0, QColor(0,0,0,0)); + painter->fillRect(bshadow, QBrush(bgrad)); + QRectF cshadow(paperRect.bottomRight(), + paperRect.bottomRight() + QPointF(shWidth, shWidth)); + QRadialGradient cgrad(cshadow.topLeft(), shWidth, cshadow.topLeft()); + cgrad.setColorAt(0.0, QColor(0,0,0,255)); + cgrad.setColorAt(1.0, QColor(0,0,0,0)); + painter->fillRect(cshadow, QBrush(cgrad)); + + // todo: drawtext "Page N" below paper +} + +class GraphicsView : public QGraphicsView +{ + Q_OBJECT +public: + GraphicsView(QWidget* parent = 0) + : QGraphicsView(parent) + {} +signals: + void resized(); + +protected: + void resizeEvent(QResizeEvent* e) + { + QGraphicsView::resizeEvent(e); + emit resized(); + } + + void showEvent(QShowEvent* e) + { + QGraphicsView::showEvent(e); + emit resized(); + } +}; + +} // anonymous namespace + +class QPrintPreviewWidgetPrivate +{ + Q_DECLARE_PUBLIC(QPrintPreviewWidget) +public: + QPrintPreviewWidgetPrivate(QPrintPreviewWidget *q) + : q_ptr(q), scene(0), curPage(1), + viewMode(QPrintPreviewWidget::SinglePageView), + zoomMode(QPrintPreviewWidget::FitInView), + zoomFactor(1), initialized(false), fitting(true) + {} + + // private slots + void _q_fit(bool doFitting = false); + void _q_updateCurrentPage(); + + void init(); + void populateScene(); + void layoutPages(); + void generatePreview(); + void setCurrentPage(int pageNumber); + void zoom(qreal zoom); + void setZoomFactor(qreal zoomFactor); + int calcCurrentPage(); + + QPrintPreviewWidget *q_ptr; + GraphicsView *graphicsView; + QGraphicsScene *scene; + + int curPage; + QList<const QPicture *> pictures; + QList<QGraphicsItem *> pages; + + QPrintPreviewWidget::ViewMode viewMode; + QPrintPreviewWidget::ZoomMode zoomMode; + qreal zoomFactor; + bool ownPrinter; + QPrinter* printer; + bool initialized; + bool fitting; +}; + +void QPrintPreviewWidgetPrivate::_q_fit(bool doFitting) +{ + Q_Q(QPrintPreviewWidget); + + if (curPage < 1 || curPage > pages.count()) + return; + + if (!doFitting && !fitting) + return; + + if (doFitting && fitting) { + QRect viewRect = graphicsView->viewport()->rect(); + if (zoomMode == QPrintPreviewWidget::FitInView) { + QList<QGraphicsItem*> containedItems = graphicsView->items(viewRect, Qt::ContainsItemBoundingRect); + foreach(QGraphicsItem* item, containedItems) { + PageItem* pg = static_cast<PageItem*>(item); + if (pg->pageNumber() == curPage) + return; + } + } + + int newPage = calcCurrentPage(); + if (newPage != curPage) + curPage = newPage; + } + + QRectF target = pages.at(curPage-1)->sceneBoundingRect(); + if (viewMode == QPrintPreviewWidget::FacingPagesView) { + // fit two pages + if (curPage % 2) + target.setLeft(target.left() - target.width()); + else + target.setRight(target.right() + target.width()); + } else if (viewMode == QPrintPreviewWidget::AllPagesView) { + target = scene->itemsBoundingRect(); + } + + if (zoomMode == QPrintPreviewWidget::FitToWidth) { + QTransform t; + qreal scale = graphicsView->viewport()->width() / target.width(); + t.scale(scale, scale); + graphicsView->setTransform(t); + if (doFitting && fitting) { + QRectF viewSceneRect = graphicsView->viewportTransform().mapRect(graphicsView->viewport()->rect()); + viewSceneRect.moveTop(target.top()); + graphicsView->ensureVisible(viewSceneRect); // Nah... + } + } else { + graphicsView->fitInView(target, Qt::KeepAspectRatio); + if (zoomMode == QPrintPreviewWidget::FitInView) { + int step = qRound(graphicsView->matrix().mapRect(target).height()); + graphicsView->verticalScrollBar()->setSingleStep(step); + graphicsView->verticalScrollBar()->setPageStep(step); + } + } + + zoomFactor = graphicsView->transform().m11() * (float(printer->logicalDpiY()) / q->logicalDpiY()); + emit q->previewChanged(); +} + +void QPrintPreviewWidgetPrivate::_q_updateCurrentPage() +{ + Q_Q(QPrintPreviewWidget); + + if (viewMode == QPrintPreviewWidget::AllPagesView) + return; + + int newPage = calcCurrentPage(); + if (newPage != curPage) { + curPage = newPage; + emit q->previewChanged(); + } +} + +int QPrintPreviewWidgetPrivate::calcCurrentPage() +{ + int maxArea = 0; + int newPage = curPage; + QRect viewRect = graphicsView->viewport()->rect(); + QList<QGraphicsItem*> items = graphicsView->items(viewRect); + for (int i=0; i<items.size(); ++i) { + PageItem* pg = static_cast<PageItem*>(items.at(i)); + QRect overlap = graphicsView->mapFromScene(pg->sceneBoundingRect()).boundingRect() & viewRect; + int area = overlap.width() * overlap.height(); + if (area > maxArea) { + maxArea = area; + newPage = pg->pageNumber(); + } else if (area == maxArea && pg->pageNumber() < newPage) { + newPage = pg->pageNumber(); + } + } + return newPage; +} + +void QPrintPreviewWidgetPrivate::init() +{ + Q_Q(QPrintPreviewWidget); + + graphicsView = new GraphicsView; + graphicsView->setInteractive(false); + graphicsView->setDragMode(QGraphicsView::ScrollHandDrag); + graphicsView->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate); + QObject::connect(graphicsView->verticalScrollBar(), SIGNAL(valueChanged(int)), + q, SLOT(_q_updateCurrentPage())); + QObject::connect(graphicsView, SIGNAL(resized()), q, SLOT(_q_fit())); + + scene = new QGraphicsScene(graphicsView); + scene->setBackgroundBrush(Qt::gray); + graphicsView->setScene(scene); + + QVBoxLayout *layout = new QVBoxLayout; + q->setLayout(layout); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(graphicsView); +} + +void QPrintPreviewWidgetPrivate::populateScene() +{ + // remove old pages + for (int i = 0; i < pages.size(); i++) + scene->removeItem(pages.at(i)); + qDeleteAll(pages); + pages.clear(); + + int numPages = pictures.count(); + QSize paperSize = printer->paperRect().size(); + QRect pageRect = printer->pageRect(); + + for (int i = 0; i < numPages; i++) { + PageItem* item = new PageItem(i+1, pictures.at(i), paperSize, pageRect); + scene->addItem(item); + pages.append(item); + } +} + +void QPrintPreviewWidgetPrivate::layoutPages() +{ + int numPages = pages.count(); + if (numPages < 1) + return; + + int numPagePlaces = numPages; + int cols = 1; // singleMode and default + if (viewMode == QPrintPreviewWidget::AllPagesView) { + if (printer->orientation() == QPrinter::Portrait) + cols = qCeil(qSqrt((float) numPages)); + else + cols = qFloor(qSqrt((float) numPages)); + cols += cols % 2; // Nicer with an even number of cols + } + else if (viewMode == QPrintPreviewWidget::FacingPagesView) { + cols = 2; + numPagePlaces += 1; + } + int rows = qCeil(qreal(numPagePlaces) / cols); + + qreal itemWidth = pages.at(0)->boundingRect().width(); + qreal itemHeight = pages.at(0)->boundingRect().height(); + int pageNum = 1; + for (int i = 0; i < rows && pageNum <= numPages; i++) { + for (int j = 0; j < cols && pageNum <= numPages; j++) { + if (!i && !j && viewMode == QPrintPreviewWidget::FacingPagesView) { + // Front page doesn't have a facing page + continue; + } else { + pages.at(pageNum-1)->setPos(QPointF(j*itemWidth, i*itemHeight)); + pageNum++; + } + } + } + scene->setSceneRect(scene->itemsBoundingRect()); +} + +void QPrintPreviewWidgetPrivate::generatePreview() +{ + //### If QPrinter::setPreviewMode() becomes public, handle the + //### case that we have been constructed with a printer that + //### _already_ has been preview-painted to, so we should + //### initially just show the pages it already contains, and not + //### emit paintRequested() until the user changes some parameter + + Q_Q(QPrintPreviewWidget); + printer->d_func()->setPreviewMode(true); + emit q->paintRequested(printer); + printer->d_func()->setPreviewMode(false); + pictures = printer->d_func()->previewPages(); + populateScene(); // i.e. setPreviewPrintedPictures() e.l. + layoutPages(); + curPage = qBound(1, curPage, pages.count()); + if (fitting) + _q_fit(); + emit q->previewChanged(); +} + +void QPrintPreviewWidgetPrivate::setCurrentPage(int pageNumber) +{ + if (pageNumber < 1 || pageNumber > pages.count()) + return; + + int lastPage = curPage; + curPage = pageNumber; + + if (lastPage != curPage && lastPage > 0 && lastPage <= pages.count()) { + if (zoomMode != QPrintPreviewWidget::FitInView) { + QScrollBar *hsc = graphicsView->horizontalScrollBar(); + QScrollBar *vsc = graphicsView->verticalScrollBar(); + QPointF pt = graphicsView->transform().map(pages.at(curPage-1)->pos()); + vsc->setValue(int(pt.y()) - 10); + hsc->setValue(int(pt.x()) - 10); + } else { + graphicsView->centerOn(pages.at(curPage-1)); + } + } +} + +void QPrintPreviewWidgetPrivate::zoom(qreal zoom) +{ + zoomFactor *= zoom; + graphicsView->scale(zoom, zoom); +} + +void QPrintPreviewWidgetPrivate::setZoomFactor(qreal _zoomFactor) +{ + Q_Q(QPrintPreviewWidget); + zoomFactor = _zoomFactor; + graphicsView->resetTransform(); + int dpi_y = q->logicalDpiY(); + int printer_dpi_y = printer->logicalDpiY(); + graphicsView->scale(zoomFactor*(dpi_y/float(printer_dpi_y)), + zoomFactor*(dpi_y/float(printer_dpi_y))); +} + +/////////////////////////////////////// + +/*! + \class QPrintPreviewWidget + \since 4.4 + + \brief The QPrintPreviewWidget class provides a widget for + previewing page layouts for printer output. + + \ingroup multimedia + + QPrintPreviewDialog uses a QPrintPreviewWidget internally, and the + purpose of QPrintPreviewWidget is to make it possible to embed the + preview into other widgets. It also makes it possible to build a different + user interface around it than the default one provided with QPrintPreviewDialog. + + Using QPrintPreviewWidget is straightforward: + + \list 1 + \o Create the QPrintPreviewWidget + + Construct the QPrintPreviewWidget either by passing in an + exisiting QPrinter object, or have QPrintPreviewWidget create a + default constructed QPrinter object for you. + + \o Connect the paintRequested() signal to a slot. + + When the widget needs to generate a set of preview pages, a + paintRequested() signal will be emitted from the widget. Connect a + slot to this signal, and draw onto the QPrinter passed in as a + signal parameter. Call QPrinter::newPage(), to start a new + page in the preview. + + \endlist + + \sa QPrinter, QPrintDialog, QPageSetupDialog, QPrintPreviewDialog +*/ + + +/*! + \enum QPrintPreviewWidget::ViewMode + + This enum is used to describe the view mode of the preview widget. + + \value SinglePageView A mode where single pages in the preview + is viewed. + + \value FacingPagesView A mode where the facing pages in the preview + is viewed. + + \value AllPagesView A view mode where all the pages in the preview + is viewed. +*/ + +/*! + \enum QPrintPreviewWidget::ZoomMode + + This enum is used to describe zoom mode of the preview widget. + + \value CustomZoom The zoom is set to a custom zoom value. + + \value FitToWidth This mode fits the current page to the width of the view. + + \value FitInView This mode fits the current page inside the view. + +*/ + +/*! + Constructs a QPrintPreviewWidget based on \a printer and with \a + parent as the parent widget. The widget flags \a flags are passed on + to the QWidget constructor. + + \sa QWidget::setWindowFlags() +*/ +QPrintPreviewWidget::QPrintPreviewWidget(QPrinter *printer, QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags), d_ptr(new QPrintPreviewWidgetPrivate(this)) +{ + Q_D(QPrintPreviewWidget); + d->printer = printer; + d->ownPrinter = false; + d->init(); +} + +/*! + \overload + + This will cause QPrintPreviewWidget to create an internal, default + constructed QPrinter object, which will be used to generate the + preview. +*/ +QPrintPreviewWidget::QPrintPreviewWidget(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags), d_ptr(new QPrintPreviewWidgetPrivate(this)) +{ + Q_D(QPrintPreviewWidget); + d->printer = new QPrinter; + d->ownPrinter = true; + d->init(); +} + + +/*! + Destroys the QPrintPreviewWidget. +*/ +QPrintPreviewWidget::~QPrintPreviewWidget() +{ + Q_D(QPrintPreviewWidget); + if (d->ownPrinter) + delete d->printer; + delete d_ptr; +} + +/*! + Returns the current view mode. The default view mode is SinglePageView. +*/ +QPrintPreviewWidget::ViewMode QPrintPreviewWidget::viewMode() const +{ + Q_D(const QPrintPreviewWidget); + return d->viewMode; +} + +/*! + Sets the view mode to \a mode. The default view mode is + SinglePageView. +*/ +void QPrintPreviewWidget::setViewMode(ViewMode mode) +{ + Q_D(QPrintPreviewWidget); + d->viewMode = mode; + d->layoutPages(); + if (d->viewMode == AllPagesView) { + d->graphicsView->fitInView(d->scene->itemsBoundingRect(), Qt::KeepAspectRatio); + d->fitting = false; + d->zoomMode = QPrintPreviewWidget::CustomZoom; + d->zoomFactor = d->graphicsView->transform().m11() * (float(d->printer->logicalDpiY()) / logicalDpiY()); + emit previewChanged(); + } else { + d->fitting = true; + d->_q_fit(); + } +} + +/*! + Returns the current orientation of the preview. This value is + obtained from the QPrinter object associated with the preview. +*/ +QPrinter::Orientation QPrintPreviewWidget::orientation() const +{ + Q_D(const QPrintPreviewWidget); + return d->printer->orientation(); +} + +/*! + Sets the current orientation to \a orientation. This value will be + set on the QPrinter object associated with the preview. +*/ +void QPrintPreviewWidget::setOrientation(QPrinter::Orientation orientation) +{ + Q_D(QPrintPreviewWidget); + d->printer->setOrientation(orientation); + d->generatePreview(); +} + +/*! + Prints the preview to the printer associated with the preview. +*/ +void QPrintPreviewWidget::print() +{ + Q_D(QPrintPreviewWidget); + // ### make use of the generated pages + emit paintRequested(d->printer); +} + +/*! + Zooms the current view in by \a factor. The default value for \a + factor is 1.1, which means the view will be scaled up by 10%. +*/ +void QPrintPreviewWidget::zoomIn(qreal factor) +{ + Q_D(QPrintPreviewWidget); + d->fitting = false; + d->zoomMode = QPrintPreviewWidget::CustomZoom; + d->zoom(factor); +} + +/*! + Zooms the current view out by \a factor. The default value for \a + factor is 1.1, which means the view will be scaled down by 10%. +*/ +void QPrintPreviewWidget::zoomOut(qreal factor) +{ + Q_D(QPrintPreviewWidget); + d->fitting = false; + d->zoomMode = QPrintPreviewWidget::CustomZoom; + d->zoom(1/factor); +} + +/*! + Returns the zoom factor of the view. +*/ +qreal QPrintPreviewWidget::zoomFactor() const +{ + Q_D(const QPrintPreviewWidget); + return d->zoomFactor; +} + +/*! + Sets the zoom factor of the view to \a factor. For example, a + value of 1.0 indicates an unscaled view, which is approximately + the size the view will have on paper. A value of 0.5 will halve + the size of the view, while a value of 2.0 will double the size of + the view. +*/ +void QPrintPreviewWidget::setZoomFactor(qreal factor) +{ + Q_D(QPrintPreviewWidget); + d->fitting = false; + d->zoomMode = QPrintPreviewWidget::CustomZoom; + d->setZoomFactor(factor); +} + +/*! + Returns the number of pages in the preview. +*/ +int QPrintPreviewWidget::numPages() const +{ + Q_D(const QPrintPreviewWidget); + return d->pages.size(); +} + +/*! + Returns the currently viewed page in the preview. +*/ +int QPrintPreviewWidget::currentPage() const +{ + Q_D(const QPrintPreviewWidget); + return d->curPage; +} + +/*! + Sets the current page in the preview. This will cause the view to + skip to the beginning of \a page. +*/ +void QPrintPreviewWidget::setCurrentPage(int page) +{ + Q_D(QPrintPreviewWidget); + d->setCurrentPage(page); +} + +/*! + This is a convenience function and is the same as calling \c + {setZoomMode(QPrintPreviewWidget::FitToWidth)}. +*/ +void QPrintPreviewWidget::fitToWidth() +{ + setZoomMode(FitToWidth); +} + +/*! + This is a convenience function and is the same as calling \c + {setZoomMode(QPrintPreviewWidget::FitInView)}. +*/ +void QPrintPreviewWidget::fitInView() +{ + setZoomMode(FitInView); +} + +/*! + Sets the zoom mode to \a zoomMode. The default zoom mode is FitInView. + + \sa zoomMode(), viewMode(), setViewMode() +*/ +void QPrintPreviewWidget::setZoomMode(QPrintPreviewWidget::ZoomMode zoomMode) +{ + Q_D(QPrintPreviewWidget); + d->zoomMode = zoomMode; + if (d->zoomMode == FitInView || d->zoomMode == FitToWidth) { + d->fitting = true; + d->_q_fit(true); + } else { + d->fitting = false; + } +} + +/*! + Returns the current zoom mode. + + \sa setZoomMode(), viewMode(), setViewMode() +*/ +QPrintPreviewWidget::ZoomMode QPrintPreviewWidget::zoomMode() const +{ + Q_D(const QPrintPreviewWidget); + return d->zoomMode; +} + +/*! + This is a convenience function and is the same as calling \c + {setOrientation(QPrinter::Landscape)}. +*/ +void QPrintPreviewWidget::setLandscapeOrientation() +{ + setOrientation(QPrinter::Landscape); +} + +/*! + This is a convenience function and is the same as calling \c + {setOrientation(QPrinter::Portrait)}. +*/ +void QPrintPreviewWidget::setPortraitOrientation() +{ + setOrientation(QPrinter::Portrait); +} + +/*! + This is a convenience function and is the same as calling \c + {setViewMode(QPrintPreviewWidget::SinglePageView)}. +*/ +void QPrintPreviewWidget::setSinglePageViewMode() +{ + setViewMode(SinglePageView); +} + +/*! + This is a convenience function and is the same as calling \c + {setViewMode(QPrintPreviewWidget::FacingPagesView)}. +*/ +void QPrintPreviewWidget::setFacingPagesViewMode() +{ + setViewMode(FacingPagesView); +} + +/*! + This is a convenience function and is the same as calling \c + {setViewMode(QPrintPreviewWidget::AllPagesView)}. +*/ +void QPrintPreviewWidget::setAllPagesViewMode() +{ + setViewMode(AllPagesView); +} + + +/*! + This function updates the preview, which causes the + paintRequested() signal to be emitted. +*/ +void QPrintPreviewWidget::updatePreview() +{ + Q_D(QPrintPreviewWidget); + d->initialized = true; + d->generatePreview(); + d->graphicsView->updateGeometry(); +} + +/*! \reimp +*/ +void QPrintPreviewWidget::setVisible(bool visible) +{ + Q_D(QPrintPreviewWidget); + if (visible && !d->initialized) + updatePreview(); + QWidget::setVisible(visible); +} + +/*! + \fn void QPrintPreviewWidget::paintRequested(QPrinter *printer) + + This signal is emitted when the preview widget needs to generate a + set of preview pages. \a printer is the printer associated with + this preview widget. +*/ + +/*! + \fn void QPrintPreviewWidget::previewChanged() + + This signal is emitted whenever the preview widget has changed + some internal state, such as the orientation. +*/ + + +QT_END_NAMESPACE + +#include "moc_qprintpreviewwidget.cpp" +#include "qprintpreviewwidget.moc" + +#endif // QT_NO_PRINTPREVIEWWIDGET diff --git a/src/gui/widgets/qprintpreviewwidget.h b/src/gui/widgets/qprintpreviewwidget.h new file mode 100644 index 0000000..27110a4 --- /dev/null +++ b/src/gui/widgets/qprintpreviewwidget.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTPREVIEWWIDGET_H +#define QPRINTPREVIEWWIDGET_H + +#include <QtGui/qwidget.h> +#include <QtGui/qprinter.h> + +#ifndef QT_NO_PRINTPREVIEWWIDGET + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPrintPreviewWidgetPrivate; + +class Q_GUI_EXPORT QPrintPreviewWidget : public QWidget +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPrintPreviewWidget) +public: + + enum ViewMode { + SinglePageView, + FacingPagesView, + AllPagesView + }; + + enum ZoomMode { + CustomZoom, + FitToWidth, + FitInView + }; + + explicit QPrintPreviewWidget(QPrinter *printer, QWidget *parent = 0, Qt::WindowFlags flags = 0); + explicit QPrintPreviewWidget(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QPrintPreviewWidget(); + + qreal zoomFactor() const; + QPrinter::Orientation orientation() const; + ViewMode viewMode() const; + ZoomMode zoomMode() const; + int currentPage() const; + int numPages() const; + void setVisible(bool visible); + +public Q_SLOTS: + void print(); + + void zoomIn(qreal zoom = 1.1); + void zoomOut(qreal zoom = 1.1); + void setZoomFactor(qreal zoomFactor); + void setOrientation(QPrinter::Orientation orientation); + void setViewMode(ViewMode viewMode); + void setZoomMode(ZoomMode zoomMode); + void setCurrentPage(int pageNumber); + + void fitToWidth(); + void fitInView(); + void setLandscapeOrientation(); + void setPortraitOrientation(); + void setSinglePageViewMode(); + void setFacingPagesViewMode(); + void setAllPagesViewMode(); + + void updatePreview(); + +Q_SIGNALS: + void paintRequested(QPrinter *printer); + void previewChanged(); + +private: + QPrintPreviewWidgetPrivate *d_ptr; + Q_PRIVATE_SLOT(d_func(), void _q_fit()) + Q_PRIVATE_SLOT(d_func(), void _q_updateCurrentPage()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_PRINTPREVIEWWIDGET +#endif // QPRINTPREVIEWWIDGET_H diff --git a/src/gui/widgets/qprogressbar.cpp b/src/gui/widgets/qprogressbar.cpp new file mode 100644 index 0000000..cdb3836 --- /dev/null +++ b/src/gui/widgets/qprogressbar.cpp @@ -0,0 +1,592 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprogressbar.h" +#ifndef QT_NO_PROGRESSBAR +#include <qevent.h> +#include <qpainter.h> +#include <qstylepainter.h> +#include <qstyleoption.h> +#include <private/qwidget_p.h> +#ifndef QT_NO_ACCESSIBILITY +#include <qaccessible.h> +#endif +#include <limits.h> + +QT_BEGIN_NAMESPACE + +class QProgressBarPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QProgressBar) + +public: + QProgressBarPrivate(); + + void init(); + inline void resetLayoutItemMargins(); + + int minimum; + int maximum; + int value; + Qt::Alignment alignment; + uint textVisible : 1; + int lastPaintedValue; + Qt::Orientation orientation; + bool invertedAppearance; + QProgressBar::Direction textDirection; + QString format; + inline int bound(int val) const { return qMax(minimum-1, qMin(maximum, val)); } + bool repaintRequired() const; +}; + +QProgressBarPrivate::QProgressBarPrivate() + : minimum(0), maximum(100), value(-1), alignment(Qt::AlignLeft), textVisible(true), + lastPaintedValue(-1), orientation(Qt::Horizontal), invertedAppearance(false), + textDirection(QProgressBar::TopToBottom), format(QLatin1String("%p%")) +{ +} + +void QProgressBarPrivate::init() +{ + Q_Q(QProgressBar); + QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed); + if (orientation == Qt::Vertical) + sp.transpose(); + q->setSizePolicy(sp); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + resetLayoutItemMargins(); +} + +void QProgressBarPrivate::resetLayoutItemMargins() +{ + Q_Q(QProgressBar); + QStyleOptionProgressBar option; + q->initStyleOption(&option); + setLayoutItemMargins(QStyle::SE_ProgressBarLayoutItem, &option); +} + +/*! + Initialize \a option with the values from this QProgressBar. This method is useful + for subclasses when they need a QStyleOptionProgressBar or QStyleOptionProgressBarV2, + but don't want to fill in all the information themselves. This function will check the version + of the QStyleOptionProgressBar and fill in the additional values for a + QStyleOptionProgressBarV2. + + \sa QStyleOption::initFrom() +*/ +void QProgressBar::initStyleOption(QStyleOptionProgressBar *option) const +{ + if (!option) + return; + Q_D(const QProgressBar); + option->initFrom(this); + + if (d->orientation == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; + option->minimum = d->minimum; + option->maximum = d->maximum; + option->progress = d->value; + option->textAlignment = d->alignment; + option->textVisible = d->textVisible; + option->text = text(); + + if (QStyleOptionProgressBarV2 *optionV2 + = qstyleoption_cast<QStyleOptionProgressBarV2 *>(option)) { + optionV2->orientation = d->orientation; // ### Qt 5: use State_Horizontal instead + optionV2->invertedAppearance = d->invertedAppearance; + optionV2->bottomToTop = (d->textDirection == QProgressBar::BottomToTop); + } +} + +bool QProgressBarPrivate::repaintRequired() const +{ + Q_Q(const QProgressBar); + if (value == lastPaintedValue) + return false; + + int valueDifference = qAbs(value - lastPaintedValue); + + // Check if the text needs to be repainted + if (value == minimum || value == maximum) + return true; + if (textVisible) { + if ((format.contains(QLatin1String("%v")))) + return true; + if ((format.contains(QLatin1String("%p")) + && valueDifference >= qAbs((maximum - minimum) / 100))) + return true; + } + + // Check if the bar needs to be repainted + QStyleOptionProgressBarV2 opt; + q->initStyleOption(&opt); + int cw = q->style()->pixelMetric(QStyle::PM_ProgressBarChunkWidth, &opt, q); + QRect groove = q->style()->subElementRect(QStyle::SE_ProgressBarGroove, &opt, q); + // This expression is basically + // (valueDifference / (maximum - minimum) > cw / groove.width()) + // transformed to avoid integer division. + int grooveBlock = (q->orientation() == Qt::Horizontal) ? groove.width() : groove.height(); + return (valueDifference * grooveBlock > cw * (maximum - minimum)); +} + +/*! + \class QProgressBar + \brief The QProgressBar widget provides a horizontal or vertical progress bar. + + \ingroup basicwidgets + \mainclass + + A progress bar is used to give the user an indication of the + progress of an operation and to reassure them that the application + is still running. + + The progress bar uses the concept of \e steps. You set it up by + specifying the minimum and maximum possible step values, and it + will display the percentage of steps that have been completed + when you later give it the current step value. The percentage is + calculated by dividing the progress (value() - minimum()) divided + by maximum() - minimum(). + + You can specify the minimum and maximum number of steps with + setMinimum() and setMaximum. The current number of steps is set + with setValue(). The progress bar can be rewound to the + beginning with reset(). + + If minimum and maximum both are set to 0, the bar shows a busy indicator + instead of a percentage of steps. This is useful, for example, when using + QFtp or QHttp to download items when they are unable to determine the + size of the item being downloaded. + + \table + \row \o \inlineimage macintosh-progressbar.png Screenshot of a Macintosh style progress bar + \o A progress bar shown in the Macintosh widget style. + \row \o \inlineimage windowsxp-progressbar.png Screenshot of a Windows XP style progress bar + \o A progress bar shown in the Windows XP widget style. + \row \o \inlineimage plastique-progressbar.png Screenshot of a Plastique style progress bar + \o A progress bar shown in the Plastique widget style. + \endtable + + \sa QTimeLine, QProgressDialog, {fowler}{GUI Design Handbook: Progress Indicator} +*/ + +/*! + \since 4.1 + \enum QProgressBar::Direction + \brief Specifies the reading direction of the \l text for vertical progress bars. + + \value TopToBottom The text is rotated 90 degrees clockwise. + \value BottomToTop The text is rotated 90 degrees counter-clockwise. + + Note that whether or not the text is drawn is dependent on the style. + Currently CDE, CleanLooks, Motif, and Plastique draw the text. Mac, Windows + and WindowsXP style do not. + + \sa textDirection +*/ + +/*! + \fn void QProgressBar::valueChanged(int value) + + This signal is emitted when the value shown in the progress bar changes. + \a value is the new value shown by the progress bar. +*/ + +/*! + Constructs a progress bar with the given \a parent. + + By default, the minimum step value is set to 0, and the maximum to 100. + + \sa setRange() +*/ + +QProgressBar::QProgressBar(QWidget *parent) + : QWidget(*(new QProgressBarPrivate), parent, 0) +{ + d_func()->init(); +} + +/*! + Reset the progress bar. The progress bar "rewinds" and shows no + progress. +*/ + +void QProgressBar::reset() +{ + Q_D(QProgressBar); + d->value = d->minimum - 1; + if (d->minimum == INT_MIN) + d->value = INT_MIN; + repaint(); +} + +/*! + \property QProgressBar::minimum + \brief the progress bar's minimum value + + When setting this property, the \l maximum is adjusted if + necessary to ensure that the range remains valid. If the + current value falls outside the new range, the progress bar is reset + with reset(). +*/ +void QProgressBar::setMinimum(int minimum) +{ + setRange(minimum, qMax(d_func()->maximum, minimum)); +} + +int QProgressBar::minimum() const +{ + return d_func()->minimum; +} + + +/*! + \property QProgressBar::maximum + \brief the progress bar's maximum value + + When setting this property, the \l minimum is adjusted if + necessary to ensure that the range remains valid. If the + current value falls outside the new range, the progress bar is reset + with reset(). +*/ + +void QProgressBar::setMaximum(int maximum) +{ + setRange(qMin(d_func()->minimum, maximum), maximum); +} + +int QProgressBar::maximum() const +{ + return d_func()->maximum; +} + +/*! + \property QProgressBar::value + \brief the progress bar's current value + + Attempting to change the current value to one outside + the minimum-maximum range has no effect on the current value. +*/ +void QProgressBar::setValue(int value) +{ + Q_D(QProgressBar); + if (d->value == value + || ((value > d->maximum || value < d->minimum) + && (d->maximum != 0 || d->minimum != 0))) + return; + d->value = value; + emit valueChanged(value); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::ValueChanged); +#endif + if (d->repaintRequired()) + repaint(); +} + +int QProgressBar::value() const +{ + return d_func()->value; +} + +/*! + Sets the progress bar'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 bar is reset + with reset(). + + \sa minimum maximum +*/ +void QProgressBar::setRange(int minimum, int maximum) +{ + Q_D(QProgressBar); + d->minimum = minimum; + d->maximum = qMax(minimum, maximum); + if ( d->value <(d->minimum-1) || d->value > d->maximum) + reset(); +} +/*! + \property QProgressBar::textVisible + \brief whether the current completed percentage should be displayed + + \sa textDirection +*/ +void QProgressBar::setTextVisible(bool visible) +{ + Q_D(QProgressBar); + if (d->textVisible != visible) { + d->textVisible = visible; + repaint(); + } +} + +bool QProgressBar::isTextVisible() const +{ + return d_func()->textVisible; +} + +/*! + \property QProgressBar::alignment + \brief the alignment of the progress bar +*/ +void QProgressBar::setAlignment(Qt::Alignment alignment) +{ + if (d_func()->alignment != alignment) { + d_func()->alignment = alignment; + repaint(); + } +} + +Qt::Alignment QProgressBar::alignment() const +{ + return d_func()->alignment; +} + +/*! + \reimp +*/ +void QProgressBar::paintEvent(QPaintEvent *) +{ + QStylePainter paint(this); + QStyleOptionProgressBarV2 opt; + initStyleOption(&opt); + paint.drawControl(QStyle::CE_ProgressBar, opt); + d_func()->lastPaintedValue = d_func()->value; +} + +/*! + \reimp +*/ +QSize QProgressBar::sizeHint() const +{ + ensurePolished(); + QFontMetrics fm = fontMetrics(); + QStyleOptionProgressBarV2 opt; + initStyleOption(&opt); + int cw = style()->pixelMetric(QStyle::PM_ProgressBarChunkWidth, &opt, this); + QSize size = QSize(qMax(9, cw) * 7 + fm.width(QLatin1Char('0')) * 4, fm.height() + 8); + if (opt.orientation == Qt::Vertical) + size.transpose(); + return style()->sizeFromContents(QStyle::CT_ProgressBar, &opt, size, this); +} + +/*! + \reimp +*/ +QSize QProgressBar::minimumSizeHint() const +{ + QSize size; + if (orientation() == Qt::Horizontal) + size = QSize(sizeHint().width(), fontMetrics().height() + 2); + else + size = QSize(fontMetrics().height() + 2, sizeHint().height()); + return size; +} + +/*! + \property QProgressBar::text + \brief the descriptive text shown with the progress bar + + The text returned is the same as the text displayed in the center + (or in some styles, to the left) of the progress bar. + + The progress shown in the text may be smaller than the minimum value, + indicating that the progress bar is in the "reset" state before any + progress is set. + + In the default implementation, the text either contains a percentage + value that indicates the progress so far, or it is blank because the + progress bar is in the reset state. +*/ +QString QProgressBar::text() const +{ + Q_D(const QProgressBar); + if (d->maximum == 0 || d->value < d->minimum + || (d->value == INT_MIN && d->minimum == INT_MIN)) + return QString(); + + qint64 totalSteps = qint64(d->maximum) - qint64(d->minimum); + + QString result = d->format; + result.replace(QLatin1String("%m"), QString::fromLatin1("%1").arg(totalSteps)); + result.replace(QLatin1String("%v"), QString::fromLatin1("%1").arg(d->value)); + + // If max and min are equal and we get this far, it means that the + // progress bar has one step and that we are on that step. Return + // 100% here in order to avoid division by zero further down. + if (totalSteps == 0) { + result.replace(QLatin1String("%p"), QString::fromLatin1("%1").arg(100)); + return result; + } + + int progress = int(((qreal(d->value) - qreal(d->minimum)) * 100.0) / totalSteps); + result.replace(QLatin1String("%p"), QString::fromLatin1("%1").arg(progress)); + return result; +} + +/*! + \since 4.1 + \property QProgressBar::orientation + \brief the orientation of the progress bar + + The orientation must be \l Qt::Horizontal (the default) or \l + Qt::Vertical. + + \sa invertedAppearance, textDirection +*/ + +void QProgressBar::setOrientation(Qt::Orientation orientation) +{ + Q_D(QProgressBar); + if (d->orientation == orientation) + return; + d->orientation = orientation; + if (!testAttribute(Qt::WA_WState_OwnSizePolicy)) { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy(sp); + setAttribute(Qt::WA_WState_OwnSizePolicy, false); + } + d->resetLayoutItemMargins(); + update(); + updateGeometry(); +} + +Qt::Orientation QProgressBar::orientation() const +{ + Q_D(const QProgressBar); + return d->orientation; +} + +/*! + \since 4.1 + \property QProgressBar::invertedAppearance + \brief whether or not a progress bar shows its progress inverted + + If this property is false, the progress bar grows in the other + direction (e.g. from right to left). By default, the progress bar + is not inverted. + + \sa orientation, layoutDirection +*/ + +void QProgressBar::setInvertedAppearance(bool invert) +{ + Q_D(QProgressBar); + d->invertedAppearance = invert; + update(); +} + +bool QProgressBar::invertedAppearance() +{ + Q_D(QProgressBar); + return d->invertedAppearance; +} + +/*! + \since 4.1 + \property QProgressBar::textDirection + \brief the reading direction of the \l text for vertical progress bars + + This property has no impact on horizontal progress bars. + By default, the reading direction is QProgressBar::TopToBottom. + + \sa orientation, textVisible +*/ +void QProgressBar::setTextDirection(QProgressBar::Direction textDirection) +{ + Q_D(QProgressBar); + d->textDirection = textDirection; + update(); +} + +QProgressBar::Direction QProgressBar::textDirection() +{ + Q_D(QProgressBar); + return d->textDirection; +} + +/*! \reimp */ +bool QProgressBar::event(QEvent *e) +{ + Q_D(QProgressBar); + if (e->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || e->type() == QEvent::MacSizeChange +#endif + ) + d->resetLayoutItemMargins(); + return QWidget::event(e); +} + +/*! + \since 4.2 + \property QProgressBar::format + \brief the string used to generate the current text + + %p - is replaced by the percentage completed. + %v - is replaced by the current value. + %m - is replaced by the total number of steps. + + The default value is "%p%". + + \sa text() +*/ +void QProgressBar::setFormat(const QString &format) +{ + Q_D(QProgressBar); + if (d->format == format) + return; + d->format = format; + update(); +} + +QString QProgressBar::format() const +{ + Q_D(const QProgressBar); + return d->format; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PROGRESSBAR diff --git a/src/gui/widgets/qprogressbar.h b/src/gui/widgets/qprogressbar.h new file mode 100644 index 0000000..57c83fe --- /dev/null +++ b/src/gui/widgets/qprogressbar.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROGRESSBAR_H +#define QPROGRESSBAR_H + +#include <QtGui/qframe.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROGRESSBAR + +class QProgressBarPrivate; +class QStyleOptionProgressBar; + +class Q_GUI_EXPORT QProgressBar : public QWidget +{ + Q_OBJECT + Q_ENUMS(Direction) + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int maximum READ maximum WRITE setMaximum) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(bool textVisible READ isTextVisible WRITE setTextVisible) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(bool invertedAppearance READ invertedAppearance WRITE setInvertedAppearance) + Q_PROPERTY(Direction textDirection READ textDirection WRITE setTextDirection) + Q_PROPERTY(QString format READ format WRITE setFormat) + +public: + enum Direction { TopToBottom, BottomToTop }; + + explicit QProgressBar(QWidget *parent = 0); + + int minimum() const; + int maximum() const; + + int value() const; + + virtual QString text() const; + void setTextVisible(bool visible); + bool isTextVisible() const; + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment alignment); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + Qt::Orientation orientation() const; + + void setInvertedAppearance(bool invert); + bool invertedAppearance(); + void setTextDirection(QProgressBar::Direction textDirection); + QProgressBar::Direction textDirection(); + + void setFormat(const QString &format); + QString format() const; + +public Q_SLOTS: + void reset(); + void setRange(int minimum, int maximum); + void setMinimum(int minimum); + void setMaximum(int maximum); + void setValue(int value); + void setOrientation(Qt::Orientation); + +Q_SIGNALS: + void valueChanged(int value); + +protected: + bool event(QEvent *e); + void paintEvent(QPaintEvent *); + void initStyleOption(QStyleOptionProgressBar *option) const; + +private: + Q_DECLARE_PRIVATE(QProgressBar) + Q_DISABLE_COPY(QProgressBar) +}; + +#endif // QT_NO_PROGRESSBAR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROGRESSBAR_H diff --git a/src/gui/widgets/qpushbutton.cpp b/src/gui/widgets/qpushbutton.cpp new file mode 100644 index 0000000..03ca751 --- /dev/null +++ b/src/gui/widgets/qpushbutton.cpp @@ -0,0 +1,732 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qbitmap.h" +#include "qdesktopwidget.h" +#include "qdialog.h" +#include <private/qdialog_p.h> +#include "qdrawutil.h" +#include "qevent.h" +#include "qfontmetrics.h" +#include "qmenu.h" +#include "qstylepainter.h" +#include "qpixmap.h" +#include "qpointer.h" +#include "qpushbutton.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qtoolbar.h" +#include "qdebug.h" +#include "qlayoutitem.h" +#include "qdialogbuttonbox.h" + +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif + +#include "private/qpushbutton_p.h" + +QT_BEGIN_NAMESPACE + + +/*! + \class QPushButton + \brief The QPushButton widget provides a command button. + + \ingroup basicwidgets + \mainclass + + The push button, or command button, is perhaps the most commonly + used widget in any graphical user interface. Push (click) a button + to command the computer to perform some action, or to answer a + question. Typical buttons are OK, Apply, Cancel, Close, Yes, No + and Help. + + A command button is rectangular and typically displays a text + label describing its action. A shortcut key can be specified by + preceding the preferred character with an ampersand in the + text. For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qpushbutton.cpp 0 + + In this example the shortcut is \e{Alt+D}. See the \l + {QShortcut#mnemonic}{QShortcut} documentation for details (to + display an actual ampersand, use '&&'). + + Push buttons display a textual label, and optionally a small + icon. These can be set using the constructors and changed later + using setText() and setIcon(). If the button is disabled the + appearance of the text and icon will be manipulated with respect + to the GUI style to make the button look "disabled". + + A push button emits the signal clicked() when it is activated by + the mouse, the Spacebar or by a keyboard shortcut. Connect to + this signal to perform the button's action. Push buttons also + provide less commonly used signals, for example, pressed() and + released(). + + Command buttons in dialogs are by default auto-default buttons, + i.e. they become the default push button automatically when they + receive the keyboard input focus. A default button is a push + button that is activated when the user presses the Enter or Return + key in a dialog. You can change this with setAutoDefault(). Note + that auto-default buttons reserve a little extra space which is + necessary to draw a default-button indicator. If you do not want + this space around your buttons, call setAutoDefault(false). + + Being so central, the button widget has grown to accommodate a + great many variations in the past decade. The Microsoft style + guide now shows about ten different states of Windows push buttons + and the text implies that there are dozens more when all the + combinations of features are taken into consideration. + + The most important modes or states are: + \list + \i Available or not (grayed out, disabled). + \i Standard push button, toggling push button or menu button. + \i On or off (only for toggling push buttons). + \i Default or normal. The default button in a dialog can generally + be "clicked" using the Enter or Return key. + \i Auto-repeat or not. + \i Pressed down or not. + \endlist + + As a general rule, use a push button when the application or + dialog window performs an action when the user clicks on it (such + as Apply, Cancel, Close and Help) \e and when the widget is + supposed to have a wide, rectangular shape with a text label. + Small, typically square buttons that change the state of the + window rather than performing an action (such as the buttons in + the top-right corner of the QFileDialog) are not command buttons, + but tool buttons. Qt provides a special class (QToolButton) for + these buttons. + + If you need toggle behavior (see setCheckable()) or a button + that auto-repeats the activation signal when being pushed down + like the arrows in a scroll bar (see setAutoRepeat()), a command + button is probably not what you want. When in doubt, use a tool + button. + + A variation of a command button is a menu button. These provide + not just one command, but several, since when they are clicked + they pop up a menu of options. Use the method setMenu() to + associate a popup menu with a push button. + + Other classes of buttons are option buttons (see QRadioButton) and + check boxes (see QCheckBox). + + \table 100% + \row \o \inlineimage macintosh-pushbutton.png Screenshot of a Macintosh style push button + \o A push button shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + + Note that when a button's width becomes smaller than 50 or + its height becomes smaller than 30, the button's corners are + changed from round to square. Use the setMinimumSize() + function to prevent this behavior. + + \row \o \inlineimage windowsxp-pushbutton.png Screenshot of a Windows XP style push button + \o A push button shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage plastique-pushbutton.png Screenshot of a Plastique style push button + \o A push button shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + In Qt, the QAbstractButton base class provides most of the modes + and other API, and QPushButton provides GUI logic. + See QAbstractButton for more information about the API. + + \sa QToolButton, QRadioButton, QCheckBox, {fowler}{GUI Design Handbook: Push Button} +*/ + +/*! + \property QPushButton::autoDefault + \brief whether the push button is an auto default button + + If this property is set to true then the push button is an auto + default button. + + In some GUI styles a default button is drawn with an extra frame + around it, up to 3 pixels or more. Qt automatically keeps this + space free around auto-default buttons, i.e. auto-default buttons + may have a slightly larger size hint. + + This property's default is true for buttons that have a QDialog + parent; otherwise it defaults to false. + + See the \l default property for details of how \l default and + auto-default interact. +*/ + +/*! + \property QPushButton::default + \brief whether the push button is the default button + + Default and autodefault buttons decide what happens when the user + presses enter in a dialog. + + A button with this property set to true (i.e., the dialog's + \e default button,) will automatically be pressed when the user presses enter, + with one exception: if an \a autoDefault button currently has focus, the autoDefault + button is pressed. When the dialog has \l autoDefault buttons but no default button, + pressing enter will press either the \l autoDefault button that currently has focus, or if no + button has focus, the next \l autoDefault button in the focus chain. + + In a dialog, only one push button at a time can be the default + button. This button is then displayed with an additional frame + (depending on the GUI style). + + The default button behavior is provided only in dialogs. Buttons + can always be clicked from the keyboard by pressing Spacebar when + the button has focus. + + If the default property is set to false on the current default button + while the dialog is visible, a new default will automatically be + assigned the next time a pushbutton in the dialog receives focus. + + This property's default is false. +*/ + +/*! + \property QPushButton::flat + \brief whether the button border is raised + + This property's default is false. If this property is set, most + styles will not paint the button background unless the button is + being pressed. setAutoFillBackground() can be used to ensure that + the background is filled using the QPalette::Button brush. +*/ + +/*! + Constructs a push button with no text and a \a parent. +*/ + +QPushButton::QPushButton(QWidget *parent) + : QAbstractButton(*new QPushButtonPrivate, parent) +{ + Q_D(QPushButton); + d->init(); +} + +/*! + Constructs a push button with the parent \a parent and the text \a + text. +*/ + +QPushButton::QPushButton(const QString &text, QWidget *parent) + : QAbstractButton(*new QPushButtonPrivate, parent) +{ + Q_D(QPushButton); + setText(text); + d->init(); +} + + +/*! + Constructs a push button with an \a icon and a \a text, and a \a parent. + + Note that you can also pass a QPixmap object as an icon (thanks to + the implicit type conversion provided by C++). + +*/ +QPushButton::QPushButton(const QIcon& icon, const QString &text, QWidget *parent) + : QAbstractButton(*new QPushButtonPrivate, parent) +{ + Q_D(QPushButton); + setText(text); + setIcon(icon); + d->init(); +} + +/*! \internal + */ +QPushButton::QPushButton(QPushButtonPrivate &dd, QWidget *parent) + : QAbstractButton(dd, parent) +{ + Q_D(QPushButton); + d->init(); +} + +/*! + Destroys the push button. +*/ +QPushButton::~QPushButton() +{ +} + +QDialog *QPushButtonPrivate::dialogParent() const +{ + Q_Q(const QPushButton); + const QWidget *p = q; + while (p && !p->isWindow()) { + p = p->parentWidget(); + if (const QDialog *dialog = qobject_cast<const QDialog *>(p)) + return const_cast<QDialog *>(dialog); + } + return 0; +} + +/*! + Initialize \a option with the values from this QPushButton. This method is useful + for subclasses when they need a QStyleOptionButton, but don't want to fill + in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QPushButton::initStyleOption(QStyleOptionButton *option) const +{ + if (!option) + return; + + Q_D(const QPushButton); + option->initFrom(this); + option->features = QStyleOptionButton::None; + if (d->flat) + option->features |= QStyleOptionButton::Flat; +#ifndef QT_NO_MENU + if (d->menu) + option->features |= QStyleOptionButton::HasMenu; +#endif + if (autoDefault() || d->defaultButton) + option->features |= QStyleOptionButton::AutoDefaultButton; + if (d->defaultButton) + option->features |= QStyleOptionButton::DefaultButton; + if (d->down || d->menuOpen) + option->state |= QStyle::State_Sunken; + if (d->checked) + option->state |= QStyle::State_On; + if (!d->flat && !d->down) + option->state |= QStyle::State_Raised; + option->text = d->text; + option->icon = d->icon; + option->iconSize = iconSize(); +} + +void QPushButton::setAutoDefault(bool enable) +{ + Q_D(QPushButton); + uint state = enable ? QPushButtonPrivate::On : QPushButtonPrivate::Off; + if (d->autoDefault != QPushButtonPrivate::Auto && d->autoDefault == state) + return; + d->autoDefault = state; + d->sizeHint = QSize(); + update(); + updateGeometry(); +} + +bool QPushButton::autoDefault() const +{ + Q_D(const QPushButton); + if(d->autoDefault == QPushButtonPrivate::Auto) + return ( d->dialogParent() != 0 ); + return d->autoDefault; +} + +void QPushButton::setDefault(bool enable) +{ + Q_D(QPushButton); + if (d->defaultButton == enable) + return; + d->defaultButton = enable; + if (d->defaultButton) { + if (QDialog *dlg = d->dialogParent()) + dlg->d_func()->setMainDefault(this); + } + update(); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::StateChanged); +#endif +} + +bool QPushButton::isDefault() const +{ + Q_D(const QPushButton); + return d->defaultButton; +} + +/*! + \reimp +*/ +QSize QPushButton::sizeHint() const +{ + Q_D(const QPushButton); + if (d->sizeHint.isValid()) + return d->sizeHint; + ensurePolished(); + + int w = 0, h = 0; + + QStyleOptionButton opt; + initStyleOption(&opt); + + // calculate contents size... +#ifndef QT_NO_ICON + + bool showButtonBoxIcons = qobject_cast<QDialogButtonBox*>(parentWidget()) + && style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons); + + if (!icon().isNull() || showButtonBoxIcons) { + int ih = opt.iconSize.height(); + int iw = opt.iconSize.width() + 4; + w += iw; + h = qMax(h, ih); + } +#endif + QString s(text()); + bool empty = s.isEmpty(); + if (empty) + s = QString::fromLatin1("XXXX"); + QFontMetrics fm = fontMetrics(); + QSize sz = fm.size(Qt::TextShowMnemonic, s); + if(!empty || !w) + w += sz.width(); + if(!empty || !h) + h = qMax(h, sz.height()); + opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height +#ifndef QT_NO_MENU + if (menu()) + w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); +#endif + d->sizeHint = (style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(w, h), this). + expandedTo(QApplication::globalStrut())); + return d->sizeHint; +} + +/*! + \reimp + */ +QSize QPushButton::minimumSizeHint() const +{ + return sizeHint(); +} + + +/*!\reimp +*/ +void QPushButton::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionButton option; + initStyleOption(&option); + p.drawControl(QStyle::CE_PushButton, option); +} + + +/*! \reimp */ +void QPushButton::keyPressEvent(QKeyEvent *e) +{ + Q_D(QPushButton); + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + if (autoDefault() || d->defaultButton) { + click(); + break; + } + // fall through + default: + QAbstractButton::keyPressEvent(e); + } +} + +/*! + \reimp +*/ +void QPushButton::focusInEvent(QFocusEvent *e) +{ + Q_D(QPushButton); + if (e->reason() != Qt::PopupFocusReason && autoDefault() && !d->defaultButton) { + d->defaultButton = true; + QDialog *dlg = qobject_cast<QDialog*>(window()); + if (dlg) + dlg->d_func()->setDefault(this); + } + QAbstractButton::focusInEvent(e); +} + +/*! + \reimp +*/ +void QPushButton::focusOutEvent(QFocusEvent *e) +{ + Q_D(QPushButton); + if (e->reason() != Qt::PopupFocusReason && autoDefault() && d->defaultButton) { + QDialog *dlg = qobject_cast<QDialog*>(window()); + if (dlg) + dlg->d_func()->setDefault(0); + else + d->defaultButton = false; + } + + QAbstractButton::focusOutEvent(e); +#ifndef QT_NO_MENU + if (d->menu && d->menu->isVisible()) // restore pressed status + setDown(true); +#endif +} + +#ifndef QT_NO_MENU +/*! + Associates the popup menu \a menu with this push button. This + turns the button into a menu button, which in some styles will + produce a small triangle to the right of the button's text. + + Ownership of the menu is \e not transferred to the push button. + + \table 100% + \row + \o \inlineimage plastique-pushbutton-menu.png Screenshot of a Plastique style push button with popup menu. + \o \inlineimage cleanlooks-pushbutton-menu.png Screenshot of a Cleanlooks style push button with popup menu. + \o Push buttons with popup menus shown in the \l{Plastique Style Widget Gallery}{Plastique widget style} + (left) and \l{Cleanlooks Style Widget Gallery}{Cleanlooks widget style} (right). + \endtable + + \sa menu() +*/ +void QPushButton::setMenu(QMenu* menu) +{ + Q_D(QPushButton); + if (menu == d->menu) + return; + + if (menu && !d->menu) { + disconnect(this, SIGNAL(pressed()), this, SLOT(_q_popupPressed())); + connect(this, SIGNAL(pressed()), this, SLOT(_q_popupPressed())); + } + if (d->menu) + removeAction(d->menu->menuAction()); + d->menu = menu; + if (d->menu) + addAction(d->menu->menuAction()); + + d->resetLayoutItemMargins(); + d->sizeHint = QSize(); + update(); + updateGeometry(); +} + +/*! + Returns the button's associated popup menu or 0 if no popup menu + has been set. + + \sa setMenu() +*/ +QMenu* QPushButton::menu() const +{ + Q_D(const QPushButton); + return d->menu; +} + +/*! + Shows (pops up) the associated popup menu. If there is no such + menu, this function does nothing. This function does not return + until the popup menu has been closed by the user. +*/ +void QPushButton::showMenu() +{ + Q_D(QPushButton); + if (!d || !d->menu) + return; + setDown(true); + d->_q_popupPressed(); +} + +void QPushButtonPrivate::_q_popupPressed() +{ + Q_Q(QPushButton); + if (!down || !menu) + return; + + menu->setNoReplayFor(q); + bool horizontal = true; +#if !defined(QT_NO_TOOLBAR) + QToolBar *tb = qobject_cast<QToolBar*>(q->parentWidget()); + if (tb && tb->orientation() == Qt::Vertical) + horizontal = false; +#endif + QWidgetItem item(q); + QRect rect = item.geometry(); + rect.setRect(rect.x() - q->x(), rect.y() - q->y(), rect.width(), rect.height()); + + QSize menuSize = menu->sizeHint(); + QPoint globalPos = q->mapToGlobal(rect.topLeft()); + int x = globalPos.x(); + int y = globalPos.y(); + if (horizontal) { + if (globalPos.y() + rect.height() + menuSize.height() <= qApp->desktop()->height()) { + y += rect.height(); + } else { + y -= menuSize.height(); + } + if (q->layoutDirection() == Qt::RightToLeft) + x += rect.width() - menuSize.width(); + } else { + if (globalPos.x() + rect.width() + menu->sizeHint().width() <= qApp->desktop()->width()) + x += rect.width(); + else + x -= menuSize.width(); + } + QPointer<QPushButton> guard(q); + + //Because of a delay in menu effects, we must keep track of the + //menu visibility to avoid flicker on button release + menuOpen = true; + menu->exec(QPoint(x, y)); + if (guard) { + menuOpen = false; + q->setDown(false); + } +} +#endif // QT_NO_MENU + +void QPushButtonPrivate::resetLayoutItemMargins() +{ + Q_Q(QPushButton); + QStyleOptionButton opt; + q->initStyleOption(&opt); + setLayoutItemMargins(QStyle::SE_PushButtonLayoutItem, &opt); +} + +void QPushButton::setFlat(bool flat) +{ + Q_D(QPushButton); + if (d->flat == flat) + return; + d->flat = flat; + d->resetLayoutItemMargins(); + d->sizeHint = QSize(); + update(); + updateGeometry(); +} + +bool QPushButton::isFlat() const +{ + Q_D(const QPushButton); + return d->flat; +} + +/*! \reimp */ +bool QPushButton::event(QEvent *e) +{ + Q_D(QPushButton); + if (e->type() == QEvent::ParentChange) { + if (QDialog *dialog = d->dialogParent()) { + if (d->defaultButton) + dialog->d_func()->setMainDefault(this); + } + } else if (e->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || e->type() == QEvent::MacSizeChange +#endif + ) { + d->resetLayoutItemMargins(); + updateGeometry(); + } + return QAbstractButton::event(e); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QPushButton::QPushButton(QWidget *parent, const char *name) + : QAbstractButton(*new QPushButtonPrivate, parent) +{ + Q_D(QPushButton); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QPushButton::QPushButton(const QString &text, QWidget *parent, const char *name) + : QAbstractButton(*new QPushButtonPrivate, parent) +{ + Q_D(QPushButton); + setObjectName(QString::fromAscii(name)); + setText(text); + d->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QPushButton::QPushButton(const QIcon& icon, const QString &text, QWidget *parent, const char *name) + : QAbstractButton(*new QPushButtonPrivate, parent) +{ + Q_D(QPushButton); + setObjectName(QString::fromAscii(name)); + setText(text); + setIcon(icon); + d->init(); +} +#endif + +/*! + \fn void QPushButton::openPopup() + + Use showMenu() instead. +*/ + +/*! + \fn bool QPushButton::isMenuButton() const + + Use menu() != 0 instead. +*/ + +/*! + \fn void QPushButton::setPopup(QMenu* popup) + + Use setMenu() instead. +*/ + +/*! + \fn QMenu* QPushButton::popup() const + + Use menu() instead. +*/ + +QT_END_NAMESPACE + +#include "moc_qpushbutton.cpp" diff --git a/src/gui/widgets/qpushbutton.h b/src/gui/widgets/qpushbutton.h new file mode 100644 index 0000000..3be304b --- /dev/null +++ b/src/gui/widgets/qpushbutton.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPUSHBUTTON_H +#define QPUSHBUTTON_H + +#include <QtGui/qabstractbutton.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPushButtonPrivate; +class QMenu; +class QStyleOptionButton; + +class Q_GUI_EXPORT QPushButton : public QAbstractButton +{ + Q_OBJECT + + Q_PROPERTY(bool autoDefault READ autoDefault WRITE setAutoDefault) + Q_PROPERTY(bool default READ isDefault WRITE setDefault) + Q_PROPERTY(bool flat READ isFlat WRITE setFlat) + +public: + explicit QPushButton(QWidget *parent=0); + explicit QPushButton(const QString &text, QWidget *parent=0); + QPushButton(const QIcon& icon, const QString &text, QWidget *parent=0); + ~QPushButton(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + bool autoDefault() const; + void setAutoDefault(bool); + bool isDefault() const; + void setDefault(bool); + +#ifndef QT_NO_MENU + void setMenu(QMenu* menu); + QMenu* menu() const; +#endif + + void setFlat(bool); + bool isFlat() const; + +public Q_SLOTS: +#ifndef QT_NO_MENU + void showMenu(); +#endif + +protected: + bool event(QEvent *e); + void paintEvent(QPaintEvent *); + void keyPressEvent(QKeyEvent *); + void focusInEvent(QFocusEvent *); + void focusOutEvent(QFocusEvent *); + void initStyleOption(QStyleOptionButton *option) const; + QPushButton(QPushButtonPrivate &dd, QWidget* parent = 0); + +public: +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QPushButton(QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QPushButton(const QString &text, QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QPushButton(const QIcon& icon, const QString &text, QWidget *parent, const char* name); + inline QT3_SUPPORT void openPopup() { showMenu(); } + inline QT3_SUPPORT bool isMenuButton() const { return menu() != 0; } + inline QT3_SUPPORT void setPopup(QMenu* popup) {setMenu(popup); } + inline QT3_SUPPORT QMenu* popup() const { return menu(); } +#endif + +private: + Q_DISABLE_COPY(QPushButton) + Q_DECLARE_PRIVATE(QPushButton) +#ifndef QT_NO_MENU + Q_PRIVATE_SLOT(d_func(), void _q_popupPressed()) +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPUSHBUTTON_H diff --git a/src/gui/widgets/qpushbutton_p.h b/src/gui/widgets/qpushbutton_p.h new file mode 100644 index 0000000..fc8f567 --- /dev/null +++ b/src/gui/widgets/qpushbutton_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qabstractbutton_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. +// + +QT_BEGIN_NAMESPACE + +class QDialog; +class QPushButton; + +class QPushButtonPrivate : public QAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QPushButton) +public: + enum AutoDefaultValue { Off = 0, On = 1, Auto = 2 }; + + QPushButtonPrivate() + : QAbstractButtonPrivate(QSizePolicy::PushButton), autoDefault(Auto), + defaultButton(false), flat(false), menuOpen(false) {} + + inline void init() { resetLayoutItemMargins(); } + void resetLayoutItemMargins(); + void _q_popupPressed(); + QDialog *dialogParent() const; + + QPointer<QMenu> menu; + uint autoDefault : 2; + uint defaultButton : 1; + uint flat : 1; + uint menuOpen : 1; +}; + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qradiobutton.cpp b/src/gui/widgets/qradiobutton.cpp new file mode 100644 index 0000000..da23150 --- /dev/null +++ b/src/gui/widgets/qradiobutton.cpp @@ -0,0 +1,288 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qradiobutton.h" +#include "qapplication.h" +#include "qbitmap.h" +#include "qbuttongroup.h" +#include "qstylepainter.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qevent.h" + +#include "private/qabstractbutton_p.h" + +QT_BEGIN_NAMESPACE + +class QRadioButtonPrivate : public QAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QRadioButton) + +public: + QRadioButtonPrivate() : QAbstractButtonPrivate(QSizePolicy::RadioButton), hovering(true) {} + void init(); + uint hovering : 1; +}; + +/* + Initializes the radio button. +*/ +void QRadioButtonPrivate::init() +{ + Q_Q(QRadioButton); + q->setCheckable(true); + q->setAutoExclusive(true); + q->setMouseTracking(true); + q->setForegroundRole(QPalette::WindowText); + setLayoutItemMargins(QStyle::SE_RadioButtonLayoutItem); +} + +/*! + \class QRadioButton + \brief The QRadioButton widget provides a radio button with a text label. + + \ingroup basicwidgets + \mainclass + + A QRadioButton is an option button that can be switched on (checked) or + off (unchecked). Radio buttons typically present the user with a "one + of many" choice. In a group of radio buttons only one radio button at + a time can be checked; if the user selects another button, the + previously selected button is switched off. + + Radio buttons are autoExclusive by default. If auto-exclusive is + enabled, radio buttons that belong to the same parent widget + behave as if they were part of the same exclusive button group. If + you need multiple exclusive button groups for radio buttons that + belong to the same parent widget, put them into a QButtonGroup. + + Whenever a button is switched on or off it emits the toggled() signal. + Connect to this signal if you want to trigger an action each time the + button changes state. Use isChecked() to see if a particular button is + selected. + + Just like QPushButton, a radio button displays text, and + optionally a small icon. The icon is set with setIcon(). The text + can be set in the constructor or with setText(). A shortcut key + can be specified by preceding the preferred character with an + ampersand in the text. For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qradiobutton.cpp 0 + + In this example the shortcut is \e{Alt+c}. See the \l + {QShortcut#mnemonic}{QShortcut} documentation for details (to + display an actual ampersand, use '&&'). + + Important inherited members: text(), setText(), text(), + setDown(), isDown(), autoRepeat(), group(), setAutoRepeat(), + toggle(), pressed(), released(), clicked(), and toggled(). + + \table 100% + \row \o \inlineimage plastique-radiobutton.png Screenshot of a Plastique radio button + \o A radio button shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \row \o \inlineimage windows-radiobutton.png Screenshot of a Windows XP radio button + \o A radio button shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage macintosh-radiobutton.png Screenshot of a Macintosh radio button + \o A radio button shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \endtable + + \sa QPushButton, QToolButton, QCheckBox, {fowler}{GUI Design Handbook: Radio Button}, + {Group Box Example} +*/ + + +/*! + Constructs a radio button with the given \a parent, but with no text or + pixmap. + + The \a parent argument is passed on to the QAbstractButton constructor. +*/ + +QRadioButton::QRadioButton(QWidget *parent) + : QAbstractButton(*new QRadioButtonPrivate, parent) +{ + Q_D(QRadioButton); + d->init(); +} + +/*! + Constructs a radio button with the given \a parent and a \a text string. + + The \a parent argument is passed on to the QAbstractButton constructor. +*/ + +QRadioButton::QRadioButton(const QString &text, QWidget *parent) + : QAbstractButton(*new QRadioButtonPrivate, parent) +{ + Q_D(QRadioButton); + d->init(); + setText(text); +} + +/*! + Initialize \a option with the values from this QRadioButton. This method is useful + for subclasses when they need a QStyleOptionButton, but don't want to fill + in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QRadioButton::initStyleOption(QStyleOptionButton *option) const +{ + if (!option) + return; + Q_D(const QRadioButton); + option->initFrom(this); + option->text = d->text; + option->icon = d->icon; + option->iconSize = iconSize(); + if (d->down) + option->state |= QStyle::State_Sunken; + option->state |= (d->checked) ? QStyle::State_On : QStyle::State_Off; + if (testAttribute(Qt::WA_Hover) && underMouse()) { + if (d->hovering) + option->state |= QStyle::State_MouseOver; + else + option->state &= ~QStyle::State_MouseOver; + } +} + +/*! + \reimp +*/ +QSize QRadioButton::sizeHint() const +{ + Q_D(const QRadioButton); + if (d->sizeHint.isValid()) + return d->sizeHint; + ensurePolished(); + QStyleOptionButton opt; + initStyleOption(&opt); + QSize sz = style()->itemTextRect(fontMetrics(), QRect(0, 0, 1, 1), Qt::TextShowMnemonic, + false, text()).size(); + if (!opt.icon.isNull()) + sz = QSize(sz.width() + opt.iconSize.width() + 4, qMax(sz.height(), opt.iconSize.height())); + d->sizeHint = (style()->sizeFromContents(QStyle::CT_RadioButton, &opt, sz, this). + expandedTo(QApplication::globalStrut())); + return d->sizeHint; +} + +/*! + \reimp +*/ +bool QRadioButton::hitButton(const QPoint &pos) const +{ + QStyleOptionButton opt; + initStyleOption(&opt); + return style()->subElementRect(QStyle::SE_RadioButtonClickRect, &opt, this).contains(pos); +} + +/*! + \reimp +*/ +void QRadioButton::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QRadioButton); + if (testAttribute(Qt::WA_Hover)) { + bool hit = false; + if (underMouse()) + hit = hitButton(e->pos()); + + if (hit != d->hovering) { + update(); + d->hovering = hit; + } + } + + QAbstractButton::mouseMoveEvent(e); +} + +/*!\reimp + */ +void QRadioButton::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionButton opt; + initStyleOption(&opt); + p.drawControl(QStyle::CE_RadioButton, opt); +} + +/*! \reimp */ +bool QRadioButton::event(QEvent *e) +{ + Q_D(QRadioButton); + if (e->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || e->type() == QEvent::MacSizeChange +#endif + ) + d->setLayoutItemMargins(QStyle::SE_RadioButtonLayoutItem); + return QAbstractButton::event(e); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QRadioButton::QRadioButton(QWidget *parent, const char* name) + : QAbstractButton(*new QRadioButtonPrivate, parent) +{ + Q_D(QRadioButton); + d->init(); + setObjectName(QString::fromAscii(name)); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QRadioButton::QRadioButton(const QString &text, QWidget *parent, const char* name) + : QAbstractButton(*new QRadioButtonPrivate, parent) +{ + Q_D(QRadioButton); + d->init(); + setObjectName(QString::fromAscii(name)); + setText(text); +} + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qradiobutton.h b/src/gui/widgets/qradiobutton.h new file mode 100644 index 0000000..158998d --- /dev/null +++ b/src/gui/widgets/qradiobutton.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRADIOBUTTON_H +#define QRADIOBUTTON_H + +#include <QtGui/qabstractbutton.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QRadioButtonPrivate; +class QStyleOptionButton; + +class Q_GUI_EXPORT QRadioButton : public QAbstractButton +{ + Q_OBJECT + +public: + explicit QRadioButton(QWidget *parent=0); + explicit QRadioButton(const QString &text, QWidget *parent=0); + + QSize sizeHint() const; + +protected: + bool event(QEvent *e); + bool hitButton(const QPoint &) const; + void paintEvent(QPaintEvent *); + void mouseMoveEvent(QMouseEvent *); + void initStyleOption(QStyleOptionButton *button) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QRadioButton(QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QRadioButton(const QString &text, QWidget *parent, const char* name); +#endif + +private: + Q_DECLARE_PRIVATE(QRadioButton) + Q_DISABLE_COPY(QRadioButton) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QRADIOBUTTON_H diff --git a/src/gui/widgets/qrubberband.cpp b/src/gui/widgets/qrubberband.cpp new file mode 100644 index 0000000..a394f4a --- /dev/null +++ b/src/gui/widgets/qrubberband.cpp @@ -0,0 +1,339 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbitmap.h" +#include "qevent.h" +#include "qstylepainter.h" +#include "qrubberband.h" +#include "qtimer.h" + +#ifndef QT_NO_RUBBERBAND + +#include "qstyle.h" +#include "qstyleoption.h" +#ifdef Q_WS_MAC +# include <private/qt_mac_p.h> +# include <private/qt_cocoa_helpers_mac_p.h> +#endif + +#include <qdebug.h> + +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +//### a rubberband window type would be a more elegant solution +#define RUBBERBAND_WINDOW_TYPE Qt::ToolTip + +class QRubberBandPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QRubberBand) +public: + QRect rect; + QRubberBand::Shape shape; + QRegion clipping; + void updateMask(); +}; + +/*! + Initialize \a option with the values from this QRubberBand. This method + is useful for subclasses when they need a QStyleOptionRubberBand, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QRubberBand::initStyleOption(QStyleOptionRubberBand *option) const +{ + if (!option) + return; + option->initFrom(this); + option->shape = d_func()->shape; +#ifndef Q_WS_MAC + option->opaque = true; +#else + option->opaque = windowFlags() & RUBBERBAND_WINDOW_TYPE; +#endif +} + +/*! + \class QRubberBand + \brief The QRubberBand class provides a rectangle or line that can + indicate a selection or a boundary. + + \ingroup misc + \mainclass + + A rubber band is often used to show a new bounding area (as in a + QSplitter or a QDockWidget that is undocking). Historically this has + been implemented using a QPainter and XOR, but this approach + doesn't always work properly since rendering can happen in the + window below the rubber band, but before the rubber band has been + "erased". + + You can create a QRubberBand whenever you need to render a rubber band + around a given area (or to represent a single line), then call + setGeometry(), move() or resize() to position and size it. A common + pattern is to do this in conjunction with mouse events. For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qrubberband.cpp 0 + + If you pass a parent to QRubberBand's constructor, the rubber band will + display only inside its parent, but stays on top of other child widgets. + If no parent is passed, QRubberBand will act as a top-level widget. + + Call show() to make the rubber band visible; also when the + rubber band is not a top-level. Hiding or destroying + the widget will make the rubber band disappear. The rubber band + can be a \l Rectangle or a \l Line (vertical or horizontal), + depending on the shape() it was given when constructed. +*/ + +// ### DOC: How about some nice convenience constructors? +//QRubberBand::QRubberBand(QRubberBand::Type t, const QRect &rect, QWidget *p) +//QRubberBand::QRubberBand(QRubberBand::Type t, int x, int y, int w, int h, QWidget *p) + +/*! + Constructs a rubber band of shape \a s, with parent \a p. + + By default a rectangular rubber band (\a s is \c Rectangle) will + use a mask, so that a small border of the rectangle is all + that is visible. Some styles (e.g., native Mac OS X) will + change this and call QWidget::setWindowOpacity() to make a + semi-transparent filled selection rectangle. +*/ +QRubberBand::QRubberBand(Shape s, QWidget *p) + : QWidget(*new QRubberBandPrivate, p, (p && p->windowType() != Qt::Desktop) ? Qt::Widget : RUBBERBAND_WINDOW_TYPE) +{ + Q_D(QRubberBand); + d->shape = s; + setAttribute(Qt::WA_TransparentForMouseEvents); +#ifndef Q_WS_WIN + setAttribute(Qt::WA_NoSystemBackground); +#endif //Q_WS_WIN + setAttribute(Qt::WA_WState_ExplicitShowHide); + setVisible(false); +#ifdef Q_WS_MAC + if (isWindow()) { + createWinId(); + extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp + macWindowSetHasShadow(qt_mac_window_for(this), false); + } +#endif +} + +/*! + Destructor. +*/ +QRubberBand::~QRubberBand() +{ +} + +/*! + \enum QRubberBand::Shape + + This enum specifies what shape a QRubberBand should have. This is + a drawing hint that is passed down to the style system, and can be + interpreted by each QStyle. + + \value Line A QRubberBand can represent a vertical or horizontal + line. Geometry is still given in rect() and the line + will fill the given geometry on most styles. + + \value Rectangle A QRubberBand can represent a rectangle. Some + styles will interpret this as a filled (often + semi-transparent) rectangle, or a rectangular + outline. +*/ + +/*! + Returns the shape of this rubber band. The shape can only be set + upon construction. +*/ +QRubberBand::Shape QRubberBand::shape() const +{ + Q_D(const QRubberBand); + return d->shape; +} + +/*! + \internal +*/ +void QRubberBandPrivate::updateMask() +{ + Q_Q(QRubberBand); + QStyleHintReturnMask mask; + QStyleOptionRubberBand opt; + q->initStyleOption(&opt); + if (q->style()->styleHint(QStyle::SH_RubberBand_Mask, &opt, q, &mask)) { + q->setMask(mask.region); + } else { + q->clearMask(); + } +} + +/*! + \reimp +*/ +void QRubberBand::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + QStyleOptionRubberBand option; + initStyleOption(&option); + painter.drawControl(QStyle::CE_RubberBand, option); +} + +/*! + \reimp +*/ +void QRubberBand::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::ParentChange: + if (parent()) { + setWindowFlags(windowFlags() & ~RUBBERBAND_WINDOW_TYPE); + } else { + setWindowFlags(windowFlags() | RUBBERBAND_WINDOW_TYPE); + } + break; + default: + break; + } + + if (e->type() == QEvent::ZOrderChange) + raise(); +} + +/*! + \reimp +*/ +void QRubberBand::showEvent(QShowEvent *e) +{ + raise(); + e->ignore(); +} + +/*! + \reimp +*/ +void QRubberBand::resizeEvent(QResizeEvent *) +{ + Q_D(QRubberBand); + d->updateMask(); +} + +/*! + \reimp +*/ +void QRubberBand::moveEvent(QMoveEvent *) +{ + Q_D(QRubberBand); + d->updateMask(); +} + +/*! + \fn void QRubberBand::move(const QPoint &p); + + \overload + + Moves the rubberband to point \a p. + + \sa resize() +*/ + +/*! + \fn void QRubberBand::move(int x, int y); + + Moves the rubberband to point (\a x, \a y). + + \sa resize() +*/ + +/*! + \fn void QRubberBand::resize(const QSize &size); + + \overload + + Resizes the rubberband so that its new size is \a size. + + \sa move() +*/ + +/*! + \fn void QRubberBand::resize(int width, int height); + + Resizes the rubberband so that its width is \a width, and its + height is \a height. + + \sa move() +*/ + +/*! + \fn void QRubberBand::setGeometry(const QRect &rect) + + Sets the geometry of the rubber band to \a rect, specified in the coordinate system + of its parent widget. + + \sa QWidget::geometry +*/ +void QRubberBand::setGeometry(const QRect &geom) +{ + QWidget::setGeometry(geom); +} + +/*! + \fn void QRubberBand::setGeometry(int x, int y, int width, int height) + \overload + + Sets the geometry of the rubberband to the rectangle whose top-left corner lies at + the point (\a x, \a y), and with dimensions specified by \a width and \a height. + The geometry is specified in the parent widget's coordinate system. +*/ + +/*! \reimp */ +bool QRubberBand::event(QEvent *e) +{ + return QWidget::event(e); +} + +QT_END_NAMESPACE + +#endif // QT_NO_RUBBERBAND diff --git a/src/gui/widgets/qrubberband.h b/src/gui/widgets/qrubberband.h new file mode 100644 index 0000000..f4c3ffa --- /dev/null +++ b/src/gui/widgets/qrubberband.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRUBBERBAND_H +#define QRUBBERBAND_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_RUBBERBAND + +class QRubberBandPrivate; +class QStyleOptionRubberBand; + +class Q_GUI_EXPORT QRubberBand : public QWidget +{ + Q_OBJECT + +public: + enum Shape { Line, Rectangle }; + explicit QRubberBand(Shape, QWidget * =0); + ~QRubberBand(); + + Shape shape() const; + + void setGeometry(const QRect &r); + + inline void setGeometry(int x, int y, int w, int h); + inline void move(int x, int y); + inline void move(const QPoint &p) + { move(p.x(), p.y()); } + inline void resize(int w, int h) + { setGeometry(geometry().x(), geometry().y(), w, h); } + inline void resize(const QSize &s) + { resize(s.width(), s.height()); } + +protected: + bool event(QEvent *e); + void paintEvent(QPaintEvent *); + void changeEvent(QEvent *); + void showEvent(QShowEvent *); + void resizeEvent(QResizeEvent *); + void moveEvent(QMoveEvent *); + void initStyleOption(QStyleOptionRubberBand *option) const; + +private: + Q_DECLARE_PRIVATE(QRubberBand) +}; + +inline void QRubberBand::setGeometry(int ax, int ay, int aw, int ah) +{ setGeometry(QRect(ax, ay, aw, ah)); } +inline void QRubberBand::move(int ax, int ay) +{ setGeometry(ax, ay, width(), height()); } + +#endif // QT_NO_RUBBERBAND + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QRUBBERBAND_H diff --git a/src/gui/widgets/qscrollarea.cpp b/src/gui/widgets/qscrollarea.cpp new file mode 100644 index 0000000..6aca7d3 --- /dev/null +++ b/src/gui/widgets/qscrollarea.cpp @@ -0,0 +1,522 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscrollarea.h" +#include "private/qscrollarea_p.h" + +#ifndef QT_NO_SCROLLAREA + +#include "qscrollbar.h" +#include "qlayout.h" +#include "qstyle.h" +#include "qapplication.h" +#include "qvariant.h" +#include "qdebug.h" +#include "private/qlayoutengine_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QScrollArea + + \brief The QScrollArea class provides a scrolling view onto + another widget. + + \ingroup basicwidgets + \mainclass + + A scroll area is used to display the contents of a child widget + within a frame. If the widget exceeds the size of the frame, the + view can provide scroll bars so that the entire area of the child + widget can be viewed. The child widget must be specified with + setWidget(). For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qscrollarea.cpp 0 + + The code above creates a scroll area (shown in the images below) + containing an image label. When scaling the image, the scroll area + can provide the necessary scroll bars: + + \table + \row + \o \inlineimage qscrollarea-noscrollbars.png + \o \inlineimage qscrollarea-onescrollbar.png + \o \inlineimage qscrollarea-twoscrollbars.png + \endtable + + The scroll bars appearance depends on the currently set \l + {Qt::ScrollBarPolicy}{scroll bar policies}. You can control the + appearance of the scroll bars using the inherited functionality + from QAbstractScrollArea. + + For example, you can set the + QAbstractScrollArea::horizontalScrollBarPolicy and + QAbstractScrollArea::verticalScrollBarPolicy properties. Or if you + want the scroll bars to adjust dynamically when the contents of + the scroll area changes, you can use the \l + {QAbstractScrollArea::horizontalScrollBar()}{horizontalScrollBar()} + and \l + {QAbstractScrollArea::verticalScrollBar()}{verticalScrollBar()} + functions (which enable you to access the scroll bars) and set the + scroll bars' values whenever the scroll area's contents change, + using the QScrollBar::setValue() function. + + You can retrieve the child widget using the widget() function. The + view can be made to be resizable with the setWidgetResizable() + function. The alignment of the widget can be specified with + setAlignment(). + + Two convenience functions ensureVisible() and + ensureWidgetVisible() ensure a certain region of the contents is + visible inside the viewport, by scrolling the contents if + necessary. + + \section1 Size Hints and Layouts + + When using a scroll area to display the contents of a custom + widget, it is important to ensure that the + \l{QWidget::sizeHint}{size hint} of the child widget is set to a + suitable value. If a standard QWidget is used for the child + widget, it may be necessary to call QWidget::setMinimumSize() to + ensure that the contents of the widget are shown correctly within + the scroll area. + + If a scroll area is used to display the contents of a widget that + contains child widgets arranged in a layout, it is important to + realise that the size policy of the layout will also determine the + size of the widget. This is especially useful to know if you intend + to dynamically change the contents of the layout. In such cases, + setting the layout's \l{QLayout::sizeConstraint}{size constraint} + property to one which provides constraints on the minimum and/or + maximum size of the layout (e.g., QLayout::SetMinAndMaxSize) will + cause the size of the the scroll area to be updated whenever the + contents of the layout changes. + + For a complete example using the QScrollArea class, see the \l + {widgets/imageviewer}{Image Viewer} example. The example shows how + to combine QLabel and QScrollArea to display an image. + + \sa QAbstractScrollArea, QScrollBar, {Image Viewer Example} +*/ + + +/*! + Constructs an empty scroll area with the given \a parent. + + \sa setWidget() +*/ +QScrollArea::QScrollArea(QWidget *parent) + : QAbstractScrollArea(*new QScrollAreaPrivate,parent) +{ + Q_D(QScrollArea); + d->viewport->setBackgroundRole(QPalette::NoRole); + d->vbar->setSingleStep(20); + d->hbar->setSingleStep(20); + d->layoutChildren(); +} + +/*! + \internal +*/ +QScrollArea::QScrollArea(QScrollAreaPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + Q_D(QScrollArea); + d->viewport->setBackgroundRole(QPalette::NoRole); + d->vbar->setSingleStep(20); + d->hbar->setSingleStep(20); + d->layoutChildren(); +} + +/*! + Destroys the scroll area and its child widget. + + \sa setWidget() +*/ +QScrollArea::~QScrollArea() +{ +} + +void QScrollAreaPrivate::updateWidgetPosition() +{ + Q_Q(QScrollArea); + Qt::LayoutDirection dir = q->layoutDirection(); + QRect scrolled = QStyle::visualRect(dir, viewport->rect(), QRect(QPoint(-hbar->value(), -vbar->value()), widget->size())); + QRect aligned = QStyle::alignedRect(dir, alignment, widget->size(), viewport->rect()); + widget->move(widget->width() < viewport->width() ? aligned.x() : scrolled.x(), + widget->height() < viewport->height() ? aligned.y() : scrolled.y()); +} + +void QScrollAreaPrivate::updateScrollBars() +{ + Q_Q(QScrollArea); + if (!widget) + return; + QSize p = viewport->size(); + QSize m = q->maximumViewportSize(); + + QSize min = qSmartMinSize(widget); + QSize max = qSmartMaxSize(widget); + + if (resizable) { + if ((widget->layout() ? widget->layout()->hasHeightForWidth() : widget->sizePolicy().hasHeightForWidth())) { + QSize p_hfw = p.expandedTo(min).boundedTo(max); + int h = widget->heightForWidth( p_hfw.width() ); + min = QSize(p_hfw.width(), qMax(p_hfw.height(), h)); + } + } + + if ((resizable && m.expandedTo(min) == m && m.boundedTo(max) == m) + || (!resizable && m.expandedTo(widget->size()) == m)) + p = m; // no scroll bars needed + + if (resizable) + widget->resize(p.expandedTo(min).boundedTo(max)); + QSize v = widget->size(); + + hbar->setRange(0, v.width() - p.width()); + hbar->setPageStep(p.width()); + vbar->setRange(0, v.height() - p.height()); + vbar->setPageStep(p.height()); + updateWidgetPosition(); + +} + +/*! + Returns the scroll area's widget, or 0 if there is none. + + \sa setWidget() +*/ + +QWidget *QScrollArea::widget() const +{ + Q_D(const QScrollArea); + return d->widget; +} + +/*! + \fn void QScrollArea::setWidget(QWidget *widget) + + Sets the scroll area's \a widget. + + The \a widget becomes a child of the scroll area, and will be + destroyed when the scroll area is deleted or when a new widget is + set. + + The widget's \l{QWidget::setAutoFillBackground()}{autoFillBackground} + property will be set to \c{true}. + + If the scroll area is visible when the \a widget is + added, you must \l{QWidget::}{show()} it explicitly. + + Note that You must add the layout of \a widget before you call + this function; if you add it later, the \a widget will not be + visible - regardless of when you \l{QWidget::}{show()} the scroll + area. In this case, you can also not \l{QWidget::}{show()} the \a + widget later. + + \sa widget() +*/ +void QScrollArea::setWidget(QWidget *widget) +{ + Q_D(QScrollArea); + if (widget == d->widget || !widget) + return; + + delete d->widget; + d->widget = 0; + d->hbar->setValue(0); + d->vbar->setValue(0); + if (widget->parentWidget() != d->viewport) + widget->setParent(d->viewport); + if (!widget->testAttribute(Qt::WA_Resized)) + widget->resize(widget->sizeHint()); + d->widget = widget; + d->widget->setAutoFillBackground(true); + widget->installEventFilter(this); + d->widgetSize = QSize(); + d->updateScrollBars(); + d->widget->show(); + +} + +/*! + Removes the scroll area's widget, and passes ownership of the + widget to the caller. + + \sa widget() + */ +QWidget *QScrollArea::takeWidget() +{ + Q_D(QScrollArea); + QWidget *w = d->widget; + d->widget = 0; + if (w) + w->setParent(0); + return w; +} + +/*! + \reimp + */ +bool QScrollArea::event(QEvent *e) +{ + Q_D(QScrollArea); + if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) { + d->updateScrollBars(); + } +#ifdef QT_KEYPAD_NAVIGATION + else if (QApplication::keypadNavigationEnabled()) { + if (e->type() == QEvent::Show) + QApplication::instance()->installEventFilter(this); + else if (e->type() == QEvent::Hide) + QApplication::instance()->removeEventFilter(this); + } +#endif + return QAbstractScrollArea::event(e); +} + + +/*! + \reimp + */ +bool QScrollArea::eventFilter(QObject *o, QEvent *e) +{ + Q_D(QScrollArea); +#ifdef QT_KEYPAD_NAVIGATION + if (d->widget && o != d->widget && e->type() == QEvent::FocusIn + && QApplication::keypadNavigationEnabled()) { + if (o->isWidgetType()) + ensureWidgetVisible(static_cast<QWidget *>(o)); + } +#endif + if (o == d->widget && e->type() == QEvent::Resize) + d->updateScrollBars(); + + return false; +} + +/*! + \reimp + */ +void QScrollArea::resizeEvent(QResizeEvent *) +{ + Q_D(QScrollArea); + d->updateScrollBars(); + +} + + +/*!\reimp + */ +void QScrollArea::scrollContentsBy(int, int) +{ + Q_D(QScrollArea); + if (!d->widget) + return; + d->updateWidgetPosition(); +} + + +/*! + \property QScrollArea::widgetResizable + \brief whether the scroll area should resize the view widget + + If this property is set to false (the default), the scroll area + honors the size of its widget. Regardless of this property, you + can programmatically resize the widget using widget()->resize(), + and the scroll area will automatically adjust itself to the new + size. + + If this property is set to true, the scroll area will + automatically resize the widget in order to avoid scroll bars + where they can be avoided, or to take advantage of extra space. +*/ +bool QScrollArea::widgetResizable() const +{ + Q_D(const QScrollArea); + return d->resizable; +} + +void QScrollArea::setWidgetResizable(bool resizable) +{ + Q_D(QScrollArea); + d->resizable = resizable; + updateGeometry(); + d->updateScrollBars(); +} + +/*! + \reimp + */ +QSize QScrollArea::sizeHint() const +{ + Q_D(const QScrollArea); + int f = 2 * d->frameWidth; + QSize sz(f, f); + int h = fontMetrics().height(); + if (d->widget) { + if (!d->widgetSize.isValid()) + d->widgetSize = d->resizable ? d->widget->sizeHint() : d->widget->size(); + sz += d->widgetSize; + } else { + sz += QSize(12 * h, 8 * h); + } + if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) + sz.setWidth(sz.width() + d->vbar->sizeHint().width()); + if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) + sz.setHeight(sz.height() + d->hbar->sizeHint().height()); + return sz.boundedTo(QSize(36 * h, 24 * h)); +} + + + +/*! + \reimp + */ +bool QScrollArea::focusNextPrevChild(bool next) +{ + if (QWidget::focusNextPrevChild(next)) { + if (QWidget *fw = focusWidget()) + ensureWidgetVisible(fw); + return true; + } + return false; +} + +/*! + Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible + inside the region of the viewport with margins specified in pixels by \a xmargin and + \a ymargin. If the specified point cannot be reached, the contents are scrolled to + the nearest valid position. The default value for both margins is 50 pixels. +*/ +void QScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin) +{ + Q_D(QScrollArea); + + int logicalX = QStyle::visualPos(layoutDirection(), d->viewport->rect(), QPoint(x, y)).x(); + + if (logicalX - xmargin < d->hbar->value()) { + d->hbar->setValue(qMax(0, logicalX - xmargin)); + } else if (logicalX > d->hbar->value() + d->viewport->width() - xmargin) { + d->hbar->setValue(qMin(logicalX - d->viewport->width() + xmargin, d->hbar->maximum())); + } + + if (y - ymargin < d->vbar->value()) { + d->vbar->setValue(qMax(0, y - ymargin)); + } else if (y > d->vbar->value() + d->viewport->height() - ymargin) { + d->vbar->setValue(qMin(y - d->viewport->height() + ymargin, d->vbar->maximum())); + } +} + +/*! + \since 4.2 + + Scrolls the contents of the scroll area so that the \a childWidget + of QScrollArea::widget() is visible inside the viewport with + margins specified in pixels by \a xmargin and \a ymargin. If the + specified point cannot be reached, the contents are scrolled to + the nearest valid position. The default value for both margins is + 50 pixels. + +*/ +void QScrollArea::ensureWidgetVisible(QWidget *childWidget, int xmargin, int ymargin) +{ + Q_D(QScrollArea); + + if (!d->widget->isAncestorOf(childWidget)) + return; + + const QRect microFocus = childWidget->inputMethodQuery(Qt::ImMicroFocus).toRect(); + const QRect defaultMicroFocus = + childWidget->QWidget::inputMethodQuery(Qt::ImMicroFocus).toRect(); + QRect focusRect = (microFocus != defaultMicroFocus) + ? QRect(childWidget->mapTo(d->widget, microFocus.topLeft()), microFocus.size()) + : QRect(childWidget->mapTo(d->widget, QPoint(0,0)), childWidget->size()); + const QRect visibleRect(-d->widget->pos(), d->viewport->size()); + + if (visibleRect.contains(focusRect)) + return; + + focusRect.adjust(-xmargin, -ymargin, xmargin, ymargin); + + if (focusRect.width() > visibleRect.width()) + d->hbar->setValue(focusRect.center().x() - d->viewport->width() / 2); + else if (focusRect.right() > visibleRect.right()) + d->hbar->setValue(focusRect.right() - d->viewport->width()); + else + d->hbar->setValue(focusRect.left()); + + if (focusRect.height() > visibleRect.height()) + d->vbar->setValue(focusRect.center().y() - d->viewport->height() / 2); + else if (focusRect.bottom() > visibleRect.bottom()) + d->vbar->setValue(focusRect.bottom() - d->viewport->height()); + else + d->vbar->setValue(focusRect.top()); +} + + +/*! + \property QScrollArea::alignment + \brief the alignment of the scroll area's widget + \since 4.2 + + By default, the widget stays rooted to the top-left corner of the + scroll area. +*/ + +void QScrollArea::setAlignment(Qt::Alignment alignment) +{ + Q_D(QScrollArea); + d->alignment = alignment; + if (d->widget) + d->updateWidgetPosition(); +} + +Qt::Alignment QScrollArea::alignment() const +{ + Q_D(const QScrollArea); + return d->alignment; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SCROLLAREA diff --git a/src/gui/widgets/qscrollarea.h b/src/gui/widgets/qscrollarea.h new file mode 100644 index 0000000..f888aef --- /dev/null +++ b/src/gui/widgets/qscrollarea.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLAREA_H +#define QSCROLLAREA_H + +#include <QtGui/qabstractscrollarea.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SCROLLAREA + +class QScrollAreaPrivate; + +class Q_GUI_EXPORT QScrollArea : public QAbstractScrollArea +{ + Q_OBJECT + Q_PROPERTY(bool widgetResizable READ widgetResizable WRITE setWidgetResizable) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + +public: + explicit QScrollArea(QWidget* parent=0); + ~QScrollArea(); + + QWidget *widget() const; + void setWidget(QWidget *widget); + QWidget *takeWidget(); + + bool widgetResizable() const; + void setWidgetResizable(bool resizable); + + QSize sizeHint() const; + bool focusNextPrevChild(bool next); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment); + + void ensureVisible(int x, int y, int xmargin = 50, int ymargin = 50); + void ensureWidgetVisible(QWidget *childWidget, int xmargin = 50, int ymargin = 50); + +protected: + QScrollArea(QScrollAreaPrivate &dd, QWidget *parent = 0); + bool event(QEvent *); + bool eventFilter(QObject *, QEvent *); + void resizeEvent(QResizeEvent *); + void scrollContentsBy(int dx, int dy); + +private: + Q_DECLARE_PRIVATE(QScrollArea) + Q_DISABLE_COPY(QScrollArea) +}; + +#endif // QT_NO_SCROLLAREA + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCROLLAREA_H diff --git a/src/gui/widgets/qscrollarea_p.h b/src/gui/widgets/qscrollarea_p.h new file mode 100644 index 0000000..bdb7ad7 --- /dev/null +++ b/src/gui/widgets/qscrollarea_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLAREA_P_H +#define QSCROLLAREA_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_SCROLLAREA + +#include "private/qabstractscrollarea_p.h" +#include <QtGui/qscrollbar.h> + +QT_BEGIN_NAMESPACE + +class QScrollAreaPrivate: public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QScrollArea) + +public: + QScrollAreaPrivate(): resizable(false), alignment(0){} + void updateScrollBars(); + void updateWidgetPosition(); + QPointer<QWidget> widget; + mutable QSize widgetSize; + bool resizable; + Qt::Alignment alignment; +}; + +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/widgets/qscrollbar.cpp b/src/gui/widgets/qscrollbar.cpp new file mode 100644 index 0000000..fc9e1a3 --- /dev/null +++ b/src/gui/widgets/qscrollbar.cpp @@ -0,0 +1,747 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qcursor.h" +#include "qevent.h" +#include "qpainter.h" +#include "qscrollbar.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qmenu.h" +#include <QtCore/qdatetime.h> + +#ifndef QT_NO_SCROLLBAR + +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#include <limits.h> +#include "qabstractslider_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QScrollBar + \brief The QScrollBar widget provides a vertical or horizontal scroll bar. + + \ingroup basicwidgets + + A scroll bar is a control that enables the user to access parts of a + document that is larger than the widget used to display it. It provides + a visual indication of the user's current position within the document + and the amount of the document that is visible. Scroll bars are usually + equipped with other controls that enable more accurate navigation. + Qt displays scroll bars in a way that is appropriate for each platform. + + If you need to provide a scrolling view onto another widget, it may be + more convenient to use the QScrollArea class because this provides a + viewport widget and scroll bars. QScrollBar is useful if you need to + implement similar functionality for specialized widgets using QAbstractScrollArea; + for example, if you decide to subclass QAbstractItemView. + For most other situations where a slider control is used to obtain a value + within a given range, the QSlider class may be more appropriate for your + needs. + + \table + \row \i \image qscrollbar-picture.png + \i Scroll bars typically include four separate controls: a slider, + scroll arrows, and a page control. + + \list + \i a. The slider provides a way to quickly go to any part of the + document, but does not support accurate navigation within large + documents. + \i b. The scroll arrows are push buttons which can be used to accurately + navigate to a particular place in a document. For a vertical scroll bar + connected to a text editor, these typically move the current position one + "line" up or down, and adjust the position of the slider by a small + amount. In editors and list boxes a "line" might mean one line of text; + in an image viewer it might mean 20 pixels. + \i c. The page control is the area over which the slider is dragged (the + scroll bar's background). Clicking here moves the scroll bar towards + the click by one "page". This value is usually the same as the length of + the slider. + \endlist + \endtable + + Each scroll bar has a value that indicates how far the slider is from + the start of the scroll bar; this is obtained with value() and set + with setValue(). This value always lies within the range of values + defined for the scroll bar, from \l{QAbstractSlider::minimum()}{minimum()} + to \l{QAbstractSlider::minimum()}{maximum()} inclusive. The range of + acceptable values can be set with setMinimum() and setMaximum(). + At the minimum value, the top edge of the slider (for a vertical scroll + bar) or left edge (for a horizontal scroll bar) will be at the top (or + left) end of the scroll bar. At the maximum value, the bottom (or right) + edge of the slider will be at the bottom (or right) end of the scroll bar. + + The length of the slider is usually related to the value of the page step, + and typically represents the proportion of the document area shown in a + scrolling view. The page step is the amount that the value changes by + when the user presses the \key{Page Up} and \key{Page Down} keys, and is + set with setPageStep(). Smaller changes to the value defined by the + line step are made using the cursor keys, and this quantity is set with + \l{QAbstractSlider::}{setSingleStep()}. + + Note that the range of values used is independent of the actual size + of the scroll bar widget. You do not need to take this into account when + you choose values for the range and the page step. + + The range of values specified for the scroll bar are often determined + differently to those for a QSlider because the length of the slider + needs to be taken into account. If we have a document with 100 lines, + and we can only show 20 lines in a widget, we may wish to construct a + scroll bar with a page step of 20, a minimum value of 0, and a maximum + value of 80. This would give us a scroll bar with five "pages". + + \table + \row \i \inlineimage qscrollbar-values.png + \i The relationship between a document length, the range of values used + in a scroll bar, and the page step is simple in many common situations. + The scroll bar's range of values is determined by subtracting a + chosen page step from some value representing the length of the document. + In such cases, the following equation is useful: + + \e{document length} = maximum() - minimum() + pageStep(). + \endtable + + QScrollBar only provides integer ranges. Note that although + QScrollBar handles very large numbers, scroll bars on current + screens cannot usefully represent ranges above about 100,000 pixels. + Beyond that, it becomes difficult for the user to control the + slider using either the keyboard or the mouse, and the scroll + arrows will have limited use. + + ScrollBar inherits a comprehensive set of signals from QAbstractSlider: + \list + \i \l{QAbstractSlider::valueChanged()}{valueChanged()} is emitted when the + scroll bar's value has changed. The tracking() determines whether this + signal is emitted during user interaction. + \i \l{QAbstractSlider::rangeChanged()}{rangeChanged()} is emitted when the + scroll bar's range of values has changed. + \i \l{QAbstractSlider::sliderPressed()}{sliderPressed()} is emitted when + the user starts to drag the slider. + \i \l{QAbstractSlider::sliderMoved()}{sliderMoved()} is emitted when the user + drags the slider. + \i \l{QAbstractSlider::sliderReleased()}{sliderReleased()} is emitted when + the user releases the slider. + \i \l{QAbstractSlider::actionTriggered()}{actionTriggered()} is emitted + when the scroll bar is changed by user interaction or via the + \l{QAbstractSlider::triggerAction()}{triggerAction()} function. + \endlist + + A scroll bar can be controlled by the keyboard, but it has a + default focusPolicy() of Qt::NoFocus. Use setFocusPolicy() to + enable keyboard interaction with the scroll bar: + \list + \i Left/Right move a horizontal scroll bar by one single step. + \i Up/Down move a vertical scroll bar by one single step. + \i PageUp moves up one page. + \i PageDown moves down one page. + \i Home moves to the start (mininum). + \i End moves to the end (maximum). + \endlist + + The slider itself can be controlled by using the + \l{QAbstractSlider::triggerAction()}{triggerAction()} function to simulate + user interaction with the scroll bar controls. This is useful if you have + many different widgets that use a common range of values. + + Most GUI styles use the pageStep() value to calculate the size of the + slider. + + \table 100% + \row \o \inlineimage macintosh-horizontalscrollbar.png Screenshot of a Macintosh style scroll bar + \o A scroll bar shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage windowsxp-horizontalscrollbar.png Screenshot of a Windows XP style scroll bar + \o A scroll bar shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage plastique-horizontalscrollbar.png Screenshot of a Plastique style scroll bar + \o A scroll bar shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QScrollArea, QSlider, QDial, QSpinBox, {fowler}{GUI Design Handbook: Scroll Bar}, {Sliders Example} +*/ + +class QScrollBarPrivate : public QAbstractSliderPrivate +{ + Q_DECLARE_PUBLIC(QScrollBar) +public: + QStyle::SubControl pressedControl; + bool pointerOutsidePressedControl; + + int clickOffset, snapBackPosition; + + void activateControl(uint control, int threshold = 500); + void stopRepeatAction(); + int pixelPosToRangeValue(int pos) const; + void init(); + bool updateHoverControl(const QPoint &pos); + QStyle::SubControl newHoverControl(const QPoint &pos); + + QStyle::SubControl hoverControl; + QRect hoverRect; +}; + +bool QScrollBarPrivate::updateHoverControl(const QPoint &pos) +{ + Q_Q(QScrollBar); + QRect lastHoverRect = hoverRect; + QStyle::SubControl lastHoverControl = hoverControl; + bool doesHover = q->testAttribute(Qt::WA_Hover); + if (lastHoverControl != newHoverControl(pos) && doesHover) { + q->update(lastHoverRect); + q->update(hoverRect); + return true; + } + return !doesHover; +} + +QStyle::SubControl QScrollBarPrivate::newHoverControl(const QPoint &pos) +{ + Q_Q(QScrollBar); + QStyleOptionSlider opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ScrollBar, &opt, pos, q); + if (hoverControl == QStyle::SC_None) + hoverRect = QRect(); + else + hoverRect = q->style()->subControlRect(QStyle::CC_ScrollBar, &opt, hoverControl, q); + return hoverControl; +} + +void QScrollBarPrivate::activateControl(uint control, int threshold) +{ + QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction; + switch (control) { + case QStyle::SC_ScrollBarAddPage: + action = QAbstractSlider::SliderPageStepAdd; + break; + case QStyle::SC_ScrollBarSubPage: + action = QAbstractSlider::SliderPageStepSub; + break; + case QStyle::SC_ScrollBarAddLine: + action = QAbstractSlider::SliderSingleStepAdd; + break; + case QStyle::SC_ScrollBarSubLine: + action = QAbstractSlider::SliderSingleStepSub; + break; + case QStyle::SC_ScrollBarFirst: + action = QAbstractSlider::SliderToMinimum; + break; + case QStyle::SC_ScrollBarLast: + action = QAbstractSlider::SliderToMaximum; + break; + default: + break; + } + + if (action) { + q_func()->setRepeatAction(action, threshold); + q_func()->triggerAction(action); + } +} + +void QScrollBarPrivate::stopRepeatAction() +{ + Q_Q(QScrollBar); + QStyle::SubControl tmp = pressedControl; + q->setRepeatAction(QAbstractSlider::SliderNoAction); + pressedControl = QStyle::SC_None; + + if (tmp == QStyle::SC_ScrollBarSlider) + q->setSliderDown(false); + + QStyleOptionSlider opt; + q->initStyleOption(&opt); + q->repaint(q->style()->subControlRect(QStyle::CC_ScrollBar, &opt, tmp, q)); +} + +/*! + Initialize \a option with the values from this QScrollBar. This method + is useful for subclasses when they need a QStyleOptionSlider, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QScrollBar::initStyleOption(QStyleOptionSlider *option) const +{ + if (!option) + return; + + Q_D(const QScrollBar); + option->initFrom(this); + option->subControls = QStyle::SC_None; + option->activeSubControls = QStyle::SC_None; + option->orientation = d->orientation; + option->minimum = d->minimum; + option->maximum = d->maximum; + option->sliderPosition = d->position; + option->sliderValue = d->value; + option->singleStep = d->singleStep; + option->pageStep = d->pageStep; + option->upsideDown = d->invertedAppearance; + if (d->orientation == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; +} + + +#define HORIZONTAL (d_func()->orientation == Qt::Horizontal) +#define VERTICAL !HORIZONTAL + +/*! + Constructs a vertical scroll bar. + + The \a parent arguments is sent to the QWidget constructor. + + The \l {QAbstractSlider::minimum} {minimum} defaults to 0, the + \l {QAbstractSlider::maximum} {maximum} to 99, with a + \l {QAbstractSlider::singleStep} {singleStep} size of 1 and a + \l {QAbstractSlider::pageStep} {pageStep} size of 10, and an + initial \l {QAbstractSlider::value} {value} of 0. +*/ +QScrollBar::QScrollBar(QWidget *parent) + : QAbstractSlider(*new QScrollBarPrivate, parent) +{ + d_func()->orientation = Qt::Vertical; + d_func()->init(); +} + +/*! + Constructs a scroll bar with the given \a orientation. + + The \a parent argument is passed to the QWidget constructor. + + The \l {QAbstractSlider::minimum} {minimum} defaults to 0, the + \l {QAbstractSlider::maximum} {maximum} to 99, with a + \l {QAbstractSlider::singleStep} {singleStep} size of 1 and a + \l {QAbstractSlider::pageStep} {pageStep} size of 10, and an + initial \l {QAbstractSlider::value} {value} of 0. +*/ +QScrollBar::QScrollBar(Qt::Orientation orientation, QWidget *parent) + : QAbstractSlider(*new QScrollBarPrivate, parent) +{ + d_func()->orientation = orientation; + d_func()->init(); +} + + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QScrollBar::QScrollBar(QWidget *parent, const char *name) + : QAbstractSlider(*new QScrollBarPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + d_func()->orientation = Qt::Vertical; + d_func()->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QScrollBar::QScrollBar(Qt::Orientation orientation, QWidget *parent, const char *name) + : QAbstractSlider(*new QScrollBarPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + d_func()->orientation = orientation; + d_func()->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QScrollBar::QScrollBar(int minimum, int maximum, int lineStep, int pageStep, + int value, Qt::Orientation orientation, + QWidget *parent, const char *name) + : QAbstractSlider(*new QScrollBarPrivate, parent) +{ + Q_D(QScrollBar); + setObjectName(QString::fromAscii(name)); + d->minimum = minimum; + d->maximum = maximum; + d->singleStep = lineStep; + d->pageStep = pageStep; + d->value = value; + d->orientation = orientation; + d->init(); +} +#endif // QT3_SUPPORT + +/*! + Destroys the scroll bar. +*/ +QScrollBar::~QScrollBar() +{ +} + +void QScrollBarPrivate::init() +{ + Q_Q(QScrollBar); + invertedControls = true; + pressedControl = hoverControl = QStyle::SC_None; + pointerOutsidePressedControl = false; + q->setFocusPolicy(Qt::NoFocus); + QSizePolicy sp(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::Slider); + if (orientation == Qt::Vertical) + sp.transpose(); + q->setSizePolicy(sp); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + q->setAttribute(Qt::WA_OpaquePaintEvent); +} + +#ifndef QT_NO_CONTEXTMENU +/*! \reimp */ +void QScrollBar::contextMenuEvent(QContextMenuEvent *event) +{ + if (!style()->styleHint(QStyle::SH_ScrollBar_ContextMenu, 0, this)) { + QAbstractSlider::contextMenuEvent(event); + return ; + } + +#ifndef QT_NO_MENU + bool horiz = HORIZONTAL; + QMenu menu; + QAction *actScrollHere = + menu.addAction(tr("Scroll here")); + menu.addSeparator(); + QAction *actScrollTop = + menu.addAction(horiz ? tr("Left edge") : tr("Top")); + QAction *actScrollBottom = + menu.addAction(horiz ? tr("Right edge") : tr("Bottom")); + menu.addSeparator(); + QAction *actPageUp = + menu.addAction(horiz ? tr("Page left") : tr("Page up")); + QAction *actPageDn = + menu.addAction(horiz ? tr("Page right") : tr("Page down")); + menu.addSeparator(); + QAction *actScrollUp = + menu.addAction(horiz ? tr("Scroll left") : tr("Scroll up")); + QAction *actScrollDn = + menu.addAction(horiz ? tr("Scroll right") : tr("Scroll down")); + + QAction *actionSelected = menu.exec(event->globalPos()); + if (actionSelected == 0) + /* do nothing */ ; + else if (actionSelected == actScrollHere) + setValue(d_func()->pixelPosToRangeValue(horiz ? event->pos().x() : event->pos().y())); + else if (actionSelected == actScrollTop) + triggerAction(QAbstractSlider::SliderToMinimum); + else if (actionSelected == actScrollBottom) + triggerAction(QAbstractSlider::SliderToMaximum); + else if (actionSelected == actPageUp) + triggerAction(QAbstractSlider::SliderPageStepSub); + else if (actionSelected == actPageDn) + triggerAction(QAbstractSlider::SliderPageStepAdd); + else if (actionSelected == actScrollUp) + triggerAction(QAbstractSlider::SliderSingleStepSub); + else if (actionSelected == actScrollDn) + triggerAction(QAbstractSlider::SliderSingleStepAdd); +#endif // QT_NO_MENU +} +#endif // QT_NO_CONTEXTMENU + + +/*! \reimp */ +QSize QScrollBar::sizeHint() const +{ + ensurePolished(); + QStyleOptionSlider opt; + initStyleOption(&opt); + + int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt, this); + int scrollBarSliderMin = style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &opt, this); + QSize size; + if (opt.orientation == Qt::Horizontal) + size = QSize(scrollBarExtent * 2 + scrollBarSliderMin, scrollBarExtent); + else + size = QSize(scrollBarExtent, scrollBarExtent * 2 + scrollBarSliderMin); + + return style()->sizeFromContents(QStyle::CT_ScrollBar, &opt, size, this) + .expandedTo(QApplication::globalStrut()); + } + +/*!\reimp */ +void QScrollBar::sliderChange(SliderChange change) +{ + QAbstractSlider::sliderChange(change); +} + +/*! + \reimp +*/ +bool QScrollBar::event(QEvent *event) +{ + switch(event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) + d_func()->updateHoverControl(he->pos()); + break; + default: + break; + } + return QAbstractSlider::event(event); +} + +/*! + \reimp +*/ +void QScrollBar::paintEvent(QPaintEvent *) +{ + Q_D(QScrollBar); + QPainter p(this); + QStyleOptionSlider opt; + initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + if (d->pressedControl) { + opt.activeSubControls = (QStyle::SubControl)d->pressedControl; + if (!d->pointerOutsidePressedControl) + opt.state |= QStyle::State_Sunken; + } else { + opt.activeSubControls = (QStyle::SubControl)d->hoverControl; + } + style()->drawComplexControl(QStyle::CC_ScrollBar, &opt, &p, this); +} + +/*! + \reimp +*/ +void QScrollBar::mousePressEvent(QMouseEvent *e) +{ + Q_D(QScrollBar); + + if (d->repeatActionTimer.isActive()) + d->stopRepeatAction(); + + bool midButtonAbsPos = style()->styleHint(QStyle::SH_ScrollBar_MiddleClickAbsolutePosition, + 0, this); + QStyleOptionSlider opt; + initStyleOption(&opt); + + if (d->maximum == d->minimum // no range + || (e->buttons() & (~e->button())) // another button was clicked before + || !(e->button() == Qt::LeftButton || (midButtonAbsPos && e->button() == Qt::MidButton))) + return; + + d->pressedControl = style()->hitTestComplexControl(QStyle::CC_ScrollBar, &opt, e->pos(), this); + d->pointerOutsidePressedControl = false; + + QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarSlider, this); + QPoint click = e->pos(); + QPoint pressValue = click - sr.center() + sr.topLeft(); + d->pressValue = d->orientation == Qt::Horizontal ? d->pixelPosToRangeValue(pressValue.x()) : + d->pixelPosToRangeValue(pressValue.y()); + if (d->pressedControl == QStyle::SC_ScrollBarSlider) { + d->clickOffset = HORIZONTAL ? (click.x()-sr.x()) : (click.y()-sr.y()); + d->snapBackPosition = d->position; + } + + if ((d->pressedControl == QStyle::SC_ScrollBarAddPage + || d->pressedControl == QStyle::SC_ScrollBarSubPage) + && ((midButtonAbsPos && e->button() == Qt::MidButton) + || (style()->styleHint(QStyle::SH_ScrollBar_LeftClickAbsolutePosition, &opt, this) + && e->button() == Qt::LeftButton))) { + int sliderLength = HORIZONTAL ? sr.width() : sr.height(); + setSliderPosition(d->pixelPosToRangeValue((HORIZONTAL ? e->pos().x() + : e->pos().y()) - sliderLength / 2)); + d->pressedControl = QStyle::SC_ScrollBarSlider; + d->clickOffset = sliderLength / 2; + } + const int initialDelay = 500; // default threshold + d->activateControl(d->pressedControl, initialDelay); + QTime time; + time.start(); + repaint(style()->subControlRect(QStyle::CC_ScrollBar, &opt, d->pressedControl, this)); + if (time.elapsed() >= initialDelay && d->repeatActionTimer.isActive()) { + // It took more than 500ms (the initial timer delay) to process the repaint(), we + // therefore need to restart the timer in case we have a pending mouse release event; + // otherwise we'll get a timer event right before the release event, + // causing the repeat action to be invoked twice on a single mouse click. + // 50ms is the default repeat time (see activateControl/setRepeatAction). + d->repeatActionTimer.start(50, this); + } + if (d->pressedControl == QStyle::SC_ScrollBarSlider) + setSliderDown(true); +} + + +/*! + \reimp +*/ +void QScrollBar::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QScrollBar); + if (!d->pressedControl) + return; + + if (e->buttons() & (~e->button())) // some other button is still pressed + return; + + d->stopRepeatAction(); +} + + +/*! + \reimp +*/ +void QScrollBar::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QScrollBar); + if (!d->pressedControl) + return; + + QStyleOptionSlider opt; + initStyleOption(&opt); + if (!(e->buttons() & Qt::LeftButton + || ((e->buttons() & Qt::MidButton) + && style()->styleHint(QStyle::SH_ScrollBar_MiddleClickAbsolutePosition, &opt, this)))) + return; + + if (d->pressedControl == QStyle::SC_ScrollBarSlider) { + QPoint click = e->pos(); + int newPosition = d->pixelPosToRangeValue((HORIZONTAL ? click.x() : click.y()) -d->clickOffset); + int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this); + if (m >= 0) { + QRect r = rect(); + r.adjust(-m, -m, m, m); + if (! r.contains(e->pos())) + newPosition = d->snapBackPosition; + } + setSliderPosition(newPosition); + } else if (!style()->styleHint(QStyle::SH_ScrollBar_ScrollWhenPointerLeavesControl, &opt, this)) { + + if (style()->styleHint(QStyle::SH_ScrollBar_RollBetweenButtons, &opt, this) + && d->pressedControl & (QStyle::SC_ScrollBarAddLine | QStyle::SC_ScrollBarSubLine)) { + QStyle::SubControl newSc = style()->hitTestComplexControl(QStyle::CC_ScrollBar, &opt, e->pos(), this); + if (newSc == d->pressedControl && !d->pointerOutsidePressedControl) + return; // nothing to do + if (newSc & (QStyle::SC_ScrollBarAddLine | QStyle::SC_ScrollBarSubLine)) { + d->pointerOutsidePressedControl = false; + QRect scRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, newSc, this); + scRect |= style()->subControlRect(QStyle::CC_ScrollBar, &opt, d->pressedControl, this); + d->pressedControl = newSc; + d->activateControl(d->pressedControl, 0); + update(scRect); + return; + } + } + + // stop scrolling when the mouse pointer leaves a control + // similar to push buttons + QRect pr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, d->pressedControl, this); + if (pr.contains(e->pos()) == d->pointerOutsidePressedControl) { + if ((d->pointerOutsidePressedControl = !d->pointerOutsidePressedControl)) { + d->pointerOutsidePressedControl = true; + setRepeatAction(SliderNoAction); + repaint(pr); + } else { + d->activateControl(d->pressedControl); + } + } + } +} + + +int QScrollBarPrivate::pixelPosToRangeValue(int pos) const +{ + Q_Q(const QScrollBar); + QStyleOptionSlider opt; + q->initStyleOption(&opt); + QRect gr = q->style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarGroove, q); + QRect sr = q->style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarSlider, q); + int sliderMin, sliderMax, sliderLength; + + if (orientation == Qt::Horizontal) { + sliderLength = sr.width(); + sliderMin = gr.x(); + sliderMax = gr.right() - sliderLength + 1; + if (q->layoutDirection() == Qt::RightToLeft) + opt.upsideDown = !opt.upsideDown; + } else { + sliderLength = sr.height(); + sliderMin = gr.y(); + sliderMax = gr.bottom() - sliderLength + 1; + } + + return QStyle::sliderValueFromPosition(minimum, maximum, pos - sliderMin, + sliderMax - sliderMin, opt.upsideDown); +} + +/*! \reimp +*/ +void QScrollBar::hideEvent(QHideEvent *) +{ + Q_D(QScrollBar); + if (d->pressedControl) { + d->pressedControl = QStyle::SC_None; + setRepeatAction(SliderNoAction); + } +} + +/*! + \fn bool QScrollBar::draggingSlider() + + Use isSliderDown() instead. +*/ + +/*! \internal + Returns the style option for scroll bar. +*/ +Q_GUI_EXPORT QStyleOptionSlider qt_qscrollbarStyleOption(QScrollBar *scrollbar) +{ + QStyleOptionSlider opt; + scrollbar->initStyleOption(&opt); + return opt; +} + +QT_END_NAMESPACE + +#endif // QT_NO_SCROLLBAR diff --git a/src/gui/widgets/qscrollbar.h b/src/gui/widgets/qscrollbar.h new file mode 100644 index 0000000..35aacf4 --- /dev/null +++ b/src/gui/widgets/qscrollbar.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLBAR_H +#define QSCROLLBAR_H + +#include <QtGui/qwidget.h> +#include <QtGui/qabstractslider.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SCROLLBAR + +class QScrollBarPrivate; +class QStyleOptionSlider; + +class Q_GUI_EXPORT QScrollBar : public QAbstractSlider +{ + Q_OBJECT +public: + explicit QScrollBar(QWidget *parent=0); + explicit QScrollBar(Qt::Orientation, QWidget *parent=0); + ~QScrollBar(); + + QSize sizeHint() const; + bool event(QEvent *event); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void hideEvent(QHideEvent*); + void sliderChange(SliderChange change); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *); +#endif + void initStyleOption(QStyleOptionSlider *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QScrollBar(QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QScrollBar(Qt::Orientation, QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QScrollBar(int minValue, int maxValue, int lineStep, int pageStep, + int value, Qt::Orientation, QWidget *parent=0, const char* name = 0); + inline QT3_SUPPORT bool draggingSlider() { return isSliderDown(); } +#endif + +private: + friend Q_GUI_EXPORT QStyleOptionSlider qt_qscrollbarStyleOption(QScrollBar *scrollBar); + + Q_DISABLE_COPY(QScrollBar) + Q_DECLARE_PRIVATE(QScrollBar) +}; + +#endif // QT_NO_SCROLLBAR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCROLLBAR_H diff --git a/src/gui/widgets/qsizegrip.cpp b/src/gui/widgets/qsizegrip.cpp new file mode 100644 index 0000000..6458b15 --- /dev/null +++ b/src/gui/widgets/qsizegrip.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsizegrip.h" + +#ifndef QT_NO_SIZEGRIP + +#include "qapplication.h" +#include "qevent.h" +#include "qpainter.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qlayout.h" +#include "qdebug.h" +#include <QDesktopWidget> + +#if defined(Q_WS_X11) +#include <private/qt_x11_p.h> +#elif defined (Q_WS_WIN) +#include "qt_windows.h" +#endif +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#endif + +#include <private/qwidget_p.h> +#include <QtGui/qabstractscrollarea.h> + +#define SZ_SIZEBOTTOMRIGHT 0xf008 +#define SZ_SIZEBOTTOMLEFT 0xf007 +#define SZ_SIZETOPLEFT 0xf004 +#define SZ_SIZETOPRIGHT 0xf005 + +QT_BEGIN_NAMESPACE + +static QWidget *qt_sizegrip_topLevelWidget(QWidget* w) +{ + while (w && !w->isWindow() && w->windowType() != Qt::SubWindow) + w = w->parentWidget(); + return w; +} + +static inline bool hasHeightForWidth(QWidget *widget) +{ + if (!widget) + return false; + if (QLayout *layout = widget->layout()) + return layout->hasHeightForWidth(); + return widget->sizePolicy().hasHeightForWidth(); +} + +class QSizeGripPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QSizeGrip) +public: + void init(); + QPoint p; + QRect r; + int d; + int dxMax; + int dyMax; + Qt::Corner m_corner; + bool gotMousePress; +#ifdef Q_WS_MAC + void updateMacSizer(bool hide) const; +#endif + Qt::Corner corner() const; + inline bool atBottom() const + { + return m_corner == Qt::BottomRightCorner || m_corner == Qt::BottomLeftCorner; + } + + inline bool atLeft() const + { + return m_corner == Qt::BottomLeftCorner || m_corner == Qt::TopLeftCorner; + } + + // This slot is invoked by QLayout when the size grip is added to + // a layout or reparented after the tlw is shown. This re-implementation is basically + // the same as QWidgetPrivate::_q_showIfNotHidden except that it checks + // for Qt::WindowFullScreen and Qt::WindowMaximized as well. + void _q_showIfNotHidden() + { + Q_Q(QSizeGrip); + bool showSizeGrip = !(q->isHidden() && q->testAttribute(Qt::WA_WState_ExplicitShowHide)); + QWidget *tlw = qt_sizegrip_topLevelWidget(q); + if (tlw && showSizeGrip) { + Qt::WindowStates sizeGripNotVisibleState = Qt::WindowFullScreen; +#ifndef Q_WS_MAC + sizeGripNotVisibleState |= Qt::WindowMaximized; +#endif + // Don't show the size grip if the tlw is maximized or in full screen mode. + showSizeGrip = !(tlw->windowState() & sizeGripNotVisibleState); + } + if (showSizeGrip) + q->setVisible(true); + } +}; + +#ifdef Q_WS_MAC +void QSizeGripPrivate::updateMacSizer(bool hide) const +{ + Q_Q(const QSizeGrip); + if (QApplication::closingDown() || !q->parentWidget()) + return; + QWidget *topLevelWindow = qt_sizegrip_topLevelWidget(const_cast<QSizeGrip *>(q)); + if(topLevelWindow && topLevelWindow->isWindow()) + QWidgetPrivate::qt_mac_update_sizer(topLevelWindow, hide ? -1 : 1); +} +#endif + +Qt::Corner QSizeGripPrivate::corner() const +{ + Q_Q(const QSizeGrip); + QWidget *tlw = qt_sizegrip_topLevelWidget(const_cast<QSizeGrip *>(q)); + const QPoint sizeGripPos = q->mapTo(tlw, QPoint(0, 0)); + bool isAtBottom = sizeGripPos.y() >= tlw->height() / 2; + bool isAtLeft = sizeGripPos.x() <= tlw->width() / 2; + if (isAtLeft) + return isAtBottom ? Qt::BottomLeftCorner : Qt::TopLeftCorner; + else + return isAtBottom ? Qt::BottomRightCorner : Qt::TopRightCorner; +} + +/*! + \class QSizeGrip + + \brief The QSizeGrip class provides a resize handle for resizing top-level windows. + + \ingroup application + \ingroup basicwidgets + \ingroup appearance + + This widget works like the standard Windows resize handle. In the + X11 version this resize handle generally works differently from + the one provided by the system if the X11 window manager does not + support necessary modern post-ICCCM specifications. + + Put this widget anywhere in a widget tree and the user can use it + to resize the top-level window or any widget with the Qt::SubWindow + flag set. Generally, this should be in the lower right-hand corner. + Note that QStatusBar already uses this widget, so if you have a + status bar (e.g., you are using QMainWindow), then you don't need + to use this widget explicitly. + + On some platforms the size grip automatically hides itself when the + window is shown full screen or maximised. + + \table 50% + \row \o \inlineimage plastique-sizegrip.png Screenshot of a Plastique style size grip + \o A size grip widget at the bottom-right corner of a main window, shown in the + \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + The QSizeGrip class inherits QWidget and reimplements the \l + {QWidget::mousePressEvent()}{mousePressEvent()} and \l + {QWidget::mouseMoveEvent()}{mouseMoveEvent()} functions to feature + the resize functionality, and the \l + {QWidget::paintEvent()}{paintEvent()} function to render the + size grip widget. + + \sa QStatusBar QWidget::windowState() +*/ + + +/*! + Constructs a resize corner as a child widget of the given \a + parent. +*/ +QSizeGrip::QSizeGrip(QWidget * parent) + : QWidget(*new QSizeGripPrivate, parent, 0) +{ + Q_D(QSizeGrip); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + + Constructs a resize corner with the given \a name, as a child + widget of the given \a parent. +*/ +QSizeGrip::QSizeGrip(QWidget * parent, const char* name) + : QWidget(*new QSizeGripPrivate, parent, 0) +{ + Q_D(QSizeGrip); + setObjectName(QString::fromAscii(name)); + d->init(); +} +#endif + +void QSizeGripPrivate::init() +{ + Q_Q(QSizeGrip); + dxMax = 0; + dyMax = 0; + m_corner = q->isLeftToRight() ? Qt::BottomRightCorner : Qt::BottomLeftCorner; + gotMousePress = false; + +#if !defined(QT_NO_CURSOR) && !defined(Q_WS_MAC) + q->setCursor(m_corner == Qt::TopLeftCorner || m_corner == Qt::BottomRightCorner + ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor); +#endif + q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); + QWidget *tlw = qt_sizegrip_topLevelWidget(q); + tlw->installEventFilter(q); +} + + +/*! + Destroys this size grip. +*/ +QSizeGrip::~QSizeGrip() +{ +} + +/*! + \reimp +*/ +QSize QSizeGrip::sizeHint() const +{ + QStyleOption opt(0); + opt.init(this); + return (style()->sizeFromContents(QStyle::CT_SizeGrip, &opt, QSize(13, 13), this). + expandedTo(QApplication::globalStrut())); +} + +/*! + Paints the resize grip. + + Resize grips are usually rendered as small diagonal textured lines + in the lower-right corner. The paint event is passed in the \a + event parameter. +*/ +void QSizeGrip::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + Q_D(QSizeGrip); + QPainter painter(this); + QStyleOptionSizeGrip opt; + opt.init(this); + opt.corner = d->m_corner; + style()->drawControl(QStyle::CE_SizeGrip, &opt, &painter, this); +} + +/*! + \fn void QSizeGrip::mousePressEvent(QMouseEvent * event) + + Receives the mouse press events for the widget, and primes the + resize operation. The mouse press event is passed in the \a event + parameter. +*/ +void QSizeGrip::mousePressEvent(QMouseEvent * e) +{ + if (e->button() != Qt::LeftButton) { + QWidget::mousePressEvent(e); + return; + } + + Q_D(QSizeGrip); + QWidget *tlw = qt_sizegrip_topLevelWidget(this); + d->p = e->globalPos(); + d->gotMousePress = true; + d->r = tlw->geometry(); + +#ifdef Q_WS_X11 + // Use a native X11 sizegrip for "real" top-level windows if supported. + if (tlw->isWindow() && X11->isSupportedByWM(ATOM(_NET_WM_MOVERESIZE)) + && !tlw->testAttribute(Qt::WA_DontShowOnScreen) && !hasHeightForWidth(tlw)) { + XEvent xev; + xev.xclient.type = ClientMessage; + xev.xclient.message_type = ATOM(_NET_WM_MOVERESIZE); + xev.xclient.display = X11->display; + xev.xclient.window = tlw->winId(); + xev.xclient.format = 32; + xev.xclient.data.l[0] = e->globalPos().x(); + xev.xclient.data.l[1] = e->globalPos().y(); + if (d->atBottom()) + xev.xclient.data.l[2] = d->atLeft() ? 6 : 4; // bottomleft/bottomright + else + xev.xclient.data.l[2] = d->atLeft() ? 0 : 2; // topleft/topright + xev.xclient.data.l[3] = Button1; + xev.xclient.data.l[4] = 0; + XUngrabPointer(X11->display, X11->time); + XSendEvent(X11->display, QX11Info::appRootWindow(x11Info().screen()), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + return; + } +#endif // Q_WS_X11 +#ifdef Q_WS_WIN + if (tlw->isWindow() && !tlw->testAttribute(Qt::WA_DontShowOnScreen) && !hasHeightForWidth(tlw)) { + uint orientation = 0; + if (d->atBottom()) + orientation = d->atLeft() ? SZ_SIZEBOTTOMLEFT : SZ_SIZEBOTTOMRIGHT; + else + orientation = d->atLeft() ? SZ_SIZETOPLEFT : SZ_SIZETOPRIGHT; + + ReleaseCapture(); + QT_WA_INLINE(PostMessageW(tlw->winId(), WM_SYSCOMMAND, orientation, 0), + PostMessageA(tlw->winId(), WM_SYSCOMMAND, orientation, 0)); + return; + } +#endif // Q_WS_WIN + + // Find available desktop/workspace geometry. + QRect availableGeometry; + bool hasVerticalSizeConstraint = true; + bool hasHorizontalSizeConstraint = true; + if (tlw->isWindow()) + availableGeometry = QApplication::desktop()->availableGeometry(tlw); + else { + const QWidget *tlwParent = tlw->parentWidget(); + // Check if tlw is inside QAbstractScrollArea/QScrollArea. + // If that's the case tlw->parentWidget() will return the viewport + // and tlw->parentWidget()->parentWidget() will return the scroll area. +#ifndef QT_NO_SCROLLAREA + QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea *>(tlwParent->parentWidget()); + if (scrollArea) { + hasHorizontalSizeConstraint = scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff; + hasVerticalSizeConstraint = scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff; + } +#endif // QT_NO_SCROLLAREA + availableGeometry = tlwParent->contentsRect(); + } + + // Find frame geometries, title bar height, and decoration sizes. + const QRect frameGeometry = tlw->frameGeometry(); + const int titleBarHeight = qMax(tlw->geometry().y() - frameGeometry.y(), 0); + const int bottomDecoration = qMax(frameGeometry.height() - tlw->height() - titleBarHeight, 0); + const int leftRightDecoration = qMax((frameGeometry.width() - tlw->width()) / 2, 0); + + // Determine dyMax depending on whether the sizegrip is at the bottom + // of the widget or not. + if (d->atBottom()) { + if (hasVerticalSizeConstraint) + d->dyMax = availableGeometry.bottom() - d->r.bottom() - bottomDecoration; + else + d->dyMax = INT_MAX; + } else { + if (hasVerticalSizeConstraint) + d->dyMax = availableGeometry.y() - d->r.y() + titleBarHeight; + else + d->dyMax = -INT_MAX; + } + + // In RTL mode, the size grip is to the left; find dxMax from the desktop/workspace + // geometry, the size grip geometry and the width of the decoration. + if (d->atLeft()) { + if (hasHorizontalSizeConstraint) + d->dxMax = availableGeometry.x() - d->r.x() + leftRightDecoration; + else + d->dxMax = -INT_MAX; + } else { + if (hasHorizontalSizeConstraint) + d->dxMax = availableGeometry.right() - d->r.right() - leftRightDecoration; + else + d->dxMax = INT_MAX; + } +} + + +/*! + \fn void QSizeGrip::mouseMoveEvent(QMouseEvent * event) + Resizes the top-level widget containing this widget. The mouse + move event is passed in the \a event parameter. +*/ +void QSizeGrip::mouseMoveEvent(QMouseEvent * e) +{ + if (e->buttons() != Qt::LeftButton) { + QWidget::mouseMoveEvent(e); + return; + } + + Q_D(QSizeGrip); + QWidget* tlw = qt_sizegrip_topLevelWidget(this); + if (!d->gotMousePress || tlw->testAttribute(Qt::WA_WState_ConfigPending)) + return; + +#ifdef Q_WS_X11 + if (tlw->isWindow() && X11->isSupportedByWM(ATOM(_NET_WM_MOVERESIZE)) + && tlw->isTopLevel() && !tlw->testAttribute(Qt::WA_DontShowOnScreen) && !hasHeightForWidth(tlw)) + return; +#endif +#ifdef Q_WS_WIN + if (tlw->isWindow() && GetSystemMenu(tlw->winId(), FALSE) != 0 && internalWinId() + && !tlw->testAttribute(Qt::WA_DontShowOnScreen) && !hasHeightForWidth(tlw)) { + MSG msg; + while(PeekMessage(&msg, winId(), WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)); + return; + } +#endif + + QPoint np(e->globalPos()); + + // Don't extend beyond the available geometry; bound to dyMax and dxMax. + QSize ns; + if (d->atBottom()) + ns.rheight() = d->r.height() + qMin(np.y() - d->p.y(), d->dyMax); + else + ns.rheight() = d->r.height() - qMax(np.y() - d->p.y(), d->dyMax); + + if (d->atLeft()) + ns.rwidth() = d->r.width() - qMax(np.x() - d->p.x(), d->dxMax); + else + ns.rwidth() = d->r.width() + qMin(np.x() - d->p.x(), d->dxMax); + + ns = QLayout::closestAcceptableSize(tlw, ns); + + QPoint p; + QRect nr(p, ns); + if (d->atBottom()) { + if (d->atLeft()) + nr.moveTopRight(d->r.topRight()); + else + nr.moveTopLeft(d->r.topLeft()); + } else { + if (d->atLeft()) + nr.moveBottomRight(d->r.bottomRight()); + else + nr.moveBottomLeft(d->r.bottomLeft()); + } + + tlw->setGeometry(nr); +} + +/*! + \reimp +*/ +void QSizeGrip::mouseReleaseEvent(QMouseEvent *mouseEvent) +{ + if (mouseEvent->button() == Qt::LeftButton) { + Q_D(QSizeGrip); + d->gotMousePress = false; + d->p = QPoint(); + } else { + QWidget::mouseReleaseEvent(mouseEvent); + } +} + +/*! + \reimp +*/ +void QSizeGrip::moveEvent(QMoveEvent * /*moveEvent*/) +{ + Q_D(QSizeGrip); + // We're inside a resize operation; no update necessary. + if (!d->p.isNull()) + return; + + d->m_corner = d->corner(); +#if !defined(QT_NO_CURSOR) && !defined(Q_WS_MAC) + setCursor(d->m_corner == Qt::TopLeftCorner || d->m_corner == Qt::BottomRightCorner + ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor); +#endif +} + +/*! + \reimp +*/ +void QSizeGrip::showEvent(QShowEvent *showEvent) +{ +#ifdef Q_WS_MAC + d_func()->updateMacSizer(false); +#endif + QWidget::showEvent(showEvent); +} + +/*! + \reimp +*/ +void QSizeGrip::hideEvent(QHideEvent *hideEvent) +{ +#ifdef Q_WS_MAC + d_func()->updateMacSizer(true); +#endif + QWidget::hideEvent(hideEvent); +} + +/*! + \reimp +*/ +void QSizeGrip::setVisible(bool visible) +{ + QWidget::setVisible(visible); +} + +/*! \reimp */ +bool QSizeGrip::eventFilter(QObject *o, QEvent *e) +{ + if ((isHidden() && testAttribute(Qt::WA_WState_ExplicitShowHide)) + || e->type() != QEvent::WindowStateChange) { + return QWidget::eventFilter(o, e); + } + QWidget *tlw = qt_sizegrip_topLevelWidget(this); + if (o != tlw) + return QWidget::eventFilter(o, e); + Qt::WindowStates sizeGripNotVisibleState = Qt::WindowFullScreen; +#ifndef Q_WS_MAC + sizeGripNotVisibleState |= Qt::WindowMaximized; +#endif + // Don't show the size grip if the tlw is maximized or in full screen mode. + setVisible(!(tlw->windowState() & sizeGripNotVisibleState)); + setAttribute(Qt::WA_WState_ExplicitShowHide, false); + return QWidget::eventFilter(o, e); +} + +/*! + \reimp +*/ +bool QSizeGrip::event(QEvent *event) +{ + return QWidget::event(event); +} + +#ifdef Q_WS_WIN +/*! \reimp */ +bool QSizeGrip::winEvent( MSG *m, long *result ) +{ + return QWidget::winEvent(m, result); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qsizegrip.cpp" + +#endif //QT_NO_SIZEGRIP diff --git a/src/gui/widgets/qsizegrip.h b/src/gui/widgets/qsizegrip.h new file mode 100644 index 0000000..cfd83a8 --- /dev/null +++ b/src/gui/widgets/qsizegrip.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIZEGRIP_H +#define QSIZEGRIP_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SIZEGRIP +class QSizeGripPrivate; +class Q_GUI_EXPORT QSizeGrip : public QWidget +{ + Q_OBJECT +public: + explicit QSizeGrip(QWidget *parent); + ~QSizeGrip(); + + QSize sizeHint() const; + void setVisible(bool); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *mouseEvent); + void moveEvent(QMoveEvent *moveEvent); + void showEvent(QShowEvent *showEvent); + void hideEvent(QHideEvent *hideEvent); + bool eventFilter(QObject *, QEvent *); + bool event(QEvent *); +#ifdef Q_WS_WIN + bool winEvent(MSG *m, long *result); +#endif + +public: +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QSizeGrip(QWidget *parent, const char *name); +#endif + +private: + Q_DECLARE_PRIVATE(QSizeGrip) + Q_DISABLE_COPY(QSizeGrip) + Q_PRIVATE_SLOT(d_func(), void _q_showIfNotHidden()) +}; +#endif // QT_NO_SIZEGRIP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSIZEGRIP_H diff --git a/src/gui/widgets/qslider.cpp b/src/gui/widgets/qslider.cpp new file mode 100644 index 0000000..32b9021 --- /dev/null +++ b/src/gui/widgets/qslider.cpp @@ -0,0 +1,676 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qslider.h" +#ifndef QT_NO_SLIDER +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#include "qapplication.h" +#include "qevent.h" +#include "qpainter.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "private/qabstractslider_p.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +class QSliderPrivate : public QAbstractSliderPrivate +{ + Q_DECLARE_PUBLIC(QSlider) +public: + QStyle::SubControl pressedControl; + int tickInterval; + QSlider::TickPosition tickPosition; + int clickOffset; + int snapBackPosition; + void init(); + void resetLayoutItemMargins(); + int pixelPosToRangeValue(int pos) const; + inline int pick(const QPoint &pt) const; + + QStyle::SubControl newHoverControl(const QPoint &pos); + bool updateHoverControl(const QPoint &pos); + QStyle::SubControl hoverControl; + QRect hoverRect; +}; + +void QSliderPrivate::init() +{ + Q_Q(QSlider); + pressedControl = QStyle::SC_None; + tickInterval = 0; + tickPosition = QSlider::NoTicks; + hoverControl = QStyle::SC_None; + q->setFocusPolicy(Qt::FocusPolicy(q->style()->styleHint(QStyle::SH_Button_FocusPolicy))); + QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::Slider); + if (orientation == Qt::Vertical) + sp.transpose(); + q->setSizePolicy(sp); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + resetLayoutItemMargins(); +} + +void QSliderPrivate::resetLayoutItemMargins() +{ + Q_Q(QSlider); + QStyleOptionSlider opt; + q->initStyleOption(&opt); + setLayoutItemMargins(QStyle::SE_SliderLayoutItem, &opt); +} + +int QSliderPrivate::pixelPosToRangeValue(int pos) const +{ + Q_Q(const QSlider); + QStyleOptionSlider opt; + q->initStyleOption(&opt); + QRect gr = q->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, q); + QRect sr = q->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, q); + int sliderMin, sliderMax, sliderLength; + + if (orientation == Qt::Horizontal) { + sliderLength = sr.width(); + sliderMin = gr.x(); + sliderMax = gr.right() - sliderLength + 1; + } else { + sliderLength = sr.height(); + sliderMin = gr.y(); + sliderMax = gr.bottom() - sliderLength + 1; + } + return QStyle::sliderValueFromPosition(minimum, maximum, pos - sliderMin, + sliderMax - sliderMin, opt.upsideDown); +} + +inline int QSliderPrivate::pick(const QPoint &pt) const +{ + return orientation == Qt::Horizontal ? pt.x() : pt.y(); +} + +/*! + Initialize \a option with the values from this QSlider. This method + is useful for subclasses when they need a QStyleOptionSlider, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QSlider::initStyleOption(QStyleOptionSlider *option) const +{ + if (!option) + return; + + Q_D(const QSlider); + option->initFrom(this); + option->subControls = QStyle::SC_None; + option->activeSubControls = QStyle::SC_None; + option->orientation = d->orientation; + option->maximum = d->maximum; + option->minimum = d->minimum; + option->tickPosition = (QSlider::TickPosition)d->tickPosition; + option->tickInterval = d->tickInterval; + option->upsideDown = (d->orientation == Qt::Horizontal) ? + (d->invertedAppearance != (option->direction == Qt::RightToLeft)) + : (!d->invertedAppearance); + option->direction = Qt::LeftToRight; // we use the upsideDown option instead + option->sliderPosition = d->position; + option->sliderValue = d->value; + option->singleStep = d->singleStep; + option->pageStep = d->pageStep; + if (d->orientation == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; +} + +bool QSliderPrivate::updateHoverControl(const QPoint &pos) +{ + Q_Q(QSlider); + QRect lastHoverRect = hoverRect; + QStyle::SubControl lastHoverControl = hoverControl; + bool doesHover = q->testAttribute(Qt::WA_Hover); + if (lastHoverControl != newHoverControl(pos) && doesHover) { + q->update(lastHoverRect); + q->update(hoverRect); + return true; + } + return !doesHover; +} + +QStyle::SubControl QSliderPrivate::newHoverControl(const QPoint &pos) +{ + Q_Q(QSlider); + QStyleOptionSlider opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + QRect handleRect = q->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, q); + QRect grooveRect = q->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, q); + QRect tickmarksRect = q->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderTickmarks, q); + + if (handleRect.contains(pos)) { + hoverRect = handleRect; + hoverControl = QStyle::SC_SliderHandle; + } else if (grooveRect.contains(pos)) { + hoverRect = grooveRect; + hoverControl = QStyle::SC_SliderGroove; + } else if (tickmarksRect.contains(pos)) { + hoverRect = tickmarksRect; + hoverControl = QStyle::SC_SliderTickmarks; + } else { + hoverRect = QRect(); + hoverControl = QStyle::SC_None; + } + + return hoverControl; +} + +/*! + \class QSlider + \brief The QSlider widget provides a vertical or horizontal slider. + + \ingroup basicwidgets + \mainclass + + The slider is the classic widget for controlling a bounded value. + It lets the user move a slider handle along a horizontal or vertical + groove and translates the handle's position into an integer value + within the legal range. + + QSlider has very few of its own functions; most of the functionality is in + QAbstractSlider. The most useful functions are setValue() to set + the slider directly to some value; triggerAction() to simulate + the effects of clicking (useful for shortcut keys); + setSingleStep(), setPageStep() to set the steps; and setMinimum() + and setMaximum() to define the range of the scroll bar. + + QSlider provides methods for controlling tickmarks. You can use + setTickPosition() to indicate where you want the tickmarks to be, + setTickInterval() to indicate how many of them you want. the + currently set tick position and interval can be queried using the + tickPosition() and tickInterval() functions, respectively. + + QSlider inherits a comprehensive set of signals: + \table + \header \o Signal \o Description + \row \o \l valueChanged() + \o Emitted when the slider's value has changed. The tracking() + determines whether this signal is emitted during user + interaction. + \row \o \l sliderPressed() + \o Emitted when the user starts to drag the slider. + \row \o \l sliderMoved() + \o Emitted when the user drags the slider. + \row \o \l sliderReleased() + \o Emitted when the user releases the slider. + \endtable + + QSlider only provides integer ranges. Note that although + QSlider handles very large numbers, it becomes difficult for users + to use a slider accurately for very large ranges. + + A slider accepts focus on Tab and provides both a mouse wheel and a + keyboard interface. The keyboard interface is the following: + + \list + \o Left/Right move a horizontal slider by one single step. + \o Up/Down move a vertical slider by one single step. + \o PageUp moves up one page. + \o PageDown moves down one page. + \o Home moves to the start (mininum). + \o End moves to the end (maximum). + \endlist + + \table 100% + \row \o \inlineimage macintosh-slider.png Screenshot of a Macintosh slider + \o A slider shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \row \o \inlineimage windows-slider.png Screenshot of a Windows XP slider + \o A slider shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage plastique-slider.png Screenshot of a Plastique slider + \o A slider shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \endtable + + \sa QScrollBar, QSpinBox, QDial, {fowler}{GUI Design Handbook: Slider}, {Sliders Example} +*/ + + +/*! + \enum QSlider::TickPosition + + This enum specifies where the tick marks are to be drawn relative + to the slider's groove and the handle the user moves. + + \value NoTicks Do not draw any tick marks. + \value TicksBothSides Draw tick marks on both sides of the groove. + \value TicksAbove Draw tick marks above the (horizontal) slider + \value TicksBelow Draw tick marks below the (horizontal) slider + \value TicksLeft Draw tick marks to the left of the (vertical) slider + \value TicksRight Draw tick marks to the right of the (vertical) slider + + \omitvalue NoMarks + \omitvalue Above + \omitvalue Left + \omitvalue Below + \omitvalue Right + \omitvalue Both +*/ + + +/*! + Constructs a vertical slider with the given \a parent. +*/ +QSlider::QSlider(QWidget *parent) + : QAbstractSlider(*new QSliderPrivate, parent) +{ + d_func()->orientation = Qt::Vertical; + d_func()->init(); +} + +/*! + Constructs a slider with the given \a parent. The \a orientation + parameter determines whether the slider is horizontal or vertical; + the valid values are Qt::Vertical and Qt::Horizontal. +*/ + +QSlider::QSlider(Qt::Orientation orientation, QWidget *parent) + : QAbstractSlider(*new QSliderPrivate, parent) +{ + d_func()->orientation = orientation; + d_func()->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use QSlider() and QObject::setObjectName() instead. + + \oldcode + QSlider *mySlider = new QSlider(parent, name); + \newcode + QSlider *mySlider = new QSlider(parent); + mySlider->setObjectName(name); + \endcode +*/ +QSlider::QSlider(QWidget *parent, const char *name) + : QAbstractSlider(*new QSliderPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + d_func()->orientation = Qt::Vertical; + d_func()->init(); +} + +/*! + Use QSlider() and QObject::setObjectName() instead. + + \oldcode + QSlider *mySlider = new QSlider(orientation, parent, name); + \newcode + QSlider *mySlider = new QSlider(orientation, parent); + mySlider->setObjectName(name); + \endcode +*/ +QSlider::QSlider(Qt::Orientation orientation, QWidget *parent, const char *name) + : QAbstractSlider(*new QSliderPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + d_func()->orientation = orientation; + d_func()->init(); +} + +/*! + Use QSlider(), QObject::setObjectName() and the functionality + inherited from QAbstractSlider instead. + + \oldcode + QSlider *mySlider = new QSlider(minValue, maxValue, pageStep, + value, orientation, parent, name); + \newcode + QSlider *mySlider = new QSlider(orientation, parent); + mySlider->setObjectName(name); + mySlider->setMinimum(minValue); + mySlider->setMaximum(maxValue); + mySlider->setPageStep(pageStep); + mySlider->setValue(value); + \endcode +*/ +QSlider::QSlider(int minValue, int maxValue, int pageStep, int value, Qt::Orientation orientation, + QWidget *parent, const char *name) + : QAbstractSlider(*new QSliderPrivate, parent) +{ + Q_D(QSlider); + setObjectName(QString::fromAscii(name)); + d->minimum = minValue; + d->maximum = maxValue; + d->pageStep = pageStep; + d->position = d->value = value; + d->orientation = orientation; + d->init(); +} +#endif + +/*! + Destroys this slider. +*/ +QSlider::~QSlider() +{ +} + +/*! + \reimp +*/ +void QSlider::paintEvent(QPaintEvent *) +{ + Q_D(QSlider); + QPainter p(this); + QStyleOptionSlider opt; + initStyleOption(&opt); + + opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + if (d->tickPosition != NoTicks) + opt.subControls |= QStyle::SC_SliderTickmarks; + if (d->pressedControl) { + opt.activeSubControls = d->pressedControl; + opt.state |= QStyle::State_Sunken; + } else { + opt.activeSubControls = d->hoverControl; + } + + style()->drawComplexControl(QStyle::CC_Slider, &opt, &p, this); +} + +/*! + \reimp +*/ + +bool QSlider::event(QEvent *event) +{ + Q_D(QSlider); + + switch(event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) + d->updateHoverControl(he->pos()); + break; + case QEvent::StyleChange: + case QEvent::MacSizeChange: + d->resetLayoutItemMargins(); + break; + default: + break; + } + return QAbstractSlider::event(event); +} + +/*! + \reimp +*/ +void QSlider::mousePressEvent(QMouseEvent *ev) +{ + Q_D(QSlider); + if (d->maximum == d->minimum || (ev->buttons() ^ ev->button())) { + ev->ignore(); + return; + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + setEditFocus(true); +#endif + ev->accept(); + if ((ev->button() & style()->styleHint(QStyle::SH_Slider_AbsoluteSetButtons)) == ev->button()) { + QStyleOptionSlider opt; + initStyleOption(&opt); + const QRect sliderRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + const QPoint center = sliderRect.center() - sliderRect.topLeft(); + // to take half of the slider off for the setSliderPosition call we use the center - topLeft + + setSliderPosition(d->pixelPosToRangeValue(d->pick(ev->pos() - center))); + triggerAction(SliderMove); + setRepeatAction(SliderNoAction); + d->pressedControl = QStyle::SC_SliderHandle; + update(); + } else if ((ev->button() & style()->styleHint(QStyle::SH_Slider_PageSetButtons)) == ev->button()) { + QStyleOptionSlider opt; + initStyleOption(&opt); + d->pressedControl = style()->hitTestComplexControl(QStyle::CC_Slider, + &opt, ev->pos(), this); + SliderAction action = SliderNoAction; + if (d->pressedControl == QStyle::SC_SliderGroove) { + const QRect sliderRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + int pressValue = d->pixelPosToRangeValue(d->pick(ev->pos() - sliderRect.center() + sliderRect.topLeft())); + d->pressValue = pressValue; + if (pressValue > d->value) + action = SliderPageStepAdd; + else if (pressValue < d->value) + action = SliderPageStepSub; + if (action) { + triggerAction(action); + setRepeatAction(action); + } + } + } else { + ev->ignore(); + return; + } + + if (d->pressedControl == QStyle::SC_SliderHandle) { + QStyleOptionSlider opt; + initStyleOption(&opt); + setRepeatAction(SliderNoAction); + QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + d->clickOffset = d->pick(ev->pos() - sr.topLeft()); + d->snapBackPosition = d->position; + update(sr); + setSliderDown(true); + } +} + +/*! + \reimp +*/ +void QSlider::mouseMoveEvent(QMouseEvent *ev) +{ + Q_D(QSlider); + if (d->pressedControl != QStyle::SC_SliderHandle) { + ev->ignore(); + return; + } + ev->accept(); + int newPosition = d->pixelPosToRangeValue(d->pick(ev->pos()) - d->clickOffset); + QStyleOptionSlider opt; + initStyleOption(&opt); + int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this); + if (m >= 0) { + QRect r = rect(); + r.adjust(-m, -m, m, m); + if (!r.contains(ev->pos())) { + newPosition = d->snapBackPosition; + } + } + setSliderPosition(newPosition); +} + + +/*! + \reimp +*/ +void QSlider::mouseReleaseEvent(QMouseEvent *ev) +{ + Q_D(QSlider); + if (d->pressedControl == QStyle::SC_None || ev->buttons()) { + ev->ignore(); + return; + } + ev->accept(); + QStyle::SubControl oldPressed = QStyle::SubControl(d->pressedControl); + d->pressedControl = QStyle::SC_None; + setRepeatAction(SliderNoAction); + if (oldPressed == QStyle::SC_SliderHandle) + setSliderDown(false); + QStyleOptionSlider opt; + initStyleOption(&opt); + opt.subControls = oldPressed; + update(style()->subControlRect(QStyle::CC_Slider, &opt, oldPressed, this)); +} + +/*! + \reimp +*/ +QSize QSlider::sizeHint() const +{ + Q_D(const QSlider); + ensurePolished(); + const int SliderLength = 84, TickSpace = 5; + QStyleOptionSlider opt; + initStyleOption(&opt); + int thick = style()->pixelMetric(QStyle::PM_SliderThickness, &opt, this); + if (d->tickPosition & TicksAbove) + thick += TickSpace; + if (d->tickPosition & TicksBelow) + thick += TickSpace; + int w = thick, h = SliderLength; + if (d->orientation == Qt::Horizontal) { + w = SliderLength; + h = thick; + } + return style()->sizeFromContents(QStyle::CT_Slider, &opt, QSize(w, h), this).expandedTo(QApplication::globalStrut()); +} + +/*! + \reimp +*/ +QSize QSlider::minimumSizeHint() const +{ + Q_D(const QSlider); + QSize s = sizeHint(); + QStyleOptionSlider opt; + initStyleOption(&opt); + int length = style()->pixelMetric(QStyle::PM_SliderLength, &opt, this); + if (d->orientation == Qt::Horizontal) + s.setWidth(length); + else + s.setHeight(length); + return s; +} + +/*! + \property QSlider::tickPosition + \brief the tickmark position for this slider + + The valid values are described by the QSlider::TickPosition enum. + + The default value is \l QSlider::NoTicks. + + \sa tickInterval +*/ + +void QSlider::setTickPosition(TickPosition position) +{ + Q_D(QSlider); + d->tickPosition = position; + d->resetLayoutItemMargins(); + update(); + updateGeometry(); +} + +QSlider::TickPosition QSlider::tickPosition() const +{ + return d_func()->tickPosition; +} + +/*! + \fn TickPosition QSlider::tickmarks() const + \compat + + Use tickPosition() instead. +*/ + +/*! + \fn QSlider::setTickmarks(TickPosition position) + \compat + + Use setTickPosition() instead. +*/ + +/*! + \property QSlider::tickInterval + \brief the interval between tickmarks + + This is a value interval, not a pixel interval. If it is 0, the + slider will choose between lineStep() and pageStep(). + + The default value is 0. + + \sa tickPosition, lineStep(), pageStep() +*/ + +void QSlider::setTickInterval(int ts) +{ + d_func()->tickInterval = qMax(0, ts); + update(); +} + +int QSlider::tickInterval() const +{ + return d_func()->tickInterval; +} + +/*! + \fn void QSlider::addStep() + + Use setValue() instead. +*/ + +/*! + \fn void QSlider::subtractStep() + + Use setValue() instead. +*/ + +/*! \internal + Returns the style option for slider. +*/ +Q_GUI_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider) +{ + QStyleOptionSlider sliderOption; + slider->initStyleOption(&sliderOption); + return sliderOption; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qslider.h b/src/gui/widgets/qslider.h new file mode 100644 index 0000000..14f763a --- /dev/null +++ b/src/gui/widgets/qslider.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSLIDER_H +#define QSLIDER_H + +#include <QtGui/qabstractslider.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SLIDER + +class QSliderPrivate; +class QStyleOptionSlider; +class Q_GUI_EXPORT QSlider : public QAbstractSlider +{ + Q_OBJECT + + Q_ENUMS(TickPosition) + Q_PROPERTY(TickPosition tickPosition READ tickPosition WRITE setTickPosition) + Q_PROPERTY(int tickInterval READ tickInterval WRITE setTickInterval) + +public: + enum TickPosition { + NoTicks = 0, + TicksAbove = 1, + TicksLeft = TicksAbove, + TicksBelow = 2, + TicksRight = TicksBelow, + TicksBothSides = 3 + +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + ,NoMarks = NoTicks, + Above = TicksAbove, + Left = TicksAbove, + Below = TicksBelow, + Right = TicksRight, + Both = TicksBothSides +#endif + }; + + explicit QSlider(QWidget *parent = 0); + explicit QSlider(Qt::Orientation orientation, QWidget *parent = 0); + + ~QSlider(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setTickPosition(TickPosition position); + TickPosition tickPosition() const; + + void setTickInterval(int ti); + int tickInterval() const; + + bool event(QEvent *event); + +protected: + void paintEvent(QPaintEvent *ev); + void mousePressEvent(QMouseEvent *ev); + void mouseReleaseEvent(QMouseEvent *ev); + void mouseMoveEvent(QMouseEvent *ev); + void initStyleOption(QStyleOptionSlider *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QSlider(QWidget *parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QSlider(Qt::Orientation, QWidget *parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QSlider(int minValue, int maxValue, int pageStep, int value, + Qt::Orientation orientation, + QWidget *parent = 0, const char *name = 0); + inline QT3_SUPPORT void setTickmarks(TickPosition position) { setTickPosition(position); } + inline QT3_SUPPORT TickPosition tickmarks() const { return tickPosition(); } +public Q_SLOTS: + inline QT_MOC_COMPAT void addStep() { triggerAction(SliderSingleStepAdd); }; + inline QT_MOC_COMPAT void subtractStep() { triggerAction(SliderSingleStepSub); }; +#endif + +private: + friend Q_GUI_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider); + + Q_DISABLE_COPY(QSlider) + Q_DECLARE_PRIVATE(QSlider) +}; + +#endif // QT_NO_SLIDER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSLIDER_H diff --git a/src/gui/widgets/qspinbox.cpp b/src/gui/widgets/qspinbox.cpp new file mode 100644 index 0000000..c691eaf --- /dev/null +++ b/src/gui/widgets/qspinbox.cpp @@ -0,0 +1,1536 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qabstractspinbox_p.h> +#include <qspinbox.h> + +#ifndef QT_NO_SPINBOX + +#include <qlineedit.h> +#include <qlocale.h> +#include <qvalidator.h> +#include <qdebug.h> + +#include <math.h> + +QT_BEGIN_NAMESPACE + +//#define QSPINBOX_QSBDEBUG +#ifdef QSPINBOX_QSBDEBUG +# define QSBDEBUG qDebug +#else +# define QSBDEBUG if (false) qDebug +#endif + +static bool isIntermediateValueHelper(qint64 num, qint64 minimum, qint64 maximum, qint64 *match = 0); + +class QSpinBoxPrivate : public QAbstractSpinBoxPrivate +{ + Q_DECLARE_PUBLIC(QSpinBox) +public: + QSpinBoxPrivate(QWidget *parent = 0); + void emitSignals(EmitPolicy ep, const QVariant &); + + virtual QVariant valueFromText(const QString &n) const; + virtual QString textFromValue(const QVariant &n) const; + QVariant validateAndInterpret(QString &input, int &pos, + QValidator::State &state) const; + bool isIntermediateValue(const QString &str) const; + QChar thousand; + + inline void init() { + setLayoutItemMargins(QStyle::SE_SpinBoxLayoutItem); + } +}; + +class QDoubleSpinBoxPrivate : public QAbstractSpinBoxPrivate +{ + Q_DECLARE_PUBLIC(QDoubleSpinBox) +public: + QDoubleSpinBoxPrivate(QWidget *parent = 0); + void emitSignals(EmitPolicy ep, const QVariant &); + bool isIntermediateValue(const QString &str) const; + + virtual QVariant valueFromText(const QString &n) const; + virtual QString textFromValue(const QVariant &n) const; + QVariant validateAndInterpret(QString &input, int &pos, + QValidator::State &state) const; + double round(double input) const; + // variables + int decimals; + QChar delimiter, thousand; +}; + + +/*! + \class QSpinBox + \brief The QSpinBox class provides a spin box widget. + + \ingroup basicwidgets + \mainclass + + QSpinBox is designed to handle integers and discrete sets of + values (e.g., month names); use QDoubleSpinBox for floating point + values. + + QSpinBox allows the user to choose a value by clicking the up/down + buttons or pressing up/down on the keyboard to increase/decrease + the value currently displayed. The user can also type the value in + manually. The spin box supports integer values but can be extended to + use different strings with validate(), textFromValue() and valueFromText(). + + Every time the value changes QSpinBox emits the valueChanged() + signals. The current value can be fetched with value() and set + with setValue(). + + Clicking the up/down buttons or using the keyboard accelerator's + up and down arrows will increase or decrease the current value in + steps of size singleStep(). If you want to change this behaviour you + can reimplement the virtual function stepBy(). The minimum and + maximum value and the step size can be set using one of the + constructors, and can be changed later with setMinimum(), + setMaximum() and setSingleStep(). + + Most spin boxes are directional, but QSpinBox can also operate as + a circular spin box, i.e. if the range is 0-99 and the current + value is 99, clicking "up" will give 0 if wrapping() is set to + true. Use setWrapping() if you want circular behavior. + + The displayed value can be prepended and appended with arbitrary + strings indicating, for example, currency or the unit of + measurement. See setPrefix() and setSuffix(). The text in the spin + box is retrieved with text() (which includes any prefix() and + suffix()), or with cleanText() (which has no prefix(), no suffix() + and no leading or trailing whitespace). + + It is often desirable to give the user a special (often default) + choice in addition to the range of numeric values. See + setSpecialValueText() for how to do this with QSpinBox. + + \table 100% + \row \o \inlineimage windowsxp-spinbox.png Screenshot of a Windows XP spin box + \o A spin box shown in the \l{Windows XP Style Widget Gallery}{Windows XP widget style}. + \row \o \inlineimage plastique-spinbox.png Screenshot of a Plastique spin box + \o A spin box shown in the \l{Plastique Style Widget Gallery}{Plastique widget style}. + \row \o \inlineimage macintosh-spinbox.png Screenshot of a Macintosh spin box + \o A spin box shown in the \l{Macintosh Style Widget Gallery}{Macintosh widget style}. + \endtable + + \section1 Subclassing QSpinBox + + If using prefix(), suffix(), and specialValueText() don't provide + enough control, you subclass QSpinBox and reimplement + valueFromText() and textFromValue(). For example, here's the code + for a custom spin box that allows the user to enter icon sizes + (e.g., "32 x 32"): + + \snippet examples/widgets/icons/iconsizespinbox.cpp 1 + \codeline + \snippet examples/widgets/icons/iconsizespinbox.cpp 2 + + See the \l{widgets/icons}{Icons} example for the full source + code. + + \sa QDoubleSpinBox, QDateTimeEdit, QSlider, {Spin Boxes Example} +*/ + +/*! + \fn void QSpinBox::valueChanged(int i) + + This signal is emitted whenever the spin box's value is changed. + The new value's integer value is passed in \a i. +*/ + +/*! + \fn void QSpinBox::valueChanged(const QString &text) + + \overload + + The new value is passed literally in \a text with no prefix() or + suffix(). +*/ + +/*! + Constructs a spin box with 0 as minimum value and 99 as maximum value, a + step value of 1. The value is initially set to 0. It is parented to \a + parent. + + \sa setMinimum(), setMaximum(), setSingleStep() +*/ + +QSpinBox::QSpinBox(QWidget *parent) + : QAbstractSpinBox(*new QSpinBoxPrivate(parent), parent) +{ + Q_D(QSpinBox); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QSpinBox::QSpinBox(QWidget *parent, const char *name) + : QAbstractSpinBox(*new QSpinBoxPrivate(parent), parent) +{ + Q_D(QSpinBox); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QSpinBox::QSpinBox(int minimum, int maximum, int step, QWidget *parent, const char *name) + : QAbstractSpinBox(*new QSpinBoxPrivate(parent), parent) +{ + Q_D(QSpinBox); + d->minimum = QVariant(qMin<int>(minimum, maximum)); + d->maximum = QVariant(qMax<int>(minimum, maximum)); + d->singleStep = QVariant(step); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +#endif + +/*! + \property QSpinBox::value + \brief the value of the spin box + + setValue() will emit valueChanged() if the new value is different + from the old one. +*/ + +int QSpinBox::value() const +{ + Q_D(const QSpinBox); + return d->value.toInt(); +} + +void QSpinBox::setValue(int value) +{ + Q_D(QSpinBox); + d->setValue(QVariant(value), EmitIfChanged); +} + +/*! + \property QSpinBox::prefix + \brief the spin box's prefix + + The prefix is prepended to the start of the displayed value. + Typical use is to display a unit of measurement or a currency + symbol. For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 0 + + To turn off the prefix display, set this property to an empty + string. The default is no prefix. The prefix is not displayed when + value() == minimum() and specialValueText() is set. + + If no prefix is set, prefix() returns an empty string. + + \sa suffix(), setSuffix(), specialValueText(), setSpecialValueText() +*/ + +QString QSpinBox::prefix() const +{ + Q_D(const QSpinBox); + return d->prefix; +} + +void QSpinBox::setPrefix(const QString &prefix) +{ + Q_D(QSpinBox); + + d->prefix = prefix; + d->updateEdit(); +} + +/*! + \property QSpinBox::suffix + \brief the suffix of the spin box + + The suffix is appended to the end of the displayed value. Typical + use is to display a unit of measurement or a currency symbol. For + example: + + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 1 + + To turn off the suffix display, set this property to an empty + string. The default is no suffix. The suffix is not displayed for + the minimum() if specialValueText() is set. + + If no suffix is set, suffix() returns an empty string. + + \sa prefix(), setPrefix(), specialValueText(), setSpecialValueText() +*/ + +QString QSpinBox::suffix() const +{ + Q_D(const QSpinBox); + + return d->suffix; +} + +void QSpinBox::setSuffix(const QString &suffix) +{ + Q_D(QSpinBox); + + d->suffix = suffix; + d->updateEdit(); +} + +/*! + \property QSpinBox::cleanText + + \brief the text of the spin box excluding any prefix, suffix, + or leading or trailing whitespace. + + \sa text, QSpinBox::prefix, QSpinBox::suffix +*/ + +QString QSpinBox::cleanText() const +{ + Q_D(const QSpinBox); + + return d->stripped(d->edit->displayText()); +} + + +/*! + \property QSpinBox::singleStep + \brief the step value + + When the user uses the arrows to change the spin box's value the + value will be incremented/decremented by the amount of the + singleStep. The default value is 1. Setting a singleStep value of + less than 0 does nothing. +*/ + +int QSpinBox::singleStep() const +{ + Q_D(const QSpinBox); + + return d->singleStep.toInt(); +} + +void QSpinBox::setSingleStep(int value) +{ + Q_D(QSpinBox); + if (value >= 0) { + d->singleStep = QVariant(value); + d->updateEdit(); + } +} + +/*! + \property QSpinBox::minimum + + \brief the minimum value of the spin box + + When setting this property the \l maximum is adjusted + if necessary to ensure that the range remains valid. + + The default minimum value is 0. + + \sa setRange() specialValueText +*/ + +int QSpinBox::minimum() const +{ + Q_D(const QSpinBox); + + return d->minimum.toInt(); +} + +void QSpinBox::setMinimum(int minimum) +{ + Q_D(QSpinBox); + const QVariant m(minimum); + d->setRange(m, (d->variantCompare(d->maximum, m) > 0 ? d->maximum : m)); +} + +/*! + \property QSpinBox::maximum + + \brief the maximum value of the spin box + + When setting this property the \l minimum is adjusted + if necessary, to ensure that the range remains valid. + + The default maximum value is 99. + + \sa setRange() specialValueText + +*/ + +int QSpinBox::maximum() const +{ + Q_D(const QSpinBox); + + return d->maximum.toInt(); +} + +void QSpinBox::setMaximum(int maximum) +{ + Q_D(QSpinBox); + const QVariant m(maximum); + d->setRange((d->variantCompare(d->minimum, m) < 0 ? d->minimum : m), m); +} + +/*! + Convenience function to set the \a minimum, and \a maximum values + with a single function call. + + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 2 + is equivalent to: + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 3 + + \sa minimum maximum +*/ + +void QSpinBox::setRange(int minimum, int maximum) +{ + Q_D(QSpinBox); + d->setRange(QVariant(minimum), QVariant(maximum)); +} + +/*! + This virtual function is used by the spin box whenever it needs + to display the given \a value. The default implementation returns + a string containing \a value printed in the standard way using + QWidget::locale().toString(). Reimplementations may return anything. (See + the example in the detailed description.) + + Note: QSpinBox does not call this function for specialValueText() + and that neither prefix() nor suffix() should be included in the + return value. + + If you reimplement this, you may also need to reimplement + valueFromText() and validate() + + \sa valueFromText(), validate() +*/ + +QString QSpinBox::textFromValue(int value) const +{ + Q_D(const QSpinBox); + QString str = locale().toString(value); + if (qAbs(value) >= 1000 || value == INT_MIN) { + str.remove(d->thousand); + } + + return str; +} + +/*! + \fn int QSpinBox::valueFromText(const QString &text) const + + This virtual function is used by the spin box whenever it needs to + interpret \a text entered by the user as a value. + + Subclasses that need to display spin box values in a non-numeric + way need to reimplement this function. + + Note: QSpinBox handles specialValueText() separately; this + function is only concerned with the other values. + + \sa textFromValue(), validate() +*/ + +int QSpinBox::valueFromText(const QString &text) const +{ + Q_D(const QSpinBox); + + QString copy = text; + int pos = d->edit->cursorPosition(); + QValidator::State state = QValidator::Acceptable; + return d->validateAndInterpret(copy, pos, state).toInt(); +} + +/*! + \reimp +*/ +QValidator::State QSpinBox::validate(QString &text, int &pos) const +{ + Q_D(const QSpinBox); + + QValidator::State state; + d->validateAndInterpret(text, pos, state); + return state; +} + + +/*! + \reimp +*/ +void QSpinBox::fixup(QString &input) const +{ + Q_D(const QSpinBox); + + input.remove(d->thousand); +} + + +// --- QDoubleSpinBox --- + +/*! + \class QDoubleSpinBox + \brief The QDoubleSpinBox class provides a spin box widget that + takes doubles. + + \ingroup basicwidgets + \mainclass + + QDoubleSpinBox allows the user to choose a value by clicking the + up and down buttons or by pressing Up or Down on the keyboard to + increase or decrease the value currently displayed. The user can + also type the value in manually. The spin box supports double + values but can be extended to use different strings with + validate(), textFromValue() and valueFromText(). + + Every time the value changes QDoubleSpinBox emits the + valueChanged() signal. The current value can be fetched with + value() and set with setValue(). + + Note: QDoubleSpinBox will round numbers so they can be displayed + with the current precision. In a QDoubleSpinBox with decimals set + to 2, calling setValue(2.555) will cause value() to return 2.56. + + Clicking the up and down buttons or using the keyboard accelerator's + Up and Down arrows will increase or decrease the current value in + steps of size singleStep(). If you want to change this behavior you + can reimplement the virtual function stepBy(). The minimum and + maximum value and the step size can be set using one of the + constructors, and can be changed later with setMinimum(), + setMaximum() and setSingleStep(). The spinbox has a default + precision of 2 decimal places but this can be changed using + setDecimals(). + + Most spin boxes are directional, but QDoubleSpinBox can also + operate as a circular spin box, i.e. if the range is 0.0-99.9 and + the current value is 99.9, clicking "up" will give 0 if wrapping() + is set to true. Use setWrapping() if you want circular behavior. + + The displayed value can be prepended and appended with arbitrary + strings indicating, for example, currency or the unit of + measurement. See setPrefix() and setSuffix(). The text in the spin + box is retrieved with text() (which includes any prefix() and + suffix()), or with cleanText() (which has no prefix(), no suffix() + and no leading or trailing whitespace). + + It is often desirable to give the user a special (often default) + choice in addition to the range of numeric values. See + setSpecialValueText() for how to do this with QDoubleSpinBox. + + \sa QSpinBox, QDateTimeEdit, QSlider, {Spin Boxes Example} +*/ + +/*! + \fn void QDoubleSpinBox::valueChanged(double d); + + This signal is emitted whenever the spin box's value is changed. + The new value is passed in \a d. +*/ + +/*! + \fn void QDoubleSpinBox::valueChanged(const QString &text); + + \overload + + The new value is passed literally in \a text with no prefix() or + suffix(). +*/ + +/*! + Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value, + a step value of 1.0 and a precision of 2 decimal places. The value is + initially set to 0.00. The spin box has the given \a parent. + + \sa setMinimum(), setMaximum(), setSingleStep() +*/ +QDoubleSpinBox::QDoubleSpinBox(QWidget *parent) + : QAbstractSpinBox(*new QDoubleSpinBoxPrivate(parent), parent) +{ +} + +/*! + \property QDoubleSpinBox::value + \brief the value of the spin box + + setValue() will emit valueChanged() if the new value is different + from the old one. + + Note: The value will be rounded so it can be displayed with the + current setting of decimals. + + \sa decimals +*/ +double QDoubleSpinBox::value() const +{ + Q_D(const QDoubleSpinBox); + + return d->value.toDouble(); +} + +void QDoubleSpinBox::setValue(double value) +{ + Q_D(QDoubleSpinBox); + QVariant v(d->round(value)); + d->setValue(v, EmitIfChanged); +} +/*! + \property QDoubleSpinBox::prefix + \brief the spin box's prefix + + The prefix is prepended to the start of the displayed value. + Typical use is to display a unit of measurement or a currency + symbol. For example: + + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 4 + + To turn off the prefix display, set this property to an empty + string. The default is no prefix. The prefix is not displayed when + value() == minimum() and specialValueText() is set. + + If no prefix is set, prefix() returns an empty string. + + \sa suffix(), setSuffix(), specialValueText(), setSpecialValueText() +*/ + +QString QDoubleSpinBox::prefix() const +{ + Q_D(const QDoubleSpinBox); + + return d->prefix; +} + +void QDoubleSpinBox::setPrefix(const QString &prefix) +{ + Q_D(QDoubleSpinBox); + + d->prefix = prefix; + d->updateEdit(); +} + +/*! + \property QDoubleSpinBox::suffix + \brief the suffix of the spin box + + The suffix is appended to the end of the displayed value. Typical + use is to display a unit of measurement or a currency symbol. For + example: + + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 5 + + To turn off the suffix display, set this property to an empty + string. The default is no suffix. The suffix is not displayed for + the minimum() if specialValueText() is set. + + If no suffix is set, suffix() returns an empty string. + + \sa prefix(), setPrefix(), specialValueText(), setSpecialValueText() +*/ + +QString QDoubleSpinBox::suffix() const +{ + Q_D(const QDoubleSpinBox); + + return d->suffix; +} + +void QDoubleSpinBox::setSuffix(const QString &suffix) +{ + Q_D(QDoubleSpinBox); + + d->suffix = suffix; + d->updateEdit(); +} + +/*! + \property QDoubleSpinBox::cleanText + + \brief the text of the spin box excluding any prefix, suffix, + or leading or trailing whitespace. + + \sa text, QDoubleSpinBox::prefix, QDoubleSpinBox::suffix +*/ + +QString QDoubleSpinBox::cleanText() const +{ + Q_D(const QDoubleSpinBox); + + return d->stripped(d->edit->displayText()); +} + +/*! + \property QDoubleSpinBox::singleStep + \brief the step value + + When the user uses the arrows to change the spin box's value the + value will be incremented/decremented by the amount of the + singleStep. The default value is 1.0. Setting a singleStep value + of less than 0 does nothing. +*/ +double QDoubleSpinBox::singleStep() const +{ + Q_D(const QDoubleSpinBox); + + return d->singleStep.toDouble(); +} + +void QDoubleSpinBox::setSingleStep(double value) +{ + Q_D(QDoubleSpinBox); + + if (value >= 0) { + d->singleStep = value; + d->updateEdit(); + } +} + +/*! + \property QDoubleSpinBox::minimum + + \brief the minimum value of the spin box + + When setting this property the \l maximum is adjusted + if necessary to ensure that the range remains valid. + + The default minimum value is 0.0. + + Note: The minimum value will be rounded to match the decimals + property. + + \sa decimals, setRange() specialValueText +*/ + +double QDoubleSpinBox::minimum() const +{ + Q_D(const QDoubleSpinBox); + + return d->minimum.toDouble(); +} + +void QDoubleSpinBox::setMinimum(double minimum) +{ + Q_D(QDoubleSpinBox); + const QVariant m(d->round(minimum)); + d->setRange(m, (d->variantCompare(d->maximum, m) > 0 ? d->maximum : m)); +} + +/*! + \property QDoubleSpinBox::maximum + + \brief the maximum value of the spin box + + When setting this property the \l minimum is adjusted + if necessary, to ensure that the range remains valid. + + The default maximum value is 99.99. + + Note: The maximum value will be rounded to match the decimals + property. + + \sa decimals, setRange() +*/ + +double QDoubleSpinBox::maximum() const +{ + Q_D(const QDoubleSpinBox); + + return d->maximum.toDouble(); +} + +void QDoubleSpinBox::setMaximum(double maximum) +{ + Q_D(QDoubleSpinBox); + const QVariant m(d->round(maximum)); + d->setRange((d->variantCompare(d->minimum, m) < 0 ? d->minimum : m), m); +} + +/*! + Convenience function to set the \a minimum and \a maximum values + with a single function call. + + Note: The maximum and minimum values will be rounded to match the + decimals property. + + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 6 + is equivalent to: + \snippet doc/src/snippets/code/src_gui_widgets_qspinbox.cpp 7 + + \sa minimum maximum +*/ + +void QDoubleSpinBox::setRange(double minimum, double maximum) +{ + Q_D(QDoubleSpinBox); + d->setRange(QVariant(d->round(minimum)), QVariant(d->round(maximum))); +} + +/*! + \property QDoubleSpinBox::decimals + + \brief the precision of the spin box, in decimals + + Sets how many decimals the spinbox will use for displaying and + interpreting doubles. + + \warning The results might not be reliable with very high values + for \a decimals. + + Note: The maximum, minimum and value might change as a result of + changing this property. +*/ + +int QDoubleSpinBox::decimals() const +{ + Q_D(const QDoubleSpinBox); + + return d->decimals; +} + +void QDoubleSpinBox::setDecimals(int decimals) +{ + Q_D(QDoubleSpinBox); + d->decimals = qMax(0, decimals); + + setRange(minimum(), maximum()); // make sure values are rounded + setValue(value()); +} + +/*! + This virtual function is used by the spin box whenever it needs to + display the given \a value. The default implementation returns a string + containing \a value printed using QWidget::locale().toString(\a value, + QLatin1Char('f'), decimals()) and will remove the thousand + separator. Reimplementations may return anything. + + Note: QDoubleSpinBox does not call this function for + specialValueText() and that neither prefix() nor suffix() should + be included in the return value. + + If you reimplement this, you may also need to reimplement + valueFromText(). + + \sa valueFromText() +*/ + + +QString QDoubleSpinBox::textFromValue(double value) const +{ + Q_D(const QDoubleSpinBox); + QString str = locale().toString(value, 'f', d->decimals); + if (qAbs(value) >= 1000.0) { + str.remove(d->thousand); + } + return str; +} + +/*! + This virtual function is used by the spin box whenever it needs to + interpret \a text entered by the user as a value. + + Subclasses that need to display spin box values in a non-numeric + way need to reimplement this function. + + Note: QDoubleSpinBox handles specialValueText() separately; this + function is only concerned with the other values. + + \sa textFromValue(), validate() +*/ +double QDoubleSpinBox::valueFromText(const QString &text) const +{ + Q_D(const QDoubleSpinBox); + + QString copy = text; + int pos = d->edit->cursorPosition(); + QValidator::State state = QValidator::Acceptable; + return d->validateAndInterpret(copy, pos, state).toDouble(); +} + +/*! + \reimp +*/ +QValidator::State QDoubleSpinBox::validate(QString &text, int &pos) const +{ + Q_D(const QDoubleSpinBox); + + QValidator::State state; + d->validateAndInterpret(text, pos, state); + return state; +} + + +/*! + \reimp +*/ +void QDoubleSpinBox::fixup(QString &input) const +{ + Q_D(const QDoubleSpinBox); + + input.remove(d->thousand); +} + +// --- QSpinBoxPrivate --- + +/*! + \internal + Constructs a QSpinBoxPrivate object +*/ + +QSpinBoxPrivate::QSpinBoxPrivate(QWidget *parent) +{ + minimum = QVariant((int)0); + maximum = QVariant((int)99); + value = minimum; + singleStep = QVariant((int)1); + type = QVariant::Int; + const QString str = (parent ? parent->locale() : QLocale()).toString(4567); + if (str.size() == 5) { + thousand = QChar(str.at(1)); + } + +} + +/*! + \internal + \reimp +*/ + +void QSpinBoxPrivate::emitSignals(EmitPolicy ep, const QVariant &old) +{ + Q_Q(QSpinBox); + if (ep != NeverEmit) { + pendingEmit = false; + if (ep == AlwaysEmit || value != old) { + emit q->valueChanged(edit->displayText()); + emit q->valueChanged(value.toInt()); + } + } +} + +/*! + \internal + \reimp +*/ + +QString QSpinBoxPrivate::textFromValue(const QVariant &value) const +{ + Q_Q(const QSpinBox); + return q->textFromValue(value.toInt()); +} +/*! + \internal + \reimp +*/ + +QVariant QSpinBoxPrivate::valueFromText(const QString &text) const +{ + Q_Q(const QSpinBox); + + return QVariant(q->valueFromText(text)); +} + + +/*! + \internal + + Return true if str can become a number which is between minimum and + maximum or false if this is not possible. +*/ + +bool QSpinBoxPrivate::isIntermediateValue(const QString &str) const +{ + const int num = q_func()->locale().toInt(str, 0, 10); + const int min = minimum.toInt(); + const int max = maximum.toInt(); + + int numDigits = 0; + int digits[10]; + int tmp = num; + if (tmp == 0) { + numDigits = 1; + digits[0] = 0; + } else { + tmp = num; + for (int i=0; tmp != 0; ++i) { + digits[numDigits++] = qAbs(tmp % 10); + tmp /= 10; + } + } + + int failures = 0; + for (int number=min; /*number<=max*/; ++number) { + tmp = number; + for (int i=0; tmp != 0;) { + if (digits[i] == qAbs(tmp % 10)) { + if (++i == numDigits) + return true; + } + tmp /= 10; + } + if (failures++ == 500000) //upper bound + return true; + if (number == max) // needed for INT_MAX + break; + } + return false; +} + +/*! + \internal Multi purpose function that parses input, sets state to + the appropriate state and returns the value it will be interpreted + as. +*/ + +QVariant QSpinBoxPrivate::validateAndInterpret(QString &input, int &pos, + QValidator::State &state) const +{ + if (cachedText == input && !input.isEmpty()) { + state = cachedState; + QSBDEBUG() << "cachedText was" << "'" << cachedText << "'" << "state was " + << state << " and value was " << cachedValue; + + return cachedValue; + } + const int max = maximum.toInt(); + const int min = minimum.toInt(); + + QString copy = stripped(input, &pos); + QSBDEBUG() << "input" << input << "copy" << copy; + state = QValidator::Acceptable; + int num = min; + + if (max != min && (copy.isEmpty() + || (min < 0 && copy == QLatin1String("-")) + || (min >= 0 && copy == QLatin1String("+")))) { + state = QValidator::Intermediate; + QSBDEBUG() << __FILE__ << __LINE__<< "num is set to" << num; + } else if (copy.startsWith(QLatin1String("-")) && min >= 0) { + state = QValidator::Invalid; // special-case -0 will be interpreted as 0 and thus not be invalid with a range from 0-100 + } else { + bool ok = false; + bool removedThousand = false; + num = q_func()->locale().toInt(copy, &ok, 10); + if (!ok && copy.contains(thousand) && (max >= 1000 || min <= -1000)) { + const int s = copy.size(); + copy.remove(thousand); + pos = qMax(0, pos - (s - copy.size())); + removedThousand = true; + num = q_func()->locale().toInt(copy, &ok, 10); + } + QSBDEBUG() << __FILE__ << __LINE__<< "num is set to" << num; + if (!ok) { + state = QValidator::Invalid; + } else if (num >= min && num <= max) { + state = removedThousand ? QValidator::Intermediate : QValidator::Acceptable; + } else if (max == min) { + state = QValidator::Invalid; + } else { + if ((num >= 0 && num > max) || (num < 0 && num < min)) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + } else { + state = isIntermediateValue(copy) ? QValidator::Intermediate : QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to " + << (state == QValidator::Intermediate ? "Intermediate" : "Acceptable"); + } + } + } + if (state != QValidator::Acceptable) + num = max > 0 ? min : max; + input = prefix + copy + suffix; + cachedText = input; + cachedState = state; + cachedValue = QVariant((int)num); + + QSBDEBUG() << "cachedText is set to '" << cachedText << "' state is set to " + << state << " and value is set to " << cachedValue; + return cachedValue; +} + +// --- QDoubleSpinBoxPrivate --- + +/*! + \internal + Constructs a QSpinBoxPrivate object +*/ + +QDoubleSpinBoxPrivate::QDoubleSpinBoxPrivate(QWidget *parent) +{ + minimum = QVariant(0.0); + maximum = QVariant(99.99); + value = minimum; + singleStep = QVariant(1.0); + decimals = 2; + type = QVariant::Double; + const QString str = (parent ? parent->locale() : QLocale()).toString(4567.1); + if (str.size() == 6) { + delimiter = str.at(4); + thousand = QChar((ushort)0); + } else if (str.size() == 7) { + thousand = str.at(1); + delimiter = str.at(5); + } + Q_ASSERT(!delimiter.isNull()); +} + +/*! + \internal + \reimp +*/ + +void QDoubleSpinBoxPrivate::emitSignals(EmitPolicy ep, const QVariant &old) +{ + Q_Q(QDoubleSpinBox); + if (ep != NeverEmit) { + pendingEmit = false; + if (ep == AlwaysEmit || value != old) { + emit q->valueChanged(edit->displayText()); + emit q->valueChanged(value.toDouble()); + } + } +} + + +bool QDoubleSpinBoxPrivate::isIntermediateValue(const QString &str) const +{ + QSBDEBUG() << "input is" << str << minimum << maximum; + qint64 dec = 1; + for (int i=0; i<decimals; ++i) + dec *= 10; + + const QLatin1Char dot('.'); + + // I know QString::number() uses CLocale so I use dot + const QString minstr = QString::number(minimum.toDouble(), 'f', decimals); + bool ok; + qint64 min_left = minstr.left(minstr.indexOf(dot)).toLongLong(&ok); + if (!ok) + return false; + qint64 min_right = minstr.mid(minstr.indexOf(dot) + 1).toLongLong(); + + const QString maxstr = QString::number(maximum.toDouble(), 'f', decimals); + qint64 max_left = maxstr.left(maxstr.indexOf(dot)).toLongLong(&ok); + if (!ok) + return true; + qint64 max_right = maxstr.mid(maxstr.indexOf(dot) + 1).toLongLong(); + + const int dotindex = str.indexOf(delimiter); + const bool negative = maximum.toDouble() < 0; + qint64 left = 0, right = 0; + bool doleft = true; + bool doright = true; + if (dotindex == -1) { + left = str.toLongLong(); + doright = false; + } else if (dotindex == 0 || (dotindex == 1 && str.at(0) == QLatin1Char('+'))) { + if (negative) { + QSBDEBUG() << __FILE__ << __LINE__ << "returns false"; + return false; + } + doleft = false; + right = str.mid(dotindex + 1).toLongLong(); + } else if (dotindex == 1 && str.at(0) == QLatin1Char('-')) { + if (!negative) { + QSBDEBUG() << __FILE__ << __LINE__ << "returns false"; + return false; + } + doleft = false; + right = str.mid(dotindex + 1).toLongLong(); + } else { + left = str.left(dotindex).toLongLong(); + if (dotindex == str.size() - 1) { + doright = false; + } else { + right = str.mid(dotindex + 1).toLongLong(); + } + } + if ((left >= 0 && max_left < 0 && !str.startsWith(QLatin1Char('-'))) || (left < 0 && min_left >= 0)) { + QSBDEBUG("returns false 0"); + return false; + } + + qint64 match = min_left; + if (doleft && !isIntermediateValueHelper(left, min_left, max_left, &match)) { + QSBDEBUG() << __FILE__ << __LINE__ << "returns false"; + return false; + } + if (doright) { + QSBDEBUG("match %lld min_left %lld max_left %lld", match, min_left, max_left); + if (!doleft) { + if (min_left == max_left) { + const bool ret = isIntermediateValueHelper(qAbs(left), + negative ? max_right : min_right, + negative ? min_right : max_right); + QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret; + return ret; + } else if (qAbs(max_left - min_left) == 1) { + const bool ret = isIntermediateValueHelper(qAbs(left), min_right, negative ? 0 : dec) + || isIntermediateValueHelper(qAbs(left), negative ? dec : 0, max_right); + QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret; + return ret; + } else { + const bool ret = isIntermediateValueHelper(qAbs(left), 0, dec); + QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret; + return ret; + } + } + if (match != min_left) { + min_right = negative ? dec : 0; + } + if (match != max_left) { + max_right = negative ? 0 : dec; + } + qint64 tmpl = negative ? max_right : min_right; + qint64 tmpr = negative ? min_right : max_right; + const bool ret = isIntermediateValueHelper(right, tmpl, tmpr); + QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret; + return ret; + } + QSBDEBUG() << __FILE__ << __LINE__ << "returns true"; + return true; +} + +/*! + \internal + \reimp +*/ +QVariant QDoubleSpinBoxPrivate::valueFromText(const QString &f) const +{ + Q_Q(const QDoubleSpinBox); + return QVariant(q->valueFromText(f)); +} + +/*! + \internal + Rounds to a double value that is restricted to decimals. + E.g. // decimals = 2 + + round(5.555) => 5.56 + */ + +double QDoubleSpinBoxPrivate::round(double value) const +{ + Q_Q(const QDoubleSpinBox); + const QString strDbl = q->locale().toString(value, 'f', decimals); + return q->locale().toDouble(strDbl); +} + + +/*! + \internal Multi purpose function that parses input, sets state to + the appropriate state and returns the value it will be interpreted + as. +*/ + +QVariant QDoubleSpinBoxPrivate::validateAndInterpret(QString &input, int &pos, + QValidator::State &state) const +{ + if (cachedText == input && !input.isEmpty()) { + state = cachedState; + QSBDEBUG() << "cachedText was" << "'" << cachedText << "'" << "state was " + << state << " and value was " << cachedValue; + return cachedValue; + } + const double max = maximum.toDouble(); + const double min = minimum.toDouble(); + + QString copy = stripped(input, &pos); + QSBDEBUG() << "input" << input << "copy" << copy; + int len = copy.size(); + double num = min; + const bool plus = max >= 0; + const bool minus = min <= 0; + + switch (len) { + case 0: + state = max != min ? QValidator::Intermediate : QValidator::Invalid; + goto end; + case 1: + if (copy.at(0) == delimiter + || (plus && copy.at(0) == QLatin1Char('+')) + || (minus && copy.at(0) == QLatin1Char('-'))) { + state = QValidator::Intermediate; + goto end; + } + break; + case 2: + if (copy.at(1) == delimiter + && ((plus && copy.at(0) == QLatin1Char('+')) || (minus && copy.at(0) == QLatin1Char('-')))) { + state = QValidator::Intermediate; + goto end; + } + break; + default: break; + } + + if (copy.at(0) == thousand) { + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + state = QValidator::Invalid; + goto end; + } else if (len > 1) { + const int dec = copy.indexOf(delimiter); + if (dec != -1) { + if (dec + 1 < copy.size() && copy.at(dec + 1) == delimiter && pos == dec + 1) { + copy.remove(dec + 1, 1); // typing a delimiter when you are on the delimiter + } // should be treated as typing right arrow + + if (copy.size() - dec > decimals + 1) { + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + state = QValidator::Invalid; + goto end; + } + for (int i=dec + 1; i<copy.size(); ++i) { + if (copy.at(i).isSpace() || copy.at(i) == thousand) { + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + state = QValidator::Invalid; + goto end; + } + } + } else { + const QChar &last = copy.at(len - 1); + const QChar &secondLast = copy.at(len - 2); + if ((last == thousand || last.isSpace()) + && (secondLast == thousand || secondLast.isSpace())) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + goto end; + } else if (last.isSpace() && (!thousand.isSpace() || secondLast.isSpace())) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + goto end; + } + } + } + + { + bool ok = false; + QLocale loc(q_func()->locale()); + num = loc.toDouble(copy, &ok); + QSBDEBUG() << __FILE__ << __LINE__ << loc << copy << num << ok; + bool notAcceptable = false; + + if (!ok) { + if (thousand.isPrint()) { + if (max < 1000 && min > -1000 && copy.contains(thousand)) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + goto end; + } + + const int len = copy.size(); + for (int i=0; i<len- 1; ++i) { + if (copy.at(i) == thousand && copy.at(i + 1) == thousand) { + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + state = QValidator::Invalid; + goto end; + } + } + + const int s = copy.size(); + copy.remove(thousand); + pos = qMax(0, pos - (s - copy.size())); + + + num = loc.toDouble(copy, &ok); + QSBDEBUG() << thousand << num << copy << ok; + + if (!ok) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + goto end; + } + notAcceptable = true; + } + } + + if (!ok) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + } else if (num >= min && num <= max) { + state = notAcceptable ? QValidator::Intermediate : QValidator::Acceptable; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to " + << (state == QValidator::Intermediate ? "Intermediate" : "Acceptable"); + } else if (max == min) { // when max and min is the same the only non-Invalid input is max (or min) + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + } else { + if ((num >= 0 && num > max) || (num < 0 && num < min)) { + state = QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid"; + } else { + state = isIntermediateValue(copy) ? QValidator::Intermediate : QValidator::Invalid; + QSBDEBUG() << __FILE__ << __LINE__<< "state is set to " + << (state == QValidator::Intermediate ? "Intermediate" : "Acceptable"); + } + } + } + +end: + if (state != QValidator::Acceptable) { + num = max > 0 ? min : max; + } + + input = prefix + copy + suffix; + cachedText = input; + cachedState = state; + cachedValue = QVariant(num); + return QVariant(num); +} + +/* + \internal + \reimp +*/ + +QString QDoubleSpinBoxPrivate::textFromValue(const QVariant &f) const +{ + Q_Q(const QDoubleSpinBox); + return q->textFromValue(f.toDouble()); +} + +/*! + \fn void QSpinBox::setLineStep(int step) + + Use setSingleStep() instead. +*/ + +/*! + \fn void QSpinBox::setMaxValue(int value) + + Use setMaximum() instead. +*/ + +/*! + \fn void QSpinBox::setMinValue(int value) + + Use setMinimum() instead. +*/ + +/*! + \fn int QSpinBox::maxValue() const + + Use maximum() instead. +*/ + +/*! + \fn int QSpinBox::minValue() const + + Use minimum() instead. +*/ + +/*! + \internal Returns whether \a str is a string which value cannot be + parsed but still might turn into something valid. +*/ + +static bool isIntermediateValueHelper(qint64 num, qint64 min, qint64 max, qint64 *match) +{ + QSBDEBUG("%lld %lld %lld", num, min, max); + + if (num >= min && num <= max) { + if (match) + *match = num; + QSBDEBUG("returns true 0"); + return true; + } + qint64 tmp = num; + + int numDigits = 0; + int digits[10]; + if (tmp == 0) { + numDigits = 1; + digits[0] = 0; + } else { + tmp = qAbs(num); + for (int i=0; tmp > 0; ++i) { + digits[numDigits++] = tmp % 10; + tmp /= 10; + } + } + + int failures = 0; + qint64 number; + for (number=max; number>=min; --number) { + tmp = qAbs(number); + for (int i=0; tmp > 0;) { + if (digits[i] == (tmp % 10)) { + if (++i == numDigits) { + if (match) + *match = number; + QSBDEBUG("returns true 1"); + return true; + } + } + tmp /= 10; + } + if (failures++ == 500000) { //upper bound + if (match) + *match = num; + QSBDEBUG("returns true 2"); + return true; + } + } + QSBDEBUG("returns false"); + return false; +} + +/*! \reimp */ +bool QSpinBox::event(QEvent *event) +{ + Q_D(QSpinBox); + if (event->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || event->type() == QEvent::MacSizeChange +#endif + ) + d->setLayoutItemMargins(QStyle::SE_SpinBoxLayoutItem); + return QAbstractSpinBox::event(event); +} + +QT_END_NAMESPACE + +#endif // QT_NO_SPINBOX diff --git a/src/gui/widgets/qspinbox.h b/src/gui/widgets/qspinbox.h new file mode 100644 index 0000000..44c88cc --- /dev/null +++ b/src/gui/widgets/qspinbox.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPINBOX_H +#define QSPINBOX_H + +#include <QtGui/qabstractspinbox.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SPINBOX + +class QSpinBoxPrivate; +class Q_GUI_EXPORT QSpinBox : public QAbstractSpinBox +{ + Q_OBJECT + + Q_PROPERTY(QString suffix READ suffix WRITE setSuffix) + Q_PROPERTY(QString prefix READ prefix WRITE setPrefix) + Q_PROPERTY(QString cleanText READ cleanText) + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int maximum READ maximum WRITE setMaximum) + Q_PROPERTY(int singleStep READ singleStep WRITE setSingleStep) + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged USER true) + +public: + explicit QSpinBox(QWidget *parent = 0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QSpinBox(QWidget *parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QSpinBox(int min, int max, int step, QWidget *parent, + const char *name = 0); +#endif + + int value() const; + + QString prefix() const; + void setPrefix(const QString &prefix); + + QString suffix() const; + void setSuffix(const QString &suffix); + + QString cleanText() const; + + int singleStep() const; + void setSingleStep(int val); + + int minimum() const; + void setMinimum(int min); + + int maximum() const; + void setMaximum(int max); + + void setRange(int min, int max); + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void setLineStep(int step) { setSingleStep(step); } + inline QT3_SUPPORT void setMaxValue(int val) { setMaximum(val); } + inline QT3_SUPPORT void setMinValue(int val) { setMinimum(val); } + inline QT3_SUPPORT int maxValue() const { return maximum(); } + inline QT3_SUPPORT int minValue() const { return minimum(); } +#endif + +protected: + bool event(QEvent *event); + virtual QValidator::State validate(QString &input, int &pos) const; + virtual int valueFromText(const QString &text) const; + virtual QString textFromValue(int val) const; + virtual void fixup(QString &str) const; + + +public Q_SLOTS: + void setValue(int val); + +Q_SIGNALS: + void valueChanged(int); + void valueChanged(const QString &); + +private: + Q_DISABLE_COPY(QSpinBox) + Q_DECLARE_PRIVATE(QSpinBox) +}; + +class QDoubleSpinBoxPrivate; +class Q_GUI_EXPORT QDoubleSpinBox : public QAbstractSpinBox +{ + Q_OBJECT + + Q_PROPERTY(QString prefix READ prefix WRITE setPrefix) + Q_PROPERTY(QString suffix READ suffix WRITE setSuffix) + Q_PROPERTY(QString cleanText READ cleanText) + Q_PROPERTY(int decimals READ decimals WRITE setDecimals) + Q_PROPERTY(double minimum READ minimum WRITE setMinimum) + Q_PROPERTY(double maximum READ maximum WRITE setMaximum) + Q_PROPERTY(double singleStep READ singleStep WRITE setSingleStep) + Q_PROPERTY(double value READ value WRITE setValue NOTIFY valueChanged USER true) +public: + explicit QDoubleSpinBox(QWidget *parent = 0); + + double value() const; + + QString prefix() const; + void setPrefix(const QString &prefix); + + QString suffix() const; + void setSuffix(const QString &suffix); + + QString cleanText() const; + + double singleStep() const; + void setSingleStep(double val); + + double minimum() const; + void setMinimum(double min); + + double maximum() const; + void setMaximum(double max); + + void setRange(double min, double max); + + int decimals() const; + void setDecimals(int prec); + + virtual QValidator::State validate(QString &input, int &pos) const; + virtual double valueFromText(const QString &text) const; + virtual QString textFromValue(double val) const; + virtual void fixup(QString &str) const; + +public Q_SLOTS: + void setValue(double val); + +Q_SIGNALS: + void valueChanged(double); + void valueChanged(const QString &); + +private: + Q_DISABLE_COPY(QDoubleSpinBox) + Q_DECLARE_PRIVATE(QDoubleSpinBox) +}; + +#endif // QT_NO_SPINBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSPINBOX_H diff --git a/src/gui/widgets/qsplashscreen.cpp b/src/gui/widgets/qsplashscreen.cpp new file mode 100644 index 0000000..4240096 --- /dev/null +++ b/src/gui/widgets/qsplashscreen.cpp @@ -0,0 +1,350 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsplashscreen.h" + +#ifndef QT_NO_SPLASHSCREEN + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qtextdocument.h" +#include "qtextcursor.h" +#include <QtCore/qdebug.h> +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +class QSplashScreenPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QSplashScreen) +public: + QPixmap pixmap; + QString currStatus; + QColor currColor; + int currAlign; + + inline QSplashScreenPrivate(); + void drawContents(); +}; + +/*! + \class QSplashScreen + \brief The QSplashScreen widget provides a splash screen that can + be shown during application startup. + + \ingroup misc + \mainclass + + A splash screen is a widget that is usually displayed when an + application is being started. Splash screens are often used for + applications that have long start up times (e.g. database or + networking applications that take time to establish connections) to + provide the user with feedback that the application is loading. + + The splash screen appears in the center of the screen. It may be + useful to add the Qt::WindowStaysOnTopHint to the splash widget's + window flags if you want to keep it above all the other windows on + the desktop. + + Some X11 window managers do not support the "stays on top" flag. A + solution is to set up a timer that periodically calls raise() on + the splash screen to simulate the "stays on top" effect. + + The most common usage is to show a splash screen before the main + widget is displayed on the screen. This is illustrated in the + following code snippet in which a splash screen is displayed and + some initialization tasks are performed before the application's + main window is shown: + + \snippet doc/src/snippets/qsplashscreen/main.cpp 0 + \dots + \snippet doc/src/snippets/qsplashscreen/main.cpp 1 + + The user can hide the splash screen by clicking on it with the + mouse. Since the splash screen is typically displayed before the + event loop has started running, it is necessary to periodically + call QApplication::processEvents() to receive the mouse clicks. + + It is sometimes useful to update the splash screen with messages, + for example, announcing connections established or modules loaded + as the application starts up: + + \snippet doc/src/snippets/code/src_gui_widgets_qsplashscreen.cpp 0 + + QSplashScreen supports this with the showMessage() function. If you + wish to do your own drawing you can get a pointer to the pixmap + used in the splash screen with pixmap(). Alternatively, you can + subclass QSplashScreen and reimplement drawContents(). +*/ + +/*! + Construct a splash screen that will display the \a pixmap. + + There should be no need to set the widget flags, \a f, except + perhaps Qt::WindowStaysOnTopHint. +*/ +QSplashScreen::QSplashScreen(const QPixmap &pixmap, Qt::WindowFlags f) + : QWidget(*(new QSplashScreenPrivate()), 0, Qt::SplashScreen | f) +{ + d_func()->pixmap = pixmap; + setPixmap(d_func()->pixmap); // Does an implicit repaint +} + +/*! + \overload + + This function allows you to specify a parent for your splashscreen. The + typical use for this constructor is if you have a multiple screens and + prefer to have the splash screen on a different screen than your primary + one. In that case pass the proper desktop() as the \a parent. +*/ +QSplashScreen::QSplashScreen(QWidget *parent, const QPixmap &pixmap, Qt::WindowFlags f) + : QWidget(*new QSplashScreenPrivate, parent, Qt::SplashScreen | f) +{ + d_func()->pixmap = pixmap; + setPixmap(d_func()->pixmap); // Does an implicit repaint +} + +/*! + Destructor. +*/ +QSplashScreen::~QSplashScreen() +{ +} + +/*! + \reimp +*/ +void QSplashScreen::mousePressEvent(QMouseEvent *) +{ + hide(); +} + +/*! + This overrides QWidget::repaint(). It differs from the standard + repaint function in that it also calls QApplication::flush() to + ensure the updates are displayed, even when there is no event loop + present. +*/ +void QSplashScreen::repaint() +{ + d_func()->drawContents(); + QWidget::repaint(); + QApplication::flush(); +} + +/*! + \fn QSplashScreen::messageChanged(const QString &message) + + This signal is emitted when the message on the splash screen + changes. \a message is the new message and is a null-string + when the message has been removed. + + \sa showMessage(), clearMessage() +*/ + + + +/*! + Draws the \a message text onto the splash screen with color \a + color and aligns the text according to the flags in \a alignment. + + \sa Qt::Alignment, clearMessage() +*/ +void QSplashScreen::showMessage(const QString &message, int alignment, + const QColor &color) +{ + Q_D(QSplashScreen); + d->currStatus = message; + d->currAlign = alignment; + d->currColor = color; + emit messageChanged(d->currStatus); + repaint(); +} + +/*! + Removes the message being displayed on the splash screen + + \sa showMessage() + */ +void QSplashScreen::clearMessage() +{ + d_func()->currStatus.clear(); + emit messageChanged(d_func()->currStatus); + repaint(); +} + +/*! + Makes the splash screen wait until the widget \a mainWin is displayed + before calling close() on itself. +*/ +void QSplashScreen::finish(QWidget *mainWin) +{ + if (mainWin) { +#if defined(Q_WS_X11) + extern void qt_x11_wait_for_window_manager(QWidget *mainWin); + qt_x11_wait_for_window_manager(mainWin); +#endif + } + close(); +} + +/*! + Sets the pixmap that will be used as the splash screen's image to + \a pixmap. +*/ +void QSplashScreen::setPixmap(const QPixmap &pixmap) +{ + Q_D(QSplashScreen); + + if (pixmap.hasAlpha()) { + QPixmap opaque(pixmap.size()); + QPainter p(&opaque); + p.fillRect(0, 0, pixmap.width(), pixmap.height(), palette().background()); + p.drawPixmap(0, 0, pixmap); + p.end(); + d->pixmap = opaque; + } else { + d->pixmap = pixmap; + } + + QRect r(0, 0, d->pixmap.size().width(), d->pixmap.size().height()); + resize(d->pixmap.size()); + move(QApplication::desktop()->screenGeometry().center() - r.center()); + if (!isVisible()) + d->drawContents(); + else + repaint(); +} + +/*! + Returns the pixmap that is used in the splash screen. The image + does not have any of the text drawn by showMessage() calls. +*/ +const QPixmap QSplashScreen::pixmap() const +{ + return d_func()->pixmap; +} + +/*! + \internal +*/ +void QSplashScreenPrivate::drawContents() +{ + Q_Q(QSplashScreen); + QPixmap textPix = pixmap; + if (!textPix.isNull()) { + QPainter painter(&textPix); + painter.initFrom(q); + q->drawContents(&painter); + QPalette p = q->palette(); + p.setBrush(q->backgroundRole(), QBrush(textPix)); + q->setPalette(p); + } +} + +/*! + \internal +*/ +inline QSplashScreenPrivate::QSplashScreenPrivate() : currAlign(Qt::AlignLeft) +{ +} + +/*! + Draw the contents of the splash screen using painter \a painter. + The default implementation draws the message passed by showMessage(). + Reimplement this function if you want to do your own drawing on + the splash screen. +*/ +void QSplashScreen::drawContents(QPainter *painter) +{ + Q_D(QSplashScreen); + painter->setPen(d->currColor); + QRect r = rect(); + r.setRect(r.x() + 5, r.y() + 5, r.width() - 10, r.height() - 10); + if (Qt::mightBeRichText(d->currStatus)) { + QTextDocument doc; +#ifdef QT_NO_TEXTHTMLPARSER + doc.setPlainText(d->currStatus); +#else + doc.setHtml(d->currStatus); +#endif + doc.setTextWidth(r.width()); + QTextCursor cursor(&doc); + cursor.select(QTextCursor::Document); + QTextBlockFormat fmt; + fmt.setAlignment(Qt::Alignment(d->currAlign)); + cursor.mergeBlockFormat(fmt); + painter->save(); + painter->translate(r.topLeft()); + doc.drawContents(painter); + painter->restore(); + } else { + painter->drawText(r, d->currAlign, d->currStatus); + } +} + +/*! + \fn void QSplashScreen::message(const QString &message, int alignment, + const QColor &color) + \compat + + Use showMessage() instead. +*/ + +/*! + \fn void QSplashScreen::clear() + \compat + + Use clearMessage() instead. +*/ + +/*! \reimp */ +bool QSplashScreen::event(QEvent *e) +{ + return QWidget::event(e); +} + +QT_END_NAMESPACE + +#endif //QT_NO_SPLASHSCREEN diff --git a/src/gui/widgets/qsplashscreen.h b/src/gui/widgets/qsplashscreen.h new file mode 100644 index 0000000..3a2be10 --- /dev/null +++ b/src/gui/widgets/qsplashscreen.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPLASHSCREEN_H +#define QSPLASHSCREEN_H + +#include <QtGui/qpixmap.h> +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SPLASHSCREEN +class QSplashScreenPrivate; + +class Q_GUI_EXPORT QSplashScreen : public QWidget +{ + Q_OBJECT +public: + explicit QSplashScreen(const QPixmap &pixmap = QPixmap(), Qt::WindowFlags f = 0); + QSplashScreen(QWidget *parent, const QPixmap &pixmap = QPixmap(), Qt::WindowFlags f = 0); + virtual ~QSplashScreen(); + + void setPixmap(const QPixmap &pixmap); + const QPixmap pixmap() const; + void finish(QWidget *w); + void repaint(); + +public Q_SLOTS: + void showMessage(const QString &message, int alignment = Qt::AlignLeft, + const QColor &color = Qt::black); + void clearMessage(); +#ifdef QT3_SUPPORT + inline QT_MOC_COMPAT void message(const QString &str, int alignment = Qt::AlignLeft, + const QColor &color = Qt::black) { showMessage(str, alignment, color); } + inline QT_MOC_COMPAT void clear() { clearMessage(); } +#endif + +Q_SIGNALS: + void messageChanged(const QString &message); + +protected: + bool event(QEvent *e); + virtual void drawContents(QPainter *painter); + void mousePressEvent(QMouseEvent *); + +private: + Q_DISABLE_COPY(QSplashScreen) + Q_DECLARE_PRIVATE(QSplashScreen) +}; + +#endif // QT_NO_SPLASHSCREEN + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSPLASHSCREEN_H diff --git a/src/gui/widgets/qsplitter.cpp b/src/gui/widgets/qsplitter.cpp new file mode 100644 index 0000000..bf8af35 --- /dev/null +++ b/src/gui/widgets/qsplitter.cpp @@ -0,0 +1,1831 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsplitter.h" +#ifndef QT_NO_SPLITTER + +#include "qapplication.h" +#include "qcursor.h" +#include "qdrawutil.h" +#include "qevent.h" +#include "qlayout.h" +#include "qlist.h" +#include "qpainter.h" +#include "qrubberband.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qtextstream.h" +#include "qvarlengtharray.h" +#include "qvector.h" +#include "private/qlayoutengine_p.h" +#include "private/qsplitter_p.h" +#include "qtimer.h" +#include "qdebug.h" + +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +//#define QSPLITTER_DEBUG + +/*! + \class QSplitterHandle + \brief The QSplitterHandle class provides handle functionality of the splitter. + + \ingroup organizers + + QSplitterHandle is typically what people think about when they think about + a splitter. It is the handle that is used to resize the widgets. + + A typical developer using QSplitter will never have to worry about + QSplitterHandle. It is provided for developers who want splitter handles + that provide extra features, such as popup menus. + + The typical way one would create splitter handles is to subclass QSplitter then + reimplement QSplitter::createHandle() to instantiate the custom splitter + handle. For example, a minimum QSplitter subclass might look like this: + + \snippet doc/src/snippets/splitterhandle/splitter.h 0 + + The \l{QSplitter::}{createHandle()} implementation simply constructs a + custom splitter handle, called \c Splitter in this example: + + \snippet doc/src/snippets/splitterhandle/splitter.cpp 1 + + Information about a given handle can be obtained using functions like + orientation() and opaqueResize(), and is retrieved from its parent splitter. + Details like these can be used to give custom handles different appearances + depending on the splitter's orientation. + + The complexity of a custom handle subclass depends on the tasks that it + needs to perform. A simple subclass might only provide a paintEvent() + implementation: + + \snippet doc/src/snippets/splitterhandle/splitter.cpp 0 + + In this example, a predefined gradient is set up differently depending on + the orientation of the handle. QSplitterHandle provides a reasonable + size hint for the handle, so the subclass does not need to provide a + reimplementation of sizeHint() unless the handle has special size + requirements. + + \sa QSplitter +*/ + +/*! + Creates a QSplitter handle with the given \a orientation and + QSplitter \a parent. +*/ +QSplitterHandle::QSplitterHandle(Qt::Orientation orientation, QSplitter *parent) + : QWidget(*new QSplitterHandlePrivate, parent, 0) +{ + Q_D(QSplitterHandle); + d->s = parent; + d->hover = false; + setOrientation(orientation); +} + +/*! + Sets the orientation of the splitter handle to \a orientation. + This is usually propogated from the QSplitter. + + \sa QSplitter::setOrientation() +*/ +void QSplitterHandle::setOrientation(Qt::Orientation orientation) +{ + Q_D(QSplitterHandle); + d->orient = orientation; +#ifndef QT_NO_CURSOR + setCursor(orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor); +#endif +} + +/*! + Returns the handle's orientation. This is usually propagated from the QSplitter. + + \sa QSplitter::orientation() +*/ +Qt::Orientation QSplitterHandle::orientation() const +{ + Q_D(const QSplitterHandle); + return d->orient; +} + + +/*! + Returns true if widgets are resized dynamically (opaquely), otherwise + returns false. This value is controlled by the QSplitter. + + \sa QSplitter::opaqueResize() + +*/ +bool QSplitterHandle::opaqueResize() const +{ + Q_D(const QSplitterHandle); + return d->s->opaqueResize(); +} + + +/*! + Returns the splitter associated with this splitter handle. + + \sa QSplitter::handle() +*/ +QSplitter *QSplitterHandle::splitter() const +{ + return d_func()->s; +} + +/*! + Tells the splitter to move this handle to position \a pos, which is + the distance from the left or top edge of the widget. + + Note that \a pos is also measured from the left (or top) for + right-to-left languages. This function will map \a pos to the + appropriate position before calling QSplitter::moveSplitter(). + + \sa QSplitter::moveSplitter() closestLegalPosition() +*/ +void QSplitterHandle::moveSplitter(int pos) +{ + Q_D(QSplitterHandle); + if (d->s->isRightToLeft() && d->orient == Qt::Horizontal) + pos = d->s->contentsRect().width() - pos; + d->s->moveSplitter(pos, d->s->indexOf(this)); +} + +/*! + Returns the closest legal position to \a pos of the splitter + handle. The positions are measured from the left or top edge of + the splitter, even for right-to-left languages. + + \sa QSplitter::closestLegalPosition(), moveSplitter() +*/ + +int QSplitterHandle::closestLegalPosition(int pos) +{ + Q_D(QSplitterHandle); + QSplitter *s = d->s; + if (s->isRightToLeft() && d->orient == Qt::Horizontal) { + int w = s->contentsRect().width(); + return w - s->closestLegalPosition(w - pos, s->indexOf(this)); + } + return s->closestLegalPosition(pos, s->indexOf(this)); +} + +/*! + \reimp +*/ +QSize QSplitterHandle::sizeHint() const +{ + Q_D(const QSplitterHandle); + int hw = d->s->handleWidth(); + QStyleOption opt(0); + opt.init(d->s); + opt.state = QStyle::State_None; + return parentWidget()->style()->sizeFromContents(QStyle::CT_Splitter, &opt, QSize(hw, hw), d->s) + .expandedTo(QApplication::globalStrut()); +} + +/*! + \reimp +*/ +bool QSplitterHandle::event(QEvent *event) +{ + Q_D(QSplitterHandle); + switch(event->type()) { + case QEvent::HoverEnter: + d->hover = true; + update(); + break; + case QEvent::HoverLeave: + d->hover = false; + update(); + break; + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +void QSplitterHandle::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QSplitterHandle); + if (!(e->buttons() & Qt::LeftButton)) + return; + int pos = d->pick(parentWidget()->mapFromGlobal(e->globalPos())) + - d->mouseOffset; + if (opaqueResize()) { + moveSplitter(pos); + } else { + d->s->setRubberBand(closestLegalPosition(pos)); + } +} + +/*! + \reimp +*/ +void QSplitterHandle::mousePressEvent(QMouseEvent *e) +{ + Q_D(QSplitterHandle); + if (e->button() == Qt::LeftButton) + d->mouseOffset = d->pick(e->pos()); +} + +/*! + \reimp +*/ +void QSplitterHandle::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QSplitterHandle); + if (!opaqueResize() && e->button() == Qt::LeftButton) { + int pos = d->pick(parentWidget()->mapFromGlobal(e->globalPos())) + - d->mouseOffset; + d->s->setRubberBand(-1); + moveSplitter(pos); + } +} + +/*! + \reimp +*/ +void QSplitterHandle::paintEvent(QPaintEvent *) +{ + Q_D(QSplitterHandle); + QPainter p(this); + QStyleOption opt(0); + opt.rect = rect(); + opt.palette = palette(); + if (orientation() == Qt::Horizontal) + opt.state = QStyle::State_Horizontal; + else + opt.state = QStyle::State_None; + if (d->hover) + opt.state |= QStyle::State_MouseOver; + if (isEnabled()) + opt.state |= QStyle::State_Enabled; + parentWidget()->style()->drawControl(QStyle::CE_Splitter, &opt, &p, d->s); +} + + +int QSplitterLayoutStruct::getWidgetSize(Qt::Orientation orient) +{ + if (sizer == -1) { + QSize s = widget->sizeHint(); + const int presizer = pick(s, orient); + const int realsize = pick(widget->size(), orient); + if (!s.isValid() || (widget->testAttribute(Qt::WA_Resized) && (realsize > presizer))) { + sizer = pick(widget->size(), orient); + } else { + sizer = presizer; + } + QSizePolicy p = widget->sizePolicy(); + int sf = (orient == Qt::Horizontal) ? p.horizontalStretch() : p.verticalStretch(); + if (sf > 1) + sizer *= sf; + } + return sizer; +} + +int QSplitterLayoutStruct::getHandleSize(Qt::Orientation orient) +{ + return pick(handle->sizeHint(), orient); +} + +void QSplitterPrivate::init() +{ + Q_Q(QSplitter); + QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Preferred); + if (orient == Qt::Vertical) + sp.transpose(); + q->setSizePolicy(sp); + q->setAttribute(Qt::WA_WState_OwnSizePolicy, false); +} + +void QSplitterPrivate::recalc(bool update) +{ + Q_Q(QSplitter); + int n = list.count(); + /* + Splitter handles before the first visible widget or right + before a hidden widget must be hidden. + */ + bool first = true; + for (int i = 0; i < n ; ++i) { + QSplitterLayoutStruct *s = list.at(i); + s->handle->setHidden(first || s->widget->isHidden()); + if (!s->widget->isHidden()) + first = false; + } + + int fi = 2 * q->frameWidth(); + int maxl = fi; + int minl = fi; + int maxt = QWIDGETSIZE_MAX; + int mint = fi; + /* + calculate min/max sizes for the whole splitter + */ + bool empty = true; + for (int j = 0; j < n; j++) { + QSplitterLayoutStruct *s = list.at(j); + + if (!s->widget->isHidden()) { + empty = false; + if (!s->handle->isHidden()) { + minl += s->getHandleSize(orient); + maxl += s->getHandleSize(orient); + } + + QSize minS = qSmartMinSize(s->widget); + minl += pick(minS); + maxl += pick(s->widget->maximumSize()); + mint = qMax(mint, trans(minS)); + int tm = trans(s->widget->maximumSize()); + if (tm > 0) + maxt = qMin(maxt, tm); + } + } + + if (empty) { + if (qobject_cast<QSplitter *>(q->parentWidget())) { + // nested splitters; be nice + maxl = maxt = 0; + } else { + // QSplitter with no children yet + maxl = QWIDGETSIZE_MAX; + } + } else { + maxl = qMin<int>(maxl, QWIDGETSIZE_MAX); + } + if (maxt < mint) + maxt = mint; + + if (update) { + if (orient == Qt::Horizontal) { + q->setMaximumSize(maxl, maxt); + if (q->isWindow()) + q->setMinimumSize(minl,mint); + } else { + q->setMaximumSize(maxt, maxl); + if (q->isWindow()) + q->setMinimumSize(mint,minl); + } + doResize(); + q->updateGeometry(); + } else { + firstShow = true; + } +} + +void QSplitterPrivate::doResize() +{ + Q_Q(QSplitter); + QRect r = q->contentsRect(); + int n = list.count(); + QVector<QLayoutStruct> a(n*2); + int i; + + bool noStretchFactorsSet = true; + for (i = 0; i < n; ++i) { + QSizePolicy p = list.at(i)->widget->sizePolicy(); + int sf = orient == Qt::Horizontal ? p.horizontalStretch() : p.verticalStretch(); + if (sf != 0) { + noStretchFactorsSet = false; + break; + } + } + + int j=0; + for (i = 0; i < n; ++i) { + QSplitterLayoutStruct *s = list.at(i); +#ifdef QSPLITTER_DEBUG + qDebug("widget %d hidden: %d collapsed: %d handle hidden: %d", i, s->widget->isHidden(), + s->collapsed, s->handle->isHidden()); +#endif + + a[j].init(); + if (s->handle->isHidden()) { + a[j].maximumSize = 0; + } else { + a[j].sizeHint = a[j].minimumSize = a[j].maximumSize = s->getHandleSize(orient); + a[j].empty = false; + } + ++j; + + a[j].init(); + if (s->widget->isHidden() || s->collapsed) { + a[j].maximumSize = 0; + } else { + a[j].minimumSize = pick(qSmartMinSize(s->widget)); + a[j].maximumSize = pick(s->widget->maximumSize()); + a[j].empty = false; + + bool stretch = noStretchFactorsSet; + if (!stretch) { + QSizePolicy p = s->widget->sizePolicy(); + int sf = orient == Qt::Horizontal ? p.horizontalStretch() : p.verticalStretch(); + stretch = (sf != 0); + } + if (stretch) { + a[j].stretch = s->getWidgetSize(orient); + a[j].sizeHint = a[j].minimumSize; + a[j].expansive = true; + } else { + a[j].sizeHint = qMax(s->getWidgetSize(orient), a[j].minimumSize); + } + } + ++j; + } + + qGeomCalc(a, 0, n*2, pick(r.topLeft()), pick(r.size()), 0); + +#ifdef QSPLITTER_DEBUG + for (i = 0; i < n*2; ++i) { + qDebug("%*s%d: stretch %d, sh %d, minS %d, maxS %d, exp %d, emp %d -> %d, %d", + i, "", i, + a[i].stretch, + a[i].sizeHint, + a[i].minimumSize, + a[i].maximumSize, + a[i].expansive, + a[i].empty, + a[i].pos, + a[i].size); + } +#endif + + for (i = 0; i < n; ++i) { + QSplitterLayoutStruct *s = list.at(i); + setGeo(s, a[i*2+1].pos, a[i*2+1].size, false); + } +} + +void QSplitterPrivate::storeSizes() +{ + for (int i = 0; i < list.size(); ++i) { + QSplitterLayoutStruct *sls = list.at(i); + sls->sizer = pick(sls->rect.size()); + } +} + +void QSplitterPrivate::addContribution(int index, int *min, int *max, bool mayCollapse) const +{ + QSplitterLayoutStruct *s = list.at(index); + if (!s->widget->isHidden()) { + if (!s->handle->isHidden()) { + *min += s->getHandleSize(orient); + *max += s->getHandleSize(orient); + } + if (mayCollapse || !s->collapsed) + *min += pick(qSmartMinSize(s->widget)); + + *max += pick(s->widget->maximumSize()); + } +} + +int QSplitterPrivate::findWidgetJustBeforeOrJustAfter(int index, int delta, int &collapsibleSize) const +{ + if (delta < 0) + index += delta; + do { + QWidget *w = list.at(index)->widget; + if (!w->isHidden()) { + if (collapsible(list.at(index))) + collapsibleSize = pick(qSmartMinSize(w)); + return index; + } + index += delta; + } while (index >= 0 && index < list.count()); + + return -1; +} + +/* + For the splitter handle with index \a index, \a min and \a max give the range without collapsing any widgets, + and \a farMin and farMax give the range with collapsing included. +*/ +void QSplitterPrivate::getRange(int index, int *farMin, int *min, int *max, int *farMax) const +{ + Q_Q(const QSplitter); + int n = list.count(); + if (index <= 0 || index >= n) + return; + + int collapsibleSizeBefore = 0; + int idJustBefore = findWidgetJustBeforeOrJustAfter(index, -1, collapsibleSizeBefore); + + int collapsibleSizeAfter = 0; + int idJustAfter = findWidgetJustBeforeOrJustAfter(index, +1, collapsibleSizeAfter); + + int minBefore = 0; + int minAfter = 0; + int maxBefore = 0; + int maxAfter = 0; + int i; + + for (i = 0; i < index; ++i) + addContribution(i, &minBefore, &maxBefore, i == idJustBefore); + for (i = index; i < n; ++i) + addContribution(i, &minAfter, &maxAfter, i == idJustAfter); + + QRect r = q->contentsRect(); + int farMinVal; + int minVal; + int maxVal; + int farMaxVal; + + int smartMinBefore = qMax(minBefore, pick(r.size()) - maxAfter); + int smartMaxBefore = qMin(maxBefore, pick(r.size()) - minAfter); + + minVal = pick(r.topLeft()) + smartMinBefore; + maxVal = pick(r.topLeft()) + smartMaxBefore; + + farMinVal = minVal; + if (minBefore - collapsibleSizeBefore >= pick(r.size()) - maxAfter) + farMinVal -= collapsibleSizeBefore; + farMaxVal = maxVal; + if (pick(r.size()) - (minAfter - collapsibleSizeAfter) <= maxBefore) + farMaxVal += collapsibleSizeAfter; + + if (farMin) + *farMin = farMinVal; + if (min) + *min = minVal; + if (max) + *max = maxVal; + if (farMax) + *farMax = farMaxVal; +} + +int QSplitterPrivate::adjustPos(int pos, int index, int *farMin, int *min, int *max, int *farMax) const +{ + const int Threshold = 40; + + getRange(index, farMin, min, max, farMax); + + if (pos >= *min) { + if (pos <= *max) { + return pos; + } else { + int delta = pos - *max; + int width = *farMax - *max; + + if (delta > width / 2 && delta >= qMin(Threshold, width)) { + return *farMax; + } else { + return *max; + } + } + } else { + int delta = *min - pos; + int width = *min - *farMin; + + if (delta > width / 2 && delta >= qMin(Threshold, width)) { + return *farMin; + } else { + return *min; + } + } +} + +bool QSplitterPrivate::collapsible(QSplitterLayoutStruct *s) const +{ + if (s->collapsible != Default) { + return (bool)s->collapsible; + } else { + return childrenCollapsible; + } +} + +void QSplitterPrivate::updateHandles() +{ + Q_Q(QSplitter); + recalc(q->isVisible()); +} + +void QSplitterPrivate::setSizes_helper(const QList<int> &sizes, bool clampNegativeSize) +{ + int j = 0; + + for (int i = 0; i < list.size(); ++i) { + QSplitterLayoutStruct *s = list.at(i); + + s->collapsed = false; + s->sizer = sizes.value(j++); + if (clampNegativeSize && s->sizer < 0) + s->sizer = 0; + int smartMinSize = pick(qSmartMinSize(s->widget)); + + // Make sure that we reset the collapsed state. + if (s->sizer == 0) { + if (collapsible(s) && smartMinSize > 0) { + s->collapsed = true; + } else { + s->sizer = smartMinSize; + } + } else { + if (s->sizer < smartMinSize) + s->sizer = smartMinSize; + } + } + doResize(); +} + +void QSplitterPrivate::setGeo(QSplitterLayoutStruct *sls, int p, int s, bool allowCollapse) +{ + Q_Q(QSplitter); + QWidget *w = sls->widget; + QRect r; + QRect contents = q->contentsRect(); + if (orient == Qt::Horizontal) { + r.setRect(p, contents.y(), s, contents.height()); + } else { + r.setRect(contents.x(), p, contents.width(), s); + } + sls->rect = r; + + int minSize = pick(qSmartMinSize(w)); + + if (orient == Qt::Horizontal && q->isRightToLeft()) + r.moveRight(contents.width() - r.left()); + + if (allowCollapse) + sls->collapsed = s <= 0 && minSize > 0 && !w->isHidden(); + + // Hide the child widget, but without calling hide() so that + // the splitter handle is still shown. + if (sls->collapsed) + r.moveTopLeft(QPoint(-r.width()-1, -r.height()-1)); + + w->setGeometry(r); + + if (!sls->handle->isHidden()) { + QSplitterHandle *h = sls->handle; + QSize hs = h->sizeHint(); + int left, top, right, bottom; + h->getContentsMargins(&left, &top, &right, &bottom); + if (orient==Qt::Horizontal) { + if (q->isRightToLeft()) + p = contents.width() - p + hs.width(); + h->setGeometry(p-hs.width() - left, contents.y(), hs.width() + left + right, contents.height()); + } else { + h->setGeometry(contents.x(), p-hs.height() - top, contents.width(), hs.height() + top + bottom); + } + } +} + +void QSplitterPrivate::doMove(bool backwards, int hPos, int index, int delta, bool mayCollapse, + int *positions, int *widths) +{ + if (index < 0 || index >= list.count()) + return; + +#ifdef QSPLITTER_DEBUG + qDebug() << "QSplitterPrivate::doMove" << backwards << hPos << index << delta << mayCollapse; +#endif + + QSplitterLayoutStruct *s = list.at(index); + QWidget *w = s->widget; + + int nextId = backwards ? index - delta : index + delta; + + if (w->isHidden()) { + doMove(backwards, hPos, nextId, delta, collapsible(nextId), positions, widths); + } else { + int hs =s->handle->isHidden() ? 0 : s->getHandleSize(orient); + + int ws = backwards ? hPos - pick(s->rect.topLeft()) + : pick(s->rect.bottomRight()) - hPos -hs + 1; + if (ws > 0 || (!s->collapsed && !mayCollapse)) { + ws = qMin(ws, pick(w->maximumSize())); + ws = qMax(ws, pick(qSmartMinSize(w))); + } else { + ws = 0; + } + positions[index] = backwards ? hPos - ws : hPos + hs; + widths[index] = ws; + doMove(backwards, backwards ? hPos - ws - hs : hPos + hs + ws, nextId, delta, + collapsible(nextId), positions, widths); + } + +} + +QSplitterLayoutStruct *QSplitterPrivate::findWidget(QWidget *w) const +{ + for (int i = 0; i < list.size(); ++i) { + if (list.at(i)->widget == w) + return list.at(i); + } + return 0; +} + +#ifdef QT3_SUPPORT +static void setStretch(QWidget *w, int sf) +{ + QSizePolicy sp = w->sizePolicy(); + sp.setHorizontalStretch(sf); + sp.setVerticalStretch(sf); + w->setSizePolicy(sp); +} + +static int getStretch(const QWidget *w) +{ + QSizePolicy sp = w->sizePolicy(); + return qMax(sp.horizontalStretch(), sp.verticalStretch()); +} + +void QSplitter::setResizeMode(QWidget *w, ResizeMode mode) +{ + /* + Internal comment: + + This function tries to simulate the Qt 3.x ResizeMode + behavior using QSizePolicy stretch factors. This isn't easy, + because the default \l ResizeMode was \l Stretch, not \l + KeepSize, whereas the default stetch factor is 0. + + So what we do is this: When the user calls setResizeMode() + the first time, we iterate through all the child widgets and + set their stretch factors to 1. Later on, if children are + added (using addWidget()), their stretch factors are also set + to 1. + + There is just one problem left: Often, setResizeMode() is + called \e{before} addWidget(), because addWidget() is called + from the event loop. In that case, we use a special value, + 243, instead of 0 to prevent 0 from being overwritten with 1 + in addWidget(). This is a wicked hack, but fortunately it + only occurs as a result of calling a \c QT3_SUPPORT function. + */ + + Q_D(QSplitter); + bool metWidget = false; + if (!d->compatMode) { + d->compatMode = true; + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + if (s->widget == w) + metWidget = true; + if (getStretch(s->widget) == 0) + setStretch(s->widget, 1); + } + } + int sf; + if (mode == KeepSize) + sf = metWidget ? 0 : 243; + else + sf = 1; + setStretch(w, sf); +} + +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QSplitter::QSplitter(QWidget *parent, const char *name) + : QFrame(*new QSplitterPrivate, parent) +{ + Q_D(QSplitter); + setObjectName(QString::fromAscii(name)); + d->orient = Qt::Horizontal; + d->init(); +} + + +/*! + Use one of the constructors that don't take the \a name argument + and then use setObjectName() instead. +*/ +QSplitter::QSplitter(Qt::Orientation orientation, QWidget *parent, const char *name) + : QFrame(*new QSplitterPrivate, parent) +{ + Q_D(QSplitter); + setObjectName(QString::fromAscii(name)); + d->orient = orientation; + d->init(); +} +#endif + +/*! + \internal +*/ +void QSplitterPrivate::insertWidget_helper(int index, QWidget *widget, bool show) +{ + Q_Q(QSplitter); + QBoolBlocker b(blockChildAdd); + bool needShow = show && q->isVisible() && + !(widget->isHidden() && widget->testAttribute(Qt::WA_WState_ExplicitShowHide)); + if (widget->parentWidget() != q) + widget->setParent(q); + if (needShow) + widget->show(); + insertWidget(index, widget); + recalc(q->isVisible()); +} + +/* + Inserts the widget \a w at position \a index in the splitter's list of widgets. + + If \a w is already in the splitter, it will be moved to the new position. +*/ + +QSplitterLayoutStruct *QSplitterPrivate::insertWidget(int index, QWidget *w) +{ + Q_Q(QSplitter); + QSplitterLayoutStruct *sls = 0; + int i; + int last = list.count(); + for (i = 0; i < list.size(); ++i) { + QSplitterLayoutStruct *s = list.at(i); + if (s->widget == w) { + sls = s; + --last; + break; + } + } + if (index < 0 || index > last) + index = last; + + if (sls) { + list.move(i,index); + } else { + QSplitterHandle *newHandle = 0; + sls = new QSplitterLayoutStruct; + QString tmp = QLatin1String("qt_splithandle_"); + tmp += w->objectName(); + newHandle = q->createHandle(); + newHandle->setObjectName(tmp); + sls->handle = newHandle; + sls->widget = w; + w->lower(); + list.insert(index,sls); + + if (newHandle && q->isVisible()) + newHandle->show(); // will trigger sending of post events + +#ifdef QT3_SUPPORT + if (compatMode) { + int sf = getStretch(sls->widget); + if (sf == 243) + setStretch(sls->widget, 0); + else if (sf == 0) + setStretch(sls->widget, 1); + } +#endif + } + return sls; +} + +/*! + \class QSplitter + \brief The QSplitter class implements a splitter widget. + + \ingroup organizers + \mainclass + + A splitter lets the user control the size of child widgets by dragging the + boundary between the children. Any number of widgets may be controlled by a + single splitter. The typical use of a QSplitter is to create several + widgets and add them using insertWidget() or addWidget(). + + The following example will show a QListView, QTreeView, and + QTextEdit side by side, with two splitter handles: + + \snippet doc/src/snippets/splitter/splitter.cpp 0 + + If a widget is already inside a QSplitter when insertWidget() or + addWidget() is called, it will move to the new position. This can be used + to reorder widgets in the splitter later. You can use indexOf(), + widget(), and count() to get access to the widgets inside the splitter. + + A default QSplitter lays out its children horizontally (side by side); you + can use setOrientation(Qt::Vertical) to lay its + children out vertically. + + By default, all widgets can be as large or as small as the user + wishes, between the \l minimumSizeHint() (or \l minimumSize()) + and \l maximumSize() of the widgets. + + QSplitter resizes its children dynamically by default. If you + would rather have QSplitter resize the children only at the end of + a resize operation, call setOpaqueResize(false). + + The initial distribution of size between the widgets is determined by + multiplying the initial size with the stretch factor. + You can also use setSizes() to set the sizes + of all the widgets. The function sizes() returns the sizes set by the user. + Alternatively, you can save and restore the sizes of the widgets from a + QByteArray using saveState() and restoreState() respectively. + + When you hide() a child its space will be distributed among the + other children. It will be reinstated when you show() it again. + + \sa QSplitterHandle, QHBoxLayout, QVBoxLayout, QTabWidget +*/ + + +/*! + Constructs a horizontal splitter with the \a parent + arguments is passed on to the QFrame constructor. + + \sa setOrientation() +*/ +QSplitter::QSplitter(QWidget *parent) + : QFrame(*new QSplitterPrivate, parent) +{ + Q_D(QSplitter); + d->orient = Qt::Horizontal; + d->init(); +} + + +/*! + Constructs a splitter with the given \a orientation and \a parent. + + \sa setOrientation() +*/ +QSplitter::QSplitter(Qt::Orientation orientation, QWidget *parent) + : QFrame(*new QSplitterPrivate, parent) +{ + Q_D(QSplitter); + d->orient = orientation; + d->init(); +} + + +/*! + Destroys the splitter. All children are deleted. +*/ + +QSplitter::~QSplitter() +{ + Q_D(QSplitter); + delete d->rubberBand; + while (!d->list.isEmpty()) + delete d->list.takeFirst(); +} + +/*! + Updates the splitter's state. You should not need to call this + function. +*/ +void QSplitter::refresh() +{ + Q_D(QSplitter); + d->recalc(true); +} + +/*! + \property QSplitter::orientation + \brief the orientation of the splitter + + By default the orientation is horizontal (i.e., the widgets are + laid out side by side). The possible orientations are + Qt::Horizontal and Qt::Vertical. + + \sa QSplitterHandle::orientation() +*/ + +void QSplitter::setOrientation(Qt::Orientation orientation) +{ + Q_D(QSplitter); + if (d->orient == orientation) + return; + + if (!testAttribute(Qt::WA_WState_OwnSizePolicy)) { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy(sp); + setAttribute(Qt::WA_WState_OwnSizePolicy, false); + } + + d->orient = orientation; + + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + s->handle->setOrientation(orientation); + } + d->recalc(isVisible()); +} + +Qt::Orientation QSplitter::orientation() const +{ + Q_D(const QSplitter); + return d->orient; +} + +/*! + \property QSplitter::childrenCollapsible + \brief whether child widgets can be resized down to size 0 by the user + + By default, children are collapsible. It is possible to enable + and disable the collapsing of individual children using + setCollapsible(). + + \sa setCollapsible() +*/ + +void QSplitter::setChildrenCollapsible(bool collapse) +{ + Q_D(QSplitter); + d->childrenCollapsible = collapse; +} + +bool QSplitter::childrenCollapsible() const +{ + Q_D(const QSplitter); + return d->childrenCollapsible; +} + +/*! + Sets whether the child widget at index \a index is collapsible to \a collapse. + + By default, children are collapsible, meaning that the user can + resize them down to size 0, even if they have a non-zero + minimumSize() or minimumSizeHint(). This behavior can be changed + on a per-widget basis by calling this function, or globally for + all the widgets in the splitter by setting the \l + childrenCollapsible property. + + \sa childrenCollapsible +*/ + +void QSplitter::setCollapsible(int index, bool collapse) +{ + Q_D(QSplitter); + + if (index < 0 || index >= d->list.size()) { + qWarning("QSplitter::setCollapsible: Index %d out of range", index); + return; + } + d->list.at(index)->collapsible = collapse ? 1 : 0; +} + +/*! + Returns true if the widget at \a index is collapsible, otherwise returns false +*/ +bool QSplitter::isCollapsible(int index) const +{ + Q_D(const QSplitter); + if (index < 0 || index >= d->list.size()) { + qWarning("QSplitter::isCollapsible: Index %d out of range", index); + return false; + } + return d->list.at(index)->collapsible; +} + +/*! + \reimp +*/ +void QSplitter::resizeEvent(QResizeEvent *) +{ + Q_D(QSplitter); + d->doResize(); +} + +/*! + Adds the given \a widget to the splitter's layout after all the other + items. + + If \a widget is already in the splitter, it will be moved to the new position. + + \sa insertWidget() widget() indexOf() +*/ +void QSplitter::addWidget(QWidget *widget) +{ + Q_D(QSplitter); + insertWidget(d->list.count(), widget); +} + +/*! + Inserts the \a widget specified into the splitter's layout at the + given \a index. + + If \a widget is already in the splitter, it will be moved to the new position. + + if \a index is an invalid index, then the widget will be inserted at the end. + + \sa addWidget() indexOf() widget() +*/ +void QSplitter::insertWidget(int index, QWidget *widget) +{ + Q_D(QSplitter); + d->insertWidget_helper(index, widget, true); +} + +/*! + \fn int QSplitter::indexOf(QWidget *widget) const + + Returns the index in the splitter's layout of the specified \a widget. This + also works for handles. + + Handles are numbered from 0. There are as many handles as there + are child widgets, but the handle at position 0 is always hidden. + + + \sa count(), widget() +*/ +int QSplitter::indexOf(QWidget *w) const +{ + Q_D(const QSplitter); + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + if (s->widget == w || s->handle == w) + return i; + } + return -1; +} + +/*! + Returns a new splitter handle as a child widget of this splitter. + This function can be reimplemented in subclasses to provide support + for custom handles. + + \sa handle(), indexOf() +*/ +QSplitterHandle *QSplitter::createHandle() +{ + Q_D(QSplitter); + return new QSplitterHandle(d->orient, this); +} + +/*! + Returns the handle to the left (or above) for the item in the + splitter's layout at the given \a index. The handle at index 0 is + always hidden. + + For right-to-left languages such as Arabic and Hebrew, the layout + of horizontal splitters is reversed. The handle will be to the + right of the widget at \a index. + + \sa count(), widget(), indexOf(), createHandle(), setHandleWidth() +*/ +QSplitterHandle *QSplitter::handle(int index) const +{ + Q_D(const QSplitter); + if (index < 0 || index >= d->list.size()) + return 0; + return d->list.at(index)->handle; +} + +/*! + Returns the widget at the given \a index in the splitter's layout. + + \sa count(), handle(), indexOf(), insertWidget() +*/ +QWidget *QSplitter::widget(int index) const +{ + Q_D(const QSplitter); + if (index < 0 || index >= d->list.size()) + return 0; + return d->list.at(index)->widget; +} + +/*! + Returns the number of widgets contained in the splitter's layout. + + \sa widget(), handle() +*/ +int QSplitter::count() const +{ + Q_D(const QSplitter); + return d->list.count(); +} + +/*! + \reimp + + Tells the splitter that the child widget described by \a c has been + inserted or removed. + + This method is also used to handle the situation where a widget is created + with the splitter as a parent but not explicitly added with insertWidget() + or addWidget(). This is for compatibility and not the recommended way of + putting widgets into a splitter in new code. Please use insertWidget() or + addWidget() in new code. + + \sa addWidget() insertWidget() +*/ + +void QSplitter::childEvent(QChildEvent *c) +{ + Q_D(QSplitter); + if (!c->child()->isWidgetType()) + return; + QWidget *w = static_cast<QWidget*>(c->child()); + + if (c->added() && !d->blockChildAdd && !w->isWindow() && !d->findWidget(w)) { + d->insertWidget_helper(d->list.count(), w, false); + } else if (c->polished() && !d->blockChildAdd) { + if (isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide))) + w->show(); + } else if (c->type() == QEvent::ChildRemoved) { + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + if (s->widget == w) { + d->list.removeAt(i); + delete s; + d->recalc(isVisible()); + return; + } + } + } +} + + +/*! + Displays a rubber band at position \a pos. If \a pos is negative, the + rubber band is removed. +*/ + +void QSplitter::setRubberBand(int pos) +{ + Q_D(QSplitter); + if (pos < 0) { + if (d->rubberBand) + QTimer::singleShot(0, d->rubberBand, SLOT(deleteLater())); + return; + } + QRect r = contentsRect(); + const int rBord = 3; // customizable? + int hw = handleWidth(); + if (!d->rubberBand) { + d->rubberBand = new QRubberBand(QRubberBand::Line); + // For accessibility to identify this special widget. + d->rubberBand->setObjectName(QLatin1String("qt_rubberband")); + } + if (d->orient == Qt::Horizontal) + d->rubberBand->setGeometry(QRect(QPoint(pos + hw / 2 - rBord, r.y()), + QSize(2 * rBord, r.height())).translated(mapToGlobal(QPoint()))); + else + d->rubberBand->setGeometry(QRect(QPoint(r.x(), pos + hw / 2 - rBord), + QSize(r.width(), 2 * rBord)).translated(mapToGlobal(QPoint()))); + if (!d->rubberBand->isVisible()) + d->rubberBand->show(); +} + +/*! + \reimp +*/ + +bool QSplitter::event(QEvent *e) +{ + Q_D(QSplitter); + switch (e->type()) { + case QEvent::Hide: + // Reset firstShow to false here since things can be done to the splitter in between + if (!d->firstShow) + d->firstShow = true; + break; + case QEvent::Show: + if (!d->firstShow) + break; + d->firstShow = false; + // fall through + case QEvent::HideToParent: + case QEvent::ShowToParent: + case QEvent::LayoutRequest: +#ifdef QT3_SUPPORT + case QEvent::LayoutHint: +#endif + d->recalc(isVisible()); + break; + default: + ; + } + return QWidget::event(e); +} + +/*! + \fn QSplitter::splitterMoved(int pos, int index) + + This signal is emitted when the splitter handle at a particular \a + index has been moved to position \a pos. + + For right-to-left languages such as Arabic and Hebrew, the layout + of horizontal splitters is reversed. \a pos is then the + distance from the right edge of the widget. + + \sa moveSplitter() +*/ + +/*! + Moves the left or top edge of the splitter handle at \a index as + close as possible to position \a pos, which is the distance from the + left or top edge of the widget. + + For right-to-left languages such as Arabic and Hebrew, the layout + of horizontal splitters is reversed. \a pos is then the distance + from the right edge of the widget. + + \sa splitterMoved(), closestLegalPosition(), getRange() +*/ +void QSplitter::moveSplitter(int pos, int index) +{ + Q_D(QSplitter); + QSplitterLayoutStruct *s = d->list.at(index); + int farMin; + int min; + int max; + int farMax; + +#ifdef QSPLITTER_DEBUG + int debugp = pos; +#endif + + pos = d->adjustPos(pos, index, &farMin, &min, &max, &farMax); + int oldP = d->pick(s->rect.topLeft()); +#ifdef QSPLITTER_DEBUG + qDebug() << "QSplitter::moveSplitter" << debugp << index << "adjusted" << pos << "oldP" << oldP; +#endif + + QVarLengthArray<int, 32> poss(d->list.count()); + QVarLengthArray<int, 32> ws(d->list.count()); + bool upLeft; + + d->doMove(false, pos, index, +1, (d->collapsible(s) && (pos > max)), poss.data(), ws.data()); + d->doMove(true, pos, index - 1, +1, (d->collapsible(index - 1) && (pos < min)), poss.data(), ws.data()); + upLeft = (pos < oldP); + + int wid, delta, count = d->list.count(); + if (upLeft) { + wid = 0; + delta = 1; + } else { + wid = count - 1; + delta = -1; + } + for (; wid >= 0 && wid < count; wid += delta) { + QSplitterLayoutStruct *sls = d->list.at( wid ); + if (!sls->widget->isHidden()) + d->setGeo(sls, poss[wid], ws[wid], true); + } + d->storeSizes(); + + emit splitterMoved(pos, index); +} + + +/*! + Returns the valid range of the splitter with index \a index in + *\a{min} and *\a{max} if \a min and \a max are not 0. +*/ + +void QSplitter::getRange(int index, int *min, int *max) const +{ + Q_D(const QSplitter); + d->getRange(index, min, 0, 0, max); +} + + +/*! + Returns the closest legal position to \a pos of the widget with index + \a index. + + For right-to-left languages such as Arabic and Hebrew, the layout + of horizontal splitters is reversed. Positions are then measured + from the right edge of the widget. + + \sa getRange() +*/ + +int QSplitter::closestLegalPosition(int pos, int index) +{ + Q_D(QSplitter); + int x, i, n, u; + return d->adjustPos(pos, index, &u, &n, &i, &x); +} + +/*! + \property QSplitter::opaqueResize + \brief whether resizing is opaque + + Opaque resizing is on by default. +*/ + +bool QSplitter::opaqueResize() const +{ + Q_D(const QSplitter); + return d->opaque; +} + + +void QSplitter::setOpaqueResize(bool on) +{ + Q_D(QSplitter); + d->opaque = on; +} + +#ifdef QT3_SUPPORT +/*! + \fn void QSplitter::moveToFirst(QWidget *widget) + + Use insertWidget(0, \a widget) instead. +*/ + + +/*! + \fn void QSplitter::moveToLast(QWidget *widget) + + Use addWidget(\a widget) instead. +*/ + +/*! + \fn void QSplitter::setResizeMode(QWidget *widget, ResizeMode mode) + + Use setStretchFactor() instead. + + \oldcode + splitter->setResizeMode(firstChild, QSplitter::KeepSize); + splitter->setResizeMode(secondChild, QSplitter::Stretch); + \newcode + splitter->setStretchFactor(splitter->indexOf(firstChild), 0); + splitter->setStretchFactor(splitter->indexOf(secondChild), 1); + \endcode +*/ + +/*! + \enum QSplitter::ResizeMode + \compat + + This enum describes the different resizing behaviors child + widgets can have: + + \value Auto The widget will be resized according to the stretch factors set in its sizePolicy(). + \value Stretch The widget will be resized when the splitter itself is resized. + \value KeepSize QSplitter will try to keep the widget's size unchanged. + \value FollowSizeHint QSplitter will resize the widget when the widget's size hint changes. + + Use setStretchFactor() instead. +*/ + +/*! + \fn void QSplitter::setCollapsible(QWidget *widget, bool collapsible) + + Use setCollapsible(indexOf(\a widget, \a collapsible)) instead. +*/ + +/*! + \fn void QSplitter::setMargin(int margin) + Sets the width of the margin around the contents of the widget to \a margin. + + Use QWidget::setContentsMargins() instead. + \sa margin(), QWidget::setContentsMargins() +*/ + +/*! + \fn int QSplitter::margin() const + Returns the with of the the margin around the contents of the widget. + + Use QWidget::getContentsMargins() instead. + \sa setMargin(), QWidget::getContentsMargins() +*/ + +#endif + +/*! + \reimp +*/ +QSize QSplitter::sizeHint() const +{ + Q_D(const QSplitter); + ensurePolished(); + int l = 0; + int t = 0; + QObjectList childList = children(); + for (int i = 0; i < childList.size(); ++i) { + if (QWidget *w = qobject_cast<QWidget *>(childList.at(i))) { + if (w->isHidden()) + continue; + QSize s = w->sizeHint(); + if (s.isValid()) { + l += d->pick(s); + t = qMax(t, d->trans(s)); + } + } + } + return orientation() == Qt::Horizontal ? QSize(l, t) : QSize(t, l); +} + + +/*! + \reimp +*/ + +QSize QSplitter::minimumSizeHint() const +{ + Q_D(const QSplitter); + ensurePolished(); + int l = 0; + int t = 0; + + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + if (!s || !s->widget) + continue; + if (s->widget->isHidden()) + continue; + QSize widgetSize = qSmartMinSize(s->widget); + if (widgetSize.isValid()) { + l += d->pick(widgetSize); + t = qMax(t, d->trans(widgetSize)); + } + if (!s->handle || s->handle->isHidden()) + continue; + QSize splitterSize = s->handle->sizeHint(); + if (splitterSize.isValid()) { + l += d->pick(splitterSize); + t = qMax(t, d->trans(splitterSize)); + } + } + return orientation() == Qt::Horizontal ? QSize(l, t) : QSize(t, l); +} + + +/*! + Returns a list of the size parameters of all the widgets in this splitter. + + If the splitter's orientation is horizontal, the list contains the + widgets width in pixels, from left to right; if the orientation is + vertical, the list contains the widgets height in pixels, + from top to bottom. + + Giving the values to another splitter's setSizes() function will + produce a splitter with the same layout as this one. + + Note that invisible widgets have a size of 0. + + \sa setSizes() +*/ + +QList<int> QSplitter::sizes() const +{ + Q_D(const QSplitter); + ensurePolished(); + + QList<int> list; + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + list.append(d->pick(s->rect.size())); + } + return list; +} + +/*! + Sets the child widgets respective sizes to the values given in the \a list. + + If the splitter is horizontal, the values set the widths of each + widget in pixels, from left to right. If the splitter is vertical, the + heights of each widget is set, from top to bottom. + + Extra values in the \a list are ignored. If \a list contains too few + values, the result is undefined but the program will still be well-behaved. + + The overall size of the splitter widget is not affected. + Instead, any additional/missing space is distributed amongst the + widgets according to the relative weight of the sizes. + + If you specify a size of 0, the widget will be invisible. The size policies + of the widgets are preserved. That is, a value smaller then the minimal size + hint of the respective widget will be replaced by the value of the hint. + + \sa sizes() +*/ + +void QSplitter::setSizes(const QList<int> &list) +{ + Q_D(QSplitter); + d->setSizes_helper(list, true); +} + +/*! + \property QSplitter::handleWidth + \brief the width of the splitter handles + + By default, this property contains a value that depends on the user's platform + and style preferences. +*/ + +int QSplitter::handleWidth() const +{ + Q_D(const QSplitter); + if (d->handleWidth > 0) { + return d->handleWidth; + } else { + return style()->pixelMetric(QStyle::PM_SplitterWidth, 0, this); + } +} + +void QSplitter::setHandleWidth(int width) +{ + Q_D(QSplitter); + d->handleWidth = width; + d->updateHandles(); +} + +/*! + \reimp +*/ +void QSplitter::changeEvent(QEvent *ev) +{ + Q_D(QSplitter); + if(ev->type() == QEvent::StyleChange) + d->updateHandles(); + QFrame::changeEvent(ev); +} + +static const qint32 SplitterMagic = 0xff; + +/*! + Saves the state of the splitter's layout. + + 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. + Here is an example: + + \snippet doc/src/snippets/splitter/splitter.cpp 1 + + \sa restoreState() +*/ +QByteArray QSplitter::saveState() const +{ + Q_D(const QSplitter); + int version = 0; + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + + stream << qint32(SplitterMagic); + stream << qint32(version); + QList<int> list; + for (int i = 0; i < d->list.size(); ++i) { + QSplitterLayoutStruct *s = d->list.at(i); + list.append(s->sizer); + } + stream << list; + stream << childrenCollapsible(); + stream << qint32(handleWidth()); + stream << opaqueResize(); + stream << qint32(orientation()); + return data; +} + +/*! + Restores the splitter's layout to the \a state specified. + Returns true if the state is restored; otherwise returns false. + + Typically this is used in conjunction with QSettings to restore the size + from a past session. Here is an example: + + Restore the splitters's state: + + \snippet doc/src/snippets/splitter/splitter.cpp 2 + + A failure to restore the splitter's layout may result from either + invalid or out-of-date data in the supplied byte array. + + \sa saveState() +*/ +bool QSplitter::restoreState(const QByteArray &state) +{ + Q_D(QSplitter); + int version = 0; + QByteArray sd = state; + QDataStream stream(&sd, QIODevice::ReadOnly); + QList<int> list; + bool b; + qint32 i; + qint32 marker; + qint32 v; + + stream >> marker; + stream >> v; + if (marker != SplitterMagic || v != version) + return false; + + stream >> list; + d->setSizes_helper(list, false); + + stream >> b; + setChildrenCollapsible(b); + + stream >> i; + setHandleWidth(i); + + stream >> b; + setOpaqueResize(b); + + stream >> i; + setOrientation(Qt::Orientation(i)); + d->doResize(); + + return true; +} + +/*! + Updates the size policy of the widget at position \a index to + have a stretch factor of \a stretch. + + \a stretch is not the effective stretch factor; the effective + stretch factor is calculated by taking the initial size of the + widget and multiplying it with \a stretch. + + This function is provided for convenience. It is equivalent to + + \snippet doc/src/snippets/code/src_gui_widgets_qsplitter.cpp 0 + + \sa setSizes(), widget() +*/ +void QSplitter::setStretchFactor(int index, int stretch) +{ + Q_D(QSplitter); + if (index <= -1 || index >= d->list.count()) + return; + + QWidget *widget = d->list.at(index)->widget; + QSizePolicy sp = widget->sizePolicy(); + sp.setHorizontalStretch(stretch); + sp.setVerticalStretch(stretch); + widget->setSizePolicy(sp); +} + + +//#ifdef QT3_SUPPORT +#ifndef QT_NO_TEXTSTREAM +/*! + \relates QSplitter + \obsolete + + Use \a ts << \a{splitter}.saveState() instead. +*/ + +QTextStream& operator<<(QTextStream& ts, const QSplitter& splitter) +{ + ts << splitter.saveState() << endl; + return ts; +} + +/*! + \relates QSplitter + \obsolete + + Use \a ts >> \a{splitter}.restoreState() instead. +*/ + +QTextStream& operator>>(QTextStream& ts, QSplitter& splitter) +{ + QString line = ts.readLine(); + line = line.simplified(); + line.replace(QLatin1Char(' '), QString()); + line = line.toUpper(); + + splitter.restoreState(line.toAscii()); + return ts; +} +#endif // QT_NO_TEXTSTREAM +//#endif // QT3_SUPPORT + +QT_END_NAMESPACE + +#endif // QT_NO_SPLITTER diff --git a/src/gui/widgets/qsplitter.h b/src/gui/widgets/qsplitter.h new file mode 100644 index 0000000..36e6e19 --- /dev/null +++ b/src/gui/widgets/qsplitter.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPLITTER_H +#define QSPLITTER_H + +#include <QtGui/qframe.h> +#include <QtGui/qsizepolicy.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SPLITTER + +class QSplitterPrivate; +class QTextStream; +template <typename T> class QList; + +class QSplitterHandle; + +class Q_GUI_EXPORT QSplitter : public QFrame +{ + Q_OBJECT + + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(bool opaqueResize READ opaqueResize WRITE setOpaqueResize) + Q_PROPERTY(int handleWidth READ handleWidth WRITE setHandleWidth) + Q_PROPERTY(bool childrenCollapsible READ childrenCollapsible WRITE setChildrenCollapsible) + +public: + explicit QSplitter(QWidget* parent = 0); + explicit QSplitter(Qt::Orientation, QWidget* parent = 0); + ~QSplitter(); + + void addWidget(QWidget *widget); + void insertWidget(int index, QWidget *widget); + + void setOrientation(Qt::Orientation); + Qt::Orientation orientation() const; + + void setChildrenCollapsible(bool); + bool childrenCollapsible() const; + + void setCollapsible(int index, bool); + bool isCollapsible(int index) const; + void setOpaqueResize(bool opaque = true); + bool opaqueResize() const; + void refresh(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + QList<int> sizes() const; + void setSizes(const QList<int> &list); + + QByteArray saveState() const; + bool restoreState(const QByteArray &state); + + int handleWidth() const; + void setHandleWidth(int); + + int indexOf(QWidget *w) const; + QWidget *widget(int index) const; + int count() const; + + void getRange(int index, int *, int *) const; + QSplitterHandle *handle(int index) const; + + void setStretchFactor(int index, int stretch); + +Q_SIGNALS: + void splitterMoved(int pos, int index); + +protected: + virtual QSplitterHandle *createHandle(); + + void childEvent(QChildEvent *); + + bool event(QEvent *); + void resizeEvent(QResizeEvent *); + + void changeEvent(QEvent *); + void moveSplitter(int pos, int index); + void setRubberBand(int position); + int closestLegalPosition(int, int); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QSplitter(QWidget* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QSplitter(Qt::Orientation, QWidget* parent, const char* name); + enum ResizeMode { Stretch, KeepSize, FollowSizeHint, Auto }; + QT3_SUPPORT void setResizeMode(QWidget *w, ResizeMode mode); + inline QT3_SUPPORT void moveToFirst(QWidget *w) { insertWidget(0,w); } + inline QT3_SUPPORT void moveToLast(QWidget *w) { addWidget(w); } + inline QT3_SUPPORT void setCollapsible(QWidget *w, bool collapse) + { setCollapsible(indexOf(w), collapse); } + QT3_SUPPORT void setMargin(int margin) { setContentsMargins(margin, margin, margin, margin); } + QT3_SUPPORT int margin() const + { int margin; int dummy; getContentsMargins(&margin, &dummy, &dummy, &dummy); return margin; } +#endif + +private: + Q_DISABLE_COPY(QSplitter) + Q_DECLARE_PRIVATE(QSplitter) +private: + friend class QSplitterHandle; +}; + +//#ifdef QT3_SUPPORT +#ifndef QT_NO_TEXTSTREAM +Q_GUI_EXPORT QTextStream& operator<<(QTextStream&, const QSplitter&); +Q_GUI_EXPORT QTextStream& operator>>(QTextStream&, QSplitter&); +#endif +//#endif + +class QSplitterHandlePrivate; +class Q_GUI_EXPORT QSplitterHandle : public QWidget +{ + Q_OBJECT +public: + QSplitterHandle(Qt::Orientation o, QSplitter *parent); + void setOrientation(Qt::Orientation o); + Qt::Orientation orientation() const; + bool opaqueResize() const; + QSplitter *splitter() const; + + QSize sizeHint() const; + +protected: + void paintEvent(QPaintEvent *); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + bool event(QEvent *); + + void moveSplitter(int p); + int closestLegalPosition(int p); + +private: + Q_DISABLE_COPY(QSplitterHandle) + Q_DECLARE_PRIVATE(QSplitterHandle) +}; + +#endif // QT_NO_SPLITTER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSPLITTER_H diff --git a/src/gui/widgets/qsplitter_p.h b/src/gui/widgets/qsplitter_p.h new file mode 100644 index 0000000..5cc43af --- /dev/null +++ b/src/gui/widgets/qsplitter_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSPLITTER_P_H +#define QSPLITTER_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/qframe_p.h" +#include "qrubberband.h" + +QT_BEGIN_NAMESPACE + +static const uint Default = 2; + +class QSplitterLayoutStruct +{ +public: + QRect rect; + int sizer; + uint collapsed : 1; + uint collapsible : 2; + QWidget *widget; + QSplitterHandle *handle; + + QSplitterLayoutStruct() : sizer(-1), collapsed(false), collapsible(Default), widget(0), handle(0) {} + ~QSplitterLayoutStruct() { delete handle; } + int getWidgetSize(Qt::Orientation orient); + int getHandleSize(Qt::Orientation orient); + int pick(const QSize &size, Qt::Orientation orient) + { return (orient == Qt::Horizontal) ? size.width() : size.height(); } +}; + +class QSplitterPrivate : public QFramePrivate +{ + Q_DECLARE_PUBLIC(QSplitter) +public: + QSplitterPrivate() : rubberBand(0), opaque(true), firstShow(true), + childrenCollapsible(true), compatMode(false), handleWidth(0), blockChildAdd(false) {} + + QPointer<QRubberBand> rubberBand; + mutable QList<QSplitterLayoutStruct *> list; + Qt::Orientation orient; + bool opaque : 8; + bool firstShow : 8; + bool childrenCollapsible : 8; + bool compatMode : 8; + int handleWidth; + bool blockChildAdd; + + inline int pick(const QPoint &pos) const + { return orient == Qt::Horizontal ? pos.x() : pos.y(); } + inline int pick(const QSize &s) const + { return orient == Qt::Horizontal ? s.width() : s.height(); } + + inline int trans(const QPoint &pos) const + { return orient == Qt::Vertical ? pos.x() : pos.y(); } + inline int trans(const QSize &s) const + { return orient == Qt::Vertical ? s.width() : s.height(); } + + void init(); + void recalc(bool update = false); + void doResize(); + void storeSizes(); + void getRange(int index, int *, int *, int *, int *) const; + void addContribution(int, int *, int *, bool) const; + int adjustPos(int, int, int *, int *, int *, int *) const; + bool collapsible(QSplitterLayoutStruct *) const; + bool collapsible(int index) const + { return (index < 0 || index >= list.size()) ? true : collapsible(list.at(index)); } + QSplitterLayoutStruct *findWidget(QWidget *) const; + void insertWidget_helper(int index, QWidget *widget, bool show); + QSplitterLayoutStruct *insertWidget(int index, QWidget *); + void doMove(bool backwards, int pos, int index, int delta, + bool mayCollapse, int *positions, int *widths); + void setGeo(QSplitterLayoutStruct *s, int pos, int size, bool allowCollapse); + int findWidgetJustBeforeOrJustAfter(int index, int delta, int &collapsibleSize) const; + void updateHandles(); + void setSizes_helper(const QList<int> &sizes, bool clampNegativeSize = false); + +}; + +class QSplitterHandlePrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QSplitterHandle) +public: + QSplitterHandlePrivate() : orient(Qt::Horizontal), opaq(false), s(0), mouseOffset(0) {} + + inline int pick(const QPoint &pos) const + { return orient == Qt::Horizontal ? pos.x() : pos.y(); } + + Qt::Orientation orient; + bool opaq; + QSplitter *s; + bool hover; + int mouseOffset; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/widgets/qstackedwidget.cpp b/src/gui/widgets/qstackedwidget.cpp new file mode 100644 index 0000000..c615630 --- /dev/null +++ b/src/gui/widgets/qstackedwidget.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstackedwidget.h" + +#ifndef QT_NO_STACKEDWIDGET + +#include <qstackedlayout.h> +#include <qevent.h> +#include <private/qframe_p.h> + +QT_BEGIN_NAMESPACE + +class QStackedWidgetPrivate : public QFramePrivate +{ + Q_DECLARE_PUBLIC(QStackedWidget) +public: + QStackedWidgetPrivate():layout(0){} + QStackedLayout *layout; + bool blockChildAdd; +}; + +/*! + \class QStackedWidget + \brief The QStackedWidget class provides a stack of widgets where + only one widget is visible at a time. + + \ingroup organizers + \ingroup geomanagement + \ingroup appearance + \mainclass + + QStackedWidget can be used to create a user interface similar to + the one provided by QTabWidget. It is a convenience layout widget + built on top of the QStackedLayout class. + + Like QStackedLayout, QStackedWidget can be constructed and + populated with a number of child widgets ("pages"): + + \snippet doc/src/snippets/qstackedwidget/main.cpp 0 + \snippet doc/src/snippets/qstackedwidget/main.cpp 2 + \snippet doc/src/snippets/qstackedwidget/main.cpp 3 + + QStackedWidget provides no intrinsic means for the user to switch + page. This is typically done through a QComboBox or a QListWidget + that stores the titles of the QStackedWidget's pages. For + example: + + \snippet doc/src/snippets/qstackedwidget/main.cpp 1 + + When populating a stacked widget, the widgets are added to an + internal list. The indexOf() function returns the index of a + widget in that list. The widgets can either be added to the end of + the list using the addWidget() function, or inserted at a given + index using the insertWidget() function. The removeWidget() + function removes a widget from the stacked widget. The number of + widgets contained in the stacked widget, can + be obtained using the count() function. + + The widget() function returns the widget at a given index + position. The index of the widget that is shown on screen is given + by currentIndex() and can be changed using setCurrentIndex(). In a + similar manner, the currently shown widget can be retrieved using + the currentWidget() function, and altered using the + setCurrentWidget() function. + + Whenever the current widget in the stacked widget changes or a + widget is removed from the stacked widget, the currentChanged() + and widgetRemoved() signals are emitted respectively. + + \sa QStackedLayout, QTabWidget, {Config Dialog Example} +*/ + +/*! + \fn void QStackedWidget::currentChanged(int index) + + This signal is emitted whenever the current widget changes. + + The parameter holds the \a index of the new current widget, or -1 + if there isn't a new one (for example, if there are no widgets in + the QStackedWidget). + + \sa currentWidget(), setCurrentWidget() +*/ + +/*! + \fn void QStackedWidget::widgetRemoved(int index) + + This signal is emitted whenever a widget is removed. The widget's + \a index is passed as parameter. + + \sa removeWidget() +*/ + +/*! + Constructs a QStackedWidget with the given \a parent. + + \sa addWidget(), insertWidget() +*/ +QStackedWidget::QStackedWidget(QWidget *parent) + : QFrame(*new QStackedWidgetPrivate, parent) +{ + Q_D(QStackedWidget); + d->layout = new QStackedLayout(this); + connect(d->layout, SIGNAL(widgetRemoved(int)), this, SIGNAL(widgetRemoved(int))); + connect(d->layout, SIGNAL(currentChanged(int)), this, SIGNAL(currentChanged(int))); +} + +/*! + Destroys this stacked widget, and frees any allocated resources. +*/ +QStackedWidget::~QStackedWidget() +{ +} + +/*! + Appends the given \a widget to the QStackedWidget and returns the + index position. Ownership of \a widget is passed on to the + QStackedWidget. + + If the QStackedWidget is empty before this function is called, + \a widget becomes the current widget. + + \sa insertWidget(), removeWidget(), setCurrentWidget() +*/ +int QStackedWidget::addWidget(QWidget *widget) +{ + return d_func()->layout->addWidget(widget); +} + +/*! + Inserts the given \a widget at the given \a index in the + QStackedWidget. Ownership of \a widget is passed on to the + QStackedWidget. If \a index is out of range, the \a widget is + appended (in which case it is the actual index of the \a widget + that is returned). + + If the QStackedWidget was empty before this function is called, + the given \a widget becomes the current widget. + + Inserting a new widget at an index less than or equal to the current index + will increment the current index, but keep the current widget. + + \sa addWidget(), removeWidget(), setCurrentWidget() +*/ +int QStackedWidget::insertWidget(int index, QWidget *widget) +{ + return d_func()->layout->insertWidget(index, widget); +} + +/*! + Removes the given \a widget from the QStackedWidget. The widget + is \e not deleted. + + \sa addWidget(), insertWidget(), currentWidget() +*/ +void QStackedWidget::removeWidget(QWidget *widget) +{ + d_func()->layout->removeWidget(widget); +} + +/*! + \property QStackedWidget::currentIndex + \brief the index position of the widget that is visible + + The current index is -1 if there is no current widget. + + By default, this property contains a value of -1 because the stack + is initially empty. + + \sa currentWidget(), indexOf() +*/ + +void QStackedWidget::setCurrentIndex(int index) +{ + d_func()->layout->setCurrentIndex(index); +} + +int QStackedWidget::currentIndex() const +{ + return d_func()->layout->currentIndex(); +} + +/*! + Returns the current widget, or 0 if there are no child widgets. + + \sa currentIndex(), setCurrentWidget() +*/ +QWidget *QStackedWidget::currentWidget() const +{ + return d_func()->layout->currentWidget(); +} + + +/*! + \fn void QStackedWidget::setCurrentWidget(QWidget *widget) + + Sets the current widget to be the specified \a widget. The new + current widget must already be contained in this stacked widget. + + \sa currentWidget(), setCurrentIndex() + */ +void QStackedWidget::setCurrentWidget(QWidget *widget) +{ + Q_D(QStackedWidget); + if (d->layout->indexOf(widget) == -1) { + qWarning("QStackedWidget::setCurrentWidget: widget %p not contained in stack", widget); + return; + } + d->layout->setCurrentWidget(widget); +} + +/*! + Returns the index of the given \a widget, or -1 if the given \a + widget is not a child of the QStackedWidget. + + \sa currentIndex(), widget() +*/ +int QStackedWidget::indexOf(QWidget *widget) const +{ + return d_func()->layout->indexOf(widget); +} + +/*! + Returns the widget at the given \a index, or 0 if there is no such + widget. + + \sa currentWidget(), indexOf() +*/ +QWidget *QStackedWidget::widget(int index) const +{ + return d_func()->layout->widget(index); +} + +/*! + \property QStackedWidget::count + \brief the number of widgets contained by this stacked widget + + By default, this property contains a value of 0. + + \sa currentIndex(), widget() +*/ +int QStackedWidget::count() const +{ + return d_func()->layout->count(); +} + +/*! \reimp */ +bool QStackedWidget::event(QEvent *e) +{ + return QFrame::event(e); +} + +QT_END_NAMESPACE + +#endif // QT_NO_STACKEDWIDGET diff --git a/src/gui/widgets/qstackedwidget.h b/src/gui/widgets/qstackedwidget.h new file mode 100644 index 0000000..773f800 --- /dev/null +++ b/src/gui/widgets/qstackedwidget.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTACKEDWIDGET_H +#define QSTACKEDWIDGET_H + +#include <QtGui/qframe.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STACKEDWIDGET + +class QStackedWidgetPrivate; + +class Q_GUI_EXPORT QStackedWidget : public QFrame +{ + Q_OBJECT + + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentChanged) + Q_PROPERTY(int count READ count) +public: + explicit QStackedWidget(QWidget *parent=0); + ~QStackedWidget(); + + int addWidget(QWidget *w); + int insertWidget(int index, QWidget *w); + void removeWidget(QWidget *w); + + QWidget *currentWidget() const; + int currentIndex() const; + + int indexOf(QWidget *) const; + QWidget *widget(int) const; + int count() const; + +public Q_SLOTS: + void setCurrentIndex(int index); + void setCurrentWidget(QWidget *w); + +Q_SIGNALS: + void currentChanged(int); + void widgetRemoved(int index); + +protected: + bool event(QEvent *e); + +private: + Q_DISABLE_COPY(QStackedWidget) + Q_DECLARE_PRIVATE(QStackedWidget) +}; + +#endif // QT_NO_STACKEDWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTACKEDWIDGET_H diff --git a/src/gui/widgets/qstatusbar.cpp b/src/gui/widgets/qstatusbar.cpp new file mode 100644 index 0000000..c970838 --- /dev/null +++ b/src/gui/widgets/qstatusbar.cpp @@ -0,0 +1,847 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstatusbar.h" +#ifndef QT_NO_STATUSBAR + +#include "qlist.h" +#include "qdebug.h" +#include "qevent.h" +#include "qlayout.h" +#include "qpainter.h" +#include "qtimer.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qsizegrip.h" +#include "qmainwindow.h" + +#include <private/qlayoutengine_p.h> +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +class QStatusBarPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QStatusBar) +public: + QStatusBarPrivate() {} + + struct SBItem { + SBItem(QWidget* widget, int stretch, bool permanent) + : s(stretch), w(widget), p(permanent) {} + int s; + QWidget * w; + bool p; + }; + + QList<SBItem *> items; + QString tempItem; + + QBoxLayout * box; + QTimer * timer; + +#ifndef QT_NO_SIZEGRIP + QSizeGrip * resizer; + bool showSizeGrip; +#endif + + int savedStrut; + +#ifdef Q_WS_MAC + QPoint dragStart; +#endif + + int indexToLastNonPermanentWidget() const + { + int i = items.size() - 1; + for (; i >= 0; --i) { + SBItem *item = items.at(i); + if (!(item && item->p)) + break; + } + return i; + } + +#ifndef QT_NO_SIZEGRIP + void tryToShowSizeGrip() + { + if (!showSizeGrip) + return; + showSizeGrip = false; + if (!resizer || resizer->isVisible()) + return; + resizer->setAttribute(Qt::WA_WState_ExplicitShowHide, false); + QMetaObject::invokeMethod(resizer, "_q_showIfNotHidden", Qt::DirectConnection); + resizer->setAttribute(Qt::WA_WState_ExplicitShowHide, false); + } +#endif + + QRect messageRect() const; +}; + + +QRect QStatusBarPrivate::messageRect() const +{ + Q_Q(const QStatusBar); + bool rtl = q->layoutDirection() == Qt::RightToLeft; + + int left = 6; + int right = q->width() - 12; + +#ifndef QT_NO_SIZEGRIP + if (resizer && resizer->isVisible()) { + if (rtl) + left = resizer->x() + resizer->width(); + else + right = resizer->x(); + } +#endif + + for (int i=0; i<items.size(); ++i) { + QStatusBarPrivate::SBItem* item = items.at(i); + if (!item) + break; + if (item->p && item->w->isVisible()) { + if (item->p) { + if (rtl) + left = qMax(left, item->w->x() + item->w->width() + 2); + else + right = qMin(right, item->w->x()-1); + } + break; + } + } + return QRect(left, 0, right-left, q->height()); +} + + +/*! + \class QStatusBar + \brief The QStatusBar class provides a horizontal bar suitable for + presenting status information. + + \ingroup application + \ingroup helpsystem + \mainclass + + Each status indicator falls into one of three categories: + + \list + \o \e Temporary - briefly occupies most of the status bar. Used + to explain tool tip texts or menu entries, for example. + \o \e Normal - occupies part of the status bar and may be hidden + by temporary messages. Used to display the page and line + number in a word processor, for example. + \o \e Permanent - is never hidden. Used for important mode + indications, for example, some applications put a Caps Lock + indicator in the status bar. + \endlist + + QStatusBar lets you display all three types of indicators. + + Typically, a request for the status bar functionality occurs in + relation to a QMainWindow object. QMainWindow provides a main + application window, with a menu bar, tool bars, dock widgets \e + and a status bar around a large central widget. The status bar can + be retrieved using the QMainWindow::statusBar() function, and + replaced using the QMainWindow::setStatusBar() function. + + Use the showMessage() slot to display a \e temporary message: + + \snippet examples/mainwindows/dockwidgets/mainwindow.cpp 8 + + To remove a temporary message, use the clearMessage() slot, or set + a time limit when calling showMessage(). For example: + + \snippet examples/mainwindows/dockwidgets/mainwindow.cpp 3 + + Use the currentMessage() function to retrieve the temporary + message currently shown. The QStatusBar class also provide the + messageChanged() signal which is emitted whenever the temporary + status message changes. + + \target permanent message + \e Normal and \e Permanent messages are displayed by creating a + small widget (QLabel, QProgressBar or even QToolButton) and then + adding it to the status bar using the addWidget() or the + addPermanentWidget() function. Use the removeWidget() function to + remove such messages from the status bar. + + \snippet doc/src/snippets/code/src_gui_widgets_qstatusbar.cpp 0 + + By default QStatusBar provides a QSizeGrip in the lower-right + corner. You can disable it using the setSizeGripEnabled() + function. Use the isSizeGripEnabled() function to determine the + current status of the size grip. + + \image plastique-statusbar.png A status bar shown in the Plastique widget style + + \sa QMainWindow, QStatusTipEvent, {fowler}{GUI Design Handbook: + Status Bar}, {Application Example} +*/ + +#ifdef QT3_SUPPORT +/*! + Constructs a status bar with a size grip and the given \a parent + and object \a name. + + Use the QStatusBar() constructor and the QObject::setObjectName() + function instead. + + \oldcode + QStatusBar *myStatusBar = new QStatusBar(parent, name); + \newcode + QStatusBar *myStatusBar = new QStatusBar(parent); + myStatusBar->setObjectName(name); + \endcode +*/ +QStatusBar::QStatusBar(QWidget * parent, const char *name) + : QWidget(*new QStatusBarPrivate, parent, 0) +{ + Q_D(QStatusBar); + setObjectName(QString::fromAscii(name)); + d->box = 0; + d->timer = 0; + +#ifndef QT_NO_SIZEGRIP + d->resizer = 0; + d->showSizeGrip = false; + setSizeGripEnabled(true); // causes reformat() +#else + reformat(); +#endif +} + + +/*! + \fn void QStatusBar::addWidget(QWidget * widget, int stretch, bool permanent) + + Use addWidget() or addPermanentWidget() instead, depending on the + value of the \a permanent parameter. + + \oldcode + QStatusBar *myStatusBar; + myStatusBar->addWidget(widget, stretch, permanent); // permanent == true + \newcode + QStatusBar *myStatusBar; + myStatusBar->addPermanentWidget(widget, stretch); + \endcode + */ + +#endif + +/*! + Constructs a status bar with a size grip and the given \a parent. + + \sa setSizeGripEnabled() +*/ +QStatusBar::QStatusBar(QWidget * parent) + : QWidget(*new QStatusBarPrivate, parent, 0) +{ + Q_D(QStatusBar); + d->box = 0; + d->timer = 0; + +#ifndef QT_NO_SIZEGRIP + d->resizer = 0; + setSizeGripEnabled(true); // causes reformat() +#else + reformat(); +#endif +} + +/*! + Destroys this status bar and frees any allocated resources and + child widgets. +*/ +QStatusBar::~QStatusBar() +{ + Q_D(QStatusBar); + while (!d->items.isEmpty()) + delete d->items.takeFirst(); +} + + +/*! + Adds the given \a widget to this status bar, reparenting the + widget if it isn't already a child of this QStatusBar object. The + \a stretch parameter is used to compute a suitable size for the + given \a widget as the status bar grows and shrinks. The default + stretch factor is 0, i.e giving the widget a minimum of space. + + The widget is located to the far left of the first permanent + widget (see addPermanentWidget()) and may be obscured by temporary + messages. + + \sa insertWidget(), removeWidget(), addPermanentWidget() +*/ + +void QStatusBar::addWidget(QWidget * widget, int stretch) +{ + if (!widget) + return; + insertWidget(d_func()->indexToLastNonPermanentWidget() + 1, widget, stretch); +} + +/*! + \since 4.2 + + Inserts the given \a widget at the given \a index to this status bar, + reparenting the widget if it isn't already a child of this + QStatusBar object. If \a index is out of range, the widget is appended + (in which case it is the actual index of the widget that is returned). + + The \a stretch parameter is used to compute a suitable size for + the given \a widget as the status bar grows and shrinks. The + default stretch factor is 0, i.e giving the widget a minimum of + space. + + The widget is located to the far left of the first permanent + widget (see addPermanentWidget()) and may be obscured by temporary + messages. + + \sa addWidget(), removeWidget(), addPermanentWidget() +*/ +int QStatusBar::insertWidget(int index, QWidget *widget, int stretch) +{ + if (!widget) + return -1; + + Q_D(QStatusBar); + QStatusBarPrivate::SBItem* item = new QStatusBarPrivate::SBItem(widget, stretch, false); + + int idx = d->indexToLastNonPermanentWidget(); + if (index < 0 || index > d->items.size() || (idx >= 0 && index > idx + 1)) { + qWarning("QStatusBar::insertWidget: Index out of range (%d), appending widget", index); + index = idx + 1; + } + d->items.insert(index, item); + + if (!d->tempItem.isEmpty()) + widget->hide(); + + reformat(); + if (!widget->isHidden() || !widget->testAttribute(Qt::WA_WState_ExplicitShowHide)) + widget->show(); + + return index; +} + +/*! + Adds the given \a widget permanently to this status bar, + reparenting the widget if it isn't already a child of this + QStatusBar object. The \a stretch parameter is used to compute a + suitable size for the given \a widget as the status bar grows and + shrinks. The default stretch factor is 0, i.e giving the widget a + minimum of space. + + Permanently means that the widget may not be obscured by temporary + messages. It is is located at the far right of the status bar. + + \sa insertPermanentWidget(), removeWidget(), addWidget() +*/ + +void QStatusBar::addPermanentWidget(QWidget * widget, int stretch) +{ + if (!widget) + return; + insertPermanentWidget(d_func()->items.size(), widget, stretch); +} + + +/*! + \since 4.2 + + Inserts the given \a widget at the given \a index permanently to this status bar, + reparenting the widget if it isn't already a child of this + QStatusBar object. If \a index is out of range, the widget is appended + (in which case it is the actual index of the widget that is returned). + + The \a stretch parameter is used to compute a + suitable size for the given \a widget as the status bar grows and + shrinks. The default stretch factor is 0, i.e giving the widget a + minimum of space. + + Permanently means that the widget may not be obscured by temporary + messages. It is is located at the far right of the status bar. + + \sa addPermanentWidget(), removeWidget(), addWidget() +*/ +int QStatusBar::insertPermanentWidget(int index, QWidget *widget, int stretch) +{ + if (!widget) + return -1; + + Q_D(QStatusBar); + QStatusBarPrivate::SBItem* item = new QStatusBarPrivate::SBItem(widget, stretch, true); + + int idx = d->indexToLastNonPermanentWidget(); + if (index < 0 || index > d->items.size() || (idx >= 0 && index <= idx)) { + qWarning("QStatusBar::insertPermanentWidget: Index out of range (%d), appending widget", index); + index = d->items.size(); + } + d->items.insert(index, item); + + reformat(); + if (!widget->isHidden() || !widget->testAttribute(Qt::WA_WState_ExplicitShowHide)) + widget->show(); + + return index; +} + +/*! + Removes the specified \a widget from the status bar. + + \note This function does not delete the widget but \e hides it. + To add the widget again, you must call both the addWidget() and + show() functions. + + \sa addWidget(), addPermanentWidget(), clearMessage() +*/ + +void QStatusBar::removeWidget(QWidget *widget) +{ + if (!widget) + return; + + Q_D(QStatusBar); + bool found = false; + QStatusBarPrivate::SBItem* item; + for (int i=0; i<d->items.size(); ++i) { + item = d->items.at(i); + if (!item) + break; + if (item->w == widget) { + d->items.removeAt(i); + item->w->hide(); + delete item; + found = true; + break; + } + } + + if (found) + reformat(); +#if defined(QT_DEBUG) + else + qDebug("QStatusBar::removeWidget(): Widget not found."); +#endif +} + +/*! + \property QStatusBar::sizeGripEnabled + + \brief whether the QSizeGrip in the bottom-right corner of the + status bar is enabled + + The size grip is enabled by default. +*/ + +bool QStatusBar::isSizeGripEnabled() const +{ +#ifdef QT_NO_SIZEGRIP + return false; +#else + Q_D(const QStatusBar); + return !!d->resizer; +#endif +} + +void QStatusBar::setSizeGripEnabled(bool enabled) +{ +#ifdef QT_NO_SIZEGRIP + Q_UNUSED(enabled); +#else + Q_D(QStatusBar); + if (!enabled != !d->resizer) { + if (enabled) { + d->resizer = new QSizeGrip(this); + d->resizer->hide(); + d->resizer->installEventFilter(this); + d->showSizeGrip = true; + } else { + delete d->resizer; + d->resizer = 0; + d->showSizeGrip = false; + } + reformat(); + if (d->resizer && isVisible()) + d->tryToShowSizeGrip(); + } +#endif +} + + +/*! + Changes the status bar's appearance to account for item changes. + + Special subclasses may need this function, but geometry management + will usually take care of any necessary rearrangements. +*/ +void QStatusBar::reformat() +{ + Q_D(QStatusBar); + if (d->box) + delete d->box; + + QBoxLayout *vbox; +#ifndef QT_NO_SIZEGRIP + if (d->resizer) { + d->box = new QHBoxLayout(this); + d->box->setMargin(0); + vbox = new QVBoxLayout; + d->box->addLayout(vbox); + } else +#endif + { + vbox = d->box = new QVBoxLayout(this); + d->box->setMargin(0); + } + vbox->addSpacing(3); + QBoxLayout* l = new QHBoxLayout; + vbox->addLayout(l); + l->addSpacing(2); + l->setSpacing(6); + + int maxH = fontMetrics().height(); + + int i; + QStatusBarPrivate::SBItem* item; + for (i=0,item=0; i<d->items.size(); ++i) { + item = d->items.at(i); + if (!item || item->p) + break; + l->addWidget(item->w, item->s); + int itemH = qMin(qSmartMinSize(item->w).height(), item->w->maximumHeight()); + maxH = qMax(maxH, itemH); + } + + l->addStretch(0); + + for (item=0; i<d->items.size(); ++i) { + item = d->items.at(i); + if (!item) + break; + l->addWidget(item->w, item->s); + int itemH = qMin(qSmartMinSize(item->w).height(), item->w->maximumHeight()); + maxH = qMax(maxH, itemH); + } +#ifndef QT_NO_SIZEGRIP + if (d->resizer) { + maxH = qMax(maxH, d->resizer->sizeHint().height()); + d->box->addSpacing(1); + d->box->addWidget(d->resizer, 0, Qt::AlignBottom); + } +#endif + l->addStrut(maxH); + d->savedStrut = maxH; + vbox->addSpacing(2); + d->box->activate(); + repaint(); +} + +/*! + + Hides the normal status indications and displays the given \a + message for the specified number of milli-seconds (\a{timeout}). If + \a{timeout} is 0 (default), the \a {message} remains displayed until + the clearMessage() slot is called or until the showMessage() slot is + called again to change the message. + + Note that showMessage() is called to show temporary explanations of + tool tip texts, so passing a \a{timeout} of 0 is not sufficient to + display a \l{permanent message}{permanent message}. + + \sa messageChanged(), currentMessage(), clearMessage() +*/ +void QStatusBar::showMessage(const QString &message, int timeout) +{ + Q_D(QStatusBar); + if (d->tempItem == message) + return; + + d->tempItem = message; + + if (timeout > 0) { + if (!d->timer) { + d->timer = new QTimer(this); + connect(d->timer, SIGNAL(timeout()), this, SLOT(clearMessage())); + } + d->timer->start(timeout); + } else if (d->timer) { + delete d->timer; + d->timer = 0; + } + + hideOrShow(); +} + +/*! + Removes any temporary message being shown. + + \sa currentMessage(), showMessage(), removeWidget() +*/ + +void QStatusBar::clearMessage() +{ + Q_D(QStatusBar); + if (d->tempItem.isEmpty()) + return; + if (d->timer) { + qDeleteInEventHandler(d->timer); + d->timer = 0; + } + d->tempItem.clear(); + hideOrShow(); +} + +/*! + Returns the temporary message currently shown, + or an empty string if there is no such message. + + \sa showMessage() +*/ +QString QStatusBar::currentMessage() const +{ + Q_D(const QStatusBar); + return d->tempItem; +} + +/*! + \fn void QStatusBar::message(const QString &message, int timeout) + + Use the showMessage() function instead. +*/ + +/*! + \fn void QStatusBar::clear() + + Use the clearMessage() function instead. +*/ + +/*! + \fn QStatusBar::messageChanged(const QString &message) + + This signal is emitted whenever the temporary status message + changes. The new temporary message is passed in the \a message + parameter which is a null-string when the message has been + removed. + + \sa showMessage(), clearMessage() +*/ + +/*! + Ensures that the right widgets are visible. + + Used by the showMessage() and clearMessage() functions. +*/ +void QStatusBar::hideOrShow() +{ + Q_D(QStatusBar); + bool haveMessage = !d->tempItem.isEmpty(); + + QStatusBarPrivate::SBItem* item = 0; + for (int i=0; i<d->items.size(); ++i) { + item = d->items.at(i); + if (!item || item->p) + break; + if (haveMessage && item->w->isVisible()) { + item->w->hide(); + item->w->setAttribute(Qt::WA_WState_ExplicitShowHide, false); + } else if (!haveMessage && !item->w->testAttribute(Qt::WA_WState_ExplicitShowHide)) { + item->w->show(); + } + } + + emit messageChanged(d->tempItem); + repaint(d->messageRect()); +} + +/*! + \reimp + */ +void QStatusBar::showEvent(QShowEvent *) +{ +#ifndef QT_NO_SIZEGRIP + Q_D(QStatusBar); + if (d->resizer && d->showSizeGrip) + d->tryToShowSizeGrip(); +#endif +} + +/*! + \reimp + \fn void QStatusBar::paintEvent(QPaintEvent *event) + + Shows the temporary message, if appropriate, in response to the + paint \a event. +*/ +void QStatusBar::paintEvent(QPaintEvent *event) +{ + Q_D(QStatusBar); + bool haveMessage = !d->tempItem.isEmpty(); + + QPainter p(this); + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive(QStyle::PE_PanelStatusBar, &opt, &p, this); + + for (int i=0; i<d->items.size(); ++i) { + QStatusBarPrivate::SBItem* item = d->items.at(i); + if (item && item->w->isVisible() && (!haveMessage || item->p)) { + QRect ir = item->w->geometry().adjusted(-2, -1, 2, 1); + if (event->rect().contains(ir)) { + QStyleOption opt(0); + opt.rect = ir; + opt.palette = palette(); + opt.state = QStyle::State_None; + style()->drawPrimitive(QStyle::PE_FrameStatusBarItem, &opt, &p, item->w); + } + } + } + if (haveMessage) { + p.setPen(palette().foreground().color()); + p.drawText(d->messageRect(), Qt::AlignLeading | Qt::AlignVCenter | Qt::TextSingleLine, d->tempItem); + } +} + +/*! + \reimp +*/ +void QStatusBar::resizeEvent(QResizeEvent * e) +{ + QWidget::resizeEvent(e); +} + +/*! + \reimp +*/ + +bool QStatusBar::event(QEvent *e) +{ + Q_D(QStatusBar); + + if (e->type() == QEvent::LayoutRequest +#ifdef QT3_SUPPORT + || e->type() == QEvent::LayoutHint +#endif + ) { + // Calculate new strut height and call reformat() if it has changed + int maxH = fontMetrics().height(); + + QStatusBarPrivate::SBItem* item = 0; + for (int i=0; i<d->items.size(); ++i) { + item = d->items.at(i); + if (!item) + break; + int itemH = qMin(qSmartMinSize(item->w).height(), item->w->maximumHeight()); + maxH = qMax(maxH, itemH); + } + +#ifndef QT_NO_SIZEGRIP + if (d->resizer) + maxH = qMax(maxH, d->resizer->sizeHint().height()); +#endif + + if (maxH != d->savedStrut) + reformat(); + else + update(); + } + if (e->type() == QEvent::ChildRemoved) { + QStatusBarPrivate::SBItem* item = 0; + for (int i=0; i<d->items.size(); ++i) { + item = d->items.at(i); + if (!item) + break; + if (item->w == ((QChildEvent*)e)->child()) { + d->items.removeAt(i); + delete item; + } + } + } + +// On Mac OS X Leopard it is possible to drag the window by clicking +// on the tool bar on most applications. +#ifndef Q_WS_MAC + return QWidget::event(e); +#else + if (QSysInfo::MacintoshVersion <= QSysInfo::MV_10_4) + return QWidget::event(e); + + // Enable drag-click only if the status bar is the status bar for a + // QMainWindow with a unifed toolbar. + if (parent() == 0 || qobject_cast<QMainWindow *>(parent()) == 0 || + qobject_cast<QMainWindow *>(parent())->unifiedTitleAndToolBarOnMac() == false ) + return QWidget::event(e); + + // Check for mouse events. + QMouseEvent *mouseEvent; + if (e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseMove || + e->type() == QEvent::MouseButtonRelease) { + mouseEvent = static_cast <QMouseEvent*>(e); + } else { + return QWidget::event(e); + } + + // The following is a standard mouse drag handler. + if (e->type() == QEvent::MouseButtonPress && (mouseEvent->button() == Qt::LeftButton)) { + d->dragStart = mouseEvent->pos(); + } else if (e->type() == QEvent::MouseMove){ + if (d->dragStart == QPoint()) + return QWidget::event(e); + QPoint pos = mouseEvent->pos(); + QPoint delta = (pos - d->dragStart); + window()->move(window()->pos() + delta); + } else if (e->type() == QEvent::MouseButtonRelease && (mouseEvent->button() == Qt::LeftButton)){ + d->dragStart = QPoint(); + } else { + return QWidget::event(e); + } + + return true; +#endif +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/widgets/qstatusbar.h b/src/gui/widgets/qstatusbar.h new file mode 100644 index 0000000..4434f74 --- /dev/null +++ b/src/gui/widgets/qstatusbar.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATUSBAR_H +#define QSTATUSBAR_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STATUSBAR + +class QStatusBarPrivate; + +class Q_GUI_EXPORT QStatusBar: public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool sizeGripEnabled READ isSizeGripEnabled WRITE setSizeGripEnabled) + +public: + explicit QStatusBar(QWidget* parent=0); + virtual ~QStatusBar(); + + void addWidget(QWidget *widget, int stretch = 0); + int insertWidget(int index, QWidget *widget, int stretch = 0); + void addPermanentWidget(QWidget *widget, int stretch = 0); + int insertPermanentWidget(int index, QWidget *widget, int stretch = 0); + void removeWidget(QWidget *widget); + + void setSizeGripEnabled(bool); + bool isSizeGripEnabled() const; + + QString currentMessage() const; + +public Q_SLOTS: + void showMessage(const QString &text, int timeout = 0); + void clearMessage(); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QStatusBar(QWidget* parent, const char* name); + QT3_SUPPORT void addWidget(QWidget *w, int stretch, bool permanent) + { if (permanent) addPermanentWidget(w, stretch); else addWidget(w, stretch); } +public Q_SLOTS: + inline QT_MOC_COMPAT void message(const QString &text, int timeout = 0) { showMessage(text, timeout); } + inline QT_MOC_COMPAT void clear() { clearMessage(); } +#endif + +Q_SIGNALS: + void messageChanged(const QString &text); + +protected: + void showEvent(QShowEvent *); + void paintEvent(QPaintEvent *); + void resizeEvent(QResizeEvent *); + + // ### Qt 5: consider making reformat() and hideOrShow() private + void reformat(); + void hideOrShow(); + bool event(QEvent *); + +private: + Q_DISABLE_COPY(QStatusBar) + Q_DECLARE_PRIVATE(QStatusBar) +}; + +#endif // QT_NO_STATUSBAR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTATUSBAR_H diff --git a/src/gui/widgets/qtabbar.cpp b/src/gui/widgets/qtabbar.cpp new file mode 100644 index 0000000..7d970ad --- /dev/null +++ b/src/gui/widgets/qtabbar.cpp @@ -0,0 +1,2301 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qlayoutengine_p.h" +#include "qabstractitemdelegate.h" +#include "qapplication.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qevent.h" +#include "qpainter.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qstylepainter.h" +#include "qtabwidget.h" +#include "qtooltip.h" +#include "qwhatsthis.h" +#include "private/qtextengine_p.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif + +#include "qdebug.h" +#include "private/qtabbar_p.h" + +#ifndef QT_NO_TABBAR + +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +inline static bool verticalTabs(QTabBar::Shape shape) +{ + return shape == QTabBar::RoundedWest + || shape == QTabBar::RoundedEast + || shape == QTabBar::TriangularWest + || shape == QTabBar::TriangularEast; +} + +void QTabBarPrivate::updateMacBorderMetrics() +{ +#if (defined Q_WS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + Q_Q(QTabBar); + ::HIContentBorderMetrics metrics; + + // TODO: get metrics to preserve the bottom value + // TODO: test tab bar position + + // push the black line at the bottom of the menu bar down to the client are so we can paint over it + metrics.top = (documentMode && q->isVisible()) ? 1 : 0; + metrics.bottom = 0; + metrics.left = 0; + metrics.right = 0; + + qt_mac_updateContentBorderMetricts(qt_mac_window_for(q), metrics); + } +#endif +} + +/*! + Initialize \a option with the values from the tab at \a tabIndex. This method + is useful for subclasses when they need a QStyleOptionTab, QStyleOptionTabV2, + or QStyleOptionTabV3 but don't want to fill in all the information themselves. + This function will check the version of the QStyleOptionTab and fill in the + additional values for a QStyleOptionTabV2 and QStyleOptionTabV3. + + \sa QStyleOption::initFrom() QTabWidget::initStyleOption() +*/ +void QTabBar::initStyleOption(QStyleOptionTab *option, int tabIndex) const +{ + Q_D(const QTabBar); + int totalTabs = d->tabList.size(); + + if (!option || (tabIndex < 0 || tabIndex >= totalTabs)) + return; + + const QTabBarPrivate::Tab &tab = d->tabList.at(tabIndex); + option->initFrom(this); + option->state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver); + option->rect = tabRect(tabIndex); + bool isCurrent = tabIndex == d->currentIndex; + option->row = 0; + if (tabIndex == d->pressedIndex) + option->state |= QStyle::State_Sunken; + if (isCurrent) + option->state |= QStyle::State_Selected; + if (isCurrent && hasFocus()) + option->state |= QStyle::State_HasFocus; + if (!tab.enabled) + option->state &= ~QStyle::State_Enabled; + if (isActiveWindow()) + option->state |= QStyle::State_Active; + if (option->rect == d->hoverRect) + option->state |= QStyle::State_MouseOver; + option->shape = d->shape; + option->text = tab.text; + + if (tab.textColor.isValid()) + option->palette.setColor(foregroundRole(), tab.textColor); + + option->icon = tab.icon; + if (QStyleOptionTabV2 *optionV2 = qstyleoption_cast<QStyleOptionTabV2 *>(option)) + optionV2->iconSize = iconSize(); // Will get the default value then. + + if (QStyleOptionTabV3 *optionV3 = qstyleoption_cast<QStyleOptionTabV3 *>(option)) { + optionV3->leftButtonSize = tab.leftWidget ? tab.leftWidget->size() : QSize(); + optionV3->rightButtonSize = tab.rightWidget ? tab.rightWidget->size() : QSize(); + optionV3->documentMode = d->documentMode; + } + + if (tabIndex > 0 && tabIndex - 1 == d->currentIndex) + option->selectedPosition = QStyleOptionTab::PreviousIsSelected; + else if (tabIndex < totalTabs - 1 && tabIndex + 1 == d->currentIndex) + option->selectedPosition = QStyleOptionTab::NextIsSelected; + else + option->selectedPosition = QStyleOptionTab::NotAdjacent; + + bool paintBeginning = (tabIndex == 0) || (d->dragInProgress && tabIndex == d->pressedIndex + 1); + bool paintEnd = (tabIndex == totalTabs - 1) || (d->dragInProgress && tabIndex == d->pressedIndex - 1); + if (paintBeginning) { + if (paintEnd) + option->position = QStyleOptionTab::OnlyOneTab; + else + option->position = QStyleOptionTab::Beginning; + } else if (paintEnd) { + option->position = QStyleOptionTab::End; + } else { + option->position = QStyleOptionTab::Middle; + } + +#ifndef QT_NO_TABWIDGET + if (const QTabWidget *tw = qobject_cast<const QTabWidget *>(parentWidget())) { + if (tw->cornerWidget(Qt::TopLeftCorner) || tw->cornerWidget(Qt::BottomLeftCorner)) + option->cornerWidgets |= QStyleOptionTab::LeftCornerWidget; + if (tw->cornerWidget(Qt::TopRightCorner) || tw->cornerWidget(Qt::BottomRightCorner)) + option->cornerWidgets |= QStyleOptionTab::RightCornerWidget; + } + + QRect textRect = style()->subElementRect(QStyle::SE_TabBarTabText, option, this); + + option->text = fontMetrics().elidedText(option->text, d->elideMode, textRect.width(), + Qt::TextShowMnemonic); +#endif +} + +/*! + \class QTabBar + \brief The QTabBar class provides a tab bar, e.g. for use in tabbed dialogs. + + \ingroup basicwidgets + \mainclass + + QTabBar is straightforward to use; it draws the tabs using one of + the predefined \link QTabBar::Shape shapes\endlink, and emits a + signal when a tab is selected. It can be subclassed to tailor the + look and feel. Qt also provides a ready-made \l{QTabWidget}. + + Each tab has a tabText(), an optional tabIcon(), an optional + tabToolTip(), optional tabWhatsThis() and optional tabData(). + The tabs's attributes can be changed with setTabText(), setTabIcon(), + setTabToolTip(), setTabWhatsThis and setTabData(). Each tabs can be + enabled or disabled individually with setTabEnabled(). + + Each tab can display text in a distinct color. The current text color + for a tab can be found with the tabTextColor() function. Set the text + color for a particular tab with setTabTextColor(). + + Tabs are added using addTab(), or inserted at particular positions + using insertTab(). The total number of tabs is given by + count(). Tabs can be removed from the tab bar with + removeTab(). Combining removeTab() and insertTab() allows you to + move tabs to different positions. + + The \l shape property defines the tabs' appearance. The choice of + shape is a matter of taste, although tab dialogs (for preferences + and similar) invariably use \l RoundedNorth. + Tab controls in windows other than dialogs almost + always use either \l RoundedSouth or \l TriangularSouth. Many + spreadsheets and other tab controls in which all the pages are + essentially similar use \l TriangularSouth, whereas \l + RoundedSouth is used mostly when the pages are different (e.g. a + multi-page tool palette). The default in QTabBar is \l + RoundedNorth. + + The most important part of QTabBar's API is the currentChanged() + signal. This is emitted whenever the current tab changes (even at + startup, when the current tab changes from 'none'). There is also + a slot, setCurrentIndex(), which can be used to select a tab + programmatically. The function currentIndex() returns the index of + the current tab, \l count holds the number of tabs. + + QTabBar creates automatic mnemonic keys in the manner of QAbstractButton; + e.g. if a tab's label is "\&Graphics", Alt+G becomes a shortcut + key for switching to that tab. + + The following virtual functions may need to be reimplemented in + order to tailor the look and feel or store extra data with each + tab: + + \list + \i tabSizeHint() calcuates the size of a tab. + \i tabInserted() notifies that a new tab was added. + \i tabRemoved() notifies that a tab was removed. + \i tabLayoutChange() notifies that the tabs have been re-laid out. + \i paintEvent() paints all tabs. + \endlist + + For subclasses, you might also need the tabRect() functions which + returns the visual geometry of a single tab. + + \table 100% + \row \o \inlineimage plastique-tabbar.png Screenshot of a Plastique style tab bar + \o A tab bar shown in the Plastique widget style. + \row \o \inlineimage plastique-tabbar-truncated.png Screenshot of a truncated Plastique tab bar + \o A truncated tab bar shown in the Plastique widget style. + \endtable + + \sa QTabWidget +*/ + +/*! + \enum QTabBar::Shape + + This enum type lists the built-in shapes supported by QTabBar. Treat these + as hints as some styles may not render some of the shapes. However, + position should be honored. + + \value RoundedNorth The normal rounded look above the pages + + \value RoundedSouth The normal rounded look below the pages + + \value RoundedWest The normal rounded look on the left side of the pages + + \value RoundedEast The normal rounded look on the right side the pages + + \value TriangularNorth Triangular tabs above the pages. + + \value TriangularSouth Triangular tabs similar to those used in + the Excel spreadsheet, for example + + \value TriangularWest Triangular tabs on the left of the pages. + + \value TriangularEast Triangular tabs on the right of the pages. + \omitvalue RoundedAbove + \omitvalue RoundedBelow + \omitvalue TriangularAbove + \omitvalue TriangularBelow +*/ + +/*! + \fn void QTabBar::currentChanged(int index) + + This signal is emitted when the tab bar's current tab changes. The + new current has the given \a index, or -1 if there isn't a new one + (for example, if there are no tab in the QTabBar) +*/ + +/*! + \fn void QTabBar::tabCloseRequested(int index) + \since 4.5 + + This signal is emitted when the close button on a tab is clicked. + The \a index is the index that should be removed. + + \sa setTabsClosable() +*/ + +/*! + \fn void QTabBar::tabMoved(int from, int to) + \since 4.5 + + This signal is emitted when the tab has moved the tab + at index position \a from to index position \a to. + + note: QTabWidget will automatically move the page when + this signal is emitted from its tab bar. + + \sa moveTab() +*/ + +int QTabBarPrivate::extraWidth() const +{ + Q_Q(const QTabBar); + return 2 * qMax(q->style()->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, 0, q), + QApplication::globalStrut().width()); +} + +void QTabBarPrivate::init() +{ + Q_Q(QTabBar); + leftB = new QToolButton(q); + leftB->setAutoRepeat(true); + QObject::connect(leftB, SIGNAL(clicked()), q, SLOT(_q_scrollTabs())); + leftB->hide(); + rightB = new QToolButton(q); + rightB->setAutoRepeat(true); + QObject::connect(rightB, SIGNAL(clicked()), q, SLOT(_q_scrollTabs())); + rightB->hide(); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + leftB->setFocusPolicy(Qt::NoFocus); + rightB->setFocusPolicy(Qt::NoFocus); + q->setFocusPolicy(Qt::NoFocus); + } else +#endif + q->setFocusPolicy(Qt::TabFocus); + q->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + elideMode = Qt::TextElideMode(q->style()->styleHint(QStyle::SH_TabBar_ElideMode, 0, q)); + useScrollButtons = !q->style()->styleHint(QStyle::SH_TabBar_PreferNoArrows, 0, q); +} + +QTabBarPrivate::Tab *QTabBarPrivate::at(int index) +{ + return validIndex(index)?&tabList[index]:0; +} + +const QTabBarPrivate::Tab *QTabBarPrivate::at(int index) const +{ + return validIndex(index)?&tabList[index]:0; +} + +int QTabBarPrivate::indexAtPos(const QPoint &p) const +{ + Q_Q(const QTabBar); + if (q->tabRect(currentIndex).contains(p)) + return currentIndex; + for (int i = 0; i < tabList.count(); ++i) + if (tabList.at(i).enabled && q->tabRect(i).contains(p)) + return i; + return -1; +} + +void QTabBarPrivate::layoutTabs() +{ + Q_Q(QTabBar); + scrollOffset = 0; + layoutDirty = false; + QSize size = q->size(); + int last, available; + int maxExtent; + int i; + bool vertTabs = verticalTabs(shape); + int tabChainIndex = 0; + + Qt::Alignment tabAlignment = Qt::Alignment(q->style()->styleHint(QStyle::SH_TabBar_Alignment, 0, q)); + QVector<QLayoutStruct> tabChain(tabList.count() + 2); + + // We put an empty item at the front and back and set its expansive attribute + // depending on tabAlignment. + tabChain[tabChainIndex].init(); + tabChain[tabChainIndex].expansive = (tabAlignment != Qt::AlignLeft) + && (tabAlignment != Qt::AlignJustify); + tabChain[tabChainIndex].empty = true; + ++tabChainIndex; + + // We now go through our list of tabs and set the minimum size and the size hint + // This will allow us to elide text if necessary. Since we don't set + // a maximum size, tabs will EXPAND to fill up the empty space. + // Since tab widget is rather *ahem* strict about keeping the geometry of the + // tab bar to its absolute minimum, this won't bleed through, but will show up + // if you use tab bar on its own (a.k.a. not a bug, but a feature). + // Update: if expanding is false, we DO set a maximum size to prevent the tabs + // being wider than necessary. + if (!vertTabs) { + int minx = 0; + int x = 0; + int maxHeight = 0; + for (i = 0; i < tabList.count(); ++i, ++tabChainIndex) { + QSize sz = q->tabSizeHint(i); + tabList[i].maxRect = QRect(x, 0, sz.width(), sz.height()); + x += sz.width(); + maxHeight = qMax(maxHeight, sz.height()); + sz = minimumTabSizeHint(i); + tabList[i].minRect = QRect(minx, 0, sz.width(), sz.height()); + minx += sz.width(); + tabChain[tabChainIndex].init(); + tabChain[tabChainIndex].sizeHint = tabList.at(i).maxRect.width(); + tabChain[tabChainIndex].minimumSize = sz.width(); + tabChain[tabChainIndex].empty = false; + tabChain[tabChainIndex].expansive = true; + + if (!expanding) + tabChain[tabChainIndex].maximumSize = tabChain[tabChainIndex].sizeHint; + } + + last = minx; + available = size.width(); + maxExtent = maxHeight; + } else { + int miny = 0; + int y = 0; + int maxWidth = 0; + for (i = 0; i < tabList.count(); ++i, ++tabChainIndex) { + QSize sz = q->tabSizeHint(i); + tabList[i].maxRect = QRect(0, y, sz.width(), sz.height()); + y += sz.height(); + maxWidth = qMax(maxWidth, sz.width()); + sz = minimumTabSizeHint(i); + tabList[i].minRect = QRect(0, miny, sz.width(), sz.height()); + miny += sz.height(); + tabChain[tabChainIndex].init(); + tabChain[tabChainIndex].sizeHint = tabList.at(i).maxRect.height(); + tabChain[tabChainIndex].minimumSize = sz.height(); + tabChain[tabChainIndex].empty = false; + tabChain[tabChainIndex].expansive = true; + + if (!expanding) + tabChain[tabChainIndex].maximumSize = tabChain[tabChainIndex].sizeHint; + } + + last = miny; + available = size.height(); + maxExtent = maxWidth; + } + + if (pressedIndex != -1 && movable) + grabCache(0, tabList.count(), true); + + Q_ASSERT(tabChainIndex == tabChain.count() - 1); // add an assert just to make sure. + // Mirror our front item. + tabChain[tabChainIndex].init(); + tabChain[tabChainIndex].expansive = (tabAlignment != Qt::AlignRight) + && (tabAlignment != Qt::AlignJustify); + tabChain[tabChainIndex].empty = true; + + // Do the calculation + qGeomCalc(tabChain, 0, tabChain.count(), 0, qMax(available, last), 0); + + // Use the results + for (i = 0; i < tabList.count(); ++i) { + const QLayoutStruct &lstruct = tabChain.at(i + 1); + if (!vertTabs) + tabList[i].rect.setRect(lstruct.pos, 0, lstruct.size, maxExtent); + else + tabList[i].rect.setRect(0, lstruct.pos, maxExtent, lstruct.size); + } + + if (useScrollButtons && tabList.count() && last > available) { + int extra = extraWidth(); + if (!vertTabs) { + Qt::LayoutDirection ld = q->layoutDirection(); + QRect arrows = QStyle::visualRect(ld, q->rect(), + QRect(available - extra, 0, extra, size.height())); + int buttonOverlap = q->style()->pixelMetric(QStyle::PM_TabBar_ScrollButtonOverlap, 0, q); + + if (ld == Qt::LeftToRight) { + leftB->setGeometry(arrows.left(), arrows.top(), extra/2, arrows.height()); + rightB->setGeometry(arrows.right() - extra/2 + buttonOverlap, arrows.top(), + extra/2, arrows.height()); + leftB->setArrowType(Qt::LeftArrow); + rightB->setArrowType(Qt::RightArrow); + } else { + rightB->setGeometry(arrows.left(), arrows.top(), extra/2, arrows.height()); + leftB->setGeometry(arrows.right() - extra/2 + buttonOverlap, arrows.top(), + extra/2, arrows.height()); + rightB->setArrowType(Qt::LeftArrow); + leftB->setArrowType(Qt::RightArrow); + } + } else { + QRect arrows = QRect(0, available - extra, size.width(), extra ); + leftB->setGeometry(arrows.left(), arrows.top(), arrows.width(), extra/2); + leftB->setArrowType(Qt::UpArrow); + rightB->setGeometry(arrows.left(), arrows.bottom() - extra/2 + 1, + arrows.width(), extra/2); + rightB->setArrowType(Qt::DownArrow); + } + leftB->setEnabled(scrollOffset > 0); + rightB->setEnabled(last - scrollOffset >= available - extra); + leftB->show(); + rightB->show(); + } else { + rightB->hide(); + leftB->hide(); + } + + layoutWidgets(); + q->tabLayoutChange(); +} + +void QTabBarPrivate::makeVisible(int index) +{ + Q_Q(QTabBar); + if (!validIndex(index) || leftB->isHidden()) + return; + + const QRect tabRect = tabList.at(index).rect; + const int oldScrollOffset = scrollOffset; + const bool horiz = !verticalTabs(shape); + const int available = (horiz ? q->width() : q->height()) - extraWidth(); + const int start = horiz ? tabRect.left() : tabRect.top(); + const int end = horiz ? tabRect.right() : tabRect.bottom(); + if (start < scrollOffset) // too far left + scrollOffset = start - (index ? 8 : 0); + else if (end > scrollOffset + available) // too far right + scrollOffset = end - available + 1; + + leftB->setEnabled(scrollOffset > 0); + const int last = horiz ? tabList.last().rect.right() : tabList.last().rect.bottom(); + rightB->setEnabled(last - scrollOffset >= available); + if (oldScrollOffset != scrollOffset) { + q->update(); + layoutWidgets(); + } +} + +void QTabBarPrivate::layoutTab(int index) +{ + Q_Q(QTabBar); + Q_ASSERT(index >= 0); + + Tab &tab = tabList[index]; + bool vertical = verticalTabs(shape); + if (!(tab.leftWidget || tab.rightWidget)) + return; + + QStyleOptionTabV3 opt; + q->initStyleOption(&opt, index); + if (tab.leftWidget) { + QRect rect = q->style()->subElementRect(QStyle::SE_TabBarTabLeftButton, &opt, q); + QPoint p = rect.topLeft(); + if ((index == pressedIndex) || paintWithOffsets) { + if (vertical) + p.setY(p.y() + tabList[index].dragOffset); + else + p.setX(p.x() + tabList[index].dragOffset); + } + tab.leftWidget->move(p); + } + if (tab.rightWidget) { + QRect rect = q->style()->subElementRect(QStyle::SE_TabBarTabRightButton, &opt, q); + QPoint p = rect.topLeft(); + if ((index == pressedIndex) || paintWithOffsets) { + if (vertical) + p.setY(p.y() + tab.dragOffset); + else + p.setX(p.x() + tab.dragOffset); + } + tab.rightWidget->move(p); + } +} + +void QTabBarPrivate::layoutWidgets(int index) +{ + Q_Q(QTabBar); + int start = 0; + int end = q->count(); + if (index != -1) { + start = qMax(index, 0); + end = qMin(end, start + 1); + } + for (int i = start; i < end; ++i) { + layoutTab(i); + } +} + +void QTabBarPrivate::_q_closeTab() +{ + Q_Q(QTabBar); + QObject *object = q->sender(); + int tabToClose = -1; + QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)q->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, q); + for (int i = 0; i < tabList.count(); ++i) { + if (closeSide == QTabBar::LeftSide) { + if (tabList.at(i).leftWidget == object) { + tabToClose = i; + break; + } + } else { + if (tabList.at(i).rightWidget == object) { + tabToClose = i; + break; + } + } + } + if (tabToClose != -1) + emit q->tabCloseRequested(tabToClose); +} + +void QTabBarPrivate::_q_scrollTabs() +{ + Q_Q(QTabBar); + const QObject *sender = q->sender(); + int i = -1; + if (!verticalTabs(shape)) { + if (sender == leftB) { + for (i = tabList.count() - 1; i >= 0; --i) { + if (tabList.at(i).rect.left() - scrollOffset < 0) { + makeVisible(i); + return; + } + } + } else if (sender == rightB) { + int availableWidth = q->width() - extraWidth(); + for (i = 0; i < tabList.count(); ++i) { + if (tabList.at(i).rect.right() - scrollOffset > availableWidth) { + makeVisible(i); + return; + } + } + } + } else { // vertical + if (sender == leftB) { + for (i = tabList.count() - 1; i >= 0; --i) { + if (tabList.at(i).rect.top() - scrollOffset < 0) { + makeVisible(i); + return; + } + } + } else if (sender == rightB) { + int available = q->height() - extraWidth(); + for (i = 0; i < tabList.count(); ++i) { + if (tabList.at(i).rect.bottom() - scrollOffset > available) { + makeVisible(i); + return; + } + } + } + } +} + +void QTabBarPrivate::refresh() +{ + Q_Q(QTabBar); + + // be safe in case a subclass is also handling move with the tabs + if (pressedIndex != -1 + && movable + && QApplication::mouseButtons() == Qt::NoButton) { + _q_moveTabFinished(pressedIndex); + if (!validIndex(pressedIndex)) + pressedIndex = -1; + } + + if (!q->isVisible()) { + layoutDirty = true; + } else { + layoutTabs(); + makeVisible(currentIndex); + q->update(); + q->updateGeometry(); + } +} + +/*! + Creates a new tab bar with the given \a parent. +*/ +QTabBar::QTabBar(QWidget* parent) + :QWidget(*new QTabBarPrivate, parent, 0) +{ + Q_D(QTabBar); + d->init(); +} + + +/*! + Destroys the tab bar. +*/ +QTabBar::~QTabBar() +{ +} + +/*! + \property QTabBar::shape + \brief the shape of the tabs in the tab bar + + Possible values for this property are described by the Shape enum. +*/ + + +QTabBar::Shape QTabBar::shape() const +{ + Q_D(const QTabBar); + return d->shape; +} + +void QTabBar::setShape(Shape shape) +{ + Q_D(QTabBar); + if (d->shape == shape) + return; + d->shape = shape; + d->refresh(); +} + +/*! + \property QTabBar::drawBase + \brief defines whether or not tab bar should draw its base. + + If true then QTabBar draws a base in relation to the styles overlab. + Otherwise only the tabs are drawn. + + \sa QStyle::pixelMetric() QStyle::PM_TabBarBaseOverlap QStyleOptionTabBarBaseV2 +*/ + +void QTabBar::setDrawBase(bool drawBase) +{ + Q_D(QTabBar); + if (d->drawBase == drawBase) + return; + d->drawBase = drawBase; + update(); +} + +bool QTabBar::drawBase() const +{ + Q_D(const QTabBar); + return d->drawBase; +} + +/*! + Adds a new tab with text \a text. Returns the new + tab's index. +*/ +int QTabBar::addTab(const QString &text) +{ + return insertTab(-1, text); +} + +/*! + \overload + + Adds a new tab with icon \a icon and text \a + text. Returns the new tab's index. +*/ +int QTabBar::addTab(const QIcon& icon, const QString &text) +{ + return insertTab(-1, icon, text); +} + +/*! + Inserts a new tab with text \a text at position \a index. If \a + index is out of range, the new tab is appened. Returns the new + tab's index. +*/ +int QTabBar::insertTab(int index, const QString &text) +{ + return insertTab(index, QIcon(), text); +} + +/*!\overload + + Inserts a new tab with icon \a icon and text \a text at position + \a index. If \a index is out of range, the new tab is + appended. Returns the new tab's index. + + If the QTabBar was empty before this function is called, the inserted tab + becomes the current tab. + + Inserting a new tab at an index less than or equal to the current index + will increment the current index, but keep the current tab. +*/ +int QTabBar::insertTab(int index, const QIcon& icon, const QString &text) +{ + Q_D(QTabBar); + if (!d->validIndex(index)) { + index = d->tabList.count(); + d->tabList.append(QTabBarPrivate::Tab(icon, text)); + } else { + d->tabList.insert(index, QTabBarPrivate::Tab(icon, text)); + } +#ifndef QT_NO_SHORTCUT + d->tabList[index].shortcutId = grabShortcut(QKeySequence::mnemonic(text)); +#endif + d->refresh(); + if (d->tabList.count() == 1) + setCurrentIndex(index); + else if (index <= d->currentIndex) + ++d->currentIndex; + + if (d->closeButtonOnTabs) { + QStyleOptionTabV3 opt; + initStyleOption(&opt, index); + ButtonPosition closeSide = (ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this); + QAbstractButton *closeButton = new CloseButton(this); + connect(closeButton, SIGNAL(clicked()), this, SLOT(_q_closeTab())); + setTabButton(index, closeSide, closeButton); + } + + for (int i = 0; i < d->tabList.count(); ++i) { + if (d->tabList[i].lastTab >= index) + ++d->tabList[i].lastTab; + } + + tabInserted(index); + return index; +} + + +/*! + Removes the tab at position \a index. + + \sa SelectionBehavior + */ +void QTabBar::removeTab(int index) +{ + Q_D(QTabBar); + if (d->validIndex(index)) { +#ifndef QT_NO_SHORTCUT + releaseShortcut(d->tabList.at(index).shortcutId); +#endif + if (d->tabList[index].leftWidget) { + d->tabList[index].leftWidget->hide(); + d->tabList[index].leftWidget->deleteLater(); + d->tabList[index].leftWidget = 0; + } + if (d->tabList[index].rightWidget) { + d->tabList[index].rightWidget->hide(); + d->tabList[index].rightWidget->deleteLater(); + d->tabList[index].rightWidget = 0; + } + + int newIndex = d->tabList[index].lastTab; + d->tabList.removeAt(index); + for (int i = 0; i < d->tabList.count(); ++i) { + if (d->tabList[i].lastTab == index) + d->tabList[i].lastTab = -1; + if (d->tabList[i].lastTab > index) + --d->tabList[i].lastTab; + } + if (index == d->currentIndex) { + // The current tab is going away, in order to make sure + // we emit that "current has changed", we need to reset this + // around. + d->currentIndex = -1; + if (d->tabList.size() > 0) { + switch(d->selectionBehaviorOnRemove) { + case SelectPreviousTab: + if (newIndex > index) + newIndex--; + if (d->validIndex(newIndex)) + break; + // else fallthrough + case SelectRightTab: + newIndex = index; + if (newIndex >= d->tabList.size()) + newIndex = d->tabList.size() - 1; + break; + case SelectLeftTab: + newIndex = index - 1; + if (newIndex < 0) + newIndex = 0; + break; + default: + break; + } + + if (d->validIndex(newIndex)) { + // don't loose newIndex's old through setCurrentIndex + int bump = d->tabList[newIndex].lastTab; + setCurrentIndex(newIndex); + d->tabList[newIndex].lastTab = bump; + } + } else { + emit currentChanged(-1); + } + } else if (index < d->currentIndex) { + setCurrentIndex(d->currentIndex - 1); + } + d->refresh(); + tabRemoved(index); + } +} + + +/*! + Returns true if the tab at position \a index is enabled; otherwise + returns false. +*/ +bool QTabBar::isTabEnabled(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->enabled; + return false; +} + +/*! + If \a enabled is true then the tab at position \a index is + enabled; otherwise the item at position \a index is disabled. +*/ +void QTabBar::setTabEnabled(int index, bool enabled) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) { + tab->enabled = enabled; +#ifndef QT_NO_SHORTCUT + setShortcutEnabled(tab->shortcutId, enabled); +#endif + update(); + if (!enabled && index == d->currentIndex) + setCurrentIndex(d->validIndex(index+1)?index+1:0); + else if (enabled && !d->validIndex(d->currentIndex)) + setCurrentIndex(index); + } +} + + +/*! + Returns the text of the tab at position \a index, or an empty + string if \a index is out of range. +*/ +QString QTabBar::tabText(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->text; + return QString(); +} + +/*! + Sets the text of the tab at position \a index to \a text. +*/ +void QTabBar::setTabText(int index, const QString &text) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) { + tab->text = text; +#ifndef QT_NO_SHORTCUT + releaseShortcut(tab->shortcutId); + tab->shortcutId = grabShortcut(QKeySequence::mnemonic(text)); + setShortcutEnabled(tab->shortcutId, tab->enabled); +#endif + d->refresh(); + } +} + +/*! + Returns the text color of the tab with the given \a index, or a invalid + color if \a index is out of range. + + \sa setTabTextColor() +*/ +QColor QTabBar::tabTextColor(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->textColor; + return QColor(); +} + +/*! + Sets the color of the text in the tab with the given \a index to the specified \a color. + + If an invalid color is specified, the tab will use the QTabBar foreground role instead. + + \sa tabTextColor() +*/ +void QTabBar::setTabTextColor(int index, const QColor &color) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) { + tab->textColor = color; + update(tabRect(index)); + } +} + +/*! + Returns the icon of the tab at position \a index, or a null icon + if \a index is out of range. +*/ +QIcon QTabBar::tabIcon(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->icon; + return QIcon(); +} + +/*! + Sets the icon of the tab at position \a index to \a icon. +*/ +void QTabBar::setTabIcon(int index, const QIcon & icon) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) { + bool simpleIconChange = (!icon.isNull() && !tab->icon.isNull()); + tab->icon = icon; + if (simpleIconChange) + update(tabRect(index)); + else + d->refresh(); + } +} + +#ifndef QT_NO_TOOLTIP +/*! + Sets the tool tip of the tab at position \a index to \a tip. +*/ +void QTabBar::setTabToolTip(int index, const QString & tip) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) + tab->toolTip = tip; +} + +/*! + Returns the tool tip of the tab at position \a index, or an empty + string if \a index is out of range. +*/ +QString QTabBar::tabToolTip(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->toolTip; + return QString(); +} +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_WHATSTHIS +/*! + \since 4.1 + + Sets the What's This help text of the tab at position \a index + to \a text. +*/ +void QTabBar::setTabWhatsThis(int index, const QString &text) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) + tab->whatsThis = text; +} + +/*! + \since 4.1 + + Returns the What's This help text of the tab at position \a index, + or an empty string if \a index is out of range. +*/ +QString QTabBar::tabWhatsThis(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->whatsThis; + return QString(); +} + +#endif // QT_NO_WHATSTHIS + +/*! + Sets the data of the tab at position \a index to \a data. +*/ +void QTabBar::setTabData(int index, const QVariant & data) +{ + Q_D(QTabBar); + if (QTabBarPrivate::Tab *tab = d->at(index)) + tab->data = data; +} + +/*! + Returns the datad of the tab at position \a index, or a null + variant if \a index is out of range. +*/ +QVariant QTabBar::tabData(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) + return tab->data; + return QVariant(); +} + +/*! + Returns the visual rectangle of the of the tab at position \a + index, or a null rectangle if \a index is out of range. +*/ +QRect QTabBar::tabRect(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) { + if (d->layoutDirty) + const_cast<QTabBarPrivate*>(d)->layoutTabs(); + QRect r = tab->rect; + if (verticalTabs(d->shape)) + r.translate(0, -d->scrollOffset); + else + r.translate(-d->scrollOffset, 0); + if (!verticalTabs(d->shape)) + r = QStyle::visualRect(layoutDirection(), rect(), r); + return r; + } + return QRect(); +} + +/*! + \since 4.3 + Returns the index of the tab that covers \a position or -1 if no + tab covers \a position; +*/ + +int QTabBar::tabAt(const QPoint &position) const +{ + Q_D(const QTabBar); + if (d->validIndex(d->currentIndex) + && tabRect(d->currentIndex).contains(position)) { + return d->currentIndex; + } + const int max = d->tabList.size(); + for (int i = 0; i < max; ++i) { + if (tabRect(i).contains(position)) { + return i; + } + } + return -1; +} + +/*! + \property QTabBar::currentIndex + \brief the index of the tab bar's visible tab + + The current index is -1 if there is no current tab. +*/ + +int QTabBar::currentIndex() const +{ + Q_D(const QTabBar); + if (d->validIndex(d->currentIndex)) + return d->currentIndex; + return -1; +} + + +void QTabBar::setCurrentIndex(int index) +{ + Q_D(QTabBar); + if (d->dragInProgress && d->pressedIndex != -1) + return; + + int oldIndex = d->currentIndex; + if (d->validIndex(index) && d->currentIndex != index) { + d->currentIndex = index; + update(); + d->makeVisible(index); +#ifdef QT3_SUPPORT + emit selected(index); +#endif + emit currentChanged(index); + d->tabList[index].lastTab = oldIndex; + d->layoutWidgets(oldIndex); + d->layoutWidgets(index); + } +} + +/*! + \property QTabBar::iconSize + \brief The size for icons in the tab bar + \since 4.1 + + The default value is style-dependent. \c iconSize is a maximum + size; icons that are smaller are not scaled up. + + \sa QTabWidget::iconSize +*/ +QSize QTabBar::iconSize() const +{ + Q_D(const QTabBar); + if (d->iconSize.isValid()) + return d->iconSize; + int iconExtent = style()->pixelMetric(QStyle::PM_TabBarIconSize, 0, this); + return QSize(iconExtent, iconExtent); + +} + +void QTabBar::setIconSize(const QSize &size) +{ + Q_D(QTabBar); + d->iconSize = size; + d->layoutDirty = true; + update(); + updateGeometry(); +} + +/*! + \property QTabBar::count + \brief the number of tabs in the tab bar +*/ + +int QTabBar::count() const +{ + Q_D(const QTabBar); + return d->tabList.count(); +} + + +/*!\reimp + */ +QSize QTabBar::sizeHint() const +{ + Q_D(const QTabBar); + if (d->layoutDirty) + const_cast<QTabBarPrivate*>(d)->layoutTabs(); + QRect r; + for (int i = 0; i < d->tabList.count(); ++i) + r = r.united(d->tabList.at(i).maxRect); + QSize sz = QApplication::globalStrut(); + return r.size().expandedTo(sz); +} + +/*!\reimp + */ +QSize QTabBar::minimumSizeHint() const +{ + Q_D(const QTabBar); + if (!d->useScrollButtons) { + QRect r; + for (int i = 0; i < d->tabList.count(); ++i) + r = r.united(d->tabList.at(i).minRect); + return r.size().expandedTo(QApplication::globalStrut()); + } + if (verticalTabs(d->shape)) + return QSize(sizeHint().width(), d->rightB->sizeHint().height() * 2 + 75); + else + return QSize(d->rightB->sizeHint().width() * 2 + 75, sizeHint().height()); +} + +static QString computeElidedText(Qt::TextElideMode mode, const QString &text) +{ + if (text.length() <= 7) + return text; + + static const QLatin1String Ellipses("..."); + QString ret; + switch (mode) { + case Qt::ElideRight: + ret = text.left(4) + Ellipses; + break; + case Qt::ElideMiddle: + ret = text.left(2) + Ellipses + text.right(2); + break; + case Qt::ElideLeft: + ret = Ellipses + text.right(4); + break; + case Qt::ElideNone: + ret = text; + break; + } + return ret; +} + +QSize QTabBarPrivate::minimumTabSizeHint(int index) +{ + Q_Q(QTabBar); + // ### Qt 5: make this a protected virtual function in QTabBar + Tab &tab = tabList[index]; + QString oldText = tab.text; + tab.text = computeElidedText(elideMode, oldText); + QSize size = q->tabSizeHint(index); + tab.text = oldText; + return size; +} + +/*! + Returns the size hint for the tab at position \a index. +*/ +QSize QTabBar::tabSizeHint(int index) const +{ + Q_D(const QTabBar); + if (const QTabBarPrivate::Tab *tab = d->at(index)) { + QStyleOptionTabV3 opt; + initStyleOption(&opt, index); + opt.text = d->tabList.at(index).text; + QSize iconSize = tab->icon.isNull() ? QSize(0, 0) : opt.iconSize; + int hframe = style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &opt, this); + int vframe = style()->pixelMetric(QStyle::PM_TabBarTabVSpace, &opt, this); + const QFontMetrics fm = fontMetrics(); + + int maxWidgetHeight = qMax(opt.leftButtonSize.height(), opt.rightButtonSize.height()); + int maxWidgetWidth = qMax(opt.leftButtonSize.width(), opt.rightButtonSize.width()); + + int widgetWidth = 0; + int widgetHeight = 0; + int padding = 0; + if (opt.leftButtonSize.isValid()) { + padding += 6 + 2; + widgetWidth += opt.leftButtonSize.width(); + widgetHeight += opt.leftButtonSize.height(); + } + if (opt.rightButtonSize.isValid()) { + padding += 6 + 2; + widgetWidth += opt.rightButtonSize.width(); + widgetHeight += opt.rightButtonSize.height(); + } + if (opt.iconSize.isValid()) + padding += 2; + + QSize csz; + if (verticalTabs(d->shape)) { + csz = QSize( qMax(maxWidgetWidth, qMax(fm.height(), iconSize.height())) + vframe, + fm.size(Qt::TextShowMnemonic, tab->text).width() + iconSize.width() + hframe + widgetHeight + padding); + } else { + csz = QSize(fm.size(Qt::TextShowMnemonic, tab->text).width() + iconSize.width() + hframe + + widgetWidth + padding, + qMax(maxWidgetHeight, qMax(fm.height(), iconSize.height())) + vframe); + } + + QSize retSize = style()->sizeFromContents(QStyle::CT_TabBarTab, &opt, csz, this); + return retSize; + } + return QSize(); +} + +/*! + This virtual handler is called after a new tab was added or + inserted at position \a index. + + \sa tabRemoved() + */ +void QTabBar::tabInserted(int index) +{ + Q_UNUSED(index) +} + +/*! + This virtual handler is called after a tab was removed from + position \a index. + + \sa tabInserted() + */ +void QTabBar::tabRemoved(int index) +{ + Q_UNUSED(index) +} + +/*! + This virtual handler is called whenever the tab layout changes. + + \sa tabRect() + */ +void QTabBar::tabLayoutChange() +{ +} + + +/*!\reimp + */ +void QTabBar::showEvent(QShowEvent *) +{ + Q_D(QTabBar); + if (d->layoutDirty) + d->refresh(); + if (!d->validIndex(d->currentIndex)) + setCurrentIndex(0); + d->updateMacBorderMetrics(); +} + +/*!\reimp + */ +void QTabBar::hideEvent(QHideEvent *) +{ + Q_D(QTabBar); + d->updateMacBorderMetrics(); +} + +/*!\reimp + */ +bool QTabBar::event(QEvent *event) +{ + Q_D(QTabBar); + if (event->type() == QEvent::HoverMove + || event->type() == QEvent::HoverEnter) { + QHoverEvent *he = static_cast<QHoverEvent *>(event); + if (!d->hoverRect.contains(he->pos())) { + QRect oldHoverRect = d->hoverRect; + for (int i = 0; i < d->tabList.count(); ++i) { + QRect area = tabRect(i); + if (area.contains(he->pos())) { + d->hoverRect = area; + break; + } + } + if (he->oldPos() != QPoint(-1, -1)) + update(oldHoverRect); + update(d->hoverRect); + } + return true; + } else if (event->type() == QEvent::HoverLeave ) { + QRect oldHoverRect = d->hoverRect; + d->hoverRect = QRect(); + update(oldHoverRect); + return true; +#ifndef QT_NO_TOOLTIP + } else if (event->type() == QEvent::ToolTip) { + if (const QTabBarPrivate::Tab *tab = d->at(tabAt(static_cast<QHelpEvent*>(event)->pos()))) { + if (!tab->toolTip.isEmpty()) { + QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), tab->toolTip, this); + return true; + } + } +#endif // QT_NO_TOOLTIP +#ifndef QT_NO_WHATSTHIS + } else if (event->type() == QEvent::QueryWhatsThis) { + const QTabBarPrivate::Tab *tab = d->at(d->indexAtPos(static_cast<QHelpEvent*>(event)->pos())); + if (!tab || tab->whatsThis.isEmpty()) + event->ignore(); + return true; + } else if (event->type() == QEvent::WhatsThis) { + if (const QTabBarPrivate::Tab *tab = d->at(d->indexAtPos(static_cast<QHelpEvent*>(event)->pos()))) { + if (!tab->whatsThis.isEmpty()) { + QWhatsThis::showText(static_cast<QHelpEvent*>(event)->globalPos(), + tab->whatsThis, this); + return true; + } + } +#endif // QT_NO_WHATSTHIS +#ifndef QT_NO_SHORTCUT + } else if (event->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(event); + for (int i = 0; i < d->tabList.count(); ++i) { + const QTabBarPrivate::Tab *tab = &d->tabList.at(i); + if (tab->shortcutId == se->shortcutId()) { + setCurrentIndex(i); + return true; + } + } +#endif + } + return QWidget::event(event); +} + +/*!\reimp + */ +void QTabBar::resizeEvent(QResizeEvent *) +{ + Q_D(QTabBar); + if (d->layoutDirty) + updateGeometry(); + d->layoutTabs(); + + d->makeVisible(d->currentIndex); +} + +/*!\reimp + */ +void QTabBar::paintEvent(QPaintEvent *) +{ + Q_D(QTabBar); + + QStyleOptionTabBarBaseV2 optTabBase; + QTabBarPrivate::initStyleBaseOption(&optTabBase, this, size()); + + QStylePainter p(this); + int selected = -1; + int cut = -1; + bool rtl = optTabBase.direction == Qt::RightToLeft; + bool vertical = verticalTabs(d->shape); + QStyleOptionTab cutTab; + selected = d->currentIndex; + + for (int i = 0; i < d->tabList.count(); ++i) + optTabBase.tabBarRect |= tabRect(i); + + optTabBase.selectedTabRect = tabRect(selected); + + if (d->drawBase) + p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); + + for (int i = 0; i < d->tabList.count(); ++i) { + QStyleOptionTabV3 tab; + initStyleOption(&tab, i); + if (d->paintWithOffsets && d->tabList[i].dragOffset != 0) { + if (vertical) { + tab.rect.moveTop(tab.rect.y() + d->tabList[i].dragOffset); + } else { + tab.rect.moveLeft(tab.rect.x() + d->tabList[i].dragOffset); + } + } + if (!(tab.state & QStyle::State_Enabled)) { + tab.palette.setCurrentColorGroup(QPalette::Disabled); + } + // If this tab is partially obscured, make a note of it so that we can pass the information + // along when we draw the tear. + if (((!vertical && (!rtl && tab.rect.left() < 0)) || (rtl && tab.rect.right() > width())) + || (vertical && tab.rect.top() < 0)) { + cut = i; + cutTab = tab; + } + // Don't bother drawing a tab if the entire tab is outside of the visible tab bar. + if ((!vertical && (tab.rect.right() < 0 || tab.rect.left() > width())) + || (vertical && (tab.rect.bottom() < 0 || tab.rect.top() > height()))) + continue; + + optTabBase.tabBarRect |= tab.rect; + if (i == selected) + continue; + + if (!d->tabList[i].animatingCache.isNull() && d->paintWithOffsets) { + p.drawPixmap(tab.rect, d->tabList[i].animatingCache); + } else { + p.drawControl(QStyle::CE_TabBarTab, tab); + } + } + + // Draw the selected tab last to get it "on top" + if (selected >= 0) { + QStyleOptionTabV3 tab; + initStyleOption(&tab, selected); + if (d->paintWithOffsets && d->tabList[selected].dragOffset != 0) { + if (vertical) + tab.rect.moveTop(tab.rect.y() + d->tabList[selected].dragOffset); + else + tab.rect.moveLeft(tab.rect.x() + d->tabList[selected].dragOffset); + } + p.drawControl(QStyle::CE_TabBarTab, tab); + } + + // Only draw the tear indicator if necessary. Most of the time we don't need too. + if (d->leftB->isVisible() && cut >= 0) { + cutTab.rect = rect(); + cutTab.rect = style()->subElementRect(QStyle::SE_TabBarTearIndicator, &cutTab, this); + p.drawPrimitive(QStyle::PE_IndicatorTabTear, cutTab); + } +} + +/* + Given that index at position from moved to position to where return where index goes. + */ +int QTabBarPrivate::calculateNewPosition(int from, int to, int index) const +{ + if (index == from) + return to; + + int start = qMin(from, to); + int end = qMax(from, to); + if (index >= start && index <= end) + index += (from < to) ? -1 : 1; + return index; +} + +/*! + Moves the item at index position \a from to index position \a to. + \since 4.5 + + \sa tabMoved(), tabLayoutChange() + */ +void QTabBar::moveTab(int from, int to) +{ + Q_D(QTabBar); + if (from == to + || !d->validIndex(from) + || !d->validIndex(to)) + return; + + bool vertical = verticalTabs(d->shape); + int oldPressedPosition = 0; + if (d->pressedIndex != -1) { + // Record the position of the pressed tab before reordering the tabs. + oldPressedPosition = vertical ? d->tabList[d->pressedIndex].rect.y() + : d->tabList[d->pressedIndex].rect.x(); + } + + // Update the locations of the tabs first + int start = qMin(from, to); + int end = qMax(from, to); + int width = vertical ? d->tabList[from].rect.height() : d->tabList[from].rect.width(); + if (from < to) + width *= -1; + bool rtl = isRightToLeft(); + for (int i = start; i <= end; ++i) { + if (i == from) + continue; + if (vertical) + d->tabList[i].rect.moveTop(d->tabList[i].rect.y() + width); + else + d->tabList[i].rect.moveLeft(d->tabList[i].rect.x() + width); + int direction = -1; + if (rtl && !vertical) + direction *= -1; + if (d->tabList[i].dragOffset != 0) + d->tabList[i].dragOffset += (direction * width); + } + + if (vertical) { + if (from < to) + d->tabList[from].rect.moveTop(d->tabList[to].rect.bottom() + 1); + else + d->tabList[from].rect.moveTop(d->tabList[to].rect.top() - width); + } else { + if (from < to) + d->tabList[from].rect.moveLeft(d->tabList[to].rect.right() + 1); + else + d->tabList[from].rect.moveLeft(d->tabList[to].rect.left() - width); + } + + // Move the actual data structures + d->tabList.move(from, to); + + // update lastTab locations + for (int i = 0; i < d->tabList.count(); ++i) + d->tabList[i].lastTab = d->calculateNewPosition(from, to, d->tabList[i].lastTab); + + // update external variables + d->currentIndex = d->calculateNewPosition(from, to, d->currentIndex); + + // If we are in the middle of a drag update the dragStartPosition + if (d->pressedIndex != -1) { + d->pressedIndex = d->calculateNewPosition(from, to, d->pressedIndex); + int newPressedPosition = vertical ? d->tabList[d->pressedIndex].rect.top() : d->tabList[d->pressedIndex].rect.left(); + int diff = oldPressedPosition - newPressedPosition; + if (isRightToLeft() && !vertical) + diff *= -1; + if (vertical) + d->dragStartPosition.setY(d->dragStartPosition.y() - diff); + else + d->dragStartPosition.setX(d->dragStartPosition.x() - diff); + } + + d->layoutWidgets(start); + update(); + emit tabMoved(from, to); + emit tabLayoutChange(); +} + +void QTabBarPrivate::slide(int from, int to) +{ + Q_Q(QTabBar); + if (from == to + || !validIndex(from) + || !validIndex(to)) + return; + bool vertical = verticalTabs(shape); + int preLocation = vertical ? q->tabRect(from).y() : q->tabRect(from).x(); + q->setUpdatesEnabled(false); + q->moveTab(from, to); + q->setUpdatesEnabled(true); + int postLocation = vertical ? q->tabRect(to).y() : q->tabRect(to).x(); + int length = postLocation - preLocation; + tabList[to].makeTimeLine(q); + tabList[to].dragOffset += -1 * length; + tabList[to].timeLine->setFrameRange(tabList[to].dragOffset, 0); + animations[tabList[to].timeLine] = to; + tabList[to].timeLine->setDuration(ANIMATION_DURATION); + if (tabList[to].timeLine->state() != QTimeLine::Running) + tabList[to].timeLine->start(); +} + +void QTabBarPrivate::_q_moveTab(int offset) +{ + Q_Q(QTabBar); + if (QTimeLine *timeLine = qobject_cast<QTimeLine *>(q->sender())) { + int index = animations[timeLine]; + if (!validIndex(index)) + return; + tabList[index].dragOffset = offset; + q->update(); + } +} + +/*!\reimp +*/ +void QTabBar::mousePressEvent(QMouseEvent *event) +{ + Q_D(QTabBar); + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + // Be safe! + if (d->pressedIndex != -1 && d->movable) + d->_q_moveTabFinished(d->pressedIndex); + + d->pressedIndex = d->indexAtPos(event->pos()); + if (d->validIndex(d->pressedIndex)) { + QStyleOptionTabBarBaseV2 optTabBase; + optTabBase.init(this); + optTabBase.documentMode = d->documentMode; + if (event->type() == style()->styleHint(QStyle::SH_TabBar_SelectMouseType, &optTabBase, this)) + setCurrentIndex(d->pressedIndex); + else + repaint(tabRect(d->pressedIndex)); + if (d->movable) { + d->dragStartPosition = event->pos(); + } + } +} + +/*!\reimp + */ +void QTabBar::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QTabBar); + if (d->movable) { + // Be safe! + if (d->pressedIndex != -1 + && event->buttons() == Qt::NoButton) + d->_q_moveTabFinished(d->pressedIndex); + + // Start drag + if (!d->dragInProgress && d->pressedIndex != -1) { + if ((event->pos() - d->dragStartPosition).manhattanLength() > QApplication::startDragDistance()) { + d->dragInProgress = true; + if (d->animations.isEmpty()) + d->grabCache(0, d->tabList.count(), false); + } + } + + int offset = (event->pos() - d->dragStartPosition).manhattanLength(); + if (event->buttons() == Qt::LeftButton + && offset > QApplication::startDragDistance() + && d->validIndex(d->pressedIndex)) { + bool vertical = verticalTabs(d->shape); + int dragDistance; + if (vertical) { + dragDistance = (event->pos().y() - d->dragStartPosition.y()); + } else { + dragDistance = (event->pos().x() - d->dragStartPosition.x()); + } + d->tabList[d->pressedIndex].dragOffset = dragDistance; + + QRect startingRect = tabRect(d->pressedIndex); + if (vertical) + startingRect.moveTop(startingRect.y() + dragDistance); + else + startingRect.moveLeft(startingRect.x() + dragDistance); + + int overIndex; + if (dragDistance < 0) + overIndex = tabAt(startingRect.topLeft()); + else + overIndex = tabAt(startingRect.topRight()); + + if (overIndex != d->pressedIndex && overIndex != -1) { + int offset = 1; + if (isRightToLeft() && !vertical) + offset *= -1; + if (dragDistance < 0) { + dragDistance *= -1; + offset *= -1; + } + for (int i = d->pressedIndex; + offset > 0 ? i < overIndex : i > overIndex; + i += offset) { + QRect overIndexRect = tabRect(overIndex); + int needsToBeOver = (vertical ? overIndexRect.height() : overIndexRect.width()) / 2; + if (dragDistance > needsToBeOver) + d->slide(i + offset, d->pressedIndex); + } + + } + // Buttons needs to follow the dragged tab + d->layoutTab(d->pressedIndex); + + update(); + } + } + + if (event->buttons() != Qt::LeftButton) { + event->ignore(); + return; + } + QStyleOptionTabBarBaseV2 optTabBase; + optTabBase.init(this); + optTabBase.documentMode = d->documentMode; +} + +void QTabBarPrivate::_q_moveTabFinished() +{ + Q_Q(QTabBar); + if (QTimeLine *timeLine = qobject_cast<QTimeLine *>(q->sender())) { + int index = animations[timeLine]; + animations.remove(timeLine); + _q_moveTabFinished(index); + } +} + +void QTabBarPrivate::grabCache(int start, int end, bool unhide) +{ + Q_Q(QTabBar); + paintWithOffsets = false; + bool showButtonsAgain = rightB->isVisible(); + rightB->hide(); + leftB->hide(); + + QWidget *topLevel = q->window(); + QPoint topLevelOffset(q->mapTo(topLevel, QPoint())); + for (int i = start; i < end; ++i) { + QRect tabRect = q->tabRect(i); + tabRect.translate(topLevelOffset); + if (unhide) { + tabList[i].unHideWidgets(); + layoutWidgets(i); + } + tabList[i].animatingCache = QPixmap::grabWidget(topLevel, tabRect); + if (i != pressedIndex) + tabList[i].hideWidgets(); + } + if (showButtonsAgain) { + rightB->show(); + leftB->show(); + } + paintWithOffsets = true; +} + +void QTabBarPrivate::_q_moveTabFinished(int index) +{ + Q_Q(QTabBar); + bool cleanup = (pressedIndex == index) || (pressedIndex == -1) || !validIndex(index); + if (animations.isEmpty() && cleanup) { + for (int i = 0; i < tabList.count(); ++i) { + tabList[i].dragOffset = 0; + tabList[i].unHideWidgets(); + tabList[i].animatingCache = QPixmap(); + } + if (pressedIndex != -1 && movable) { + pressedIndex = -1; + dragInProgress = false; + dragStartPosition = QPoint(); + } + layoutWidgets(); + } else { + if (!validIndex(index)) + return; + tabList[index].dragOffset = 0; + } + q->update(); +} + +/*!\reimp +*/ +void QTabBar::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QTabBar); + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + + if (d->movable && d->dragInProgress && d->validIndex(d->pressedIndex)) { + int length = d->tabList[d->pressedIndex].dragOffset; + int width = verticalTabs(d->shape) + ? tabRect(d->pressedIndex).height() + : tabRect(d->pressedIndex).width(); + int duration = qMin(ANIMATION_DURATION, + ((length < 0 ? (-1 * length) : length) * ANIMATION_DURATION) / width); + if (duration > 0) { + d->tabList[d->pressedIndex].makeTimeLine(this); + d->tabList[d->pressedIndex].timeLine->setFrameRange(length, 0); + d->animations[d->tabList[d->pressedIndex].timeLine] = d->pressedIndex; + d->tabList[d->pressedIndex].timeLine->setDuration(duration); + if (d->tabList[d->pressedIndex].timeLine->state() != QTimeLine::Running) + d->tabList[d->pressedIndex].timeLine->start(); + } else { + d->_q_moveTabFinished(d->pressedIndex); + } + d->dragInProgress = false; + d->dragStartPosition = QPoint(); + } + + int i = d->indexAtPos(event->pos()) == d->pressedIndex ? d->pressedIndex : -1; + d->pressedIndex = -1; + QStyleOptionTabBarBaseV2 optTabBase; + optTabBase.initFrom(this); + optTabBase.documentMode = d->documentMode; + if (style()->styleHint(QStyle::SH_TabBar_SelectMouseType, &optTabBase, this) == QEvent::MouseButtonRelease) + setCurrentIndex(i); +} + +/*!\reimp + */ +void QTabBar::keyPressEvent(QKeyEvent *event) +{ + Q_D(QTabBar); + if (event->key() != Qt::Key_Left && event->key() != Qt::Key_Right) { + event->ignore(); + return; + } + int dx = event->key() == (isRightToLeft() ? Qt::Key_Right : Qt::Key_Left) ? -1 : 1; + for (int index = d->currentIndex + dx; d->validIndex(index); index += dx) { + if (d->tabList.at(index).enabled) { + setCurrentIndex(index); + break; + } + } +} + +/*!\reimp + */ +#ifndef QT_NO_WHEELEVENT +void QTabBar::wheelEvent(QWheelEvent *event) +{ + Q_D(QTabBar); + int overIndex = d->indexAtPos(event->pos()); + if (overIndex != -1) { + int offset = event->delta() > 0 ? -1 : 1; + setCurrentIndex(currentIndex() + offset); + } + QWidget::wheelEvent(event); +} +#endif //QT_NO_WHEELEVENT + +/*!\reimp + */ +void QTabBar::changeEvent(QEvent *event) +{ + Q_D(QTabBar); + if (event->type() == QEvent::StyleChange) { + d->elideMode = Qt::TextElideMode(style()->styleHint(QStyle::SH_TabBar_ElideMode, 0, this)); + d->useScrollButtons = !style()->styleHint(QStyle::SH_TabBar_PreferNoArrows, 0, this); + } + d->refresh(); + QWidget::changeEvent(event); +} + +/*! + \property QTabBar::elideMode + \brief how to elide text in the tab bar + \since 4.2 + + This property controls how items are elided when there is not + enough space to show them for a given tab bar size. + + By default the value is style dependent. + + \sa QTabWidget::elideMode usesScrollButtons QStyle::SH_TabBar_ElideMode +*/ + +Qt::TextElideMode QTabBar::elideMode() const +{ + Q_D(const QTabBar); + return d->elideMode; +} + +void QTabBar::setElideMode(Qt::TextElideMode mode) +{ + Q_D(QTabBar); + d->elideMode = mode; + d->refresh(); +} + +/*! + \property QTabBar::usesScrollButtons + \brief Whether or not a tab bar should use buttons to scroll tabs when it + has many tabs. + \since 4.2 + + When there are too many tabs in a tab bar for its size, the tab bar can either choose + to expand its size or to add buttons that allow you to scroll through the tabs. + + By default the value is style dependant. + + \sa elideMode QTabWidget::usesScrollButtons QStyle::SH_TabBar_PreferNoArrows +*/ +bool QTabBar::usesScrollButtons() const +{ + return d_func()->useScrollButtons; +} + +void QTabBar::setUsesScrollButtons(bool useButtons) +{ + Q_D(QTabBar); + if (d->useScrollButtons == useButtons) + return; + d->useScrollButtons = useButtons; + d->refresh(); +} + +/*! + \fn void QTabBar::setCurrentTab(int index) + + Use setCurrentIndex() instead. +*/ + +/*! + \fn void QTabBar::selected(int index); + + Use currentChanged() instead. +*/ + + +/*! + \property QTabBar::tabsClosable + \brief Whether or not a tab bar should place close buttons on each tab + \since 4.5 + + When tabsClosable is set to true a close button will appear on the tab on + either the left or right hand side depending upon the style. When the button + is clicked the tab the signal tabCloseRequested will be emitted. + + By default the value is false. + + \sa setTabButton(), tabRemoved() +*/ + +bool QTabBar::tabsClosable() const +{ + Q_D(const QTabBar); + return d->closeButtonOnTabs; +} + +void QTabBar::setTabsClosable(bool closable) +{ + Q_D(QTabBar); + if (d->closeButtonOnTabs == closable) + return; + d->closeButtonOnTabs = closable; + ButtonPosition closeSide = (ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this); + if (!closable) { + for (int i = 0; i < d->tabList.count(); ++i) { + if (closeSide == LeftSide && d->tabList[i].leftWidget) { + d->tabList[i].leftWidget->deleteLater(); + d->tabList[i].leftWidget = 0; + } + if (closeSide == RightSide && d->tabList[i].rightWidget) { + d->tabList[i].rightWidget->deleteLater(); + d->tabList[i].rightWidget = 0; + } + } + } else { + bool newButtons = false; + for (int i = 0; i < d->tabList.count(); ++i) { + if (tabButton(i, closeSide)) + continue; + newButtons = true; + QAbstractButton *closeButton = new CloseButton(this); + connect(closeButton, SIGNAL(clicked()), this, SLOT(_q_closeTab())); + setTabButton(i, closeSide, closeButton); + } + if (newButtons) + d->layoutTabs(); + } + update(); +} + +/*! + \enum QTabBar::ButtonPosition + \since 4.5 + + This enum type lists the location of the widget on a tab. + + \value LeftSide Left side of the tab. + + \value RightSide Right side of the tab. + +*/ + +/*! + \enum QTabBar::SelectionBehavior + \since 4.5 + + This enum type lists the behavior of QTabBar when a tab is removed + and the tab being removed is also the current tab. + + \value SelectLeftTab Select the tab to the left of the one being removed. + + \value SelectRightTab Select the tab to the right of the one being removed. + + \value SelectPreviousTab Select the previously selected tab. + +*/ + +/*! + \property QTabBar::selectionBehaviorOnRemove + \brief What tab should be set as current when removeTab is called if + the removed tab is also the current tab. + \since 4.5 + + By default the value is SelectRightTab. + + \sa removeTab() +*/ + + +QTabBar::SelectionBehavior QTabBar::selectionBehaviorOnRemove() const +{ + Q_D(const QTabBar); + return d->selectionBehaviorOnRemove; +} + +void QTabBar::setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior) +{ + Q_D(QTabBar); + d->selectionBehaviorOnRemove = behavior; +} + +/*! + \property QTabBar::expanding + \brief When expanding is true QTabBar will expand the tabs to use the empty space. + \since 4.5 + + By default the value is true. + + \sa QTabWidget::documentMode +*/ + +bool QTabBar::expanding() const +{ + Q_D(const QTabBar); + return d->expanding; +} + +void QTabBar::setExpanding(bool enabled) +{ + Q_D(QTabBar); + if (d->expanding == enabled) + return; + d->expanding = enabled; + d->layoutTabs(); +} + +/*! + \property QTabBar::movable + \brief This property holds whether the user can move the tabs + within the tabbar area. + + \since 4.5 + + By default, this property is false; +*/ + +bool QTabBar::isMovable() const +{ + Q_D(const QTabBar); + return d->movable; +} + +void QTabBar::setMovable(bool movable) +{ + Q_D(QTabBar); + d->movable = movable; +} + + +/*! + \property QTabBar::documentMode + \brief Whether or not the tab bar is rendered in a mode suitable for the main window. + \since 4.5 + + This property is used as a hint for styles to draw the tabs in a different + way then they would normally look in a tab widget. On Mac OS X this will + look similar to the tabs in Safari or Leopard's Terminal.app. + + \sa QTabWidget::documentMode +*/ +bool QTabBar::documentMode() const +{ + return d_func()->documentMode; +} + +void QTabBar::setDocumentMode(bool enabled) +{ + Q_D(QTabBar); + d->documentMode = enabled; + d->updateMacBorderMetrics(); +} + +/*! + Sets \a widget on the tab \a index. The widget is placed + on the left or right hand side depending upon the \a position. + \since 4.5 + + Any previously set widget in \a position is hidden. + + The tab bar will take ownership of the widget and so all widgets set here + will be deleted by the tab bar when it is destroyed unless you separately + reparent the widget after setting some other widget (or 0). + + \sa tabsClosable() + */ +void QTabBar::setTabButton(int index, ButtonPosition position, QWidget *widget) +{ + Q_D(QTabBar); + if (index < 0 || index >= d->tabList.count()) + return; + if (widget) { + widget->setParent(this); + // make sure our left and right widgets stay on top + widget->lower(); + } + if (position == LeftSide) { + if (d->tabList[index].leftWidget) + d->tabList[index].leftWidget->hide(); + d->tabList[index].leftWidget = widget; + if(!d->tabList[index].hidLeft && widget) + widget->show(); + } else { + if (d->tabList[index].rightWidget) + d->tabList[index].rightWidget->hide(); + d->tabList[index].rightWidget = widget; + if(!d->tabList[index].hidRight && widget) + widget->show(); + } + d->layoutTabs(); + update(); +} + +/*! + Returns the widget set a tab \a index and \a position or 0 if + one is not set. + */ +QWidget *QTabBar::tabButton(int index, ButtonPosition position) const +{ + Q_D(const QTabBar); + if (index < 0 || index >= d->tabList.count()) + return 0; + if (position == LeftSide) + return d->tabList.at(index).leftWidget; + else + return d->tabList.at(index).rightWidget; +} + +CloseButton::CloseButton(QWidget *parent) + : QAbstractButton(parent) +{ + setFocusPolicy(Qt::NoFocus); +#ifndef QT_NO_CURSOR + setCursor(Qt::ArrowCursor); +#endif +#ifndef QT_NO_TOOLTIP + setToolTip(tr("Close Tab")); +#endif + resize(sizeHint()); +} + +QSize CloseButton::sizeHint() const +{ + ensurePolished(); + int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this); + int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this); + return QSize(width, height); +} + +void CloseButton::enterEvent(QEvent *event) +{ + if (isEnabled()) + update(); + QAbstractButton::enterEvent(event); +} + +void CloseButton::leaveEvent(QEvent *event) +{ + if (isEnabled()) + update(); + QAbstractButton::leaveEvent(event); +} + +void CloseButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QStyleOption opt; + opt.init(this); + opt.state |= QStyle::State_AutoRaise; + if (isEnabled() && underMouse() && !isChecked() && !isDown()) + opt.state |= QStyle::State_Raised; + if (isChecked()) + opt.state |= QStyle::State_On; + if (isDown()) + opt.state |= QStyle::State_Sunken; + + if (const QTabBar *tb = qobject_cast<const QTabBar *>(parent())) { + int index = tb->currentIndex(); + QTabBar::ButtonPosition position = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb); + if (tb->tabButton(index, position) == this) + opt.state |= QStyle::State_Selected; + } + + style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this); +} + +QT_END_NAMESPACE + +#include "moc_qtabbar.cpp" + +#endif // QT_NO_TABBAR diff --git a/src/gui/widgets/qtabbar.h b/src/gui/widgets/qtabbar.h new file mode 100644 index 0000000..49931ad --- /dev/null +++ b/src/gui/widgets/qtabbar.h @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABBAR_H +#define QTABBAR_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TABBAR + +class QIcon; +class QTabBarPrivate; +class QStyleOptionTab; + +class Q_GUI_EXPORT QTabBar: public QWidget +{ + Q_OBJECT + + Q_ENUMS(Shape) + Q_PROPERTY(Shape shape READ shape WRITE setShape) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentChanged) + Q_PROPERTY(int count READ count) + Q_PROPERTY(bool drawBase READ drawBase WRITE setDrawBase) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + Q_PROPERTY(Qt::TextElideMode elideMode READ elideMode WRITE setElideMode) + Q_PROPERTY(bool usesScrollButtons READ usesScrollButtons WRITE setUsesScrollButtons) + Q_PROPERTY(bool tabsClosable READ tabsClosable WRITE setTabsClosable) + Q_PROPERTY(SelectionBehavior selectionBehaviorOnRemove READ selectionBehaviorOnRemove WRITE setSelectionBehaviorOnRemove) + Q_PROPERTY(bool expanding READ expanding WRITE setExpanding) + Q_PROPERTY(bool movable READ isMovable WRITE setMovable) + Q_PROPERTY(bool documentMode READ documentMode WRITE setDocumentMode) + +public: + explicit QTabBar(QWidget* parent=0); + ~QTabBar(); + + enum Shape { RoundedNorth, RoundedSouth, RoundedWest, RoundedEast, + TriangularNorth, TriangularSouth, TriangularWest, TriangularEast +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + , RoundedAbove = RoundedNorth, RoundedBelow = RoundedSouth, + TriangularAbove = TriangularNorth, TriangularBelow = TriangularSouth +#endif + }; + + enum ButtonPosition { + LeftSide, + RightSide + }; + + enum SelectionBehavior { + SelectLeftTab, + SelectRightTab, + SelectPreviousTab + }; + + Shape shape() const; + void setShape(Shape shape); + + int addTab(const QString &text); + int addTab(const QIcon &icon, const QString &text); + + int insertTab(int index, const QString &text); + int insertTab(int index, const QIcon&icon, const QString &text); + + void removeTab(int index); + void moveTab(int from, int to); + + bool isTabEnabled(int index) const; + void setTabEnabled(int index, bool); + + QString tabText(int index) const; + void setTabText(int index, const QString &text); + + QColor tabTextColor(int index) const; + void setTabTextColor(int index, const QColor &color); + + QIcon tabIcon(int index) const; + void setTabIcon(int index, const QIcon &icon); + + Qt::TextElideMode elideMode() const; + void setElideMode(Qt::TextElideMode); + +#ifndef QT_NO_TOOLTIP + void setTabToolTip(int index, const QString &tip); + QString tabToolTip(int index) const; +#endif + +#ifndef QT_NO_WHATSTHIS + void setTabWhatsThis(int index, const QString &text); + QString tabWhatsThis(int index) const; +#endif + + void setTabData(int index, const QVariant &data); + QVariant tabData(int index) const; + + QRect tabRect(int index) const; + int tabAt(const QPoint &pos) const; + + int currentIndex() const; + int count() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setDrawBase(bool drawTheBase); + bool drawBase() const; + + QSize iconSize() const; + void setIconSize(const QSize &size); + + bool usesScrollButtons() const; + void setUsesScrollButtons(bool useButtons); + + bool tabsClosable() const; + void setTabsClosable(bool closable); + + void setTabButton(int index, ButtonPosition position, QWidget *widget); + QWidget *tabButton(int index, ButtonPosition position) const; + + SelectionBehavior selectionBehaviorOnRemove() const; + void setSelectionBehaviorOnRemove(SelectionBehavior behavior); + + bool expanding() const; + void setExpanding(bool enabled); + + bool isMovable() const; + void setMovable(bool movable); + + bool documentMode() const; + void setDocumentMode(bool set); + +public Q_SLOTS: + void setCurrentIndex(int index); + +Q_SIGNALS: + void currentChanged(int index); + void tabCloseRequested(int index); + void tabMoved(int from, int to); + +protected: + virtual QSize tabSizeHint(int index) const; + virtual void tabInserted(int index); + virtual void tabRemoved(int index); + virtual void tabLayoutChange(); + + bool event(QEvent *); + void resizeEvent(QResizeEvent *); + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + void paintEvent(QPaintEvent *); + void mousePressEvent (QMouseEvent *); + void mouseMoveEvent (QMouseEvent *); + void mouseReleaseEvent (QMouseEvent *); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *event); +#endif + void keyPressEvent(QKeyEvent *); + void changeEvent(QEvent *); + void initStyleOption(QStyleOptionTab *option, int tabIndex) const; + +#ifdef QT3_SUPPORT +public Q_SLOTS: + QT_MOC_COMPAT void setCurrentTab(int index) { setCurrentIndex(index); } +Q_SIGNALS: + QT_MOC_COMPAT void selected(int); +#endif + + friend class QAccessibleTabBar; +private: + Q_DISABLE_COPY(QTabBar) + Q_DECLARE_PRIVATE(QTabBar) + Q_PRIVATE_SLOT(d_func(), void _q_scrollTabs()) + Q_PRIVATE_SLOT(d_func(), void _q_closeTab()) + Q_PRIVATE_SLOT(d_func(), void _q_moveTab(int)) + Q_PRIVATE_SLOT(d_func(), void _q_moveTabFinished()) +}; + +#endif // QT_NO_TABBAR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTABBAR_H diff --git a/src/gui/widgets/qtabbar_p.h b/src/gui/widgets/qtabbar_p.h new file mode 100644 index 0000000..a117aa3 --- /dev/null +++ b/src/gui/widgets/qtabbar_p.h @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABBAR_P_H +#define QTABBAR_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 "qtabbar.h" +#include "private/qwidget_p.h" + +#include <qicon.h> +#include <qtoolbutton.h> +#include <qtimeline.h> +#include <qhash.h> +#include <qdebug.h> + +#ifndef QT_NO_TABBAR + +#define ANIMATION_DURATION 250 + +#include <qstyleoption.h> + +QT_BEGIN_NAMESPACE + + +class QTabBarPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QTabBar) +public: + QTabBarPrivate() + :currentIndex(-1), pressedIndex(-1), + shape(QTabBar::RoundedNorth), + layoutDirty(false), drawBase(true), scrollOffset(0), expanding(true), closeButtonOnTabs(false), selectionBehaviorOnRemove(QTabBar::SelectRightTab), paintWithOffsets(true), movable(false), dragInProgress(false), documentMode(false) {} + + int currentIndex; + int pressedIndex; + QTabBar::Shape shape; + bool layoutDirty; + bool drawBase; + int scrollOffset; + + struct Tab { + inline Tab(const QIcon &ico, const QString &txt) + : enabled(true) + , shortcutId(0) + , text(txt) + , icon(ico) + , leftWidget(0) + , rightWidget(0) + , lastTab(-1) + , timeLine(0) + , dragOffset(0) + , hidLeft(false) + , hidRight(false) + {} + bool enabled; + int shortcutId; + QString text; +#ifndef QT_NO_TOOLTIP + QString toolTip; +#endif +#ifndef QT_NO_WHATSTHIS + QString whatsThis; +#endif + QIcon icon; + QRect rect; + QRect minRect; + QRect maxRect; + + QColor textColor; + QVariant data; + QWidget *leftWidget; + QWidget *rightWidget; + int lastTab; + + QTimeLine *timeLine; + int dragOffset; + QPixmap animatingCache; + bool hidLeft; + bool hidRight; + + void makeTimeLine(QWidget *q) { + if (timeLine) + return; + timeLine = new QTimeLine(ANIMATION_DURATION, q); + q->connect(timeLine, SIGNAL(frameChanged(int)), q, SLOT(_q_moveTab(int))); + q->connect(timeLine, SIGNAL(finished()), q, SLOT(_q_moveTabFinished())); + } + + void hideWidgets() { + if (!hidRight && rightWidget) { + hidRight = rightWidget->isVisible(); + rightWidget->hide(); + } + + if (!hidLeft && leftWidget) { + hidLeft = leftWidget->isVisible(); + leftWidget->hide(); + } + } + + void unHideWidgets() { + if (leftWidget && hidLeft) + leftWidget->show(); + hidLeft = false; + if (rightWidget && hidRight) + rightWidget->show(); + hidRight = false; + } + + }; + QList<Tab> tabList; + QHash<QTimeLine*, int> animations; + + int calculateNewPosition(int from, int to, int index) const; + void slide(int from, int to); + void init(); + int extraWidth() const; + + Tab *at(int index); + const Tab *at(int index) const; + + int indexAtPos(const QPoint &p) const; + + inline bool validIndex(int index) const { return index >= 0 && index < tabList.count(); } + + QSize minimumTabSizeHint(int index); + + QToolButton* rightB; // right or bottom + QToolButton* leftB; // left or top + + void _q_scrollTabs(); + void _q_closeTab(); + void _q_moveTab(int); + void _q_moveTabFinished(); + void _q_moveTabFinished(int offset); + QRect hoverRect; + + void grabCache(int start, int end, bool unhide); + void refresh(); + void layoutTabs(); + void layoutWidgets(int index = -1); + void layoutTab(int index); + void updateMacBorderMetrics(); + + void makeVisible(int index); + QSize iconSize; + Qt::TextElideMode elideMode; + bool useScrollButtons; + + bool expanding; + bool closeButtonOnTabs; + QTabBar::SelectionBehavior selectionBehaviorOnRemove; + + QPoint dragStartPosition; + bool paintWithOffsets; + bool movable; + bool dragInProgress; + bool documentMode; + + // shared by tabwidget and qtabbar + static void initStyleBaseOption(QStyleOptionTabBarBaseV2 *optTabBase, QTabBar *tabbar, QSize size) + { + QStyleOptionTab tabOverlap; + tabOverlap.shape = tabbar->shape(); + int overlap = tabbar->style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, tabbar); + QWidget *theParent = tabbar->parentWidget(); + optTabBase->init(tabbar); + optTabBase->shape = tabbar->shape(); + optTabBase->documentMode = tabbar->documentMode(); + if (theParent && overlap > 0) { + QRect rect; + switch (tabOverlap.shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect.setRect(0, size.height()-overlap, size.width(), overlap); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect.setRect(0, 0, size.width(), overlap); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + rect.setRect(0, 0, overlap, size.height()); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + rect.setRect(size.width() - overlap, 0, overlap, size.height()); + break; + } + optTabBase->rect = rect; + } + } + +}; + +class CloseButton : public QAbstractButton +{ + Q_OBJECT + +public: + CloseButton(QWidget *parent = 0); + + QSize sizeHint() const; + inline QSize minimumSizeHint() const + { return sizeHint(); } + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); +}; + + +QT_END_NAMESPACE + +#endif + +#endif diff --git a/src/gui/widgets/qtabwidget.cpp b/src/gui/widgets/qtabwidget.cpp new file mode 100644 index 0000000..c16e000 --- /dev/null +++ b/src/gui/widgets/qtabwidget.cpp @@ -0,0 +1,1450 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtabwidget.h" + +#ifndef QT_NO_TABWIDGET +#include "private/qwidget_p.h" +#include "private/qtabbar_p.h" +#include "qapplication.h" +#include "qbitmap.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qlayout.h" +#include "qstackedwidget.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qstylepainter.h" +#include "qtabbar.h" +#include "qtoolbutton.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QTabWidget + \brief The QTabWidget class provides a stack of tabbed widgets. + + \ingroup organizers + \ingroup basicwidgets + \mainclass + + A tab widget provides a tab bar (see QTabBar) and a "page area" + that is used to display pages related to each tab. By default, the + tab bar is shown above the page area, but different configurations + are available (see \l{TabPosition}). Each tab is associated with a + different widget (called a page). Only the current page is shown in + the page area; all the other pages are hidden. The user can show a + different page by clicking on its tab or by pressing its + Alt+\e{letter} shortcut if it has one. + + The normal way to use QTabWidget is to do the following: + \list 1 + \i Create a QTabWidget. + \i Create a QWidget for each of the pages in the tab dialog, but + do not specify parent widgets for them. + \i Insert child widgets into the page widget, using layouts to + position them as normal. + \i Call addTab() or insertTab() to put the page widgets into the + tab widget, giving each tab a suitable label with an optional + keyboard shortcut. + \endlist + + The position of the tabs is defined by \l tabPosition, their shape + by \l tabShape. + + The signal currentChanged() is emitted when the user selects a + page. + + The current page index is available as currentIndex(), the current + page widget with currentWidget(). You can retrieve a pointer to a + page widget with a given index using widget(), and can find the + index position of a widget with indexOf(). Use setCurrentWidget() + or setCurrentIndex() to show a particular page. + + You can change a tab's text and icon using setTabText() or + setTabIcon(). A tab and its associated page can be removed with + removeTab(). + + Each tab is either enabled or disabled at any given time (see + setTabEnabled()). If a tab is enabled, the tab text is drawn + normally and the user can select that tab. If it is disabled, the + tab is drawn in a different way and the user cannot select that + tab. Note that even if a tab is disabled, the page can still be + visible, for example if all of the tabs happen to be disabled. + + Tab widgets can be a very good way to split up a complex dialog. + An alternative is to use a QStackedWidget for which you provide some + means of navigating between pages, for example, a QToolBar or a + QListWidget. + + Most of the functionality in QTabWidget is provided by a QTabBar + (at the top, providing the tabs) and a QStackedWidget (most of the + area, organizing the individual pages). + + \table 100% + \row \o \inlineimage windowsxp-tabwidget.png Screenshot of a Windows XP style tab widget + \o \inlineimage macintosh-tabwidget.png Screenshot of a Macintosh style tab widget + \o \inlineimage plastique-tabwidget.png Screenshot of a Plastique style tab widget + \row \o A Windows XP style tab widget. + \o A Macintosh style tab widget. + \o A Plastique style tab widget. + \endtable + + \sa QTabBar, QStackedWidget, QToolBox, {Tab Dialog Example} +*/ + +/*! + \enum QTabWidget::TabPosition + + This enum type defines where QTabWidget draws the tab row: + + \value North The tabs are drawn above the pages. + \value South The tabs are drawn below the pages. + \value West The tabs are drawn to the left of the pages. + \value East The tabs are drawn to the right of the pages. + \omitvalue Bottom + \omitvalue Top +*/ + +/*! + \enum QTabWidget::TabShape + + This enum type defines the shape of the tabs: + \value Rounded The tabs are drawn with a rounded look. This is the default + shape. + \value Triangular The tabs are drawn with a triangular look. +*/ + +/*! + \fn void QTabWidget::selected(const QString &tabLabel) + + This signal is emitted whenever a tab is selected (raised), + including during the first show(). + + You can normally use currentChanged() instead. +*/ + +/*! + \fn void QTabWidget::currentChanged(int index) + + This signal is emitted whenever the current page index changes. + The parameter is the new current page \a index position, or -1 + if there isn't a new one (for example, if there are no widgets + in the QTabWidget) + + \sa currentWidget() currentIndex +*/ + +/*! + \fn void QTabWidget::tabCloseRequested(int index) + \since 4.5 + + This signal is emitted when the close button on a tab is clicked. + The \a index is the index that should be removed. + + \sa setTabsClosable() +*/ + +class QTabWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QTabWidget) + +public: + QTabWidgetPrivate(); + ~QTabWidgetPrivate(); + void updateTabBarPosition(); + void _q_showTab(int); + void _q_removeTab(int); + void _q_tabMoved(int from, int to); + void init(); + + QTabBar *tabs; + QStackedWidget *stack; + QRect panelRect; + bool dirty; + QTabWidget::TabPosition pos; + QTabWidget::TabShape shape; + int alignment; + QWidget *leftCornerWidget; + QWidget *rightCornerWidget; +}; + +QTabWidgetPrivate::QTabWidgetPrivate() + : tabs(0), stack(0), dirty(true), + pos(QTabWidget::North), shape(QTabWidget::Rounded), + leftCornerWidget(0), rightCornerWidget(0) +{} + +QTabWidgetPrivate::~QTabWidgetPrivate() +{} + +void QTabWidgetPrivate::init() +{ + Q_Q(QTabWidget); + + stack = new QStackedWidget(q); + stack->setObjectName(QLatin1String("qt_tabwidget_stackedwidget")); + stack->setLineWidth(0); + // hack so that QMacStyle::layoutSpacing() can detect tab widget pages + stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::TabWidget)); + + QObject::connect(stack, SIGNAL(widgetRemoved(int)), q, SLOT(_q_removeTab(int))); + QTabBar *tabBar = new QTabBar(q); + tabBar->setObjectName(QLatin1String("qt_tabwidget_tabbar")); + tabBar->setDrawBase(false); + q->setTabBar(tabBar); + + q->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, + QSizePolicy::TabWidget)); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + q->setFocusPolicy(Qt::NoFocus); + else +#endif + q->setFocusPolicy(Qt::TabFocus); + q->setFocusProxy(tabs); + q->setTabPosition(static_cast<QTabWidget::TabPosition> (q->style()->styleHint( + QStyle::SH_TabWidget_DefaultTabPosition, 0, q ))); + +} + +/*! + Initialize \a option with the values from this QTabWidget. This method is useful + for subclasses when they need a QStyleOptionTabWidgetFrame, but don't want to fill + in all the information themselves. + + \sa QStyleOption::initFrom() QTabBar::initStyleOption() +*/ +void QTabWidget::initStyleOption(QStyleOptionTabWidgetFrame *option) const +{ + if (!option) + return; + + Q_D(const QTabWidget); + option->initFrom(this); + + if (documentMode()) + option->lineWidth = 0; + else + option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this); + + int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, 0, this); + QSize t(0, d->stack->frameWidth()); + if (d->tabs->isVisibleTo(const_cast<QTabWidget *>(this))) { + t = d->tabs->sizeHint(); + if (documentMode()) { + if (tabPosition() == East || tabPosition() == West) { + t.setHeight(height()); + } else { + t.setWidth(width()); + } + } + } + + if (d->rightCornerWidget) { + const QSize rightCornerSizeHint = d->rightCornerWidget->sizeHint(); + const QSize bounds(rightCornerSizeHint.width(), t.height() - exth); + option->rightCornerWidgetSize = rightCornerSizeHint.boundedTo(bounds); + } else { + option->rightCornerWidgetSize = QSize(0, 0); + } + + if (d->leftCornerWidget) { + const QSize leftCornerSizeHint = d->leftCornerWidget->sizeHint(); + const QSize bounds(leftCornerSizeHint.width(), t.height() - exth); + option->leftCornerWidgetSize = leftCornerSizeHint.boundedTo(bounds); + } else { + option->leftCornerWidgetSize = QSize(0, 0); + } + + switch (d->pos) { + case QTabWidget::North: + option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedNorth + : QTabBar::TriangularNorth; + break; + case QTabWidget::South: + option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedSouth + : QTabBar::TriangularSouth; + break; + case QTabWidget::West: + option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedWest + : QTabBar::TriangularWest; + break; + case QTabWidget::East: + option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedEast + : QTabBar::TriangularEast; + break; + } + option->tabBarSize = t; +} + +/*! + Constructs a tabbed widget with parent \a parent. +*/ +QTabWidget::QTabWidget(QWidget *parent) + : QWidget(*new QTabWidgetPrivate, parent, 0) +{ + Q_D(QTabWidget); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QTabWidget::QTabWidget(QWidget *parent, const char *name, Qt::WindowFlags f) + : QWidget(*new QTabWidgetPrivate, parent, f) +{ + Q_D(QTabWidget); + setObjectName(QString::fromAscii(name)); + d->init(); +} +#endif + +/*! + Destroys the tabbed widget. +*/ +QTabWidget::~QTabWidget() +{ +} + +/*! + \fn int QTabWidget::addTab(QWidget *page, const QString &label) + + Adds a tab with the given \a page and \a label to the tab widget, + and returns the index of the tab in the tab bar. + + If the tab's \a label contains an ampersand, the letter following + the ampersand is used as a shortcut for the tab, e.g. if the + label is "Bro\&wse" then Alt+W becomes a shortcut which will + move the focus to this tab. + + \note If you call addTab() after show(), the layout system will try + to adjust to the changes in its widgets hierarchy and may cause + flicker. To prevent this, you can set the QWidget::updatesEnabled + property to false prior to changes; remember to set the property + to true when the changes are done, making the widget receive paint + events again. + + \sa insertTab() +*/ +int QTabWidget::addTab(QWidget *child, const QString &label) +{ + return insertTab(-1, child, label); +} + + +/*! + \fn int QTabWidget::addTab(QWidget *page, const QIcon &icon, const QString &label) + \overload + + Adds a tab with the given \a page, \a icon, and \a label to the tab + widget, and returns the index of the tab in the tab bar. + + This function is the same as addTab(), but with an additional \a + icon. +*/ +int QTabWidget::addTab(QWidget *child, const QIcon& icon, const QString &label) +{ + return insertTab(-1, child, icon, label); +} + + +/*! + \fn int QTabWidget::insertTab(int index, QWidget *page, const QString &label) + + Inserts a tab with the given \a label and \a page into the tab + widget at the specified \a index, and returns the index of the + inserted tab in the tab bar. + + The label is displayed in the tab and may vary in appearance depending + on the configuration of the tab widget. + + If the tab's \a label contains an ampersand, the letter following + the ampersand is used as a shortcut for the tab, e.g. if the + label is "Bro\&wse" then Alt+W becomes a shortcut which will + move the focus to this tab. + + If \a index is out of range, the tab is simply appended. + Otherwise it is inserted at the specified position. + + If the QTabWidget was empty before this function is called, the + new page becomes the current page. Inserting a new tab at an index + less than or equal to the current index will increment the current + index, but keep the current page. + + \note If you call insertTab() after show(), the layout system will try + to adjust to the changes in its widgets hierarchy and may cause + flicker. To prevent this, you can set the QWidget::updatesEnabled + property to false prior to changes; remember to set the property + to true when the changes are done, making the widget receive paint + events again. + + \sa addTab() +*/ +int QTabWidget::insertTab(int index, QWidget *w, const QString &label) +{ + return insertTab(index, w, QIcon(), label); +} + + +/*! + \fn int QTabWidget::insertTab(int index, QWidget *page, const QIcon& icon, const QString &label) + \overload + + Inserts a tab with the given \a label, \a page, and \a icon into + the tab widget at the specified \a index, and returns the index of the + inserted tab in the tab bar. + + This function is the same as insertTab(), but with an additional + \a icon. +*/ +int QTabWidget::insertTab(int index, QWidget *w, const QIcon& icon, const QString &label) +{ + Q_D(QTabWidget); + if(!w) + return -1; + index = d->stack->insertWidget(index, w); + d->tabs->insertTab(index, icon, label); + setUpLayout(); + tabInserted(index); + + return index; +} + + +/*! + Defines a new \a label for the page at position \a index's tab. + + If the provided text contains an ampersand character ('&'), a + shortcut is automatically created for it. The character that + follows the '&' will be used as the shortcut key. Any previous + shortcut will be overwritten, or cleared if no shortcut is defined + by the text. See the \l {QShortcut#mnemonic}{QShortcut} + documentation for details (to display an actual ampersand, use + '&&'). + +*/ +void QTabWidget::setTabText(int index, const QString &label) +{ + Q_D(QTabWidget); + d->tabs->setTabText(index, label); + setUpLayout(); +} + +/*! + Returns the label text for the tab on the page at position \a index. +*/ + +QString QTabWidget::tabText(int index) const +{ + Q_D(const QTabWidget); + return d->tabs->tabText(index); +} + +/*! + \overload + + Sets the \a icon for the tab at position \a index. +*/ +void QTabWidget::setTabIcon(int index, const QIcon &icon) +{ + Q_D(QTabWidget); + d->tabs->setTabIcon(index, icon); + setUpLayout(); +} + +/*! + Returns the icon for the tab on the page at position \a index. +*/ + +QIcon QTabWidget::tabIcon(int index) const +{ + Q_D(const QTabWidget); + return d->tabs->tabIcon(index); +} + +/*! + Returns true if the the page at position \a index is enabled; otherwise returns false. + + \sa setTabEnabled(), QWidget::isEnabled() +*/ + +bool QTabWidget::isTabEnabled(int index) const +{ + Q_D(const QTabWidget); + return d->tabs->isTabEnabled(index); +} + +/*! + If \a enable is true, the page at position \a index is enabled; otherwise the page at position \a index is + disabled. The page's tab is redrawn appropriately. + + QTabWidget uses QWidget::setEnabled() internally, rather than + keeping a separate flag. + + Note that even a disabled tab/page may be visible. If the page is + visible already, QTabWidget will not hide it; if all the pages are + disabled, QTabWidget will show one of them. + + \sa isTabEnabled(), QWidget::setEnabled() +*/ + +void QTabWidget::setTabEnabled(int index, bool enable) +{ + Q_D(QTabWidget); + d->tabs->setTabEnabled(index, enable); +} + +/*! + \fn void QTabWidget::setCornerWidget(QWidget *widget, Qt::Corner corner) + + Sets the given \a widget to be shown in the specified \a corner of the + tab widget. The geometry of the widget is determined based on the widget's + sizeHint() and the style(). + + Only the horizontal element of the \a corner will be used. + + Passing 0 shows no widget in the corner. + + Any previously set corner widget is hidden. + + All widgets set here will be deleted by the tab widget when it is + destroyed unless you separately reparent the widget after setting + some other corner widget (or 0). + + Note: Corner widgets are designed for \l North and \l South tab positions; + other orientations are known to not work properly. + + \sa cornerWidget(), setTabPosition() +*/ +void QTabWidget::setCornerWidget(QWidget * widget, Qt::Corner corner) +{ + Q_D(QTabWidget); + if (widget && widget->parentWidget() != this) + widget->setParent(this); + + if (corner & Qt::TopRightCorner) { + if (d->rightCornerWidget) + d->rightCornerWidget->hide(); + d->rightCornerWidget = widget; + } else { + if (d->leftCornerWidget) + d->leftCornerWidget->hide(); + d->leftCornerWidget = widget; + } + setUpLayout(); +} + +/*! + Returns the widget shown in the \a corner of the tab widget or 0. +*/ +QWidget * QTabWidget::cornerWidget(Qt::Corner corner) const +{ + Q_D(const QTabWidget); + if (corner & Qt::TopRightCorner) + return d->rightCornerWidget; + return d->leftCornerWidget; +} + +/*! + Removes the tab at position \a index from this stack of widgets. + The page widget itself is not deleted. + + \sa addTab(), insertTab() +*/ +void QTabWidget::removeTab(int index) +{ + Q_D(QTabWidget); + if (QWidget *w = d->stack->widget(index)) + d->stack->removeWidget(w); +} + +/*! + Returns a pointer to the page currently being displayed by the tab + dialog. The tab dialog does its best to make sure that this value + is never 0 (but if you try hard enough, it can be). + + \sa currentIndex(), setCurrentWidget() +*/ + +QWidget * QTabWidget::currentWidget() const +{ + Q_D(const QTabWidget); + return d->stack->currentWidget(); +} + +/*! + Makes \a widget the current widget. The \a widget used must be a page in + this tab widget. + + \sa addTab(), setCurrentIndex(), currentWidget() + */ +void QTabWidget::setCurrentWidget(QWidget *widget) +{ + Q_D(const QTabWidget); + d->tabs->setCurrentIndex(indexOf(widget)); +} + + +/*! + \property QTabWidget::currentIndex + \brief the index position of the current tab page + + The current index is -1 if there is no current widget. + + By default, this property contains a value of -1 because there are initially + no tabs in the widget. +*/ + +int QTabWidget::currentIndex() const +{ + Q_D(const QTabWidget); + return d->tabs->currentIndex(); +} + +void QTabWidget::setCurrentIndex(int index) +{ + Q_D(QTabWidget); + d->tabs->setCurrentIndex(index); +} + + +/*! + Returns the index position of the page occupied by the widget \a + w, or -1 if the widget cannot be found. +*/ +int QTabWidget::indexOf(QWidget* w) const +{ + Q_D(const QTabWidget); + return d->stack->indexOf(w); +} + + +/*! + \reimp +*/ +void QTabWidget::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + setUpLayout(); +} + +/*! + Replaces the dialog's QTabBar heading with the tab bar \a tb. Note + that this must be called \e before any tabs have been added, or + the behavior is undefined. + + \sa tabBar() +*/ +void QTabWidget::setTabBar(QTabBar* tb) +{ + Q_D(QTabWidget); + Q_ASSERT(tb); + + if (tb->parentWidget() != this) { + tb->setParent(this); + tb->show(); + } + delete d->tabs; + d->tabs = tb; + setFocusProxy(d->tabs); + connect(d->tabs, SIGNAL(currentChanged(int)), + this, SLOT(_q_showTab(int))); + connect(d->tabs, SIGNAL(tabMoved(int, int)), + this, SLOT(_q_tabMoved(int, int))); + if (d->tabs->tabsClosable()) + connect(d->tabs, SIGNAL(tabCloseRequested(int)), + this, SIGNAL(tabCloseRequested(int))); + tb->setExpanding(!documentMode()); + setUpLayout(); +} + + +/*! + Returns the current QTabBar. + + \sa setTabBar() +*/ +QTabBar* QTabWidget::tabBar() const +{ + Q_D(const QTabWidget); + return d->tabs; +} + +/*! + Ensures that the selected tab's page is visible and appropriately + sized. +*/ + +void QTabWidgetPrivate::_q_showTab(int index) +{ + Q_Q(QTabWidget); + if (index < stack->count() && index >= 0) + stack->setCurrentIndex(index); + emit q->currentChanged(index); +#ifdef QT3_SUPPORT + emit q->selected(q->tabText(index)); + emit q->currentChanged(stack->widget(index)); +#endif +} + +void QTabWidgetPrivate::_q_removeTab(int index) +{ + Q_Q(QTabWidget); + tabs->removeTab(index); + q->setUpLayout(); + q->tabRemoved(index); +} + +void QTabWidgetPrivate::_q_tabMoved(int from, int to) +{ + stack->blockSignals(true); + QWidget *w = stack->widget(from); + stack->removeWidget(w); + stack->insertWidget(to, w); + stack->blockSignals(false); +} + +/* + Set up the layout. + Get subrect from the current style, and set the geometry for the + stack widget, tab bar and corner widgets. +*/ +void QTabWidget::setUpLayout(bool onlyCheck) +{ + Q_D(QTabWidget); + if (onlyCheck && !d->dirty) + return; // nothing to do + + QStyleOptionTabWidgetFrame option; + initStyleOption(&option); + + // this must be done immediately, because QWidgetItem relies on it (even if !isVisible()) + d->setLayoutItemMargins(QStyle::SE_TabWidgetLayoutItem, &option); + + if (!isVisible()) { + d->dirty = true; + return; // we'll do it later + } + + QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this); + d->panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this); + QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this); + QRect leftCornerRect = style()->subElementRect(QStyle::SE_TabWidgetLeftCorner, &option, this); + QRect rightCornerRect = style()->subElementRect(QStyle::SE_TabWidgetRightCorner, &option, this); + + d->tabs->setGeometry(tabRect); + d->stack->setGeometry(contentsRect); + if (d->leftCornerWidget) + d->leftCornerWidget->setGeometry(leftCornerRect); + if (d->rightCornerWidget) + d->rightCornerWidget->setGeometry(rightCornerRect); + + if (!onlyCheck) + update(); + updateGeometry(); +} + +/*! + \internal +*/ +static inline QSize basicSize( + bool horizontal, const QSize &lc, const QSize &rc, const QSize &s, const QSize &t) +{ + return horizontal + ? QSize(qMax(s.width(), t.width() + rc.width() + lc.width()), + s.height() + (qMax(rc.height(), qMax(lc.height(), t.height())))) + : QSize(s.width() + (qMax(rc.width(), qMax(lc.width(), t.width()))), + qMax(s.height(), t.height() + rc.height() + lc.height())); +} + +/*! + \reimp +*/ +QSize QTabWidget::sizeHint() const +{ + Q_D(const QTabWidget); + QSize lc(0, 0), rc(0, 0); + QStyleOption opt(0); + opt.init(this); + opt.state = QStyle::State_None; + + if (d->leftCornerWidget) + lc = d->leftCornerWidget->sizeHint(); + if(d->rightCornerWidget) + rc = d->rightCornerWidget->sizeHint(); + if (!d->dirty) { + QTabWidget *that = (QTabWidget*)this; + that->setUpLayout(true); + } + QSize s(d->stack->sizeHint()); + QSize t(d->tabs->sizeHint()); + if(usesScrollButtons()) + t = t.boundedTo(QSize(200,200)); + else + t = t.boundedTo(QApplication::desktop()->size()); + + QSize sz = basicSize(d->pos == North || d->pos == South, lc, rc, s, t); + + return style()->sizeFromContents(QStyle::CT_TabWidget, &opt, sz, this) + .expandedTo(QApplication::globalStrut()); +} + + +/*! + \reimp + + Returns a suitable minimum size for the tab widget. +*/ +QSize QTabWidget::minimumSizeHint() const +{ + Q_D(const QTabWidget); + QSize lc(0, 0), rc(0, 0); + + if(d->leftCornerWidget) + lc = d->leftCornerWidget->minimumSizeHint(); + if(d->rightCornerWidget) + rc = d->rightCornerWidget->minimumSizeHint(); + if (!d->dirty) { + QTabWidget *that = (QTabWidget*)this; + that->setUpLayout(true); + } + QSize s(d->stack->minimumSizeHint()); + QSize t(d->tabs->minimumSizeHint()); + + QSize sz = basicSize(d->pos == North || d->pos == South, lc, rc, s, t); + + QStyleOption opt(0); + opt.rect = rect(); + opt.palette = palette(); + opt.state = QStyle::State_None; + return style()->sizeFromContents(QStyle::CT_TabWidget, &opt, sz, this) + .expandedTo(QApplication::globalStrut()); +} + +/*! + \reimp + */ +void QTabWidget::showEvent(QShowEvent *) +{ + setUpLayout(); +} + +void QTabWidgetPrivate::updateTabBarPosition() +{ + Q_Q(QTabWidget); + switch (pos) { + case QTabWidget::North: + tabs->setShape(shape == QTabWidget::Rounded ? QTabBar::RoundedNorth + : QTabBar::TriangularNorth); + break; + case QTabWidget::South: + tabs->setShape(shape == QTabWidget::Rounded ? QTabBar::RoundedSouth + : QTabBar::TriangularSouth); + break; + case QTabWidget::West: + tabs->setShape(shape == QTabWidget::Rounded ? QTabBar::RoundedWest + : QTabBar::TriangularWest); + break; + case QTabWidget::East: + tabs->setShape(shape == QTabWidget::Rounded ? QTabBar::RoundedEast + : QTabBar::TriangularEast); + break; + } + q->setUpLayout(); +} + +/*! + \property QTabWidget::tabPosition + \brief the position of the tabs in this tab widget + + Possible values for this property are described by the TabPosition + enum. + + By default, this property is set to \l North. + + \sa TabPosition +*/ +QTabWidget::TabPosition QTabWidget::tabPosition() const +{ + Q_D(const QTabWidget); + return d->pos; +} + +void QTabWidget::setTabPosition(TabPosition pos) +{ + Q_D(QTabWidget); + if (d->pos == pos) + return; + d->pos = pos; + d->updateTabBarPosition(); +} + +/*! + \property QTabWidget::tabsClosable + \brief whether close buttons are automatically added to each tab. + + \since 4.5 + + \sa QTabBar::tabsClosable() +*/ +bool QTabWidget::tabsClosable() const +{ + return tabBar()->tabsClosable(); +} + +void QTabWidget::setTabsClosable(bool closeable) +{ + if (tabsClosable() == closeable) + return; + + tabBar()->setTabsClosable(closeable); + if (closeable) + connect(tabBar(), SIGNAL(tabCloseRequested(int)), + this, SIGNAL(tabCloseRequested(int))); + else + disconnect(tabBar(), SIGNAL(tabCloseRequested(int)), + this, SIGNAL(tabCloseRequested(int))); + setUpLayout(); +} + +/*! + \property QTabWidget::movable + \brief This property holds whether the user can move the tabs + within the tabbar area. + + \since 4.5 + + By default, this property is false; +*/ + +bool QTabWidget::isMovable() const +{ + return tabBar()->isMovable(); +} + +void QTabWidget::setMovable(bool movable) +{ + tabBar()->setMovable(movable); +} + +/*! + \property QTabWidget::tabShape + \brief the shape of the tabs in this tab widget + + Possible values for this property are QTabWidget::Rounded + (default) or QTabWidget::Triangular. + + \sa TabShape +*/ + +QTabWidget::TabShape QTabWidget::tabShape() const +{ + Q_D(const QTabWidget); + return d->shape; +} + +void QTabWidget::setTabShape(TabShape s) +{ + Q_D(QTabWidget); + if (d->shape == s) + return; + d->shape = s; + d->updateTabBarPosition(); +} + +/*! + \reimp + */ +bool QTabWidget::event(QEvent *ev) +{ + if (ev->type() == QEvent::LayoutRequest) + setUpLayout(); + return QWidget::event(ev); +} + +/*! + \reimp + */ +void QTabWidget::changeEvent(QEvent *ev) +{ + if (ev->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || ev->type() == QEvent::MacSizeChange +#endif + ) + setUpLayout(); + QWidget::changeEvent(ev); +} + + +/*! + \reimp + */ +void QTabWidget::keyPressEvent(QKeyEvent *e) +{ + Q_D(QTabWidget); + if (((e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) && + count() > 1 && e->modifiers() & Qt::ControlModifier) +#ifdef QT_KEYPAD_NAVIGATION + || QApplication::keypadNavigationEnabled() && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right) && count() > 1 +#endif + ) { + int pageCount = d->tabs->count(); + int page = currentIndex(); + int dx = (e->key() == Qt::Key_Backtab || e->modifiers() & Qt::ShiftModifier) ? -1 : 1; +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right)) + dx = e->key() == (isRightToLeft() ? Qt::Key_Right : Qt::Key_Left) ? -1 : 1; +#endif + for (int pass = 0; pass < pageCount; ++pass) { + page+=dx; + if (page < 0 +#ifdef QT_KEYPAD_NAVIGATION + && !e->isAutoRepeat() +#endif + ) { + page = count() - 1; + } else if (page >= pageCount +#ifdef QT_KEYPAD_NAVIGATION + && !e->isAutoRepeat() +#endif + ) { + page = 0; + } + if (d->tabs->isTabEnabled(page)) { + setCurrentIndex(page); + break; + } + } + if (!qApp->focusWidget()) + d->tabs->setFocus(); + } else { + e->ignore(); + } +} + +/*! + Returns the tab page at index position \a index or 0 if the \a + index is out of range. +*/ +QWidget *QTabWidget::widget(int index) const +{ + Q_D(const QTabWidget); + return d->stack->widget(index); +} + +/*! + \property QTabWidget::count + \brief the number of tabs in the tab bar + + By default, this property contains a value of 0. +*/ +int QTabWidget::count() const +{ + Q_D(const QTabWidget); + return d->tabs->count(); +} + +#ifndef QT_NO_TOOLTIP +/*! + Sets the tab tool tip for the page at position \a index to \a tip. + + \sa tabToolTip() +*/ +void QTabWidget::setTabToolTip(int index, const QString & tip) +{ + Q_D(QTabWidget); + d->tabs->setTabToolTip(index, tip); +} + +/*! + Returns the tab tool tip for the page at position \a index or + an empty string if no tool tip has been set. + + \sa setTabToolTip() +*/ +QString QTabWidget::tabToolTip(int index) const +{ + Q_D(const QTabWidget); + return d->tabs->tabToolTip(index); +} +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_WHATSTHIS +/*! + \since 4.1 + + Sets the What's This help text for the page at position \a index + to \a text. +*/ +void QTabWidget::setTabWhatsThis(int index, const QString &text) +{ + Q_D(QTabWidget); + d->tabs->setTabWhatsThis(index, text); +} + +/*! + \since 4.1 + + Returns the What's This help text for the page at position \a index, + or an empty string if no help text has been set. +*/ +QString QTabWidget::tabWhatsThis(int index) const +{ + Q_D(const QTabWidget); + return d->tabs->tabWhatsThis(index); +} +#endif // QT_NO_WHATSTHIS + +/*! + This virtual handler is called after a new tab was added or + inserted at position \a index. + + \sa tabRemoved() + */ +void QTabWidget::tabInserted(int index) +{ + Q_UNUSED(index) +} + +/*! + This virtual handler is called after a tab was removed from + position \a index. + + \sa tabInserted() + */ +void QTabWidget::tabRemoved(int index) +{ + Q_UNUSED(index) +} + +/*! + \fn void QTabWidget::paintEvent(QPaintEvent *event) + + Paints the tab widget's tab bar in response to the paint \a event. +*/ +void QTabWidget::paintEvent(QPaintEvent *) +{ + Q_D(QTabWidget); + QStylePainter p(this); + if (documentMode()) { + if (QWidget *w = cornerWidget(Qt::TopLeftCorner)) { + QStyleOptionTabBarBaseV2 opt; + QTabBarPrivate::initStyleBaseOption(&opt, tabBar(), w->size()); + opt.rect.moveLeft(w->x() + opt.rect.x()); + opt.rect.moveTop(w->y() + opt.rect.y()); + p.drawPrimitive(QStyle::PE_FrameTabBarBase, opt); + } + if (QWidget *w = cornerWidget(Qt::TopRightCorner)) { + QStyleOptionTabBarBaseV2 opt; + QTabBarPrivate::initStyleBaseOption(&opt, tabBar(), w->size()); + opt.rect.moveLeft(w->x() + opt.rect.x()); + opt.rect.moveTop(w->y() + opt.rect.y()); + p.drawPrimitive(QStyle::PE_FrameTabBarBase, opt); + } + return; + } + + QStyleOptionTabWidgetFrame opt; + initStyleOption(&opt); + opt.rect = d->panelRect; + p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); +} + +/*! + \property QTabWidget::iconSize + \brief The size for icons in the tab bar + \since 4.2 + + The default value is style-dependent. This is the maximum size + that the icons will have. Icons are not scaled up if they are of + smaller size. + + \sa QTabBar::iconSize +*/ +QSize QTabWidget::iconSize() const +{ + return d_func()->tabs->iconSize(); +} + +void QTabWidget::setIconSize(const QSize &size) +{ + d_func()->tabs->setIconSize(size); +} + +/*! + \property QTabWidget::elideMode + \brief how to elide text in the tab bar + \since 4.2 + + This property controls how items are elided when there is not + enough space to show them for a given tab bar size. + + By default the value is style dependant. + + \sa QTabBar::elideMode usesScrollButtons QStyle::SH_TabBar_ElideMode +*/ +Qt::TextElideMode QTabWidget::elideMode() const +{ + return d_func()->tabs->elideMode(); +} + +void QTabWidget::setElideMode(Qt::TextElideMode mode) +{ + d_func()->tabs->setElideMode(mode); +} + +/*! + \property QTabWidget::usesScrollButtons + \brief Whether or not a tab bar should use buttons to scroll tabs when it + has many tabs. + \since 4.2 + + When there are too many tabs in a tab bar for its size, the tab bar can either choose + to expand its size or to add buttons that allow you to scroll through the tabs. + + By default the value is style dependant. + + \sa elideMode QTabBar::usesScrollButtons QStyle::SH_TabBar_PreferNoArrows +*/ +bool QTabWidget::usesScrollButtons() const +{ + return d_func()->tabs->usesScrollButtons(); +} + +void QTabWidget::setUsesScrollButtons(bool useButtons) +{ + d_func()->tabs->setUsesScrollButtons(useButtons); +} + +/*! + \property QTabWidget::documentMode + \brief Whether or not the tab widget is rendered in a mode suitable for document + pages. This is the same as document mode on Mac OS X. + \since 4.5 + + When this property is set the tab widget frame is not rendered. This mode is useful + for showing document-type pages where the page covers most of the tab widget + area. + + \sa elideMode, QTabBar::documentMode, QTabBar::usesScrollButtons, QStyle::SH_TabBar_PreferNoArrows +*/ +bool QTabWidget::documentMode() const +{ + Q_D(const QTabWidget); + return d->tabs->documentMode(); +} + +void QTabWidget::setDocumentMode(bool enabled) +{ + Q_D(QTabWidget); + d->tabs->setDocumentMode(enabled); + d->tabs->setExpanding(!enabled); + d->tabs->setDrawBase(enabled); + setUpLayout(); +} + +/*! + Removes all the pages, but does not delete them. Calling this function + is equivalent to calling removeTab() until the tab widget is empty. +*/ +void QTabWidget::clear() +{ + // ### optimize by introduce QStackedLayout::clear() + while (count()) + removeTab(0); +} + +/*! + \fn void QTabWidget::insertTab(QWidget *widget, const QString &label, int index) + + Use insertTab(index, widget, label) instead. +*/ + +/*! + \fn void QTabWidget::insertTab(QWidget *widget, const QIcon& icon, const QString &label, int index) + + Use insertTab(index, widget, icon, label) instead. +*/ + +/*! + \fn void QTabWidget::changeTab(QWidget *widget, const QString + &label) + + Use setTabText() instead. + +*/ + +/*! + \fn void QTabWidget::changeTab(QWidget *widget, const QIcon& icon, const QString &label) + + Use setTabText() and setTabIcon() instead. +*/ + +/*! + \fn bool QTabWidget::isTabEnabled( QWidget *widget) const + + Use isTabEnabled(tabWidget->indexOf(widget)) instead. +*/ + +/*! + \fn void QTabWidget::setTabEnabled(QWidget *widget, bool b) + + Use setTabEnabled(tabWidget->indexOf(widget), b) instead. +*/ + +/*! + \fn QString QTabWidget::tabLabel(QWidget *widget) const + + Use tabText(tabWidget->indexOf(widget)) instead. +*/ + +/*! + \fn void QTabWidget::setTabLabel(QWidget *widget, const QString + &label) + + Use setTabText(tabWidget->indexOf(widget), label) instead. +*/ + +/*! + \fn QIcon QTabWidget::tabIconSet(QWidget * widget) const + + Use tabIcon(tabWidget->indexOf(widget)) instead. +*/ + +/*! + \fn void QTabWidget::setTabIconSet(QWidget * widget, const QIcon & icon) + + Use setTabIcon(tabWidget->indexOf(widget), icon) instead. +*/ + +/*! + \fn void QTabWidget::removeTabToolTip(QWidget * widget) + + Use setTabToolTip(tabWidget->indexOf(widget), QString()) instead. +*/ + +/*! + \fn void QTabWidget::setTabToolTip(QWidget * widget, const QString & tip) + + Use setTabToolTip(tabWidget->indexOf(widget), tip) instead. +*/ + +/*! + \fn QString QTabWidget::tabToolTip(QWidget * widget) const + + Use tabToolTip(tabWidget->indexOf(widget)) instead. +*/ + +/*! + \fn QWidget * QTabWidget::currentPage() const + + Use currentWidget() instead. +*/ + +/*! + \fn QWidget *QTabWidget::page(int index) const + + Use widget() instead. +*/ + +/*! + \fn QString QTabWidget::label(int index) const + + Use tabText() instead. +*/ + +/*! + \fn int QTabWidget::currentPageIndex() const + + Use currentIndex() instead. +*/ + +/*! + \fn int QTabWidget::margin() const + + This function is kept only to make old code compile. + This functionality is no longer supported by QTabWidget. + + \sa contentsRect(), setContentsMargins() +*/ + +/*! + \fn void QTabWidget::setMargin(int margin) + + This function is kept only to make old code compile. + This functionality is no longer supported by QTabWidget. + + \sa contentsRect(), setContentsMargins() +*/ + +/*! + \fn void QTabWidget::setCurrentPage(int index) + + Use setCurrentIndex() instead. +*/ + +/*! + \fn void QTabWidget::showPage(QWidget *widget) + + Use setCurrentIndex(indexOf(widget)) instead. +*/ + +/*! + \fn void QTabWidget::removePage(QWidget *widget) + + Use removeTab(indexOf(widget)) instead. +*/ + +/*! + \fn void QTabWidget::currentChanged(QWidget *widget) + + Use currentChanged(int) instead. +*/ + +QT_END_NAMESPACE + +#include "moc_qtabwidget.cpp" + +#endif //QT_NO_TABWIDGET diff --git a/src/gui/widgets/qtabwidget.h b/src/gui/widgets/qtabwidget.h new file mode 100644 index 0000000..9307c48 --- /dev/null +++ b/src/gui/widgets/qtabwidget.h @@ -0,0 +1,252 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABWIDGET_H +#define QTABWIDGET_H + +#include <QtGui/qwidget.h> +#include <QtGui/qicon.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TABWIDGET + +class QTabBar; +class QTabWidgetPrivate; +class QStyleOptionTabWidgetFrame; + +class Q_GUI_EXPORT QTabWidget : public QWidget +{ + Q_OBJECT + Q_ENUMS(TabPosition TabShape) + Q_PROPERTY(TabPosition tabPosition READ tabPosition WRITE setTabPosition) + Q_PROPERTY(TabShape tabShape READ tabShape WRITE setTabShape) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentChanged) + Q_PROPERTY(int count READ count) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + Q_PROPERTY(Qt::TextElideMode elideMode READ elideMode WRITE setElideMode) + Q_PROPERTY(bool usesScrollButtons READ usesScrollButtons WRITE setUsesScrollButtons) + Q_PROPERTY(bool documentMode READ documentMode WRITE setDocumentMode) + Q_PROPERTY(bool tabsClosable READ tabsClosable WRITE setTabsClosable) + Q_PROPERTY(bool movable READ isMovable WRITE setMovable) + +public: + explicit QTabWidget(QWidget *parent = 0); + ~QTabWidget(); + + int addTab(QWidget *widget, const QString &); + int addTab(QWidget *widget, const QIcon& icon, const QString &label); + + int insertTab(int index, QWidget *widget, const QString &); + int insertTab(int index, QWidget *widget, const QIcon& icon, const QString &label); + + void removeTab(int index); + + bool isTabEnabled(int index) const; + void setTabEnabled(int index, bool); + + QString tabText(int index) const; + void setTabText(int index, const QString &); + + QIcon tabIcon(int index) const; + void setTabIcon(int index, const QIcon & icon); + +#ifndef QT_NO_TOOLTIP + void setTabToolTip(int index, const QString & tip); + QString tabToolTip(int index) const; +#endif + +#ifndef QT_NO_WHATSTHIS + void setTabWhatsThis(int index, const QString &text); + QString tabWhatsThis(int index) const; +#endif + + int currentIndex() const; + QWidget *currentWidget() const; + QWidget *widget(int index) const; + int indexOf(QWidget *widget) const; + int count() const; + + enum TabPosition { North, South, West, East +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + , Top = North, Bottom = South +#endif + }; + TabPosition tabPosition() const; + void setTabPosition(TabPosition); + + bool tabsClosable() const; + void setTabsClosable(bool closeable); + + bool isMovable() const; + void setMovable(bool movable); + + enum TabShape { Rounded, Triangular }; + TabShape tabShape() const; + void setTabShape(TabShape s); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setCornerWidget(QWidget * w, Qt::Corner corner = Qt::TopRightCorner); + QWidget * cornerWidget(Qt::Corner corner = Qt::TopRightCorner) const; + + Qt::TextElideMode elideMode() const; + void setElideMode(Qt::TextElideMode); + + QSize iconSize() const; + void setIconSize(const QSize &size); + + bool usesScrollButtons() const; + void setUsesScrollButtons(bool useButtons); + + bool documentMode() const; + void setDocumentMode(bool set); + + void clear(); + +public Q_SLOTS: + void setCurrentIndex(int index); + void setCurrentWidget(QWidget *widget); + +Q_SIGNALS: + void currentChanged(int index); + void tabCloseRequested(int index); + +protected: + virtual void tabInserted(int index); + virtual void tabRemoved(int index); + + void showEvent(QShowEvent *); + void resizeEvent(QResizeEvent *); + void keyPressEvent(QKeyEvent *); + void paintEvent(QPaintEvent *); + void setTabBar(QTabBar *); + QTabBar* tabBar() const; + void changeEvent(QEvent *); + bool event(QEvent *); + void initStyleOption(QStyleOptionTabWidgetFrame *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QTabWidget(QWidget *parent, const char *name, Qt::WindowFlags f = 0); + + inline QT3_SUPPORT void insertTab(QWidget * w, const QString &s, int index = -1) { insertTab(index, w, s); } + inline QT3_SUPPORT void insertTab(QWidget *child, const QIcon& icon, + const QString &label, int index = -1) { insertTab(index, child, icon, label); } + + inline QT3_SUPPORT void changeTab(QWidget *w, const QString &s) {setTabText(indexOf(w), s); } + inline QT3_SUPPORT void changeTab(QWidget *w, const QIcon& icon, + const QString &label) { int idx = indexOf(w); setTabText(idx, label); setTabIcon(idx, icon); } + + inline QT3_SUPPORT bool isTabEnabled( QWidget *w) const {return isTabEnabled(indexOf(w)); } + inline QT3_SUPPORT void setTabEnabled(QWidget *w, bool b) { setTabEnabled(indexOf(w), b); } + + inline QT3_SUPPORT QString tabLabel(QWidget *w) const {return tabText(indexOf(w)); } + inline QT3_SUPPORT void setTabLabel(QWidget *w, const QString &l) { setTabText(indexOf(w), l); } + + inline QT3_SUPPORT QIcon tabIconSet(QWidget * w) const {return tabIcon(indexOf(w)); } + inline QT3_SUPPORT void setTabIconSet(QWidget * w, const QIcon & icon) { setTabIcon(indexOf(w), icon); } + + inline QT3_SUPPORT void removeTabToolTip(QWidget * w) { +#ifndef QT_NO_TOOLTIP + setTabToolTip(indexOf(w), QString()); +#else + Q_UNUSED(w); +#endif + } + inline QT3_SUPPORT void setTabToolTip(QWidget * w, const QString & tip) { +#ifndef QT_NO_TOOLTIP + setTabToolTip(indexOf(w), tip); +#else + Q_UNUSED(w); + Q_UNUSED(tip); +#endif + } + + inline QT3_SUPPORT QString tabToolTip(QWidget * w) const { +#ifndef QT_NO_TOOLTIP + return tabToolTip(indexOf(w)); +#else + Q_UNUSED(w); + return QString(); +#endif + } + + inline QT3_SUPPORT QWidget * currentPage() const { return currentWidget(); } + inline QT3_SUPPORT QWidget *page(int index) const { return widget(index); } + inline QT3_SUPPORT QString label(int index) const { return tabText(index); } + inline QT3_SUPPORT int currentPageIndex() const { return currentIndex(); } + + inline QT3_SUPPORT int margin() const { return 0; } + inline QT3_SUPPORT void setMargin(int) {} + +public Q_SLOTS: + inline QT_MOC_COMPAT void setCurrentPage(int index) { setCurrentIndex(index); } + inline QT_MOC_COMPAT void showPage(QWidget *w) { setCurrentIndex(indexOf(w)); } + inline QT_MOC_COMPAT void removePage(QWidget *w) { removeTab(indexOf(w)); } + +Q_SIGNALS: + QT_MOC_COMPAT void currentChanged(QWidget *); + QT_MOC_COMPAT void selected(const QString&); +#endif // QT3_SUPPORT + +private: + Q_DECLARE_PRIVATE(QTabWidget) + Q_DISABLE_COPY(QTabWidget) + Q_PRIVATE_SLOT(d_func(), void _q_showTab(int)) + Q_PRIVATE_SLOT(d_func(), void _q_removeTab(int)) + Q_PRIVATE_SLOT(d_func(), void _q_tabMoved(int, int)) + void setUpLayout(bool = false); + friend class Q3TabDialog; +}; + +#endif // QT_NO_TABWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTABWIDGET_H diff --git a/src/gui/widgets/qtextbrowser.cpp b/src/gui/widgets/qtextbrowser.cpp new file mode 100644 index 0000000..a1f4d34 --- /dev/null +++ b/src/gui/widgets/qtextbrowser.cpp @@ -0,0 +1,1275 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextbrowser.h" +#include "qtextedit_p.h" + +#ifndef QT_NO_TEXTBROWSER + +#include <qstack.h> +#include <qapplication.h> +#include <qevent.h> +#include <qdesktopwidget.h> +#include <qdebug.h> +#include <qabstracttextdocumentlayout.h> +#include "private/qtextdocumentlayout_p.h" +#include <qtextcodec.h> +#include <qpainter.h> +#include <qdir.h> +#include <qwhatsthis.h> +#include <qtextobject.h> +#include <qdesktopservices.h> + +QT_BEGIN_NAMESPACE + +class QTextBrowserPrivate : public QTextEditPrivate +{ + Q_DECLARE_PUBLIC(QTextBrowser) +public: + inline QTextBrowserPrivate() + : textOrSourceChanged(false), forceLoadOnSourceChange(false), openExternalLinks(false), + openLinks(true) +#ifdef QT_KEYPAD_NAVIGATION + , lastKeypadScrollValue(-1) +#endif + {} + + void init(); + + struct HistoryEntry { + inline HistoryEntry() + : hpos(0), vpos(0), focusIndicatorPosition(-1), + focusIndicatorAnchor(-1) {} + QUrl url; + QString title; + int hpos; + int vpos; + int focusIndicatorPosition, focusIndicatorAnchor; + }; + + HistoryEntry history(int i) const + { + if (i <= 0) + if (-i < stack.count()) + return stack[stack.count()+i-1]; + else + return HistoryEntry(); + else + if (i <= forwardStack.count()) + return forwardStack[forwardStack.count()-i]; + else + return HistoryEntry(); + } + + + HistoryEntry createHistoryEntry() const; + void restoreHistoryEntry(const HistoryEntry entry); + + QStack<HistoryEntry> stack; + QStack<HistoryEntry> forwardStack; + QUrl home; + QUrl currentURL; + + QStringList searchPaths; + + /*flag necessary to give the linkClicked() signal some meaningful + semantics when somebody connected to it calls setText() or + setSource() */ + bool textOrSourceChanged; + bool forceLoadOnSourceChange; + + bool openExternalLinks; + bool openLinks; + +#ifndef QT_NO_CURSOR + QCursor oldCursor; +#endif + + QString findFile(const QUrl &name) const; + + inline void _q_documentModified() + { + textOrSourceChanged = true; + forceLoadOnSourceChange = !currentURL.path().isEmpty(); + } + + void _q_activateAnchor(const QString &href); + void _q_highlightLink(const QString &href); + + void setSource(const QUrl &url); + + // re-imlemented from QTextEditPrivate + virtual QUrl resolveUrl(const QUrl &url) const; + inline QUrl resolveUrl(const QString &url) const + { return resolveUrl(QUrl::fromEncoded(url.toUtf8())); } + +#ifdef QT_KEYPAD_NAVIGATION + void keypadMove(bool next); + QTextCursor prevFocus; + int lastKeypadScrollValue; +#endif +}; + +QString QTextBrowserPrivate::findFile(const QUrl &name) const +{ + QString fileName; + if (name.scheme() == QLatin1String("qrc")) + fileName = QLatin1String(":/") + name.path(); + else + fileName = name.toLocalFile(); + + if (QFileInfo(fileName).isAbsolute()) + return fileName; + + foreach (QString path, searchPaths) { + if (!path.endsWith(QLatin1Char('/'))) + path.append(QLatin1Char('/')); + path.append(fileName); + if (QFileInfo(path).isReadable()) + return path; + } + + return fileName; +} + +QUrl QTextBrowserPrivate::resolveUrl(const QUrl &url) const +{ + if (!url.isRelative()) + return url; + + // For the second case QUrl can merge "#someanchor" with "foo.html" + // correctly to "foo.html#someanchor" + if (!(currentURL.isRelative() + || (currentURL.scheme() == QLatin1String("file") + && !QFileInfo(currentURL.toLocalFile()).isAbsolute())) + || (url.hasFragment() && url.path().isEmpty())) { + return currentURL.resolved(url); + } + + // this is our last resort when current url and new url are both relative + // we try to resolve against the current working directory in the local + // file system. + QFileInfo fi(currentURL.toLocalFile()); + if (fi.exists()) { + return QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(url); + } + + return url; +} + +void QTextBrowserPrivate::_q_activateAnchor(const QString &href) +{ + if (href.isEmpty()) + return; + Q_Q(QTextBrowser); + +#ifndef QT_NO_CURSOR + viewport->setCursor(oldCursor); +#endif + + const QUrl url = resolveUrl(href); + + if (!openLinks) { + emit q->anchorClicked(url); + return; + } + + textOrSourceChanged = false; + +#ifndef QT_NO_DESKTOPSERVICES + if ((openExternalLinks + && url.scheme() != QLatin1String("file") + && url.scheme() != QLatin1String("qrc") + && !url.isRelative()) + || (url.isRelative() && !currentURL.isRelative() + && currentURL.scheme() != QLatin1String("file") + && currentURL.scheme() != QLatin1String("qrc"))) { + QDesktopServices::openUrl(url); + return; + } +#endif + + emit q->anchorClicked(url); + + if (textOrSourceChanged) + return; + + q->setSource(url); +} + +void QTextBrowserPrivate::_q_highlightLink(const QString &anchor) +{ + Q_Q(QTextBrowser); + if (anchor.isEmpty()) { +#ifndef QT_NO_CURSOR + if (viewport->cursor().shape() != Qt::PointingHandCursor) + oldCursor = viewport->cursor(); + viewport->setCursor(oldCursor); +#endif + emit q->highlighted(QUrl()); + emit q->highlighted(QString()); + } else { +#ifndef QT_NO_CURSOR + viewport->setCursor(Qt::PointingHandCursor); +#endif + + const QUrl url = resolveUrl(anchor); + emit q->highlighted(url); + // convenience to ease connecting to QStatusBar::showMessage(const QString &) + emit q->highlighted(url.toString()); + } +} + +void QTextBrowserPrivate::setSource(const QUrl &url) +{ + Q_Q(QTextBrowser); +#ifndef QT_NO_CURSOR + if (q->isVisible()) + qApp->setOverrideCursor(Qt::WaitCursor); +#endif + textOrSourceChanged = true; + + QString txt; + + bool doSetText = false; + + QUrl currentUrlWithoutFragment = currentURL; + currentUrlWithoutFragment.setFragment(QString()); + QUrl newUrlWithoutFragment = currentURL.resolved(url); + newUrlWithoutFragment.setFragment(QString()); + + if (url.isValid() + && (newUrlWithoutFragment != currentUrlWithoutFragment || forceLoadOnSourceChange)) { + QVariant data = q->loadResource(QTextDocument::HtmlResource, resolveUrl(url)); + if (data.type() == QVariant::String) { + txt = data.toString(); + } else if (data.type() == QVariant::ByteArray) { +#ifndef QT_NO_TEXTCODEC + QByteArray ba = data.toByteArray(); + QTextCodec *codec = Qt::codecForHtml(ba); + txt = codec->toUnicode(ba); +#else + txt = data.toString(); +#endif + } + if (txt.isEmpty()) + qWarning("QTextBrowser: No document for %s", url.toString().toLatin1().constData()); + + if (q->isVisible()) { + QString firstTag = txt.left(txt.indexOf(QLatin1Char('>')) + 1); + if (firstTag.left(3) == QLatin1String("<qt") && firstTag.contains(QLatin1String("type")) && firstTag.contains(QLatin1String("detail"))) { +#ifndef QT_NO_CURSOR + qApp->restoreOverrideCursor(); +#endif +#ifndef QT_NO_WHATSTHIS + QWhatsThis::showText(QCursor::pos(), txt, q); +#endif + return; + } + } + + currentURL = resolveUrl(url); + doSetText = true; + } + + if (!home.isValid()) + home = url; + + if (doSetText) { +#ifndef QT_NO_TEXTHTMLPARSER + q->QTextEdit::setHtml(txt); + q->document()->setMetaInformation(QTextDocument::DocumentUrl, currentURL.toString()); +#else + q->QTextEdit::setPlainText(txt); +#endif + +#ifdef QT_KEYPAD_NAVIGATION + prevFocus.movePosition(QTextCursor::Start); +#endif + } + + forceLoadOnSourceChange = false; + + if (!url.fragment().isEmpty()) { + q->scrollToAnchor(url.fragment()); + } else { + hbar->setValue(0); + vbar->setValue(0); + } +#ifdef QT_KEYPAD_NAVIGATION + lastKeypadScrollValue = vbar->value(); + emit q->highlighted(QUrl()); + emit q->highlighted(QString()); +#endif + +#ifndef QT_NO_CURSOR + if (q->isVisible()) + qApp->restoreOverrideCursor(); +#endif + emit q->sourceChanged(url); +} + +#ifdef QT_KEYPAD_NAVIGATION +void QTextBrowserPrivate::keypadMove(bool next) +{ + Q_Q(QTextBrowser); + + const int height = viewport->height(); + const int overlap = qBound(20, height / 5, 40); // XXX arbitrary, but a good balance + const int visibleLinkAmount = overlap; // consistent, but maybe not the best choice (?) + int yOffset = vbar->value(); + int scrollYOffset = qBound(0, next ? yOffset + height - overlap : yOffset - height + overlap, vbar->maximum()); + + bool foundNextAnchor = false; + bool focusIt = false; + int focusedPos = -1; + + QTextCursor anchorToFocus; + + QRectF viewRect = QRectF(0, yOffset, control->size().width(), height); + QRectF newViewRect = QRectF(0, scrollYOffset, control->size().width(), height); + QRectF bothViewRects = viewRect.united(newViewRect); + + // If we don't have a previous anchor, pretend that we had the first/last character + // on the screen selected. + if (prevFocus.isNull()) { + if (next) + prevFocus = control->cursorForPosition(QPointF(0, yOffset)); + else + prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height)); + } + + // First, check to see if someone has moved the scroll bars independently + if (lastKeypadScrollValue != yOffset) { + // Someone (user or programmatically) has moved us, so we might + // need to start looking from the current position instead of prevFocus + + bool findOnScreen = true; + + // If prevFocus is on screen at all, we just use it. + if (prevFocus.hasSelection()) { + QRectF prevRect = control->selectionRect(prevFocus); + if (viewRect.intersects(prevRect)) + findOnScreen = false; + } + + // Otherwise, we find a new anchor that's on screen. + // Basically, create a cursor with the last/first character + // on screen + if (findOnScreen) { + if (next) + prevFocus = control->cursorForPosition(QPointF(0, yOffset)); + else + prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height)); + } + foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus); + } else if (prevFocus.hasSelection()) { + // Check the pathological case that the current anchor is higher + // than the screen, and just scroll through it in that case + QRectF prevRect = control->selectionRect(prevFocus); + if ((next && prevRect.bottom() > (yOffset + height)) || + (!next && prevRect.top() < yOffset)) { + anchorToFocus = prevFocus; + focusedPos = scrollYOffset; + focusIt = true; + } else { + // This is the "normal" case - no scroll bar adjustments, no large anchors, + // and no wrapping. + foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus); + } + } + + // If not found yet, see if we need to wrap + if (!focusIt && !foundNextAnchor) { + if (next) { + if (yOffset == vbar->maximum()) { + prevFocus.movePosition(QTextCursor::Start); + yOffset = scrollYOffset = 0; + + // Refresh the rectangles + viewRect = QRectF(0, yOffset, control->size().width(), height); + newViewRect = QRectF(0, scrollYOffset, control->size().width(), height); + bothViewRects = viewRect.united(newViewRect); + } + } else { + if (yOffset == 0) { + prevFocus.movePosition(QTextCursor::End); + yOffset = scrollYOffset = vbar->maximum(); + + // Refresh the rectangles + viewRect = QRectF(0, yOffset, control->size().width(), height); + newViewRect = QRectF(0, scrollYOffset, control->size().width(), height); + bothViewRects = viewRect.united(newViewRect); + } + } + + // Try looking now + foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus); + } + + // If we did actually find an anchor to use... + if (foundNextAnchor) { + QRectF desiredRect = control->selectionRect(anchorToFocus); + + // XXX This is an arbitrary heuristic + // Decide to focus an anchor if it will be at least be + // in the middle region of the screen after a scroll. + // This can result in partial anchors with focus, but + // insisting on links being completely visible before + // selecting them causes disparities between links that + // take up 90% of the screen height and those that take + // up e.g. 110% + // Obviously if a link is entirely visible, we still + // focus it. + if(bothViewRects.contains(desiredRect) + || bothViewRects.adjusted(0, visibleLinkAmount, 0, -visibleLinkAmount).intersects(desiredRect)) { + focusIt = true; + + // We aim to put the new link in the middle of the screen, + // unless the link is larger than the screen (we just move to + // display the first page of the link) + if (desiredRect.height() > height) { + if (next) + focusedPos = (int) desiredRect.top(); + else + focusedPos = (int) desiredRect.bottom() - height; + } else + focusedPos = (int) ((desiredRect.top() + desiredRect.bottom()) / 2 - (height / 2)); + + // and clamp it to make sure we don't skip content. + if (next) + focusedPos = qBound(yOffset, focusedPos, scrollYOffset); + else + focusedPos = qBound(scrollYOffset, focusedPos, yOffset); + } + } + + // If we didn't get a new anchor, check if the old one is still on screen when we scroll + // Note that big (larger than screen height) anchors also have some handling at the + // start of this function. + if (!focusIt && prevFocus.hasSelection()) { + QRectF desiredRect = control->selectionRect(prevFocus); + // XXX this may be better off also using the visibleLinkAmount value + if(newViewRect.intersects(desiredRect)) { + focusedPos = scrollYOffset; + focusIt = true; + anchorToFocus = prevFocus; + } + } + + // setTextCursor ensures that the cursor is visible. save & restore + // the scroll bar values therefore + const int savedXOffset = hbar->value(); + + // Now actually process our decision + if (focusIt && control->setFocusToAnchor(anchorToFocus)) { + // Save the focus for next time + prevFocus = control->textCursor(); + + // Scroll + vbar->setValue(focusedPos); + lastKeypadScrollValue = focusedPos; + hbar->setValue(savedXOffset); + + // Ensure that the new selection is highlighted. + const QString href = control->anchorAtCursor(); + QUrl url = resolveUrl(href); + emit q->highlighted(url); + emit q->highlighted(url.toString()); + } else { + // Scroll + vbar->setValue(scrollYOffset); + lastKeypadScrollValue = scrollYOffset; + + // now make sure we don't have a focused anchor + QTextCursor cursor = control->textCursor(); + cursor.clearSelection(); + + control->setTextCursor(cursor); + + hbar->setValue(savedXOffset); + vbar->setValue(scrollYOffset); + + emit q->highlighted(QUrl()); + emit q->highlighted(QString()); + } +} +#endif + +QTextBrowserPrivate::HistoryEntry QTextBrowserPrivate::createHistoryEntry() const +{ + HistoryEntry entry; + entry.url = q_func()->source(); + entry.title = q_func()->documentTitle(); + entry.hpos = hbar->value(); + entry.vpos = vbar->value(); + + const QTextCursor cursor = control->textCursor(); + if (control->cursorIsFocusIndicator() + && cursor.hasSelection()) { + + entry.focusIndicatorPosition = cursor.position(); + entry.focusIndicatorAnchor = cursor.anchor(); + } + return entry; +} + +void QTextBrowserPrivate::restoreHistoryEntry(const HistoryEntry entry) +{ + setSource(entry.url); + hbar->setValue(entry.hpos); + vbar->setValue(entry.vpos); + if (entry.focusIndicatorAnchor != -1 && entry.focusIndicatorPosition != -1) { + QTextCursor cursor(control->document()); + cursor.setPosition(entry.focusIndicatorAnchor); + cursor.setPosition(entry.focusIndicatorPosition, QTextCursor::KeepAnchor); + control->setTextCursor(cursor); + control->setCursorIsFocusIndicator(true); + } +#ifdef QT_KEYPAD_NAVIGATION + lastKeypadScrollValue = vbar->value(); + prevFocus = control->textCursor(); + + Q_Q(QTextBrowser); + const QString href = prevFocus.charFormat().anchorHref(); + QUrl url = resolveUrl(href); + emit q->highlighted(url); + emit q->highlighted(url.toString()); +#endif +} + +/*! + \class QTextBrowser + \brief The QTextBrowser class provides a rich text browser with hypertext navigation. + + \ingroup text + + This class extends QTextEdit (in read-only mode), adding some navigation + functionality so that users can follow links in hypertext documents. + + If you want to provide your users with an editable rich text editor, + use QTextEdit. If you want a text browser without hypertext navigation + use QTextEdit, and use QTextEdit::setReadOnly() to disable + editing. If you just need to display a small piece of rich text + use QLabel. + + \section1 Document Source and Contents + + The contents of QTextEdit are set with setHtml() or setPlainText(), + but QTextBrowser also implements the setSource() function, making it + possible to use a named document as the source text. The name is looked + up in a list of search paths and in the directory of the current document + factory. + + If a document name ends with + an anchor (for example, "\c #anchor"), the text browser automatically + scrolls to that position (using scrollToAnchor()). When the user clicks + on a hyperlink, the browser will call setSource() itself with the link's + \c href value as argument. You can track the current source by connecting + to the sourceChanged() signal. + + \section1 Navigation + + QTextBrowser provides backward() and forward() slots which you can + use to implement Back and Forward buttons. The home() slot sets + the text to the very first document displayed. The anchorClicked() + signal is emitted when the user clicks an anchor. To override the + default navigation behavior of the browser, call the setSource() + function to supply new document text in a slot connected to this + signal. + + If you want to load documents stored in the Qt resource system use + \c{qrc} as the scheme in the URL to load. For example, for the document + resource path \c{:/docs/index.html} use \c{qrc:/docs/index.html} as + the URL with setSource(). + + \sa QTextEdit, QTextDocument +*/ + +/*! + \property QTextBrowser::modified + \brief whether the contents of the text browser have been modified +*/ + +/*! + \property QTextBrowser::readOnly + \brief whether the text browser is read-only + + By default, this property is true. +*/ + +/*! + \property QTextBrowser::undoRedoEnabled + \brief whether the text browser supports undo/redo operations + + By default, this property is false. +*/ + +void QTextBrowserPrivate::init() +{ + Q_Q(QTextBrowser); + control->setTextInteractionFlags(Qt::TextBrowserInteraction); +#ifndef QT_NO_CURSOR + viewport->setCursor(oldCursor); +#endif + q->setUndoRedoEnabled(false); + viewport->setMouseTracking(true); + QObject::connect(q->document(), SIGNAL(contentsChanged()), q, SLOT(_q_documentModified())); + QObject::connect(control, SIGNAL(linkActivated(QString)), + q, SLOT(_q_activateAnchor(QString))); + QObject::connect(control, SIGNAL(linkHovered(QString)), + q, SLOT(_q_highlightLink(QString))); +} + +/*! + Constructs an empty QTextBrowser with parent \a parent. +*/ +QTextBrowser::QTextBrowser(QWidget *parent) + : QTextEdit(*new QTextBrowserPrivate, parent) +{ + Q_D(QTextBrowser); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QTextBrowser::QTextBrowser(QWidget *parent, const char *name) + : QTextEdit(*new QTextBrowserPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + Q_D(QTextBrowser); + d->init(); +} +#endif + +/*! + \internal +*/ +QTextBrowser::~QTextBrowser() +{ +} + +/*! + \property QTextBrowser::source + \brief the name of the displayed document. + + This is a an invalid url if no document is displayed or if the + source is unknown. + + When setting this property QTextBrowser tries to find a document + with the specified name in the paths of the searchPaths property + and directory of the current source, unless the value is an absolute + file path. It also checks for optional anchors and scrolls the document + accordingly + + If the first tag in the document is \c{<qt type=detail>}, the + document is displayed as a popup rather than as new document in + the browser window itself. Otherwise, the document is displayed + normally in the text browser with the text set to the contents of + the named document with setHtml(). + + By default, this property contains an empty URL. +*/ +QUrl QTextBrowser::source() const +{ + Q_D(const QTextBrowser); + if (d->stack.isEmpty()) + return QUrl(); + else + return d->stack.top().url; +} + +/*! + \property QTextBrowser::searchPaths + \brief the search paths used by the text browser to find supporting + content + + QTextBrowser uses this list to locate images and documents. + + By default, this property contains an empty string list. +*/ + +QStringList QTextBrowser::searchPaths() const +{ + Q_D(const QTextBrowser); + return d->searchPaths; +} + +void QTextBrowser::setSearchPaths(const QStringList &paths) +{ + Q_D(QTextBrowser); + d->searchPaths = paths; +} + +/*! + Reloads the current set source. +*/ +void QTextBrowser::reload() +{ + Q_D(QTextBrowser); + QUrl s = d->currentURL; + d->currentURL = QUrl(); + setSource(s); +} + +void QTextBrowser::setSource(const QUrl &url) +{ + Q_D(QTextBrowser); + + const QTextBrowserPrivate::HistoryEntry historyEntry = d->createHistoryEntry(); + + d->setSource(url); + + if (!url.isValid()) + return; + + // the same url you are already watching? + if (!d->stack.isEmpty() && d->stack.top().url == url) + return; + + if (!d->stack.isEmpty()) + d->stack.top() = historyEntry; + + QTextBrowserPrivate::HistoryEntry entry; + entry.url = url; + entry.title = documentTitle(); + entry.hpos = 0; + entry.vpos = 0; + d->stack.push(entry); + + emit backwardAvailable(d->stack.count() > 1); + + if (!d->forwardStack.isEmpty() && d->forwardStack.top().url == url) { + d->forwardStack.pop(); + emit forwardAvailable(d->forwardStack.count() > 0); + } else { + d->forwardStack.clear(); + emit forwardAvailable(false); + } + + emit historyChanged(); +} + +/*! + \fn void QTextBrowser::backwardAvailable(bool available) + + This signal is emitted when the availability of backward() + changes. \a available is false when the user is at home(); + otherwise it is true. +*/ + +/*! + \fn void QTextBrowser::forwardAvailable(bool available) + + This signal is emitted when the availability of forward() changes. + \a available is true after the user navigates backward() and false + when the user navigates or goes forward(). +*/ + +/*! + \fn void QTextBrowser::historyChanged() + \since 4.4 + + This signal is emitted when the history changes. + + \sa historyTitle(), historyUrl() +*/ + +/*! + \fn void QTextBrowser::sourceChanged(const QUrl &src) + + This signal is emitted when the source has changed, \a src + being the new source. + + Source changes happen both programmatically when calling + setSource(), forward(), backword() or home() or when the user + clicks on links or presses the equivalent key sequences. +*/ + +/*! \fn void QTextBrowser::highlighted(const QUrl &link) + + This signal is emitted when the user has selected but not + activated an anchor in the document. The URL referred to by the + anchor is passed in \a link. +*/ + +/*! \fn void QTextBrowser::highlighted(const QString &link) + \overload + + Convenience signal that allows connecting to a slot + that takes just a QString, like for example QStatusBar's + message(). +*/ + + +/*! + \fn void QTextBrowser::anchorClicked(const QUrl &link) + + This signal is emitted when the user clicks an anchor. The + URL referred to by the anchor is passed in \a link. + + Note that the browser will automatically handle navigation to the + location specified by \a link unless the openLinks property + is set to false or you call setSource() in a slot connected. + This mechanism is used to override the default navigation features of the browser. +*/ + +/*! + Changes the document displayed to the previous document in the + list of documents built by navigating links. Does nothing if there + is no previous document. + + \sa forward(), backwardAvailable() +*/ +void QTextBrowser::backward() +{ + Q_D(QTextBrowser); + if (d->stack.count() <= 1) + return; + + // Update the history entry + d->forwardStack.push(d->createHistoryEntry()); + d->stack.pop(); // throw away the old version of the current entry + d->restoreHistoryEntry(d->stack.top()); // previous entry + emit backwardAvailable(d->stack.count() > 1); + emit forwardAvailable(true); + emit historyChanged(); +} + +/*! + Changes the document displayed to the next document in the list of + documents built by navigating links. Does nothing if there is no + next document. + + \sa backward(), forwardAvailable() +*/ +void QTextBrowser::forward() +{ + Q_D(QTextBrowser); + if (d->forwardStack.isEmpty()) + return; + if (!d->stack.isEmpty()) { + // Update the history entry + d->stack.top() = d->createHistoryEntry(); + } + d->stack.push(d->forwardStack.pop()); + d->restoreHistoryEntry(d->stack.top()); + emit backwardAvailable(true); + emit forwardAvailable(!d->forwardStack.isEmpty()); + emit historyChanged(); +} + +/*! + Changes the document displayed to be the first document from + the history. +*/ +void QTextBrowser::home() +{ + Q_D(QTextBrowser); + if (d->home.isValid()) + setSource(d->home); +} + +/*! + The event \a ev is used to provide the following keyboard shortcuts: + \table + \header \i Keypress \i Action + \row \i Alt+Left Arrow \i \l backward() + \row \i Alt+Right Arrow \i \l forward() + \row \i Alt+Up Arrow \i \l home() + \endtable +*/ +void QTextBrowser::keyPressEvent(QKeyEvent *ev) +{ +#ifdef QT_KEYPAD_NAVIGATION + Q_D(QTextBrowser); + switch (ev->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus()) { + setEditFocus(true); + return; + } else { + QTextCursor cursor = d->control->textCursor(); + QTextCharFormat charFmt = cursor.charFormat(); + if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) { + ev->accept(); + return; + } + } + } + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled()) { + if (hasEditFocus()) { + setEditFocus(false); + ev->accept(); + return; + } + } + QTextEdit::keyPressEvent(ev); + return; + default: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + ev->ignore(); + return; + } + } +#endif + + if (ev->modifiers() & Qt::AltModifier) { + switch (ev->key()) { + case Qt::Key_Right: + forward(); + ev->accept(); + return; + case Qt::Key_Left: + backward(); + ev->accept(); + return; + case Qt::Key_Up: + home(); + ev->accept(); + return; + } + } +#ifdef QT_KEYPAD_NAVIGATION + else { + if (ev->key() == Qt::Key_Up) { + d->keypadMove(false); + return; + } else if (ev->key() == Qt::Key_Down) { + d->keypadMove(true); + return; + } + } +#endif + QTextEdit::keyPressEvent(ev); +} + +/*! + \reimp +*/ +void QTextBrowser::mouseMoveEvent(QMouseEvent *e) +{ + QTextEdit::mouseMoveEvent(e); +} + +/*! + \reimp +*/ +void QTextBrowser::mousePressEvent(QMouseEvent *e) +{ + QTextEdit::mousePressEvent(e); +} + +/*! + \reimp +*/ +void QTextBrowser::mouseReleaseEvent(QMouseEvent *e) +{ + QTextEdit::mouseReleaseEvent(e); +} + +/*! + \reimp +*/ +void QTextBrowser::focusOutEvent(QFocusEvent *ev) +{ +#ifndef QT_NO_CURSOR + Q_D(QTextBrowser); + d->viewport->setCursor((!(d->control->textInteractionFlags() & Qt::TextEditable)) ? d->oldCursor : Qt::IBeamCursor); +#endif + QTextEdit::focusOutEvent(ev); +} + +/*! + \reimp +*/ +bool QTextBrowser::focusNextPrevChild(bool next) +{ + Q_D(QTextBrowser); + if (d->control->setFocusToNextOrPreviousAnchor(next)) { +#ifdef QT_KEYPAD_NAVIGATION + // Might need to synthesize a highlight event. + if (d->prevFocus != d->control->textCursor() && d->control->textCursor().hasSelection()) { + const QString href = d->control->anchorAtCursor(); + QUrl url = d->resolveUrl(href); + emit highlighted(url); + emit highlighted(url.toString()); + } + d->prevFocus = d->control->textCursor(); +#endif + return true; + } else { +#ifdef QT_KEYPAD_NAVIGATION + // We assume we have no highlight now. + emit highlighted(QUrl()); + emit highlighted(QString()); +#endif + } + return QTextEdit::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +void QTextBrowser::paintEvent(QPaintEvent *e) +{ + Q_D(QTextBrowser); + QPainter p(d->viewport); + d->paint(&p, e); +} + +/*! + This function is called when the document is loaded and for + each image in the document. The \a type indicates the type of resource + to be loaded. An invalid QVariant is returned if the resource cannot be + loaded. + + The default implementation ignores \a type and tries to locate + the resources by interpreting \a name as a file name. If it is + not an absolute path it tries to find the file in the paths of + the \l searchPaths property and in the same directory as the + current source. On success, the result is a QVariant that stores + a QByteArray with the contents of the file. + + If you reimplement this function, you can return other QVariant + types. The table below shows which variant types are supported + depending on the resource type: + + \table + \header \i ResourceType \i QVariant::Type + \row \i QTextDocument::HtmlResource \i QString or QByteArray + \row \i QTextDocument::ImageResource \i QImage, QPixmap or QByteArray + \row \i QTextDocument::StyleSheetResource \i QString or QByteArray + \endtable +*/ +QVariant QTextBrowser::loadResource(int /*type*/, const QUrl &name) +{ + Q_D(QTextBrowser); + + QByteArray data; + QString fileName = d->findFile(d->resolveUrl(name)); + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + data = f.readAll(); + f.close(); + } else { + return QVariant(); + } + + return data; +} + +/*! + \since 4.2 + + Returns true if the text browser can go backward in the document history + using backward(). + + \sa backwardAvailable(), backward() +*/ +bool QTextBrowser::isBackwardAvailable() const +{ + Q_D(const QTextBrowser); + return d->stack.count() > 1; +} + +/*! + \since 4.2 + + Returns true if the text browser can go forward in the document history + using forward(). + + \sa forwardAvailable(), forward() +*/ +bool QTextBrowser::isForwardAvailable() const +{ + Q_D(const QTextBrowser); + return !d->forwardStack.isEmpty(); +} + +/*! + \since 4.2 + + Clears the history of visited documents and disables the forward and + backward navigation. + + \sa backward(), forward() +*/ +void QTextBrowser::clearHistory() +{ + Q_D(QTextBrowser); + d->forwardStack.clear(); + if (!d->stack.isEmpty()) { + QTextBrowserPrivate::HistoryEntry historyEntry = d->stack.top(); + d->stack.resize(0); + d->stack.push(historyEntry); + d->home = historyEntry.url; + } + emit forwardAvailable(false); + emit backwardAvailable(false); + emit historyChanged(); +} + +/*! + Returns the url of the HistoryItem. + + \table + \header \i Input \i Return + \row \i \a{i} < 0 \i \l backward() history + \row \i\a{i} == 0 \i current, see QTextBrowser::source() + \row \i \a{i} > 0 \i \l forward() history + \endtable + + \since 4.4 +*/ +QUrl QTextBrowser::historyUrl(int i) const +{ + Q_D(const QTextBrowser); + return d->history(i).url; +} + +/*! + Returns the documentTitle() of the HistoryItem. + + \table + \header \i Input \i Return + \row \i \a{i} < 0 \i \l backward() history + \row \i \a{i} == 0 \i current, see QTextBrowser::source() + \row \i \a{i} > 0 \i \l forward() history + \endtable + + \snippet doc/src/snippets/code/src_gui_widgets_qtextbrowser.cpp 0 + + \since 4.4 +*/ +QString QTextBrowser::historyTitle(int i) const +{ + Q_D(const QTextBrowser); + return d->history(i).title; +} + + +/*! + Returns the number of locations forward in the history. + + \since 4.4 +*/ +int QTextBrowser::forwardHistoryCount() const +{ + Q_D(const QTextBrowser); + return d->forwardStack.count(); +} + +/*! + Returns the number of locations backward in the history. + + \since 4.4 +*/ +int QTextBrowser::backwardHistoryCount() const +{ + Q_D(const QTextBrowser); + return d->stack.count()-1; +} + +/*! + \property QTextBrowser::openExternalLinks + \since 4.2 + + Specifies whether QTextBrowser should automatically open links to external + sources using QDesktopServices::openUrl() instead of emitting the + anchorClicked signal. Links are considered external if their scheme is + neither file or qrc. + + The default value is false. +*/ +bool QTextBrowser::openExternalLinks() const +{ + Q_D(const QTextBrowser); + return d->openExternalLinks; +} + +void QTextBrowser::setOpenExternalLinks(bool open) +{ + Q_D(QTextBrowser); + d->openExternalLinks = open; +} + +/*! + \property QTextBrowser::openLinks + \since 4.3 + + This property specifies whether QTextBrowser should automatically open links the user tries to + activate by mouse or keyboard. + + Regardless of the value of this property the anchorClicked signal is always emitted. + + The default value is true. +*/ + +bool QTextBrowser::openLinks() const +{ + Q_D(const QTextBrowser); + return d->openLinks; +} + +void QTextBrowser::setOpenLinks(bool open) +{ + Q_D(QTextBrowser); + d->openLinks = open; +} + +/*! \reimp */ +bool QTextBrowser::event(QEvent *e) +{ + return QTextEdit::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qtextbrowser.cpp" + +#endif // QT_NO_TEXTBROWSER diff --git a/src/gui/widgets/qtextbrowser.h b/src/gui/widgets/qtextbrowser.h new file mode 100644 index 0000000..1cad463 --- /dev/null +++ b/src/gui/widgets/qtextbrowser.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTBROWSER_H +#define QTEXTBROWSER_H + +#include <QtGui/qtextedit.h> +#include <QtCore/qurl.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TEXTBROWSER + +class QTextBrowserPrivate; + +class Q_GUI_EXPORT QTextBrowser : public QTextEdit +{ + Q_OBJECT + + Q_PROPERTY(QUrl source READ source WRITE setSource) + Q_OVERRIDE(bool modified SCRIPTABLE false) + Q_OVERRIDE(bool readOnly DESIGNABLE false SCRIPTABLE false) + Q_OVERRIDE(bool undoRedoEnabled DESIGNABLE false SCRIPTABLE false) + Q_PROPERTY(QStringList searchPaths READ searchPaths WRITE setSearchPaths) + Q_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) + Q_PROPERTY(bool openLinks READ openLinks WRITE setOpenLinks) + +public: + explicit QTextBrowser(QWidget* parent = 0); + virtual ~QTextBrowser(); + + QUrl source() const; + + QStringList searchPaths() const; + void setSearchPaths(const QStringList &paths); + + virtual QVariant loadResource(int type, const QUrl &name); + + bool isBackwardAvailable() const; + bool isForwardAvailable() const; + void clearHistory(); + QString historyTitle(int) const; + QUrl historyUrl(int) const; + int backwardHistoryCount() const; + int forwardHistoryCount() const; + + bool openExternalLinks() const; + void setOpenExternalLinks(bool open); + + bool openLinks() const; + void setOpenLinks(bool open); + +public Q_SLOTS: + virtual void setSource(const QUrl &name); + virtual void backward(); + virtual void forward(); + virtual void home(); + virtual void reload(); + +Q_SIGNALS: + void backwardAvailable(bool); + void forwardAvailable(bool); + void historyChanged(); + void sourceChanged(const QUrl &); + void highlighted(const QUrl &); + void highlighted(const QString &); + void anchorClicked(const QUrl &); + +protected: + bool event(QEvent *e); + virtual void keyPressEvent(QKeyEvent *ev); + virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void mousePressEvent(QMouseEvent *ev); + virtual void mouseReleaseEvent(QMouseEvent *ev); + virtual void focusOutEvent(QFocusEvent *ev); + virtual bool focusNextPrevChild(bool next); + virtual void paintEvent(QPaintEvent *e); + +#if defined(QT3_SUPPORT) +public: + QT3_SUPPORT_CONSTRUCTOR QTextBrowser(QWidget *parent, const char *name); +#endif + +private: + Q_DISABLE_COPY(QTextBrowser) + Q_DECLARE_PRIVATE(QTextBrowser) + Q_PRIVATE_SLOT(d_func(), void _q_documentModified()) + Q_PRIVATE_SLOT(d_func(), void _q_activateAnchor(const QString &)) + Q_PRIVATE_SLOT(d_func(), void _q_highlightLink(const QString &)) +}; + +#endif // QT_NO_TEXTBROWSER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTBROWSER_H diff --git a/src/gui/widgets/qtextedit.cpp b/src/gui/widgets/qtextedit.cpp new file mode 100644 index 0000000..b239e32 --- /dev/null +++ b/src/gui/widgets/qtextedit.cpp @@ -0,0 +1,2783 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextedit_p.h" +#include "qlineedit.h" +#include "qtextbrowser.h" + +#ifndef QT_NO_TEXTEDIT +#include <qfont.h> +#include <qpainter.h> +#include <qevent.h> +#include <qdebug.h> +#include <qmime.h> +#include <qdrag.h> +#include <qclipboard.h> +#include <qmenu.h> +#include <qstyle.h> +#include <qtimer.h> +#include "private/qtextdocumentlayout_p.h" +#include "qtextdocument.h" +#include "private/qtextdocument_p.h" +#include "qtextlist.h" +#include "private/qtextcontrol_p.h" + +#include <qtextformat.h> +#include <qdatetime.h> +#include <qapplication.h> +#include <limits.h> +#include <qtexttable.h> +#include <qvariant.h> + +#include <qinputcontext.h> +#endif + +QT_BEGIN_NAMESPACE + + +#ifndef QT_NO_TEXTEDIT + +class QTextEditControl : public QTextControl +{ +public: + inline QTextEditControl(QObject *parent) : QTextControl(parent) {} + + virtual QMimeData *createMimeDataFromSelection() const { + QTextEdit *ed = qobject_cast<QTextEdit *>(parent()); + if (!ed) + return QTextControl::createMimeDataFromSelection(); + return ed->createMimeDataFromSelection(); + } + virtual bool canInsertFromMimeData(const QMimeData *source) const { + QTextEdit *ed = qobject_cast<QTextEdit *>(parent()); + if (!ed) + return QTextControl::canInsertFromMimeData(source); + return ed->canInsertFromMimeData(source); + } + virtual void insertFromMimeData(const QMimeData *source) { + QTextEdit *ed = qobject_cast<QTextEdit *>(parent()); + if (!ed) + QTextControl::insertFromMimeData(source); + else + ed->insertFromMimeData(source); + } +}; + +QTextEditPrivate::QTextEditPrivate() + : control(0), + autoFormatting(QTextEdit::AutoNone), tabChangesFocus(false), + lineWrap(QTextEdit::WidgetWidth), lineWrapColumnOrWidth(0), + wordWrap(QTextOption::WrapAtWordBoundaryOrAnywhere), textFormat(Qt::AutoText) +{ + ignoreAutomaticScrollbarAdjustment = false; + preferRichText = false; + showCursorOnInitialShow = true; + inDrag = false; +} + +void QTextEditPrivate::createAutoBulletList() +{ + QTextCursor cursor = control->textCursor(); + cursor.beginEditBlock(); + + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextListFormat listFmt; + listFmt.setStyle(QTextListFormat::ListDisc); + listFmt.setIndent(blockFmt.indent() + 1); + + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + + cursor.createList(listFmt); + + cursor.endEditBlock(); + control->setTextCursor(cursor); +} + +void QTextEditPrivate::init(const QString &html) +{ + Q_Q(QTextEdit); + control = new QTextEditControl(q); + control->setPalette(q->palette()); + + QObject::connect(control, SIGNAL(microFocusChanged()), q, SLOT(updateMicroFocus())); + QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), q, SLOT(_q_adjustScrollbars())); + QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(_q_repaintContents(QRectF))); + QObject::connect(control, SIGNAL(visibilityRequest(QRectF)), q, SLOT(_q_ensureVisible(QRectF))); + QObject::connect(control, SIGNAL(currentCharFormatChanged(QTextCharFormat)), + q, SLOT(_q_currentCharFormatChanged(QTextCharFormat))); + + QObject::connect(control, SIGNAL(textChanged()), q, SIGNAL(textChanged())); + QObject::connect(control, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); + QObject::connect(control, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); + QObject::connect(control, SIGNAL(copyAvailable(bool)), q, SIGNAL(copyAvailable(bool))); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged())); + + QTextDocument *doc = control->document(); + // set a null page size initially to avoid any relayouting until the textedit + // is shown. relayoutDocument() will take care of setting the page size to the + // viewport dimensions later. + doc->setPageSize(QSize(0, 0)); + doc->documentLayout()->setPaintDevice(viewport); + doc->setDefaultFont(q->font()); + doc->setUndoRedoEnabled(false); // flush undo buffer. + doc->setUndoRedoEnabled(true); + + if (!html.isEmpty()) + control->setHtml(html); + + hbar->setSingleStep(20); + vbar->setSingleStep(20); + + viewport->setBackgroundRole(QPalette::Base); + q->setAcceptDrops(true); + q->setFocusPolicy(Qt::WheelFocus); + q->setAttribute(Qt::WA_KeyCompression); + q->setAttribute(Qt::WA_InputMethodEnabled); + +#ifndef QT_NO_CURSOR + viewport->setCursor(Qt::IBeamCursor); +#endif +} + +void QTextEditPrivate::_q_repaintContents(const QRectF &contentsRect) +{ + if (!contentsRect.isValid()) { + viewport->update(); + return; + } + const int xOffset = horizontalOffset(); + const int yOffset = verticalOffset(); + const QRectF visibleRect(xOffset, yOffset, viewport->width(), viewport->height()); + + QRect r = contentsRect.intersected(visibleRect).toAlignedRect(); + if (r.isEmpty()) + return; + + r.translate(-xOffset, -yOffset); + viewport->update(r); +} + +void QTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode) +{ + QTextCursor cursor = control->textCursor(); + bool moved = false; + qreal lastY = control->cursorRect(cursor).top(); + qreal distance = 0; + // move using movePosition to keep the cursor's x + do { + qreal y = control->cursorRect(cursor).top(); + distance += qAbs(y - lastY); + lastY = y; + moved = cursor.movePosition(op, moveMode); + } while (moved && distance < viewport->height()); + + if (moved) { + if (op == QTextCursor::Up) { + cursor.movePosition(QTextCursor::Down, moveMode); + vbar->triggerAction(QAbstractSlider::SliderPageStepSub); + } else { + cursor.movePosition(QTextCursor::Up, moveMode); + vbar->triggerAction(QAbstractSlider::SliderPageStepAdd); + } + } + control->setTextCursor(cursor); +} + +#ifndef QT_NO_SCROLLBAR +static QSize documentSize(QTextControl *control) +{ + QTextDocument *doc = control->document(); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + + QSize docSize; + + if (QTextDocumentLayout *tlayout = qobject_cast<QTextDocumentLayout *>(layout)) { + docSize = tlayout->dynamicDocumentSize().toSize(); + int percentageDone = tlayout->layoutStatus(); + // extrapolate height + if (percentageDone > 0) + docSize.setHeight(docSize.height() * 100 / percentageDone); + } else { + docSize = layout->documentSize().toSize(); + } + + return docSize; +} + +void QTextEditPrivate::_q_adjustScrollbars() +{ + if (ignoreAutomaticScrollbarAdjustment) + return; + ignoreAutomaticScrollbarAdjustment = true; // avoid recursion, #106108 + + QSize viewportSize = viewport->size(); + QSize docSize = documentSize(control); + + // due to the recursion guard we have to repeat this step a few times, + // as adding/removing a scroll bar will cause the document or viewport + // size to change + // ideally we should loop until the viewport size and doc size stabilize, + // but in corner cases they might fluctuate, so we need to limit the + // number of iterations + for (int i = 0; i < 4; ++i) { + hbar->setRange(0, docSize.width() - viewportSize.width()); + hbar->setPageStep(viewportSize.width()); + + vbar->setRange(0, docSize.height() - viewportSize.height()); + vbar->setPageStep(viewportSize.height()); + + // if we are in left-to-right mode widening the document due to + // lazy layouting does not require a repaint. If in right-to-left + // the scroll bar has the value zero and it visually has the maximum + // value (it is visually at the right), then widening the document + // keeps it at value zero but visually adjusts it to the new maximum + // on the right, hence we need an update. + if (q_func()->isRightToLeft()) + viewport->update(); + + _q_showOrHideScrollBars(); + + const QSize oldViewportSize = viewportSize; + const QSize oldDocSize = docSize; + + // make sure the document is layouted if the viewport width changes + viewportSize = viewport->size(); + if (viewportSize.width() != oldViewportSize.width()) + relayoutDocument(); + + docSize = documentSize(control); + if (viewportSize == oldViewportSize && docSize == oldDocSize) + break; + } + ignoreAutomaticScrollbarAdjustment = false; +} +#endif + +// rect is in content coordinates +void QTextEditPrivate::_q_ensureVisible(const QRectF &_rect) +{ + const QRect rect = _rect.toRect(); + if ((vbar->isVisible() && vbar->maximum() < rect.bottom()) + || (hbar->isVisible() && hbar->maximum() < rect.right())) + _q_adjustScrollbars(); + const int visibleWidth = viewport->width(); + const int visibleHeight = viewport->height(); + const bool rtl = q_func()->isRightToLeft(); + + if (rect.x() < horizontalOffset()) { + if (rtl) + hbar->setValue(hbar->maximum() - rect.x()); + else + hbar->setValue(rect.x()); + } else if (rect.x() + rect.width() > horizontalOffset() + visibleWidth) { + if (rtl) + hbar->setValue(hbar->maximum() - (rect.x() + rect.width() - visibleWidth)); + else + hbar->setValue(rect.x() + rect.width() - visibleWidth); + } + + if (rect.y() < verticalOffset()) + vbar->setValue(rect.y()); + else if (rect.y() + rect.height() > verticalOffset() + visibleHeight) + vbar->setValue(rect.y() + rect.height() - visibleHeight); +} + +/*! + \class QTextEdit + \brief The QTextEdit class provides a widget that is used to edit and display + both plain and rich text. + + \ingroup text + \mainclass + + \tableofcontents + + \section1 Introduction and Concepts + + QTextEdit is an advanced WYSIWYG viewer/editor supporting rich + text formatting using HTML-style tags. It is optimized to handle + large documents and to respond quickly to user input. + + QTextEdit works on paragraphs and characters. A paragraph is a + formatted string which is word-wrapped to fit into the width of + the widget. By default when reading plain text, one newline + signifies a paragraph. A document consists of zero or more + paragraphs. The words in the paragraph are aligned in accordance + with the paragraph's alignment. Paragraphs are separated by hard + line breaks. Each character within a paragraph has its own + attributes, for example, font and color. + + QTextEdit can display images, lists and tables. If the text is + too large to view within the text edit's viewport, scroll bars will + appear. The text edit can load both plain text and HTML files (a + subset of HTML 3.2 and 4). + + If you just need to display a small piece of rich text use QLabel. + + The rich text support in Qt is designed to provide a fast, portable and + efficient way to add reasonable online help facilities to + applications, and to provide a basis for rich text editors. If + you find the HTML support insufficient for your needs you may consider + the use of QtWebKit, which provides a full-featured web browser + widget. + + The shape of the mouse cursor on a QTextEdit is Qt::IBeamCursor by default. + It can be changed through the viewport()'s cursor property. + + \section1 Using QTextEdit as a Display Widget + + QTextEdit can display a large HTML subset, including tables and + images. + + The text is set or replaced using setHtml() which deletes any + existing text and replaces it with the text passed in the + setHtml() call. If you call setHtml() with legacy HTML, and then + call toHtml(), the text that is returned may have different markup, + but will render the same. The entire text can be deleted with clear(). + + Text itself can be inserted using the QTextCursor class or using the + convenience functions insertHtml(), insertPlainText(), append() or + paste(). QTextCursor is also able to insert complex objects like tables + or lists into the document, and it deals with creating selections + and applying changes to selected text. + + By default the text edit wraps words at whitespace to fit within + the text edit widget. The setLineWrapMode() function is used to + specify the kind of line wrap you want, or \l NoWrap if you don't + want any wrapping. Call setLineWrapMode() to set a fixed pixel width + \l FixedPixelWidth, or character column (e.g. 80 column) \l + FixedColumnWidth with the pixels or columns specified with + setLineWrapColumnOrWidth(). If you use word wrap to the widget's width + \l WidgetWidth, you can specify whether to break on whitespace or + anywhere with setWordWrapMode(). + + The find() function can be used to find and select a given string + within the text. + + If you want to limit the total number of paragraphs in a QTextEdit, + as it is for example open useful in a log viewer, then you can use + QTextDocument's maximumBlockCount property for that. + + \section2 Read-only Key Bindings + + When QTextEdit is used read-only the key bindings are limited to + navigation, and text may only be selected with the mouse: + \table + \header \i Keypresses \i Action + \row \i Up \i Moves one line up. + \row \i Down \i Moves one line down. + \row \i Left \i Moves one character to the left. + \row \i Right \i Moves one character to the right. + \row \i PageUp \i Moves one (viewport) page up. + \row \i PageDown \i Moves one (viewport) page down. + \row \i Home \i Moves to the beginning of the text. + \row \i End \i Moves to the end of the text. + \row \i Alt+Wheel + \i Scrolls the page horizontally (the Wheel is the mouse wheel). + \row \i Ctrl+Wheel \i Zooms the text. + \row \i Ctrl+A \i Selects all text. + \endtable + + The text edit may be able to provide some meta-information. For + example, the documentTitle() function will return the text from + within HTML \c{<title>} tags. + + \section1 Using QTextEdit as an Editor + + All the information about using QTextEdit as a display widget also + applies here. + + The current char format's attributes are set with setFontItalic(), + setFontWeight(), setFontUnderline(), setFontFamily(), + setFontPointSize(), setTextColor() and setCurrentFont(). The current + paragraph's alignment is set with setAlignment(). + + Selection of text is handled by the QTextCursor class, which provides + functionality for creating selections, retrieving the text contents or + deleting selections. You can retrieve the object that corresponds with + the user-visible cursor using the textCursor() method. If you want to set + a selection in QTextEdit just create one on a QTextCursor object and + then make that cursor the visible cursor using setTextCursor(). The selection + can be copied to the clipboard with copy(), or cut to the clipboard with + cut(). The entire text can be selected using selectAll(). + + When the cursor is moved and the underlying formatting attributes change, + the currentCharFormatChanged() signal is emitted to reflect the new attributes + at the new cursor position. + + QTextEdit holds a QTextDocument object which can be retrieved using the + document() method. You can also set your own document object using setDocument(). + QTextDocument emits a textChanged() signal if the text changes and it also + provides a isModified() function which will return true if the text has been + modified since it was either loaded or since the last call to setModified + with false as argument. In addition it provides methods for undo and redo. + + \section2 Drag and Drop + + QTextEdit also supports custom drag and drop behavior. By default, + QTextEdit will insert plain text, HTML and rich text when the user drops + data of these MIME types onto a document. Reimplement + canInsertFromMimeData() and insertFromMimeData() to add support for + additional MIME types. + + For example, to allow the user to drag and drop an image onto a QTextEdit, + you could the implement these functions in the following way: + + \snippet doc/src/snippets/textdocument-imagedrop/textedit.cpp 0 + + We add support for image MIME types by returning true. For all other + MIME types, we use the default implementation. + + \snippet doc/src/snippets/textdocument-imagedrop/textedit.cpp 1 + + We unpack the image from the QVariant held by the MIME source and insert + it into the document as a resource. + + \section2 Editing Key Bindings + + The list of key bindings which are implemented for editing: + \table + \header \i Keypresses \i Action + \row \i Backspace \i Deletes the character to the left of the cursor. + \row \i Delete \i Deletes the character to the right of the cursor. + \row \i Ctrl+C \i Copy the selected text to the clipboard. + \row \i Ctrl+Insert \i Copy the selected text to the clipboard. + \row \i Ctrl+K \i Deletes to the end of the line. + \row \i Ctrl+V \i Pastes the clipboard text into text edit. + \row \i Shift+Insert \i Pastes the clipboard text into text edit. + \row \i Ctrl+X \i Deletes the selected text and copies it to the clipboard. + \row \i Shift+Delete \i Deletes the selected text and copies it to the clipboard. + \row \i Ctrl+Z \i Undoes the last operation. + \row \i Ctrl+Y \i Redoes the last operation. + \row \i Left \i Moves the cursor one character to the left. + \row \i Ctrl+Left \i Moves the cursor one word to the left. + \row \i Right \i Moves the cursor one character to the right. + \row \i Ctrl+Right \i Moves the cursor one word to the right. + \row \i Up \i Moves the cursor one line up. + \row \i Down \i Moves the cursor one line down. + \row \i PageUp \i Moves the cursor one page up. + \row \i PageDown \i Moves the cursor one page down. + \row \i Home \i Moves the cursor to the beginning of the line. + \row \i Ctrl+Home \i Moves the cursor to the beginning of the text. + \row \i End \i Moves the cursor to the end of the line. + \row \i Ctrl+End \i Moves the cursor to the end of the text. + \row \i Alt+Wheel \i Scrolls the page horizontally (the Wheel is the mouse wheel). + \endtable + + To select (mark) text hold down the Shift key whilst pressing one + of the movement keystrokes, for example, \e{Shift+Right} + will select the character to the right, and \e{Shift+Ctrl+Right} will select the word to the right, etc. + + \sa QTextDocument, QTextCursor, {Application Example}, + {Syntax Highlighter Example}, {Rich Text Processing} +*/ + +/*! + \property QTextEdit::plainText + \since 4.3 + + This property gets and sets the text editor's contents as plain + text. Previous contents are removed and undo/redo history is reset + when the property is set. + + If the text edit has another content type, it will not be replaced + by plain text if you call toPlainText(). + + By default, for an editor with no contents, this property contains + an empty string. + + \sa html +*/ + +/*! + \property QTextEdit::undoRedoEnabled + \brief whether undo and redo are enabled + + Users are only able to undo or redo actions if this property is + true, and if there is an action that can be undone (or redone). +*/ + +/*! + \enum QTextEdit::LineWrapMode + + \value NoWrap + \value WidgetWidth + \value FixedPixelWidth + \value FixedColumnWidth +*/ + +/*! + \enum QTextEdit::AutoFormattingFlag + + \value AutoNone Don't do any automatic formatting. + \value AutoBulletList Automatically create bullet lists (e.g. when + the user enters an asterisk ('*') in the left most column, or + presses Enter in an existing list item. + \value AutoAll Apply all automatic formatting. Currently only + automatic bullet lists are supported. +*/ + +#ifdef QT3_SUPPORT +/*! + \enum QTextEdit::CursorAction + \compat + + \value MoveBackward + \value MoveForward + \value MoveWordBackward + \value MoveWordForward + \value MoveUp + \value MoveDown + \value MoveLineStart + \value MoveLineEnd + \value MoveHome + \value MoveEnd + \value MovePageUp + \value MovePageDown + + \omitvalue MovePgUp + \omitvalue MovePgDown +*/ +#endif + +/*! + Constructs an empty QTextEdit with parent \a + parent. +*/ +QTextEdit::QTextEdit(QWidget *parent) + : QAbstractScrollArea(*new QTextEditPrivate, parent) +{ + Q_D(QTextEdit); + d->init(); +} + +/*! + \internal +*/ +QTextEdit::QTextEdit(QTextEditPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + Q_D(QTextEdit); + d->init(); +} + +/*! + Constructs a QTextEdit with parent \a parent. The text edit will display + the text \a text. The text is interpreted as html. +*/ +QTextEdit::QTextEdit(const QString &text, QWidget *parent) + : QAbstractScrollArea(*new QTextEditPrivate, parent) +{ + Q_D(QTextEdit); + d->init(text); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QTextEdit::QTextEdit(QWidget *parent, const char *name) + : QAbstractScrollArea(*new QTextEditPrivate, parent) +{ + Q_D(QTextEdit); + d->init(); + setObjectName(QString::fromAscii(name)); +} +#endif + + +/*! + Destructor. +*/ +QTextEdit::~QTextEdit() +{ +} + +/*! + Returns the point size of the font of the current format. + + \sa setFontFamily() setCurrentFont() setFontPointSize() +*/ +qreal QTextEdit::fontPointSize() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().fontPointSize(); +} + +/*! + Returns the font family of the current format. + + \sa setFontFamily() setCurrentFont() setFontPointSize() +*/ +QString QTextEdit::fontFamily() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().fontFamily(); +} + +/*! + Returns the font weight of the current format. + + \sa setFontWeight() setCurrentFont() setFontPointSize() QFont::Weight +*/ +int QTextEdit::fontWeight() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().fontWeight(); +} + +/*! + Returns true if the font of the current format is underlined; otherwise returns + false. + + \sa setFontUnderline() +*/ +bool QTextEdit::fontUnderline() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().fontUnderline(); +} + +/*! + Returns true if the font of the current format is italic; otherwise returns + false. + + \sa setFontItalic() +*/ +bool QTextEdit::fontItalic() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().fontItalic(); +} + +/*! + Returns the text color of the current format. + + \sa setTextColor() +*/ +QColor QTextEdit::textColor() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().foreground().color(); +} + +/*! + \since 4.4 + + Returns the text background color of the current format. + + \sa setTextBackgroundColor() +*/ +QColor QTextEdit::textBackgroundColor() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().background().color(); +} + +/*! + Returns the font of the current format. + + \sa setCurrentFont() setFontFamily() setFontPointSize() +*/ +QFont QTextEdit::currentFont() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().charFormat().font(); +} + +/*! + Sets the alignment of the current paragraph to \a a. Valid + alignments are Qt::AlignLeft, Qt::AlignRight, + Qt::AlignJustify and Qt::AlignCenter (which centers + horizontally). +*/ +void QTextEdit::setAlignment(Qt::Alignment a) +{ + Q_D(QTextEdit); + QTextBlockFormat fmt; + fmt.setAlignment(a); + QTextCursor cursor = d->control->textCursor(); + cursor.mergeBlockFormat(fmt); + d->control->setTextCursor(cursor); +} + +/*! + Returns the alignment of the current paragraph. + + \sa setAlignment() +*/ +Qt::Alignment QTextEdit::alignment() const +{ + Q_D(const QTextEdit); + return d->control->textCursor().blockFormat().alignment(); +} + +/*! + Makes \a document the new document of the text editor. + + \note The editor \e{does not take ownership of the document} unless it + is the document's parent object. The parent object of the provided document + remains the owner of the object. + + If the current document is a child of the text editor, then it is deleted. + + \sa document() +*/ +void QTextEdit::setDocument(QTextDocument *document) +{ + Q_D(QTextEdit); + d->control->setDocument(document); + d->updateDefaultTextOption(); + d->relayoutDocument(); +} + +/*! + Returns a pointer to the underlying document. + + \sa setDocument() +*/ +QTextDocument *QTextEdit::document() const +{ + Q_D(const QTextEdit); + return d->control->document(); +} + +/*! + Sets the visible \a cursor. +*/ +void QTextEdit::setTextCursor(const QTextCursor &cursor) +{ + Q_D(QTextEdit); + d->control->setTextCursor(cursor); +} + +/*! + Returns a copy of the QTextCursor that represents the currently visible cursor. + Note that changes on the returned cursor do not affect QTextEdit's cursor; use + setTextCursor() to update the visible cursor. + */ +QTextCursor QTextEdit::textCursor() const +{ + Q_D(const QTextEdit); + return d->control->textCursor(); +} + +/*! + Sets the font family of the current format to \a fontFamily. + + \sa fontFamily() setCurrentFont() +*/ +void QTextEdit::setFontFamily(const QString &fontFamily) +{ + QTextCharFormat fmt; + fmt.setFontFamily(fontFamily); + mergeCurrentCharFormat(fmt); +} + +/*! + Sets the point size of the current format to \a s. + + Note that if \a s is zero or negative, the behavior of this + function is not defined. + + \sa fontPointSize() setCurrentFont() setFontFamily() +*/ +void QTextEdit::setFontPointSize(qreal s) +{ + QTextCharFormat fmt; + fmt.setFontPointSize(s); + mergeCurrentCharFormat(fmt); +} + +/*! + \fn void QTextEdit::setFontWeight(int weight) + + Sets the font weight of the current format to the given \a weight, + where the value used is in the range defined by the QFont::Weight + enum. + + \sa fontWeight(), setCurrentFont(), setFontFamily() +*/ +void QTextEdit::setFontWeight(int w) +{ + QTextCharFormat fmt; + fmt.setFontWeight(w); + mergeCurrentCharFormat(fmt); +} + +/*! + If \a underline is true, sets the current format to underline; + otherwise sets the current format to non-underline. + + \sa fontUnderline() +*/ +void QTextEdit::setFontUnderline(bool underline) +{ + QTextCharFormat fmt; + fmt.setFontUnderline(underline); + mergeCurrentCharFormat(fmt); +} + +/*! + If \a italic is true, sets the current format to italic; + otherwise sets the current format to non-italic. + + \sa fontItalic() +*/ +void QTextEdit::setFontItalic(bool italic) +{ + QTextCharFormat fmt; + fmt.setFontItalic(italic); + mergeCurrentCharFormat(fmt); +} + +/*! + Sets the text color of the current format to \a c. + + \sa textColor() +*/ +void QTextEdit::setTextColor(const QColor &c) +{ + QTextCharFormat fmt; + fmt.setForeground(QBrush(c)); + mergeCurrentCharFormat(fmt); +} + +/*! + \since 4.4 + + Sets the text background color of the current format to \a c. + + \sa textBackgroundColor() +*/ +void QTextEdit::setTextBackgroundColor(const QColor &c) +{ + QTextCharFormat fmt; + fmt.setBackground(QBrush(c)); + mergeCurrentCharFormat(fmt); +} + +/*! + Sets the font of the current format to \a f. + + \sa currentFont() setFontPointSize() setFontFamily() +*/ +void QTextEdit::setCurrentFont(const QFont &f) +{ + QTextCharFormat fmt; + fmt.setFont(f); + mergeCurrentCharFormat(fmt); +} + +/*! + \since 4.2 + + Undoes the last operation. + + If there is no operation to undo, i.e. there is no undo step in + the undo/redo history, nothing happens. + + \sa redo() +*/ +void QTextEdit::undo() +{ + Q_D(QTextEdit); + d->control->undo(); +} + +void QTextEdit::redo() +{ + Q_D(QTextEdit); + d->control->redo(); +} + +/*! + \fn void QTextEdit::undo() const + \fn void QTextEdit::redo() const + \overload + + Use the non-const overload instead. +*/ + +/*! + \fn void QTextEdit::redo() + \since 4.2 + + Redoes the last operation. + + If there is no operation to redo, i.e. there is no redo step in + the undo/redo history, nothing happens. + + \sa undo() +*/ + +#ifndef QT_NO_CLIPBOARD +/*! + Copies the selected text to the clipboard and deletes it from + the text edit. + + If there is no selected text nothing happens. + + \sa copy() paste() +*/ + +void QTextEdit::cut() +{ + Q_D(QTextEdit); + d->control->cut(); +} + +/*! + Copies any selected text to the clipboard. + + \sa copyAvailable() +*/ + +void QTextEdit::copy() +{ + Q_D(QTextEdit); + d->control->copy(); +} + +/*! + Pastes the text from the clipboard into the text edit at the + current cursor position. + + If there is no text in the clipboard nothing happens. + + To change the behavior of this function, i.e. to modify what + QTextEdit can paste and how it is being pasted, reimplement the + virtual canInsertFromMimeData() and insertFromMimeData() + functions. + + \sa cut() copy() +*/ + +void QTextEdit::paste() +{ + Q_D(QTextEdit); + d->control->paste(); +} +#endif + +/*! + Deletes all the text in the text edit. + + Note that the undo/redo history is cleared by this function. + + \sa cut() setPlainText() setHtml() +*/ +void QTextEdit::clear() +{ + Q_D(QTextEdit); + // clears and sets empty content + d->control->clear(); +} + + +/*! + Selects all text. + + \sa copy() cut() textCursor() + */ +void QTextEdit::selectAll() +{ + Q_D(QTextEdit); + d->control->selectAll(); +} + +/*! \internal +*/ +bool QTextEdit::event(QEvent *e) +{ + Q_D(QTextEdit); +#ifndef QT_NO_CONTEXTMENU + if (e->type() == QEvent::ContextMenu + && static_cast<QContextMenuEvent *>(e)->reason() == QContextMenuEvent::Keyboard) { + Q_D(QTextEdit); + ensureCursorVisible(); + const QPoint cursorPos = cursorRect().center(); + QContextMenuEvent ce(QContextMenuEvent::Keyboard, cursorPos, d->viewport->mapToGlobal(cursorPos)); + ce.setAccepted(e->isAccepted()); + const bool result = QAbstractScrollArea::event(&ce); + e->setAccepted(ce.isAccepted()); + return result; + } else if (e->type() == QEvent::ShortcutOverride + || e->type() == QEvent::ToolTip) { + d->sendControlEvent(e); + } +#endif // QT_NO_CONTEXTMENU +#ifdef QT_KEYPAD_NAVIGATION + if (e->type() == QEvent::EnterEditFocus || e->type() == QEvent::LeaveEditFocus) { + if (QApplication::keypadNavigationEnabled()) + d->sendControlEvent(e); + } +#endif + return QAbstractScrollArea::event(e); +} + +/*! \internal +*/ + +void QTextEdit::timerEvent(QTimerEvent *e) +{ + Q_D(QTextEdit); + if (e->timerId() == d->autoScrollTimer.timerId()) { + QRect visible = d->viewport->rect(); + QPoint pos; + if (d->inDrag) { + pos = d->autoScrollDragPos; + visible.adjust(qMin(visible.width()/3,20), qMin(visible.height()/3,20), + -qMin(visible.width()/3,20), -qMin(visible.height()/3,20)); + } else { + const QPoint globalPos = QCursor::pos(); + pos = d->viewport->mapFromGlobal(globalPos); + QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mouseMoveEvent(&ev); + } + int deltaY = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height(); + int deltaX = qMax(pos.x() - visible.left(), visible.right() - pos.x()) - visible.width(); + int delta = qMax(deltaX, deltaY); + if (delta >= 0) { + if (delta < 7) + delta = 7; + int timeout = 4900 / (delta * delta); + d->autoScrollTimer.start(timeout, this); + + if (deltaY > 0) + d->vbar->triggerAction(pos.y() < visible.center().y() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + if (deltaX > 0) + d->hbar->triggerAction(pos.x() < visible.center().x() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + } + } +#ifdef QT_KEYPAD_NAVIGATION + else if (e->timerId() == d->deleteAllTimer.timerId()) { + d->deleteAllTimer.stop(); + clear(); + } +#endif +} + +/*! + Changes the text of the text edit to the string \a text. + Any previous text is removed. + + \a text is interpreted as plain text. + + Note that the undo/redo history is cleared by this function. + + \sa toPlainText() +*/ + +void QTextEdit::setPlainText(const QString &text) +{ + Q_D(QTextEdit); + d->control->setPlainText(text); + d->preferRichText = false; +} + +/*! + \fn QString QTextEdit::toPlainText() const + + Returns the text of the text edit as plain text. + + \sa QTextEdit::setPlainText() + */ + + +/*! + \property QTextEdit::html + + This property provides an HTML interface to the text of the text edit. + + toHtml() returns the text of the text edit as html. + + setHtml() changes the text of the text edit. Any previous text is + removed and the undo/redo history is cleared. The input text is + interpreted as rich text in html format. + + \note It is the responsibility of the caller to make sure that the + text is correctly decoded when a QString containing HTML is created + and passed to setHtml(). + + By default, for a newly-created, empty document, this property contains + text to describe an HTML 4.0 document with no body text. + + \sa {Supported HTML Subset}, plainText +*/ + +#ifndef QT_NO_TEXTHTMLPARSER +void QTextEdit::setHtml(const QString &text) +{ + Q_D(QTextEdit); + d->control->setHtml(text); + d->preferRichText = true; +} +#endif + +/*! \reimp +*/ +void QTextEdit::keyPressEvent(QKeyEvent *e) +{ + Q_D(QTextEdit); + +#ifdef QT_KEYPAD_NAVIGATION + switch (e->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + // code assumes linksaccessible + editable isn't meaningful + if (d->control->textInteractionFlags() & Qt::TextEditable) { + setEditFocus(!hasEditFocus()); + } else { + if (!hasEditFocus()) + setEditFocus(true); + else { + QTextCursor cursor = d->control->textCursor(); + QTextCharFormat charFmt = cursor.charFormat(); + if (!(d->control->textInteractionFlags() & Qt::LinksAccessibleByKeyboard) + || !cursor.hasSelection() || charFmt.anchorHref().isEmpty()) { + e->accept(); + return; + } + } + } + } + break; + case Qt::Key_Back: + case Qt::Key_No: + if (!QApplication::keypadNavigationEnabled() + || (QApplication::keypadNavigationEnabled() && !hasEditFocus())) { + e->ignore(); + return; + } + break; + default: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus() && !(e->modifiers() & Qt::ControlModifier)) { + if (e->text()[0].isPrint()) { + setEditFocus(true); + clear(); + } else { + e->ignore(); + return; + } + } + } + break; + } +#endif + + if (!(d->control->textInteractionFlags() & Qt::TextEditable)) { + switch (e->key()) { + case Qt::Key_Space: + e->accept(); + if (e->modifiers() & Qt::ShiftModifier) + d->vbar->triggerAction(QAbstractSlider::SliderPageStepSub); + else + d->vbar->triggerAction(QAbstractSlider::SliderPageStepAdd); + break; + default: + d->sendControlEvent(e); + if (!e->isAccepted() && e->modifiers() == Qt::NoModifier) { + if (e->key() == Qt::Key_Home) { + d->vbar->triggerAction(QAbstractSlider::SliderToMinimum); + e->accept(); + } else if (e->key() == Qt::Key_End) { + d->vbar->triggerAction(QAbstractSlider::SliderToMaximum); + e->accept(); + } + } + if (!e->isAccepted()) { + QAbstractScrollArea::keyPressEvent(e); + } + } + return; + } + +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::MoveToPreviousPage) { + e->accept(); + d->pageUpDown(QTextCursor::Up, QTextCursor::MoveAnchor); + return; + } else if (e == QKeySequence::MoveToNextPage) { + e->accept(); + d->pageUpDown(QTextCursor::Down, QTextCursor::MoveAnchor); + return; + } else if (e == QKeySequence::SelectPreviousPage) { + e->accept(); + d->pageUpDown(QTextCursor::Up, QTextCursor::KeepAnchor); + return; + } else if (e ==QKeySequence::SelectNextPage) { + e->accept(); + d->pageUpDown(QTextCursor::Down, QTextCursor::KeepAnchor); + return; + } +#endif // QT_NO_SHORTCUT + + { + QTextCursor cursor = d->control->textCursor(); + const QString text = e->text(); + if (cursor.atBlockStart() + && (d->autoFormatting & AutoBulletList) + && (text.length() == 1) + && (text.at(0) == QLatin1Char('-') || text.at(0) == QLatin1Char('*')) + && (!cursor.currentList())) { + + d->createAutoBulletList(); + e->accept(); + return; + } + } + + d->sendControlEvent(e); +#ifdef QT_KEYPAD_NAVIGATION + if (!e->isAccepted()) { + switch (e->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + if (QApplication::keypadNavigationEnabled()) { + // Cursor position didn't change, so we want to leave + // these keys to change focus. + e->ignore(); + return; + } + break; + case Qt::Key_Back: + if (!e->isAutoRepeat()) { + if (QApplication::keypadNavigationEnabled()) { + if (document()->isEmpty() || !(d->control->textInteractionFlags() & Qt::TextEditable)) { + setEditFocus(false); + e->accept(); + } else if (!d->deleteAllTimer.isActive()) { + e->accept(); + d->deleteAllTimer.start(750, this); + } + } else { + e->ignore(); + return; + } + } + break; + default: break; + } + } +#endif +} + +/*! \reimp +*/ +void QTextEdit::keyReleaseEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + Q_D(QTextEdit); + if (QApplication::keypadNavigationEnabled()) { + if (!e->isAutoRepeat() && e->key() == Qt::Key_Back + && d->deleteAllTimer.isActive()) { + d->deleteAllTimer.stop(); + QTextCursor cursor = d->control->textCursor(); + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + if (list && cursor.atBlockStart()) { + list->remove(cursor.block()); + } else if (cursor.atBlockStart() && blockFmt.indent() > 0) { + blockFmt.setIndent(blockFmt.indent() - 1); + cursor.setBlockFormat(blockFmt); + } else { + cursor.deletePreviousChar(); + } + setTextCursor(cursor); + e->accept(); + return; + } + } +#endif + e->ignore(); +} + +/*! + Loads the resource specified by the given \a type and \a name. + + This function is an extension of QTextDocument::loadResource(). + + \sa QTextDocument::loadResource() +*/ +QVariant QTextEdit::loadResource(int type, const QUrl &name) +{ + Q_UNUSED(type); + Q_UNUSED(name); + return QVariant(); +} + +/*! \reimp +*/ +void QTextEdit::resizeEvent(QResizeEvent *e) +{ + Q_D(QTextEdit); + + if (d->lineWrap == NoWrap) { + QTextDocument *doc = d->control->document(); + QVariant alignmentProperty = doc->documentLayout()->property("contentHasAlignment"); + + if (!doc->pageSize().isNull() + && alignmentProperty.type() == QVariant::Bool + && !alignmentProperty.toBool()) { + + d->_q_adjustScrollbars(); + return; + } + } + + if (d->lineWrap != FixedPixelWidth + && e->oldSize().width() != e->size().width()) + d->relayoutDocument(); + else + d->_q_adjustScrollbars(); +} + +void QTextEditPrivate::relayoutDocument() +{ + QTextDocument *doc = control->document(); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + + if (QTextDocumentLayout *tlayout = qobject_cast<QTextDocumentLayout *>(layout)) { + if (lineWrap == QTextEdit::FixedColumnWidth) + tlayout->setFixedColumnWidth(lineWrapColumnOrWidth); + else + tlayout->setFixedColumnWidth(-1); + } + + QTextDocumentLayout *tlayout = qobject_cast<QTextDocumentLayout *>(layout); + QSize lastUsedSize; + if (tlayout) + lastUsedSize = tlayout->dynamicDocumentSize().toSize(); + else + lastUsedSize = layout->documentSize().toSize(); + + // ignore calls to _q_adjustScrollbars caused by an emission of the + // usedSizeChanged() signal in the layout, as we're calling it + // later on our own anyway (or deliberately not) . + const bool oldIgnoreScrollbarAdjustment = ignoreAutomaticScrollbarAdjustment; + ignoreAutomaticScrollbarAdjustment = true; + + int width = viewport->width(); + if (lineWrap == QTextEdit::FixedPixelWidth) + width = lineWrapColumnOrWidth; + else if (lineWrap == QTextEdit::NoWrap) { + QVariant alignmentProperty = doc->documentLayout()->property("contentHasAlignment"); + if (alignmentProperty.type() == QVariant::Bool && !alignmentProperty.toBool()) { + + width = 0; + } + } + + doc->setPageSize(QSize(width, -1)); + if (tlayout) + tlayout->ensureLayouted(verticalOffset() + viewport->height()); + + ignoreAutomaticScrollbarAdjustment = oldIgnoreScrollbarAdjustment; + + QSize usedSize; + if (tlayout) + usedSize = tlayout->dynamicDocumentSize().toSize(); + else + usedSize = layout->documentSize().toSize(); + + // this is an obscure situation in the layout that can happen: + // if a character at the end of a line is the tallest one and therefore + // influencing the total height of the line and the line right below it + // is always taller though, then it can happen that if due to line breaking + // that tall character wraps into the lower line the document not only shrinks + // horizontally (causing the character to wrap in the first place) but also + // vertically, because the original line is now smaller and the one below kept + // its size. So a layout with less width _can_ take up less vertical space, too. + // If the wider case causes a vertical scroll bar to appear and the narrower one + // (narrower because the vertical scroll bar takes up horizontal space)) to disappear + // again then we have an endless loop, as _q_adjustScrollBars sets new ranges on the + // scroll bars, the QAbstractScrollArea will find out about it and try to show/hide + // the scroll bars again. That's why we try to detect this case here and break out. + // + // (if you change this please also check the layoutingLoop() testcase in + // QTextEdit's autotests) + if (lastUsedSize.isValid() + && !vbar->isHidden() + && viewport->width() < lastUsedSize.width() + && usedSize.height() < lastUsedSize.height() + && usedSize.height() <= viewport->height()) + return; + + _q_adjustScrollbars(); +} + +void QTextEditPrivate::paint(QPainter *p, QPaintEvent *e) +{ + const int xOffset = horizontalOffset(); + const int yOffset = verticalOffset(); + + QRect r = e->rect(); + p->translate(-xOffset, -yOffset); + r.translate(xOffset, yOffset); + + QTextDocument *doc = control->document(); + QTextDocumentLayout *layout = qobject_cast<QTextDocumentLayout *>(doc->documentLayout()); + + // the layout might need to expand the root frame to + // the viewport if NoWrap is set + if (layout) + layout->setViewport(viewport->rect()); + + control->drawContents(p, r, q_func()); + + if (layout) + layout->setViewport(QRect()); +} + +/*! \reimp +*/ +void QTextEdit::paintEvent(QPaintEvent *e) +{ + Q_D(QTextEdit); + QPainter p(d->viewport); + d->paint(&p, e); +} + +void QTextEditPrivate::_q_currentCharFormatChanged(const QTextCharFormat &fmt) +{ + Q_Q(QTextEdit); + emit q->currentCharFormatChanged(fmt); +#ifdef QT3_SUPPORT + // compat signals + emit q->currentFontChanged(fmt.font()); + emit q->currentColorChanged(fmt.foreground().color()); +#endif +} + +void QTextEditPrivate::updateDefaultTextOption() +{ + QTextDocument *doc = control->document(); + + QTextOption opt = doc->defaultTextOption(); + QTextOption::WrapMode oldWrapMode = opt.wrapMode(); + + if (lineWrap == QTextEdit::NoWrap) + opt.setWrapMode(QTextOption::NoWrap); + else + opt.setWrapMode(wordWrap); + + if (opt.wrapMode() != oldWrapMode) + doc->setDefaultTextOption(opt); +} + +/*! \reimp +*/ +void QTextEdit::mousePressEvent(QMouseEvent *e) +{ + Q_D(QTextEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) + setEditFocus(true); +#endif + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QTextEdit::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QTextEdit); + d->inDrag = false; // paranoia + const QPoint pos = e->pos(); + d->sendControlEvent(e); + if (!(e->buttons() & Qt::LeftButton)) + return; + QRect visible = d->viewport->rect(); + if (visible.contains(pos)) + d->autoScrollTimer.stop(); + else if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); +} + +/*! \reimp +*/ +void QTextEdit::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QTextEdit); + d->sendControlEvent(e); + if (d->autoScrollTimer.isActive()) { + d->autoScrollTimer.stop(); + ensureCursorVisible(); + } +} + +/*! \reimp +*/ +void QTextEdit::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QTextEdit); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +bool QTextEdit::focusNextPrevChild(bool next) +{ + Q_D(const QTextEdit); + if (!d->tabChangesFocus && d->control->textInteractionFlags() & Qt::TextEditable) + return false; + return QAbstractScrollArea::focusNextPrevChild(next); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \fn void QTextEdit::contextMenuEvent(QContextMenuEvent *event) + + Shows the standard context menu created with createStandardContextMenu(). + + If you do not want the text edit to have a context menu, you can set + its \l contextMenuPolicy to Qt::NoContextMenu. If you want to + customize the context menu, reimplement this function. If you want + to extend the standard context menu, reimplement this function, call + createStandardContextMenu() and extend the menu returned. + + Information about the event is passed in the \a event object. + + \snippet doc/src/snippets/code/src_gui_widgets_qtextedit.cpp 0 +*/ +void QTextEdit::contextMenuEvent(QContextMenuEvent *e) +{ + Q_D(QTextEdit); + d->sendControlEvent(e); +} +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp +*/ +void QTextEdit::dragEnterEvent(QDragEnterEvent *e) +{ + Q_D(QTextEdit); + d->inDrag = true; + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QTextEdit::dragLeaveEvent(QDragLeaveEvent *e) +{ + Q_D(QTextEdit); + d->inDrag = false; + d->autoScrollTimer.stop(); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QTextEdit::dragMoveEvent(QDragMoveEvent *e) +{ + Q_D(QTextEdit); + d->autoScrollDragPos = e->pos(); + if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QTextEdit::dropEvent(QDropEvent *e) +{ + Q_D(QTextEdit); + d->inDrag = false; + d->autoScrollTimer.stop(); + d->sendControlEvent(e); +} + +#endif // QT_NO_DRAGANDDROP + +/*! \reimp + */ +void QTextEdit::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QTextEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (d->control->textInteractionFlags() & Qt::TextEditable + && QApplication::keypadNavigationEnabled() + && !hasEditFocus()) { + setEditFocus(true); + selectAll(); // so text is replaced rather than appended to + } +#endif + d->sendControlEvent(e); +} + +/*!\reimp +*/ +void QTextEdit::scrollContentsBy(int dx, int dy) +{ + Q_D(QTextEdit); + if (isRightToLeft()) + dx = -dx; + d->viewport->scroll(dx, dy); +} + +/*!\reimp +*/ +QVariant QTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QTextEdit); + QVariant v = d->control->inputMethodQuery(property); + const QPoint offset(-d->horizontalOffset(), -d->verticalOffset()); + if (v.type() == QVariant::RectF) + v = v.toRectF().toRect().translated(offset); + else if (v.type() == QVariant::PointF) + v = v.toPointF().toPoint() + offset; + else if (v.type() == QVariant::Rect) + v = v.toRect().translated(offset); + else if (v.type() == QVariant::Point) + v = v.toPoint() + offset; + return v; +} + +/*! \reimp +*/ +void QTextEdit::focusInEvent(QFocusEvent *e) +{ + Q_D(QTextEdit); + QAbstractScrollArea::focusInEvent(e); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QTextEdit::focusOutEvent(QFocusEvent *e) +{ + Q_D(QTextEdit); + QAbstractScrollArea::focusOutEvent(e); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QTextEdit::showEvent(QShowEvent *) +{ + Q_D(QTextEdit); + if (!d->anchorToScrollToWhenVisible.isEmpty()) { + scrollToAnchor(d->anchorToScrollToWhenVisible); + d->anchorToScrollToWhenVisible.clear(); + d->showCursorOnInitialShow = false; + } else if (d->showCursorOnInitialShow) { + d->showCursorOnInitialShow = false; + ensureCursorVisible(); + } +} + +/*! \reimp +*/ +void QTextEdit::changeEvent(QEvent *e) +{ + Q_D(QTextEdit); + QAbstractScrollArea::changeEvent(e); + if (e->type() == QEvent::ApplicationFontChange + || e->type() == QEvent::FontChange) { + d->control->document()->setDefaultFont(font()); + } else if(e->type() == QEvent::ActivationChange) { + if (!isActiveWindow()) + d->autoScrollTimer.stop(); + } else if (e->type() == QEvent::EnabledChange) { + e->setAccepted(isEnabled()); + d->control->setPalette(palette()); + d->sendControlEvent(e); + } else if (e->type() == QEvent::PaletteChange) { + d->control->setPalette(palette()); + } else if (e->type() == QEvent::LayoutDirectionChange) { + d->sendControlEvent(e); + } +} + +/*! \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QTextEdit::wheelEvent(QWheelEvent *e) +{ + Q_D(QTextEdit); + if (!(d->control->textInteractionFlags() & Qt::TextEditable)) { + if (e->modifiers() & Qt::ControlModifier) { + const int delta = e->delta(); + if (delta < 0) + zoomOut(); + else if (delta > 0) + zoomIn(); + return; + } + } + QAbstractScrollArea::wheelEvent(e); + updateMicroFocus(); +} +#endif + +#ifndef QT_NO_CONTEXTMENU +/*! This function creates the standard context menu which is shown + when the user clicks on the text edit with the right mouse + button. It is called from the default contextMenuEvent() handler. + The popup menu's ownership is transferred to the caller. + + We recommend that you use the createStandardContextMenu(QPoint) version instead + which will enable the actions that are sensitive to where the user clicked. +*/ + +QMenu *QTextEdit::createStandardContextMenu() +{ + Q_D(QTextEdit); + return d->control->createStandardContextMenu(QPointF(), this); +} + +/*! + \since 4.4 + This function creates the standard context menu which is shown + when the user clicks on the text edit with the right mouse + button. It is called from the default contextMenuEvent() handler + and it takes the \a position of where the mouse click was. + This can enable actions that are sensitive to the position where the user clicked. + The popup menu's ownership is transferred to the caller. +*/ + +QMenu *QTextEdit::createStandardContextMenu(const QPoint &position) +{ + Q_D(QTextEdit); + return d->control->createStandardContextMenu(position, this); +} +#endif // QT_NO_CONTEXTMENU + +/*! + returns a QTextCursor at position \a pos (in viewport coordinates). +*/ +QTextCursor QTextEdit::cursorForPosition(const QPoint &pos) const +{ + Q_D(const QTextEdit); + return d->control->cursorForPosition(d->mapToContents(pos)); +} + +/*! + returns a rectangle (in viewport coordinates) that includes the + \a cursor. + */ +QRect QTextEdit::cursorRect(const QTextCursor &cursor) const +{ + Q_D(const QTextEdit); + if (cursor.isNull()) + return QRect(); + + QRect r = d->control->cursorRect(cursor).toRect(); + r.translate(-d->horizontalOffset(),-d->verticalOffset()); + return r; +} + +/*! + returns a rectangle (in viewport coordinates) that includes the + cursor of the text edit. + */ +QRect QTextEdit::cursorRect() const +{ + Q_D(const QTextEdit); + QRect r = d->control->cursorRect().toRect(); + r.translate(-d->horizontalOffset(),-d->verticalOffset()); + return r; +} + + +/*! + Returns the reference of the anchor at position \a pos, or an + empty string if no anchor exists at that point. +*/ +QString QTextEdit::anchorAt(const QPoint& pos) const +{ + Q_D(const QTextEdit); + return d->control->anchorAt(d->mapToContents(pos)); +} + +/*! + \property QTextEdit::overwriteMode + \since 4.1 + \brief whether text entered by the user will overwrite existing text + + As with many text editors, the text editor widget can be configured + to insert or overwrite existing text with new text entered by the user. + + If this property is true, existing text is overwritten, character-for-character + by new text; otherwise, text is inserted at the cursor position, displacing + existing text. + + By default, this property is false (new text does not overwrite existing text). +*/ + +bool QTextEdit::overwriteMode() const +{ + Q_D(const QTextEdit); + return d->control->overwriteMode(); +} + +void QTextEdit::setOverwriteMode(bool overwrite) +{ + Q_D(QTextEdit); + d->control->setOverwriteMode(overwrite); +} + +/*! + \property QTextEdit::tabStopWidth + \brief the tab stop width in pixels + \since 4.1 + + By default, this property contains a value of 80. +*/ + +int QTextEdit::tabStopWidth() const +{ + Q_D(const QTextEdit); + return qRound(d->control->document()->defaultTextOption().tabStop()); +} + +void QTextEdit::setTabStopWidth(int width) +{ + Q_D(QTextEdit); + QTextOption opt = d->control->document()->defaultTextOption(); + if (opt.tabStop() == width || width < 0) + return; + opt.setTabStop(width); + d->control->document()->setDefaultTextOption(opt); +} + +/*! + \since 4.2 + \property QTextEdit::cursorWidth + + This property specifies the width of the cursor in pixels. The default value is 1. +*/ +int QTextEdit::cursorWidth() const +{ + Q_D(const QTextEdit); + return d->control->cursorWidth(); +} + +void QTextEdit::setCursorWidth(int width) +{ + Q_D(QTextEdit); + d->control->setCursorWidth(width); +} + +/*! + \property QTextEdit::acceptRichText + \brief whether the text edit accepts rich text insertions by the user + \since 4.1 + + When this propery is set to false text edit will accept only + plain text input from the user. For example through clipboard or drag and drop. + + This property's default is true. +*/ + +bool QTextEdit::acceptRichText() const +{ + Q_D(const QTextEdit); + return d->control->acceptRichText(); +} + +void QTextEdit::setAcceptRichText(bool accept) +{ + Q_D(QTextEdit); + d->control->setAcceptRichText(accept); +} + +/*! + \class QTextEdit::ExtraSelection + \since 4.2 + \brief The QTextEdit::ExtraSelection structure provides a way of specifying a + character format for a given selection in a document +*/ + +/*! + \variable QTextEdit::ExtraSelection::cursor + A cursor that contains a selection in a QTextDocument +*/ + +/*! + \variable QTextEdit::ExtraSelection::format + A format that is used to specify a foreground or background brush/color + for the selection. +*/ + +/*! + \since 4.2 + This function allows temporarily marking certain regions in the document + with a given color, specified as \a selections. This can be useful for + example in a programming editor to mark a whole line of text with a given + background color to indicate the existence of a breakpoint. + + \sa QTextEdit::ExtraSelection, extraSelections() +*/ +void QTextEdit::setExtraSelections(const QList<ExtraSelection> &selections) +{ + Q_D(QTextEdit); + d->control->setExtraSelections(selections); +} + +/*! + \since 4.2 + Returns previously set extra selections. + + \sa setExtraSelections() +*/ +QList<QTextEdit::ExtraSelection> QTextEdit::extraSelections() const +{ + Q_D(const QTextEdit); + return d->control->extraSelections(); +} + +/*! + This function returns a new MIME data object to represent the contents + of the text edit's current selection. It is called when the selection needs + to be encapsulated into a new QMimeData object; for example, when a drag + and drop operation is started, or when data is copyied to the clipboard. + + If you reimplement this function, note that the ownership of the returned + QMimeData object is passed to the caller. The selection can be retrieved + by using the textCursor() function. +*/ +QMimeData *QTextEdit::createMimeDataFromSelection() const +{ + Q_D(const QTextEdit); + return d->control->QTextControl::createMimeDataFromSelection(); +} + +/*! + This function returns true if the contents of the MIME data object, specified + by \a source, can be decoded and inserted into the document. It is called + for example when during a drag operation the mouse enters this widget and it + is necessary to determine whether it is possible to accept the drag and drop + operation. + + Reimplement this function to enable drag and drop support for additional MIME types. + */ +bool QTextEdit::canInsertFromMimeData(const QMimeData *source) const +{ + Q_D(const QTextEdit); + return d->control->QTextControl::canInsertFromMimeData(source); +} + +/*! + This function inserts the contents of the MIME data object, specified + by \a source, into the text edit at the current cursor position. It is + called whenever text is inserted as the result of a clipboard paste + operation, or when the text edit accepts data from a drag and drop + operation. + + Reimplement this function to enable drag and drop support for additional MIME types. + */ +void QTextEdit::insertFromMimeData(const QMimeData *source) +{ + Q_D(QTextEdit); + d->control->QTextControl::insertFromMimeData(source); +} + +/*! + \property QTextEdit::readOnly + \brief whether the text edit is read-only + + In a read-only text edit the user can only navigate through the + text and select text; modifying the text is not possible. + + This property's default is false. +*/ + +bool QTextEdit::isReadOnly() const +{ + Q_D(const QTextEdit); + return !(d->control->textInteractionFlags() & Qt::TextEditable); +} + +void QTextEdit::setReadOnly(bool ro) +{ + Q_D(QTextEdit); + Qt::TextInteractionFlags flags = Qt::NoTextInteraction; + if (ro) { + flags = Qt::TextSelectableByMouse; +#ifndef QT_NO_TEXTBROWSER + if (qobject_cast<QTextBrowser *>(this)) + flags |= Qt::TextBrowserInteraction; +#endif + } else { + flags = Qt::TextEditorInteraction; + } + setAttribute(Qt::WA_InputMethodEnabled, !ro); + d->control->setTextInteractionFlags(flags); +} + +/*! + \property QTextEdit::textInteractionFlags + \since 4.2 + + Specifies how the widget should interact with user input. + + The default value depends on whether the QTextEdit is read-only + or editable, and whether it is a QTextBrowser or not. +*/ + +void QTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QTextEdit); + d->control->setTextInteractionFlags(flags); +} + +Qt::TextInteractionFlags QTextEdit::textInteractionFlags() const +{ + Q_D(const QTextEdit); + return d->control->textInteractionFlags(); +} + +/*! + Merges the properties specified in \a modifier into the current character + format by calling QTextCursor::mergeCharFormat on the editor's cursor. + If the editor has a selection then the properties of \a modifier are + directly applied to the selection. + + \sa QTextCursor::mergeCharFormat() + */ +void QTextEdit::mergeCurrentCharFormat(const QTextCharFormat &modifier) +{ + Q_D(QTextEdit); + d->control->mergeCurrentCharFormat(modifier); +} + +/*! + Sets the char format that is be used when inserting new text to \a + format by calling QTextCursor::setCharFormat() on the editor's + cursor. If the editor has a selection then the char format is + directly applied to the selection. + */ +void QTextEdit::setCurrentCharFormat(const QTextCharFormat &format) +{ + Q_D(QTextEdit); + d->control->setCurrentCharFormat(format); +} + +/*! + Returns the char format that is used when inserting new text. + */ +QTextCharFormat QTextEdit::currentCharFormat() const +{ + Q_D(const QTextEdit); + return d->control->currentCharFormat(); +} + +/*! + \property QTextEdit::autoFormatting + \brief the enabled set of auto formatting features + + The value can be any combination of the values in the + AutoFormattingFlag enum. The default is AutoNone. Choose + AutoAll to enable all automatic formatting. + + Currently, the only automatic formatting feature provided is + AutoBulletList; future versions of Qt may offer more. +*/ + +QTextEdit::AutoFormatting QTextEdit::autoFormatting() const +{ + Q_D(const QTextEdit); + return d->autoFormatting; +} + +void QTextEdit::setAutoFormatting(AutoFormatting features) +{ + Q_D(QTextEdit); + d->autoFormatting = features; +} + +/*! + Convenience slot that inserts \a text at the current + cursor position. + + It is equivalent to + + \snippet doc/src/snippets/code/src_gui_widgets_qtextedit.cpp 1 + */ +void QTextEdit::insertPlainText(const QString &text) +{ + Q_D(QTextEdit); + d->control->insertPlainText(text); +} + +/*! + Convenience slot that inserts \a text which is assumed to be of + html formatting at the current cursor position. + + It is equivalent to: + + \snippet doc/src/snippets/code/src_gui_widgets_qtextedit.cpp 2 + + \note When using this function with a style sheet, the style sheet will + only apply to the current block in the document. In order to apply a style + sheet throughout a document, use QTextDocument::setDefaultStyleSheet() + instead. + */ +#ifndef QT_NO_TEXTHTMLPARSER +void QTextEdit::insertHtml(const QString &text) +{ + Q_D(QTextEdit); + d->control->insertHtml(text); +} +#endif // QT_NO_TEXTHTMLPARSER + +/*! + Scrolls the text edit so that the anchor with the given \a name is + visible; does nothing if the \a name is empty, or is already + visible, or isn't found. +*/ +void QTextEdit::scrollToAnchor(const QString &name) +{ + Q_D(QTextEdit); + if (name.isEmpty()) + return; + + if (!isVisible()) { + d->anchorToScrollToWhenVisible = name; + return; + } + + QPointF p = d->control->anchorPosition(name); + const int newPosition = qRound(p.y()); + if ( d->vbar->maximum() < newPosition ) + d->_q_adjustScrollbars(); + d->vbar->setValue(newPosition); +} + +/*! + \fn QTextEdit::zoomIn(int range) + + Zooms in on the text by making the base font size \a range + points larger and recalculating all font sizes to be the new size. + This does not change the size of any images. + + \sa zoomOut() +*/ +void QTextEdit::zoomIn(int range) +{ + QFont f = font(); + const int newSize = f.pointSize() + range; + if (newSize <= 0) + return; + f.setPointSize(newSize); + setFont(f); +} + +/*! + \fn QTextEdit::zoomOut(int range) + + \overload + + Zooms out on the text by making the base font size \a range points + smaller and recalculating all font sizes to be the new size. This + does not change the size of any images. + + \sa zoomIn() +*/ +void QTextEdit::zoomOut(int range) +{ + zoomIn(-range); +} + +/*! + \since 4.2 + Moves the cursor by performing the given \a operation. + + If \a mode is QTextCursor::KeepAnchor, the cursor selects the text it moves over. + This is the same effect that the user achieves when they hold down the Shift key + and move the cursor with the cursor keys. + + \sa QTextCursor::movePosition() +*/ +void QTextEdit::moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode) +{ + Q_D(QTextEdit); + d->control->moveCursor(operation, mode); +} + +/*! + \since 4.2 + Returns whether text can be pasted from the clipboard into the textedit. +*/ +bool QTextEdit::canPaste() const +{ + Q_D(const QTextEdit); + return d->control->canPaste(); +} + +#ifndef QT_NO_PRINTER +/*! + \since 4.3 + Convenience function to print the text edit's document to the given \a printer. This + is equivalent to calling the print method on the document directly except that this + function also supports QPrinter::Selection as print range. + + \sa QTextDocument::print() +*/ +void QTextEdit::print(QPrinter *printer) const +{ + Q_D(const QTextEdit); + d->control->print(printer); +} +#endif // QT _NO_PRINTER + +/*! \property QTextEdit::tabChangesFocus + \brief whether \gui Tab changes focus or is accepted as input + + In some occasions text edits should not allow the user to input + tabulators or change indentation using the \gui Tab key, as this breaks + the focus chain. The default is false. + +*/ + +bool QTextEdit::tabChangesFocus() const +{ + Q_D(const QTextEdit); + return d->tabChangesFocus; +} + +void QTextEdit::setTabChangesFocus(bool b) +{ + Q_D(QTextEdit); + d->tabChangesFocus = b; +} + +/*! + \property QTextEdit::documentTitle + \brief the title of the document parsed from the text. + + By default, for a newly-created, empty document, this property contains + an empty string. +*/ + +/*! + \property QTextEdit::lineWrapMode + \brief the line wrap mode + + The default mode is WidgetWidth which causes words to be + wrapped at the right edge of the text edit. Wrapping occurs at + whitespace, keeping whole words intact. If you want wrapping to + occur within words use setWordWrapMode(). If you set a wrap mode of + FixedPixelWidth or FixedColumnWidth you should also call + setLineWrapColumnOrWidth() with the width you want. + + \sa lineWrapColumnOrWidth +*/ + +QTextEdit::LineWrapMode QTextEdit::lineWrapMode() const +{ + Q_D(const QTextEdit); + return d->lineWrap; +} + +void QTextEdit::setLineWrapMode(LineWrapMode wrap) +{ + Q_D(QTextEdit); + if (d->lineWrap == wrap) + return; + d->lineWrap = wrap; + d->updateDefaultTextOption(); + d->relayoutDocument(); +} + +/*! + \property QTextEdit::lineWrapColumnOrWidth + \brief the position (in pixels or columns depending on the wrap mode) where text will be wrapped + + If the wrap mode is FixedPixelWidth, the value is the number of + pixels from the left edge of the text edit at which text should be + wrapped. If the wrap mode is FixedColumnWidth, the value is the + column number (in character columns) from the left edge of the + text edit at which text should be wrapped. + + By default, this property contains a value of 0. + + \sa lineWrapMode +*/ + +int QTextEdit::lineWrapColumnOrWidth() const +{ + Q_D(const QTextEdit); + return d->lineWrapColumnOrWidth; +} + +void QTextEdit::setLineWrapColumnOrWidth(int w) +{ + Q_D(QTextEdit); + d->lineWrapColumnOrWidth = w; + d->relayoutDocument(); +} + +/*! + \property QTextEdit::wordWrapMode + \brief the mode QTextEdit will use when wrapping text by words + + By default, this property is set to QTextOption::WrapAtWordBoundaryOrAnywhere. + + \sa QTextOption::WrapMode +*/ + +QTextOption::WrapMode QTextEdit::wordWrapMode() const +{ + Q_D(const QTextEdit); + return d->wordWrap; +} + +void QTextEdit::setWordWrapMode(QTextOption::WrapMode mode) +{ + Q_D(QTextEdit); + if (mode == d->wordWrap) + return; + d->wordWrap = mode; + d->updateDefaultTextOption(); +} + +/*! + Finds the next occurrence of the string, \a exp, using the given + \a options. Returns true if \a exp was found and changes the + cursor to select the match; otherwise returns false. +*/ +bool QTextEdit::find(const QString &exp, QTextDocument::FindFlags options) +{ + Q_D(QTextEdit); + return d->control->find(exp, options); +} + +/*! + \fn void QTextEdit::copyAvailable(bool yes) + + This signal is emitted when text is selected or de-selected in the + text edit. + + When text is selected this signal will be emitted with \a yes set + to true. If no text has been selected or if the selected text is + de-selected this signal is emitted with \a yes set to false. + + If \a yes is true then copy() can be used to copy the selection to + the clipboard. If \a yes is false then copy() does nothing. + + \sa selectionChanged() +*/ + +/*! + \fn void QTextEdit::currentCharFormatChanged(const QTextCharFormat &f) + + This signal is emitted if the current character format has changed, for + example caused by a change of the cursor position. + + The new format is \a f. + + \sa setCurrentCharFormat() +*/ + +/*! + \fn void QTextEdit::selectionChanged() + + This signal is emitted whenever the selection changes. + + \sa copyAvailable() +*/ + +/*! + \fn void QTextEdit::cursorPositionChanged() + + This signal is emitted whenever the position of the + cursor changed. +*/ + +/*! + \since 4.2 + + Sets the text edit's \a text. The text can be plain text or HTML + and the text edit will try to guess the right format. + + Use setHtml() or setPlainText() directly to avoid text edit's guessing. +*/ +void QTextEdit::setText(const QString &text) +{ + Q_D(QTextEdit); + Qt::TextFormat format = d->textFormat; + if (d->textFormat == Qt::AutoText) + format = Qt::mightBeRichText(text) ? Qt::RichText : Qt::PlainText; +#ifndef QT_NO_TEXTHTMLPARSER + if (format == Qt::RichText || format == Qt::LogText) + setHtml(text); + else +#endif + setPlainText(text); +} + +#ifdef QT3_SUPPORT +/*! + Use the QTextCursor class instead. +*/ +void QTextEdit::moveCursor(CursorAction action, QTextCursor::MoveMode mode) +{ + Q_D(QTextEdit); + if (action == MovePageUp) { + d->pageUpDown(QTextCursor::Up, mode); + return; + } else if (action == MovePageDown) { + d->pageUpDown(QTextCursor::Down, mode); + return; + } + + QTextCursor cursor = d->control->textCursor(); + QTextCursor::MoveOperation op = QTextCursor::NoMove; + switch (action) { + case MoveBackward: op = QTextCursor::Left; break; + case MoveForward: op = QTextCursor::Right; break; + case MoveWordBackward: op = QTextCursor::WordLeft; break; + case MoveWordForward: op = QTextCursor::WordRight; break; + case MoveUp: op = QTextCursor::Up; break; + case MoveDown: op = QTextCursor::Down; break; + case MoveLineStart: op = QTextCursor::StartOfLine; break; + case MoveLineEnd: op = QTextCursor::EndOfLine; break; + case MoveHome: op = QTextCursor::Start; break; + case MoveEnd: op = QTextCursor::End; break; + default: return; + } + cursor.movePosition(op, mode); + d->control->setTextCursor(cursor); +} + +/*! + Use the QTextCursor class instead. +*/ +void QTextEdit::moveCursor(CursorAction action, bool select) +{ + moveCursor(action, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); +} + +/*! + Executes keyboard action \a action. + + Use the QTextCursor class instead. + + \sa textCursor() +*/ +void QTextEdit::doKeyboardAction(KeyboardAction action) +{ + Q_D(QTextEdit); + QTextCursor cursor = d->control->textCursor(); + switch (action) { + case ActionBackspace: cursor.deletePreviousChar(); break; + case ActionDelete: cursor.deleteChar(); break; + case ActionReturn: cursor.insertBlock(); break; + case ActionKill: { + QTextBlock block = cursor.block(); + if (cursor.position() == block.position() + block.length() - 2) + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + else + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.deleteChar(); + break; + } + case ActionWordBackspace: + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + cursor.deletePreviousChar(); + break; + case ActionWordDelete: + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + cursor.deleteChar(); + break; + } + d->control->setTextCursor(cursor); +} + +/*! + Returns all the text in the text edit as plain text. +*/ +QString QTextEdit::text() const +{ + Q_D(const QTextEdit); + if (d->textFormat == Qt::RichText || d->textFormat == Qt::LogText || (d->textFormat == Qt::AutoText && d->preferRichText)) + return d->control->toHtml(); + else + return d->control->toPlainText(); +} + + +/*! + Sets the text format to format \a f. + + \sa textFormat() +*/ +void QTextEdit::setTextFormat(Qt::TextFormat f) +{ + Q_D(QTextEdit); + d->textFormat = f; +} + +/*! + Returns the text format. + + \sa setTextFormat() +*/ +Qt::TextFormat QTextEdit::textFormat() const +{ + Q_D(const QTextEdit); + return d->textFormat; +} + +#endif // QT3_SUPPORT + +/*! + Appends a new paragraph with \a text to the end of the text edit. + + \note The new paragraph appended will have the same character format and + block format as the current paragraph, determined by the position of the cursor. + + \sa currentCharFormat(), QTextCursor::blockFormat() +*/ + +void QTextEdit::append(const QString &text) +{ + Q_D(QTextEdit); + QTextBlock lastBlock = d->control->document()->lastBlock(); + const bool atBottom = isReadOnly() ? d->verticalOffset() >= d->vbar->maximum() : + d->control->textCursor().atEnd(); + d->control->append(text); + if (atBottom) + d->vbar->setValue(d->vbar->maximum()); +} + +/*! + Ensures that the cursor is visible by scrolling the text edit if + necessary. +*/ +void QTextEdit::ensureCursorVisible() +{ + Q_D(QTextEdit); + d->control->ensureCursorVisible(); +} + + +/*! + \enum QTextEdit::KeyboardAction + + \compat + + \value ActionBackspace + \value ActionDelete + \value ActionReturn + \value ActionKill + \value ActionWordBackspace + \value ActionWordDelete +*/ + +/*! + \fn bool QTextEdit::find(const QString &exp, bool cs, bool wo) + + Use the find() overload that takes a QTextDocument::FindFlags + argument. +*/ + +/*! + \fn void QTextEdit::sync() + + Does nothing. +*/ + +/*! + \fn void QTextEdit::setBold(bool b) + + Use setFontWeight() instead. +*/ + +/*! + \fn void QTextEdit::setUnderline(bool b) + + Use setFontUnderline() instead. +*/ + +/*! + \fn void QTextEdit::setItalic(bool i) + + Use setFontItalic() instead. +*/ + +/*! + \fn void QTextEdit::setFamily(const QString &family) + + Use setFontFamily() instead. +*/ + +/*! + \fn void QTextEdit::setPointSize(int size) + + Use setFontPointSize() instead. +*/ + +/*! + \fn bool QTextEdit::italic() const + + Use fontItalic() instead. +*/ + +/*! + \fn bool QTextEdit::bold() const + + Use fontWeight() >= QFont::Bold instead. +*/ + +/*! + \fn bool QTextEdit::underline() const + + Use fontUnderline() instead. +*/ + +/*! + \fn QString QTextEdit::family() const + + Use fontFamily() instead. +*/ + +/*! + \fn int QTextEdit::pointSize() const + + Use int(fontPointSize()+0.5) instead. +*/ + +/*! + \fn bool QTextEdit::hasSelectedText() const + + Use textCursor().hasSelection() instead. +*/ + +/*! + \fn QString QTextEdit::selectedText() const + + Use textCursor().selectedText() instead. +*/ + +/*! + \fn bool QTextEdit::isUndoAvailable() const + + Use document()->isUndoAvailable() instead. +*/ + +/*! + \fn bool QTextEdit::isRedoAvailable() const + + Use document()->isRedoAvailable() instead. +*/ + +/*! + \fn void QTextEdit::insert(const QString &text) + + Use insertPlainText() instead. +*/ + +/*! + \fn bool QTextEdit::isModified() const + + Use document()->isModified() instead. +*/ + +/*! + \fn QColor QTextEdit::color() const + + Use textColor() instead. +*/ + +/*! + \fn void QTextEdit::textChanged() + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. +*/ + +/*! + \fn void QTextEdit::undoAvailable(bool available) + + This signal is emitted whenever undo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn void QTextEdit::redoAvailable(bool available) + + This signal is emitted whenever redo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn void QTextEdit::currentFontChanged(const QFont &font) + + Use currentCharFormatChanged() instead. +*/ + +/*! + \fn void QTextEdit::currentColorChanged(const QColor &color) + + Use currentCharFormatChanged() instead. +*/ + +/*! + \fn void QTextEdit::setModified(bool m) + + Use document->setModified() instead. +*/ + +/*! + \fn void QTextEdit::setColor(const QColor &color) + + Use setTextColor() instead. +*/ +#endif // QT_NO_TEXTEDIT + +QT_END_NAMESPACE + +#include "moc_qtextedit.cpp" diff --git a/src/gui/widgets/qtextedit.h b/src/gui/widgets/qtextedit.h new file mode 100644 index 0000000..0835cc1 --- /dev/null +++ b/src/gui/widgets/qtextedit.h @@ -0,0 +1,430 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTEDIT_H +#define QTEXTEDIT_H + +#include <QtGui/qabstractscrollarea.h> +#include <QtGui/qtextdocument.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qtextformat.h> + +#ifndef QT_NO_TEXTEDIT + +#ifdef QT3_SUPPORT +#include <QtGui/qtextobject.h> +#include <QtGui/qtextlayout.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyleSheet; +class QTextDocument; +class QMenu; +class QTextEditPrivate; +class QMimeData; + +class Q_GUI_EXPORT QTextEdit : public QAbstractScrollArea +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QTextEdit) + Q_FLAGS(AutoFormatting) + Q_ENUMS(LineWrapMode) + Q_PROPERTY(AutoFormatting autoFormatting READ autoFormatting WRITE setAutoFormatting) + Q_PROPERTY(bool tabChangesFocus READ tabChangesFocus WRITE setTabChangesFocus) + Q_PROPERTY(QString documentTitle READ documentTitle WRITE setDocumentTitle) + Q_PROPERTY(bool undoRedoEnabled READ isUndoRedoEnabled WRITE setUndoRedoEnabled) + Q_PROPERTY(LineWrapMode lineWrapMode READ lineWrapMode WRITE setLineWrapMode) + QDOC_PROPERTY(QTextOption::WrapMode wordWrapMode READ wordWrapMode WRITE setWordWrapMode) + Q_PROPERTY(int lineWrapColumnOrWidth READ lineWrapColumnOrWidth WRITE setLineWrapColumnOrWidth) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) +#ifndef QT_NO_TEXTHTMLPARSER + Q_PROPERTY(QString html READ toHtml WRITE setHtml NOTIFY textChanged USER true) +#endif + Q_PROPERTY(QString plainText READ toPlainText WRITE setPlainText DESIGNABLE false) + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + Q_PROPERTY(int tabStopWidth READ tabStopWidth WRITE setTabStopWidth) + Q_PROPERTY(bool acceptRichText READ acceptRichText WRITE setAcceptRichText) + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags WRITE setTextInteractionFlags) +public: + enum LineWrapMode { + NoWrap, + WidgetWidth, + FixedPixelWidth, + FixedColumnWidth + }; + + enum AutoFormattingFlag { + AutoNone = 0, + AutoBulletList = 0x00000001, + AutoAll = 0xffffffff + }; + + Q_DECLARE_FLAGS(AutoFormatting, AutoFormattingFlag) + +#if defined(QT3_SUPPORT) + enum CursorAction { + MoveBackward, + MoveForward, + MoveWordBackward, + MoveWordForward, + MoveUp, + MoveDown, + MoveLineStart, + MoveLineEnd, + MoveHome, + MoveEnd, + MovePageUp, + MovePageDown +#if !defined(Q_MOC_RUN) + , + MovePgUp = MovePageUp, + MovePgDown = MovePageDown +#endif + }; +#endif + + explicit QTextEdit(QWidget *parent = 0); + explicit QTextEdit(const QString &text, QWidget *parent = 0); + virtual ~QTextEdit(); + + void setDocument(QTextDocument *document); + QTextDocument *document() const; + + void setTextCursor(const QTextCursor &cursor); + QTextCursor textCursor() const; + + bool isReadOnly() const; + void setReadOnly(bool ro); + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + qreal fontPointSize() const; + QString fontFamily() const; + int fontWeight() const; + bool fontUnderline() const; + bool fontItalic() const; + QColor textColor() const; + QColor textBackgroundColor() const; + QFont currentFont() const; + Qt::Alignment alignment() const; + + void mergeCurrentCharFormat(const QTextCharFormat &modifier); + + void setCurrentCharFormat(const QTextCharFormat &format); + QTextCharFormat currentCharFormat() const; + + AutoFormatting autoFormatting() const; + void setAutoFormatting(AutoFormatting features); + + bool tabChangesFocus() const; + void setTabChangesFocus(bool b); + + inline void setDocumentTitle(const QString &title) + { document()->setMetaInformation(QTextDocument::DocumentTitle, title); } + inline QString documentTitle() const + { return document()->metaInformation(QTextDocument::DocumentTitle); } + + inline bool isUndoRedoEnabled() const + { return document()->isUndoRedoEnabled(); } + inline void setUndoRedoEnabled(bool enable) + { document()->setUndoRedoEnabled(enable); } + + LineWrapMode lineWrapMode() const; + void setLineWrapMode(LineWrapMode mode); + + int lineWrapColumnOrWidth() const; + void setLineWrapColumnOrWidth(int w); + + QTextOption::WrapMode wordWrapMode() const; + void setWordWrapMode(QTextOption::WrapMode policy); + + bool find(const QString &exp, QTextDocument::FindFlags options = 0); + + inline QString toPlainText() const + { return document()->toPlainText(); } +#ifndef QT_NO_TEXTHTMLPARSER + inline QString toHtml() const + { return document()->toHtml(); } +#endif + + void ensureCursorVisible(); + + virtual QVariant loadResource(int type, const QUrl &name); +#ifndef QT_NO_CONTEXTMENU + QMenu *createStandardContextMenu(); + QMenu *createStandardContextMenu(const QPoint &position); +#endif + + QTextCursor cursorForPosition(const QPoint &pos) const; + QRect cursorRect(const QTextCursor &cursor) const; + QRect cursorRect() const; + + QString anchorAt(const QPoint& pos) const; + + bool overwriteMode() const; + void setOverwriteMode(bool overwrite); + + int tabStopWidth() const; + void setTabStopWidth(int width); + + int cursorWidth() const; + void setCursorWidth(int width); + + bool acceptRichText() const; + void setAcceptRichText(bool accept); + + struct ExtraSelection + { + QTextCursor cursor; + QTextCharFormat format; + }; + void setExtraSelections(const QList<ExtraSelection> &selections); + QList<ExtraSelection> extraSelections() const; + + void moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + bool canPaste() const; + +#ifndef QT_NO_PRINTER + void print(QPrinter *printer) const; +#endif + +public Q_SLOTS: + void setFontPointSize(qreal s); + void setFontFamily(const QString &fontFamily); + void setFontWeight(int w); + void setFontUnderline(bool b); + void setFontItalic(bool b); + void setTextColor(const QColor &c); + void setTextBackgroundColor(const QColor &c); + void setCurrentFont(const QFont &f); + void setAlignment(Qt::Alignment a); + + void setPlainText(const QString &text); +#ifndef QT_NO_TEXTHTMLPARSER + void setHtml(const QString &text); +#endif + void setText(const QString &text); + +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy(); + void paste(); +#endif + + void undo(); + void redo(); + + void clear(); + void selectAll(); + + void insertPlainText(const QString &text); +#ifndef QT_NO_TEXTHTMLPARSER + void insertHtml(const QString &text); +#endif // QT_NO_TEXTHTMLPARSER + + void append(const QString &text); + + void scrollToAnchor(const QString &name); + + void zoomIn(int range = 1); + void zoomOut(int range = 1); + +Q_SIGNALS: + void textChanged(); + void undoAvailable(bool b); + void redoAvailable(bool b); + void currentCharFormatChanged(const QTextCharFormat &format); + void copyAvailable(bool b); + void selectionChanged(); + void cursorPositionChanged(); + +protected: + virtual bool event(QEvent *e); + virtual void timerEvent(QTimerEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + virtual void keyReleaseEvent(QKeyEvent *e); + virtual void resizeEvent(QResizeEvent *e); + virtual void paintEvent(QPaintEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual bool focusNextPrevChild(bool next); +#ifndef QT_NO_CONTEXTMENU + virtual void contextMenuEvent(QContextMenuEvent *e); +#endif +#ifndef QT_NO_DRAGANDDROP + virtual void dragEnterEvent(QDragEnterEvent *e); + virtual void dragLeaveEvent(QDragLeaveEvent *e); + virtual void dragMoveEvent(QDragMoveEvent *e); + virtual void dropEvent(QDropEvent *e); +#endif + virtual void focusInEvent(QFocusEvent *e); + virtual void focusOutEvent(QFocusEvent *e); + virtual void showEvent(QShowEvent *); + virtual void changeEvent(QEvent *e); +#ifndef QT_NO_WHEELEVENT + virtual void wheelEvent(QWheelEvent *e); +#endif + + virtual QMimeData *createMimeDataFromSelection() const; + virtual bool canInsertFromMimeData(const QMimeData *source) const; + virtual void insertFromMimeData(const QMimeData *source); + + virtual void inputMethodEvent(QInputMethodEvent *); + QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + + QTextEdit(QTextEditPrivate &dd, QWidget *parent); + + virtual void scrollContentsBy(int dx, int dy); + +#ifdef QT3_SUPPORT +Q_SIGNALS: + QT_MOC_COMPAT void currentFontChanged(const QFont &f); + QT_MOC_COMPAT void currentColorChanged(const QColor &c); + +public: + QT3_SUPPORT_CONSTRUCTOR QTextEdit(QWidget *parent, const char *name); + inline QT3_SUPPORT bool find(const QString &exp, bool cs, bool wo) + { + QTextDocument::FindFlags flags = 0; + if (cs) + flags |= QTextDocument::FindCaseSensitively; + if (wo) + flags |= QTextDocument::FindWholeWords; + return find(exp, flags); + } + + inline QT3_SUPPORT void sync() {} + + QT3_SUPPORT void moveCursor(CursorAction action, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + QT3_SUPPORT void moveCursor(CursorAction action, bool select); + + enum KeyboardAction { + ActionBackspace, + ActionDelete, + ActionReturn, + ActionKill, + ActionWordBackspace, + ActionWordDelete + }; + + QT3_SUPPORT void doKeyboardAction(KeyboardAction action); + + QT3_SUPPORT QString text() const; + QT3_SUPPORT void setTextFormat(Qt::TextFormat); + QT3_SUPPORT Qt::TextFormat textFormat() const; + + inline QT3_SUPPORT void setBold(bool b) { setFontWeight(b ? QFont::Bold : QFont::Normal); } + inline QT3_SUPPORT void setUnderline(bool b) { setFontUnderline(b); } + inline QT3_SUPPORT void setItalic(bool i) { setFontItalic(i); } + inline QT3_SUPPORT void setFamily(const QString &family) { setFontFamily(family); } + inline QT3_SUPPORT void setPointSize(int size) { setFontPointSize(size); } + + inline QT3_SUPPORT bool italic() const { return fontItalic(); } + inline QT3_SUPPORT bool bold() const { return fontWeight() >= QFont::Bold; } + inline QT3_SUPPORT bool underline() const { return fontUnderline(); } + inline QT3_SUPPORT QString family() const { return fontFamily(); } + inline QT3_SUPPORT int pointSize() const { return (int)(fontPointSize()+0.5); } + + inline QT3_SUPPORT bool hasSelectedText() const + { return textCursor().hasSelection(); } + inline QT3_SUPPORT QString selectedText() const + { return textCursor().selectedText(); } + + inline QT3_SUPPORT bool isUndoAvailable() const + { return document()->isUndoAvailable(); } + inline QT3_SUPPORT bool isRedoAvailable() const + { return document()->isRedoAvailable(); } + + inline QT3_SUPPORT void insert(const QString &text) + { insertPlainText(text); } + + inline QT3_SUPPORT bool isModified() const + { return document()->isModified(); } + + inline QT3_SUPPORT QColor color() const + { return textColor(); } + +public Q_SLOTS: + inline QT_MOC_COMPAT void setModified(bool m = true) + { document()->setModified(m); } +public: + inline QT3_SUPPORT void undo() const + { document()->undo(); } + inline QT3_SUPPORT void redo() const + { document()->redo(); } + +public Q_SLOTS: + inline QT_MOC_COMPAT void setColor(const QColor &c) + { setTextColor(c); } + +#endif + +private: + Q_DISABLE_COPY(QTextEdit) + Q_PRIVATE_SLOT(d_func(), void _q_repaintContents(const QRectF &r)) + Q_PRIVATE_SLOT(d_func(), void _q_currentCharFormatChanged(const QTextCharFormat &)) + Q_PRIVATE_SLOT(d_func(), void _q_adjustScrollbars()) + Q_PRIVATE_SLOT(d_func(), void _q_ensureVisible(const QRectF &)) + friend class QTextEditControl; + friend class QTextDocument; + friend class QTextControl; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextEdit::AutoFormatting) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_TEXTEDIT + +#endif // QTEXTEDIT_H diff --git a/src/gui/widgets/qtextedit_p.h b/src/gui/widgets/qtextedit_p.h new file mode 100644 index 0000000..3c37868 --- /dev/null +++ b/src/gui/widgets/qtextedit_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTEDIT_P_H +#define QTEXTEDIT_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/qabstractscrollarea_p.h" +#include "QtGui/qtextdocumentfragment.h" +#include "QtGui/qscrollbar.h" +#include "QtGui/qtextcursor.h" +#include "QtGui/qtextformat.h" +#include "QtGui/qmenu.h" +#include "QtGui/qabstracttextdocumentlayout.h" +#include "QtCore/qbasictimer.h" +#include "QtCore/qurl.h" +#include "private/qtextcontrol_p.h" +#include "qtextedit.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TEXTEDIT + +class QMimeData; + +class QTextEditPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QTextEdit) +public: + QTextEditPrivate(); + + void init(const QString &html = QString()); + void paint(QPainter *p, QPaintEvent *e); + void _q_repaintContents(const QRectF &contentsRect); + + inline QPoint mapToContents(const QPoint &point) const + { return QPoint(point.x() + horizontalOffset(), point.y() + verticalOffset()); } + + void _q_adjustScrollbars(); + void _q_ensureVisible(const QRectF &rect); + void relayoutDocument(); + + void createAutoBulletList(); + void pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode); + + inline int horizontalOffset() const + { return q_func()->isRightToLeft() ? (hbar->maximum() - hbar->value()) : hbar->value(); } + inline int verticalOffset() const + { return vbar->value(); } + + inline void sendControlEvent(QEvent *e) + { control->processEvent(e, QPointF(horizontalOffset(), verticalOffset()), viewport); } + + void _q_currentCharFormatChanged(const QTextCharFormat &format); + + void updateDefaultTextOption(); + + // re-implemented by QTextBrowser, called by QTextDocument::loadResource + virtual QUrl resolveUrl(const QUrl &url) const + { return url; } + + QTextControl *control; + + QTextEdit::AutoFormatting autoFormatting; + bool tabChangesFocus; + + QBasicTimer autoScrollTimer; + QPoint autoScrollDragPos; + + QTextEdit::LineWrapMode lineWrap; + int lineWrapColumnOrWidth; + QTextOption::WrapMode wordWrap; + + uint ignoreAutomaticScrollbarAdjustment : 1; + uint preferRichText : 1; + uint showCursorOnInitialShow : 1; + uint inDrag : 1; + + // Qt3 COMPAT only, for setText + Qt::TextFormat textFormat; + + QString anchorToScrollToWhenVisible; + +#ifdef QT_KEYPAD_NAVIGATION + QBasicTimer deleteAllTimer; +#endif +}; +#endif // QT_NO_TEXTEDIT + + +QT_END_NAMESPACE + +#endif // QTEXTEDIT_P_H diff --git a/src/gui/widgets/qtoolbar.cpp b/src/gui/widgets/qtoolbar.cpp new file mode 100644 index 0000000..85d6ea2 --- /dev/null +++ b/src/gui/widgets/qtoolbar.cpp @@ -0,0 +1,1291 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtoolbar.h" + +#ifndef QT_NO_TOOLBAR + +#include <qapplication.h> +#include <qcombobox.h> +#include <qevent.h> +#include <qlayout.h> +#include <qmainwindow.h> +#include <qmenu.h> +#include <qmenubar.h> +#include <qrubberband.h> +#include <qsignalmapper.h> +#include <qstylepainter.h> +#include <qtoolbutton.h> +#include <qwidgetaction.h> +#include <qtimer.h> +#include <private/qwidgetaction_p.h> +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#endif + +#include <private/qmainwindowlayout_p.h> + +#include "qtoolbar_p.h" +#include "qtoolbarseparator_p.h" +#include "qtoolbarlayout_p.h" + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_MAC +static void qt_mac_updateToolBarButtonHint(QWidget *parentWidget) +{ + if (!(parentWidget->windowFlags() & Qt::CustomizeWindowHint)) + parentWidget->setWindowFlags(parentWidget->windowFlags() | Qt::MacWindowToolBarButtonHint); +} +#endif + +/****************************************************************************** +** QToolBarPrivate +*/ + +void QToolBarPrivate::init() +{ + Q_Q(QToolBar); + + waitForPopupTimer = new QTimer(q); + waitForPopupTimer->setSingleShot(false); + waitForPopupTimer->setInterval(500); + QObject::connect(waitForPopupTimer, SIGNAL(timeout()), q, SLOT(_q_waitForPopup())); + + floatable = true; + movable = true; + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + q->setBackgroundRole(QPalette::Button); + q->setAttribute(Qt::WA_Hover); + q->setAttribute(Qt::WA_X11NetWmWindowTypeToolBar); + + QStyle *style = q->style(); + int e = style->pixelMetric(QStyle::PM_ToolBarIconSize, 0, q); + iconSize = QSize(e, e); + + layout = new QToolBarLayout(q); + layout->updateMarginAndSpacing(); + +#ifdef Q_WS_MAC + if (q->parentWidget() && q->parentWidget()->isWindow()) { + // Make sure that the window has the "toolbar" button. + QWidget *parentWidget = q->parentWidget(); + qt_mac_updateToolBarButtonHint(parentWidget); + reinterpret_cast<QToolBar *>(parentWidget)->d_func()->createWinId(); // Please let me create your winId... + extern OSWindowRef qt_mac_window_for(const QWidget *); // qwidget_mac.cpp + macWindowToolbarShow(q->parentWidget(), true); + } +#endif + + toggleViewAction = new QAction(q); + toggleViewAction->setCheckable(true); + q->setMovable(q->style()->styleHint(QStyle::SH_ToolBar_Movable, 0, q )); + QObject::connect(toggleViewAction, SIGNAL(triggered(bool)), q, SLOT(_q_toggleView(bool))); +} + +void QToolBarPrivate::_q_toggleView(bool b) +{ + Q_Q(QToolBar); + if (b == q->isHidden()) { + if (b) + q->show(); + else + q->close(); + } +} + +void QToolBarPrivate::_q_updateIconSize(const QSize &sz) +{ + Q_Q(QToolBar); + if (!explicitIconSize) { + // iconSize not explicitly set + q->setIconSize(sz); + explicitIconSize = false; + } +} + +void QToolBarPrivate::_q_updateToolButtonStyle(Qt::ToolButtonStyle style) +{ + Q_Q(QToolBar); + if (!explicitToolButtonStyle) { + q->setToolButtonStyle(style); + explicitToolButtonStyle = false; + } +} + +void QToolBarPrivate::updateWindowFlags(bool floating, bool unplug) +{ + Q_Q(QToolBar); + Qt::WindowFlags flags = floating ? Qt::Tool : Qt::Widget; + + flags |= Qt::FramelessWindowHint; + + if (unplug) { + flags |= Qt::X11BypassWindowManagerHint; +#ifdef Q_WS_MAC + flags |= Qt::WindowStaysOnTopHint; +#endif + } + + q->setWindowFlags(flags); +} + +void QToolBarPrivate::setWindowState(bool floating, bool unplug, const QRect &rect) +{ + Q_Q(QToolBar); + bool visible = !q->isHidden(); + bool wasFloating = q->isFloating(); // ...is also currently using popup menus + + q->hide(); + + updateWindowFlags(floating, unplug); + + if (floating != wasFloating) + layout->checkUsePopupMenu(); + + if (!rect.isNull()) + q->setGeometry(rect); + + if (visible) + q->show(); +} + +void QToolBarPrivate::initDrag(const QPoint &pos) +{ + Q_Q(QToolBar); + + if (state != 0) + return; + + QMainWindow *win = qobject_cast<QMainWindow*>(q->parentWidget()); + Q_ASSERT(win != 0); + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(win->layout()); + Q_ASSERT(layout != 0); + if (layout->pluggingWidget != 0) // the main window is animating a docking operation + return; + + state = new DragState; + state->pressPos = pos; + state->dragging = false; + state->moving = false; + state->widgetItem = 0; + + if (q->layoutDirection() == Qt::RightToLeft) + state->pressPos = QPoint(q->width() - state->pressPos.x(), state->pressPos.y()); +} + +void QToolBarPrivate::startDrag(bool moving) +{ + Q_Q(QToolBar); + + Q_ASSERT(state != 0); + + if ((moving && state->moving) || state->dragging) + return; + + QMainWindow *win = qobject_cast<QMainWindow*>(q->parentWidget()); + Q_ASSERT(win != 0); + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(win->layout()); + Q_ASSERT(layout != 0); + + if (!moving) { + state->widgetItem = layout->unplug(q); +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + if (q->isWindow()) { + setWindowState(true, true); //set it to floating + } +#endif + Q_ASSERT(state->widgetItem != 0); + } + state->dragging = !moving; + state->moving = moving; +} + +void QToolBarPrivate::endDrag() +{ + Q_Q(QToolBar); + Q_ASSERT(state != 0); + + q->releaseMouse(); + + if (state->dragging) { + QMainWindowLayout *layout = + qobject_cast<QMainWindowLayout *>(q->parentWidget()->layout()); + Q_ASSERT(layout != 0); + + if (!layout->plug(state->widgetItem)) { + if (q->isFloatable()) { + layout->restore(); +#if defined(Q_WS_X11) || defined(Q_WS_MAC) + setWindowState(true); // gets rid of the X11BypassWindowManager window flag + // and activates the resizer +#endif + q->activateWindow(); + } else { + layout->revert(state->widgetItem); + } + } + } + + delete state; + state = 0; +} + +bool QToolBarPrivate::mousePressEvent(QMouseEvent *event) +{ + if (layout->handleRect().contains(event->pos()) == false) { +#ifdef Q_WS_MAC + Q_Q(QToolBar); + // When using the unified toolbar on Mac OS X the user can can click and + // drag between toolbar contents to move the window. Make this work by + // implementing the standard mouse-dragging code and then call + // window->move() in mouseMoveEvent below. + if (QMainWindow *mainWindow = qobject_cast<QMainWindow *>(q->parentWidget())) { + if (mainWindow->toolBarArea(q) == Qt::TopToolBarArea + && mainWindow->unifiedTitleAndToolBarOnMac() + && q->childAt(event->pos()) == 0) { + macWindowDragging = true; + macWindowDragPressPosition = event->pos(); + return true; + } + } +#endif + return false; + } + + if (event->button() != Qt::LeftButton) + return true; + + if (!layout->movable()) + return true; + + initDrag(event->pos()); + return true; +} + +bool QToolBarPrivate::mouseReleaseEvent(QMouseEvent*) +{ + if (state != 0) { + endDrag(); + return true; + } else { +#ifdef Q_WS_MAC + macWindowDragging = false; + macWindowDragPressPosition = QPoint(); + return true; +#endif + return false; + } +} + +bool QToolBarPrivate::mouseMoveEvent(QMouseEvent *event) +{ + Q_Q(QToolBar); + + if (!state) { +#ifdef Q_WS_MAC + if (!macWindowDragging) + return false; + QWidget *w = q->window(); + const QPoint delta = event->pos() - macWindowDragPressPosition; + w->move(w->pos() + delta); + return true; +#endif + return false; + } + + QMainWindow *win = qobject_cast<QMainWindow*>(q->parentWidget()); + if (win == 0) + return true; + + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(win->layout()); + Q_ASSERT(layout != 0); + + if (layout->pluggingWidget == 0 + && (event->pos() - state->pressPos).manhattanLength() > QApplication::startDragDistance()) { + const bool wasDragging = state->dragging; + const bool moving = !q->isWindow() && (orientation == Qt::Vertical ? + event->x() >= 0 && event->x() < q->width() : + event->y() >= 0 && event->y() < q->height()); + + startDrag(moving); + if (!moving && !wasDragging) { +#ifdef Q_OS_WIN + grabMouseWhileInWindow(); +#else + q->grabMouse(); +#endif + } + } + + if (state->dragging) { + QPoint pos = event->globalPos(); + // if we are right-to-left, we move so as to keep the right edge the same distance + // from the mouse + if (q->layoutDirection() == Qt::LeftToRight) + pos -= state->pressPos; + else + pos += QPoint(state->pressPos.x() - q->width(), -state->pressPos.y()); + + q->move(pos); + layout->hover(state->widgetItem, event->globalPos()); + } else if (state->moving) { + + const QPoint rtl(q->width() - state->pressPos.x(), state->pressPos.y()); //for RTL + const QPoint globalPressPos = q->mapToGlobal(q->layoutDirection() == Qt::RightToLeft ? rtl : state->pressPos); + int pos = 0; + + QPoint delta = event->globalPos() - globalPressPos; + if (orientation == Qt::Vertical) { + pos = q->y() + delta.y(); + } else { + if (q->layoutDirection() == Qt::RightToLeft) { + pos = win->width() - q->width() - q->x() - delta.x(); + } else { + pos = q->x() + delta.x(); + } + } + + layout->moveToolBar(q, pos); + } + return true; +} + +void QToolBarPrivate::unplug(const QRect &_r) +{ + Q_Q(QToolBar); + + QRect r = _r; + r.moveTopLeft(q->mapToGlobal(QPoint(0, 0))); + setWindowState(true, true, r); +} + +void QToolBarPrivate::plug(const QRect &r) +{ + setWindowState(false, false, r); +} + +/****************************************************************************** +** QToolBar +*/ + +/*! + \class QToolBar + + \brief The QToolBar class provides a movable panel that contains a + set of controls. + + \ingroup application + \mainclass + + Toolbar buttons are added by adding \e actions, using addAction() + or insertAction(). Groups of buttons can be separated using + addSeparator() or insertSeparator(). If a toolbar button is not + appropriate, a widget can be inserted instead using addWidget() or + insertWidget(); examples of suitable widgets are QSpinBox, + QDoubleSpinBox, and QComboBox. When a toolbar button is pressed it + emits the actionTriggered() signal. + + A toolbar can be fixed in place in a particular area (e.g. at the + top of the window), or it can be movable (isMovable()) between + toolbar areas; see allowedAreas() and isAreaAllowed(). + + When a toolbar is resized in such a way that it is too small to + show all the items it contains, an extension button will appear as + the last item in the toolbar. Pressing the extension button will + pop up a menu containing the items that does not currently fit in + the toolbar. + + When a QToolBar is not a child of a QMainWindow, it looses the ability + to populate the extension pop up with widgets added to the toolbar using + addWidget(). Please use widget actions created by inheriting QWidgetAction + and implementing QWidgetAction::createWidget() instead. This is a known + issue which will be fixed in a future release. + + \sa QToolButton, QMenu, QAction, {Application Example} +*/ + +/*! + \fn bool QToolBar::isAreaAllowed(Qt::ToolBarArea area) const + + Returns true if this toolbar is dockable in the given \a area; + otherwise returns false. +*/ + +/*! + \fn void QToolBar::addAction(QAction *action) + \overload + + Appends the action \a action to the toolbar's list of actions. + + \sa QMenu::addAction(), QWidget::addAction() +*/ + +/*! + \fn void QToolBar::actionTriggered(QAction *action) + + This signal is emitted when an action in this toolbar is triggered. + This happens when the action's tool button is pressed, or when the + action is triggered in some other way outside the tool bar. The parameter + holds the triggered \a action. +*/ + +/*! + \fn void QToolBar::allowedAreasChanged(Qt::ToolBarAreas allowedAreas) + + This signal is emitted when the collection of allowed areas for the + toolbar is changed. The new areas in which the toolbar can be positioned + are specified by \a allowedAreas. + + \sa allowedAreas +*/ + +/*! + \fn void QToolBar::iconSizeChanged(const QSize &iconSize) + + This signal is emitted when the icon size is changed. The \a + iconSize parameter holds the toolbar's new icon size. + + \sa iconSize QMainWindow::iconSize +*/ + +/*! + \fn void QToolBar::movableChanged(bool movable) + + This signal is emitted when the toolbar becomes movable or fixed. + If the toolbar can be moved, \a movable is true; otherwise it is + false. + + \sa movable +*/ + +/*! + \fn void QToolBar::orientationChanged(Qt::Orientation orientation) + + This signal is emitted when the orientation of the toolbar changes. + The new orientation is specified by the \a orientation given. + + \sa orientation +*/ + +/*! + \fn void QToolBar::toolButtonStyleChanged(Qt::ToolButtonStyle toolButtonStyle) + + This signal is emitted when the tool button style is changed. The + \a toolButtonStyle parameter holds the toolbar's new tool button + style. + + \sa toolButtonStyle QMainWindow::toolButtonStyle +*/ + +/*! + Constructs a QToolBar with the given \a parent. +*/ +QToolBar::QToolBar(QWidget *parent) + : QWidget(*new QToolBarPrivate, parent, 0) +{ + Q_D(QToolBar); + d->init(); +} + +/*! + Constructs a QToolBar with the given \a parent. + + The given window \a title identifies the toolbar and is shown in + the context menu provided by QMainWindow. + + \sa setWindowTitle() +*/ +QToolBar::QToolBar(const QString &title, QWidget *parent) + : QWidget(*new QToolBarPrivate, parent, 0) +{ + Q_D(QToolBar); + d->init(); + setWindowTitle(title); +} + +#ifdef QT3_SUPPORT +/*! \obsolete + Constructs a QToolBar with the given \a parent and \a name. +*/ +QToolBar::QToolBar(QWidget *parent, const char *name) + : QWidget(*new QToolBarPrivate, parent, 0) +{ + Q_D(QToolBar); + d->init(); + setObjectName(QString::fromAscii(name)); +} +#endif + +/*! + Destroys the toolbar. +*/ +QToolBar::~QToolBar() +{ + // Remove the toolbar button if there is nothing left. + QMainWindow *mainwindow = qobject_cast<QMainWindow *>(parentWidget()); + if (mainwindow) { +#ifdef Q_WS_MAC + QMainWindowLayout *mainwin_layout = qobject_cast<QMainWindowLayout *>(mainwindow->layout()); + if (mainwin_layout && mainwin_layout->layoutState.toolBarAreaLayout.isEmpty() + && mainwindow->testAttribute(Qt::WA_WState_Created)) + macWindowToolbarShow(mainwindow, false); +#endif + } +} + +/*! \property QToolBar::movable + \brief whether the user can move the toolbar within the toolbar area, + or between toolbar areas + + By default, this property is true. + + This property only makes sense if the toolbar is in a + QMainWindow. + + \sa allowedAreas +*/ + +void QToolBar::setMovable(bool movable) +{ + Q_D(QToolBar); + if (!movable == !d->movable) + return; + d->movable = movable; + d->layout->invalidate(); + emit movableChanged(d->movable); +} + +bool QToolBar::isMovable() const +{ + Q_D(const QToolBar); + return d->movable; +} + +/*! + \property QToolBar::floatable + \brief whether the toolbar can be dragged and dropped as an independent window. + + The default is true. +*/ +bool QToolBar::isFloatable() const +{ + Q_D(const QToolBar); + return d->floatable; +} + +void QToolBar::setFloatable(bool floatable) +{ + Q_D(QToolBar); + d->floatable = floatable; +} + +/*! + \property QToolBar::floating + \brief whether the toolbar is an independent window. + + By default, this property is true. + + \sa QWidget::isWindow() +*/ +bool QToolBar::isFloating() const +{ + return isWindow(); +} + +/*! + \property QToolBar::allowedAreas + \brief areas where the toolbar may be placed + + The default is Qt::AllToolBarAreas. + + This property only makes sense if the toolbar is in a + QMainWindow. + + \sa movable +*/ + +void QToolBar::setAllowedAreas(Qt::ToolBarAreas areas) +{ + Q_D(QToolBar); + areas &= Qt::ToolBarArea_Mask; + if (areas == d->allowedAreas) + return; + d->allowedAreas = areas; + emit allowedAreasChanged(d->allowedAreas); +} + +Qt::ToolBarAreas QToolBar::allowedAreas() const +{ + Q_D(const QToolBar); +#ifdef Q_WS_MAC + if (QMainWindow *window = qobject_cast<QMainWindow *>(parentWidget())) { + if (window->unifiedTitleAndToolBarOnMac()) // Don't allow drags to the top (for now). + return (d->allowedAreas & ~Qt::TopToolBarArea); + } +#endif + return d->allowedAreas; +} + +/*! \property QToolBar::orientation + \brief orientation of the toolbar + + The default is Qt::Horizontal. + + This function should not be used when the toolbar is managed + by QMainWindow. You can use QMainWindow::addToolBar() or + QMainWindow::insertToolBar() if you wish to move a toolbar (that + is already added to a main window) to another Qt::ToolBarArea. +*/ + +void QToolBar::setOrientation(Qt::Orientation orientation) +{ + Q_D(QToolBar); + if (orientation == d->orientation) + return; + + d->orientation = orientation; + + if (orientation == Qt::Vertical) + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred)); + else + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + + d->layout->invalidate(); + d->layout->activate(); + + emit orientationChanged(d->orientation); +} + +Qt::Orientation QToolBar::orientation() const +{ Q_D(const QToolBar); return d->orientation; } + +/*! + \property QToolBar::iconSize + \brief size of icons in the toolbar. + + The default size is determined by the application's style and is + derived from the QStyle::PM_ToolBarIconSize pixel metric. It is + the maximum size an icon can have. Icons of smaller size will not + be scaled up. +*/ + +QSize QToolBar::iconSize() const +{ Q_D(const QToolBar); return d->iconSize; } + +void QToolBar::setIconSize(const QSize &iconSize) +{ + Q_D(QToolBar); + QSize sz = iconSize; + if (!sz.isValid()) { + QMainWindow *mw = qobject_cast<QMainWindow *>(parentWidget()); + if (mw && mw->layout()) { + QLayout *layout = mw->layout(); + int i = 0; + QLayoutItem *item = 0; + do { + item = layout->itemAt(i++); + if (item && (item->widget() == this)) + sz = mw->iconSize(); + } while (!sz.isValid() && item != 0); + } + } + if (!sz.isValid()) { + const int metric = style()->pixelMetric(QStyle::PM_ToolBarIconSize, 0, this); + sz = QSize(metric, metric); + } + if (d->iconSize != sz) { + d->iconSize = sz; + setMinimumSize(0, 0); + emit iconSizeChanged(d->iconSize); + } + d->explicitIconSize = iconSize.isValid(); + + d->layout->invalidate(); +} + +/*! + \property QToolBar::toolButtonStyle + \brief the style of toolbar buttons + + This property defines the style of all tool buttons that are added + as \l{QAction}s. Note that if you add a QToolButton with the + addWidget() method, it will not get this button style. + + The default is Qt::ToolButtonIconOnly. +*/ + +Qt::ToolButtonStyle QToolBar::toolButtonStyle() const +{ Q_D(const QToolBar); return d->toolButtonStyle; } + +void QToolBar::setToolButtonStyle(Qt::ToolButtonStyle toolButtonStyle) +{ + Q_D(QToolBar); + d->explicitToolButtonStyle = true; + if (d->toolButtonStyle == toolButtonStyle) + return; + d->toolButtonStyle = toolButtonStyle; + setMinimumSize(0, 0); + emit toolButtonStyleChanged(d->toolButtonStyle); +} + +/*! + Removes all actions from the toolbar. + + \sa removeAction() +*/ +void QToolBar::clear() +{ + QList<QAction *> actions = this->actions(); + for(int i = 0; i < actions.size(); i++) + removeAction(actions.at(i)); +} + +/*! + \overload + + Creates a new action with the given \a text. This action is added to + the end of the toolbar. +*/ +QAction *QToolBar::addAction(const QString &text) +{ + QAction *action = new QAction(text, this); + addAction(action); + return action; +} + +/*! + \overload + + Creates a new action with the given \a icon and \a text. This + action is added to the end of the toolbar. +*/ +QAction *QToolBar::addAction(const QIcon &icon, const QString &text) +{ + QAction *action = new QAction(icon, text, this); + addAction(action); + return action; +} + +/*! + \overload + + Creates a new action with the given \a text. This action is added to + the end of the toolbar. The action's \link QAction::triggered() + triggered()\endlink signal is connected to \a member in \a + receiver. +*/ +QAction *QToolBar::addAction(const QString &text, + const QObject *receiver, const char* member) +{ + QAction *action = new QAction(text, this); + QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); + addAction(action); + return action; +} + +/*! + \overload + + Creates a new action with the icon \a icon and text \a text. This + action is added to the end of the toolbar. The action's \link + QAction::triggered() triggered()\endlink signal is connected to \a + member in \a receiver. +*/ +QAction *QToolBar::addAction(const QIcon &icon, const QString &text, + const QObject *receiver, const char* member) +{ + QAction *action = new QAction(icon, text, this); + QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); + addAction(action); + return action; +} + +/*! + Adds a separator to the end of the toolbar. + + \sa insertSeparator() +*/ +QAction *QToolBar::addSeparator() +{ + QAction *action = new QAction(this); + action->setSeparator(true); + addAction(action); + return action; +} + +/*! + Inserts a separator into the toolbar in front of the toolbar + item associated with the \a before action. + + \sa addSeparator() +*/ +QAction *QToolBar::insertSeparator(QAction *before) +{ + QAction *action = new QAction(this); + action->setSeparator(true); + insertAction(before, action); + return action; +} + +/*! + Adds the given \a widget to the toolbar as the toolbar's last + item. + + The toolbar takes ownership of \a widget. + + If you add a QToolButton with this method, the tools bar's + Qt::ToolButtonStyle will not be respected. + + Note: You should use QAction::setVisible() to change the + visibility of the widget. Using QWidget::setVisible(), + QWidget::show() and QWidget::hide() does not work. + + \sa insertWidget() +*/ +QAction *QToolBar::addWidget(QWidget *widget) +{ + QWidgetAction *action = new QWidgetAction(this); + action->setDefaultWidget(widget); + action->d_func()->autoCreated = true; + addAction(action); + return action; +} + +/*! + Inserts the given \a widget in front of the toolbar item + associated with the \a before action. + + Note: You should use QAction::setVisible() to change the + visibility of the widget. Using QWidget::setVisible(), + QWidget::show() and QWidget::hide() does not work. + + \sa addWidget() +*/ +QAction *QToolBar::insertWidget(QAction *before, QWidget *widget) +{ + QWidgetAction *action = new QWidgetAction(this); + action->setDefaultWidget(widget); + action->d_func()->autoCreated = true; + insertAction(before, action); + return action; +} + +/*! + \internal + + Returns the geometry of the toolbar item associated with the given + \a action, or an invalid QRect if no matching item is found. +*/ +QRect QToolBar::actionGeometry(QAction *action) const +{ + Q_D(const QToolBar); + + int index = d->layout->indexOf(action); + if (index == -1) + return QRect(); + return d->layout->itemAt(index)->widget()->geometry(); +} + +/*! + Returns the action at point \a p. This function returns zero if no + action was found. + + \sa QWidget::childAt() +*/ +QAction *QToolBar::actionAt(const QPoint &p) const +{ + Q_D(const QToolBar); + QWidget *widget = childAt(p); + int index = d->layout->indexOf(widget); + if (index == -1) + return 0; + QLayoutItem *item = d->layout->itemAt(index); + return static_cast<QToolBarItem*>(item)->action; +} + +/*! \fn QAction *QToolBar::actionAt(int x, int y) const + \overload + + Returns the action at the point \a x, \a y. This function returns + zero if no action was found. +*/ + +/*! \reimp */ +void QToolBar::actionEvent(QActionEvent *event) +{ + Q_D(QToolBar); + QAction *action = event->action(); + QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(action); + + switch (event->type()) { + case QEvent::ActionAdded: { + Q_ASSERT_X(widgetAction == 0 || d->layout->indexOf(widgetAction) == -1, + "QToolBar", "widgets cannot be inserted multiple times"); + + // reparent the action to this toolbar if it has been created + // using the addAction(text) etc. convenience functions, to + // preserve Qt 4.1.x behavior. The widget is already + // reparented to us due to the createWidget call inside + // createItem() + if (widgetAction != 0 && widgetAction->d_func()->autoCreated) + widgetAction->setParent(this); + + int index = d->layout->count(); + if (event->before()) { + index = d->layout->indexOf(event->before()); + Q_ASSERT_X(index != -1, "QToolBar::insertAction", "internal error"); + } + d->layout->insertAction(index, action); + break; + } + + case QEvent::ActionChanged: + d->layout->invalidate(); + break; + + case QEvent::ActionRemoved: { + int index = d->layout->indexOf(action); + if (index != -1) { + delete d->layout->takeAt(index); + } + break; + } + + default: + Q_ASSERT_X(false, "QToolBar::actionEvent", "internal error"); + } +} + +/*! \reimp */ +void QToolBar::changeEvent(QEvent *event) +{ + Q_D(QToolBar); + switch (event->type()) { + case QEvent::WindowTitleChange: + d->toggleViewAction->setText(windowTitle()); + break; + case QEvent::StyleChange: + d->layout->invalidate(); + if (!d->explicitIconSize) + setIconSize(QSize()); + d->layout->updateMarginAndSpacing(); + break; + case QEvent::LayoutDirectionChange: + d->layout->invalidate(); + break; + default: + break; + } + QWidget::changeEvent(event); +} + +/*! \reimp */ +void QToolBar::paintEvent(QPaintEvent *) +{ + Q_D(QToolBar); + + QPainter p(this); + QStyle *style = this->style(); + QStyleOptionToolBar opt; + initStyleOption(&opt); + + if (d->layout->expanded || d->layout->animating || isWindow()) { + //if the toolbar is expended, we need to fill the background with the window color + //because some styles may expects that. + p.fillRect(opt.rect, palette().background()); + style->drawControl(QStyle::CE_ToolBar, &opt, &p, this); + style->drawPrimitive(QStyle::PE_FrameMenu, &opt, &p, this); + } else { + style->drawControl(QStyle::CE_ToolBar, &opt, &p, this); + } + + opt.rect = d->layout->handleRect(); + if (opt.rect.isValid()) + style->drawPrimitive(QStyle::PE_IndicatorToolBarHandle, &opt, &p, this); +} + +/* + Checks if an expanded toolbar has to wait for this popup to close before + the toolbar collapses. This is true if + 1) the popup has the toolbar in its parent chain, + 2) the popup is a menu whose menuAction is somewhere in the toolbar. +*/ +static bool waitForPopup(QToolBar *tb, QWidget *popup) +{ + if (popup == 0 || popup->isHidden()) + return false; + + QWidget *w = popup; + while (w != 0) { + if (w == tb) + return true; + w = w->parentWidget(); + } + + QMenu *menu = qobject_cast<QMenu*>(popup); + if (menu == 0) + return false; + + QAction *action = menu->menuAction(); + QList<QWidget*> widgets = action->associatedWidgets(); + for (int i = 0; i < widgets.count(); ++i) { + if (waitForPopup(tb, widgets.at(i))) + return true; + } + + return false; +} + +/*! \reimp */ +bool QToolBar::event(QEvent *event) +{ + Q_D(QToolBar); + + switch (event->type()) { + case QEvent::Hide: + if (!isHidden()) + break; + // fallthrough intended + case QEvent::Show: + d->toggleViewAction->setChecked(event->type() == QEvent::Show); +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + // Fall through + case QEvent::LayoutRequest: { + // There's currently no way to invalidate the size and let + // HIToolbar know about it. This forces a re-check. + int earlyResult = -1; + if (QMainWindow *mainWindow = qobject_cast<QMainWindow *>(parentWidget())) { + bool needUpdate = true; + if (event->type() == QEvent::LayoutRequest) { + QSize oldSizeHint = sizeHint(); + earlyResult = QWidget::event(event) ? 1 : 0; + needUpdate = oldSizeHint != sizeHint(); + } + + if (needUpdate) { + OSWindowRef windowRef = qt_mac_window_for(this); + if (mainWindow->unifiedTitleAndToolBarOnMac() + && mainWindow->toolBarArea(this) == Qt::TopToolBarArea + && macWindowToolbarVisible(windowRef)) { + DisableScreenUpdates(); + macWindowToolbarShow(this, false); + macWindowToolbarShow(this, true); + EnableScreenUpdates(); + } + } + + if (earlyResult != -1) + return earlyResult; + } + } +#endif + break; + case QEvent::ParentChange: + d->layout->checkUsePopupMenu(); +#if defined(Q_WS_MAC) + if (parentWidget() && parentWidget()->isWindow()) + qt_mac_updateToolBarButtonHint(parentWidget()); +#endif + break; + + case QEvent::MouseButtonPress: { + if (d->mousePressEvent(static_cast<QMouseEvent*>(event))) + return true; + break; + } + case QEvent::MouseButtonRelease: + if (d->mouseReleaseEvent(static_cast<QMouseEvent*>(event))) + return true; + break; + case QEvent::HoverMove: { +#ifndef QT_NO_CURSOR + QHoverEvent *e = static_cast<QHoverEvent*>(event); + if (d->layout->handleRect().contains(e->pos())) + setCursor(Qt::SizeAllCursor); + else + unsetCursor(); +#endif + break; + } + case QEvent::MouseMove: + if (d->mouseMoveEvent(static_cast<QMouseEvent*>(event))) + return true; + break; + case QEvent::Leave: + if (d->state != 0 && d->state->dragging) { +#ifdef Q_OS_WIN + // This is a workaround for loosing the mouse on Vista. + QPoint pos = QCursor::pos(); + QMouseEvent fake(QEvent::MouseMove, mapFromGlobal(pos), pos, Qt::NoButton, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + d->mouseMoveEvent(&fake); +#endif + } else { + if (!d->layout->expanded) + break; + + QWidget *w = qApp->activePopupWidget(); + if (waitForPopup(this, w)) { + d->waitForPopupTimer->start(); + break; + } + + d->waitForPopupTimer->stop(); + d->layout->setExpanded(false); + break; + } + default: + break; + } + return QWidget::event(event); +} + +void QToolBarPrivate::_q_waitForPopup() +{ + Q_Q(QToolBar); + + QWidget *w = qApp->activePopupWidget(); + if (!waitForPopup(q, w)) { + waitForPopupTimer->stop(); + if (!q->underMouse()) + layout->setExpanded(false); + } +} + +/*! + Returns a checkable action that can be used to show or hide this + toolbar. + + The action's text is set to the toolbar's window title. + + \sa QAction::text QWidget::windowTitle +*/ +QAction *QToolBar::toggleViewAction() const +{ Q_D(const QToolBar); return d->toggleViewAction; } + +/*! + \fn void QToolBar::setLabel(const QString &label) + + Use setWindowTitle() instead. +*/ + +/*! + \fn QString QToolBar::label() const + + Use windowTitle() instead. +*/ + +/*! + \since 4.2 + + Returns the widget associated with the specified \a action. + + \sa addWidget() +*/ +QWidget *QToolBar::widgetForAction(QAction *action) const +{ + Q_D(const QToolBar); + + int index = d->layout->indexOf(action); + if (index == -1) + return 0; + + return d->layout->itemAt(index)->widget(); +} + +/*! + \internal +*/ +void QToolBar::initStyleOption(QStyleOptionToolBar *option) const +{ + Q_D(const QToolBar); + + if (!option) + return; + + option->initFrom(this); + if (orientation() == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; + option->lineWidth = style()->pixelMetric(QStyle::PM_ToolBarFrameWidth, 0, this); + option->features = d->layout->movable() + ? QStyleOptionToolBar::Movable + : QStyleOptionToolBar::None; + // if the tool bar is not in a QMainWindow, this will make the painting right + option->toolBarArea = Qt::NoToolBarArea; + + // Add more styleoptions if the toolbar has been added to a mainwindow. + QMainWindow *mainWindow = qobject_cast<QMainWindow *>(parentWidget()); + + if (!mainWindow) + return; + + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout *>(mainWindow->layout()); + Q_ASSERT_X(layout != 0, "QToolBar::initStyleOption()", + "QMainWindow->layout() != QMainWindowLayout"); + + layout->getStyleOptionInfo(option, const_cast<QToolBar *>(this)); +} + +/*! + \reimp +*/ +void QToolBar::childEvent(QChildEvent *event) // ### remove me in 5.0 +{ + QWidget::childEvent(event); +} + +/*! + \reimp +*/ +void QToolBar::resizeEvent(QResizeEvent *event) // ### remove me in 5.0 +{ + QWidget::resizeEvent(event); +} + +QT_END_NAMESPACE + +#include "moc_qtoolbar.cpp" + +#endif // QT_NO_TOOLBAR diff --git a/src/gui/widgets/qtoolbar.h b/src/gui/widgets/qtoolbar.h new file mode 100644 index 0000000..33477d5 --- /dev/null +++ b/src/gui/widgets/qtoolbar.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICTOOLBAR_H +#define QDYNAMICTOOLBAR_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TOOLBAR + +class QToolBarPrivate; + +class QAction; +class QIcon; +class QMainWindow; +class QStyleOptionToolBar; + +class Q_GUI_EXPORT QToolBar : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool movable READ isMovable WRITE setMovable + DESIGNABLE (qobject_cast<QMainWindow *>(parentWidget()) != 0) + NOTIFY movableChanged) + Q_PROPERTY(Qt::ToolBarAreas allowedAreas READ allowedAreas WRITE setAllowedAreas + DESIGNABLE (qobject_cast<QMainWindow *>(parentWidget()) != 0) + NOTIFY allowedAreasChanged) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation + DESIGNABLE (qobject_cast<QMainWindow *>(parentWidget()) == 0) + NOTIFY orientationChanged) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged) + Q_PROPERTY(Qt::ToolButtonStyle toolButtonStyle READ toolButtonStyle WRITE setToolButtonStyle + NOTIFY toolButtonStyleChanged) + Q_PROPERTY(bool floating READ isFloating) + Q_PROPERTY(bool floatable READ isFloatable WRITE setFloatable) + +public: + explicit QToolBar(const QString &title, QWidget *parent = 0); + explicit QToolBar(QWidget *parent = 0); + ~QToolBar(); + + void setMovable(bool movable); + bool isMovable() const; + + void setAllowedAreas(Qt::ToolBarAreas areas); + Qt::ToolBarAreas allowedAreas() const; + + inline bool isAreaAllowed(Qt::ToolBarArea area) const + { return (allowedAreas() & area) == area; } + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + void clear(); + +#ifdef Q_NO_USING_KEYWORD + inline void addAction(QAction *action) + { QWidget::addAction(action); } +#else + using QWidget::addAction; +#endif + + QAction *addAction(const QString &text); + QAction *addAction(const QIcon &icon, const QString &text); + QAction *addAction(const QString &text, const QObject *receiver, const char* member); + QAction *addAction(const QIcon &icon, const QString &text, + const QObject *receiver, const char* member); + + QAction *addSeparator(); + QAction *insertSeparator(QAction *before); + + QAction *addWidget(QWidget *widget); + QAction *insertWidget(QAction *before, QWidget *widget); + + QRect actionGeometry(QAction *action) const; + QAction *actionAt(const QPoint &p) const; + inline QAction *actionAt(int x, int y) const; + + QAction *toggleViewAction() const; + + QSize iconSize() const; + Qt::ToolButtonStyle toolButtonStyle() const; + + QWidget *widgetForAction(QAction *action) const; + + bool isFloatable() const; + void setFloatable(bool floatable); + bool isFloating() const; + +public Q_SLOTS: + void setIconSize(const QSize &iconSize); + void setToolButtonStyle(Qt::ToolButtonStyle toolButtonStyle); + +Q_SIGNALS: + void actionTriggered(QAction *action); + void movableChanged(bool movable); + void allowedAreasChanged(Qt::ToolBarAreas allowedAreas); + void orientationChanged(Qt::Orientation orientation); + void iconSizeChanged(const QSize &iconSize); + void toolButtonStyleChanged(Qt::ToolButtonStyle toolButtonStyle); + +protected: + void actionEvent(QActionEvent *event); + void changeEvent(QEvent *event); + void childEvent(QChildEvent *event); + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + bool event(QEvent *event); + void initStyleOption(QStyleOptionToolBar *option) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QToolBar(QWidget *parent, const char *name); + inline QT3_SUPPORT void setLabel(const QString &label) + { setWindowTitle(label); } + inline QT3_SUPPORT QString label() const + { return windowTitle(); } +#endif + +private: + Q_DECLARE_PRIVATE(QToolBar) + Q_DISABLE_COPY(QToolBar) + Q_PRIVATE_SLOT(d_func(), void _q_toggleView(bool)) + Q_PRIVATE_SLOT(d_func(), void _q_updateIconSize(const QSize &)) + Q_PRIVATE_SLOT(d_func(), void _q_updateToolButtonStyle(Qt::ToolButtonStyle)) + Q_PRIVATE_SLOT(d_func(), void _q_waitForPopup()) + + friend class QMainWindow; + friend class QMainWindowLayout; + friend class QToolBarLayout; + friend class QToolBarAreaLayout; +}; + +inline QAction *QToolBar::actionAt(int ax, int ay) const +{ return actionAt(QPoint(ax, ay)); } + +#endif // QT_NO_TOOLBAR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDYNAMICTOOLBAR_H diff --git a/src/gui/widgets/qtoolbar_p.h b/src/gui/widgets/qtoolbar_p.h new file mode 100644 index 0000000..598f054 --- /dev/null +++ b/src/gui/widgets/qtoolbar_p.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICTOOLBAR_P_H +#define QDYNAMICTOOLBAR_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 "qtoolbar.h" +#include "QtGui/qaction.h" +#include "private/qwidget_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TOOLBAR + +class QToolBarLayout; +class QTimer; + +class QToolBarPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QToolBar) + +public: + inline QToolBarPrivate() + : explicitIconSize(false), explicitToolButtonStyle(false), movable(false), + allowedAreas(Qt::AllToolBarAreas), orientation(Qt::Horizontal), + toolButtonStyle(Qt::ToolButtonIconOnly), + layout(0), state(0) +#ifdef Q_WS_MAC + , macWindowDragging(false) +#endif + { } + + void init(); + void actionTriggered(); + void _q_toggleView(bool b); + void _q_updateIconSize(const QSize &sz); + void _q_updateToolButtonStyle(Qt::ToolButtonStyle style); + void _q_waitForPopup(); + + bool explicitIconSize; + bool explicitToolButtonStyle; + bool movable; + Qt::ToolBarAreas allowedAreas; + Qt::Orientation orientation; + Qt::ToolButtonStyle toolButtonStyle; + QSize iconSize; + bool floatable; + + QAction *toggleViewAction; + + QToolBarLayout *layout; + + struct DragState { + QPoint pressPos; + bool dragging; + bool moving; + QLayoutItem *widgetItem; + }; + DragState *state; + +#ifdef Q_WS_MAC + bool macWindowDragging; + QPoint macWindowDragPressPosition; +#endif + + bool mousePressEvent(QMouseEvent *e); + bool mouseReleaseEvent(QMouseEvent *e); + bool mouseMoveEvent(QMouseEvent *e); + + void updateWindowFlags(bool floating, bool unplug = false); + void setWindowState(bool floating, bool unplug = false, const QRect &rect = QRect()); + void initDrag(const QPoint &pos); + void startDrag(bool moving = false); + void endDrag(); + + void unplug(const QRect &r); + void plug(const QRect &r); + + QTimer *waitForPopupTimer; +}; + +#endif // QT_NO_TOOLBAR + +QT_END_NAMESPACE + +#endif // QDYNAMICTOOLBAR_P_H diff --git a/src/gui/widgets/qtoolbararealayout.cpp b/src/gui/widgets/qtoolbararealayout.cpp new file mode 100644 index 0000000..49f4a9e --- /dev/null +++ b/src/gui/widgets/qtoolbararealayout.cpp @@ -0,0 +1,1370 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QWidgetItem> +#include <QToolBar> +#include <QStyleOption> +#include <QApplication> +#include <qdebug.h> + +#include "qtoolbararealayout_p.h" +#include "qmainwindowlayout_p.h" +#include "qwidgetanimator_p.h" +#include "qtoolbarlayout_p.h" +#include "qtoolbar_p.h" + +/****************************************************************************** +** QToolBarAreaLayoutItem +*/ + +#ifndef QT_NO_TOOLBAR + +QT_BEGIN_NAMESPACE + +QSize QToolBarAreaLayoutItem::minimumSize() const +{ + if (skip()) + return QSize(0, 0); + return qSmartMinSize(static_cast<QWidgetItem*>(widgetItem)); +} + +QSize QToolBarAreaLayoutItem::sizeHint() const +{ + if (skip()) + return QSize(0, 0); + + return realSizeHint(); +} + +//returns the real size hint not taking into account the visibility of the widget +QSize QToolBarAreaLayoutItem::realSizeHint() const +{ + QWidget *wid = widgetItem->widget(); + QSize s = wid->sizeHint().expandedTo(wid->minimumSizeHint()); + if (wid->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored) + s.setWidth(0); + if (wid->sizePolicy().verticalPolicy() == QSizePolicy::Ignored) + s.setHeight(0); + s = s.boundedTo(wid->maximumSize()) + .expandedTo(wid->minimumSize()); + return s; +} + +bool QToolBarAreaLayoutItem::skip() const +{ + if (gap) + return false; + return widgetItem == 0 || widgetItem->isEmpty(); +} + +/****************************************************************************** +** QToolBarAreaLayoutLine +*/ + +QToolBarAreaLayoutLine::QToolBarAreaLayoutLine(Qt::Orientation orientation) + : o(orientation) +{ +} + +QSize QToolBarAreaLayoutLine::sizeHint() const +{ + int a = 0, b = 0; + for (int i = 0; i < toolBarItems.count(); ++i) { + const QToolBarAreaLayoutItem &item = toolBarItems.at(i); + if (item.skip()) + continue; + + QSize sh = item.sizeHint(); + a += pick(o, sh) + item.extraSpace; + b = qMax(b, perp(o, sh)); + } + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + + return result; +} + +QSize QToolBarAreaLayoutLine::minimumSize() const +{ + int a = 0, b = 0; + for (int i = 0; i < toolBarItems.count(); ++i) { + const QToolBarAreaLayoutItem &item = toolBarItems[i]; + if (item.skip()) + continue; + + QSize ms = item.minimumSize(); + a += pick(o, ms); + b = qMax(b, perp(o, ms)); + } + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + + return result; +} + +void QToolBarAreaLayoutLine::fitLayout() +{ + int last = -1; + int min = pick(o, minimumSize()); + int space = pick(o, rect.size()); + int extra = qMax(0, space - min); + + for (int i = 0; i < toolBarItems.count(); ++i) { + QToolBarAreaLayoutItem &item = toolBarItems[i]; + if (item.skip()) + continue; + + QToolBarLayout *tblayout = qobject_cast<QToolBarLayout*>(item.widgetItem->widget()->layout()); + if (tblayout) + tblayout->checkUsePopupMenu(); + + int itemMin = pick(o, item.minimumSize()); + int itemHint = pick(o, item.sizeHint()); + //we ensure the extraspace is not too low + item.extraSpace = qMax(itemMin - itemHint, item.extraSpace); + itemHint += item.extraSpace; + int itemExtra = qMin(itemHint - itemMin, extra); + + item.size = itemMin + itemExtra; + extra -= itemExtra; + + last = i; + } + + // calculate the positions from the sizes + int pos = 0; + for (int i = 0; i < toolBarItems.count(); ++i) { + QToolBarAreaLayoutItem &item = toolBarItems[i]; + if (item.skip()) + continue; + + item.pos = pos; + if (i == last) // stretch the last item to the end of the line + item.size = qMax(0, pick(o, rect.size()) - item.pos); + pos += item.size; + } +} + +bool QToolBarAreaLayoutLine::skip() const +{ + for (int i = 0; i < toolBarItems.count(); ++i) { + if (!toolBarItems.at(i).skip()) + return false; + } + return true; +} + +/****************************************************************************** +** QToolBarAreaLayoutInfo +*/ + +QToolBarAreaLayoutInfo::QToolBarAreaLayoutInfo(QInternal::DockPosition pos) + : dockPos(pos), dirty(false) +{ + switch (pos) { + case QInternal::LeftDock: + case QInternal::RightDock: + o = Qt::Vertical; + break; + case QInternal::TopDock: + case QInternal::BottomDock: + o = Qt::Horizontal; + break; + default: + o = Qt::Horizontal; + break; + } +} + +QSize QToolBarAreaLayoutInfo::sizeHint() const +{ + int a = 0, b = 0; + for (int i = 0; i < lines.count(); ++i) { + const QToolBarAreaLayoutLine &l = lines.at(i); + if (l.skip()) + continue; + + QSize hint = l.sizeHint(); + a = qMax(a, pick(o, hint)); + b += perp(o, hint); + } + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + + return result; +} + +QSize QToolBarAreaLayoutInfo::minimumSize() const +{ + int a = 0, b = 0; + for (int i = 0; i < lines.count(); ++i) { + const QToolBarAreaLayoutLine &l = lines.at(i); + if (l.skip()) + continue; + + QSize m = l.minimumSize(); + a = qMax(a, pick(o, m)); + b += perp(o, m); + } + + QSize result; + rpick(o, result) = a; + rperp(o, result) = b; + + return result; +} + +void QToolBarAreaLayoutInfo::fitLayout() +{ + dirty = false; + + int b = 0; + + bool reverse = dockPos == QInternal::RightDock || dockPos == QInternal::BottomDock; + + int i = reverse ? lines.count() - 1 : 0; + for (;;) { + if ((reverse && i < 0) || (!reverse && i == lines.count())) + break; + + QToolBarAreaLayoutLine &l = lines[i]; + if (!l.skip()) { + if (o == Qt::Horizontal) { + l.rect.setLeft(rect.left()); + l.rect.setRight(rect.right()); + l.rect.setTop(b + rect.top()); + b += l.sizeHint().height(); + l.rect.setBottom(b - 1 + rect.top()); + } else { + l.rect.setTop(rect.top()); + l.rect.setBottom(rect.bottom()); + l.rect.setLeft(b + rect.left()); + b += l.sizeHint().width(); + l.rect.setRight(b - 1 + rect.left()); + } + + l.fitLayout(); + } + + i += reverse ? -1 : 1; + } +} + +QLayoutItem *QToolBarAreaLayoutInfo::insertToolBar(QToolBar *before, QToolBar *toolBar) +{ + toolBar->setOrientation(o); + QLayoutItem *item = new QWidgetItemV2(toolBar); + insertItem(before, item); + return item; +} + +void QToolBarAreaLayoutInfo::insertItem(QToolBar *before, QLayoutItem *item) +{ + if (before == 0) { + if (lines.isEmpty()) + lines.append(QToolBarAreaLayoutLine(o)); + lines.last().toolBarItems.append(item); + return; + } + + for (int j = 0; j < lines.count(); ++j) { + QToolBarAreaLayoutLine &line = lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if (line.toolBarItems.at(k).widgetItem->widget() == before) { + line.toolBarItems.insert(k, item); + return; + } + } + } +} + +void QToolBarAreaLayoutInfo::removeToolBar(QToolBar *toolBar) +{ + for (int j = 0; j < lines.count(); ++j) { + QToolBarAreaLayoutLine &line = lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + QToolBarAreaLayoutItem &item = line.toolBarItems[k]; + if (item.widgetItem->widget() == toolBar) { + delete item.widgetItem; + item.widgetItem = 0; + line.toolBarItems.removeAt(k); + + if (line.toolBarItems.isEmpty() && j < lines.count() - 1) + lines.removeAt(j); + + return; + } + } + } +} + +void QToolBarAreaLayoutInfo::insertToolBarBreak(QToolBar *before) +{ + if (before == 0) { + if (!lines.isEmpty() && lines.last().toolBarItems.isEmpty()) + return; + lines.append(QToolBarAreaLayoutLine(o)); + return; + } + + for (int j = 0; j < lines.count(); ++j) { + QToolBarAreaLayoutLine &line = lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if (line.toolBarItems.at(k).widgetItem->widget() == before) { + if (k == 0) + return; + + QToolBarAreaLayoutLine newLine(o); + newLine.toolBarItems = line.toolBarItems.mid(k); + line.toolBarItems = line.toolBarItems.mid(0, k); + lines.insert(j + 1, newLine); + + return; + } + } + } +} + +void QToolBarAreaLayoutInfo::removeToolBarBreak(QToolBar *before) +{ + for (int j = 0; j < lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = lines.at(j); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if (line.toolBarItems.at(k).widgetItem->widget() == before) { + if (k != 0) + return; + if (j == 0) + return; + + lines[j - 1].toolBarItems += lines[j].toolBarItems; + lines.removeAt(j); + + return; + } + } + } +} + +void QToolBarAreaLayoutInfo::moveToolBar(QToolBar *toolbar, int pos) +{ + if (dirty) { + fitLayout(); + } + + dirty = true; + + if (o == Qt::Vertical) { + pos -= rect.top(); + } + + //here we actually update the extraSpace for the line containing the toolbar so that we move it + for (int j = 0; j < lines.count(); ++j) { + QToolBarAreaLayoutLine &line = lines[j]; + + int previousIndex = -1; + int minPos = 0; + for (int k = 0; k < line.toolBarItems.count(); ++k) { + QToolBarAreaLayoutItem ¤t = line.toolBarItems[k]; + if (current.widgetItem->widget() == toolbar) { + int newPos = current.pos; + + if (previousIndex >= 0) { + QToolBarAreaLayoutItem &previous = line.toolBarItems[previousIndex]; + if (pos < current.pos) { + newPos = qMax(pos, minPos); + } else { + //we check the max value for the position (until everything at the right is "compressed") + int maxPos = pick(o, rect.size()); + for(int l = k; l < line.toolBarItems.count(); ++l) { + const QToolBarAreaLayoutItem &item = line.toolBarItems.at(l); + if (!item.skip()) { + maxPos -= pick(o, item.minimumSize()); + } + } + newPos = qMin(pos, maxPos); + } + + //let's update the previous extra space + int extra = newPos - current.pos; + + if (qAbs(previous.extraSpace + extra) < QApplication::startDragDistance()) { + //we stick to the default space + extra = 0; + } + + //update for the current item + current.extraSpace -= extra; + //this ensures the toolbars to be pushed to the right when necessary + current.extraSpace = qMax(pick(o,current.minimumSize())- pick(o,current.sizeHint()), current.extraSpace); + + if (extra >= 0) { + previous.extraSpace += extra; + + } else { + //we need to push the toolbars on the left starting with previous + extra = -extra; // we just need to know the number of pixels + ///at this point we need to get extra pixels from the toolbars at the left + for(int l = previousIndex; l >=0; --l) { + QToolBarAreaLayoutItem &item = line.toolBarItems[l]; + if (!item.skip()) { + const int minExtraSpace = pick(o, item.minimumSize()) - pick(o, item.sizeHint()); + const int margin = item.extraSpace - minExtraSpace; + if (margin < extra) { + item.extraSpace = minExtraSpace; + extra -= margin; + } else { + item.extraSpace -= extra; + extra = 0; + } + } + } + Q_ASSERT(extra == 0); + } + } else { + //the item is the first one, it should be at position 0 + } + + return; + + } else if (!current.skip()) { + previousIndex = k; + minPos += pick(o, current.minimumSize()); + } + } + } +} + + +QList<int> QToolBarAreaLayoutInfo::gapIndex(const QPoint &pos) const +{ + int p = pick(o, pos); + + if (rect.contains(pos)) { + for (int j = 0; j < lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = lines.at(j); + if (line.skip()) + continue; + if (!line.rect.contains(pos)) + continue; + + int k = 0; + for (; k < line.toolBarItems.count(); ++k) { + const QToolBarAreaLayoutItem &item = line.toolBarItems.at(k); + if (item.skip()) + continue; + + int size = qMin(item.size, pick(o, item.sizeHint())); + + if (p > item.pos + size) + continue; + if (p > item.pos + size/2) + ++k; + break; + } + + QList<int> result; + result << j << k; + return result; + } + } else if (appendLineDropRect().contains(pos)) { + QList<int> result; + result << lines.count() << 0; + return result; + } + + return QList<int>(); +} + +bool QToolBarAreaLayoutInfo::insertGap(QList<int> path, QLayoutItem *item) +{ + int j = path.at(0); + if (j == lines.count()) + lines.append(QToolBarAreaLayoutLine(o)); + + QToolBarAreaLayoutLine &line = lines[j]; + const int k = path.at(1); + + QToolBarAreaLayoutItem gap_item; + gap_item.gap = true; + gap_item.widgetItem = item; + + //update the previous item's extra space + for(int p = k - 1 ; p >= 0; --p) { + QToolBarAreaLayoutItem &previous = line.toolBarItems[p]; + if (!previous.skip()) { + //we found the previous one + gap_item.extraSpace = qMax(0, previous.extraSpace - pick(o, gap_item.sizeHint())); + previous.extraSpace = qMin(previous.extraSpace, 0); + break; + } + } + + line.toolBarItems.insert(k, gap_item); + return true; + +} + +void QToolBarAreaLayoutInfo::clear() +{ + lines.clear(); + rect = QRect(0, 0, -1, -1); +} + +QRect QToolBarAreaLayoutInfo::itemRect(QList<int> path) const +{ + int j = path.at(0); + int k = path.at(1); + + const QToolBarAreaLayoutLine &line = lines.at(j); + const QToolBarAreaLayoutItem &item = line.toolBarItems.at(k); + + QRect result = line.rect; + + if (o == Qt::Horizontal) { + result.setLeft(item.pos + line.rect.left()); + result.setWidth(item.size); + } else { + result.setTop(item.pos + line.rect.top()); + result.setHeight(item.size); + } + + return result; +} + +QRect QToolBarAreaLayoutInfo::appendLineDropRect() const +{ + QRect result; + + switch (dockPos) { + case QInternal::LeftDock: + result = QRect(rect.right(), rect.top(), + EmptyDockAreaSize, rect.height()); + break; + case QInternal::RightDock: + result = QRect(rect.left() - EmptyDockAreaSize, rect.top(), + EmptyDockAreaSize, rect.height()); + break; + case QInternal::TopDock: + result = QRect(rect.left(), rect.bottom() + 1, + rect.width(), EmptyDockAreaSize); + break; + case QInternal::BottomDock: + result = QRect(rect.left(), rect.top() - EmptyDockAreaSize, + rect.width(), EmptyDockAreaSize); + break; + default: + break; + } + + return result; +} + +/****************************************************************************** +** QToolBarAreaLayout +*/ + +QToolBarAreaLayout::QToolBarAreaLayout(QMainWindow *win) +{ + visible = true; + mainWindow = win; + for (int i = 0; i < QInternal::DockCount; ++i) { + QInternal::DockPosition pos = static_cast<QInternal::DockPosition>(i); + docks[i] = QToolBarAreaLayoutInfo(pos); + } +} + +QRect QToolBarAreaLayout::fitLayout() +{ + if (!visible) + return rect; + + QSize left_hint = docks[QInternal::LeftDock].sizeHint(); + QSize right_hint = docks[QInternal::RightDock].sizeHint(); + QSize top_hint = docks[QInternal::TopDock].sizeHint(); + QSize bottom_hint = docks[QInternal::BottomDock].sizeHint(); + + QRect center = rect.adjusted(left_hint.width(), top_hint.height(), + -right_hint.width(), -bottom_hint.height()); + + docks[QInternal::TopDock].rect = QRect(rect.left(), rect.top(), + rect.width(), top_hint.height()); + docks[QInternal::LeftDock].rect = QRect(rect.left(), center.top(), + left_hint.width(), center.height()); + docks[QInternal::RightDock].rect = QRect(center.right() + 1, center.top(), + right_hint.width(), center.height()); + docks[QInternal::BottomDock].rect = QRect(rect.left(), center.bottom() + 1, + rect.width(), bottom_hint.height()); + + if (!mainWindow->unifiedTitleAndToolBarOnMac()) { + docks[QInternal::TopDock].fitLayout(); + } + docks[QInternal::LeftDock].fitLayout(); + docks[QInternal::RightDock].fitLayout(); + docks[QInternal::BottomDock].fitLayout(); + + return center; +} + +QSize QToolBarAreaLayout::minimumSize(const QSize ¢erMin) const +{ + if (!visible) + return centerMin; + + QSize result = centerMin; + + QSize left_min = docks[QInternal::LeftDock].minimumSize(); + QSize right_min = docks[QInternal::RightDock].minimumSize(); + QSize top_min = docks[QInternal::TopDock].minimumSize(); + QSize bottom_min = docks[QInternal::BottomDock].minimumSize(); + + result.setWidth(qMax(top_min.width(), result.width())); + result.setWidth(qMax(bottom_min.width(), result.width())); + result.setHeight(qMax(left_min.height(), result.height())); + result.setHeight(qMax(right_min.height(), result.height())); + + result.rwidth() += left_min.width() + right_min.width(); + result.rheight() += top_min.height() + bottom_min.height(); + + return result; +} + +QSize QToolBarAreaLayout::sizeHint(const QSize ¢erHint) const +{ + if (!visible) + return centerHint; + + QSize result = centerHint; + + QSize left_hint = docks[QInternal::LeftDock].sizeHint(); + QSize right_hint = docks[QInternal::RightDock].sizeHint(); + QSize top_hint = docks[QInternal::TopDock].sizeHint(); + QSize bottom_hint = docks[QInternal::BottomDock].sizeHint(); + + result.setWidth(qMax(top_hint.width(), result.width())); + result.setWidth(qMax(bottom_hint.width(), result.width())); + result.setHeight(qMax(left_hint.height(), result.height())); + result.setHeight(qMax(right_hint.height(), result.height())); + + result.rwidth() += left_hint.width() + right_hint.width(); + result.rheight() += top_hint.height() + bottom_hint.height(); + + return result; +} + +QRect QToolBarAreaLayout::rectHint(const QRect &r) const +{ + int coef = visible ? 1 : -1; + + QRect result = r; + + QSize left_hint = docks[QInternal::LeftDock].sizeHint(); + QSize right_hint = docks[QInternal::RightDock].sizeHint(); + QSize top_hint = docks[QInternal::TopDock].sizeHint(); + QSize bottom_hint = docks[QInternal::BottomDock].sizeHint(); + + result.adjust(-left_hint.width()*coef, -top_hint.height()*coef, + right_hint.width()*coef, bottom_hint.height()*coef); + + return result; +} + +QLayoutItem *QToolBarAreaLayout::itemAt(int *x, int index) const +{ + Q_ASSERT(x != 0); + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if ((*x)++ == index) + return line.toolBarItems.at(k).widgetItem; + } + } + } + + return 0; +} + +QLayoutItem *QToolBarAreaLayout::takeAt(int *x, int index) +{ + Q_ASSERT(x != 0); + + for (int i = 0; i < QInternal::DockCount; ++i) { + QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + QToolBarAreaLayoutLine &line = dock.lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if ((*x)++ == index) { + QLayoutItem *result = line.toolBarItems.takeAt(k).widgetItem; + if (line.toolBarItems.isEmpty()) + dock.lines.removeAt(j); + return result; + } + } + } + } + + return 0; +} + +void QToolBarAreaLayout::deleteAllLayoutItems() +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + QToolBarAreaLayoutLine &line = dock.lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + QToolBarAreaLayoutItem &item = line.toolBarItems[k]; + delete item.widgetItem; + item.widgetItem = 0; + } + } + } +} + +QInternal::DockPosition QToolBarAreaLayout::findToolBar(QToolBar *toolBar) const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if (line.toolBarItems.at(k).widgetItem->widget() == toolBar) + return static_cast<QInternal::DockPosition>(i); + } + } + } + + return QInternal::DockCount; +} + +QLayoutItem *QToolBarAreaLayout::insertToolBar(QToolBar *before, QToolBar *toolBar) +{ + QInternal::DockPosition pos = findToolBar(before); + if (pos == QInternal::DockCount) + return 0; + + return docks[pos].insertToolBar(before, toolBar); +} + +void QToolBarAreaLayout::removeToolBar(QToolBar *toolBar) +{ + QInternal::DockPosition pos = findToolBar(toolBar); + if (pos == QInternal::DockCount) + return; + docks[pos].removeToolBar(toolBar); +} + +QLayoutItem *QToolBarAreaLayout::addToolBar(QInternal::DockPosition pos, QToolBar *toolBar) +{ + return docks[pos].insertToolBar(0, toolBar); +} + +void QToolBarAreaLayout::insertToolBarBreak(QToolBar *before) +{ + QInternal::DockPosition pos = findToolBar(before); + if (pos == QInternal::DockCount) + return; + docks[pos].insertToolBarBreak(before); +} + +void QToolBarAreaLayout::removeToolBarBreak(QToolBar *before) +{ + QInternal::DockPosition pos = findToolBar(before); + if (pos == QInternal::DockCount) + return; + docks[pos].removeToolBarBreak(before); +} + +void QToolBarAreaLayout::addToolBarBreak(QInternal::DockPosition pos) +{ + docks[pos].insertToolBarBreak(0); +} + +void QToolBarAreaLayout::moveToolBar(QToolBar *toolbar, int p) +{ + QInternal::DockPosition pos = findToolBar(toolbar); + if (pos == QInternal::DockCount) + return; + docks[pos].moveToolBar(toolbar, p); +} + + +void QToolBarAreaLayout::insertItem(QInternal::DockPosition pos, QLayoutItem *item) +{ + if (docks[pos].lines.isEmpty()) + docks[pos].lines.append(QToolBarAreaLayoutLine(docks[pos].o)); + docks[pos].lines.last().toolBarItems.append(item); +} + +void QToolBarAreaLayout::insertItem(QToolBar *before, QLayoutItem *item) +{ + QInternal::DockPosition pos = findToolBar(before); + if (pos == QInternal::DockCount) + return; + + docks[pos].insertItem(before, item); +} + +void QToolBarAreaLayout::apply(bool animate) +{ + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(mainWindow->layout()); + Q_ASSERT(layout != 0); + + Qt::LayoutDirection dir = mainWindow->layoutDirection(); + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + if (line.skip()) + continue; + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + const QToolBarAreaLayoutItem &item = line.toolBarItems.at(k); + if (item.skip() || item.gap) + continue; + + QRect geo; + if (visible) { + if (line.o == Qt::Horizontal) { + geo.setTop(line.rect.top()); + geo.setBottom(line.rect.bottom()); + geo.setLeft(line.rect.left() + item.pos); + geo.setRight(line.rect.left() + item.pos + item.size - 1); + } else { + geo.setLeft(line.rect.left()); + geo.setRight(line.rect.right()); + geo.setTop(line.rect.top() + item.pos); + geo.setBottom(line.rect.top() + item.pos + item.size - 1); + } + } + + QWidget *widget = item.widgetItem->widget(); + if (QToolBar *toolBar = qobject_cast<QToolBar*>(widget)) { + QToolBarLayout *tbl = qobject_cast<QToolBarLayout*>(toolBar->layout()); + if (tbl->expanded) { + QPoint tr = geo.topRight(); + QSize size = tbl->expandedSize(geo.size()); + geo.setSize(size); + geo.moveTopRight(tr); + if (geo.bottom() > rect.bottom()) + geo.moveBottom(rect.bottom()); + if (geo.right() > rect.right()) + geo.moveRight(rect.right()); + if (geo.left() < 0) + geo.moveLeft(0); + if (geo.top() < 0) + geo.moveTop(0); + } + } + + if (visible && dock.o == Qt::Horizontal) + geo = QStyle::visualRect(dir, line.rect, geo); + + layout->widgetAnimator->animate(widget, geo, animate); + } + } + } +} + +bool QToolBarAreaLayout::toolBarBreak(QToolBar *toolBar) const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if (line.toolBarItems.at(k).widgetItem->widget() == toolBar) + return j > 0 && k == 0; + } + } + } + + return false; +} + +void QToolBarAreaLayout::getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + if (line.toolBarItems.at(k).widgetItem->widget() == toolBar) { + if (line.toolBarItems.count() == 1) + option->positionWithinLine = QStyleOptionToolBar::OnlyOne; + else if (k == 0) + option->positionWithinLine = QStyleOptionToolBar::Beginning; + else if (k == line.toolBarItems.count() - 1) + option->positionWithinLine = QStyleOptionToolBar::End; + else + option->positionWithinLine = QStyleOptionToolBar::Middle; + + if (dock.lines.count() == 1) + option->positionOfLine = QStyleOptionToolBar::OnlyOne; + else if (j == 0) + option->positionOfLine = QStyleOptionToolBar::Beginning; + else if (j == dock.lines.count() - 1) + option->positionOfLine = QStyleOptionToolBar::End; + else + option->positionOfLine = QStyleOptionToolBar::Middle; + + return; + } + } + } + } +} + +QList<int> QToolBarAreaLayout::indexOf(QWidget *toolBar) const +{ + QList<int> result; + + bool found = false; + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + const QToolBarAreaLayoutItem &item = line.toolBarItems.at(k); + if (!item.gap && item.widgetItem->widget() == toolBar) { + found = true; + result.prepend(k); + break; + } + } + + if (found) { + result.prepend(j); + break; + } + } + + if (found) { + result.prepend(i); + break; + } + } + + return result; +} + +QList<int> QToolBarAreaLayout::gapIndex(const QPoint &pos) const +{ + Qt::LayoutDirection dir = mainWindow->layoutDirection(); + for (int i = 0; i < QInternal::DockCount; ++i) { + QPoint p = pos; + if (docks[i].o == Qt::Horizontal) + p = QStyle::visualPos(dir, docks[i].rect, p); + QList<int> result = docks[i].gapIndex(p); + if (!result.isEmpty()) { + result.prepend(i); + return result; + } + } + + return QList<int>(); +} + +QList<int> QToolBarAreaLayout::currentGapIndex() const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); k++) { + if (line.toolBarItems[k].gap) { + QList<int> result; + result << i << j << k; + return result; + } + } + } + } + return QList<int>(); +} + +bool QToolBarAreaLayout::insertGap(QList<int> path, QLayoutItem *item) +{ + Q_ASSERT(!path.isEmpty()); + int i = path.takeFirst(); + Q_ASSERT(i >= 0 && i < QInternal::DockCount); + return docks[i].insertGap(path, item); +} + +void QToolBarAreaLayout::remove(QList<int> path) +{ + docks[path.at(0)].lines[path.at(1)].toolBarItems.removeAt(path.at(2)); +} + +void QToolBarAreaLayout::remove(QLayoutItem *item) +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + QToolBarAreaLayoutLine &line = dock.lines[j]; + + for (int k = 0; k < line.toolBarItems.count(); k++) { + if (line.toolBarItems[k].widgetItem == item) { + line.toolBarItems.removeAt(k); + if (line.toolBarItems.isEmpty()) + dock.lines.removeAt(j); + return; + } + } + } + } +} + +void QToolBarAreaLayout::clear() +{ + for (int i = 0; i < QInternal::DockCount; ++i) + docks[i].clear(); + rect = QRect(0, 0, -1, -1); +} + +QToolBarAreaLayoutItem &QToolBarAreaLayout::item(QList<int> path) +{ + Q_ASSERT(path.count() == 3); + + Q_ASSERT(path.at(0) >= 0 && path.at(0) < QInternal::DockCount); + QToolBarAreaLayoutInfo &info = docks[path.at(0)]; + Q_ASSERT(path.at(1) >= 0 && path.at(1) < info.lines.count()); + QToolBarAreaLayoutLine &line = info.lines[path.at(1)]; + Q_ASSERT(path.at(2) >= 0 && path.at(2) < line.toolBarItems.count()); + return line.toolBarItems[path.at(2)]; +} + +QRect QToolBarAreaLayout::itemRect(QList<int> path) const +{ + int i = path.takeFirst(); + + QRect r = docks[i].itemRect(path); + if (docks[i].o == Qt::Horizontal) + r = QStyle::visualRect(mainWindow->layoutDirection(), + docks[i].rect, r); + return r; +} + +QLayoutItem *QToolBarAreaLayout::plug(QList<int> path) +{ + QToolBarAreaLayoutItem &item = this->item(path); + Q_ASSERT(item.gap); + Q_ASSERT(item.widgetItem != 0); + item.gap = false; + return item.widgetItem; +} + +QLayoutItem *QToolBarAreaLayout::unplug(QList<int> path, QToolBarAreaLayout *other) +{ + //other needs to be update as well + QToolBarAreaLayoutItem &item = this->item(path); + + //update the leading space here + QToolBarAreaLayoutInfo &info = docks[path.at(0)]; + QToolBarAreaLayoutLine &line = info.lines[path.at(1)]; + if (item.extraSpace != 0) { + int newExtraSpace = 0; + for (int i = path.at(2) - 1; i >= 0; --i) { + QToolBarAreaLayoutItem &previous = line.toolBarItems[i]; + if (!previous.skip()) { + for (int j = path.at(2) + 1; j < line.toolBarItems.count(); ++j) { + const QToolBarAreaLayoutItem &next = line.toolBarItems.at(j); + if (!next.skip()) { + newExtraSpace = previous.extraSpace = next.pos - previous.pos - pick(line.o, previous.sizeHint()); + } + break; + } + break; + } + } + + if (other) { + QToolBarAreaLayoutInfo &info = other->docks[path.at(0)]; + QToolBarAreaLayoutLine &line = info.lines[path.at(1)]; + for (int i = path.at(2) - 1; i >= 0; --i) { + QToolBarAreaLayoutItem &previous = line.toolBarItems[i]; + if (!previous.skip()) { + previous.extraSpace = newExtraSpace; + break; + } + } + + } + } + + + Q_ASSERT(!item.gap); + item.gap = true; + return item.widgetItem; +} + +static QRect unpackRect(uint geom0, uint geom1, bool *floating) +{ + *floating = geom0 & 1; + if (!*floating) + return QRect(); + + geom0 >>= 1; + + int x = (int)(geom0 & 0x0000ffff) - 0x7FFF; + int y = (int)(geom1 & 0x0000ffff) - 0x7FFF; + + geom0 >>= 16; + geom1 >>= 16; + + int w = geom0 & 0x0000ffff; + int h = geom1 & 0x0000ffff; + + return QRect(x, y, w, h); +} + +static void packRect(uint *geom0, uint *geom1, const QRect &rect, bool floating) +{ + *geom0 = 0; + *geom1 = 0; + + if (!floating) + return; + + // The 0x7FFF is half of 0xFFFF. We add it so we can handle negative coordinates on + // dual monitors. It's subtracted when unpacking. + + *geom0 |= qMax(0, rect.width()) & 0x0000ffff; + *geom1 |= qMax(0, rect.height()) & 0x0000ffff; + + *geom0 <<= 16; + *geom1 <<= 16; + + *geom0 |= qMax(0, rect.x() + 0x7FFF) & 0x0000ffff; + *geom1 |= qMax(0, rect.y() + 0x7FFF) & 0x0000ffff; + + // yeah, we chop one bit off the width, but it still has a range up to 32512 + + *geom0 <<= 1; + *geom0 |= 1; +} + + +void QToolBarAreaLayout::saveState(QDataStream &stream) const +{ + // save toolbar state + stream << (uchar) ToolBarStateMarkerEx; + + int lineCount = 0; + for (int i = 0; i < QInternal::DockCount; ++i) + lineCount += docks[i].lines.count(); + + stream << lineCount; + + for (int i = 0; i < QInternal::DockCount; ++i) { + const QToolBarAreaLayoutInfo &dock = docks[i]; + + for (int j = 0; j < dock.lines.count(); ++j) { + const QToolBarAreaLayoutLine &line = dock.lines.at(j); + + stream << i << line.toolBarItems.count(); + + for (int k = 0; k < line.toolBarItems.count(); ++k) { + const QToolBarAreaLayoutItem &item = line.toolBarItems.at(k); + QWidget *widget = const_cast<QLayoutItem*>(item.widgetItem)->widget(); + QString objectName = widget->objectName(); + if (objectName.isEmpty()) { + qWarning("QMainWindow::saveState(): 'objectName' not set for QToolBar %p '%s'", + widget, widget->windowTitle().toLocal8Bit().constData()); + } + stream << objectName; + // we store information as: + // 1st bit: 1 if shown + // 2nd bit: 1 if orientation is vertical (default is horizontal) + uchar shownOrientation = (uchar)!widget->isHidden(); + if (QToolBar * tb= qobject_cast<QToolBar*>(widget)) { + if (tb->orientation() == Qt::Vertical) + shownOrientation |= 2; + } + stream << shownOrientation; + stream << item.pos; + //if extraSpace is 0 the item has its "normal" size, so no need to store the size (we store -1) + stream << (item.extraSpace == 0 ? -1 : (pick(line.o, item.realSizeHint()) + item.extraSpace)); + + uint geom0, geom1; + packRect(&geom0, &geom1, widget->geometry(), widget->isWindow()); + stream << geom0 << geom1; + } + } + } +} + +static inline int getInt(QDataStream &stream, Qt::Orientation o, bool pre43) +{ + if (pre43) { + QPoint p; + stream >> p; + return pick(o, p); + } else { + int x; + stream >> x; + return x; + } +} + + +bool QToolBarAreaLayout::restoreState(QDataStream &stream, const QList<QToolBar*> &_toolBars, uchar tmarker, bool pre43, bool testing) +{ + QList<QToolBar*> toolBars = _toolBars; + int lines; + stream >> lines; + + for (int j = 0; j < lines; ++j) { + int pos; + stream >> pos; + if (pos < 0 || pos >= QInternal::DockCount) + return false; + int cnt; + stream >> cnt; + + QToolBarAreaLayoutInfo &dock = docks[pos]; + QToolBarAreaLayoutLine line(dock.o); + + for (int k = 0; k < cnt; ++k) { + QToolBarAreaLayoutItem item; + + QString objectName; + stream >> objectName; + uchar shown; + stream >> shown; + item.pos = getInt(stream, dock.o, pre43); + item.size = getInt(stream, dock.o, pre43); + + /* + 4.3.0 added floating toolbars, but failed to add the ability to restore them. + We need to store there geometry (four ints). We cannot change the format in a + patch release (4.3.1) by adding ToolBarStateMarkerEx2 to signal extra data. So + for now we'll pack it in the two legacy ints we no longer used in Qt4.3.0. + In 4.4, we should add ToolBarStateMarkerEx2 and fix this properly. + */ + + QRect rect; + bool floating = false; + uint geom0, geom1; + geom0 = getInt(stream, dock.o, pre43); + if (tmarker == ToolBarStateMarkerEx) { + geom1 = getInt(stream, dock.o, pre43); + rect = unpackRect(geom0, geom1, &floating); + } + + QToolBar *toolBar = 0; + for (int x = 0; x < toolBars.count(); ++x) { + if (toolBars.at(x)->objectName() == objectName) { + toolBar = toolBars.takeAt(x); + break; + } + } + if (toolBar == 0) { + continue; + } + + if (!testing) { + item.widgetItem = new QWidgetItemV2(toolBar); + toolBar->setOrientation(floating ? ((shown & 2) ? Qt::Vertical : Qt::Horizontal) : dock.o); + toolBar->setVisible(shown & 1); + toolBar->d_func()->setWindowState(floating, true, rect); + + //if it is -1, it means we should use the default size + item.extraSpace = (item.size == -1) ? 0 : item.size - pick(line.o, item.realSizeHint()); + + + line.toolBarItems.append(item); + } + } + + if (!testing) { + dock.lines.append(line); + } + } + + + return stream.status() == QDataStream::Ok; +} + +bool QToolBarAreaLayout::isEmpty() const +{ + for (int i = 0; i < QInternal::DockCount; ++i) { + if (!docks[i].lines.isEmpty()) + return false; + } + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_TOOLBAR diff --git a/src/gui/widgets/qtoolbararealayout_p.h b/src/gui/widgets/qtoolbararealayout_p.h new file mode 100644 index 0000000..574e366 --- /dev/null +++ b/src/gui/widgets/qtoolbararealayout_p.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTOOLBARAREALAYOUT_P_H +#define QTOOLBARAREALAYOUT_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 <QList> +#include <QSize> +#include <QRect> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TOOLBAR + +class QToolBar; +class QLayoutItem; +class QMainWindow; +class QStyleOptionToolBar; + +class QToolBarAreaLayoutItem +{ +public: + QToolBarAreaLayoutItem(QLayoutItem *item = 0) + : widgetItem(item), pos(0), size(-1), extraSpace(0), gap(false) {} + + bool skip() const; + QSize minimumSize() const; + QSize sizeHint() const; + QSize realSizeHint() const; + + QLayoutItem *widgetItem; + int pos; + int size; + int extraSpace; + bool gap; +}; + +class QToolBarAreaLayoutLine +{ +public: + QToolBarAreaLayoutLine(Qt::Orientation orientation); + + QSize sizeHint() const; + QSize minimumSize() const; + + void fitLayout(); + bool skip() const; + + QRect rect; + Qt::Orientation o; + + QList<QToolBarAreaLayoutItem> toolBarItems; +}; + +class QToolBarAreaLayoutInfo +{ +public: + enum { EmptyDockAreaSize = 80 }; // when a dock area is empty, how "wide" is it? + + QToolBarAreaLayoutInfo(QInternal::DockPosition pos = QInternal::TopDock); + + QList<QToolBarAreaLayoutLine> lines; + + QSize sizeHint() const; + QSize minimumSize() const; + + void fitLayout(); + + QLayoutItem *insertToolBar(QToolBar *before, QToolBar *toolBar); + void insertItem(QToolBar *before, QLayoutItem *item); + void removeToolBar(QToolBar *toolBar); + void insertToolBarBreak(QToolBar *before); + void removeToolBarBreak(QToolBar *before); + void moveToolBar(QToolBar *toolbar, int pos); + + QList<int> gapIndex(const QPoint &pos) const; + bool insertGap(QList<int> path, QLayoutItem *item); + void clear(); + QRect itemRect(QList<int> path) const; + QRect appendLineDropRect() const; + + QRect rect; + Qt::Orientation o; + QInternal::DockPosition dockPos; + bool dirty; +}; + +class QToolBarAreaLayout +{ +public: + enum { // sentinel values used to validate state data + ToolBarStateMarker = 0xfe, + ToolBarStateMarkerEx = 0xfc + }; + + QRect rect; + QMainWindow *mainWindow; + QToolBarAreaLayoutInfo docks[4]; + bool visible; + + QToolBarAreaLayout(QMainWindow *win); + + QRect fitLayout(); + + QSize minimumSize(const QSize ¢erMin) const; + QRect rectHint(const QRect &r) const; + QSize sizeHint(const QSize ¢er) const; + void apply(bool animate); + + QLayoutItem *itemAt(int *x, int index) const; + QLayoutItem *takeAt(int *x, int index); + void deleteAllLayoutItems(); + + QLayoutItem *insertToolBar(QToolBar *before, QToolBar *toolBar); + void removeToolBar(QToolBar *toolBar); + QLayoutItem *addToolBar(QInternal::DockPosition pos, QToolBar *toolBar); + void insertToolBarBreak(QToolBar *before); + void removeToolBarBreak(QToolBar *before); + void addToolBarBreak(QInternal::DockPosition pos); + void moveToolBar(QToolBar *toolbar, int pos); + + void insertItem(QInternal::DockPosition pos, QLayoutItem *item); + void insertItem(QToolBar *before, QLayoutItem *item); + + QInternal::DockPosition findToolBar(QToolBar *toolBar) const; + bool toolBarBreak(QToolBar *toolBar) const; + + void getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const; + + QList<int> indexOf(QWidget *toolBar) const; + QList<int> gapIndex(const QPoint &pos) const; + QList<int> currentGapIndex() const; + bool insertGap(QList<int> path, QLayoutItem *item); + void remove(QList<int> path); + void remove(QLayoutItem *item); + void clear(); + QToolBarAreaLayoutItem &item(QList<int> path); + QRect itemRect(QList<int> path) const; + QLayoutItem *plug(QList<int> path); + QLayoutItem *unplug(QList<int> path, QToolBarAreaLayout *other); + + void saveState(QDataStream &stream) const; + bool restoreState(QDataStream &stream, const QList<QToolBar*> &toolBars, uchar tmarker, bool pre43, bool testing = false); + bool isEmpty() const; +}; + + +QT_END_NAMESPACE +#endif // QT_NO_TOOLBAR +#endif // QTOOLBARAREALAYOUT_P_H diff --git a/src/gui/widgets/qtoolbarextension.cpp b/src/gui/widgets/qtoolbarextension.cpp new file mode 100644 index 0000000..a1c5fd6 --- /dev/null +++ b/src/gui/widgets/qtoolbarextension.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtoolbarextension_p.h" +#include <qpixmap.h> +#include <qstyle.h> +#include <qstylepainter.h> +#include <qstyleoption.h> + +#ifndef QT_NO_TOOLBUTTON + +QT_BEGIN_NAMESPACE + +QToolBarExtension::QToolBarExtension(QWidget *parent) + : QToolButton(parent) +{ + setObjectName(QLatin1String("qt_toolbar_ext_button")); + setAutoRaise(true); + setOrientation(Qt::Horizontal); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setCheckable(true); +} + +void QToolBarExtension::setOrientation(Qt::Orientation o) +{ + if (o == Qt::Horizontal) { + setIcon(style()->standardIcon(QStyle::SP_ToolBarHorizontalExtensionButton)); + } else { + setIcon(style()->standardIcon(QStyle::SP_ToolBarVerticalExtensionButton)); + } +} + +void QToolBarExtension::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + // We do not need to draw both extention arrows + opt.features &= ~QStyleOptionToolButton::HasMenu; + p.drawComplexControl(QStyle::CC_ToolButton, opt); +} + + +QSize QToolBarExtension::sizeHint() const +{ + int ext = style()->pixelMetric(QStyle::PM_ToolBarExtensionExtent); + return QSize(ext, ext); +} + +QT_END_NAMESPACE + +#endif // QT_NO_TOOLBUTTON diff --git a/src/gui/widgets/qtoolbarextension_p.h b/src/gui/widgets/qtoolbarextension_p.h new file mode 100644 index 0000000..3f3a459 --- /dev/null +++ b/src/gui/widgets/qtoolbarextension_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICTOOLBAREXTENSION_P_H +#define QDYNAMICTOOLBAREXTENSION_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 "QtGui/qtoolbutton.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TOOLBUTTON + +class QToolBarExtension : public QToolButton +{ + Q_OBJECT + Qt::Orientation orientation; + +public: + explicit QToolBarExtension(QWidget *parent); + void paintEvent(QPaintEvent *); + QSize sizeHint() const; + +public Q_SLOTS: + void setOrientation(Qt::Orientation o); +}; + +#endif // QT_NO_TOOLBUTTON + +QT_END_NAMESPACE + +#endif // QDYNAMICTOOLBAREXTENSION_P_H diff --git a/src/gui/widgets/qtoolbarlayout.cpp b/src/gui/widgets/qtoolbarlayout.cpp new file mode 100644 index 0000000..7771f46 --- /dev/null +++ b/src/gui/widgets/qtoolbarlayout.cpp @@ -0,0 +1,752 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qaction.h> +#include <qwidgetaction.h> +#include <qtoolbar.h> +#include <qstyleoption.h> +#include <qtoolbutton.h> +#include <qmenu.h> +#include <qdebug.h> +#include <qmath.h> + +#include "qmainwindowlayout_p.h" +#include "qtoolbarextension_p.h" +#include "qtoolbarlayout_p.h" +#include "qtoolbarseparator_p.h" + +#ifndef QT_NO_TOOLBAR + +QT_BEGIN_NAMESPACE + +/****************************************************************************** +** QToolBarItem +*/ + +QToolBarItem::QToolBarItem(QWidget *widget) + : QWidgetItem(widget), action(0), customWidget(false) +{ +} + +bool QToolBarItem::isEmpty() const +{ + return action == 0 || !action->isVisible(); +} + +/****************************************************************************** +** QToolBarLayout +*/ + +QToolBarLayout::QToolBarLayout(QWidget *parent) + : QLayout(parent), expanded(false), animating(false), dirty(true), + expanding(false), empty(true), expandFlag(false), popupMenu(0) +{ + QToolBar *tb = qobject_cast<QToolBar*>(parent); + if (!tb) + return; + + extension = new QToolBarExtension(tb); + extension->setFocusPolicy(Qt::NoFocus); + extension->hide(); + QObject::connect(tb, SIGNAL(orientationChanged(Qt::Orientation)), + extension, SLOT(setOrientation(Qt::Orientation))); + + setUsePopupMenu(qobject_cast<QMainWindow*>(tb->parentWidget()) == 0); +} + +QToolBarLayout::~QToolBarLayout() +{ + while (!items.isEmpty()) { + QToolBarItem *item = items.takeFirst(); + if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(item->action)) { + if (item->customWidget) + widgetAction->releaseWidget(item->widget()); + } + delete item; + } +} + +void QToolBarLayout::updateMarginAndSpacing() +{ + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return; + QStyle *style = tb->style(); + QStyleOptionToolBar opt; + tb->initStyleOption(&opt); + setMargin(style->pixelMetric(QStyle::PM_ToolBarItemMargin, &opt, tb) + + style->pixelMetric(QStyle::PM_ToolBarFrameWidth, &opt, tb)); + setSpacing(style->pixelMetric(QStyle::PM_ToolBarItemSpacing, &opt, tb)); +} + +bool QToolBarLayout::hasExpandFlag() const +{ + return expandFlag; +} + +void QToolBarLayout::setUsePopupMenu(bool set) +{ + if (!dirty && ((popupMenu == 0) == set)) + invalidate(); + if (!set) { + QObject::connect(extension, SIGNAL(clicked(bool)), + this, SLOT(setExpanded(bool))); + extension->setPopupMode(QToolButton::DelayedPopup); + extension->setMenu(0); + delete popupMenu; + popupMenu = 0; + } else { + QObject::disconnect(extension, SIGNAL(clicked(bool)), + this, SLOT(setExpanded(bool))); + extension->setPopupMode(QToolButton::InstantPopup); + if (!popupMenu) { + popupMenu = new QMenu(extension); + } + extension->setMenu(popupMenu); + } +} + +void QToolBarLayout::checkUsePopupMenu() +{ + QToolBar *tb = static_cast<QToolBar *>(parent()); + QMainWindow *mw = qobject_cast<QMainWindow *>(tb->parent()); + Qt::Orientation o = tb->orientation(); + setUsePopupMenu(!mw || tb->isFloating() || perp(o, expandedSize(mw->size())) >= perp(o, mw->size())); +} + +void QToolBarLayout::addItem(QLayoutItem*) +{ + qWarning() << "QToolBarLayout::addItem(): please use addAction() instead"; + return; +} + +QLayoutItem *QToolBarLayout::itemAt(int index) const +{ + if (index < 0 || index >= items.count()) + return 0; + return items.at(index); +} + +QLayoutItem *QToolBarLayout::takeAt(int index) +{ + if (index < 0 || index >= items.count()) + return 0; + QToolBarItem *item = items.takeAt(index); + + if (popupMenu) + popupMenu->removeAction(item->action); + + QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(item->action); + if (widgetAction != 0 && item->customWidget) { + widgetAction->releaseWidget(item->widget()); + } else { + // destroy the QToolButton/QToolBarSeparator + item->widget()->hide(); + item->widget()->deleteLater(); + } + + invalidate(); + return item; +} + +void QToolBarLayout::insertAction(int index, QAction *action) +{ + index = qMax(0, index); + index = qMin(items.count(), index); + + QToolBarItem *item = createItem(action); + if (item) { + items.insert(index, item); + invalidate(); + } +} + +int QToolBarLayout::indexOf(QAction *action) const +{ + for (int i = 0; i < items.count(); ++i) { + if (items.at(i)->action == action) + return i; + } + return -1; +} + +int QToolBarLayout::count() const +{ + return items.count(); +} + +bool QToolBarLayout::isEmpty() const +{ + if (dirty) + updateGeomArray(); + return empty; +} + +void QToolBarLayout::invalidate() +{ + dirty = true; + QLayout::invalidate(); +} + +Qt::Orientations QToolBarLayout::expandingDirections() const +{ + if (dirty) + updateGeomArray(); + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return Qt::Orientations(0); + Qt::Orientation o = tb->orientation(); + return expanding ? Qt::Orientations(o) : Qt::Orientations(0); +} + +bool QToolBarLayout::movable() const +{ + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return false; + QMainWindow *win = qobject_cast<QMainWindow*>(tb->parentWidget()); + return tb->isMovable() && win != 0; +} + +void QToolBarLayout::updateGeomArray() const +{ + if (!dirty) + return; + + QToolBarLayout *that = const_cast<QToolBarLayout*>(this); + + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return; + QStyle *style = tb->style(); + QStyleOptionToolBar opt; + tb->initStyleOption(&opt); + const int handleExtent = movable() + ? style->pixelMetric(QStyle::PM_ToolBarHandleExtent, &opt, tb) : 0; + const int margin = this->margin(); + const int spacing = this->spacing(); + const int extensionExtent = style->pixelMetric(QStyle::PM_ToolBarExtensionExtent, &opt, tb); + Qt::Orientation o = tb->orientation(); + + that->minSize = QSize(0, 0); + that->hint = QSize(0, 0); + rperp(o, that->minSize) = style->pixelMetric(QStyle::PM_ToolBarHandleExtent, &opt, tb); + rperp(o, that->hint) = style->pixelMetric(QStyle::PM_ToolBarHandleExtent, &opt, tb); + + that->expanding = false; + that->empty = false; + + QVector<QLayoutStruct> a(items.count() + 1); // + 1 for the stretch + + int count = 0; + for (int i = 0; i < items.count(); ++i) { + QToolBarItem *item = items.at(i); + + QSize max = item->maximumSize(); + QSize min = item->minimumSize(); + QSize hint = item->sizeHint(); + Qt::Orientations exp = item->expandingDirections(); + bool empty = item->isEmpty(); + + that->expanding = expanding || exp & o; + + + if (item->widget()) { + if ((item->widget()->sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag)) { + that->expandFlag = true; + } + } + + if (!empty) { + if (count == 0) // the minimum size only displays one widget + rpick(o, that->minSize) += pick(o, min); + int s = perp(o, minSize); + rperp(o, that->minSize) = qMax(s, perp(o, min)); + + //we only add spacing before item (ie never before the first one) + rpick(o, that->hint) += (count == 0 ? 0 : spacing) + pick(o, hint); + s = perp(o, that->hint); + rperp(o, that->hint) = qMax(s, perp(o, hint)); + ++count; + } + + a[i].sizeHint = pick(o, hint); + a[i].maximumSize = pick(o, max); + a[i].minimumSize = pick(o, min); + a[i].expansive = exp & o; + if (o == Qt::Horizontal) + a[i].stretch = item->widget()->sizePolicy().horizontalStretch(); + else + a[i].stretch = item->widget()->sizePolicy().verticalStretch(); + a[i].empty = empty; + } + + that->geomArray = a; + that->empty = count == 0; + + rpick(o, that->minSize) += handleExtent; + that->minSize += QSize(2*margin, 2*margin); + if (items.count() > 1) + rpick(o, that->minSize) += spacing + extensionExtent; + + rpick(o, that->hint) += handleExtent; + that->hint += QSize(2*margin, 2*margin); + that->dirty = false; +#ifdef Q_WS_MAC + if (QMainWindow *mw = qobject_cast<QMainWindow *>(parentWidget()->parentWidget())) { + if (mw->unifiedTitleAndToolBarOnMac() + && mw->toolBarArea(static_cast<QToolBar *>(parentWidget())) == Qt::TopToolBarArea) { + if (that->expandFlag) { + tb->setMaximumSize(0xFFFFFF, 0xFFFFFF); + } else { + tb->setMaximumSize(hint); + } + } + } +#endif + + that->dirty = false; +} + +static bool defaultWidgetAction(QToolBarItem *item) +{ + QWidgetAction *a = qobject_cast<QWidgetAction*>(item->action); + return a != 0 && a->defaultWidget() == item->widget(); +} + +void QToolBarLayout::setGeometry(const QRect &rect) +{ + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return; + QStyle *style = tb->style(); + QStyleOptionToolBar opt; + tb->initStyleOption(&opt); + const int handleExtent = movable() + ? style->pixelMetric(QStyle::PM_ToolBarHandleExtent, &opt, tb) : 0; + const int margin = this->margin(); + const int extensionExtent = style->pixelMetric(QStyle::PM_ToolBarExtensionExtent, &opt, tb); + Qt::Orientation o = tb->orientation(); + + QLayout::setGeometry(rect); + if (movable()) { + if (o == Qt::Horizontal) { + handRect = QRect(margin, margin, handleExtent, rect.height() - 2*margin); + handRect = QStyle::visualRect(parentWidget()->layoutDirection(), rect, handRect); + } else { + handRect = QRect(margin, margin, rect.width() - 2*margin, handleExtent); + } + } else { + handRect = QRect(); + } + + bool ranOutOfSpace = false; + if (!animating) + ranOutOfSpace = layoutActions(rect.size()); + + if (expanded || animating || ranOutOfSpace) { + Qt::ToolBarArea area = Qt::TopToolBarArea; + if (QMainWindow *win = qobject_cast<QMainWindow*>(tb->parentWidget())) + area = win->toolBarArea(tb); + QSize hint = sizeHint(); + + QPoint pos; + rpick(o, pos) = pick(o, rect.bottomRight()) - margin - extensionExtent + 2; + if (area == Qt::LeftToolBarArea || area == Qt::TopToolBarArea) + rperp(o, pos) = perp(o, rect.topLeft()) + margin; + else + rperp(o, pos) = perp(o, rect.bottomRight()) - margin - (perp(o, hint) - 2*margin) + 1; + QSize size; + rpick(o, size) = extensionExtent; + rperp(o, size) = perp(o, hint) - 2*margin; + QRect r(pos, size); + + if (o == Qt::Horizontal) + r = QStyle::visualRect(parentWidget()->layoutDirection(), rect, r); + + extension->setGeometry(r); + + if (extension->isHidden()) + extension->show(); + } else { + if (!extension->isHidden()) + extension->hide(); + } +#ifdef Q_WS_MAC + // Nothing to do for Carbon... probably +# ifdef QT_MAC_USE_COCOA + if (QMainWindow *win = qobject_cast<QMainWindow*>(tb->parentWidget())) { + Qt::ToolBarArea area = win->toolBarArea(tb); + if (win->unifiedTitleAndToolBarOnMac() && area == Qt::TopToolBarArea) { + static_cast<QMainWindowLayout *>(win->layout())->fixSizeInUnifiedToolbar(tb); + } + } +# endif +#endif + +} + +bool QToolBarLayout::layoutActions(const QSize &size) +{ + if (dirty) + updateGeomArray(); + + QRect rect(0, 0, size.width(), size.height()); + + QList<QWidget*> showWidgets, hideWidgets; + + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return false; + QStyle *style = tb->style(); + QStyleOptionToolBar opt; + tb->initStyleOption(&opt); + const int handleExtent = movable() + ? style->pixelMetric(QStyle::PM_ToolBarHandleExtent, &opt, tb) : 0; + const int margin = this->margin(); + const int spacing = this->spacing(); + const int extensionExtent = style->pixelMetric(QStyle::PM_ToolBarExtensionExtent, &opt, tb); + Qt::Orientation o = tb->orientation(); + bool extensionMenuContainsOnlyWidgetActions = true; + + int space = pick(o, rect.size()) - 2*margin - handleExtent; + if (space <= 0) + return false; // nothing to do. + + if(popupMenu) + popupMenu->clear(); + + bool ranOutOfSpace = false; + int rows = 0; + int rowPos = perp(o, rect.topLeft()) + margin; + int i = 0; + while (i < items.count()) { + QVector<QLayoutStruct> a = geomArray; + + int start = i; + int size = 0; + int prev = -1; + int rowHeight = 0; + int count = 0; + int maximumSize = 0; + bool expansiveRow = false; + for (; i < items.count(); ++i) { + if (a[i].empty) + continue; + + int newSize = size + (count == 0 ? 0 : spacing) + a[i].minimumSize; + if (prev != -1 && newSize > space) { + if (rows == 0) + ranOutOfSpace = true; + // do we have to move the previous item to the next line to make space for + // the extension button? + if (count > 1 && size + spacing + extensionExtent > space) + i = prev; + break; + } + + if (expanded) + rowHeight = qMax(rowHeight, perp(o, items.at(i)->sizeHint())); + expansiveRow = expansiveRow || a[i].expansive; + size = newSize; + maximumSize += spacing + (a[i].expansive ? a[i].maximumSize : a[i].smartSizeHint()); + prev = i; + ++count; + } + + // stretch at the end + a[i].sizeHint = 0; + a[i].maximumSize = QWIDGETSIZE_MAX; + a[i].minimumSize = 0; + a[i].expansive = true; + a[i].stretch = 0; + a[i].empty = true; + + if (expansiveRow && maximumSize < space) { + expansiveRow = false; + a[i].maximumSize = space - maximumSize; + } + + qGeomCalc(a, start, i - start + (expansiveRow ? 0 : 1), 0, + space - (ranOutOfSpace ? (extensionExtent + spacing) : 0), + spacing); + + for (int j = start; j < i; ++j) { + QToolBarItem *item = items.at(j); + + if (a[j].empty) { + if (!item->widget()->isHidden()) + hideWidgets << item->widget(); + continue; + } + + QPoint pos; + rpick(o, pos) = margin + handleExtent + a[j].pos; + rperp(o, pos) = rowPos; + QSize size; + rpick(o, size) = a[j].size; + if (expanded) + rperp(o, size) = rowHeight; + else + rperp(o, size) = perp(o, rect.size()) - 2*margin; + QRect r(pos, size); + + if (o == Qt::Horizontal) + r = QStyle::visualRect(parentWidget()->layoutDirection(), rect, r); + + item->setGeometry(r); + + if (item->widget()->isHidden()) + showWidgets << item->widget(); + } + + if (!expanded) { + for (int j = i; j < items.count(); ++j) { + QToolBarItem *item = items.at(j); + if (!item->widget()->isHidden()) + hideWidgets << item->widget(); + if (popupMenu) { + if (!defaultWidgetAction(item)) { + popupMenu->addAction(item->action); + extensionMenuContainsOnlyWidgetActions = false; + } + } + } + break; + } + + rowPos += rowHeight + spacing; + ++rows; + } + + // if we are using a popup menu, not the expadning toolbar effect, we cannot move custom + // widgets into the menu. If only custom widget actions are chopped off, the popup menu + // is empty. So we show the little extension button to show something is chopped off, + // but we make it disabled. + extension->setEnabled(popupMenu == 0 || !extensionMenuContainsOnlyWidgetActions); + + // we have to do the show/hide here, because it triggers more calls to setGeometry :( + for (int i = 0; i < showWidgets.count(); ++i) + showWidgets.at(i)->show(); + for (int i = 0; i < hideWidgets.count(); ++i) + hideWidgets.at(i)->hide(); + + return ranOutOfSpace; +} + +QSize QToolBarLayout::expandedSize(const QSize &size) const +{ + if (dirty) + updateGeomArray(); + + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return QSize(0, 0); + QMainWindow *win = qobject_cast<QMainWindow*>(tb->parentWidget()); + Qt::Orientation o = tb->orientation(); + QStyle *style = tb->style(); + QStyleOptionToolBar opt; + tb->initStyleOption(&opt); + const int handleExtent = movable() + ? style->pixelMetric(QStyle::PM_ToolBarHandleExtent, &opt, tb) : 0; + const int margin = this->margin(); + const int spacing = this->spacing(); + const int extensionExtent = style->pixelMetric(QStyle::PM_ToolBarExtensionExtent, &opt, tb); + + int total_w = 0; + int count = 0; + for (int x = 0; x < items.count(); ++x) { + if (!geomArray[x].empty) { + total_w += (count == 0 ? 0 : spacing) + geomArray[x].minimumSize; + ++count; + } + } + if (count == 0) + return QSize(0, 0); + + int min_w = pick(o, size); + int rows = (int)qSqrt(qreal(count)); + if (rows == 1) + ++rows; // we want to expand to at least two rows + int space = total_w/rows + spacing + extensionExtent; + space = qMax(space, min_w - 2*margin - handleExtent); + if (win != 0) + space = qMin(space, pick(o, win->size()) - 2*margin - handleExtent); + + int w = 0; + int h = 0; + int i = 0; + while (i < items.count()) { + int count = 0; + int size = 0; + int prev = -1; + int rowHeight = 0; + for (; i < items.count(); ++i) { + if (geomArray[i].empty) + continue; + + int newSize = size + (count == 0 ? 0 : spacing) + geomArray[i].minimumSize; + rowHeight = qMax(rowHeight, perp(o, items.at(i)->sizeHint())); + if (prev != -1 && newSize > space) { + if (count > 1 && size + spacing + extensionExtent > space) { + size -= spacing + geomArray[prev].minimumSize; + i = prev; + } + break; + } + + size = newSize; + prev = i; + ++count; + } + + w = qMax(size, w); + h += rowHeight + spacing; + } + + w += 2*margin + handleExtent + spacing + extensionExtent; + w = qMax(w, min_w); + if (win != 0) + w = qMin(w, pick(o, win->size())); + h += 2*margin - spacing; //there is no spacing before the first row + + QSize result; + rpick(o, result) = w; + rperp(o, result) = h; + return result; +} + +void QToolBarLayout::setExpanded(bool exp) +{ + if (exp == expanded) + return; + + expanded = exp; + extension->setChecked(expanded); + + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return; + if (QMainWindow *win = qobject_cast<QMainWindow*>(tb->parentWidget())) { + animating = true; + QMainWindowLayout *layout = qobject_cast<QMainWindowLayout*>(win->layout()); + if (expanded) { + tb->raise(); + } else { + QList<int> path = layout->layoutState.indexOf(tb); + if (!path.isEmpty()) { + QRect rect = layout->layoutState.itemRect(path); + layoutActions(rect.size()); + } + } + layout->layoutState.toolBarAreaLayout.apply(true); + } +} + +QSize QToolBarLayout::minimumSize() const +{ + if (dirty) + updateGeomArray(); + return minSize; +} + +QSize QToolBarLayout::sizeHint() const +{ + if (dirty) + updateGeomArray(); + return hint; +} + +QToolBarItem *QToolBarLayout::createItem(QAction *action) +{ + bool customWidget = false; + bool standardButtonWidget = false; + QWidget *widget = 0; + QToolBar *tb = qobject_cast<QToolBar*>(parentWidget()); + if (!tb) + return (QToolBarItem *)0; + + if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(action)) { + widget = widgetAction->requestWidget(tb); + if (widget != 0) { + widget->setAttribute(Qt::WA_LayoutUsesWidgetRect); + customWidget = true; + } + } else if (action->isSeparator()) { + QToolBarSeparator *sep = new QToolBarSeparator(tb); + connect(tb, SIGNAL(orientationChanged(Qt::Orientation)), + sep, SLOT(setOrientation(Qt::Orientation))); + widget = sep; + } + + if (!widget) { + QToolButton *button = new QToolButton(tb); + button->setAutoRaise(true); + button->setFocusPolicy(Qt::NoFocus); + button->setIconSize(tb->iconSize()); + button->setToolButtonStyle(tb->toolButtonStyle()); + QObject::connect(tb, SIGNAL(iconSizeChanged(QSize)), + button, SLOT(setIconSize(QSize))); + QObject::connect(tb, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), + button, SLOT(setToolButtonStyle(Qt::ToolButtonStyle))); + button->setDefaultAction(action); + QObject::connect(button, SIGNAL(triggered(QAction*)), tb, SIGNAL(actionTriggered(QAction*))); + widget = button; + standardButtonWidget = true; + } + + widget->hide(); + QToolBarItem *result = new QToolBarItem(widget); + if (standardButtonWidget) + result->setAlignment(Qt::AlignJustify); + result->customWidget = customWidget; + result->action = action; + return result; +} + +QRect QToolBarLayout::handleRect() const +{ + return handRect; +} + +QT_END_NAMESPACE + +#endif // QT_NO_TOOLBAR diff --git a/src/gui/widgets/qtoolbarlayout_p.h b/src/gui/widgets/qtoolbarlayout_p.h new file mode 100644 index 0000000..2eca773 --- /dev/null +++ b/src/gui/widgets/qtoolbarlayout_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTOOLBARLAYOUT_P_H +#define QTOOLBARLAYOUT_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 <QtGui/qlayout.h> +#include <private/qlayoutengine_p.h> +#include <QVector> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TOOLBAR + +class QAction; +class QToolBarExtension; +class QMenu; + +class Q_GUI_EXPORT QToolBarItem : public QWidgetItem +{ +public: + QToolBarItem(QWidget *widget); + bool isEmpty() const; + + QAction *action; + bool customWidget; +}; + +class Q_GUI_EXPORT QToolBarLayout : public QLayout +{ + Q_OBJECT + +public: + QToolBarLayout(QWidget *parent = 0); + ~QToolBarLayout(); + + void addItem(QLayoutItem *item); + QLayoutItem *itemAt(int index) const; + QLayoutItem *takeAt(int index); + int count() const; + + bool isEmpty() const; + void invalidate(); + Qt::Orientations expandingDirections() const; + + void setGeometry(const QRect &r); + QSize minimumSize() const; + QSize sizeHint() const; + + void insertAction(int index, QAction *action); + int indexOf(QAction *action) const; + int indexOf(QWidget *widget) const { return QLayout::indexOf(widget); } + + QRect handleRect() const; + + bool layoutActions(const QSize &size); + QSize expandedSize(const QSize &size) const; + bool expanded, animating; + + void setUsePopupMenu(bool set); // Yeah, there's no getter, but it's internal. + void checkUsePopupMenu(); + + bool movable() const; + void updateMarginAndSpacing(); + bool hasExpandFlag() const; + +public slots: + void setExpanded(bool b); + +private: + QList<QToolBarItem*> items; + QSize hint, minSize; + bool dirty, expanding, empty, expandFlag; + QVector<QLayoutStruct> geomArray; + QRect handRect; + QToolBarExtension *extension; + + void updateGeomArray() const; + QToolBarItem *createItem(QAction *action); + QMenu *popupMenu; +}; + +#endif // QT_NO_TOOLBAR + +QT_END_NAMESPACE + +#endif // QTOOLBARLAYOUT_P_H diff --git a/src/gui/widgets/qtoolbarseparator.cpp b/src/gui/widgets/qtoolbarseparator.cpp new file mode 100644 index 0000000..c242687 --- /dev/null +++ b/src/gui/widgets/qtoolbarseparator.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtoolbarseparator_p.h" + +#ifndef QT_NO_TOOLBAR + +#include <qstyle.h> +#include <qstyleoption.h> +#include <qtoolbar.h> +#include <qpainter.h> + +QT_BEGIN_NAMESPACE + +void QToolBarSeparator::initStyleOption(QStyleOption *option) const +{ + option->initFrom(this); + if (orientation() == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; +} + +QToolBarSeparator::QToolBarSeparator(QToolBar *parent) + : QWidget(parent), orient(parent->orientation()) +{ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } + +void QToolBarSeparator::setOrientation(Qt::Orientation orientation) +{ + orient = orientation; + update(); +} + +Qt::Orientation QToolBarSeparator::orientation() const +{ return orient; } + +QSize QToolBarSeparator::sizeHint() const +{ + QStyleOption opt; + initStyleOption(&opt); + const int extent = style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget()); + return QSize(extent, extent); +} + +void QToolBarSeparator::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QStyleOption opt; + initStyleOption(&opt); + style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget()); +} + +QT_END_NAMESPACE + +#endif // QT_NO_TOOLBAR diff --git a/src/gui/widgets/qtoolbarseparator_p.h b/src/gui/widgets/qtoolbarseparator_p.h new file mode 100644 index 0000000..c3552e6 --- /dev/null +++ b/src/gui/widgets/qtoolbarseparator_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDYNAMICTOOLBARSEPARATOR_P_H +#define QDYNAMICTOOLBARSEPARATOR_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 "QtGui/qwidget.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TOOLBAR + +class QStyleOption; +class QToolBar; + +class QToolBarSeparator : public QWidget +{ + Q_OBJECT + Qt::Orientation orient; + +public: + explicit QToolBarSeparator(QToolBar *parent); + + Qt::Orientation orientation() const; + + QSize sizeHint() const; + + void paintEvent(QPaintEvent *); + void initStyleOption(QStyleOption *option) const; + +public Q_SLOTS: + void setOrientation(Qt::Orientation orientation); +}; + +#endif // QT_NO_TOOLBAR + +QT_END_NAMESPACE + +#endif // QDYNAMICTOOLBARSEPARATOR_P_H diff --git a/src/gui/widgets/qtoolbox.cpp b/src/gui/widgets/qtoolbox.cpp new file mode 100644 index 0000000..81935a5 --- /dev/null +++ b/src/gui/widgets/qtoolbox.cpp @@ -0,0 +1,822 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtoolbox.h" + +#ifndef QT_NO_TOOLBOX + +#include <qapplication.h> +#include <qeventloop.h> +#include <qlayout.h> +#include <qlist.h> +#include <qpainter.h> +#include <qscrollarea.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qtooltip.h> +#include <qabstractbutton.h> + +#include "qframe_p.h" + +QT_BEGIN_NAMESPACE + +class QToolBoxButton : public QAbstractButton +{ + Q_OBJECT +public: + QToolBoxButton(QWidget *parent) + : QAbstractButton(parent), selected(false), indexInPage(-1) + { + setBackgroundRole(QPalette::Window); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + setFocusPolicy(Qt::NoFocus); + } + + inline void setSelected(bool b) { selected = b; update(); } + inline void setIndex(int newIndex) { indexInPage = newIndex; } + + QSize sizeHint() const; + QSize minimumSizeHint() const; + +protected: + void initStyleOption(QStyleOptionToolBox *opt) const; + void paintEvent(QPaintEvent *); + +private: + bool selected; + int indexInPage; +}; + + +class QToolBoxPrivate : public QFramePrivate +{ + Q_DECLARE_PUBLIC(QToolBox) +public: + struct Page + { + QToolBoxButton *button; + QScrollArea *sv; + QWidget *widget; + + inline void setText(const QString &text) { button->setText(text); } + inline void setIcon(const QIcon &is) { button->setIcon(is); } +#ifndef QT_NO_TOOLTIP + inline void setToolTip(const QString &tip) { button->setToolTip(tip); } + inline QString toolTip() const { return button->toolTip(); } +#endif + inline QString text() const { return button->text(); } + inline QIcon icon() const { return button->icon(); } + + inline bool operator==(const Page& other) const + { + return widget == other.widget; + } + }; + typedef QList<Page> PageList; + + inline QToolBoxPrivate() + : currentPage(0) + { + } + void _q_buttonClicked(); + void _q_widgetDestroyed(QObject*); + + Page *page(QWidget *widget) const; + const Page *page(int index) const; + Page *page(int index); + + void updateTabs(); + void relayout(); + + PageList pageList; + QVBoxLayout *layout; + Page *currentPage; +}; + +QToolBoxPrivate::Page *QToolBoxPrivate::page(QWidget *widget) const +{ + if (!widget) + return 0; + + for (PageList::ConstIterator i = pageList.constBegin(); i != pageList.constEnd(); ++i) + if ((*i).widget == widget) + return (Page*) &(*i); + return 0; +} + +QToolBoxPrivate::Page *QToolBoxPrivate::page(int index) +{ + if (index >= 0 && index < pageList.size()) + return &pageList[index]; + return 0; +} + +const QToolBoxPrivate::Page *QToolBoxPrivate::page(int index) const +{ + if (index >= 0 && index < pageList.size()) + return &pageList.at(index); + return 0; +} + +void QToolBoxPrivate::updateTabs() +{ + QToolBoxButton *lastButton = currentPage ? currentPage->button : 0; + bool after = false; + int index = 0; + for (index = 0; index < pageList.count(); ++index) { + const Page &page = pageList.at(index); + QToolBoxButton *tB = page.button; + // update indexes, since the updates are delayed, the indexes will be correct + // when we actually paint. + tB->setIndex(index); + QWidget *tW = page.widget; + if (after) { + QPalette p = tB->palette(); + p.setColor(tB->backgroundRole(), tW->palette().color(tW->backgroundRole())); + tB->setPalette(p); + tB->update(); + } else if (tB->backgroundRole() != QPalette::Window) { + tB->setBackgroundRole(QPalette::Window); + tB->update(); + } + after = tB == lastButton; + } +} + +QSize QToolBoxButton::sizeHint() const +{ + QSize iconSize(8, 8); + if (!icon().isNull()) { + int icone = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, parentWidget() /* QToolBox */); + iconSize += QSize(icone + 2, icone); + } + QSize textSize = fontMetrics().size(Qt::TextShowMnemonic, text()) + QSize(0, 8); + + QSize total(iconSize.width() + textSize.width(), qMax(iconSize.height(), textSize.height())); + return total.expandedTo(QApplication::globalStrut()); +} + +QSize QToolBoxButton::minimumSizeHint() const +{ + if (icon().isNull()) + return QSize(); + int icone = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, parentWidget() /* QToolBox */); + return QSize(icone + 8, icone + 8); +} + +void QToolBoxButton::initStyleOption(QStyleOptionToolBox *option) const +{ + if (!option) + return; + option->initFrom(this); + if (selected) + option->state |= QStyle::State_Selected; + if (isDown()) + option->state |= QStyle::State_Sunken; + option->text = text(); + option->icon = icon(); + + if (QStyleOptionToolBoxV2 *optionV2 = qstyleoption_cast<QStyleOptionToolBoxV2 *>(option)) { + QToolBox *toolBox = static_cast<QToolBox *>(parentWidget()); // I know I'm in a tool box. + int widgetCount = toolBox->count(); + int currIndex = toolBox->currentIndex(); + if (widgetCount == 1) { + optionV2->position = QStyleOptionToolBoxV2::OnlyOneTab; + } else if (indexInPage == 0) { + optionV2->position = QStyleOptionToolBoxV2::Beginning; + } else if (indexInPage == widgetCount - 1) { + optionV2->position = QStyleOptionToolBoxV2::End; + } else { + optionV2->position = QStyleOptionToolBoxV2::Middle; + } + if (currIndex == indexInPage - 1) { + optionV2->selectedPosition = QStyleOptionToolBoxV2::PreviousIsSelected; + } else if (currIndex == indexInPage + 1) { + optionV2->selectedPosition = QStyleOptionToolBoxV2::NextIsSelected; + } else { + optionV2->selectedPosition = QStyleOptionToolBoxV2::NotAdjacent; + } + } +} + +void QToolBoxButton::paintEvent(QPaintEvent *) +{ + QPainter paint(this); + QString text = QAbstractButton::text(); + QPainter *p = &paint; + QStyleOptionToolBoxV2 opt; + initStyleOption(&opt); + style()->drawControl(QStyle::CE_ToolBoxTab, &opt, p, parentWidget()); +} + +/*! + \class QToolBox + + \brief The QToolBox class provides a column of tabbed widget items. + + \mainclass + \ingroup basicwidgets + + A toolbox is a widget that displays a column of tabs one above the + other, with the current item displayed below the current tab. + Every tab has an index position within the column of tabs. A tab's + item is a QWidget. + + Each item has an itemText(), an optional itemIcon(), an optional + itemToolTip(), and a widget(). The item's attributes can be + changed with setItemText(), setItemIcon(), and + setItemToolTip(). Each item can be enabled or disabled + individually with setItemEnabled(). + + Items are added using addItem(), or inserted at particular + positions using insertItem(). The total number of items is given + by count(). Items can be deleted with delete, or removed from the + toolbox with removeItem(). Combining removeItem() and insertItem() + allows you to move items to different positions. + + The index of the current item widget is returned by currentIndex(), + and set with setCurrentIndex(). The index of a particular item can + be found using indexOf(), and the item at a given index is returned + by item(). + + The currentChanged() signal is emitted when the current item is + changed. + + \sa QTabWidget +*/ + +/*! + \fn void QToolBox::currentChanged(int index) + + This signal is emitted when the current item is changed. The new + current item's index is passed in \a index, or -1 if there is no + current item. +*/ + +#ifdef QT3_SUPPORT +/*! + Constructs a toolbox called \a name with parent \a parent and flags \a f. +*/ +QToolBox::QToolBox(QWidget *parent, const char *name, Qt::WindowFlags f) + : QFrame(*new QToolBoxPrivate, parent, f) +{ + Q_D(QToolBox); + setObjectName(QString::fromAscii(name)); + d->layout = new QVBoxLayout(this); + d->layout->setMargin(0); + setBackgroundRole(QPalette::Button); +} +#endif + +/*! + Constructs a new toolbox with the given \a parent and the flags, \a f. +*/ +QToolBox::QToolBox(QWidget *parent, Qt::WindowFlags f) + : QFrame(*new QToolBoxPrivate, parent, f) +{ + Q_D(QToolBox); + d->layout = new QVBoxLayout(this); + d->layout->setMargin(0); + setBackgroundRole(QPalette::Button); +} + +/*! + Destroys the toolbox. +*/ + +QToolBox::~QToolBox() +{ +} + +/*! + \fn int QToolBox::addItem(QWidget *w, const QString &text) + \overload + + Adds the widget \a w in a new tab at bottom of the toolbox. The + new tab's text is set to \a text. Returns the new tab's index. +*/ + +/*! + \fn int QToolBox::addItem(QWidget *widget, const QIcon &iconSet,const QString &text) + Adds the \a widget in a new tab at bottom of the toolbox. The + new tab's text is set to \a text, and the \a iconSet is + displayed to the left of the \a text. Returns the new tab's index. +*/ + +/*! + \fn int QToolBox::insertItem(int index, QWidget *widget, const QString &text) + \overload + + Inserts the \a widget at position \a index, or at the bottom + of the toolbox if \a index is out of range. The new item's text is + set to \a text. Returns the new item's index. +*/ + +/*! + Inserts the \a widget at position \a index, or at the bottom + of the toolbox if \a index is out of range. The new item's text + is set to \a text, and the \a icon is displayed to the left of + the \a text. Returns the new item's index. +*/ + +int QToolBox::insertItem(int index, QWidget *widget, const QIcon &icon, const QString &text) +{ + if (!widget) + return -1; + + Q_D(QToolBox); + connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_q_widgetDestroyed(QObject*))); + + QToolBoxPrivate::Page c; + c.widget = widget; + c.button = new QToolBoxButton(this); + c.button->setObjectName(QLatin1String("qt_toolbox_toolboxbutton")); + connect(c.button, SIGNAL(clicked()), this, SLOT(_q_buttonClicked())); + + c.sv = new QScrollArea(this); + c.sv->setWidget(widget); + c.sv->setWidgetResizable(true); + c.sv->hide(); + c.sv->setFrameStyle(QFrame::NoFrame); + + c.setText(text); + c.setIcon(icon); + + if (index < 0 || index >= (int)d->pageList.count()) { + index = d->pageList.count(); + d->pageList.append(c); + d->layout->addWidget(c.button); + d->layout->addWidget(c.sv); + if (index == 0) + setCurrentIndex(index); + } else { + d->pageList.insert(index, c); + d->relayout(); + if (d->currentPage) { + QWidget *current = d->currentPage->widget; + int oldindex = indexOf(current); + if (index <= oldindex) { + d->currentPage = 0; // trigger change + setCurrentIndex(oldindex); + } + } + } + + c.button->show(); + + d->updateTabs(); + itemInserted(index); + return index; +} + +void QToolBoxPrivate::_q_buttonClicked() +{ + Q_Q(QToolBox); + QToolBoxButton *tb = qobject_cast<QToolBoxButton*>(q->sender()); + QWidget* item = 0; + for (QToolBoxPrivate::PageList::ConstIterator i = pageList.constBegin(); i != pageList.constEnd(); ++i) + if ((*i).button == tb) { + item = (*i).widget; + break; + } + + q->setCurrentIndex(q->indexOf(item)); +} + +/*! + \property QToolBox::count + \brief The number of items contained in the toolbox. + + By default, this property has a value of 0. +*/ + +int QToolBox::count() const +{ + Q_D(const QToolBox); + return d->pageList.count(); +} + +void QToolBox::setCurrentIndex(int index) +{ + Q_D(QToolBox); + QToolBoxPrivate::Page *c = d->page(index); + if (!c || d->currentPage == c) + return; + + c->button->setSelected(true); + if (d->currentPage) { + d->currentPage->sv->hide(); + d->currentPage->button->setSelected(false); + } + d->currentPage = c; + d->currentPage->sv->show(); + d->updateTabs(); + emit currentChanged(index); +} + +void QToolBoxPrivate::relayout() +{ + Q_Q(QToolBox); + delete layout; + layout = new QVBoxLayout(q); + layout->setMargin(0); + for (QToolBoxPrivate::PageList::ConstIterator i = pageList.constBegin(); i != pageList.constEnd(); ++i) { + layout->addWidget((*i).button); + layout->addWidget((*i).sv); + } +} + +void QToolBoxPrivate::_q_widgetDestroyed(QObject *object) +{ + Q_Q(QToolBox); + // no verification - vtbl corrupted already + QWidget *p = (QWidget*)object; + + QToolBoxPrivate::Page *c = page(p); + if (!p || !c) + return; + + layout->removeWidget(c->sv); + layout->removeWidget(c->button); + c->sv->deleteLater(); // page might still be a child of sv + delete c->button; + + bool removeCurrent = c == currentPage; + pageList.removeAll(*c); + + if (!pageList.count()) { + currentPage = 0; + emit q->currentChanged(-1); + } else if (removeCurrent) { + currentPage = 0; + q->setCurrentIndex(0); + } +} + +/*! + Removes the item at position \a index from the toolbox. Note that + the widget is \e not deleted. +*/ + +void QToolBox::removeItem(int index) +{ + Q_D(QToolBox); + if (QWidget *w = widget(index)) { + disconnect(w, SIGNAL(destroyed(QObject*)), this, SLOT(_q_widgetDestroyed(QObject*))); + w->setParent(this); + // destroy internal data + d->_q_widgetDestroyed(w); + itemRemoved(index); + } +} + + +/*! + \property QToolBox::currentIndex + \brief the index of the current item + + By default, for an empty toolbox, this property has a value of -1. + + \sa indexOf(), widget() +*/ + + +int QToolBox::currentIndex() const +{ + Q_D(const QToolBox); + return d->currentPage ? indexOf(d->currentPage->widget) : -1; +} + +/*! + Returns a pointer to the current widget, or 0 if there is no such item. + + \sa currentIndex(), setCurrentWidget() +*/ + +QWidget * QToolBox::currentWidget() const +{ + Q_D(const QToolBox); + return d->currentPage ? d->currentPage->widget : 0; +} + +/*! + Makes\a widget the current widget. The \a widget must be an item in this tool box. + + \sa addItem(), setCurrentIndex(), currentWidget() + */ +void QToolBox::setCurrentWidget(QWidget *widget) +{ + int i = indexOf(widget); + if (i >= 0) + setCurrentIndex(i); + else + qWarning("QToolBox::setCurrentWidget: widget not contained in tool box"); +} + +/*! + Returns the widget at position \a index, or 0 if there is no such + item. +*/ + +QWidget *QToolBox::widget(int index) const +{ + Q_D(const QToolBox); + if (index < 0 || index >= (int) d->pageList.size()) + return 0; + return d->pageList.at(index).widget; +} + +/*! + Returns the index of \a widget, or -1 if the item does not + exist. +*/ + +int QToolBox::indexOf(QWidget *widget) const +{ + Q_D(const QToolBox); + QToolBoxPrivate::Page *c = (widget ? d->page(widget) : 0); + return c ? d->pageList.indexOf(*c) : -1; +} + +/*! + If \a enabled is true then the item at position \a index is enabled; otherwise + the item at position \a index is disabled. +*/ + +void QToolBox::setItemEnabled(int index, bool enabled) +{ + Q_D(QToolBox); + QToolBoxPrivate::Page *c = d->page(index); + if (!c) + return; + + c->button->setEnabled(enabled); + if (!enabled && c == d->currentPage) { + int curIndexUp = index; + int curIndexDown = curIndexUp; + const int count = d->pageList.count(); + while (curIndexUp > 0 || curIndexDown < count-1) { + if (curIndexDown < count-1) { + if (d->page(++curIndexDown)->button->isEnabled()) { + index = curIndexDown; + break; + } + } + if (curIndexUp > 0) { + if (d->page(--curIndexUp)->button->isEnabled()) { + index = curIndexUp; + break; + } + } + } + setCurrentIndex(index); + } +} + + +/*! + Sets the text of the item at position \a index to \a text. + + If the provided text contains an ampersand character ('&'), a + mnemonic is automatically created for it. The character that + follows the '&' will be used as the shortcut key. Any previous + mnemonic will be overwritten, or cleared if no mnemonic is defined + by the text. See the \l {QShortcut#mnemonic}{QShortcut} + documentation for details (to display an actual ampersand, use + '&&'). +*/ + +void QToolBox::setItemText(int index, const QString &text) +{ + Q_D(QToolBox); + QToolBoxPrivate::Page *c = d->page(index); + if (c) + c->setText(text); +} + +/*! + Sets the icon of the item at position \a index to \a icon. +*/ + +void QToolBox::setItemIcon(int index, const QIcon &icon) +{ + Q_D(QToolBox); + QToolBoxPrivate::Page *c = d->page(index); + if (c) + c->setIcon(icon); +} + +#ifndef QT_NO_TOOLTIP +/*! + Sets the tooltip of the item at position \a index to \a toolTip. +*/ + +void QToolBox::setItemToolTip(int index, const QString &toolTip) +{ + Q_D(QToolBox); + QToolBoxPrivate::Page *c = d->page(index); + if (c) + c->setToolTip(toolTip); +} +#endif // QT_NO_TOOLTIP + +/*! + Returns true if the item at position \a index is enabled; otherwise returns false. +*/ + +bool QToolBox::isItemEnabled(int index) const +{ + Q_D(const QToolBox); + const QToolBoxPrivate::Page *c = d->page(index); + return c && c->button->isEnabled(); +} + +/*! + Returns the text of the item at position \a index, or an empty string if + \a index is out of range. +*/ + +QString QToolBox::itemText(int index) const +{ + Q_D(const QToolBox); + const QToolBoxPrivate::Page *c = d->page(index); + return (c ? c->text() : QString()); +} + +/*! + Returns the icon of the item at position \a index, or a null + icon if \a index is out of range. +*/ + +QIcon QToolBox::itemIcon(int index) const +{ + Q_D(const QToolBox); + const QToolBoxPrivate::Page *c = d->page(index); + return (c ? c->icon() : QIcon()); +} + +#ifndef QT_NO_TOOLTIP +/*! + Returns the tooltip of the item at position \a index, or an + empty string if \a index is out of range. +*/ + +QString QToolBox::itemToolTip(int index) const +{ + Q_D(const QToolBox); + const QToolBoxPrivate::Page *c = d->page(index); + return (c ? c->toolTip() : QString()); +} +#endif // QT_NO_TOOLTIP + +/*! \reimp */ +void QToolBox::showEvent(QShowEvent *e) +{ + QWidget::showEvent(e); +} + +/*! \reimp */ +void QToolBox::changeEvent(QEvent *ev) +{ + Q_D(QToolBox); + if(ev->type() == QEvent::StyleChange) + d->updateTabs(); + QFrame::changeEvent(ev); +} + +/*! + This virtual handler is called after a new item was added or + inserted at position \a index. + + \sa itemRemoved() + */ +void QToolBox::itemInserted(int index) +{ + Q_UNUSED(index) +} + +/*! + This virtual handler is called after an item was removed from + position \a index. + + \sa itemInserted() + */ +void QToolBox::itemRemoved(int index) +{ + Q_UNUSED(index) +} + +/*! + \fn void QToolBox::setItemLabel(int index, const QString &text) + + Use setItemText() instead. +*/ + +/*! + \fn QString QToolBox::itemLabel(int index) const + + Use itemText() instead. +*/ + +/*! + \fn QWidget *QToolBox::currentItem() const + + Use widget(currentIndex()) instead. +*/ + +/*! + \fn void QToolBox::setCurrentItem(QWidget *widget) + + Use setCurrentIndex(indexOf(widget)) instead. +*/ + +/*! + \fn void QToolBox::setItemIconSet(int index, const QIcon &icon) + + Use setItemIcon() instead. +*/ + +/*! + \fn QIcon QToolBox::itemIconSet(int index) const + + Use itemIcon() instead. +*/ + +/*! + \fn int QToolBox::removeItem(QWidget *widget) + + Use toolbox->removeItem(toolbox->indexOf(widget)) instead. +*/ + +/*! + \fn QWidget *QToolBox::item(int index) const + + Use widget() instead. +*/ + +/*! + \fn void QToolBox::setMargin(int margin) + Sets the width of the margin around the contents of the widget to \a margin. + + Use QWidget::setContentsMargins() instead. + \sa margin(), QWidget::setContentsMargins() +*/ + +/*! + \fn int QToolBox::margin() const + Returns the with of the the margin around the contents of the widget. + + Use QWidget::getContentsMargins() instead. + \sa setMargin(), QWidget::getContentsMargins() +*/ + +/*! \reimp */ +bool QToolBox::event(QEvent *e) +{ + return QFrame::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qtoolbox.cpp" +#include "qtoolbox.moc" + +#endif //QT_NO_TOOLBOX diff --git a/src/gui/widgets/qtoolbox.h b/src/gui/widgets/qtoolbox.h new file mode 100644 index 0000000..435fab9 --- /dev/null +++ b/src/gui/widgets/qtoolbox.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTOOLBOX_H +#define QTOOLBOX_H + +#include <QtGui/qframe.h> +#include <QtGui/qicon.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TOOLBOX + +class QToolBoxPrivate; + +class Q_GUI_EXPORT QToolBox : public QFrame +{ + Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentChanged) + Q_PROPERTY(int count READ count) + +public: + explicit QToolBox(QWidget *parent = 0, Qt::WindowFlags f = 0); + ~QToolBox(); + + int addItem(QWidget *widget, const QString &text); + int addItem(QWidget *widget, const QIcon &icon, const QString &text); + int insertItem(int index, QWidget *widget, const QString &text); + int insertItem(int index, QWidget *widget, const QIcon &icon, const QString &text); + + void removeItem(int index); + + void setItemEnabled(int index, bool enabled); + bool isItemEnabled(int index) const; + + void setItemText(int index, const QString &text); + QString itemText(int index) const; + + void setItemIcon(int index, const QIcon &icon); + QIcon itemIcon(int index) const; + +#ifndef QT_NO_TOOLTIP + void setItemToolTip(int index, const QString &toolTip); + QString itemToolTip(int index) const; +#endif + + int currentIndex() const; + QWidget *currentWidget() const; + QWidget *widget(int index) const; + int indexOf(QWidget *widget) const; + int count() const; + +public Q_SLOTS: + void setCurrentIndex(int index); + void setCurrentWidget(QWidget *widget); + +Q_SIGNALS: + void currentChanged(int index); + +protected: + bool event(QEvent *e); + virtual void itemInserted(int index); + virtual void itemRemoved(int index); + void showEvent(QShowEvent *e); + void changeEvent(QEvent *); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QToolBox(QWidget *parent, const char *name, Qt::WindowFlags f = 0); + inline QT3_SUPPORT void setItemLabel(int index, const QString &text) { setItemText(index, text); } + inline QT3_SUPPORT QString itemLabel(int index) const { return itemText(index); } + inline QT3_SUPPORT QWidget *currentItem() const { return widget(currentIndex()); } + inline QT3_SUPPORT void setCurrentItem(QWidget *item) { setCurrentIndex(indexOf(item)); } + inline QT3_SUPPORT void setItemIconSet(int index, const QIcon &icon) { setItemIcon(index, icon); } + inline QT3_SUPPORT QIcon itemIconSet(int index) const { return itemIcon(index); } + inline QT3_SUPPORT int removeItem(QWidget *item) + { int i = indexOf(item); removeItem(i); return i; } + inline QT3_SUPPORT QWidget *item(int index) const { return widget(index); } + QT3_SUPPORT void setMargin(int margin) { setContentsMargins(margin, margin, margin, margin); } + QT3_SUPPORT int margin() const + { int margin; int dummy; getContentsMargins(&margin, &dummy, &dummy, &dummy); return margin; } +#endif + +private: + Q_DECLARE_PRIVATE(QToolBox) + Q_DISABLE_COPY(QToolBox) + Q_PRIVATE_SLOT(d_func(), void _q_buttonClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_widgetDestroyed(QObject*)) +}; + + +inline int QToolBox::addItem(QWidget *item, const QString &text) +{ return insertItem(-1, item, QIcon(), text); } +inline int QToolBox::addItem(QWidget *item, const QIcon &iconSet, + const QString &text) +{ return insertItem(-1, item, iconSet, text); } +inline int QToolBox::insertItem(int index, QWidget *item, const QString &text) +{ return insertItem(index, item, QIcon(), text); } + +#endif // QT_NO_TOOLBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTOOLBOX_H diff --git a/src/gui/widgets/qtoolbutton.cpp b/src/gui/widgets/qtoolbutton.cpp new file mode 100644 index 0000000..7390d04 --- /dev/null +++ b/src/gui/widgets/qtoolbutton.cpp @@ -0,0 +1,1251 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtoolbutton.h" +#ifndef QT_NO_TOOLBUTTON + +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qdrawutil.h> +#include <qevent.h> +#include <qicon.h> +#include <qmenu.h> +#include <qpainter.h> +#include <qpointer.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qtooltip.h> +#include <qmainwindow.h> +#include <qtoolbar.h> +#include <qvariant.h> +#include <qstylepainter.h> +#include <private/qabstractbutton_p.h> +#include <private/qaction_p.h> +#include <private/qmenu_p.h> + +QT_BEGIN_NAMESPACE + +class QToolButtonPrivate : public QAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QToolButton) +public: + void init(); +#ifndef QT_NO_MENU + void _q_buttonPressed(); + void popupTimerDone(); + void _q_updateButtonDown(); + void _q_menuTriggered(QAction *); +#endif + bool updateHoverControl(const QPoint &pos); + void _q_actionTriggered(); + QStyle::SubControl newHoverControl(const QPoint &pos); + QStyle::SubControl hoverControl; + QRect hoverRect; + QPointer<QAction> menuAction; //the menu set by the user (setMenu) + QBasicTimer popupTimer; + int delay; + Qt::ArrowType arrowType; + Qt::ToolButtonStyle toolButtonStyle; + QToolButton::ToolButtonPopupMode popupMode; + enum { NoButtonPressed=0, MenuButtonPressed=1, ToolButtonPressed=2 }; + uint buttonPressed : 2; + uint menuButtonDown : 1; + uint autoRaise : 1; + uint repeat : 1; + QAction *defaultAction; +#ifndef QT_NO_MENU + bool hasMenu() const; + //workaround for task 177850 + QList<QAction *> actionsCopy; +#endif +#ifdef QT3_SUPPORT + bool userDefinedPopupDelay; +#endif +}; + +#ifndef QT_NO_MENU +bool QToolButtonPrivate::hasMenu() const +{ + Q_Q(const QToolButton); + return ((defaultAction && defaultAction->menu()) + || (menuAction && menuAction->menu()) + || q->actions().size() > (defaultAction ? 1 : 0)); +} +#endif + +/*! + \class QToolButton + \brief The QToolButton class provides a quick-access button to + commands or options, usually used inside a QToolBar. + + \ingroup basicwidgets + \mainclass + + A tool button is a special button that provides quick-access to + specific commands or options. As opposed to a normal command + button, a tool button usually doesn't show a text label, but shows + an icon instead. + + Tool buttons are normally created when new QAction instances are + created with QToolBar::addAction() or existing actions are added + to a toolbar with QToolBar::addAction(). It is also possible to + construct tool buttons in the same way as any other widget, and + arrange them alongside other widgets in layouts. + + One classic use of a tool button is to select tools; for example, + the "pen" tool in a drawing program. This would be implemented + by using a QToolButton as a toggle button (see setToggleButton()). + + QToolButton supports auto-raising. In auto-raise mode, the button + draws a 3D frame only when the mouse points at it. The feature is + automatically turned on when a button is used inside a QToolBar. + Change it with setAutoRaise(). + + A tool button's icon is set as QIcon. This makes it possible to + specify different pixmaps for the disabled and active state. The + disabled pixmap is used when the button's functionality is not + available. The active pixmap is displayed when the button is + auto-raised because the mouse pointer is hovering over it. + + The button's look and dimension is adjustable with + setToolButtonStyle() and setIconSize(). When used inside a + QToolBar in a QMainWindow, the button automatically adjusts to + QMainWindow's settings (see QMainWindow::setToolButtonStyle() and + QMainWindow::setIconSize()). Instead of an icon, a tool button can + also display an arrow symbol, specified with + \l{QToolButton::arrowType} {arrowType}. + + A tool button can offer additional choices in a popup menu. The + popup menu can be set using setMenu(). Use setPopupMode() to + configure the different modes available for tool buttons with a + menu set. The default mode is DelayedPopupMode which is sometimes + used with the "Back" button in a web browser. After pressing and + holding the button down for a while, a menu pops up showing a list + of possible pages to jump to. The default delay is 600 ms; you can + adjust it with setPopupDelay(). + + \table 100% + \row \o \inlineimage assistant-toolbar.png Qt Assistant's toolbar with tool buttons + \row \o Qt Assistant's toolbar contains tool buttons that are associated + with actions used in other parts of the main window. + \endtable + + \sa QPushButton, QToolBar, QMainWindow, QAction, + {fowler}{GUI Design Handbook: Push Button} +*/ + +/*! + \fn void QToolButton::triggered(QAction *action) + + This signal is emitted when the given \a action is triggered. + + The action may also be associated with other parts of the user interface, + such as menu items and keyboard shortcuts. Sharing actions in this + way helps make the user interface more consistent and is often less work + to implement. +*/ + +/*! + Constructs an empty tool button with parent \a + parent. +*/ +QToolButton::QToolButton(QWidget * parent) + : QAbstractButton(*new QToolButtonPrivate, parent) +{ + Q_D(QToolButton); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Constructs an empty tool button called \a name, with parent \a + parent. +*/ + +QToolButton::QToolButton(QWidget * parent, const char *name) + : QAbstractButton(*new QToolButtonPrivate, parent) +{ + Q_D(QToolButton); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Constructs a tool button called \a name, that is a child of \a + parent. + + The tool button will display the given \a icon, with its text + label and tool tip set to \a textLabel and its status bar message + set to \a statusTip. It will be connected to the \a slot in + object \a receiver. +*/ + +QToolButton::QToolButton(const QIcon& icon, const QString &textLabel, + const QString& statusTip, + QObject * receiver, const char *slot, + QWidget * parent, const char *name) + : QAbstractButton(*new QToolButtonPrivate, parent) +{ + Q_D(QToolButton); + setObjectName(QString::fromAscii(name)); + d->init(); + setIcon(icon); + setText(textLabel); + if (receiver && slot) + connect(this, SIGNAL(clicked()), receiver, slot); +#ifndef QT_NO_TOOLTIP + if (!textLabel.isEmpty()) + setToolTip(textLabel); +#endif +#ifndef QT_NO_STATUSTIP + if (!statusTip.isEmpty()) + setStatusTip(statusTip); +#else + Q_UNUSED(statusTip); +#endif +} + + +/*! + Constructs a tool button as an arrow button. The Qt::ArrowType \a + type defines the arrow direction. Possible values are + Qt::LeftArrow, Qt::RightArrow, Qt::UpArrow, and Qt::DownArrow. + + An arrow button has auto-repeat turned on by default. + + The \a parent and \a name arguments are sent to the QWidget + constructor. +*/ +QToolButton::QToolButton(Qt::ArrowType type, QWidget *parent, const char *name) + : QAbstractButton(*new QToolButtonPrivate, parent) +{ + Q_D(QToolButton); + setObjectName(QString::fromAscii(name)); + d->init(); + setAutoRepeat(true); + d->arrowType = type; +} + +#endif + + +/* Set-up code common to all the constructors */ + +void QToolButtonPrivate::init() +{ + Q_Q(QToolButton); + delay = q->style()->styleHint(QStyle::SH_ToolButton_PopupDelay, 0, q); +#ifdef QT3_SUPPORT + userDefinedPopupDelay = false; +#endif + defaultAction = 0; +#ifndef QT_NO_TOOLBAR + if (qobject_cast<QToolBar*>(q->parentWidget())) + autoRaise = true; + else +#endif + autoRaise = false; + arrowType = Qt::NoArrow; + menuButtonDown = false; + popupMode = QToolButton::DelayedPopup; + buttonPressed = QToolButtonPrivate::NoButtonPressed; + + toolButtonStyle = Qt::ToolButtonIconOnly; + hoverControl = QStyle::SC_None; + + q->setFocusPolicy(Qt::TabFocus); + q->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed, + QSizePolicy::ToolButton)); + +#ifndef QT_NO_MENU + QObject::connect(q, SIGNAL(pressed()), q, SLOT(_q_buttonPressed())); +#endif + + setLayoutItemMargins(QStyle::SE_ToolButtonLayoutItem); + +} + +/*! + Initialize \a option with the values from this QToolButton. This method + is useful for subclasses when they need a QStyleOptionToolButton, but don't want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QToolButton::initStyleOption(QStyleOptionToolButton *option) const +{ + if (!option) + return; + + Q_D(const QToolButton); + option->initFrom(this); + bool forceNoText = false; + option->iconSize = iconSize(); //default value + +#ifndef QT_NO_TOOLBAR + if (parentWidget()) { + if (QToolBar *toolBar = qobject_cast<QToolBar *>(parentWidget())) { + option->iconSize = toolBar->iconSize(); + } +#ifdef QT3_SUPPORT + else if (parentWidget()->inherits("Q3ToolBar")) { + if (!option->iconSize.isValid()) { + int iconSize = style()->pixelMetric(QStyle::PM_ToolBarIconSize, option, this); + option->iconSize = d->icon.actualSize(QSize(iconSize, iconSize)); + } + forceNoText = d->toolButtonStyle == Qt::ToolButtonIconOnly; + } +#endif + } +#endif // QT_NO_TOOLBAR + + if (!forceNoText) + option->text = d->text; + option->icon = d->icon; + option->arrowType = d->arrowType; + if (d->down) + option->state |= QStyle::State_Sunken; + if (d->checked) + option->state |= QStyle::State_On; + if (d->autoRaise) + option->state |= QStyle::State_AutoRaise; + if (!d->checked && !d->down) + option->state |= QStyle::State_Raised; + + option->subControls = QStyle::SC_ToolButton; + option->activeSubControls = QStyle::SC_None; + + option->features = QStyleOptionToolButton::None; + if (d->popupMode == QToolButton::MenuButtonPopup) { + option->subControls |= QStyle::SC_ToolButtonMenu; + option->features |= QStyleOptionToolButton::MenuButtonPopup; + } + if (option->state & QStyle::State_MouseOver) { + option->activeSubControls = d->hoverControl; + } + if (d->menuButtonDown) { + option->state |= QStyle::State_Sunken; + option->activeSubControls |= QStyle::SC_ToolButtonMenu; + } + if (d->down) { + option->state |= QStyle::State_Sunken; + option->activeSubControls |= QStyle::SC_ToolButton; + } + + + if (d->arrowType != Qt::NoArrow) + option->features |= QStyleOptionToolButton::Arrow; + if (d->popupMode == QToolButton::DelayedPopup) + option->features |= QStyleOptionToolButton::PopupDelay; +#ifndef QT_NO_MENU + if (d->hasMenu()) + option->features |= QStyleOptionToolButton::HasMenu; +#endif + option->toolButtonStyle = d->toolButtonStyle; + if (d->icon.isNull() && d->arrowType == Qt::NoArrow && !forceNoText) { + if (!d->text.isEmpty()) + option->toolButtonStyle = Qt::ToolButtonTextOnly; + else if (option->toolButtonStyle != Qt::ToolButtonTextOnly) + option->toolButtonStyle = Qt::ToolButtonIconOnly; + } else { + if (d->text.isEmpty() && option->toolButtonStyle != Qt::ToolButtonIconOnly) + option->toolButtonStyle = Qt::ToolButtonIconOnly; + } + + option->pos = pos(); + option->font = font(); +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QToolButton::~QToolButton() +{ +} + +/*! + \reimp +*/ +QSize QToolButton::sizeHint() const +{ + Q_D(const QToolButton); + if (d->sizeHint.isValid()) + return d->sizeHint; + ensurePolished(); + + int w = 0, h = 0; + QStyleOptionToolButton opt; + initStyleOption(&opt); + + QFontMetrics fm = fontMetrics(); + if (opt.toolButtonStyle != Qt::ToolButtonTextOnly) { + QSize icon = opt.iconSize; + w = icon.width(); + h = icon.height(); + } + + if (opt.toolButtonStyle != Qt::ToolButtonIconOnly) { + QSize textSize = fm.size(Qt::TextShowMnemonic, text()); + textSize.setWidth(textSize.width() + fm.width(QLatin1Char(' '))*2); + if (opt.toolButtonStyle == Qt::ToolButtonTextUnderIcon) { + h += 4 + textSize.height(); + if (textSize.width() > w) + w = textSize.width(); + } else if (opt.toolButtonStyle == Qt::ToolButtonTextBesideIcon) { + w += 4 + textSize.width(); + if (textSize.height() > h) + h = textSize.height(); + } else { // TextOnly + w = textSize.width(); + h = textSize.height(); + } + } + + opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height + if (d->popupMode == MenuButtonPopup) + w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); + + d->sizeHint = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this). + expandedTo(QApplication::globalStrut()); + return d->sizeHint; +} + +/*! + \reimp + */ +QSize QToolButton::minimumSizeHint() const +{ + return sizeHint(); +} + +/*! + \enum QToolButton::TextPosition + \compat + + This enum describes the position of the tool button's text label in + relation to the tool button's icon. + + \value BesideIcon The text appears beside the icon. + \value BelowIcon The text appears below the icon. + \omitvalue Right + \omitvalue Under +*/ + +/*! + \property QToolButton::toolButtonStyle + \brief whether the tool button displays an icon only, text only, + or text beside/below the icon. + + The default is Qt::ToolButtonIconOnly. + + QToolButton automatically connects this slot to the relevant + signal in the QMainWindow in which is resides. +*/ + +/*! + \property QToolButton::arrowType + \brief whether the button displays an arrow instead of a normal icon + + This displays an arrow as the icon for the QToolButton. + + By default, this property is set to Qt::NoArrow. +*/ + +Qt::ToolButtonStyle QToolButton::toolButtonStyle() const +{ + Q_D(const QToolButton); + return d->toolButtonStyle; +} + +Qt::ArrowType QToolButton::arrowType() const +{ + Q_D(const QToolButton); + return d->arrowType; +} + + +void QToolButton::setToolButtonStyle(Qt::ToolButtonStyle style) +{ + Q_D(QToolButton); + if (d->toolButtonStyle == style) + return; + + d->toolButtonStyle = style; + d->sizeHint = QSize(); + updateGeometry(); + if (isVisible()) { + update(); + } +} + +void QToolButton::setArrowType(Qt::ArrowType type) +{ + Q_D(QToolButton); + if (d->arrowType == type) + return; + + d->arrowType = type; + d->sizeHint = QSize(); + updateGeometry(); + if (isVisible()) { + update(); + } +} + +/*! + \fn void QToolButton::paintEvent(QPaintEvent *event) + + Paints the button in response to the paint \a event. +*/ +void QToolButton::paintEvent(QPaintEvent *) +{ + QStylePainter p(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + p.drawComplexControl(QStyle::CC_ToolButton, opt); +} + +/*! + \reimp + */ +void QToolButton::actionEvent(QActionEvent *event) +{ + Q_D(QToolButton); + QAction *action = event->action(); + switch (event->type()) { + case QEvent::ActionChanged: + if (action == d->defaultAction) + setDefaultAction(action); // update button state + break; + case QEvent::ActionAdded: + connect(action, SIGNAL(triggered()), this, SLOT(_q_actionTriggered())); + break; + case QEvent::ActionRemoved: + if (d->defaultAction == action) + d->defaultAction = 0; +#ifndef QT_NO_MENU + if (action == d->menuAction) + d->menuAction = 0; +#endif + action->disconnect(this); + break; + default: + ; + } + QAbstractButton::actionEvent(event); +} + +QStyle::SubControl QToolButtonPrivate::newHoverControl(const QPoint &pos) +{ + Q_Q(QToolButton); + QStyleOptionToolButton opt; + q->initStyleOption(&opt); + opt.subControls = QStyle::SC_All; + hoverControl = q->style()->hitTestComplexControl(QStyle::CC_ToolButton, &opt, pos, q); + if (hoverControl == QStyle::SC_None) + hoverRect = QRect(); + else + hoverRect = q->style()->subControlRect(QStyle::CC_ToolButton, &opt, hoverControl, q); + return hoverControl; +} + +bool QToolButtonPrivate::updateHoverControl(const QPoint &pos) +{ + Q_Q(QToolButton); + QRect lastHoverRect = hoverRect; + QStyle::SubControl lastHoverControl = hoverControl; + bool doesHover = q->testAttribute(Qt::WA_Hover); + if (lastHoverControl != newHoverControl(pos) && doesHover) { + q->update(lastHoverRect); + q->update(hoverRect); + return true; + } + return !doesHover; +} + +void QToolButtonPrivate::_q_actionTriggered() +{ + Q_Q(QToolButton); + if (QAction *action = qobject_cast<QAction *>(q->sender())) + emit q->triggered(action); +} + +/*! + \reimp + */ +void QToolButton::enterEvent(QEvent * e) +{ + Q_D(QToolButton); + if (d->autoRaise) + update(); + if (d->defaultAction) + d->defaultAction->hover(); + QAbstractButton::enterEvent(e); +} + + +/*! + \reimp + */ +void QToolButton::leaveEvent(QEvent * e) +{ + Q_D(QToolButton); + if (d->autoRaise) + update(); + + QAbstractButton::leaveEvent(e); +} + + +/*! + \reimp + */ +void QToolButton::timerEvent(QTimerEvent *e) +{ +#ifndef QT_NO_MENU + Q_D(QToolButton); + if (e->timerId() == d->popupTimer.timerId()) { + d->popupTimerDone(); + return; + } +#endif + QAbstractButton::timerEvent(e); +} + + +/*! + \reimp +*/ +void QToolButton::changeEvent(QEvent *e) +{ +#ifndef QT_NO_TOOLBAR + Q_D(QToolButton); + if (e->type() == QEvent::ParentChange) { + if (qobject_cast<QToolBar*>(parentWidget())) + d->autoRaise = true; + } else if (e->type() == QEvent::StyleChange +#ifdef Q_WS_MAC + || e->type() == QEvent::MacSizeChange +#endif + ) { +#ifdef QT3_SUPPORT + if (!d->userDefinedPopupDelay) +#endif + d->delay = style()->styleHint(QStyle::SH_ToolButton_PopupDelay, 0, this); + d->setLayoutItemMargins(QStyle::SE_ToolButtonLayoutItem); + } +#endif + QAbstractButton::changeEvent(e); +} + +/*! + \reimp +*/ +void QToolButton::mousePressEvent(QMouseEvent *e) +{ + Q_D(QToolButton); +#ifndef QT_NO_MENU + QStyleOptionToolButton opt; + initStyleOption(&opt); + if (e->button() == Qt::LeftButton && (d->popupMode == MenuButtonPopup)) { + QRect popupr = style()->subControlRect(QStyle::CC_ToolButton, &opt, + QStyle::SC_ToolButtonMenu, this); + if (popupr.isValid() && popupr.contains(e->pos())) { + d->buttonPressed = QToolButtonPrivate::MenuButtonPressed; + showMenu(); + return; + } + } +#endif + d->buttonPressed = QToolButtonPrivate::ToolButtonPressed; + QAbstractButton::mousePressEvent(e); +} + +/*! + \reimp +*/ +void QToolButton::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QToolButton); + QAbstractButton::mouseReleaseEvent(e); + d->buttonPressed = QToolButtonPrivate::NoButtonPressed; +} + +/*! + \reimp +*/ +bool QToolButton::hitButton(const QPoint &pos) const +{ + Q_D(const QToolButton); + if(QAbstractButton::hitButton(pos)) + return (d->buttonPressed != QToolButtonPrivate::MenuButtonPressed); + return false; +} + +#ifdef QT3_SUPPORT + +/*! + Use icon() instead. +*/ +QIcon QToolButton::onIconSet() const +{ + return icon(); +} + +/*! + Use icon() instead. +*/ +QIcon QToolButton::offIconSet() const +{ + return icon(); +} + + +/*! + \obsolete + + Use setIcon() instead. +*/ +void QToolButton::setOnIconSet(const QIcon& set) +{ + setIcon(set); +} + +/*! + \obsolete + + Use setIcon() instead. +*/ +void QToolButton::setOffIconSet(const QIcon& set) +{ + setIcon(set); +} + + +/*! \overload + \obsolete + + Since Qt 3.0, QIcon contains both the On and Off icons. + + For ease of porting, this function ignores the \a on parameter and + sets the \l{QAbstractButton::icon} {icon} property. If you relied on + the \a on parameter, you probably want to update your code to use + the QIcon On/Off mechanism. + + \sa icon QIcon::State +*/ + +void QToolButton::setIconSet(const QIcon & set, bool /* on */) +{ + QAbstractButton::setIcon(set); +} + +/*! \overload + \obsolete + + Since Qt 3.0, QIcon contains both the On and Off icons. + + For ease of porting, this function ignores the \a on parameter and + returns the \l{QAbstractButton::icon} {icon} property. If you relied + on the \a on parameter, you probably want to update your code to use + the QIcon On/Off mechanism. +*/ +QIcon QToolButton::iconSet(bool /* on */) const +{ + return QAbstractButton::icon(); +} + +#endif + +#ifndef QT_NO_MENU +/*! + Associates the given \a menu with this tool button. + + The menu will be shown according to the button's \l popupMode. + + Ownership of the menu is not transferred to the tool button. + + \sa menu() +*/ +void QToolButton::setMenu(QMenu* menu) +{ + Q_D(QToolButton); + + if (d->menuAction) + removeAction(d->menuAction); + + if (menu) { + d->menuAction = menu->menuAction(); + addAction(d->menuAction); + } else { + d->menuAction = 0; + } + update(); +} + +/*! + Returns the associated menu, or 0 if no menu has been defined. + + \sa setMenu() +*/ +QMenu* QToolButton::menu() const +{ + Q_D(const QToolButton); + if (d->menuAction) + return d->menuAction->menu(); + return 0; +} + +/*! + Shows (pops up) the associated popup menu. If there is no such + menu, this function does nothing. This function does not return + until the popup menu has been closed by the user. +*/ +void QToolButton::showMenu() +{ + Q_D(QToolButton); + if (!d->hasMenu()) { + d->menuButtonDown = false; + return; // no menu to show + } + + d->menuButtonDown = true; + repaint(); + d->popupTimer.stop(); + d->popupTimerDone(); +} + +void QToolButtonPrivate::_q_buttonPressed() +{ + Q_Q(QToolButton); + if (!hasMenu()) + return; // no menu to show + + if (delay > 0 && popupMode == QToolButton::DelayedPopup) + popupTimer.start(delay, q); + else if (delay == 0 || popupMode == QToolButton::InstantPopup) + q->showMenu(); +} + +void QToolButtonPrivate::popupTimerDone() +{ + Q_Q(QToolButton); + popupTimer.stop(); + if (!menuButtonDown && !down) + return; + + menuButtonDown = true; + QPointer<QMenu> actualMenu; + bool mustDeleteActualMenu = false; + if(menuAction) { + actualMenu = menuAction->menu(); + } else if (defaultAction && defaultAction->menu()) { + actualMenu = defaultAction->menu(); + } else { + actualMenu = new QMenu(q); + mustDeleteActualMenu = true; + QList<QAction*> actions = q->actions(); + for(int i = 0; i < actions.size(); i++) + actualMenu->addAction(actions.at(i)); + } + repeat = q->autoRepeat(); + q->setAutoRepeat(false); + bool horizontal = true; +#if !defined(QT_NO_TOOLBAR) + QToolBar *tb = qobject_cast<QToolBar*>(q->parentWidget()); + if (tb && tb->orientation() == Qt::Vertical) + horizontal = false; +#endif + QPoint p; + QRect screen = qApp->desktop()->availableGeometry(q); + QSize sh = ((QToolButton*)(QMenu*)actualMenu)->receivers(SIGNAL(aboutToShow()))? QSize() : actualMenu->sizeHint(); + QRect rect = q->rect(); + if (horizontal) { + if (q->isRightToLeft()) { + if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.height()) { + p = q->mapToGlobal(rect.bottomRight()); + } else { + p = q->mapToGlobal(rect.topRight() - QPoint(0, sh.height())); + } + p.rx() -= sh.width(); + } else { + if (q->mapToGlobal(QPoint(0, rect.bottom())).y() + sh.height() <= screen.height()) { + p = q->mapToGlobal(rect.bottomLeft()); + } else { + p = q->mapToGlobal(rect.topLeft() - QPoint(0, sh.height())); + } + } + } else { + if (q->isRightToLeft()) { + if (q->mapToGlobal(QPoint(rect.left(), 0)).x() - sh.width() <= screen.x()) { + p = q->mapToGlobal(rect.topRight()); + } else { + p = q->mapToGlobal(rect.topLeft()); + p.rx() -= sh.width(); + } + } else { + if (q->mapToGlobal(QPoint(rect.right(), 0)).x() + sh.width() <= screen.right()) { + p = q->mapToGlobal(rect.topRight()); + } else { + p = q->mapToGlobal(rect.topLeft() - QPoint(sh.width(), 0)); + } + } + } + p.rx() = qMax(screen.left(), qMin(p.x(), screen.right() - sh.width())); + p.ry() += 1; + QPointer<QToolButton> that = q; + actualMenu->setNoReplayFor(q); + if (!mustDeleteActualMenu) //only if action are not in this widget + QObject::connect(actualMenu, SIGNAL(triggered(QAction*)), q, SLOT(_q_menuTriggered(QAction*))); + QObject::connect(actualMenu, SIGNAL(aboutToHide()), q, SLOT(_q_updateButtonDown())); + actualMenu->d_func()->causedPopup.widget = q; + actualMenu->d_func()->causedPopup.action = defaultAction; + actionsCopy = q->actions(); //(the list of action may be modified in slots) + actualMenu->exec(p); + QObject::disconnect(actualMenu, SIGNAL(aboutToHide()), q, SLOT(_q_updateButtonDown())); + if (mustDeleteActualMenu) + delete actualMenu; + else + QObject::disconnect(actualMenu, SIGNAL(triggered(QAction*)), q, SLOT(_q_menuTriggered(QAction*))); + + if (!that) + return; + + actionsCopy.clear(); + + if (repeat) + q->setAutoRepeat(true); +} + +void QToolButtonPrivate::_q_updateButtonDown() +{ + Q_Q(QToolButton); + menuButtonDown = false; + if (q->isDown()) + q->setDown(false); + else + q->repaint(); +} + +void QToolButtonPrivate::_q_menuTriggered(QAction *action) +{ + Q_Q(QToolButton); + if (action && !actionsCopy.contains(action)) + emit q->triggered(action); +} +#endif // QT_NO_MENU + +#ifdef QT3_SUPPORT +/*! + \fn void QToolButton::setPopupDelay(int delay) + + Use the style hint QStyle::SH_ToolButton_PopupDelay instead. +*/ +void QToolButton::setPopupDelay(int delay) +{ + Q_D(QToolButton); + d->userDefinedPopupDelay = true; + d->delay = delay; + + update(); +} + +/*! + Use the style hint QStyle::SH_ToolButton_PopupDelay instead. +*/ +int QToolButton::popupDelay() const +{ + Q_D(const QToolButton); + return d->delay; +} +#endif + +#ifndef QT_NO_MENU +/*! \enum QToolButton::ToolButtonPopupMode + + Describes how a menu should be popped up for tool buttons that has + a menu set or contains a list of actions. + + \value DelayedPopup After pressing and holding the tool button + down for a certain amount of time (the timeout is style dependant, + see QStyle::SH_ToolButton_PopupDelay), the menu is displayed. A + typical application example is the "back" button in some web + browsers's tool bars. If the user clicks it, the browser simply + browses back to the previous page. If the user presses and holds + the button down for a while, the tool button shows a menu + containing the current history list + + \value MenuButtonPopup In this mode the tool button displays a + special arrow to indicate that a menu is present. The menu is + displayed when the arrow part of the button is pressed. + + \value InstantPopup The menu is displayed, without delay, when + the tool button is pressed. In this mode, the button's own action + is not triggered. +*/ + +/*! + \property QToolButton::popupMode + \brief describes the way that popup menus are used with tool buttons + + By default, this property is set to \l DelayedPopup. +*/ + +void QToolButton::setPopupMode(ToolButtonPopupMode mode) +{ + Q_D(QToolButton); + d->popupMode = mode; +} + +QToolButton::ToolButtonPopupMode QToolButton::popupMode() const +{ + Q_D(const QToolButton); + return d->popupMode; +} +#endif + +/*! + \property QToolButton::autoRaise + \brief whether auto-raising is enabled or not. + + The default is disabled (i.e. false). + + This property is currently ignored on Mac OS X when using QMacStyle. +*/ +void QToolButton::setAutoRaise(bool enable) +{ + Q_D(QToolButton); + d->autoRaise = enable; + + update(); +} + +bool QToolButton::autoRaise() const +{ + Q_D(const QToolButton); + return d->autoRaise; +} + +/*! + Sets the default action to \a action. + + If a tool button has a default action, the action defines the + button's properties like text, icon, tool tip, etc. + */ +void QToolButton::setDefaultAction(QAction *action) +{ + Q_D(QToolButton); +#ifndef QT_NO_MENU + bool hadMenu = false; + hadMenu = d->hasMenu(); +#endif + d->defaultAction = action; + if (!action) + return; + if (!actions().contains(action)) + addAction(action); + setText(action->iconText()); + setIcon(action->icon()); +#ifndef QT_NO_TOOLTIP + setToolTip(action->toolTip()); +#endif +#ifndef QT_NO_STATUSTIP + setStatusTip(action->statusTip()); +#endif +#ifndef QT_NO_WHATSTHIS + setWhatsThis(action->whatsThis()); +#endif +#ifndef QT_NO_MENU + if (action->menu() && !hadMenu) { + // new 'default' popup mode defined introduced by tool bar. We + // should have changed QToolButton's default instead. Do that + // in 4.2. + setPopupMode(QToolButton::MenuButtonPopup); + } +#endif + setCheckable(action->isCheckable()); + setChecked(action->isChecked()); + setEnabled(action->isEnabled()); + if (action->d_func()->fontSet) + setFont(action->font()); +} + + +/*! + Returns the default action. + + \sa setDefaultAction() + */ +QAction *QToolButton::defaultAction() const +{ + Q_D(const QToolButton); + return d->defaultAction; +} + + + +/*! + \reimp + */ +void QToolButton::nextCheckState() +{ + Q_D(QToolButton); + if (!d->defaultAction) + QAbstractButton::nextCheckState(); + else + d->defaultAction->trigger(); +} + +/*! \reimp */ +bool QToolButton::event(QEvent *event) +{ + switch(event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) + d_func()->updateHoverControl(he->pos()); + break; + default: + break; + } + return QAbstractButton::event(event); +} + +/*! \internal + */ +QToolButton::QToolButton(QToolButtonPrivate &dd, QWidget *parent) + :QAbstractButton(dd, parent) +{ + Q_D(QToolButton); + d->init(); +} + +/*! + \fn void QToolButton::setPixmap(const QPixmap &pixmap) + + Use setIcon(QIcon(pixmap)) instead. +*/ + +/*! + \fn void QToolButton::setIconSet(const QIcon &icon) + + Use setIcon() instead. +*/ + +/*! + \fn void QToolButton::setTextLabel(const QString &text, bool tooltip) + + Use setText() and setToolTip() instead. +*/ + +/*! + \fn QString QToolButton::textLabel() const + + Use text() instead. +*/ + +/*! + \fn QIcon QToolButton::iconSet() const + + Use icon() instead. +*/ + +/*! + \fn void QToolButton::openPopup() + + Use showMenu() instead. +*/ + +/*! + \fn void QToolButton::setPopup(QMenu* popup) + + Use setMenu() instead. +*/ + +/*! + \fn QMenu* QToolButton::popup() const + + Use menu() instead. +*/ + +/*! + \fn TextPosition QToolButton::textPosition() const + + Use toolButtonStyle() instead. +*/ + +/*! + \fn void QToolButton::setTextPosition(QToolButton::TextPosition pos) + + Use setToolButtonStyle() instead. +*/ + +/*! + \fn bool QToolButton::usesBigPixmap() const + + Use iconSize() instead. +*/ + +/*! + \fn void QToolButton::setUsesBigPixmap(bool enable) + + Use setIconSize() instead. +*/ + +/*! + \fn bool QToolButton::usesTextLabel() const + + Use toolButtonStyle() instead. +*/ + +/*! + \fn void QToolButton::setUsesTextLabel(bool enable) + + Use setToolButtonStyle() instead. +*/ + +QT_END_NAMESPACE + +#include "moc_qtoolbutton.cpp" + +#endif diff --git a/src/gui/widgets/qtoolbutton.h b/src/gui/widgets/qtoolbutton.h new file mode 100644 index 0000000..d869081 --- /dev/null +++ b/src/gui/widgets/qtoolbutton.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTOOLBUTTON_H +#define QTOOLBUTTON_H + +#include <QtGui/qabstractbutton.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TOOLBUTTON + +class QToolButtonPrivate; +class QMenu; +class QStyleOptionToolButton; + +class Q_GUI_EXPORT QToolButton : public QAbstractButton +{ + Q_OBJECT + Q_ENUMS(Qt::ToolButtonStyle Qt::ArrowType ToolButtonPopupMode) +#ifndef QT_NO_MENU + Q_PROPERTY(ToolButtonPopupMode popupMode READ popupMode WRITE setPopupMode) +#endif + Q_PROPERTY(Qt::ToolButtonStyle toolButtonStyle READ toolButtonStyle WRITE setToolButtonStyle) + Q_PROPERTY(bool autoRaise READ autoRaise WRITE setAutoRaise) + Q_PROPERTY(Qt::ArrowType arrowType READ arrowType WRITE setArrowType) + +public: + enum ToolButtonPopupMode { + DelayedPopup, + MenuButtonPopup, + InstantPopup + }; + + explicit QToolButton(QWidget * parent=0); + ~QToolButton(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + Qt::ToolButtonStyle toolButtonStyle() const; + + Qt::ArrowType arrowType() const; + void setArrowType(Qt::ArrowType type); + +#ifndef QT_NO_MENU + void setMenu(QMenu* menu); + QMenu* menu() const; + + void setPopupMode(ToolButtonPopupMode mode); + ToolButtonPopupMode popupMode() const; +#endif + + QAction *defaultAction() const; + + void setAutoRaise(bool enable); + bool autoRaise() const; + +public Q_SLOTS: +#ifndef QT_NO_MENU + void showMenu(); +#endif + void setToolButtonStyle(Qt::ToolButtonStyle style); + void setDefaultAction(QAction *); + +Q_SIGNALS: + void triggered(QAction *); + +protected: + QToolButton(QToolButtonPrivate &, QWidget* parent); + bool event(QEvent *e); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void paintEvent(QPaintEvent *); + void actionEvent(QActionEvent *); + + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + void timerEvent(QTimerEvent *); + void changeEvent(QEvent *); + + bool hitButton(const QPoint &pos) const; + void nextCheckState(); + void initStyleOption(QStyleOptionToolButton *option) const; + +private: + Q_DISABLE_COPY(QToolButton) + Q_DECLARE_PRIVATE(QToolButton) +#ifndef QT_NO_MENU + Q_PRIVATE_SLOT(d_func(), void _q_buttonPressed()) + Q_PRIVATE_SLOT(d_func(), void _q_updateButtonDown()) + Q_PRIVATE_SLOT(d_func(), void _q_menuTriggered(QAction*)) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_actionTriggered()) + +#ifdef QT3_SUPPORT +public: + enum TextPosition { + BesideIcon, + BelowIcon + , Right = BesideIcon, + Under = BelowIcon + }; + + QT3_SUPPORT_CONSTRUCTOR QToolButton(QWidget * parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QToolButton(Qt::ArrowType type, QWidget *parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QToolButton( const QIcon& s, const QString &textLabel, + const QString& grouptext, + QObject * receiver, const char* slot, + QWidget * parent, const char* name=0 ); + inline QT3_SUPPORT void setPixmap(const QPixmap &pixmap) { setIcon(static_cast<QIcon>(pixmap)); } + QT3_SUPPORT void setOnIconSet(const QIcon&); + QT3_SUPPORT void setOffIconSet(const QIcon&); + inline QT3_SUPPORT void setIconSet(const QIcon &icon){setIcon(icon);} + QT3_SUPPORT void setIconSet(const QIcon &, bool on); + inline QT3_SUPPORT void setTextLabel(const QString &text, bool tooltip = true) { + setText(text); +#ifndef QT_NO_TOOLTIP + if (tooltip) + setToolTip(text); +#else + Q_UNUSED(tooltip); +#endif + } + inline QT3_SUPPORT QString textLabel() const { return text(); } + QT3_SUPPORT QIcon onIconSet() const; + QT3_SUPPORT QIcon offIconSet() const; + QT3_SUPPORT QIcon iconSet(bool on) const; + inline QT3_SUPPORT QIcon iconSet() const { return icon(); } + inline QT3_SUPPORT void openPopup() { showMenu(); } + inline QT3_SUPPORT void setPopup(QMenu* popup) {setMenu(popup); } + inline QT3_SUPPORT QMenu* popup() const { return menu(); } + inline QT3_SUPPORT bool usesBigPixmap() const { return iconSize().height() > 22; } + inline QT3_SUPPORT bool usesTextLabel() const { return toolButtonStyle() != Qt::ToolButtonIconOnly; } + inline QT3_SUPPORT TextPosition textPosition() const + { return toolButtonStyle() == Qt::ToolButtonTextUnderIcon ? BelowIcon : BesideIcon; } + QT3_SUPPORT void setPopupDelay(int delay); + QT3_SUPPORT int popupDelay() const; + +public Q_SLOTS: + QT_MOC_COMPAT void setUsesBigPixmap(bool enable) + { setIconSize(enable?QSize(32,32):QSize(22,22)); } + QT_MOC_COMPAT void setUsesTextLabel(bool enable) + { setToolButtonStyle(enable?Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly); } + QT_MOC_COMPAT void setTextPosition(QToolButton::TextPosition pos) + { setToolButtonStyle(pos == BesideIcon ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonTextUnderIcon); } + +#endif +}; + +#endif // QT_NO_TOOLBUTTON + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTOOLBUTTON_H diff --git a/src/gui/widgets/qvalidator.cpp b/src/gui/widgets/qvalidator.cpp new file mode 100644 index 0000000..3aca13d --- /dev/null +++ b/src/gui/widgets/qvalidator.cpp @@ -0,0 +1,909 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdebug.h> + +#include "qvalidator.h" +#ifndef QT_NO_VALIDATOR +#include "private/qobject_p.h" +#include "private/qlocale_p.h" + +#include <limits.h> +#include <math.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QValidator + \brief The QValidator class provides validation of input text. + + \ingroup misc + \mainclass + + The class itself is abstract. Two subclasses, \l QIntValidator and + \l QDoubleValidator, provide basic numeric-range checking, and \l + QRegExpValidator provides general checking using a custom regular + expression. + + If the built-in validators aren't sufficient, you can subclass + QValidator. The class has two virtual functions: validate() and + fixup(). + + \l validate() must be implemented by every subclass. It returns + \l Invalid, \l Intermediate or \l Acceptable depending on whether + its argument is valid (for the subclass's definition of valid). + + These three states require some explanation. An \l Invalid string + is \e clearly invalid. \l Intermediate is less obvious: the + concept of validity is difficult to apply when the string is + incomplete (still being edited). QValidator defines \l Intermediate + as the property of a string that is neither clearly invalid nor + acceptable as a final result. \l Acceptable means that the string + is acceptable as a final result. One might say that any string + that is a plausible intermediate state during entry of an \l + Acceptable string is \l Intermediate. + + Here are some examples: + + \list + + \i For a line edit that accepts integers from 10 to 1000 inclusive, + 42 and 123 are \l Acceptable, the empty string and 5 are \l + Intermediate, and "asdf" and 1114 is \l Invalid. + + \i For an editable combobox that accepts URLs, any well-formed URL + is \l Acceptable, "http://qtsoftware.com/," is \l Intermediate + (it might be a cut and paste action that accidentally took in a + comma at the end), the empty string is \l Intermediate (the user + might select and delete all of the text in preparation for entering + a new URL) and "http:///./" is \l Invalid. + + \i For a spin box that accepts lengths, "11cm" and "1in" are \l + Acceptable, "11" and the empty string are \l Intermediate, and + "http://qtsoftware.com" and "hour" are \l Invalid. + + \endlist + + \l fixup() is provided for validators that can repair some user + errors. The default implementation does nothing. QLineEdit, for + example, will call fixup() if the user presses Enter (or Return) + and the content is not currently valid. This allows the fixup() + function the opportunity of performing some magic to make an \l + Invalid string \l Acceptable. + + A validator has a locale, set with setLocale(). It is typically used + to parse localized data. For example, QIntValidator and QDoubleValidator + use it to parse localized representations of integers and doubles. + + QValidator is typically used with QLineEdit, QSpinBox and + QComboBox. + + \sa QIntValidator, QDoubleValidator, QRegExpValidator, {Line Edits Example} +*/ + + +/*! + \enum QValidator::State + + This enum type defines the states in which a validated string can + exist. + + \value Invalid The string is \e clearly invalid. + \value Intermediate The string is a plausible intermediate value. + \value Acceptable The string is acceptable as a final result; + i.e. it is valid. + + \omitvalue Valid +*/ + +class QValidatorPrivate : public QObjectPrivate{ + Q_DECLARE_PUBLIC(QValidator) +public: + QValidatorPrivate() : QObjectPrivate() + { + } + + QLocale locale; +}; + + +/*! + Sets up the validator. The \a parent parameter is + passed on to the QObject constructor. +*/ + +QValidator::QValidator(QObject * parent) + : QObject(*new QValidatorPrivate, parent) +{ +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + Sets up the validator. The \a parent and \a name parameters are + passed on to the QObject constructor. +*/ + +QValidator::QValidator(QObject * parent, const char *name) + : QObject(*new QValidatorPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); +} +#endif + +/*! + Destroys the validator, freeing any storage and other resources + used. +*/ + +QValidator::~QValidator() +{ +} + +/*! + Returns the locale for the validator. The locale is by default initialized to the same as QLocale(). + + \sa setLocale() + \sa QLocale::QLocale() +*/ +QLocale QValidator::locale() const +{ + Q_D(const QValidator); + return d->locale; +} + +/*! + Sets the \a locale that will be used for the validator. Unless + setLocale has been called, the validator will use the default + locale set with QLocale::setDefault(). If a default locale has not + been set, it is the operating system's locale. + + \sa locale() QLocale::setDefault() +*/ +void QValidator::setLocale(const QLocale &locale) +{ + Q_D(QValidator); + d->locale = locale; +} + +/*! + \fn QValidator::State QValidator::validate(QString &input, int &pos) const + + This virtual function returns \l Invalid if \a input is invalid + according to this validator's rules, \l Intermediate if it + is likely that a little more editing will make the input + acceptable (e.g. the user types "4" into a widget which accepts + integers between 10 and 99), and \l Acceptable if the input is + valid. + + The function can change both \a input and \a pos (the cursor position) + if required. +*/ + + +/*! + \fn void QValidator::fixup(QString & input) const + + This function attempts to change \a input to be valid according to + this validator's rules. It need not result in a valid string: + callers of this function must re-test afterwards; the default does + nothing. + + Reimplementations of this function can change \a input even if + they do not produce a valid string. For example, an ISBN validator + might want to delete every character except digits and "-", even + if the result is still not a valid ISBN; a surname validator might + want to remove whitespace from the start and end of the string, + even if the resulting string is not in the list of accepted + surnames. +*/ + +void QValidator::fixup(QString &) const +{ +} + + +/*! + \class QIntValidator + \brief The QIntValidator class provides a validator that ensures + a string contains a valid integer within a specified range. + + \ingroup misc + + Example of use: + + \snippet doc/src/snippets/code/src_gui_widgets_qvalidator.cpp 0 + + Below we present some examples of validators. In practice they would + normally be associated with a widget as in the example above. + + \snippet doc/src/snippets/code/src_gui_widgets_qvalidator.cpp 1 + + Notice that the value \c 999 returns Intermediate. Values + consisting of a number of digits equal to or less than the max + value are considered intermediate. This is intended because the + digit that prevents a number to be in range is not necessarily the + last digit typed. This also means that an intermediate number can + have leading zeros. + + The minimum and maximum values are set in one call with setRange(), + or individually with setBottom() and setTop(). + + QIntValidator uses its locale() to interpret the number. For example, + in Arabic locales, QIntValidator will accept Arabic digits. In addition, + QIntValidator is always guaranteed to accept a number formatted according + to the "C" locale. + + \sa QDoubleValidator, QRegExpValidator, {Line Edits Example} +*/ + +/*! + Constructs a validator with a \a parent object that + accepts all integers. +*/ + +QIntValidator::QIntValidator(QObject * parent) + : QValidator(parent) +{ + b = INT_MIN; + t = INT_MAX; +} + + +/*! + Constructs a validator with a \a parent, that accepts integers + from \a minimum to \a maximum inclusive. +*/ + +QIntValidator::QIntValidator(int minimum, int maximum, + QObject * parent) + : QValidator(parent) +{ + b = minimum; + t = maximum; +} + + +#ifdef QT3_SUPPORT +/*! + \obsolete + + Constructs a validator with a \a parent object and a \a name that + accepts all integers. +*/ + +QIntValidator::QIntValidator(QObject * parent, const char *name) + : QValidator(parent) +{ + setObjectName(QString::fromAscii(name)); + b = INT_MIN; + t = INT_MAX; +} + + +/*! + \obsolete + + Constructs a validator called \a name with a \a parent, that + accepts integers from \a minimum to \a maximum inclusive. +*/ + +QIntValidator::QIntValidator(int minimum, int maximum, + QObject * parent, const char* name) + : QValidator(parent) +{ + setObjectName(QString::fromAscii(name)); + b = minimum; + t = maximum; +} +#endif + +/*! + Destroys the validator. +*/ + +QIntValidator::~QIntValidator() +{ + // nothing +} + + +/*! + \fn QValidator::State QIntValidator::validate(QString &input, int &pos) const + + Returns \l Acceptable if the \a input is an integer within the + valid range, \l Intermediate if the \a input is a prefix of an integer in the + valid range, and \l Invalid otherwise. + + If the valid range consists of just positive integers (e.g., 32 to 100) + and \a input is a negative integer, then Invalid is returned. (On the other + hand, if the range consists of negative integers (e.g., -100 to -32) and + \a input is a positive integer, then Intermediate is returned, because + the user might be just about to type the minus (especially for right-to-left + languages). + + \snippet doc/src/snippets/code/src_gui_widgets_qvalidator.cpp 2 + + By default, the \a pos parameter is not used by this validator. +*/ + +static int numDigits(qlonglong n) +{ + if (n == 0) + return 1; + return (int)log10(double(n)) + 1; +}; + +static qlonglong pow10(int exp) +{ + qlonglong result = 1; + for (int i = 0; i < exp; ++i) + result *= 10; + return result; +} + +QValidator::State QIntValidator::validate(QString & input, int&) const +{ + QByteArray buff; + if (!locale().d()->validateChars(input, QLocalePrivate::IntegerMode, &buff)) { + QLocale cl(QLocale::C); + if (!cl.d()->validateChars(input, QLocalePrivate::IntegerMode, &buff)) + return Invalid; + } + + if (buff.isEmpty()) + return Intermediate; + + if (b >= 0 && buff.startsWith('-')) + return Invalid; + + if (t < 0 && buff.startsWith('+')) + return Invalid; + + if (buff.size() == 1 && (buff.at(0) == '+' || buff.at(0) == '-')) + return Intermediate; + + bool ok, overflow; + qlonglong entered = QLocalePrivate::bytearrayToLongLong(buff.constData(), 10, &ok, &overflow); + if (overflow || !ok) + return Invalid; + if (entered >= b && entered <= t) + return Acceptable; + + if (entered >= 0) { + // the -entered < b condition is necessary to allow people to type + // the minus last (e.g. for right-to-left languages) + return (entered > t && -entered < b) ? Invalid : Intermediate; + } else { + return (entered < b) ? Invalid : Intermediate; + } +} + + +/*! + Sets the range of the validator to only accept integers between \a + bottom and \a top inclusive. +*/ + +void QIntValidator::setRange(int bottom, int top) +{ + b = bottom; + t = top; +} + + +/*! + \property QIntValidator::bottom + \brief the validator's lowest acceptable value + + By default, this property's value is derived from the lowest signed + integer available (typically -2147483647). + + \sa setRange() +*/ +void QIntValidator::setBottom(int bottom) +{ + setRange(bottom, top()); +} + +/*! + \property QIntValidator::top + \brief the validator's highest acceptable value + + By default, this property's value is derived from the highest signed + integer available (typically 2147483647). + + \sa setRange() +*/ +void QIntValidator::setTop(int top) +{ + setRange(bottom(), top); +} + + +#ifndef QT_NO_REGEXP + +/*! + \internal +*/ +QValidator::QValidator(QObjectPrivate &d, QObject *parent) + : QObject(d, parent) +{ +} + +/*! + \internal +*/ +QValidator::QValidator(QValidatorPrivate &d, QObject *parent) + : QObject(d, parent) +{ +} + +class QDoubleValidatorPrivate : public QValidatorPrivate +{ + Q_DECLARE_PUBLIC(QDoubleValidator) +public: + QDoubleValidatorPrivate() + : QValidatorPrivate() + , notation(QDoubleValidator::ScientificNotation) + { + } + + QDoubleValidator::Notation notation; +}; + + +/*! + \class QDoubleValidator + + \brief The QDoubleValidator class provides range checking of + floating-point numbers. + + \ingroup misc + + QDoubleValidator provides an upper bound, a lower bound, and a + limit on the number of digits after the decimal point. It does not + provide a fixup() function. + + You can set the acceptable range in one call with setRange(), or + with setBottom() and setTop(). Set the number of decimal places + with setDecimals(). The validate() function returns the validation + state. + + QDoubleValidator uses its locale() to interpret the number. For example, + in the German locale, "1,234" will be accepted as the fractional number + 1.234. In Arabic locales, QDoubleValidator will accept Arabic digits. + + In addition, QDoubleValidator is always guaranteed to accept a number + formatted according to the "C" locale. QDoubleValidator will not accept + numbers with thousand-seperators. + + \sa QIntValidator, QRegExpValidator, {Line Edits Example} +*/ + + /*! + \enum QDoubleValidator::Notation + \since 4.3 + This enum defines the allowed notations for entering a double. + + \value StandardNotation The string is written as a standard number + (i.e. 0.015). + \value ScientificNotation The string is written in scientific + form. It may have an exponent part(i.e. 1.5E-2). +*/ + +/*! + Constructs a validator object with a \a parent object + that accepts any double. +*/ + +QDoubleValidator::QDoubleValidator(QObject * parent) + : QValidator(*new QDoubleValidatorPrivate , parent) +{ + b = -HUGE_VAL; + t = HUGE_VAL; + dec = 1000; +} + + +/*! + Constructs a validator object with a \a parent object. This + validator will accept doubles from \a bottom to \a top inclusive, + with up to \a decimals digits after the decimal point. +*/ + +QDoubleValidator::QDoubleValidator(double bottom, double top, int decimals, + QObject * parent) + : QValidator(*new QDoubleValidatorPrivate , parent) +{ + b = bottom; + t = top; + dec = decimals; +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + + Constructs a validator object with a \a parent object and a \a name + that accepts any double. +*/ + +QDoubleValidator::QDoubleValidator(QObject * parent, const char *name) + : QValidator(*new QDoubleValidatorPrivate , parent) +{ + setObjectName(QString::fromAscii(name)); + b = -HUGE_VAL; + t = HUGE_VAL; + dec = 1000; +} + + +/*! + \obsolete + + Constructs a validator object with a \a parent object, called \a + name. This validator will accept doubles from \a bottom to \a top + inclusive, with up to \a decimals digits after the decimal point. +*/ + +QDoubleValidator::QDoubleValidator(double bottom, double top, int decimals, + QObject * parent, const char* name) + : QValidator(*new QDoubleValidatorPrivate, parent) +{ + setObjectName(QString::fromAscii(name)); + b = bottom; + t = top; + dec = decimals; +} +#endif + +/*! + Destroys the validator. +*/ + +QDoubleValidator::~QDoubleValidator() +{ +} + + +/*! + \fn QValidator::State QDoubleValidator::validate(QString &input, int &pos) const + + Returns \l Acceptable if the string \a input contains a double + that is within the valid range and is in the correct format. + + Returns \l Intermediate if \a input contains a double that is + outside the range or is in the wrong format; e.g. with too many + digits after the decimal point or is empty. + + Returns \l Invalid if the \a input is not a double. + + Note: If the valid range consists of just positive doubles (e.g. 0.0 to 100.0) + and \a input is a negative double then \l Invalid is returned. If notation() + is set to StandardNotation, and the input contains more digits before the + decimal point than a double in the valid range may have, \l Invalid is returned. + If notation() is ScientificNotation, and the input is not in the valid range, + \l Intermediate is returned. The value may yet become valid by changing the exponent. + + By default, the \a pos parameter is not used by this validator. +*/ + +#ifndef LLONG_MAX +# define LLONG_MAX Q_INT64_C(0x7fffffffffffffff) +#endif + +QValidator::State QDoubleValidator::validate(QString & input, int &) const +{ + Q_D(const QDoubleValidator); + + QLocalePrivate::NumberMode numMode = QLocalePrivate::DoubleStandardMode; + switch (d->notation) { + case StandardNotation: + numMode = QLocalePrivate::DoubleStandardMode; + break; + case ScientificNotation: + numMode = QLocalePrivate::DoubleScientificMode; + break; + }; + + QByteArray buff; + if (!locale().d()->validateChars(input, numMode, &buff, dec)) { + QLocale cl(QLocale::C); + if (!cl.d()->validateChars(input, numMode, &buff, dec)) + return Invalid; + } + + if (buff.isEmpty()) + return Intermediate; + + if (b >= 0 && buff.startsWith('-')) + return Invalid; + + if (t < 0 && buff.startsWith('+')) + return Invalid; + + bool ok, overflow; + double i = QLocalePrivate::bytearrayToDouble(buff.constData(), &ok, &overflow); + if (overflow) + return Invalid; + if (!ok) + return Intermediate; + + if (i >= b && i <= t) + return Acceptable; + + if (d->notation == StandardNotation) { + double max = qMax(qAbs(b), qAbs(t)); + if (max < LLONG_MAX) { + qlonglong n = pow10(numDigits(qlonglong(max))) - 1; + if (qAbs(i) > n) + return Invalid; + } + } + + return Intermediate; +} + + +/*! + Sets the validator to accept doubles from \a minimum to \a maximum + inclusive, with at most \a decimals digits after the decimal + point. +*/ + +void QDoubleValidator::setRange(double minimum, double maximum, int decimals) +{ + b = minimum; + t = maximum; + dec = decimals; +} + +/*! + \property QDoubleValidator::bottom + \brief the validator's minimum acceptable value + + By default, this property contains a value of -infinity. + + \sa setRange() +*/ + +void QDoubleValidator::setBottom(double bottom) +{ + setRange(bottom, top(), decimals()); +} + + +/*! + \property QDoubleValidator::top + \brief the validator's maximum acceptable value + + By default, this property contains a value of infinity. + + \sa setRange() +*/ + +void QDoubleValidator::setTop(double top) +{ + setRange(bottom(), top, decimals()); +} + +/*! + \property QDoubleValidator::decimals + \brief the validator's maximum number of digits after the decimal point + + By default, this property contains a value of 1000. + + \sa setRange() +*/ + +void QDoubleValidator::setDecimals(int decimals) +{ + setRange(bottom(), top(), decimals); +} + +/*! + \property QDoubleValidator::notation + \since 4.3 + \brief the notation of how a string can describe a number + + By default, this property is set to ScientificNotation. + + \sa Notation +*/ + +void QDoubleValidator::setNotation(Notation newNotation) +{ + Q_D(QDoubleValidator); + d->notation = newNotation; +} + +QDoubleValidator::Notation QDoubleValidator::notation() const +{ + Q_D(const QDoubleValidator); + return d->notation; +} + +/*! + \class QRegExpValidator + \brief The QRegExpValidator class is used to check a string + against a regular expression. + + \ingroup misc + + QRegExpValidator uses a regular expression (regexp) to + determine whether an input string is \l Acceptable, \l + Intermediate, or \l Invalid. The regexp can either be supplied + when the QRegExpValidator is constructed, or at a later time. + + When QRegExpValidator determines whether a string is \l Acceptable + or not, the regexp is treated as if it begins with the start of string + assertion (\bold{^}) and ends with the end of string assertion + (\bold{$}); the match is against the entire input string, or from + the given position if a start position greater than zero is given. + + If a string is a prefix of an \l Acceptable string, it is considered + \l Intermediate. For example, "" and "A" are \l Intermediate for the + regexp \bold{[A-Z][0-9]} (whereas "_" would be \l Invalid). + + For a brief introduction to Qt's regexp engine, see \l QRegExp. + + Example of use: + \snippet doc/src/snippets/code/src_gui_widgets_qvalidator.cpp 3 + + Below we present some examples of validators. In practice they would + normally be associated with a widget as in the example above. + + \snippet doc/src/snippets/code/src_gui_widgets_qvalidator.cpp 4 + + \sa QRegExp, QIntValidator, QDoubleValidator, {Settings Editor Example} +*/ + +/*! + Constructs a validator with a \a parent object that accepts + any string (including an empty one) as valid. +*/ + +QRegExpValidator::QRegExpValidator(QObject *parent) + : QValidator(parent), r(QString::fromLatin1(".*")) +{ +} + +/*! + Constructs a validator with a \a parent object that + accepts all strings that match the regular expression \a rx. + + The match is made against the entire string; e.g. if the regexp is + \bold{[A-Fa-f0-9]+} it will be treated as \bold{^[A-Fa-f0-9]+$}. +*/ + +QRegExpValidator::QRegExpValidator(const QRegExp& rx, QObject *parent) + : QValidator(parent), r(rx) +{ +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + + Constructs a validator with a \a parent object and \a name that accepts + any string (including an empty one) as valid. +*/ + +QRegExpValidator::QRegExpValidator(QObject *parent, const char *name) + : QValidator(parent), r(QString::fromLatin1(".*")) +{ + setObjectName(QString::fromAscii(name)); +} + +/*! + \obsolete + + Constructs a validator with a \a parent object and a \a name that + accepts all strings that match the regular expression \a rx. + + The match is made against the entire string; e.g. if the regexp is + \bold{[A-Fa-f0-9]+} it will be treated as \bold{^[A-Fa-f0-9]+$}. +*/ + +QRegExpValidator::QRegExpValidator(const QRegExp& rx, QObject *parent, + const char *name) + : QValidator(parent), r(rx) +{ + setObjectName(QString::fromAscii(name)); +} +#endif + +/*! + Destroys the validator. +*/ + +QRegExpValidator::~QRegExpValidator() +{ +} + +/*! + Returns \l Acceptable if \a input is matched by the regular + expression for this validator, \l Intermediate if it has matched + partially (i.e. could be a valid match if additional valid + characters are added), and \l Invalid if \a input is not matched. + + The \a pos parameter is set to the length of the \a input parameter. + + For example, if the regular expression is \bold{\\w\\d\\d} + (word-character, digit, digit) then "A57" is \l Acceptable, + "E5" is \l Intermediate, and "+9" is \l Invalid. + + \sa QRegExp::exactMatch() +*/ + +QValidator::State QRegExpValidator::validate(QString &input, int& pos) const +{ + if (r.exactMatch(input)) { + return Acceptable; + } else { + if (const_cast<QRegExp &>(r).matchedLength() == input.size()) { + return Intermediate; + } else { + pos = input.size(); + return Invalid; + } + } +} + +/*! + \property QRegExpValidator::regExp + \brief the regular expression used for validation + + By default, this property contains a regular expression with the pattern \c{.*} + that matches any string. +*/ + +void QRegExpValidator::setRegExp(const QRegExp& rx) +{ + r = rx; +} + +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_VALIDATOR diff --git a/src/gui/widgets/qvalidator.h b/src/gui/widgets/qvalidator.h new file mode 100644 index 0000000..8114ca2 --- /dev/null +++ b/src/gui/widgets/qvalidator.h @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVALIDATOR_H +#define QVALIDATOR_H + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> +#include <QtCore/qregexp.h> +#include <QtCore/qlocale.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_VALIDATOR + +class QValidatorPrivate; + +class Q_GUI_EXPORT QValidator : public QObject +{ + Q_OBJECT +public: + explicit QValidator(QObject * parent); + ~QValidator(); + + enum State { + Invalid, + Intermediate, + Acceptable + +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + , Valid = Intermediate +#endif + }; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + virtual State validate(QString &, int &) const = 0; + virtual void fixup(QString &) const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QValidator(QObject * parent, const char *name); +#endif +protected: + QValidator(QObjectPrivate &d, QObject *parent); + QValidator(QValidatorPrivate &d, QObject *parent); + +private: + Q_DISABLE_COPY(QValidator) + Q_DECLARE_PRIVATE(QValidator) +}; + +class Q_GUI_EXPORT QIntValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(int bottom READ bottom WRITE setBottom) + Q_PROPERTY(int top READ top WRITE setTop) + +public: + explicit QIntValidator(QObject * parent); + QIntValidator(int bottom, int top, QObject * parent); + ~QIntValidator(); + + QValidator::State validate(QString &, int &) const; + + void setBottom(int); + void setTop(int); + virtual void setRange(int bottom, int top); + + int bottom() const { return b; } + int top() const { return t; } + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QIntValidator(QObject * parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QIntValidator(int bottom, int top, QObject * parent, const char *name); +#endif + +private: + Q_DISABLE_COPY(QIntValidator) + + int b; + int t; +}; + +#ifndef QT_NO_REGEXP + +class QDoubleValidatorPrivate; + +class Q_GUI_EXPORT QDoubleValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(double bottom READ bottom WRITE setBottom) + Q_PROPERTY(double top READ top WRITE setTop) + Q_PROPERTY(int decimals READ decimals WRITE setDecimals) + Q_PROPERTY(Notation notation READ notation WRITE setNotation) + +public: + explicit QDoubleValidator(QObject * parent); + QDoubleValidator(double bottom, double top, int decimals, QObject * parent); + ~QDoubleValidator(); + + enum Notation { + StandardNotation, + ScientificNotation + }; + + QValidator::State validate(QString &, int &) const; + + virtual void setRange(double bottom, double top, int decimals = 0); + void setBottom(double); + void setTop(double); + void setDecimals(int); + void setNotation(Notation); + + double bottom() const { return b; } + double top() const { return t; } + int decimals() const { return dec; } + Notation notation() const; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QDoubleValidator(QObject * parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QDoubleValidator(double bottom, double top, int decimals, + QObject * parent, const char *name); +#endif +private: + Q_DECLARE_PRIVATE(QDoubleValidator) + Q_DISABLE_COPY(QDoubleValidator) + + double b; + double t; + int dec; +}; + + +class Q_GUI_EXPORT QRegExpValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(QRegExp regExp READ regExp WRITE setRegExp) + +public: + explicit QRegExpValidator(QObject *parent); + QRegExpValidator(const QRegExp& rx, QObject *parent); + ~QRegExpValidator(); + + virtual QValidator::State validate(QString& input, int& pos) const; + + void setRegExp(const QRegExp& rx); + const QRegExp& regExp() const { return r; } // ### make inline for 5.0 + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QRegExpValidator(QObject *parent, const char *name); + QT3_SUPPORT_CONSTRUCTOR QRegExpValidator(const QRegExp& rx, QObject *parent, const char *name); +#endif + +private: + Q_DISABLE_COPY(QRegExpValidator) + + QRegExp r; +}; + +#endif // QT_NO_REGEXP + +#endif // QT_NO_VALIDATOR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QVALIDATOR_H diff --git a/src/gui/widgets/qwidgetanimator.cpp b/src/gui/widgets/qwidgetanimator.cpp new file mode 100644 index 0000000..5584ba1 --- /dev/null +++ b/src/gui/widgets/qwidgetanimator.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qtimer.h> +#include <QtCore/qdatetime.h> +#include <QtGui/qwidget.h> +#include <QtGui/qtextedit.h> +#include <QtGui/private/qwidget_p.h> +#include <qdebug.h> + +#include "qwidgetanimator_p.h" + +QT_BEGIN_NAMESPACE + +static const int g_animation_steps = 12; +static const int g_animation_interval = 16; + +// 1000 * (x/(1 + x*x) + 0.5) on interval [-1, 1] +static const int g_animate_function[] = +{ + 0, 1, 5, 12, 23, 38, 58, 84, 116, 155, 199, 251, 307, 368, + 433, 500, 566, 631, 692, 748, 799, 844, 883, 915, 941, 961, + 976, 987, 994, 998, 1000 +}; +static const int g_animate_function_points = sizeof(g_animate_function)/sizeof(int); + +static inline int animateHelper(int start, int stop, int step, int steps) +{ + if (start == stop) + return start; + if (step == 0) + return start; + if (step == steps) + return stop; + + int x = g_animate_function_points*step/(steps + 1); + return start + g_animate_function[x]*(stop - start)/1000; +} + +QWidgetAnimator::QWidgetAnimator(QObject *parent) + : QObject(parent) +{ + m_time = new QTime(); + m_timer = new QTimer(this); + m_timer->setInterval(g_animation_interval); + connect(m_timer, SIGNAL(timeout()), this, SLOT(animationStep())); +} + +QWidgetAnimator::~QWidgetAnimator() +{ + delete m_time; +} + +void QWidgetAnimator::abort(QWidget *w) +{ + if (m_animation_map.remove(w) == 0) + return; + if (m_animation_map.isEmpty()) { + m_timer->stop(); + emit finishedAll(); + } +} + +void QWidgetAnimator::animate(QWidget *widget, const QRect &_final_geometry, bool animate) +{ + QRect final_geometry = _final_geometry; + + QRect r = widget->geometry(); + if (r.right() < 0 || r.bottom() < 0) + r = QRect(); + + if (r.isNull() || final_geometry.isNull()) + animate = false; + + AnimationMap::const_iterator it = m_animation_map.constFind(widget); + if (it == m_animation_map.constEnd()) { + if (r == final_geometry) { + emit finished(widget); + return; + } + } else { + if ((*it).r2 == final_geometry) + return; + } + + if (animate) { + AnimationItem item(widget, r, final_geometry); + m_animation_map[widget] = item; + if (!m_timer->isActive()) { + m_timer->start(); + m_time->start(); + } + } else { + m_animation_map.remove(widget); + if (m_animation_map.isEmpty()) + m_timer->stop(); + + if (!final_geometry.isValid() && !widget->isWindow()) { + // Make the wigdet go away by sending it to negative space + QSize s = widget->size(); + final_geometry = QRect(-500 - s.width(), -500 - s.height(), s.width(), s.height()); + } + widget->setGeometry(final_geometry); + + emit finished(widget); + + if (m_animation_map.isEmpty()) + emit finishedAll(); + + return; + } +} + +void QWidgetAnimator::animationStep() +{ + int steps = (1 + m_time->restart())/g_animation_interval; + AnimationMap::iterator it = m_animation_map.begin(); + while (it != m_animation_map.end()) { + AnimationItem &item = *it; + + item.step = qMin(item.step + steps, g_animation_steps); + + int x = animateHelper(item.r1.left(), item.r2.left(), + item.step, g_animation_steps); + int y = animateHelper(item.r1.top(), item.r2.top(), + item.step, g_animation_steps); + int w = animateHelper(item.r1.width(), item.r2.width(), + item.step, g_animation_steps); + int h = animateHelper(item.r1.height(), item.r2.height(), + item.step, g_animation_steps); + + item.widget->setGeometry(x, y, w, h); + + if (item.step == g_animation_steps) { + emit finished(item.widget); + AnimationMap::iterator tmp = it; + ++it; + m_animation_map.erase(tmp); + } else { + ++it; + } + } + + if (m_animation_map.isEmpty()) { + m_timer->stop(); + emit finishedAll(); + } +} + +bool QWidgetAnimator::animating() const +{ + return m_timer->isActive(); +} + +bool QWidgetAnimator::animating(QWidget *widget) +{ + return m_animation_map.contains(widget); +} + +QT_END_NAMESPACE diff --git a/src/gui/widgets/qwidgetanimator_p.h b/src/gui/widgets/qwidgetanimator_p.h new file mode 100644 index 0000000..7204983 --- /dev/null +++ b/src/gui/widgets/qwidgetanimator_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGET_ANIMATOR_P_H +#define QWIDGET_ANIMATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <qobject.h> +#include <qrect.h> +#include <qmap.h> + +QT_BEGIN_NAMESPACE + +class QWidget; +class QTimer; +class QTime; + +class QWidgetAnimator : public QObject +{ + Q_OBJECT +public: + QWidgetAnimator(QObject *parent = 0); + ~QWidgetAnimator(); + void animate(QWidget *widget, const QRect &final_geometry, bool animate); + bool animating() const; + bool animating(QWidget *widget); + + void abort(QWidget *widget); + +signals: + void finished(QWidget *widget); + void finishedAll(); + +private slots: + void animationStep(); + +private: + struct AnimationItem { + AnimationItem(QWidget *_widget = 0, const QRect &_r1 = QRect(), + const QRect &_r2 = QRect()) + : widget(_widget), r1(_r1), r2(_r2), step(0) {} + QWidget *widget; + QRect r1, r2; + int step; + }; + typedef QMap<QWidget*, AnimationItem> AnimationMap; + AnimationMap m_animation_map; + QTimer *m_timer; + QTime *m_time; +}; + +QT_END_NAMESPACE + +#endif // QWIDGET_ANIMATOR_P_H diff --git a/src/gui/widgets/qwidgetresizehandler.cpp b/src/gui/widgets/qwidgetresizehandler.cpp new file mode 100644 index 0000000..9171244 --- /dev/null +++ b/src/gui/widgets/qwidgetresizehandler.cpp @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidgetresizehandler_p.h" + +#ifndef QT_NO_RESIZEHANDLER +#include "qframe.h" +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qcursor.h" +#include "qsizegrip.h" +#include "qevent.h" +#if defined(Q_WS_WIN) +#include "qt_windows.h" +#endif +#include "qdebug.h" +#include "private/qlayoutengine_p.h" + +QT_BEGIN_NAMESPACE + +#define RANGE 4 + +static bool resizeHorizontalDirectionFixed = false; +static bool resizeVerticalDirectionFixed = false; + +QWidgetResizeHandler::QWidgetResizeHandler(QWidget *parent, QWidget *cw) + : QObject(parent), widget(parent), childWidget(cw ? cw : parent), + fw(0), extrahei(0), buttonDown(false), moveResizeMode(false), sizeprotect(true), movingEnabled(true) +{ + mode = Nowhere; + widget->setMouseTracking(true); + QFrame *frame = qobject_cast<QFrame*>(widget); + range = frame ? frame->frameWidth() : RANGE; + range = qMax(RANGE, range); + activeForMove = activeForResize = true; + widget->installEventFilter(this); +} + +void QWidgetResizeHandler::setActive(Action ac, bool b) +{ + if (ac & Move) + activeForMove = b; + if (ac & Resize) + activeForResize = b; + + if (!isActive()) + setMouseCursor(Nowhere); +} + +bool QWidgetResizeHandler::isActive(Action ac) const +{ + bool b = false; + if (ac & Move) b = activeForMove; + if (ac & Resize) b |= activeForResize; + + return b; +} + +bool QWidgetResizeHandler::eventFilter(QObject *o, QEvent *ee) +{ + if (!isActive() + || (ee->type() != QEvent::MouseButtonPress + && ee->type() != QEvent::MouseButtonRelease + && ee->type() != QEvent::MouseMove + && ee->type() != QEvent::KeyPress + && ee->type() != QEvent::ShortcutOverride) + ) + return false; + + Q_ASSERT(o == widget); + QWidget *w = widget; + if (QApplication::activePopupWidget()) { + if (buttonDown && ee->type() == QEvent::MouseButtonRelease) + buttonDown = false; + return false; + } + + QMouseEvent *e = (QMouseEvent*)ee; + switch (e->type()) { + case QEvent::MouseButtonPress: { + if (w->isMaximized()) + break; + if (!widget->rect().contains(widget->mapFromGlobal(e->globalPos()))) + return false; + if (e->button() == Qt::LeftButton) { +#if defined(Q_WS_X11) + /* + Implicit grabs do not stop the X server from changing + the cursor in children, which looks *really* bad when + doing resizingk, so we grab the cursor. Note that we do + not do this on Windows since double clicks are lost due + to the grab (see change 198463). + */ + if (e->spontaneous()) +# if !defined(QT_NO_CURSOR) + widget->grabMouse(widget->cursor()); +# else + widget->grabMouse(); +# endif // QT_NO_CURSOR +#endif // Q_WS_X11 + buttonDown = false; + emit activate(); + bool me = movingEnabled; + movingEnabled = (me && o == widget); + mouseMoveEvent(e); + movingEnabled = me; + buttonDown = true; + moveOffset = widget->mapFromGlobal(e->globalPos()); + invertedMoveOffset = widget->rect().bottomRight() - moveOffset; + if (mode == Center) { + if (movingEnabled) + return true; + } else { + return true; + } + } + } break; + case QEvent::MouseButtonRelease: + if (w->isMaximized()) + break; + if (e->button() == Qt::LeftButton) { + moveResizeMode = false; + buttonDown = false; + widget->releaseMouse(); + widget->releaseKeyboard(); + if (mode == Center) { + if (movingEnabled) + return true; + } else { + return true; + } + } + break; + case QEvent::MouseMove: { + if (w->isMaximized()) + break; + buttonDown = buttonDown && (e->buttons() & Qt::LeftButton); // safety, state machine broken! + bool me = movingEnabled; + movingEnabled = (me && o == widget && (buttonDown || moveResizeMode)); + mouseMoveEvent(e); + movingEnabled = me; + if (mode == Center) { + if (movingEnabled) + return true; + } else { + return true; + } + } break; + case QEvent::KeyPress: + keyPressEvent((QKeyEvent*)e); + break; + case QEvent::ShortcutOverride: + if (buttonDown) { + ((QKeyEvent*)ee)->accept(); + return true; + } + break; + default: + break; + } + + return false; +} + +void QWidgetResizeHandler::mouseMoveEvent(QMouseEvent *e) +{ + QPoint pos = widget->mapFromGlobal(e->globalPos()); + if (!moveResizeMode && !buttonDown) { + if (pos.y() <= range && pos.x() <= range) + mode = TopLeft; + else if (pos.y() >= widget->height()-range && pos.x() >= widget->width()-range) + mode = BottomRight; + else if (pos.y() >= widget->height()-range && pos.x() <= range) + mode = BottomLeft; + else if (pos.y() <= range && pos.x() >= widget->width()-range) + mode = TopRight; + else if (pos.y() <= range) + mode = Top; + else if (pos.y() >= widget->height()-range) + mode = Bottom; + else if (pos.x() <= range) + mode = Left; + else if ( pos.x() >= widget->width()-range) + mode = Right; + else if (widget->rect().contains(pos)) + mode = Center; + else + mode = Nowhere; + + if (widget->isMinimized() || !isActive(Resize)) + mode = Center; +#ifndef QT_NO_CURSOR + setMouseCursor(mode); +#endif + return; + } + + if (mode == Center && !movingEnabled) + return; + + if (widget->testAttribute(Qt::WA_WState_ConfigPending)) + return; + + + QPoint globalPos = (!widget->isWindow() && widget->parentWidget()) ? + widget->parentWidget()->mapFromGlobal(e->globalPos()) : e->globalPos(); + if (!widget->isWindow() && !widget->parentWidget()->rect().contains(globalPos)) { + if (globalPos.x() < 0) + globalPos.rx() = 0; + if (globalPos.y() < 0) + globalPos.ry() = 0; + if (sizeprotect && globalPos.x() > widget->parentWidget()->width()) + globalPos.rx() = widget->parentWidget()->width(); + if (sizeprotect && globalPos.y() > widget->parentWidget()->height()) + globalPos.ry() = widget->parentWidget()->height(); + } + + QPoint p = globalPos + invertedMoveOffset; + QPoint pp = globalPos - moveOffset; + +#ifdef Q_WS_X11 + // Workaround for window managers which refuse to move a tool window partially offscreen. + QRect desktop = qApp->desktop()->availableGeometry(widget); + pp.rx() = qMax(pp.x(), desktop.left()); + pp.ry() = qMax(pp.y(), desktop.top()); + p.rx() = qMin(p.x(), desktop.right()); + p.ry() = qMin(p.y(), desktop.bottom()); +#endif + + QSize ms = qSmartMinSize(childWidget); + int mw = ms.width(); + int mh = ms.height(); + if (childWidget != widget) { + mw += 2 * fw; + mh += 2 * fw + extrahei; + } + + QSize maxsize(childWidget->maximumSize()); + if (childWidget != widget) + maxsize += QSize(2 * fw, 2 * fw + extrahei); + QSize mpsize(widget->geometry().right() - pp.x() + 1, + widget->geometry().bottom() - pp.y() + 1); + mpsize = mpsize.expandedTo(widget->minimumSize()).expandedTo(QSize(mw, mh)) + .boundedTo(maxsize); + QPoint mp(widget->geometry().right() - mpsize.width() + 1, + widget->geometry().bottom() - mpsize.height() + 1); + + QRect geom = widget->geometry(); + + switch (mode) { + case TopLeft: + geom = QRect(mp, widget->geometry().bottomRight()) ; + break; + case BottomRight: + geom = QRect(widget->geometry().topLeft(), p) ; + break; + case BottomLeft: + geom = QRect(QPoint(mp.x(), widget->geometry().y()), QPoint(widget->geometry().right(), p.y())) ; + break; + case TopRight: + geom = QRect(QPoint(widget->geometry().x(), mp.y()), QPoint(p.x(), widget->geometry().bottom())) ; + break; + case Top: + geom = QRect(QPoint(widget->geometry().left(), mp.y()), widget->geometry().bottomRight()) ; + break; + case Bottom: + geom = QRect(widget->geometry().topLeft(), QPoint(widget->geometry().right(), p.y())) ; + break; + case Left: + geom = QRect(QPoint(mp.x(), widget->geometry().top()), widget->geometry().bottomRight()) ; + break; + case Right: + geom = QRect(widget->geometry().topLeft(), QPoint(p.x(), widget->geometry().bottom())) ; + break; + case Center: + geom.moveTopLeft(pp); + break; + default: + break; + } + + geom = QRect(geom.topLeft(), + geom.size().expandedTo(widget->minimumSize()) + .expandedTo(QSize(mw, mh)) + .boundedTo(maxsize)); + + if (geom != widget->geometry() && + (widget->isWindow() || widget->parentWidget()->rect().intersects(geom))) { + if (mode == Center) + widget->move(geom.topLeft()); + else + widget->setGeometry(geom); + } + + QApplication::syncX(); +} + +void QWidgetResizeHandler::setMouseCursor(MousePosition m) +{ +#ifdef QT_NO_CURSOR + Q_UNUSED(m); +#else + QObjectList children = widget->children(); + for (int i = 0; i < children.size(); ++i) { + if (QWidget *w = qobject_cast<QWidget*>(children.at(i))) { + if (!w->testAttribute(Qt::WA_SetCursor) && !w->inherits("QWorkspaceTitleBar")) { + w->setCursor(Qt::ArrowCursor); + } + } + } + + switch (m) { + case TopLeft: + case BottomRight: + widget->setCursor(Qt::SizeFDiagCursor); + break; + case BottomLeft: + case TopRight: + widget->setCursor(Qt::SizeBDiagCursor); + break; + case Top: + case Bottom: + widget->setCursor(Qt::SizeVerCursor); + break; + case Left: + case Right: + widget->setCursor(Qt::SizeHorCursor); + break; + default: + widget->setCursor(Qt::ArrowCursor); + break; + } +#endif // QT_NO_CURSOR +} + +void QWidgetResizeHandler::keyPressEvent(QKeyEvent * e) +{ + if (!isMove() && !isResize()) + return; + bool is_control = e->modifiers() & Qt::ControlModifier; + int delta = is_control?1:8; + QPoint pos = QCursor::pos(); + switch (e->key()) { + case Qt::Key_Left: + pos.rx() -= delta; + if (pos.x() <= QApplication::desktop()->geometry().left()) { + if (mode == TopLeft || mode == BottomLeft) { + moveOffset.rx() += delta; + invertedMoveOffset.rx() += delta; + } else { + moveOffset.rx() -= delta; + invertedMoveOffset.rx() -= delta; + } + } + if (isResize() && !resizeHorizontalDirectionFixed) { + resizeHorizontalDirectionFixed = true; + if (mode == BottomRight) + mode = BottomLeft; + else if (mode == TopRight) + mode = TopLeft; +#ifndef QT_NO_CURSOR + setMouseCursor(mode); + widget->grabMouse(widget->cursor()); +#else + widget->grabMouse(); +#endif + } + break; + case Qt::Key_Right: + pos.rx() += delta; + if (pos.x() >= QApplication::desktop()->geometry().right()) { + if (mode == TopRight || mode == BottomRight) { + moveOffset.rx() += delta; + invertedMoveOffset.rx() += delta; + } else { + moveOffset.rx() -= delta; + invertedMoveOffset.rx() -= delta; + } + } + if (isResize() && !resizeHorizontalDirectionFixed) { + resizeHorizontalDirectionFixed = true; + if (mode == BottomLeft) + mode = BottomRight; + else if (mode == TopLeft) + mode = TopRight; +#ifndef QT_NO_CURSOR + setMouseCursor(mode); + widget->grabMouse(widget->cursor()); +#else + widget->grabMouse(); +#endif + } + break; + case Qt::Key_Up: + pos.ry() -= delta; + if (pos.y() <= QApplication::desktop()->geometry().top()) { + if (mode == TopLeft || mode == TopRight) { + moveOffset.ry() += delta; + invertedMoveOffset.ry() += delta; + } else { + moveOffset.ry() -= delta; + invertedMoveOffset.ry() -= delta; + } + } + if (isResize() && !resizeVerticalDirectionFixed) { + resizeVerticalDirectionFixed = true; + if (mode == BottomLeft) + mode = TopLeft; + else if (mode == BottomRight) + mode = TopRight; +#ifndef QT_NO_CURSOR + setMouseCursor(mode); + widget->grabMouse(widget->cursor()); +#else + widget->grabMouse(); +#endif + } + break; + case Qt::Key_Down: + pos.ry() += delta; + if (pos.y() >= QApplication::desktop()->geometry().bottom()) { + if (mode == BottomLeft || mode == BottomRight) { + moveOffset.ry() += delta; + invertedMoveOffset.ry() += delta; + } else { + moveOffset.ry() -= delta; + invertedMoveOffset.ry() -= delta; + } + } + if (isResize() && !resizeVerticalDirectionFixed) { + resizeVerticalDirectionFixed = true; + if (mode == TopLeft) + mode = BottomLeft; + else if (mode == TopRight) + mode = BottomRight; +#ifndef QT_NO_CURSOR + setMouseCursor(mode); + widget->grabMouse(widget->cursor()); +#else + widget->grabMouse(); +#endif + } + break; + case Qt::Key_Space: + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Escape: + moveResizeMode = false; + widget->releaseMouse(); + widget->releaseKeyboard(); + buttonDown = false; + break; + default: + return; + } + QCursor::setPos(pos); +} + + +void QWidgetResizeHandler::doResize() +{ + if (!activeForResize) + return; + + moveResizeMode = true; + moveOffset = widget->mapFromGlobal(QCursor::pos()); + if (moveOffset.x() < widget->width()/2) { + if (moveOffset.y() < widget->height()/2) + mode = TopLeft; + else + mode = BottomLeft; + } else { + if (moveOffset.y() < widget->height()/2) + mode = TopRight; + else + mode = BottomRight; + } + invertedMoveOffset = widget->rect().bottomRight() - moveOffset; +#ifndef QT_NO_CURSOR + setMouseCursor(mode); + widget->grabMouse(widget->cursor() ); +#else + widget->grabMouse(); +#endif + widget->grabKeyboard(); + resizeHorizontalDirectionFixed = false; + resizeVerticalDirectionFixed = false; +} + +void QWidgetResizeHandler::doMove() +{ + if (!activeForMove) + return; + + mode = Center; + moveResizeMode = true; + moveOffset = widget->mapFromGlobal(QCursor::pos()); + invertedMoveOffset = widget->rect().bottomRight() - moveOffset; +#ifndef QT_NO_CURSOR + widget->grabMouse(Qt::SizeAllCursor); +#else + widget->grabMouse(); +#endif + widget->grabKeyboard(); +} + +QT_END_NAMESPACE + +#endif //QT_NO_RESIZEHANDLER diff --git a/src/gui/widgets/qwidgetresizehandler_p.h b/src/gui/widgets/qwidgetresizehandler_p.h new file mode 100644 index 0000000..1eab292 --- /dev/null +++ b/src/gui/widgets/qwidgetresizehandler_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGETRESIZEHANDLER_P_H +#define QWIDGETRESIZEHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// + +#include "QtCore/qobject.h" +#include "QtCore/qpoint.h" + +#ifndef QT_NO_RESIZEHANDLER + +QT_BEGIN_NAMESPACE + +class QMouseEvent; +class QKeyEvent; + +class Q_GUI_EXPORT QWidgetResizeHandler : public QObject +{ + Q_OBJECT + +public: + enum Action { + Move = 0x01, + Resize = 0x02, + Any = Move|Resize + }; + + explicit QWidgetResizeHandler(QWidget *parent, QWidget *cw = 0); + void setActive(bool b) { setActive(Any, b); } + void setActive(Action ac, bool b); + bool isActive() const { return isActive(Any); } + bool isActive(Action ac) const; + void setMovingEnabled(bool b) { movingEnabled = b; } + bool isMovingEnabled() const { return movingEnabled; } + + bool isButtonDown() const { return buttonDown; } + + void setExtraHeight(int h) { extrahei = h; } + void setSizeProtection(bool b) { sizeprotect = b; } + + void setFrameWidth(int w) { fw = w; } + + void doResize(); + void doMove(); + +Q_SIGNALS: + void activate(); + +protected: + bool eventFilter(QObject *o, QEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void keyPressEvent(QKeyEvent *e); + +private: + Q_DISABLE_COPY(QWidgetResizeHandler) + + enum MousePosition { + Nowhere, + TopLeft, BottomRight, BottomLeft, TopRight, + Top, Bottom, Left, Right, + Center + }; + + QWidget *widget; + QWidget *childWidget; + QPoint moveOffset; + QPoint invertedMoveOffset; + MousePosition mode; + int fw; + int extrahei; + int range; + uint buttonDown :1; + uint moveResizeMode :1; + uint activeForResize :1; + uint sizeprotect :1; + uint movingEnabled :1; + uint activeForMove :1; + + void setMouseCursor(MousePosition m); + bool isMove() const { + return moveResizeMode && mode == Center; + } + bool isResize() const { + return moveResizeMode && !isMove(); + } +}; + +QT_END_NAMESPACE + +#endif // QT_NO_RESIZEHANDLER + +#endif // QWIDGETRESIZEHANDLER_P_H diff --git a/src/gui/widgets/qworkspace.cpp b/src/gui/widgets/qworkspace.cpp new file mode 100644 index 0000000..5221deb --- /dev/null +++ b/src/gui/widgets/qworkspace.cpp @@ -0,0 +1,3382 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qworkspace.h" +#ifndef QT_NO_WORKSPACE +#include "qapplication.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qdatetime.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qhash.h" +#include "qicon.h" +#include "qimage.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qmenubar.h" +#include "qmenu.h" +#include "qpainter.h" +#include "qpointer.h" +#include "qscrollbar.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qtooltip.h" +#include "qdebug.h" +#include <private/qwidget_p.h> +#include <private/qwidgetresizehandler_p.h> +#include <private/qlayoutengine_p.h> + +QT_BEGIN_NAMESPACE + +class QWorkspaceTitleBarPrivate; + + +/************************************************************** +* QMDIControl +* +* Used for displaying MDI controls in a maximized MDI window +* +*/ +class QMDIControl : public QWidget +{ + Q_OBJECT +signals: + void _q_minimize(); + void _q_restore(); + void _q_close(); + +public: + QMDIControl(QWidget *widget); + +private: + QSize sizeHint() const; + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void leaveEvent(QEvent *event); + bool event(QEvent *event); + void initStyleOption(QStyleOptionComplex *option) const; + QStyle::SubControl activeControl; //control locked by pressing and holding the mouse + QStyle::SubControl hoverControl; //previously active hover control, used for tracking repaints +}; + +bool QMDIControl::event(QEvent *event) +{ + if (event->type() == QEvent::ToolTip) { + QStyleOptionComplex opt; + initStyleOption(&opt); +#ifndef QT_NO_TOOLTIP + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + QStyle::SubControl ctrl = style()->hitTestComplexControl(QStyle::CC_MdiControls, &opt, + helpEvent->pos(), this); + if (ctrl == QStyle::SC_MdiCloseButton) + QToolTip::showText(helpEvent->globalPos(), QWorkspace::tr("Close")); + else if (ctrl == QStyle::SC_MdiMinButton) + QToolTip::showText(helpEvent->globalPos(), QWorkspace::tr("Minimize")); + else if (ctrl == QStyle::SC_MdiNormalButton) + QToolTip::showText(helpEvent->globalPos(), QWorkspace::tr("Restore Down")); + else + QToolTip::hideText(); +#endif // QT_NO_TOOLTIP + } + return QWidget::event(event); +} + +void QMDIControl::initStyleOption(QStyleOptionComplex *option) const +{ + option->initFrom(this); + option->subControls = QStyle::SC_All; + option->activeSubControls = QStyle::SC_None; +} + +QMDIControl::QMDIControl(QWidget *widget) + : QWidget(widget), activeControl(QStyle::SC_None), + hoverControl(QStyle::SC_None) +{ + setObjectName(QLatin1String("qt_maxcontrols")); + setFocusPolicy(Qt::NoFocus); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setMouseTracking(true); +} + +QSize QMDIControl::sizeHint() const +{ + ensurePolished(); + QStyleOptionComplex opt; + initStyleOption(&opt); + QSize size(48, 16); + return style()->sizeFromContents(QStyle::CT_MdiControls, &opt, size, this); +} + +void QMDIControl::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + QStyleOptionComplex opt; + initStyleOption(&opt); + QStyle::SubControl ctrl = style()->hitTestComplexControl(QStyle::CC_MdiControls, &opt, + event->pos(), this); + activeControl = ctrl; + update(); +} + +void QMDIControl::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + event->ignore(); + return; + } + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QStyle::SubControl under_mouse = style()->hitTestComplexControl(QStyle::CC_MdiControls, &opt, + event->pos(), this); + if (under_mouse == activeControl) { + switch (activeControl) { + case QStyle::SC_MdiCloseButton: + emit _q_close(); + break; + case QStyle::SC_MdiNormalButton: + emit _q_restore(); + break; + case QStyle::SC_MdiMinButton: + emit _q_minimize(); + break; + default: + break; + } + } + activeControl = QStyle::SC_None; + update(); +} + +void QMDIControl::leaveEvent(QEvent * /*event*/) +{ + hoverControl = QStyle::SC_None; + update(); +} + +void QMDIControl::mouseMoveEvent(QMouseEvent *event) +{ + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QStyle::SubControl under_mouse = style()->hitTestComplexControl(QStyle::CC_MdiControls, &opt, + event->pos(), this); + //test if hover state changes + if (hoverControl != under_mouse) { + hoverControl = under_mouse; + update(); + } +} + +void QMDIControl::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QStyleOptionComplex opt; + initStyleOption(&opt); + if (activeControl == hoverControl) { + opt.activeSubControls = activeControl; + opt.state |= QStyle::State_Sunken; + } else if (hoverControl != QStyle::SC_None && (activeControl == QStyle::SC_None)) { + opt.activeSubControls = hoverControl; + opt.state |= QStyle::State_MouseOver; + } + style()->drawComplexControl(QStyle::CC_MdiControls, &opt, &p, this); +} + +class QWorkspaceTitleBar : public QWidget +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWorkspaceTitleBar) + Q_PROPERTY(bool autoRaise READ autoRaise WRITE setAutoRaise) + Q_PROPERTY(bool movable READ isMovable WRITE setMovable) + +public: + QWorkspaceTitleBar (QWidget *w, QWidget *parent, Qt::WindowFlags f = 0); + ~QWorkspaceTitleBar(); + + bool isActive() const; + bool usesActiveColor() const; + + bool isMovable() const; + void setMovable(bool); + + bool autoRaise() const; + void setAutoRaise(bool); + + QWidget *window() const; + bool isTool() const; + + QSize sizeHint() const; + void initStyleOption(QStyleOptionTitleBar *option) const; + +public slots: + void setActive(bool); + +signals: + void doActivate(); + void doNormal(); + void doClose(); + void doMaximize(); + void doMinimize(); + void doShade(); + void showOperationMenu(); + void popupOperationMenu(const QPoint&); + void doubleClicked(); + +protected: + bool event(QEvent *); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *); +#endif + void mousePressEvent(QMouseEvent *); + void mouseDoubleClickEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void mouseMoveEvent(QMouseEvent *); + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void paintEvent(QPaintEvent *p); + +private: + Q_DISABLE_COPY(QWorkspaceTitleBar) +}; + + +class QWorkspaceTitleBarPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QWorkspaceTitleBar) +public: + QWorkspaceTitleBarPrivate() + : + lastControl(QStyle::SC_None), +#ifndef QT_NO_TOOLTIP + toolTip(0), +#endif + act(0), window(0), movable(1), pressed(0), autoraise(0), moving(0) + { + } + + Qt::WindowFlags flags; + QStyle::SubControl buttonDown; + QStyle::SubControl lastControl; + QPoint moveOffset; +#ifndef QT_NO_TOOLTIP + QToolTip *toolTip; +#endif + bool act :1; + QPointer<QWidget> window; + bool movable :1; + bool pressed :1; + bool autoraise :1; + bool moving : 1; + + int titleBarState() const; + void readColors(); +}; + +inline int QWorkspaceTitleBarPrivate::titleBarState() const +{ + Q_Q(const QWorkspaceTitleBar); + uint state = window ? window->windowState() : static_cast<Qt::WindowStates>(Qt::WindowNoState); + state |= uint((act && q->isActiveWindow()) ? QStyle::State_Active : QStyle::State_None); + return (int)state; +} + +void QWorkspaceTitleBar::initStyleOption(QStyleOptionTitleBar *option) const +{ + Q_D(const QWorkspaceTitleBar); + option->initFrom(this); + //################ + if (d->window && (d->flags & Qt::WindowTitleHint)) { + option->text = d->window->windowTitle(); + QIcon icon = d->window->windowIcon(); + QSize s = icon.actualSize(QSize(64, 64)); + option->icon = icon.pixmap(s); + } + option->subControls = QStyle::SC_All; + option->activeSubControls = QStyle::SC_None; + option->titleBarState = d->titleBarState(); + option->titleBarFlags = d->flags; + option->state &= ~QStyle::State_MouseOver; +} + +QWorkspaceTitleBar::QWorkspaceTitleBar(QWidget *w, QWidget *parent, Qt::WindowFlags f) + : QWidget(*new QWorkspaceTitleBarPrivate, parent, Qt::FramelessWindowHint) +{ + Q_D(QWorkspaceTitleBar); + if (f == 0 && w) + f = w->windowFlags(); + d->flags = f; + d->window = w; + d->buttonDown = QStyle::SC_None; + d->act = 0; + if (w) { + if (w->maximumSize() != QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) + d->flags &= ~Qt::WindowMaximizeButtonHint; + setWindowTitle(w->windowTitle()); + } + + d->readColors(); + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + setMouseTracking(true); + setAutoRaise(style()->styleHint(QStyle::SH_TitleBar_AutoRaise, 0, this)); +} + +QWorkspaceTitleBar::~QWorkspaceTitleBar() +{ +} + + +#ifdef Q_WS_WIN +static inline QRgb colorref2qrgb(COLORREF col) +{ + return qRgb(GetRValue(col),GetGValue(col),GetBValue(col)); +} +#endif + +void QWorkspaceTitleBarPrivate::readColors() +{ + Q_Q(QWorkspaceTitleBar); + QPalette pal = q->palette(); + + bool colorsInitialized = false; + +#ifdef Q_WS_WIN // ask system properties on windows +#ifndef SPI_GETGRADIENTCAPTIONS +#define SPI_GETGRADIENTCAPTIONS 0x1008 +#endif +#ifndef COLOR_GRADIENTACTIVECAPTION +#define COLOR_GRADIENTACTIVECAPTION 27 +#endif +#ifndef COLOR_GRADIENTINACTIVECAPTION +#define COLOR_GRADIENTINACTIVECAPTION 28 +#endif + if (QApplication::desktopSettingsAware()) { + pal.setColor(QPalette::Active, QPalette::Highlight, colorref2qrgb(GetSysColor(COLOR_ACTIVECAPTION))); + pal.setColor(QPalette::Inactive, QPalette::Highlight, colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTION))); + pal.setColor(QPalette::Active, QPalette::HighlightedText, colorref2qrgb(GetSysColor(COLOR_CAPTIONTEXT))); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTIONTEXT))); + if (QSysInfo::WindowsVersion != QSysInfo::WV_95 && QSysInfo::WindowsVersion != QSysInfo::WV_NT) { + colorsInitialized = true; + BOOL gradient; + QT_WA({ + SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &gradient, 0); + } , { + SystemParametersInfoA(SPI_GETGRADIENTCAPTIONS, 0, &gradient, 0); + }); + if (gradient) { + pal.setColor(QPalette::Active, QPalette::Base, colorref2qrgb(GetSysColor(COLOR_GRADIENTACTIVECAPTION))); + pal.setColor(QPalette::Inactive, QPalette::Base, colorref2qrgb(GetSysColor(COLOR_GRADIENTINACTIVECAPTION))); + } else { + pal.setColor(QPalette::Active, QPalette::Base, pal.color(QPalette::Active, QPalette::Highlight)); + pal.setColor(QPalette::Inactive, QPalette::Base, pal.color(QPalette::Inactive, QPalette::Highlight)); + } + } + } +#endif // Q_WS_WIN + if (!colorsInitialized) { + pal.setColor(QPalette::Active, QPalette::Highlight, + pal.color(QPalette::Active, QPalette::Highlight)); + pal.setColor(QPalette::Active, QPalette::Base, + pal.color(QPalette::Active, QPalette::Highlight)); + pal.setColor(QPalette::Inactive, QPalette::Highlight, + pal.color(QPalette::Inactive, QPalette::Dark)); + pal.setColor(QPalette::Inactive, QPalette::Base, + pal.color(QPalette::Inactive, QPalette::Dark)); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, + pal.color(QPalette::Inactive, QPalette::Window)); + } + + q->setPalette(pal); + q->setActive(act); +} + +void QWorkspaceTitleBar::mousePressEvent(QMouseEvent *e) +{ + Q_D(QWorkspaceTitleBar); + if (!d->act) + emit doActivate(); + if (e->button() == Qt::LeftButton) { + if (style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, 0) + && !rect().adjusted(5, 5, -5, 0).contains(e->pos())) { + // propagate border events to the QWidgetResizeHandler + e->ignore(); + return; + } + + d->pressed = true; + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QStyle::SubControl ctrl = style()->hitTestComplexControl(QStyle::CC_TitleBar, &opt, + e->pos(), this); + switch (ctrl) { + case QStyle::SC_TitleBarSysMenu: + if (d->flags & Qt::WindowSystemMenuHint) { + d->buttonDown = QStyle::SC_None; + static QTime *t = 0; + static QWorkspaceTitleBar *tc = 0; + if (!t) + t = new QTime; + if (tc != this || t->elapsed() > QApplication::doubleClickInterval()) { + emit showOperationMenu(); + t->start(); + tc = this; + } else { + tc = 0; + emit doClose(); + return; + } + } + break; + + case QStyle::SC_TitleBarShadeButton: + case QStyle::SC_TitleBarUnshadeButton: + if (d->flags & Qt::WindowShadeButtonHint) + d->buttonDown = ctrl; + break; + + case QStyle::SC_TitleBarNormalButton: + d->buttonDown = ctrl; + break; + + case QStyle::SC_TitleBarMinButton: + if (d->flags & Qt::WindowMinimizeButtonHint) + d->buttonDown = ctrl; + break; + + case QStyle::SC_TitleBarMaxButton: + if (d->flags & Qt::WindowMaximizeButtonHint) + d->buttonDown = ctrl; + break; + + case QStyle::SC_TitleBarCloseButton: + if (d->flags & Qt::WindowSystemMenuHint) + d->buttonDown = ctrl; + break; + + case QStyle::SC_TitleBarLabel: + d->buttonDown = ctrl; + d->moveOffset = mapToParent(e->pos()); + break; + + default: + break; + } + update(); + } else { + d->pressed = false; + } +} + +#ifndef QT_NO_CONTEXTMENU +void QWorkspaceTitleBar::contextMenuEvent(QContextMenuEvent *e) +{ + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QStyle::SubControl ctrl = style()->hitTestComplexControl(QStyle::CC_TitleBar, &opt, e->pos(), + this); + if(ctrl == QStyle::SC_TitleBarLabel || ctrl == QStyle::SC_TitleBarSysMenu) { + e->accept(); + emit popupOperationMenu(e->globalPos()); + } else { + e->ignore(); + } +} +#endif // QT_NO_CONTEXTMENU + +void QWorkspaceTitleBar::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QWorkspaceTitleBar); + if (!d->window) { + // could have been deleted as part of a double click event on the sysmenu + return; + } + if (e->button() == Qt::LeftButton && d->pressed) { + if (style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, 0) + && !rect().adjusted(5, 5, -5, 0).contains(e->pos())) { + // propagate border events to the QWidgetResizeHandler + e->ignore(); + d->buttonDown = QStyle::SC_None; + d->pressed = false; + return; + } + e->accept(); + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QStyle::SubControl ctrl = style()->hitTestComplexControl(QStyle::CC_TitleBar, &opt, + e->pos(), this); + + if (d->pressed) { + update(); + d->pressed = false; + d->moving = false; + } + if (ctrl == d->buttonDown) { + d->buttonDown = QStyle::SC_None; + switch(ctrl) { + case QStyle::SC_TitleBarShadeButton: + case QStyle::SC_TitleBarUnshadeButton: + if(d->flags & Qt::WindowShadeButtonHint) + emit doShade(); + break; + + case QStyle::SC_TitleBarNormalButton: + if(d->flags & Qt::WindowMinMaxButtonsHint) + emit doNormal(); + break; + + case QStyle::SC_TitleBarMinButton: + if(d->flags & Qt::WindowMinimizeButtonHint) { + if (d->window && d->window->isMinimized()) + emit doNormal(); + else + emit doMinimize(); + } + break; + + case QStyle::SC_TitleBarMaxButton: + if(d->flags & Qt::WindowMaximizeButtonHint) { + if(d->window && d->window->isMaximized()) + emit doNormal(); + else + emit doMaximize(); + } + break; + + case QStyle::SC_TitleBarCloseButton: + if(d->flags & Qt::WindowSystemMenuHint) { + d->buttonDown = QStyle::SC_None; + emit doClose(); + return; + } + break; + + default: + break; + } + } + } else { + e->ignore(); + } +} + +void QWorkspaceTitleBar::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QWorkspaceTitleBar); + e->ignore(); + if ((e->buttons() & Qt::LeftButton) && style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, 0) + && !rect().adjusted(5, 5, -5, 0).contains(e->pos()) && !d->pressed) { + // propagate border events to the QWidgetResizeHandler + return; + } + + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QStyle::SubControl under_mouse = style()->hitTestComplexControl(QStyle::CC_TitleBar, &opt, + e->pos(), this); + if(under_mouse != d->lastControl) { + d->lastControl = under_mouse; + update(); + } + + switch (d->buttonDown) { + case QStyle::SC_None: + break; + case QStyle::SC_TitleBarSysMenu: + break; + case QStyle::SC_TitleBarLabel: + if (d->buttonDown == QStyle::SC_TitleBarLabel && d->movable && d->pressed) { + if (d->moving || (d->moveOffset - mapToParent(e->pos())).manhattanLength() >= 4) { + d->moving = true; + QPoint p = mapFromGlobal(e->globalPos()); + + QWidget *parent = d->window ? d->window->parentWidget() : 0; + if(parent && parent->inherits("QWorkspaceChild")) { + QWidget *workspace = parent->parentWidget(); + p = workspace->mapFromGlobal(e->globalPos()); + if (!workspace->rect().contains(p)) { + if (p.x() < 0) + p.rx() = 0; + if (p.y() < 0) + p.ry() = 0; + if (p.x() > workspace->width()) + p.rx() = workspace->width(); + if (p.y() > workspace->height()) + p.ry() = workspace->height(); + } + } + + QPoint pp = p - d->moveOffset; + if (!parentWidget()->isMaximized()) + parentWidget()->move(pp); + } + } + e->accept(); + break; + default: + break; + } +} + +bool QWorkspaceTitleBar::isTool() const +{ + Q_D(const QWorkspaceTitleBar); + return (d->flags & Qt::WindowType_Mask) == Qt::Tool; +} + +// from qwidget.cpp +extern QString qt_setWindowTitle_helperHelper(const QString &, const QWidget*); + +void QWorkspaceTitleBar::paintEvent(QPaintEvent *) +{ + Q_D(QWorkspaceTitleBar); + QStyleOptionTitleBar opt; + initStyleOption(&opt); + opt.subControls = QStyle::SC_TitleBarLabel; + opt.activeSubControls = d->buttonDown; + + if (d->window && (d->flags & Qt::WindowTitleHint)) { + QString title = qt_setWindowTitle_helperHelper(opt.text, d->window); + int maxw = style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarLabel, + this).width(); + opt.text = fontMetrics().elidedText(title, Qt::ElideRight, maxw); + } + + if (d->flags & Qt::WindowSystemMenuHint) { + opt.subControls |= QStyle::SC_TitleBarSysMenu | QStyle::SC_TitleBarCloseButton; + if (d->window && (d->flags & Qt::WindowShadeButtonHint)) { + if (d->window->isMinimized()) + opt.subControls |= QStyle::SC_TitleBarUnshadeButton; + else + opt.subControls |= QStyle::SC_TitleBarShadeButton; + } + if (d->window && (d->flags & Qt::WindowMinMaxButtonsHint)) { + if(d->window && d->window->isMinimized()) + opt.subControls |= QStyle::SC_TitleBarNormalButton; + else + opt.subControls |= QStyle::SC_TitleBarMinButton; + } + if (d->window && (d->flags & Qt::WindowMaximizeButtonHint) && !d->window->isMaximized()) + opt.subControls |= QStyle::SC_TitleBarMaxButton; + } + + QStyle::SubControl under_mouse = QStyle::SC_None; + under_mouse = style()->hitTestComplexControl(QStyle::CC_TitleBar, &opt, + mapFromGlobal(QCursor::pos()), this); + if ((d->buttonDown == under_mouse) && d->pressed) { + opt.state |= QStyle::State_Sunken; + } else if( autoRaise() && under_mouse != QStyle::SC_None && !d->pressed) { + opt.activeSubControls = under_mouse; + opt.state |= QStyle::State_MouseOver; + } + opt.palette.setCurrentColorGroup(usesActiveColor() ? QPalette::Active : QPalette::Inactive); + + QPainter p(this); + style()->drawComplexControl(QStyle::CC_TitleBar, &opt, &p, this); +} + +void QWorkspaceTitleBar::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QWorkspaceTitleBar); + if (e->button() != Qt::LeftButton) { + e->ignore(); + return; + } + e->accept(); + QStyleOptionTitleBar opt; + initStyleOption(&opt); + switch (style()->hitTestComplexControl(QStyle::CC_TitleBar, &opt, e->pos(), this)) { + case QStyle::SC_TitleBarLabel: + emit doubleClicked(); + break; + + case QStyle::SC_TitleBarSysMenu: + if (d->flags & Qt::WindowSystemMenuHint) + emit doClose(); + break; + + default: + break; + } +} + +void QWorkspaceTitleBar::leaveEvent(QEvent *) +{ + Q_D(QWorkspaceTitleBar); + d->lastControl = QStyle::SC_None; + if(autoRaise() && !d->pressed) + update(); +} + +void QWorkspaceTitleBar::enterEvent(QEvent *) +{ + Q_D(QWorkspaceTitleBar); + if(autoRaise() && !d->pressed) + update(); + QEvent e(QEvent::Leave); + QApplication::sendEvent(parentWidget(), &e); +} + +void QWorkspaceTitleBar::setActive(bool active) +{ + Q_D(QWorkspaceTitleBar); + if (d->act == active) + return ; + + d->act = active; + update(); +} + +bool QWorkspaceTitleBar::isActive() const +{ + Q_D(const QWorkspaceTitleBar); + return d->act; +} + +bool QWorkspaceTitleBar::usesActiveColor() const +{ + return (isActive() && isActiveWindow()) || + (!window() && QWidget::window()->isActiveWindow()); +} + +QWidget *QWorkspaceTitleBar::window() const +{ + Q_D(const QWorkspaceTitleBar); + return d->window; +} + +bool QWorkspaceTitleBar::event(QEvent *e) +{ + Q_D(QWorkspaceTitleBar); + if (e->type() == QEvent::ApplicationPaletteChange) { + d->readColors(); + } else if (e->type() == QEvent::WindowActivate + || e->type() == QEvent::WindowDeactivate) { + if (d->act) + update(); + } + return QWidget::event(e); +} + +void QWorkspaceTitleBar::setMovable(bool b) +{ + Q_D(QWorkspaceTitleBar); + d->movable = b; +} + +bool QWorkspaceTitleBar::isMovable() const +{ + Q_D(const QWorkspaceTitleBar); + return d->movable; +} + +void QWorkspaceTitleBar::setAutoRaise(bool b) +{ + Q_D(QWorkspaceTitleBar); + d->autoraise = b; +} + +bool QWorkspaceTitleBar::autoRaise() const +{ + Q_D(const QWorkspaceTitleBar); + return d->autoraise; +} + +QSize QWorkspaceTitleBar::sizeHint() const +{ + ensurePolished(); + QStyleOptionTitleBar opt; + initStyleOption(&opt); + QRect menur = style()->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarSysMenu, this); + return QSize(menur.width(), style()->pixelMetric(QStyle::PM_TitleBarHeight, &opt, this)); +} + +/*! + \class QWorkspace + \obsolete + \brief The QWorkspace widget provides a workspace window that can be + used in an MDI application. + \ingroup application + + This class is deprecated. Use QMdiArea instead. + + Multiple Document Interface (MDI) applications are typically + composed of a main window containing a menu bar, a toolbar, and + a central QWorkspace widget. The workspace itself is used to display + a number of child windows, each of which is a widget. + + The workspace itself is an ordinary Qt widget. It has a standard + constructor that takes a parent widget. + Workspaces can be placed in any layout, but are typically given + as the central widget in a QMainWindow: + + \snippet doc/src/snippets/code/src_gui_widgets_qworkspace.cpp 0 + + Child windows (MDI windows) are standard Qt widgets that are + inserted into the workspace with addWindow(). As with top-level + widgets, you can call functions such as show(), hide(), + showMaximized(), and setWindowTitle() on a child window to change + its appearance within the workspace. You can also provide widget + flags to determine the layout of the decoration or the behavior of + the widget itself. + + To change or retrieve the geometry of a child window, you must + operate on its parentWidget(). The parentWidget() provides + access to the decorated frame that contains the child window + widget. When a child window is maximised, its decorated frame + is hidden. If the top-level widget contains a menu bar, it will display + the maximised window's operations menu to the left of the menu + entries, and the window's controls to the right. + + A child window becomes active when it gets the keyboard focus, + or when setFocus() is called. The user can activate a window by moving + focus in the usual ways, for example by clicking a window or by pressing + Tab. The workspace emits a signal windowActivated() when the active + window changes, and the function activeWindow() returns a pointer to the + active child window, or 0 if no window is active. + + The convenience function windowList() returns a list of all child + windows. This information could be used in a popup menu + containing a list of windows, for example. This feature is also + available as part of the \l{Window Menu} Solution. + + QWorkspace provides two built-in layout strategies for child + windows: cascade() and tile(). Both are slots so you can easily + connect menu entries to them. + + \table + \row \o \inlineimage mdi-cascade.png + \o \inlineimage mdi-tile.png + \endtable + + If you want your users to be able to work with child windows + larger than the visible workspace area, set the scrollBarsEnabled + property to true. + + \sa QDockWidget, {MDI Example} +*/ + + +class QWorkspaceChild : public QWidget +{ + Q_OBJECT + + friend class QWorkspacePrivate; + friend class QWorkspace; + friend class QWorkspaceTitleBar; + +public: + QWorkspaceChild(QWidget* window, QWorkspace* parent=0, Qt::WindowFlags flags = 0); + ~QWorkspaceChild(); + + void setActive(bool); + bool isActive() const; + + void adjustToFullscreen(); + + QWidget* windowWidget() const; + QWidget* iconWidget() const; + + void doResize(); + void doMove(); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + QSize baseSize() const; + + int frameWidth() const; + + void show(); + + bool isWindowOrIconVisible() const; + +signals: + void showOperationMenu(); + void popupOperationMenu(const QPoint&); + +public slots: + void activate(); + void showMinimized(); + void showMaximized(); + void showNormal(); + void showShaded(); + void internalRaise(); + void titleBarDoubleClicked(); + +protected: + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + void childEvent(QChildEvent*); + void resizeEvent(QResizeEvent *); + void moveEvent(QMoveEvent *); + bool eventFilter(QObject *, QEvent *); + + void paintEvent(QPaintEvent *); + void changeEvent(QEvent *); + +private: + void updateMask(); + + Q_DISABLE_COPY(QWorkspaceChild) + + QWidget *childWidget; + QWidgetResizeHandler *widgetResizeHandler; + QWorkspaceTitleBar *titlebar; + QPointer<QWorkspaceTitleBar> iconw; + QSize windowSize; + QSize shadeRestore; + QSize shadeRestoreMin; + bool act :1; + bool shademode :1; +}; + +int QWorkspaceChild::frameWidth() const +{ + return contentsRect().left(); +} + + + +class QWorkspacePrivate : public QWidgetPrivate { + Q_DECLARE_PUBLIC(QWorkspace) +public: + QWorkspaceChild* active; + QList<QWorkspaceChild *> windows; + QList<QWorkspaceChild *> focus; + QList<QWidget *> icons; + QWorkspaceChild* maxWindow; + QRect maxRestore; + QPointer<QMDIControl> maxcontrols; + QPointer<QMenuBar> maxmenubar; + QHash<int, const char*> shortcutMap; + + int px; + int py; + QWidget *becomeActive; + QPointer<QLabel> maxtools; + QString topTitle; + + QMenu *popup, *toolPopup; + enum WSActs { RestoreAct, MoveAct, ResizeAct, MinimizeAct, MaximizeAct, CloseAct, StaysOnTopAct, ShadeAct, NCountAct }; + QAction *actions[NCountAct]; + + QScrollBar *vbar, *hbar; + QWidget *corner; + int yoffset, xoffset; + QBrush background; + + void init(); + void insertIcon(QWidget* w); + void removeIcon(QWidget* w); + void place(QWidget*); + + QWorkspaceChild* findChild(QWidget* w); + void showMaximizeControls(); + void hideMaximizeControls(); + void activateWindow(QWidget* w, bool change_focus = true); + void hideChild(QWorkspaceChild *c); + void showWindow(QWidget* w); + void maximizeWindow(QWidget* w); + void minimizeWindow(QWidget* w); + void normalizeWindow(QWidget* w); + + QRect updateWorkspace(); + +private: + void _q_normalizeActiveWindow(); + void _q_minimizeActiveWindow(); + void _q_showOperationMenu(); + void _q_popupOperationMenu(const QPoint&); + void _q_operationMenuActivated(QAction *); + void _q_scrollBarChanged(); + void _q_updateActions(); + bool inTitleChange; +}; + +static bool isChildOf(QWidget * child, QWidget * parent) +{ + if (!parent || !child) + return false; + QWidget * w = child; + while(w && w != parent) + w = w->parentWidget(); + return w != 0; +} + +/*! + Constructs a workspace with the given \a parent. +*/ +QWorkspace::QWorkspace(QWidget *parent) + : QWidget(*new QWorkspacePrivate, parent, 0) +{ + Q_D(QWorkspace); + d->init(); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QWorkspace::QWorkspace(QWidget *parent, const char *name) + : QWidget(*new QWorkspacePrivate, parent, 0) +{ + Q_D(QWorkspace); + setObjectName(QString::fromAscii(name)); + d->init(); +} +#endif // QT3_SUPPORT + +/*! + \internal +*/ +void +QWorkspacePrivate::init() +{ + Q_Q(QWorkspace); + + maxcontrols = 0; + active = 0; + maxWindow = 0; + maxtools = 0; + px = 0; + py = 0; + becomeActive = 0; + popup = new QMenu(q); + toolPopup = new QMenu(q); + popup->setObjectName(QLatin1String("qt_internal_mdi_popup")); + toolPopup->setObjectName(QLatin1String("qt_internal_mdi_tool_popup")); + + actions[QWorkspacePrivate::RestoreAct] = new QAction(QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarNormalButton, 0, q)), + QWorkspace::tr("&Restore"), q); + actions[QWorkspacePrivate::MoveAct] = new QAction(QWorkspace::tr("&Move"), q); + actions[QWorkspacePrivate::ResizeAct] = new QAction(QWorkspace::tr("&Size"), q); + actions[QWorkspacePrivate::MinimizeAct] = new QAction(QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarMinButton, 0, q)), + QWorkspace::tr("Mi&nimize"), q); + actions[QWorkspacePrivate::MaximizeAct] = new QAction(QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarMaxButton, 0, q)), + QWorkspace::tr("Ma&ximize"), q); + actions[QWorkspacePrivate::CloseAct] = new QAction(QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarCloseButton, 0, q)), + QWorkspace::tr("&Close") +#ifndef QT_NO_SHORTCUT + +QLatin1Char('\t')+(QString)QKeySequence(Qt::CTRL+Qt::Key_F4) +#endif + ,q); + QObject::connect(actions[QWorkspacePrivate::CloseAct], SIGNAL(triggered()), q, SLOT(closeActiveWindow())); + actions[QWorkspacePrivate::StaysOnTopAct] = new QAction(QWorkspace::tr("Stay on &Top"), q); + actions[QWorkspacePrivate::StaysOnTopAct]->setChecked(true); + actions[QWorkspacePrivate::ShadeAct] = new QAction(QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarShadeButton, 0, q)), + QWorkspace::tr("Sh&ade"), q); + + QObject::connect(popup, SIGNAL(aboutToShow()), q, SLOT(_q_updateActions())); + QObject::connect(popup, SIGNAL(triggered(QAction*)), q, SLOT(_q_operationMenuActivated(QAction*))); + popup->addAction(actions[QWorkspacePrivate::RestoreAct]); + popup->addAction(actions[QWorkspacePrivate::MoveAct]); + popup->addAction(actions[QWorkspacePrivate::ResizeAct]); + popup->addAction(actions[QWorkspacePrivate::MinimizeAct]); + popup->addAction(actions[QWorkspacePrivate::MaximizeAct]); + popup->addSeparator(); + popup->addAction(actions[QWorkspacePrivate::CloseAct]); + + QObject::connect(toolPopup, SIGNAL(aboutToShow()), q, SLOT(_q_updateActions())); + QObject::connect(toolPopup, SIGNAL(triggered(QAction*)), q, SLOT(_q_operationMenuActivated(QAction*))); + toolPopup->addAction(actions[QWorkspacePrivate::MoveAct]); + toolPopup->addAction(actions[QWorkspacePrivate::ResizeAct]); + toolPopup->addAction(actions[QWorkspacePrivate::StaysOnTopAct]); + toolPopup->addSeparator(); + toolPopup->addAction(actions[QWorkspacePrivate::ShadeAct]); + toolPopup->addAction(actions[QWorkspacePrivate::CloseAct]); + +#ifndef QT_NO_SHORTCUT + // Set up shortcut bindings (id -> slot), most used first + QList <QKeySequence> shortcuts = QKeySequence::keyBindings(QKeySequence::NextChild); + foreach (const QKeySequence &seq, shortcuts) + shortcutMap.insert(q->grabShortcut(seq), "activateNextWindow"); + + shortcuts = QKeySequence::keyBindings(QKeySequence::PreviousChild); + foreach (const QKeySequence &seq, shortcuts) + shortcutMap.insert(q->grabShortcut(seq), "activatePreviousWindow"); + + shortcuts = QKeySequence::keyBindings(QKeySequence::Close); + foreach (const QKeySequence &seq, shortcuts) + shortcutMap.insert(q->grabShortcut(seq), "closeActiveWindow"); + + shortcutMap.insert(q->grabShortcut(QKeySequence(QLatin1String("ALT+-"))), "_q_showOperationMenu"); +#endif // QT_NO_SHORTCUT + + q->setBackgroundRole(QPalette::Dark); + q->setAutoFillBackground(true); + q->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + + hbar = vbar = 0; + corner = 0; + xoffset = yoffset = 0; + + q->window()->installEventFilter(q); + + inTitleChange = false; + updateWorkspace(); +} + +/*! + Destroys the workspace and frees any allocated resources. +*/ + +QWorkspace::~QWorkspace() +{ +} + +/*! \reimp */ +QSize QWorkspace::sizeHint() const +{ + QSize s(QApplication::desktop()->size()); + return QSize(s.width()*2/3, s.height()*2/3); +} + + +#ifdef QT3_SUPPORT +/*! + Sets the background color to \a c. + Use setBackground() instead. +*/ +void QWorkspace::setPaletteBackgroundColor(const QColor & c) +{ + setBackground(c); +} + +/*! + Sets the background pixmap to \a pm. + Use setBackground() instead. +*/ +void QWorkspace::setPaletteBackgroundPixmap(const QPixmap & pm) +{ + setBackground(pm); +} +#endif // QT3_SUPPORT + +/*! + \property QWorkspace::background + \brief the workspace's background +*/ +QBrush QWorkspace::background() const +{ + Q_D(const QWorkspace); + if (d->background.style() == Qt::NoBrush) + return palette().dark(); + return d->background; +} + +void QWorkspace::setBackground(const QBrush &background) +{ + Q_D(QWorkspace); + d->background = background; + setAttribute(Qt::WA_OpaquePaintEvent, background.style() == Qt::NoBrush); + update(); +} + +/*! + Adds widget \a w as new sub window to the workspace. If \a flags + are non-zero, they will override the flags set on the widget. + + Returns the widget used for the window frame. + + To remove the widget \a w from the workspace, simply call + setParent() with the new parent (or 0 to make it a stand-alone + window). +*/ +QWidget * QWorkspace::addWindow(QWidget *w, Qt::WindowFlags flags) +{ + Q_D(QWorkspace); + if (!w) + return 0; + + w->setAutoFillBackground(true); + + QWidgetPrivate::adjustFlags(flags); + +#if 0 + bool wasMaximized = w->isMaximized(); + bool wasMinimized = w->isMinimized(); +#endif + bool hasSize = w->testAttribute(Qt::WA_Resized); + int x = w->x(); + int y = w->y(); + bool hasPos = w->testAttribute(Qt::WA_Moved); + QSize s = w->size().expandedTo(qSmartMinSize(w)); + if (!hasSize && w->sizeHint().isValid()) + w->adjustSize(); + + QWorkspaceChild* child = new QWorkspaceChild(w, this, flags); + child->setObjectName(QLatin1String("qt_workspacechild")); + child->installEventFilter(this); + + connect(child, SIGNAL(popupOperationMenu(QPoint)), + this, SLOT(_q_popupOperationMenu(QPoint))); + connect(child, SIGNAL(showOperationMenu()), + this, SLOT(_q_showOperationMenu())); + d->windows.append(child); + if (child->isVisibleTo(this)) + d->focus.append(child); + child->internalRaise(); + + if (!hasPos) + d->place(child); + if (!hasSize) + child->adjustSize(); + if (hasPos) + child->move(x, y); + + return child; + +#if 0 + if (wasMaximized) + w->showMaximized(); + else if (wasMinimized) + w->showMinimized(); + else if (!hasBeenHidden) + d->activateWindow(w); + + d->updateWorkspace(); + return child; +#endif +} + +/*! \reimp */ +void QWorkspace::childEvent(QChildEvent * e) +{ + Q_D(QWorkspace); + if (e->removed()) { + if (d->windows.removeAll(static_cast<QWorkspaceChild*>(e->child()))) { + d->focus.removeAll(static_cast<QWorkspaceChild*>(e->child())); + if (d->maxWindow == e->child()) + d->maxWindow = 0; + d->updateWorkspace(); + } + } +} + +/*! \reimp */ +#ifndef QT_NO_WHEELEVENT +void QWorkspace::wheelEvent(QWheelEvent *e) +{ + Q_D(QWorkspace); + if (!scrollBarsEnabled()) + return; + // the scroll bars are children of the workspace, so if we receive + // a wheel event we redirect to the scroll bars using a direct event + // call, /not/ using sendEvent() because if the scroll bar ignores the + // event QApplication::sendEvent() will propagate the event to the parent widget, + // which is us, who /just/ sent it. + if (d->vbar && d->vbar->isVisible() && !(e->modifiers() & Qt::AltModifier)) + d->vbar->event(e); + else if (d->hbar && d->hbar->isVisible()) + d->hbar->event(e); +} +#endif + +void QWorkspacePrivate::activateWindow(QWidget* w, bool change_focus) +{ + Q_Q(QWorkspace); + if (!w) { + active = 0; + emit q->windowActivated(0); + return; + } + if (!q->isVisible()) { + becomeActive = w; + return; + } + + if (active && active->windowWidget() == w) { + if (!isChildOf(q->focusWidget(), w)) // child window does not have focus + active->setActive(true); + return; + } + + active = 0; + // First deactivate all other workspace clients + QList<QWorkspaceChild *>::Iterator it(windows.begin()); + while (it != windows.end()) { + QWorkspaceChild* c = *it; + ++it; + if (c->windowWidget() == w) + active = c; + else + c->setActive(false); + } + + if (!active) + return; + + // Then activate the new one, so the focus is stored correctly + active->setActive(true); + + if (!active) + return; + + if (maxWindow && maxWindow != active && active->windowWidget() && + (active->windowWidget()->windowFlags() & Qt::WindowMaximizeButtonHint)) + active->showMaximized(); + + active->internalRaise(); + + if (change_focus) { + int from = focus.indexOf(active); + if (from >= 0) + focus.move(from, focus.size() - 1); + } + + updateWorkspace(); + emit q->windowActivated(w); +} + + +/*! + Returns a pointer to the widget corresponding to the active child + window, or 0 if no window is active. + + \sa setActiveWindow() +*/ +QWidget* QWorkspace::activeWindow() const +{ + Q_D(const QWorkspace); + return d->active? d->active->windowWidget() : 0; +} + +/*! + Makes the child window that contains \a w the active child window. + + \sa activeWindow() +*/ +void QWorkspace::setActiveWindow(QWidget *w) +{ + Q_D(QWorkspace); + d->activateWindow(w, true); + if (w && w->isMinimized()) + w->setWindowState(w->windowState() & ~Qt::WindowMinimized); +} + +void QWorkspacePrivate::place(QWidget *w) +{ + Q_Q(QWorkspace); + + QList<QWidget *> widgets; + for (QList<QWorkspaceChild *>::Iterator it(windows.begin()); it != windows.end(); ++it) + if (*it != w) + widgets.append(*it); + + int overlap, minOverlap = 0; + int possible; + + QRect r1(0, 0, 0, 0); + QRect r2(0, 0, 0, 0); + QRect maxRect = q->rect(); + int x = maxRect.left(), y = maxRect.top(); + QPoint wpos(maxRect.left(), maxRect.top()); + + bool firstPass = true; + + do { + if (y + w->height() > maxRect.bottom()) { + overlap = -1; + } else if(x + w->width() > maxRect.right()) { + overlap = -2; + } else { + overlap = 0; + + r1.setRect(x, y, w->width(), w->height()); + + QWidget *l; + QList<QWidget *>::Iterator it(widgets.begin()); + while (it != widgets.end()) { + l = *it; + ++it; + + if (maxWindow == l) + r2 = QStyle::visualRect(q->layoutDirection(), maxRect, maxRestore); + else + r2 = QStyle::visualRect(q->layoutDirection(), maxRect, + QRect(l->x(), l->y(), l->width(), l->height())); + + if (r2.intersects(r1)) { + r2.setCoords(qMax(r1.left(), r2.left()), + qMax(r1.top(), r2.top()), + qMin(r1.right(), r2.right()), + qMin(r1.bottom(), r2.bottom()) + ); + + overlap += (r2.right() - r2.left()) * + (r2.bottom() - r2.top()); + } + } + } + + if (overlap == 0) { + wpos = QPoint(x, y); + break; + } + + if (firstPass) { + firstPass = false; + minOverlap = overlap; + } else if (overlap >= 0 && overlap < minOverlap) { + minOverlap = overlap; + wpos = QPoint(x, y); + } + + if (overlap > 0) { + possible = maxRect.right(); + if (possible - w->width() > x) possible -= w->width(); + + QWidget *l; + QList<QWidget *>::Iterator it(widgets.begin()); + while (it != widgets.end()) { + l = *it; + ++it; + if (maxWindow == l) + r2 = QStyle::visualRect(q->layoutDirection(), maxRect, maxRestore); + else + r2 = QStyle::visualRect(q->layoutDirection(), maxRect, + QRect(l->x(), l->y(), l->width(), l->height())); + + if((y < r2.bottom()) && (r2.top() < w->height() + y)) { + if(r2.right() > x) + possible = possible < r2.right() ? + possible : r2.right(); + + if(r2.left() - w->width() > x) + possible = possible < r2.left() - w->width() ? + possible : r2.left() - w->width(); + } + } + + x = possible; + } else if (overlap == -2) { + x = maxRect.left(); + possible = maxRect.bottom(); + + if (possible - w->height() > y) possible -= w->height(); + + QWidget *l; + QList<QWidget *>::Iterator it(widgets.begin()); + while (it != widgets.end()) { + l = *it; + ++it; + if (maxWindow == l) + r2 = QStyle::visualRect(q->layoutDirection(), maxRect, maxRestore); + else + r2 = QStyle::visualRect(q->layoutDirection(), maxRect, + QRect(l->x(), l->y(), l->width(), l->height())); + + if(r2.bottom() > y) + possible = possible < r2.bottom() ? + possible : r2.bottom(); + + if(r2.top() - w->height() > y) + possible = possible < r2.top() - w->height() ? + possible : r2.top() - w->height(); + } + + y = possible; + } + } + while(overlap != 0 && overlap != -1); + + QRect resultRect = w->geometry(); + resultRect.moveTo(wpos); + w->setGeometry(QStyle::visualRect(q->layoutDirection(), maxRect, resultRect)); + updateWorkspace(); +} + + +void QWorkspacePrivate::insertIcon(QWidget* w) +{ + Q_Q(QWorkspace); + if (!w || icons.contains(w)) + return; + icons.append(w); + if (w->parentWidget() != q) { + w->setParent(q, 0); + w->move(0,0); + } + QRect cr = updateWorkspace(); + int x = 0; + int y = cr.height() - w->height(); + + QList<QWidget *>::Iterator it(icons.begin()); + while (it != icons.end()) { + QWidget* i = *it; + ++it; + if (x > 0 && x + i->width() > cr.width()){ + x = 0; + y -= i->height(); + } + + if (i != w && + i->geometry().intersects(QRect(x, y, w->width(), w->height()))) + x += i->width(); + } + w->move(x, y); + + if (q->isVisibleTo(q->parentWidget())) { + w->show(); + w->lower(); + } + updateWorkspace(); +} + + +void QWorkspacePrivate::removeIcon(QWidget* w) +{ + if (icons.removeAll(w)) + w->hide(); +} + + +/*! \reimp */ +void QWorkspace::resizeEvent(QResizeEvent *) +{ + Q_D(QWorkspace); + if (d->maxWindow) { + d->maxWindow->adjustToFullscreen(); + if (d->maxWindow->windowWidget()) + d->maxWindow->windowWidget()->overrideWindowState(Qt::WindowMaximized); + } + d->updateWorkspace(); +} + +/*! \reimp */ +void QWorkspace::showEvent(QShowEvent *e) +{ + Q_D(QWorkspace); + if (d->maxWindow) + d->showMaximizeControls(); + QWidget::showEvent(e); + if (d->becomeActive) { + d->activateWindow(d->becomeActive); + d->becomeActive = 0; + } else if (d->windows.count() > 0 && !d->active) { + d->activateWindow(d->windows.first()->windowWidget()); + } + +// // force a frame repaint - this is a workaround for what seems to be a bug +// // introduced when changing the QWidget::show() implementation. Might be +// // a windows bug as well though. +// for (int i = 0; i < d->windows.count(); ++i) { +// QWorkspaceChild* c = d->windows.at(i); +// c->update(c->rect()); +// } + + d->updateWorkspace(); +} + +/*! \reimp */ +void QWorkspace::hideEvent(QHideEvent *) +{ + Q_D(QWorkspace); + if (!isVisible()) + d->hideMaximizeControls(); +} + +/*! \reimp */ +void QWorkspace::paintEvent(QPaintEvent *) +{ + Q_D(QWorkspace); + + if (d->background.style() != Qt::NoBrush) { + QPainter p(this); + p.fillRect(0, 0, width(), height(), d->background); + } +} + +void QWorkspacePrivate::minimizeWindow(QWidget* w) +{ + QWorkspaceChild* c = findChild(w); + + if (!w || !(w->windowFlags() & Qt::WindowMinimizeButtonHint)) + return; + + if (c) { + bool wasMax = false; + if (c == maxWindow) { + wasMax = true; + maxWindow = 0; + hideMaximizeControls(); + for (QList<QWorkspaceChild *>::Iterator it(windows.begin()); it != windows.end(); ++it) { + QWorkspaceChild* c = *it; + if (c->titlebar) + c->titlebar->setMovable(true); + c->widgetResizeHandler->setActive(true); + } + } + c->hide(); + if (wasMax) + c->setGeometry(maxRestore); + if (!focus.contains(c)) + focus.append(c); + insertIcon(c->iconWidget()); + + if (!maxWindow) + activateWindow(w); + + updateWorkspace(); + + w->overrideWindowState(Qt::WindowMinimized); + c->overrideWindowState(Qt::WindowMinimized); + } +} + +void QWorkspacePrivate::normalizeWindow(QWidget* w) +{ + Q_Q(QWorkspace); + QWorkspaceChild* c = findChild(w); + if (!w) + return; + if (c) { + w->overrideWindowState(Qt::WindowNoState); + hideMaximizeControls(); + if (!maxmenubar || q->style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, q) || !maxWindow) { + if (w->minimumSize() != w->maximumSize()) + c->widgetResizeHandler->setActive(true); + if (c->titlebar) + c->titlebar->setMovable(true); + } + w->overrideWindowState(Qt::WindowNoState); + c->overrideWindowState(Qt::WindowNoState); + + if (c == maxWindow) { + c->setGeometry(maxRestore); + maxWindow = 0; + } else { + if (c->iconw) + removeIcon(c->iconw->parentWidget()); + c->show(); + } + + hideMaximizeControls(); + for (QList<QWorkspaceChild *>::Iterator it(windows.begin()); it != windows.end(); ++it) { + QWorkspaceChild* c = *it; + if (c->titlebar) + c->titlebar->setMovable(true); + if (c->childWidget && c->childWidget->minimumSize() != c->childWidget->maximumSize()) + c->widgetResizeHandler->setActive(true); + } + activateWindow(w, true); + updateWorkspace(); + } +} + +void QWorkspacePrivate::maximizeWindow(QWidget* w) +{ + Q_Q(QWorkspace); + QWorkspaceChild* c = findChild(w); + + if (!w || !(w->windowFlags() & Qt::WindowMaximizeButtonHint)) + return; + + if (!c || c == maxWindow) + return; + + bool updatesEnabled = q->updatesEnabled(); + q->setUpdatesEnabled(false); + + if (c->iconw && icons.contains(c->iconw->parentWidget())) + normalizeWindow(w); + QRect r(c->geometry()); + QWorkspaceChild *oldMaxWindow = maxWindow; + maxWindow = c; + + showMaximizeControls(); + + c->adjustToFullscreen(); + c->show(); + c->internalRaise(); + if (oldMaxWindow != c) { + if (oldMaxWindow) { + oldMaxWindow->setGeometry(maxRestore); + oldMaxWindow->overrideWindowState(Qt::WindowNoState); + if(oldMaxWindow->windowWidget()) + oldMaxWindow->windowWidget()->overrideWindowState(Qt::WindowNoState); + } + maxRestore = r; + } + + activateWindow(w); + + if(!maxmenubar || q->style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, q)) { + if (!active && becomeActive) { + active = (QWorkspaceChild*)becomeActive->parentWidget(); + active->setActive(true); + becomeActive = 0; + emit q->windowActivated(active->windowWidget()); + } + c->widgetResizeHandler->setActive(false); + if (c->titlebar) + c->titlebar->setMovable(false); + } + updateWorkspace(); + + w->overrideWindowState(Qt::WindowMaximized); + c->overrideWindowState(Qt::WindowMaximized); + q->setUpdatesEnabled(updatesEnabled); +} + +void QWorkspacePrivate::showWindow(QWidget* w) +{ + if (w->isMinimized() && (w->windowFlags() & Qt::WindowMinimizeButtonHint)) + minimizeWindow(w); + else if ((maxWindow || w->isMaximized()) && w->windowFlags() & Qt::WindowMaximizeButtonHint) + maximizeWindow(w); + else if (w->windowFlags() & Qt::WindowMaximizeButtonHint) + normalizeWindow(w); + else + w->parentWidget()->show(); + if (maxWindow) + maxWindow->internalRaise(); + updateWorkspace(); +} + + +QWorkspaceChild* QWorkspacePrivate::findChild(QWidget* w) +{ + QList<QWorkspaceChild *>::Iterator it(windows.begin()); + while (it != windows.end()) { + QWorkspaceChild* c = *it; + ++it; + if (c->windowWidget() == w) + return c; + } + return 0; +} + +/*! + Returns a list of all visible or minimized child windows. If \a + order is CreationOrder (the default), the windows are listed in + the order in which they were inserted into the workspace. If \a + order is StackingOrder, the windows are listed in their stacking + order, with the topmost window as the last item in the list. +*/ +QWidgetList QWorkspace::windowList(WindowOrder order) const +{ + Q_D(const QWorkspace); + QWidgetList windows; + if (order == StackingOrder) { + QObjectList cl = children(); + for (int i = 0; i < cl.size(); ++i) { + QWorkspaceChild *c = qobject_cast<QWorkspaceChild*>(cl.at(i)); + if (c && c->isWindowOrIconVisible()) + windows.append(c->windowWidget()); + } + } else { + QList<QWorkspaceChild *>::ConstIterator it(d->windows.begin()); + while (it != d->windows.end()) { + QWorkspaceChild* c = *it; + ++it; + if (c && c->isWindowOrIconVisible()) + windows.append(c->windowWidget()); + } + } + return windows; +} + + +/*! \reimp */ +bool QWorkspace::event(QEvent *e) +{ +#ifndef QT_NO_SHORTCUT + Q_D(QWorkspace); + if (e->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(e); + const char *theSlot = d->shortcutMap.value(se->shortcutId(), 0); + if (theSlot) + QMetaObject::invokeMethod(this, theSlot); + } else +#endif + if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut){ + return true; + } + return QWidget::event(e); +} + +/*! \reimp */ +bool QWorkspace::eventFilter(QObject *o, QEvent * e) +{ + Q_D(QWorkspace); + static QTime* t = 0; + static QWorkspace* tc = 0; + if (o == d->maxtools) { + switch (e->type()) { + case QEvent::MouseButtonPress: + { + QMenuBar* b = (QMenuBar*)o->parent(); + if (!t) + t = new QTime; + if (tc != this || t->elapsed() > QApplication::doubleClickInterval()) { + if (isRightToLeft()) { + QPoint p = b->mapToGlobal(QPoint(b->x() + b->width(), b->y() + b->height())); + p.rx() -= d->popup->sizeHint().width(); + d->_q_popupOperationMenu(p); + } else { + d->_q_popupOperationMenu(b->mapToGlobal(QPoint(b->x(), b->y() + b->height()))); + } + t->start(); + tc = this; + } else { + tc = 0; + closeActiveWindow(); + } + return true; + } + default: + break; + } + return QWidget::eventFilter(o, e); + } + switch (e->type()) { + case QEvent::HideToParent: + break; + case QEvent::ShowToParent: + if (QWorkspaceChild *c = qobject_cast<QWorkspaceChild*>(o)) + if (!d->focus.contains(c)) + d->focus.append(c); + d->updateWorkspace(); + break; + case QEvent::WindowTitleChange: + if (!d->inTitleChange) { + if (o == window()) + d->topTitle = window()->windowTitle(); + if (d->maxWindow && d->maxWindow->windowWidget() && d->topTitle.size()) { + d->inTitleChange = true; + window()->setWindowTitle(tr("%1 - [%2]") + .arg(d->topTitle).arg(d->maxWindow->windowWidget()->windowTitle())); + d->inTitleChange = false; + } + } + break; + + case QEvent::ModifiedChange: + if (o == d->maxWindow) + window()->setWindowModified(d->maxWindow->isWindowModified()); + break; + + case QEvent::Close: + if (o == window()) + { + QList<QWorkspaceChild *>::Iterator it(d->windows.begin()); + while (it != d->windows.end()) { + QWorkspaceChild* c = *it; + ++it; + if (c->shademode) + c->showShaded(); + } + } else if (qobject_cast<QWorkspaceChild*>(o)) { + d->popup->hide(); + } + d->updateWorkspace(); + break; + default: + break; + } + return QWidget::eventFilter(o, e); +} + +static QMenuBar *findMenuBar(QWidget *w) +{ + // don't search recursively to avoid finding a menu bar of a + // mainwindow that happens to be a workspace window (like + // a mainwindow in designer) + QList<QObject *> children = w->children(); + for (int i = 0; i < children.count(); ++i) { + QMenuBar *bar = qobject_cast<QMenuBar *>(children.at(i)); + if (bar) + return bar; + } + return 0; +} + +void QWorkspacePrivate::showMaximizeControls() +{ + Q_Q(QWorkspace); + Q_ASSERT(maxWindow); + + // merge windowtitle and modified state + if (!topTitle.size()) + topTitle = q->window()->windowTitle(); + + if (maxWindow->windowWidget()) { + QString docTitle = maxWindow->windowWidget()->windowTitle(); + if (topTitle.size() && docTitle.size()) { + inTitleChange = true; + q->window()->setWindowTitle(QWorkspace::tr("%1 - [%2]").arg(topTitle).arg(docTitle)); + inTitleChange = false; + } + q->window()->setWindowModified(maxWindow->windowWidget()->isWindowModified()); + } + + if (!q->style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, q)) { + QMenuBar* b = 0; + + // Do a breadth-first search first on every parent, + QWidget* w = q->parentWidget(); + while (w) { + b = findMenuBar(w); + if (b) + break; + w = w->parentWidget(); + } + + // last attempt. + if (!b) + b = findMenuBar(q->window()); + + if (!b) + return; + + if (!maxcontrols) { + maxmenubar = b; + maxcontrols = new QMDIControl(b); + QObject::connect(maxcontrols, SIGNAL(_q_minimize()), + q, SLOT(_q_minimizeActiveWindow())); + QObject::connect(maxcontrols, SIGNAL(_q_restore()), + q, SLOT(_q_normalizeActiveWindow())); + QObject::connect(maxcontrols, SIGNAL(_q_close()), + q, SLOT(closeActiveWindow())); + } + + b->setCornerWidget(maxcontrols); + if (b->isVisible()) + maxcontrols->show(); + if (!active && becomeActive) { + active = (QWorkspaceChild*)becomeActive->parentWidget(); + active->setActive(true); + becomeActive = 0; + emit q->windowActivated(active->windowWidget()); + } + if (active) { + if (!maxtools) { + maxtools = new QLabel(q->window()); + maxtools->setObjectName(QLatin1String("qt_maxtools")); + maxtools->installEventFilter(q); + } + if (active->windowWidget() && !active->windowWidget()->windowIcon().isNull()) { + QIcon icon = active->windowWidget()->windowIcon(); + int iconSize = maxcontrols->size().height(); + maxtools->setPixmap(icon.pixmap(QSize(iconSize, iconSize))); + } else { + QPixmap pm = q->style()->standardPixmap(QStyle::SP_TitleBarMenuButton, 0, q); + if (pm.isNull()) { + pm = QPixmap(14,14); + pm.fill(Qt::black); + } + maxtools->setPixmap(pm); + } + b->setCornerWidget(maxtools, Qt::TopLeftCorner); + if (b->isVisible()) + maxtools->show(); + } + } +} + + +void QWorkspacePrivate::hideMaximizeControls() +{ + Q_Q(QWorkspace); + if (maxmenubar && !q->style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, q)) { + if (maxmenubar) { + maxmenubar->setCornerWidget(0, Qt::TopLeftCorner); + maxmenubar->setCornerWidget(0, Qt::TopRightCorner); + } + if (maxcontrols) { + maxcontrols->deleteLater(); + maxcontrols = 0; + } + if (maxtools) { + maxtools->deleteLater(); + maxtools = 0; + } + } + + //unmerge the title bar/modification state + if (topTitle.size()) { + inTitleChange = true; + q->window()->setWindowTitle(topTitle); + inTitleChange = false; + } + q->window()->setWindowModified(false); +} + +/*! + Closes the child window that is currently active. + + \sa closeAllWindows() +*/ +void QWorkspace::closeActiveWindow() +{ + Q_D(QWorkspace); + if (d->maxWindow && d->maxWindow->windowWidget()) + d->maxWindow->windowWidget()->close(); + else if (d->active && d->active->windowWidget()) + d->active->windowWidget()->close(); + d->updateWorkspace(); +} + +/*! + Closes all child windows. + + If any child window fails to accept the close event, the remaining windows + will remain open. + + \sa closeActiveWindow() +*/ +void QWorkspace::closeAllWindows() +{ + Q_D(QWorkspace); + bool did_close = true; + QList<QWorkspaceChild *>::const_iterator it = d->windows.constBegin(); + while (it != d->windows.constEnd() && did_close) { + QWorkspaceChild *c = *it; + ++it; + if (c->windowWidget() && !c->windowWidget()->isHidden()) + did_close = c->windowWidget()->close(); + } +} + +void QWorkspacePrivate::_q_normalizeActiveWindow() +{ + if (maxWindow) + maxWindow->showNormal(); + else if (active) + active->showNormal(); +} + +void QWorkspacePrivate::_q_minimizeActiveWindow() +{ + if (maxWindow) + maxWindow->showMinimized(); + else if (active) + active->showMinimized(); +} + +void QWorkspacePrivate::_q_showOperationMenu() +{ + Q_Q(QWorkspace); + if (!active || !active->windowWidget()) + return; + Q_ASSERT((active->windowWidget()->windowFlags() & Qt::WindowSystemMenuHint)); + QPoint p; + QMenu *popup = (active->titlebar && active->titlebar->isTool()) ? toolPopup : this->popup; + if (q->isRightToLeft()) { + p = QPoint(active->windowWidget()->mapToGlobal(QPoint(active->windowWidget()->width(),0))); + p.rx() -= popup->sizeHint().width(); + } else { + p = QPoint(active->windowWidget()->mapToGlobal(QPoint(0,0))); + } + if (!active->isVisible()) { + p = active->iconWidget()->mapToGlobal(QPoint(0,0)); + p.ry() -= popup->sizeHint().height(); + } + _q_popupOperationMenu(p); +} + +void QWorkspacePrivate::_q_popupOperationMenu(const QPoint& p) +{ + if (!active || !active->windowWidget() || !(active->windowWidget()->windowFlags() & Qt::WindowSystemMenuHint)) + return; + if (active->titlebar && active->titlebar->isTool()) + toolPopup->popup(p); + else + popup->popup(p); +} + +void QWorkspacePrivate::_q_updateActions() +{ + Q_Q(QWorkspace); + for (int i = 1; i < NCountAct-1; i++) { + bool enable = active != 0; + actions[i]->setEnabled(enable); + } + + if (!active || !active->windowWidget()) + return; + + QWidget *windowWidget = active->windowWidget(); + bool canResize = windowWidget->maximumSize() != windowWidget->minimumSize(); + actions[QWorkspacePrivate::ResizeAct]->setEnabled(canResize); + actions[QWorkspacePrivate::MinimizeAct]->setEnabled((windowWidget->windowFlags() & Qt::WindowMinimizeButtonHint)); + actions[QWorkspacePrivate::MaximizeAct]->setEnabled((windowWidget->windowFlags() & Qt::WindowMaximizeButtonHint) && canResize); + + if (active == maxWindow) { + actions[QWorkspacePrivate::MoveAct]->setEnabled(false); + actions[QWorkspacePrivate::ResizeAct]->setEnabled(false); + actions[QWorkspacePrivate::MaximizeAct]->setEnabled(false); + actions[QWorkspacePrivate::RestoreAct]->setEnabled(true); + } else if (active->isVisible()){ + actions[QWorkspacePrivate::RestoreAct]->setEnabled(false); + } else { + actions[QWorkspacePrivate::MoveAct]->setEnabled(false); + actions[QWorkspacePrivate::ResizeAct]->setEnabled(false); + actions[QWorkspacePrivate::MinimizeAct]->setEnabled(false); + actions[QWorkspacePrivate::RestoreAct]->setEnabled(true); + } + if (active->shademode) { + actions[QWorkspacePrivate::ShadeAct]->setIcon( + QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarUnshadeButton, 0, q))); + actions[QWorkspacePrivate::ShadeAct]->setText(QWorkspace::tr("&Unshade")); + } else { + actions[QWorkspacePrivate::ShadeAct]->setIcon( + QIcon(q->style()->standardPixmap(QStyle::SP_TitleBarShadeButton, 0, q))); + actions[QWorkspacePrivate::ShadeAct]->setText(QWorkspace::tr("Sh&ade")); + } + actions[QWorkspacePrivate::StaysOnTopAct]->setEnabled(!active->shademode && canResize); + actions[QWorkspacePrivate::StaysOnTopAct]->setChecked( + (active->windowWidget()->windowFlags() & Qt::WindowStaysOnTopHint)); +} + +void QWorkspacePrivate::_q_operationMenuActivated(QAction *action) +{ + if (!active) + return; + if(action == actions[QWorkspacePrivate::RestoreAct]) { + active->showNormal(); + } else if(action == actions[QWorkspacePrivate::MoveAct]) { + active->doMove(); + } else if(action == actions[QWorkspacePrivate::ResizeAct]) { + if (active->shademode) + active->showShaded(); + active->doResize(); + } else if(action == actions[QWorkspacePrivate::MinimizeAct]) { + active->showMinimized(); + } else if(action == actions[QWorkspacePrivate::MaximizeAct]) { + active->showMaximized(); + } else if(action == actions[QWorkspacePrivate::ShadeAct]) { + active->showShaded(); + } else if(action == actions[QWorkspacePrivate::StaysOnTopAct]) { + if(QWidget* w = active->windowWidget()) { + if ((w->windowFlags() & Qt::WindowStaysOnTopHint)) { + w->overrideWindowFlags(w->windowFlags() & ~Qt::WindowStaysOnTopHint); + } else { + w->overrideWindowFlags(w->windowFlags() | Qt::WindowStaysOnTopHint); + w->parentWidget()->raise(); + } + } + } +} + + +void QWorkspacePrivate::hideChild(QWorkspaceChild *c) +{ + Q_Q(QWorkspace); + +// bool updatesEnabled = q->updatesEnabled(); +// q->setUpdatesEnabled(false); + focus.removeAll(c); + QRect restore; + if (maxWindow == c) + restore = maxRestore; + if (active == c) { + q->setFocus(); + q->activatePreviousWindow(); + } + if (active == c) + activateWindow(0); + if (maxWindow == c) { + hideMaximizeControls(); + maxWindow = 0; + } + c->hide(); + if (!restore.isEmpty()) + c->setGeometry(restore); +// q->setUpdatesEnabled(updatesEnabled); +} + +/*! + Gives the input focus to the next window in the list of child + windows. + + \sa activatePreviousWindow() +*/ +void QWorkspace::activateNextWindow() +{ + Q_D(QWorkspace); + + if (d->focus.isEmpty()) + return; + if (!d->active) { + if (d->focus.first()) + d->activateWindow(d->focus.first()->windowWidget(), false); + return; + } + + int a = d->focus.indexOf(d->active) + 1; + + a = a % d->focus.count(); + + if (d->focus.at(a)) + d->activateWindow(d->focus.at(a)->windowWidget(), false); + else + d->activateWindow(0); +} + +/*! + Gives the input focus to the previous window in the list of child + windows. + + \sa activateNextWindow() +*/ +void QWorkspace::activatePreviousWindow() +{ + Q_D(QWorkspace); + + if (d->focus.isEmpty()) + return; + if (!d->active) { + if (d->focus.last()) + d->activateWindow(d->focus.first()->windowWidget(), false); + else + d->activateWindow(0); + return; + } + + int a = d->focus.indexOf(d->active) - 1; + if (a < 0) + a = d->focus.count()-1; + + if (d->focus.at(a)) + d->activateWindow(d->focus.at(a)->windowWidget(), false); + else + d->activateWindow(0); +} + + +/*! + \fn void QWorkspace::windowActivated(QWidget* w) + + This signal is emitted when the child window \a w becomes active. + Note that \a w can be 0, and that more than one signal may be + emitted for a single activation event. + + \sa activeWindow(), windowList() +*/ + +/*! + Arranges all the child windows in a cascade pattern. + + \sa tile(), arrangeIcons() +*/ +void QWorkspace::cascade() +{ + Q_D(QWorkspace); + blockSignals(true); + if (d->maxWindow) + d->maxWindow->showNormal(); + + if (d->vbar) { + d->vbar->blockSignals(true); + d->vbar->setValue(0); + d->vbar->blockSignals(false); + d->hbar->blockSignals(true); + d->hbar->setValue(0); + d->hbar->blockSignals(false); + d->_q_scrollBarChanged(); + } + + const int xoffset = 13; + const int yoffset = 20; + + // make a list of all relevant mdi clients + QList<QWorkspaceChild *> widgets; + QList<QWorkspaceChild *>::Iterator it(d->windows.begin()); + QWorkspaceChild* wc = 0; + + for (it = d->focus.begin(); it != d->focus.end(); ++it) { + wc = *it; + if (wc->windowWidget()->isVisibleTo(this) && !(wc->titlebar && wc->titlebar->isTool())) + widgets.append(wc); + } + + int x = 0; + int y = 0; + + it = widgets.begin(); + while (it != widgets.end()) { + QWorkspaceChild *child = *it; + ++it; + + QSize prefSize = child->windowWidget()->sizeHint().expandedTo(qSmartMinSize(child->windowWidget())); + if (!prefSize.isValid()) + prefSize = child->windowWidget()->size(); + prefSize = prefSize.expandedTo(qSmartMinSize(child->windowWidget())); + if (prefSize.isValid()) + prefSize += QSize(child->baseSize().width(), child->baseSize().height()); + + int w = prefSize.width(); + int h = prefSize.height(); + + child->showNormal(); + if (y + h > height()) + y = 0; + if (x + w > width()) + x = 0; + child->setGeometry(x, y, w, h); + x += xoffset; + y += yoffset; + child->internalRaise(); + } + d->updateWorkspace(); + blockSignals(false); +} + +/*! + Arranges all child windows in a tile pattern. + + \sa cascade(), arrangeIcons() +*/ +void QWorkspace::tile() +{ + Q_D(QWorkspace); + blockSignals(true); + QWidget *oldActive = d->active ? d->active->windowWidget() : 0; + if (d->maxWindow) + d->maxWindow->showNormal(); + + if (d->vbar) { + d->vbar->blockSignals(true); + d->vbar->setValue(0); + d->vbar->blockSignals(false); + d->hbar->blockSignals(true); + d->hbar->setValue(0); + d->hbar->blockSignals(false); + d->_q_scrollBarChanged(); + } + + int rows = 1; + int cols = 1; + int n = 0; + QWorkspaceChild* c; + + QList<QWorkspaceChild *>::Iterator it(d->windows.begin()); + while (it != d->windows.end()) { + c = *it; + ++it; + if (!c->windowWidget()->isHidden() + && !(c->windowWidget()->windowFlags() & Qt::WindowStaysOnTopHint) + && !c->iconw) + n++; + } + + while (rows * cols < n) { + if (cols <= rows) + cols++; + else + rows++; + } + int add = cols * rows - n; + bool* used = new bool[cols*rows]; + for (int i = 0; i < rows*cols; i++) + used[i] = false; + + int row = 0; + int col = 0; + int w = width() / cols; + int h = height() / rows; + + it = d->windows.begin(); + while (it != d->windows.end()) { + c = *it; + ++it; + if (c->iconw || c->windowWidget()->isHidden() || (c->titlebar && c->titlebar->isTool())) + continue; + if (!row && !col) { + w -= c->baseSize().width(); + h -= c->baseSize().height(); + } + if ((c->windowWidget()->windowFlags() & Qt::WindowStaysOnTopHint)) { + QPoint p = c->pos(); + if (p.x()+c->width() < 0) + p.setX(0); + if (p.x() > width()) + p.setX(width() - c->width()); + if (p.y() + 10 < 0) + p.setY(0); + if (p.y() > height()) + p.setY(height() - c->height()); + + if (p != c->pos()) + c->QWidget::move(p); + } else { + c->showNormal(); + used[row*cols+col] = true; + QSize sz(w, h); + QSize bsize(c->baseSize()); + sz = sz.expandedTo(c->windowWidget()->minimumSize()).boundedTo(c->windowWidget()->maximumSize()); + sz += bsize; + + if ( add ) { + if (sz.height() == h + bsize.height()) // no relevant constrains + sz.rheight() *= 2; + used[(row+1)*cols+col] = true; + add--; + } + + c->setGeometry(col*w + col*bsize.width(), row*h + row*bsize.height(), sz.width(), sz.height()); + + while(row < rows && col < cols && used[row*cols+col]) { + col++; + if (col == cols) { + col = 0; + row++; + } + } + } + } + delete [] used; + + d->activateWindow(oldActive); + d->updateWorkspace(); + blockSignals(false); +} + +/*! + Arranges all iconified windows at the bottom of the workspace. + + \sa cascade(), tile() +*/ +void QWorkspace::arrangeIcons() +{ + Q_D(QWorkspace); + + QRect cr = d->updateWorkspace(); + int x = 0; + int y = -1; + + QList<QWidget *>::Iterator it(d->icons.begin()); + while (it != d->icons.end()) { + QWidget* i = *it; + if (y == -1) + y = cr.height() - i->height(); + if (x > 0 && x + i->width() > cr.width()) { + x = 0; + y -= i->height(); + } + i->move(x, y); + x += i->width(); + ++it; + } + d->updateWorkspace(); +} + + +QWorkspaceChild::QWorkspaceChild(QWidget* window, QWorkspace *parent, Qt::WindowFlags flags) + : QWidget(parent, + Qt::FramelessWindowHint | Qt::SubWindow) +{ + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_NoMousePropagation); + setMouseTracking(true); + act = false; + iconw = 0; + shademode = false; + titlebar = 0; + setAutoFillBackground(true); + + setBackgroundRole(QPalette::Window); + if (window) { + flags |= (window->windowFlags() & Qt::MSWindowsOwnDC); + if (flags) + window->setParent(this, flags & ~Qt::WindowType_Mask); + else + window->setParent(this); + } + + if (window && (flags & (Qt::WindowTitleHint + | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowContextHelpButtonHint))) { + titlebar = new QWorkspaceTitleBar(window, this, flags); + connect(titlebar, SIGNAL(doActivate()), + this, SLOT(activate())); + connect(titlebar, SIGNAL(doClose()), + window, SLOT(close())); + connect(titlebar, SIGNAL(doMinimize()), + this, SLOT(showMinimized())); + connect(titlebar, SIGNAL(doNormal()), + this, SLOT(showNormal())); + connect(titlebar, SIGNAL(doMaximize()), + this, SLOT(showMaximized())); + connect(titlebar, SIGNAL(popupOperationMenu(QPoint)), + this, SIGNAL(popupOperationMenu(QPoint))); + connect(titlebar, SIGNAL(showOperationMenu()), + this, SIGNAL(showOperationMenu())); + connect(titlebar, SIGNAL(doShade()), + this, SLOT(showShaded())); + connect(titlebar, SIGNAL(doubleClicked()), + this, SLOT(titleBarDoubleClicked())); + } + + setMinimumSize(128, 0); + int fw = style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, this); + setContentsMargins(fw, fw, fw, fw); + + childWidget = window; + if (!childWidget) + return; + + setWindowTitle(childWidget->windowTitle()); + + QPoint p; + QSize s; + QSize cs; + + bool hasBeenResized = childWidget->testAttribute(Qt::WA_Resized); + + if (!hasBeenResized) + cs = childWidget->sizeHint().expandedTo(childWidget->minimumSizeHint()).expandedTo(childWidget->minimumSize()).boundedTo(childWidget->maximumSize()); + else + cs = childWidget->size(); + + windowSize = cs; + + int th = titlebar ? titlebar->sizeHint().height() : 0; + if (titlebar) { + if (!childWidget->windowIcon().isNull()) + titlebar->setWindowIcon(childWidget->windowIcon()); + + if (style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) + th -= contentsRect().y(); + + p = QPoint(contentsRect().x(), + th + contentsRect().y()); + s = QSize(cs.width() + 2*frameWidth(), + cs.height() + 2*frameWidth() + th); + } else { + p = QPoint(contentsRect().x(), contentsRect().y()); + s = QSize(cs.width() + 2*frameWidth(), + cs.height() + 2*frameWidth()); + } + + childWidget->move(p); + resize(s); + + childWidget->installEventFilter(this); + + widgetResizeHandler = new QWidgetResizeHandler(this, window); + widgetResizeHandler->setSizeProtection(!parent->scrollBarsEnabled()); + widgetResizeHandler->setFrameWidth(frameWidth()); + connect(widgetResizeHandler, SIGNAL(activate()), + this, SLOT(activate())); + if (!style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) + widgetResizeHandler->setExtraHeight(th + contentsRect().y() - 2*frameWidth()); + else + widgetResizeHandler->setExtraHeight(th + contentsRect().y() - frameWidth()); + if (childWidget->minimumSize() == childWidget->maximumSize()) + widgetResizeHandler->setActive(QWidgetResizeHandler::Resize, false); + setBaseSize(baseSize()); +} + +QWorkspaceChild::~QWorkspaceChild() +{ + QWorkspace *workspace = qobject_cast<QWorkspace*>(parentWidget()); + if (iconw) { + if (workspace) + workspace->d_func()->removeIcon(iconw->parentWidget()); + delete iconw->parentWidget(); + } + + if (workspace) { + workspace->d_func()->focus.removeAll(this); + if (workspace->d_func()->active == this) + workspace->activatePreviousWindow(); + if (workspace->d_func()->active == this) + workspace->d_func()->activateWindow(0); + if (workspace->d_func()->maxWindow == this) { + workspace->d_func()->hideMaximizeControls(); + workspace->d_func()->maxWindow = 0; + } + } +} + +void QWorkspaceChild::moveEvent(QMoveEvent *) +{ + ((QWorkspace*)parentWidget())->d_func()->updateWorkspace(); +} + +void QWorkspaceChild::resizeEvent(QResizeEvent *) +{ + bool wasMax = isMaximized(); + QRect r = contentsRect(); + QRect cr; + + updateMask(); + + if (titlebar) { + int th = titlebar->sizeHint().height(); + QRect tbrect(0, 0, width(), th); + if (!style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) + tbrect = QRect(r.x(), r.y(), r.width(), th); + titlebar->setGeometry(tbrect); + + if (style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) + th -= frameWidth(); + cr = QRect(r.x(), r.y() + th + (shademode ? (frameWidth() * 3) : 0), + r.width(), r.height() - th); + } else { + cr = r; + } + + if (!childWidget) + return; + + bool doContentsResize = (windowSize == childWidget->size() + || !(childWidget->testAttribute(Qt::WA_Resized) && childWidget->testAttribute(Qt::WA_PendingResizeEvent)) + ||childWidget->isMaximized()); + + windowSize = cr.size(); + childWidget->move(cr.topLeft()); + if (doContentsResize) + childWidget->resize(cr.size()); + ((QWorkspace*)parentWidget())->d_func()->updateWorkspace(); + + if (wasMax) { + overrideWindowState(Qt::WindowMaximized); + childWidget->overrideWindowState(Qt::WindowMaximized); + } +} + +QSize QWorkspaceChild::baseSize() const +{ + int th = titlebar ? titlebar->sizeHint().height() : 0; + if (style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) + th -= frameWidth(); + return QSize(2*frameWidth(), 2*frameWidth() + th); +} + +QSize QWorkspaceChild::sizeHint() const +{ + if (!childWidget) + return QWidget::sizeHint() + baseSize(); + + QSize prefSize = windowWidget()->sizeHint().expandedTo(windowWidget()->minimumSizeHint()); + prefSize = prefSize.expandedTo(windowWidget()->minimumSize()).boundedTo(windowWidget()->maximumSize()); + prefSize += baseSize(); + + return prefSize; +} + +QSize QWorkspaceChild::minimumSizeHint() const +{ + if (!childWidget) + return QWidget::minimumSizeHint() + baseSize(); + QSize s = childWidget->minimumSize(); + if (s.isEmpty()) + s = childWidget->minimumSizeHint(); + return s + baseSize(); +} + +void QWorkspaceChild::activate() +{ + ((QWorkspace*)parentWidget())->d_func()->activateWindow(windowWidget()); +} + +bool QWorkspaceChild::eventFilter(QObject * o, QEvent * e) +{ + if (!isActive() + && (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::FocusIn)) { + if (iconw) { + ((QWorkspace*)parentWidget())->d_func()->normalizeWindow(windowWidget()); + if (iconw) { + ((QWorkspace*)parentWidget())->d_func()->removeIcon(iconw->parentWidget()); + delete iconw->parentWidget(); + iconw = 0; + } + } + activate(); + } + + // for all widgets except the window, that's the only thing we + // process, and if we have no childWidget we skip totally + if (o != childWidget || childWidget == 0) + return false; + + switch (e->type()) { + case QEvent::ShowToParent: + if (((QWorkspace*)parentWidget())->d_func()->focus.indexOf(this) < 0) + ((QWorkspace*)parentWidget())->d_func()->focus.append(this); + + if (windowWidget() && (windowWidget()->windowFlags() & Qt::WindowStaysOnTopHint)) { + internalRaise(); + show(); + } + ((QWorkspace*)parentWidget())->d_func()->showWindow(windowWidget()); + break; + case QEvent::WindowStateChange: { + if (static_cast<QWindowStateChangeEvent*>(e)->isOverride()) + break; + Qt::WindowStates state = windowWidget()->windowState(); + + if (state & Qt::WindowMinimized) { + ((QWorkspace*)parentWidget())->d_func()->minimizeWindow(windowWidget()); + } else if (state & Qt::WindowMaximized) { + if (windowWidget()->maximumSize().isValid() && + (windowWidget()->maximumWidth() < parentWidget()->width() || + windowWidget()->maximumHeight() < parentWidget()->height())) { + windowWidget()->resize(windowWidget()->maximumSize()); + windowWidget()->overrideWindowState(Qt::WindowNoState); + if (titlebar) + titlebar->update(); + break; + } + if ((windowWidget()->windowFlags() & Qt::WindowMaximizeButtonHint)) + ((QWorkspace*)parentWidget())->d_func()->maximizeWindow(windowWidget()); + else + ((QWorkspace*)parentWidget())->d_func()->normalizeWindow(windowWidget()); + } else { + ((QWorkspace*)parentWidget())->d_func()->normalizeWindow(windowWidget()); + if (iconw) { + ((QWorkspace*)parentWidget())->d_func()->removeIcon(iconw->parentWidget()); + delete iconw->parentWidget(); + } + } + } break; + case QEvent::HideToParent: + { + QWidget * w = iconw; + if (w && (w = w->parentWidget())) { + ((QWorkspace*)parentWidget())->d_func()->removeIcon(w); + delete w; + } + ((QWorkspace*)parentWidget())->d_func()->hideChild(this); + } break; + case QEvent::WindowIconChange: + { + QWorkspace* ws = (QWorkspace*)parentWidget(); + if (ws->d_func()->maxtools && ws->d_func()->maxWindow == this) { + int iconSize = ws->d_func()->maxtools->size().height(); + ws->d_func()->maxtools->setPixmap(childWidget->windowIcon().pixmap(QSize(iconSize, iconSize))); + } + } + // fall through + case QEvent::WindowTitleChange: + setWindowTitle(windowWidget()->windowTitle()); + if (titlebar) + titlebar->update(); + if (iconw) + iconw->update(); + break; + case QEvent::ModifiedChange: + setWindowModified(windowWidget()->isWindowModified()); + if (titlebar) + titlebar->update(); + if (iconw) + iconw->update(); + break; + case QEvent::Resize: + { + QResizeEvent* re = (QResizeEvent*)e; + if (re->size() != windowSize && !shademode) { + resize(re->size() + baseSize()); + childWidget->update(); //workaround + } + } + break; + + case QEvent::WindowDeactivate: + if (titlebar && titlebar->isActive()) { + update(); + } + break; + + case QEvent::WindowActivate: + if (titlebar && titlebar->isActive()) { + update(); + } + break; + + default: + break; + } + + return QWidget::eventFilter(o, e); +} + +void QWorkspaceChild::childEvent(QChildEvent* e) +{ + if (e->type() == QEvent::ChildRemoved && e->child() == childWidget) { + childWidget = 0; + if (iconw) { + ((QWorkspace*)parentWidget())->d_func()->removeIcon(iconw->parentWidget()); + delete iconw->parentWidget(); + } + close(); + } +} + + +void QWorkspaceChild::doResize() +{ + widgetResizeHandler->doResize(); +} + +void QWorkspaceChild::doMove() +{ + widgetResizeHandler->doMove(); +} + +void QWorkspaceChild::enterEvent(QEvent *) +{ +} + +void QWorkspaceChild::leaveEvent(QEvent *) +{ +#ifndef QT_NO_CURSOR + if (!widgetResizeHandler->isButtonDown()) + setCursor(Qt::ArrowCursor); +#endif +} + +void QWorkspaceChild::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QStyleOptionFrame opt; + opt.rect = rect(); + opt.palette = palette(); + opt.state = QStyle::State_None; + opt.lineWidth = style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, this); + opt.midLineWidth = 1; + + if (titlebar && titlebar->isActive() && isActiveWindow()) + opt.state |= QStyle::State_Active; + + style()->drawPrimitive(QStyle::PE_FrameWindow, &opt, &p, this); +} + +void QWorkspaceChild::changeEvent(QEvent *ev) +{ + if(ev->type() == QEvent::StyleChange) { + resizeEvent(0); + if (iconw) { + QFrame *frame = qobject_cast<QFrame*>(iconw->parentWidget()); + Q_ASSERT(frame); + if (!style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) { + frame->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); + frame->resize(196+2*frame->frameWidth(), 20 + 2*frame->frameWidth()); + } else { + frame->resize(196, 20); + } + } + updateMask(); + } + QWidget::changeEvent(ev); +} + +void QWorkspaceChild::setActive(bool b) +{ + if (!childWidget) + return; + + bool hasFocus = isChildOf(window()->focusWidget(), this); + if (act == b && (act == hasFocus)) + return; + + act = b; + + if (titlebar) + titlebar->setActive(act); + if (iconw) + iconw->setActive(act); + update(); + + QList<QWidget*> wl = qFindChildren<QWidget*>(childWidget); + if (act) { + for (int i = 0; i < wl.size(); ++i) { + QWidget *w = wl.at(i); + w->removeEventFilter(this); + } + if (!hasFocus) { + QWidget *lastfocusw = childWidget->focusWidget(); + if (lastfocusw && lastfocusw->focusPolicy() != Qt::NoFocus) { + lastfocusw->setFocus(); + } else if (childWidget->focusPolicy() != Qt::NoFocus) { + childWidget->setFocus(); + } else { + // find something, anything, that accepts focus, and use that. + for (int i = 0; i < wl.size(); ++i) { + QWidget *w = wl.at(i); + if(w->focusPolicy() != Qt::NoFocus) { + w->setFocus(); + hasFocus = true; + break; + } + } + if (!hasFocus) + setFocus(); + } + } + } else { + for (int i = 0; i < wl.size(); ++i) { + QWidget *w = wl.at(i); + w->removeEventFilter(this); + w->installEventFilter(this); + } + } +} + +bool QWorkspaceChild::isActive() const +{ + return act; +} + +QWidget* QWorkspaceChild::windowWidget() const +{ + return childWidget; +} + +bool QWorkspaceChild::isWindowOrIconVisible() const +{ + return childWidget && (!isHidden() || (iconw && !iconw->isHidden())); +} + +void QWorkspaceChild::updateMask() +{ + QStyleOptionTitleBar titleBarOptions; + titleBarOptions.rect = rect(); + titleBarOptions.titleBarFlags = windowFlags(); + titleBarOptions.titleBarState = windowState(); + + QStyleHintReturnMask frameMask; + if (style()->styleHint(QStyle::SH_WindowFrame_Mask, &titleBarOptions, this, &frameMask)) { + setMask(frameMask.region); + } else if (!mask().isEmpty()) { + clearMask(); + } + + if (iconw) { + QFrame *frame = qobject_cast<QFrame *>(iconw->parentWidget()); + Q_ASSERT(frame); + + titleBarOptions.rect = frame->rect(); + titleBarOptions.titleBarFlags = frame->windowFlags(); + titleBarOptions.titleBarState = frame->windowState() | Qt::WindowMinimized; + if (style()->styleHint(QStyle::SH_WindowFrame_Mask, &titleBarOptions, frame, &frameMask)) { + frame->setMask(frameMask.region); + } else if (!frame->mask().isEmpty()) { + frame->clearMask(); + } + } +} + +QWidget* QWorkspaceChild::iconWidget() const +{ + if (!iconw) { + QWorkspaceChild* that = (QWorkspaceChild*) this; + + QFrame* frame = new QFrame(that, Qt::Window); + QVBoxLayout *vbox = new QVBoxLayout(frame); + vbox->setMargin(0); + QWorkspaceTitleBar *tb = new QWorkspaceTitleBar(windowWidget(), frame); + vbox->addWidget(tb); + tb->setObjectName(QLatin1String("_workspacechild_icon_")); + QStyleOptionTitleBar opt; + tb->initStyleOption(&opt); + int th = style()->pixelMetric(QStyle::PM_TitleBarHeight, &opt, tb); + int iconSize = style()->pixelMetric(QStyle::PM_MdiSubWindowMinimizedWidth, 0, this); + if (!style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) { + frame->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); + frame->resize(iconSize+2*frame->frameWidth(), th+2*frame->frameWidth()); + } else { + frame->resize(iconSize, th); + } + + that->iconw = tb; + that->updateMask(); + iconw->setActive(isActive()); + + connect(iconw, SIGNAL(doActivate()), + this, SLOT(activate())); + connect(iconw, SIGNAL(doClose()), + windowWidget(), SLOT(close())); + connect(iconw, SIGNAL(doNormal()), + this, SLOT(showNormal())); + connect(iconw, SIGNAL(doMaximize()), + this, SLOT(showMaximized())); + connect(iconw, SIGNAL(popupOperationMenu(QPoint)), + this, SIGNAL(popupOperationMenu(QPoint))); + connect(iconw, SIGNAL(showOperationMenu()), + this, SIGNAL(showOperationMenu())); + connect(iconw, SIGNAL(doubleClicked()), + this, SLOT(titleBarDoubleClicked())); + } + if (windowWidget()) { + iconw->setWindowTitle(windowWidget()->windowTitle()); + } + return iconw->parentWidget(); +} + +void QWorkspaceChild::showMinimized() +{ + windowWidget()->setWindowState(Qt::WindowMinimized | (windowWidget()->windowState() & ~Qt::WindowMaximized)); +} + +void QWorkspaceChild::showMaximized() +{ + windowWidget()->setWindowState(Qt::WindowMaximized | (windowWidget()->windowState() & ~Qt::WindowMinimized)); +} + +void QWorkspaceChild::showNormal() +{ + windowWidget()->setWindowState(windowWidget()->windowState() & ~(Qt::WindowMinimized|Qt::WindowMaximized)); +} + +void QWorkspaceChild::showShaded() +{ + if (!titlebar) + return; + ((QWorkspace*)parentWidget())->d_func()->activateWindow(windowWidget()); + QWidget* w = windowWidget(); + if (shademode) { + w->overrideWindowState(Qt::WindowNoState); + overrideWindowState(Qt::WindowNoState); + + shademode = false; + resize(shadeRestore.expandedTo(minimumSizeHint())); + setMinimumSize(shadeRestoreMin); + style()->polish(this); + } else { + shadeRestore = size(); + shadeRestoreMin = minimumSize(); + setMinimumHeight(0); + shademode = true; + w->overrideWindowState(Qt::WindowMinimized); + overrideWindowState(Qt::WindowMinimized); + + if (style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar)) + resize(width(), titlebar->height()); + else + resize(width(), titlebar->height() + 2*frameWidth() + 1); + style()->polish(this); + } + titlebar->update(); +} + +void QWorkspaceChild::titleBarDoubleClicked() +{ + if (!windowWidget()) + return; + if (iconw) + showNormal(); + else if (windowWidget()->windowFlags() & Qt::WindowShadeButtonHint) + showShaded(); + else if (windowWidget()->windowFlags() & Qt::WindowMaximizeButtonHint) + showMaximized(); +} + +void QWorkspaceChild::adjustToFullscreen() +{ + if (!childWidget) + return; + + if(!((QWorkspace*)parentWidget())->d_func()->maxmenubar || style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, this)) { + setGeometry(parentWidget()->rect()); + } else { + int fw = style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, this); + bool noBorder = style()->styleHint(QStyle::SH_TitleBar_NoBorder, 0, titlebar); + int th = titlebar ? titlebar->sizeHint().height() : 0; + int w = parentWidget()->width() + 2*fw; + int h = parentWidget()->height() + (noBorder ? fw : 2*fw) + th; + w = qMax(w, childWidget->minimumWidth()); + h = qMax(h, childWidget->minimumHeight()); + setGeometry(-fw, (noBorder ? 0 : -fw) - th, w, h); + } + childWidget->overrideWindowState(Qt::WindowMaximized); + overrideWindowState(Qt::WindowMaximized); +} + +void QWorkspaceChild::internalRaise() +{ + + QWidget *stackUnderWidget = 0; + if (!windowWidget() || (windowWidget()->windowFlags() & Qt::WindowStaysOnTopHint) == 0) { + + QList<QWorkspaceChild *>::Iterator it(((QWorkspace*)parent())->d_func()->windows.begin()); + while (it != ((QWorkspace*)parent())->d_func()->windows.end()) { + QWorkspaceChild* c = *it; + ++it; + if (c->windowWidget() && + !c->windowWidget()->isHidden() && + (c->windowWidget()->windowFlags() & Qt::WindowStaysOnTopHint)) { + if (stackUnderWidget) + c->stackUnder(stackUnderWidget); + else + c->raise(); + stackUnderWidget = c; + } + } + } + + if (stackUnderWidget) { + if (iconw) + iconw->parentWidget()->stackUnder(stackUnderWidget); + stackUnder(stackUnderWidget); + } else { + if (iconw) + iconw->parentWidget()->raise(); + raise(); + } + +} + +void QWorkspaceChild::show() +{ + if (childWidget && childWidget->isHidden()) + childWidget->show(); + QWidget::show(); +} + +bool QWorkspace::scrollBarsEnabled() const +{ + Q_D(const QWorkspace); + return d->vbar != 0; +} + +/*! + \property QWorkspace::scrollBarsEnabled + \brief whether the workspace provides scroll bars + + If this property is true, the workspace will provide scroll bars if any + of the child windows extend beyond the edges of the visible + workspace. The workspace area will automatically increase to + contain child windows if they are resized beyond the right or + bottom edges of the visible area. + + If this property is false (the default), resizing child windows + out of the visible area of the workspace is not permitted, although + it is still possible to position them partially outside the visible area. +*/ +void QWorkspace::setScrollBarsEnabled(bool enable) +{ + Q_D(QWorkspace); + if ((d->vbar != 0) == enable) + return; + + d->xoffset = d->yoffset = 0; + if (enable) { + d->vbar = new QScrollBar(Qt::Vertical, this); + d->vbar->setObjectName(QLatin1String("vertical scrollbar")); + connect(d->vbar, SIGNAL(valueChanged(int)), this, SLOT(_q_scrollBarChanged())); + d->hbar = new QScrollBar(Qt::Horizontal, this); + d->hbar->setObjectName(QLatin1String("horizontal scrollbar")); + connect(d->hbar, SIGNAL(valueChanged(int)), this, SLOT(_q_scrollBarChanged())); + d->corner = new QWidget(this); + d->corner->setBackgroundRole(QPalette::Window); + d->corner->setObjectName(QLatin1String("qt_corner")); + d->updateWorkspace(); + } else { + delete d->vbar; + delete d->hbar; + delete d->corner; + d->vbar = d->hbar = 0; + d->corner = 0; + } + + QList<QWorkspaceChild *>::Iterator it(d->windows.begin()); + while (it != d->windows.end()) { + QWorkspaceChild *child = *it; + ++it; + child->widgetResizeHandler->setSizeProtection(!enable); + } +} + +QRect QWorkspacePrivate::updateWorkspace() +{ + Q_Q(QWorkspace); + QRect cr(q->rect()); + + if (q->scrollBarsEnabled() && !maxWindow) { + corner->raise(); + vbar->raise(); + hbar->raise(); + if (maxWindow) + maxWindow->internalRaise(); + + QRect r(0, 0, 0, 0); + QList<QWorkspaceChild *>::Iterator it(windows.begin()); + while (it != windows.end()) { + QWorkspaceChild *child = *it; + ++it; + if (!child->isHidden()) + r = r.unite(child->geometry()); + } + vbar->blockSignals(true); + hbar->blockSignals(true); + + int hsbExt = hbar->sizeHint().height(); + int vsbExt = vbar->sizeHint().width(); + + + bool showv = yoffset || yoffset + r.bottom() - q->height() + 1 > 0 || yoffset + r.top() < 0; + bool showh = xoffset || xoffset + r.right() - q->width() + 1 > 0 || xoffset + r.left() < 0; + + if (showh && !showv) + showv = yoffset + r.bottom() - q->height() + hsbExt + 1 > 0; + if (showv && !showh) + showh = xoffset + r.right() - q->width() + vsbExt + 1 > 0; + + if (!showh) + hsbExt = 0; + if (!showv) + vsbExt = 0; + + if (showv) { + vbar->setSingleStep(qMax(q->height() / 12, 30)); + vbar->setPageStep(q->height() - hsbExt); + vbar->setMinimum(qMin(0, yoffset + qMin(0, r.top()))); + vbar->setMaximum(qMax(0, yoffset + qMax(0, r.bottom() - q->height() + hsbExt + 1))); + vbar->setGeometry(q->width() - vsbExt, 0, vsbExt, q->height() - hsbExt); + vbar->setValue(yoffset); + vbar->show(); + } else { + vbar->hide(); + } + + if (showh) { + hbar->setSingleStep(qMax(q->width() / 12, 30)); + hbar->setPageStep(q->width() - vsbExt); + hbar->setMinimum(qMin(0, xoffset + qMin(0, r.left()))); + hbar->setMaximum(qMax(0, xoffset + qMax(0, r.right() - q->width() + vsbExt + 1))); + hbar->setGeometry(0, q->height() - hsbExt, q->width() - vsbExt, hsbExt); + hbar->setValue(xoffset); + hbar->show(); + } else { + hbar->hide(); + } + + if (showh && showv) { + corner->setGeometry(q->width() - vsbExt, q->height() - hsbExt, vsbExt, hsbExt); + corner->show(); + } else { + corner->hide(); + } + + vbar->blockSignals(false); + hbar->blockSignals(false); + + cr.setRect(0, 0, q->width() - vsbExt, q->height() - hsbExt); + } + + QList<QWidget *>::Iterator ii(icons.begin()); + while (ii != icons.end()) { + QWidget* w = *ii; + ++ii; + int x = w->x(); + int y = w->y(); + bool m = false; + if (x+w->width() > cr.width()) { + m = true; + x = cr.width() - w->width(); + } + if (y+w->height() > cr.height()) { + y = cr.height() - w->height(); + m = true; + } + if (m) { + if (QWorkspaceChild *child = qobject_cast<QWorkspaceChild*>(w)) + child->move(x, y); + else + w->move(x, y); + } + } + + return cr; + +} + +void QWorkspacePrivate::_q_scrollBarChanged() +{ + int ver = yoffset - vbar->value(); + int hor = xoffset - hbar->value(); + yoffset = vbar->value(); + xoffset = hbar->value(); + + QList<QWorkspaceChild *>::Iterator it(windows.begin()); + while (it != windows.end()) { + QWorkspaceChild *child = *it; + ++it; + // we do not use move() due to the reimplementation in QWorkspaceChild + child->setGeometry(child->x() + hor, child->y() + ver, child->width(), child->height()); + } + updateWorkspace(); +} + +/*! + \enum QWorkspace::WindowOrder + + Specifies the order in which child windows are returned from windowList(). + + \value CreationOrder The windows are returned in the order of their creation + \value StackingOrder The windows are returned in the order of their stacking +*/ + +/*!\reimp */ +void QWorkspace::changeEvent(QEvent *ev) +{ + Q_D(QWorkspace); + if(ev->type() == QEvent::StyleChange) { + if (isVisible() && d->maxWindow && d->maxmenubar) { + if(style()->styleHint(QStyle::SH_Workspace_FillSpaceOnMaximize, 0, this)) { + d->hideMaximizeControls(); //hide any visible maximized controls + d->showMaximizeControls(); //updates the modification state as well + } + } + } + QWidget::changeEvent(ev); +} + +QT_END_NAMESPACE + +#include "moc_qworkspace.cpp" + +#include "qworkspace.moc" + +#endif // QT_NO_WORKSPACE diff --git a/src/gui/widgets/qworkspace.h b/src/gui/widgets/qworkspace.h new file mode 100644 index 0000000..41caf37 --- /dev/null +++ b/src/gui/widgets/qworkspace.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWORKSPACE_H +#define QWORKSPACE_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_WORKSPACE + +class QAction; +class QWorkspaceChild; +class QShowEvent; +class QWorkspacePrivate; + +class Q_GUI_EXPORT QWorkspace : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool scrollBarsEnabled READ scrollBarsEnabled WRITE setScrollBarsEnabled) + Q_PROPERTY(QBrush background READ background WRITE setBackground) + +public: + explicit QWorkspace(QWidget* parent=0); + ~QWorkspace(); + + enum WindowOrder { CreationOrder, StackingOrder }; + + QWidget* activeWindow() const; + QWidgetList windowList(WindowOrder order = CreationOrder) const; + + QWidget * addWindow(QWidget *w, Qt::WindowFlags flags = 0); + + QSize sizeHint() const; + + bool scrollBarsEnabled() const; + void setScrollBarsEnabled(bool enable); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QWorkspace(QWidget* parent, const char* name); + QT3_SUPPORT void setPaletteBackgroundColor(const QColor &); + QT3_SUPPORT void setPaletteBackgroundPixmap(const QPixmap &); +#endif + + void setBackground(const QBrush &background); + QBrush background() const; + +Q_SIGNALS: + void windowActivated(QWidget* w); + +public Q_SLOTS: + void setActiveWindow(QWidget *w); + void cascade(); + void tile(); + void arrangeIcons(); + void closeActiveWindow(); + void closeAllWindows(); + void activateNextWindow(); + void activatePreviousWindow(); + +protected: + bool event(QEvent *e); + void paintEvent(QPaintEvent *e); + void changeEvent(QEvent *); + void childEvent(QChildEvent *); + void resizeEvent(QResizeEvent *); + bool eventFilter(QObject *, QEvent *); + void showEvent(QShowEvent *e); + void hideEvent(QHideEvent *e); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *e); +#endif + +private: + Q_DECLARE_PRIVATE(QWorkspace) + Q_DISABLE_COPY(QWorkspace) + Q_PRIVATE_SLOT(d_func(), void _q_normalizeActiveWindow()) + Q_PRIVATE_SLOT(d_func(), void _q_minimizeActiveWindow()) + Q_PRIVATE_SLOT(d_func(), void _q_showOperationMenu()) + Q_PRIVATE_SLOT(d_func(), void _q_popupOperationMenu(const QPoint&)) + Q_PRIVATE_SLOT(d_func(), void _q_operationMenuActivated(QAction *)) + Q_PRIVATE_SLOT(d_func(), void _q_updateActions()) + Q_PRIVATE_SLOT(d_func(), void _q_scrollBarChanged()) + + friend class QWorkspaceChild; +}; + +#endif // QT_NO_WORKSPACE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWORKSPACE_H diff --git a/src/gui/widgets/widgets.pri b/src/gui/widgets/widgets.pri new file mode 100644 index 0000000..86dc453 --- /dev/null +++ b/src/gui/widgets/widgets.pri @@ -0,0 +1,162 @@ +# Qt widgets module + +HEADERS += \ + widgets/qbuttongroup.h \ + widgets/qabstractbutton.h \ + widgets/qabstractbutton_p.h \ + widgets/qabstractslider.h \ + widgets/qabstractslider_p.h \ + widgets/qabstractspinbox.h \ + widgets/qabstractspinbox_p.h \ + widgets/qcalendartextnavigator_p.h \ + widgets/qcalendarwidget.h \ + widgets/qcheckbox.h \ + widgets/qcombobox.h \ + widgets/qcombobox_p.h \ + widgets/qcommandlinkbutton.h \ + widgets/qdatetimeedit.h \ + widgets/qdatetimeedit_p.h \ + widgets/qdial.h \ + widgets/qdialogbuttonbox.h \ + widgets/qdockwidget.h \ + widgets/qdockwidget_p.h \ + widgets/qdockarealayout_p.h \ + widgets/qfontcombobox.h \ + widgets/qframe.h \ + widgets/qframe_p.h \ + widgets/qgroupbox.h \ + widgets/qlabel.h \ + widgets/qlabel_p.h \ + widgets/qlcdnumber.h \ + widgets/qlineedit.h \ + widgets/qlineedit_p.h \ + widgets/qmainwindow.h \ + widgets/qmainwindowlayout_p.h \ + widgets/qmdiarea.h \ + widgets/qmdiarea_p.h \ + widgets/qmdisubwindow.h \ + widgets/qmdisubwindow_p.h \ + widgets/qmenu.h \ + widgets/qmenubar.h \ + widgets/qmenudata.h \ + widgets/qprogressbar.h \ + widgets/qpushbutton.h \ + widgets/qpushbutton_p.h \ + widgets/qradiobutton.h \ + widgets/qrubberband.h \ + widgets/qscrollbar.h \ + widgets/qscrollarea_p.h \ + widgets/qsizegrip.h \ + widgets/qslider.h \ + widgets/qspinbox.h \ + widgets/qsplashscreen.h \ + widgets/qsplitter.h \ + widgets/qsplitter_p.h \ + widgets/qstackedwidget.h \ + widgets/qstatusbar.h \ + widgets/qtabbar.h \ + widgets/qtabbar_p.h \ + widgets/qtabwidget.h \ + widgets/qtextedit.h \ + widgets/qtextedit_p.h \ + widgets/qtextbrowser.h \ + widgets/qtoolbar.h \ + widgets/qtoolbar_p.h \ + widgets/qtoolbarlayout_p.h \ + widgets/qtoolbarextension_p.h \ + widgets/qtoolbarseparator_p.h \ + widgets/qtoolbox.h \ + widgets/qtoolbutton.h \ + widgets/qvalidator.h \ + widgets/qabstractscrollarea.h \ + widgets/qabstractscrollarea_p.h \ + widgets/qwidgetresizehandler_p.h \ + widgets/qfocusframe.h \ + widgets/qscrollarea.h \ + widgets/qworkspace.h \ + widgets/qwidgetanimator_p.h \ + widgets/qtoolbararealayout_p.h \ + widgets/qplaintextedit.h \ + widgets/qplaintextedit_p.h \ + widgets/qprintpreviewwidget.h + +SOURCES += \ + widgets/qabstractbutton.cpp \ + widgets/qabstractslider.cpp \ + widgets/qabstractspinbox.cpp \ + widgets/qcalendarwidget.cpp \ + widgets/qcheckbox.cpp \ + widgets/qcombobox.cpp \ + widgets/qcommandlinkbutton.cpp \ + widgets/qdatetimeedit.cpp \ + widgets/qdial.cpp \ + widgets/qdialogbuttonbox.cpp \ + widgets/qdockwidget.cpp \ + widgets/qdockarealayout.cpp \ + widgets/qeffects.cpp \ + widgets/qfontcombobox.cpp \ + widgets/qframe.cpp \ + widgets/qgroupbox.cpp \ + widgets/qlabel.cpp \ + widgets/qlcdnumber.cpp \ + widgets/qlineedit.cpp \ + widgets/qmainwindow.cpp \ + widgets/qmainwindowlayout.cpp \ + widgets/qmdiarea.cpp \ + widgets/qmdisubwindow.cpp \ + widgets/qmenu.cpp \ + widgets/qmenubar.cpp \ + widgets/qmenudata.cpp \ + widgets/qprogressbar.cpp \ + widgets/qpushbutton.cpp \ + widgets/qradiobutton.cpp \ + widgets/qrubberband.cpp \ + widgets/qscrollbar.cpp \ + widgets/qsizegrip.cpp \ + widgets/qslider.cpp \ + widgets/qspinbox.cpp \ + widgets/qsplashscreen.cpp \ + widgets/qsplitter.cpp \ + widgets/qstackedwidget.cpp \ + widgets/qstatusbar.cpp \ + widgets/qtabbar.cpp \ + widgets/qtabwidget.cpp \ + widgets/qtextedit.cpp \ + widgets/qtextbrowser.cpp \ + widgets/qtoolbar.cpp \ + widgets/qtoolbarlayout.cpp \ + widgets/qtoolbarextension.cpp \ + widgets/qtoolbarseparator.cpp \ + widgets/qtoolbox.cpp \ + widgets/qtoolbutton.cpp \ + widgets/qvalidator.cpp \ + widgets/qabstractscrollarea.cpp \ + widgets/qwidgetresizehandler.cpp \ + widgets/qfocusframe.cpp \ + widgets/qscrollarea.cpp \ + widgets/qworkspace.cpp \ + widgets/qwidgetanimator.cpp \ + widgets/qtoolbararealayout.cpp \ + widgets/qplaintextedit.cpp \ + widgets/qprintpreviewwidget.cpp + + +!embedded:mac { + HEADERS += widgets/qmacnativewidget_mac.h \ + widgets/qmaccocoaviewcontainer_mac.h + OBJECTIVE_HEADERS += widgets/qcocoatoolbardelegate_mac_p.h \ + widgets/qcocoamenu_mac_p.h + OBJECTIVE_SOURCES += widgets/qmenu_mac.mm \ + widgets/qmaccocoaviewcontainer_mac.mm \ + widgets/qcocoatoolbardelegate_mac.mm \ + widgets/qmainwindowlayout_mac.mm \ + widgets/qmacnativewidget_mac.mm \ + widgets/qcocoamenu_mac.mm +} + +wince*: { + SOURCES += widgets/qmenu_wince.cpp + HEADERS += widgets/qmenu_wince_resource_p.h + RC_FILE = widgets/qmenu_wince.rc + !static: QMAKE_WRITE_DEFAULT_RC = 1 +} |