/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWidgets module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #if QT_CONFIG(toolbutton) #include #endif #include #include #include #ifdef Q_OS_OSX #include #endif #include "qmainwindowlayout_p.h" #if QT_CONFIG(toolbutton) #include "qtoolbarextension_p.h" #endif #include "qtoolbarlayout_p.h" #include "qtoolbarseparator_p.h" QT_BEGIN_NAMESPACE // qmainwindow.cpp extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window); /****************************************************************************** ** QToolBarItem */ QToolBarItem::QToolBarItem(QWidget *widget) : QWidgetItem(widget), action(nullptr), customWidget(false) { } bool QToolBarItem::isEmpty() const { return action == nullptr || !action->isVisible(); } /****************************************************************************** ** QToolBarLayout */ QToolBarLayout::QToolBarLayout(QWidget *parent) : QLayout(parent), expanded(false), animating(false), dirty(true), expanding(false), empty(true), expandFlag(false), popupMenu(nullptr) { QToolBar *tb = qobject_cast(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(tb->parentWidget()) == 0); } QToolBarLayout::~QToolBarLayout() { while (!items.isEmpty()) { QToolBarItem *item = items.takeFirst(); if (QWidgetAction *widgetAction = qobject_cast(item->action)) { if (item->customWidget) widgetAction->releaseWidget(item->widget()); } delete item; } } void QToolBarLayout::updateMarginAndSpacing() { QToolBar *tb = qobject_cast(parentWidget()); if (!tb) return; QStyle *style = tb->style(); QStyleOptionToolBar opt; tb->initStyleOption(&opt); const int margin = style->pixelMetric(QStyle::PM_ToolBarItemMargin, &opt, tb) + style->pixelMetric(QStyle::PM_ToolBarFrameWidth, &opt, tb); setContentsMargins(margin, margin, margin, margin); setSpacing(style->pixelMetric(QStyle::PM_ToolBarItemSpacing, &opt, tb)); } bool QToolBarLayout::hasExpandFlag() const { return expandFlag; } void QToolBarLayout::setUsePopupMenu(bool set) { if (!dirty && ((popupMenu == nullptr) == set)) invalidate(); if (!set) { QObject::connect(extension, SIGNAL(clicked(bool)), this, SLOT(setExpanded(bool)), Qt::UniqueConnection); extension->setPopupMode(QToolButton::DelayedPopup); extension->setMenu(nullptr); delete popupMenu; popupMenu = nullptr; } 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(parent()); QMainWindow *mw = qobject_cast(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 nullptr; return items.at(index); } QLayoutItem *QToolBarLayout::takeAt(int index) { if (index < 0 || index >= items.count()) return nullptr; QToolBarItem *item = items.takeAt(index); if (popupMenu) popupMenu->removeAction(item->action); QWidgetAction *widgetAction = qobject_cast(item->action); if (widgetAction != nullptr && 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(const QGuiAction *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(parentWidget()); if (!tb) return {}; Qt::Orientation o = tb->orientation(); return expanding ? Qt::Orientations(o) : Qt::Orientations{}; } bool QToolBarLayout::movable() const { QToolBar *tb = qobject_cast(parentWidget()); if (!tb) return false; QMainWindow *win = qobject_cast(tb->parentWidget()); return tb->isMovable() && win != nullptr; } void QToolBarLayout::updateGeomArray() const { if (!dirty) return; QToolBarLayout *that = const_cast(this); QToolBar *tb = qobject_cast(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 QMargins margins = contentsMargins(); 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 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(pick(Qt::Horizontal, margins), pick(Qt::Vertical, margins)); if (items.count() > 1) rpick(o, that->minSize) += spacing + extensionExtent; rpick(o, that->hint) += handleExtent; that->hint += QSize(pick(Qt::Horizontal, margins), pick(Qt::Vertical, margins)); that->dirty = false; } static bool defaultWidgetAction(QToolBarItem *item) { QWidgetAction *a = qobject_cast(item->action); return a != nullptr && a->defaultWidget() == item->widget(); } void QToolBarLayout::updateMacBorderMetrics() { #ifdef Q_OS_OSX QToolBar *tb = qobject_cast(parentWidget()); if (!tb) return; QRect rect = geometry(); QMainWindow *mainWindow = qobject_cast(tb->parentWidget()); if (!mainWindow || !mainWindow->isWindow() || !mainWindow->unifiedTitleAndToolBarOnMac()) return; QPlatformNativeInterface *nativeInterface = QApplication::platformNativeInterface(); if (!nativeInterface) return; // Not Cocoa platform plugin. QPlatformNativeInterface::NativeResourceForIntegrationFunction function = nativeInterface->nativeResourceFunctionForIntegration("registerContentBorderArea"); if (!function) return; // Not Cocoa platform plugin. QPoint upper = tb->mapToParent(rect.topLeft()); QPoint lower = tb->mapToParent(rect.bottomLeft() + QPoint(0, 1)); typedef void (*RegisterContentBorderAreaFunction)(QWindow *window, void *identifier, int upper, int lower); if (mainWindow->toolBarArea(tb) == Qt::TopToolBarArea) { (reinterpret_cast(function))(tb->window()->windowHandle(), tb, upper.y(), lower.y()); } else { (reinterpret_cast(function))(tb->window()->windowHandle(), tb, 0, 0); } #endif } void QToolBarLayout::setGeometry(const QRect &rect) { QToolBar *tb = qobject_cast(parentWidget()); if (!tb) return; QStyle *style = tb->style(); QStyleOptionToolBar opt; tb->initStyleOption(&opt); const QMargins margins = contentsMargins(); const int extensionExtent = style->pixelMetric(QStyle::PM_ToolBarExtensionExtent, &opt, tb); Qt::Orientation o = tb->orientation(); QLayout::setGeometry(rect); updateMacBorderMetrics(); bool ranOutOfSpace = false; if (!animating) ranOutOfSpace = layoutActions(rect.size()); if (expanded || animating || ranOutOfSpace) { Qt::ToolBarArea area = Qt::TopToolBarArea; if (QMainWindow *win = qobject_cast(tb->parentWidget())) area = win->toolBarArea(tb); QSize hint = sizeHint(); QPoint pos; rpick(o, pos) = pick(o, rect.bottomRight()) - pick(o, QSize(margins.bottom(), margins.right())) - extensionExtent + 2; if (area == Qt::LeftToolBarArea || area == Qt::TopToolBarArea) rperp(o, pos) = perp(o, rect.topLeft()) + perp(o, QSize(margins.top(), margins.left())); else rperp(o, pos) = perp(o, rect.bottomRight()) - perp(o, QSize(margins.bottom(), margins.right())) - (perp(o, hint) - perp(o, margins)) + 1; QSize size; rpick(o, size) = extensionExtent; rperp(o, size) = perp(o, hint) - perp(o, margins); 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(); } } bool QToolBarLayout::layoutActions(const QSize &size) { if (dirty) updateGeomArray(); QRect rect(0, 0, size.width(), size.height()); QList showWidgets, hideWidgets; QToolBar *tb = qobject_cast(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 QMargins margins = contentsMargins(); 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()) - pick(o, margins) - 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()) + perp(o, QSize(margins.top(), margins.left())); int i = 0; while (i < items.count()) { QVector 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) = pick(o, QSize(margins.top(), margins.left())) + 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()) - perp(o, margins); 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 == nullptr || !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(parentWidget()); if (!tb) return QSize(0, 0); QMainWindow *win = qobject_cast(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 QMargins margins = contentsMargins(); 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 - pick(o, margins) - handleExtent); if (win != nullptr) space = qMin(space, pick(o, win->size()) - pick(o, margins) - 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 += pick(Qt::Horizontal, margins) + handleExtent + spacing + extensionExtent; w = qMax(w, min_w); if (win != nullptr) w = qMin(w, pick(o, win->size())); h += pick(Qt::Vertical, margins) - 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) { QWidget *tb = qobject_cast(parentWidget()); if (!tb) return; if (exp == expanded && !tb->isWindow()) return; expanded = exp; extension->setChecked(expanded); if (QMainWindow *win = qobject_cast(tb->parentWidget())) { #if !QT_CONFIG(dockwidget) animating = false; #else animating = !tb->isWindow() && win->isAnimated(); #endif QMainWindowLayout *layout = qt_mainwindow_layout(win); if (expanded) { tb->raise(); } else { QList path = layout->layoutState.indexOf(tb); if (!path.isEmpty()) { QRect rect = layout->layoutState.itemRect(path); layoutActions(rect.size()); } } layout->layoutState.toolBarAreaLayout.apply(animating); } } 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 = nullptr; QToolBar *tb = qobject_cast(parentWidget()); if (!tb) return (QToolBarItem *)nullptr; if (QWidgetAction *widgetAction = qobject_cast(action)) { widget = widgetAction->requestWidget(tb); if (widget != nullptr) { 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; } QT_END_NAMESPACE #include "moc_qtoolbarlayout_p.cpp"