diff options
Diffstat (limited to 'src/gui/widgets/qtoolbarlayout.cpp')
-rw-r--r-- | src/gui/widgets/qtoolbarlayout.cpp | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/src/gui/widgets/qtoolbarlayout.cpp b/src/gui/widgets/qtoolbarlayout.cpp new file mode 100644 index 0000000000..7771f463a9 --- /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 |