11#include <QWidgetAction>
15#define RibbonGroupLayout_DEBUG_PRINT 0
16#define HELP_DRAW_RECT(p, rect) \
19 QPen _pen(Qt::DashLine); \
20 _pen.setColor(Qt::blue); \
22 p.setBrush(QBrush()); \
27RibbonGroupItem::RibbonGroupItem(QWidget *widget)
39class RibbonGroupLayoutPrivate
43 RibbonGroupLayoutPrivate();
46 void columnWidthInfo(
int colindex,
int &width,
int &maximum)
const;
49 void recalcExpandGeomArray(
const QRect &setrect);
52 void updateGeomArray(
const QRect &setrect);
55 QList<RibbonGroupItem *> m_items;
57 static QMargins s_contentsMargins;
64QMargins RibbonGroupLayoutPrivate::s_contentsMargins = QMargins(1, 1, 1, 1);
66RibbonGroupLayoutPrivate::RibbonGroupLayoutPrivate()
79void RibbonGroupLayoutPrivate::columnWidthInfo(
int colIndex,
int &width,
int &maximum)
const
83 for (RibbonGroupItem *item : std::as_const(m_items)) {
84 if (!item->isEmpty() && (item->columnIndex == colIndex)) {
85 width = qMax(width, item->willGeometry.width());
86 maximum = qMax(maximum, item->widget()->maximumWidth());
91void RibbonGroupLayoutPrivate::recalcExpandGeomArray(
const QRect &setrect)
94 int expandwidth = setrect.width() - m_sizeHint.width();
96 if (expandwidth <= 0) {
100 struct _columnExpandInfo {
101 int oldColumnWidth = 0;
102 int columnMaximumWidth = -1;
103 int columnExpandedWidth = 0;
104 QList<RibbonGroupItem *> expandItems;
107 QMap<int, _columnExpandInfo> columnExpandInfo;
109 for (RibbonGroupItem *item : std::as_const(m_items)) {
110 if ((!item->isEmpty()) && item->expandingDirections() & Qt::Horizontal) {
112 QMap<int, _columnExpandInfo>::iterator i = columnExpandInfo.find(item->columnIndex);
113 if (i == columnExpandInfo.end()) {
114 i = columnExpandInfo.insert(item->columnIndex, _columnExpandInfo());
116 i.value().expandItems.append(item);
119 if (columnExpandInfo.size() <= 0) {
125 int oneColCanExpandWidth = expandwidth / columnExpandInfo.size();
127 for (QMap<int, _columnExpandInfo>::iterator i = columnExpandInfo.begin(); i != columnExpandInfo.end();) {
128 int &oldColumnWidth = i.value().oldColumnWidth;
129 int &columnMaximumWidth = i.value().columnMaximumWidth;
130 columnWidthInfo(i.key(), oldColumnWidth, columnMaximumWidth);
131 if ((oldColumnWidth <= 0) || (oldColumnWidth > columnMaximumWidth)) {
134 i = columnExpandInfo.erase(i);
138 int colWidth = oneColCanExpandWidth + oldColumnWidth;
139 if (colWidth >= columnMaximumWidth) {
140 i.value().columnExpandedWidth = columnMaximumWidth;
142 i.value().columnExpandedWidth = colWidth;
148 for (
auto i = columnExpandInfo.begin(); i != columnExpandInfo.end(); ++i) {
149 int moveXLen = i.value().columnExpandedWidth - i.value().oldColumnWidth;
150 for (RibbonGroupItem *item : this->m_items) {
151 if (item->isEmpty() || (item->columnIndex < i.key())) {
155 if (item->columnIndex == i.key()) {
157 if (i.value().expandItems.contains(item)) {
159 item->willGeometry.setWidth(i.value().columnExpandedWidth);
166 item->willGeometry.moveLeft(item->willGeometry.x() + moveXLen);
186 Q_Q(RibbonGroupLayout);
187 bool customWidget =
false;
189 RibbonGroup *group = qobject_cast<RibbonGroup *>(q->parentWidget());
193 if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(action)) {
194 widget = widgetAction->requestWidget(group);
196 widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);
200 }
else if (action->isSeparator()) {
201 RibbonSeparator *sep =
new RibbonSeparator(group);
209 RibbonButton *
button =
new RibbonButton(group);
210 button->setAutoRaise(
true);
211 button->setFocusPolicy(Qt::NoFocus);
212 button->setButtonType(buttonType);
217 button->setDefaultAction(action);
224 RibbonGroupItem *result =
new RibbonGroupItem(widget);
226 result->rowProportion = rp;
227 result->customWidget = customWidget;
228 result->action = action;
235void RibbonGroupLayoutPrivate::updateGeomArray(
const QRect &setrect)
237 Q_Q(RibbonGroupLayout);
238 RibbonGroup *group = qobject_cast<RibbonGroup *>(q->parentWidget());
242#if RibbonGroupLayout_DEBUG_PRINT
243 qDebug() <<
"RibbonGroupLayoutPrivate::updateGeomArray(" << setrect <<
")";
245 int height = setrect.height();
246 const QMargins &margin = q->groupContentsMargins();
247 const int spacing = q->spacing();
248 int x = margin.left();
253 const int largeHeight = q->calcLargeHeight(setrect, group);
254 m_largeHeight = largeHeight;
256 const int smallHeight = (largeHeight - (rowCount - 1) * spacing) / rowCount;
263 const int mediumSpacing = (largeHeight - 2 * smallHeight) / 3;
264 const int yMediumRow0 = (2 == rowCount) ? margin.top() : (margin.top() + mediumSpacing);
265 const int yMediumRow1 =
266 (2 == rowCount) ? (yMediumRow0 + smallHeight + spacing) : (yMediumRow0 + smallHeight + mediumSpacing);
268 const int ySmallRow0 = margin.top();
269 const int ySmallRow1 = ySmallRow0 + smallHeight + spacing;
270 const int ySmallRow2 = ySmallRow1 + smallHeight + spacing;
281 int columnMaxWidth = 0;
285 int itemCount = m_items.count();
287#if RibbonGroupLayout_DEBUG_PRINT
288 qDebug() <<
"\r\n\r\n============================================="
289 <<
"\r\RibbonGroupLayoutPrivate::updateGeomArray()"
290 <<
" setrect:" << setrect <<
"\r\ngroup name:" << group->windowTitle()
291 <<
"\r\n largeHeight:" << largeHeight <<
"\r\n smallHeight:" << smallHeight
292 <<
"\r\n rowCount:" << rowCount;
297 RibbonGroupItem *lastGeomItem =
Q_NULLPTR;
298 for (
int i = 0; i < itemCount; ++i) {
299 RibbonGroupItem *item = m_items.at(i);
300 if (item->isEmpty()) {
302#if RibbonGroupLayout_DEBUG_PRINT
303 qDebug() << item->widget()->metaObject()->className() <<
"is hide"
304 <<
" row:" << row <<
" col:" << column;
307 item->columnIndex = -1;
311 QSize hint = item->sizeHint();
312 Qt::Orientations exp = item->expandingDirections();
313 if (item->widget()) {
315 if ((item->widget()->sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag)) {
322 if (exp & Qt::Vertical) {
335 x += (columnMaxWidth + spacing);
341 item->columnIndex = column;
342 item->willGeometry = QRect(x, margin.top(), hint.width(), largeHeight);
343 columnMaxWidth = hint.width();
345 x += (columnMaxWidth + spacing);
356 item->columnIndex = column;
357 item->willGeometry = QRect(x, yMediumRow0, hint.width(), smallHeight);
359 columnMaxWidth = hint.width();
364 item->columnIndex = column;
365 item->willGeometry = QRect(x, yMediumRow1, hint.width(), smallHeight);
367 columnMaxWidth = qMax(columnMaxWidth, hint.width());
369 x += (columnMaxWidth + spacing);
378 item->columnIndex = column;
379 item->willGeometry = QRect(x, yMediumRow0, hint.width(), smallHeight);
381 columnMaxWidth = hint.width();
384 }
else if (1 == row) {
386 item->columnIndex = column;
387 item->willGeometry = QRect(x, yMediumRow1, hint.width(), smallHeight);
388 columnMaxWidth = qMax(columnMaxWidth, hint.width());
390 x += (columnMaxWidth + spacing);
397 x += (columnMaxWidth + spacing);
403 item->columnIndex = column;
404 item->willGeometry = QRect(x, yMediumRow0, hint.width(), smallHeight);
406 columnMaxWidth = hint.width();
416 item->columnIndex = column;
417 item->willGeometry = QRect(x, ySmallRow0, hint.width(), smallHeight);
419 columnMaxWidth = hint.width();
422 }
else if (1 == row) {
425 item->columnIndex = column;
427 item->willGeometry = QRect(x, ySmallRow1, hint.width(), smallHeight);
430 item->willGeometry = QRect(x, yMediumRow1, hint.width(), smallHeight);
433 columnMaxWidth = qMax(columnMaxWidth, hint.width());
438 x += (columnMaxWidth + spacing);
449 x += (columnMaxWidth + spacing);
457 item->columnIndex = column;
458 item->willGeometry = QRect(x, ySmallRow2, hint.width(), smallHeight);
459 columnMaxWidth = qMax(columnMaxWidth, hint.width());
461 x += (columnMaxWidth + spacing);
473#if RibbonGroupLayout_DEBUG_PRINT
474 qDebug() << item->widget()->metaObject()->className() <<
" rp:" << rp <<
" row:" << item->rowIndex
475 <<
" col:" << item->columnIndex <<
" new row:" << row <<
" new column:" << column
476 <<
" willGeometry:" << item->willGeometry <<
" sizeHint:" << hint <<
" x:" << x;
482 if (lastGeomItem->columnIndex != column) {
484 m_columnCount = column;
486 totalWidth = x + margin.right();
489 m_columnCount = column + 1;
491 totalWidth = x + columnMaxWidth + spacing + margin.right();
495 if (group->hasOptionAction()) {
496 if (!group->titleVisible()) {
497 totalWidth += group->optionActionButtonSize().width();
501 if (totalWidth < setrect.width()) {
503 recalcExpandGeomArray(setrect);
505 m_sizeHint = QSize(totalWidth, height);
511void RibbonGroupLayoutPrivate::layoutActions()
513 Q_Q(RibbonGroupLayout);
515 updateGeomArray(q->geometry());
517 QList<QWidget *> showWidgets, hideWidgets;
519#if RibbonGroupLayout_DEBUG_PRINT
520 qDebug() <<
"\r\n\r\n =============================================="
521 "\r\n RibbonGroupLayoutPrivate::layoutActions"
522 <<
" \r\n name:" << q->parentWidget()->windowTitle() <<
" sizehint:" << q->sizeHint();
524 for (RibbonGroupItem *item : std::as_const(m_items)) {
525 if (item->isEmpty()) {
526 hideWidgets << item->widget();
528 item->setGeometry(item->willGeometry);
531 showWidgets << item->widget();
532#if RibbonGroupLayout_DEBUG_PRINT
533 qDebug() <<
"[" << item->rowIndex <<
"," << item->columnIndex <<
"]"
534 <<
" -> " << item->willGeometry <<
":" << item->widget()->metaObject()->className();
540 for (QWidget *w : std::as_const(showWidgets)) {
543 for (QWidget *w : std::as_const(hideWidgets)) {
558 while (!d->m_items.isEmpty()) {
560 if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(item->
action)) {
562 widgetAction->releaseWidget(item->widget());
578 for (
int i = 0; i < d->m_items.count(); ++i) {
579 if (d->m_items.at(i)->action == action) {
589 qWarning(
"RibbonGroupLayout::addItem(): please use insertAction() instead");
602 if (index < 0 || index > d->m_items.count()) {
603 index = d->m_items.count();
608 d->m_items.insert(index, item);
617 if ((index < 0) || (index >= d->m_items.count())) {
620 return d->m_items.at(index);
626 if ((index < 0) || (index >= d->m_items.count())) {
631 QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(item->
action);
634 widgetAction->releaseWidget(item->widget());
637 item->widget()->hide();
638 item->widget()->deleteLater();
648 return d->m_items.count();
654 return d->m_items.isEmpty();
661 QLayout::invalidate();
666 return Qt::Horizontal;
672 return d->m_sizeHint;
678 return d->m_sizeHint;
687 return d->m_items[index];
695 if (d->m_items.isEmpty()) {
698 return d->m_items.last();
710 return item->widget();
731 if (from < 0 || from >= c || to < 0 || to >= c) {
734 d->m_items.move(from, to);
757 return setrect.height() - m.top() - m.bottom() - group->
titleHeight();
770 return RibbonGroupLayoutPrivate::s_contentsMargins;
782 if (RibbonGroupLayoutPrivate::s_contentsMargins != m) {
783 RibbonGroupLayoutPrivate::s_contentsMargins = m;
798 d->updateGeomArray(rect);
799 QLayout::setGeometry(rect);
是对 group 所有子窗口的抽象,参考 qt 的 toolbar
QAction * action
记录action,参考QToolBarLayoutItem
bool customWidget
对于没有窗口的action,实际也会有一个RibbonButton,在销毁时要delete掉
bool isEmpty() const override
QLayoutItem * takeAt(int index) override
QSize minimumSize() const override
virtual int indexOf(QAction *action) const
通过action查找索引,用于actionEvent添加action用
const QList< RibbonGroupItem * > & ribbonGroupItems() const
static const QMargins & groupContentsMargins()
全局的contentsMargins
RibbonGroupItem * groupItem(QAction *action) const
void move(int from, int to)
移动两个item
int count() const override
QSize sizeHint() const override
static void setGroupContentsMargins(const QMargins &m)
全局的contentsMargins
RibbonGroupItem * lastItem() const
void setGeometry(const QRect &rect) override
RibbonGroupLayout(QWidget *p=Q_NULLPTR)
QWidget * lastWidget() const
获取最后生成的窗口
void invalidate() override
static int calcLargeHeight(const QRect &setrect, const RibbonGroup *group)
计算大图标的高度
void insertAction(int index, QAction *act, RibbonGroup::RowProportion rp=RibbonGroup::Auto)
RibbonGroup主要通过此函数来添加action
Qt::Orientations expandingDirections() const override
void addItem(QLayoutItem *item) override
bool isDirty() const
判断是否需要重新布局
bool isEmpty() const override
QLayoutItem * itemAt(int index) const override
group 用于对 ribbon 页窗口中的控件进行分组
void actionTriggered(QAction *action)
@ ThreeRowMode
三行布局模式,office就是三行布局模式,group能布置3行小toolbutton,默认模式
@ Medium
中占比,在 RibbonGroup::groupLayoutMode 为 RibbonGroup::ThreeRowMode 时才会起作用, 且要同一列里两个都是 Medium 时,会在三行中占据两行
@ Small
小占比,占RibbonGroup的一行,Medium在不满足条件时也会变为Small,但不会变为Large
@ Auto
未定义占比,这时候将会依据expandingDirections来判断,如果能有Qt::Vertical,就等同于Large,否则就是Small
@ Large
大占比,一个widget的高度会充满整个group
#define QX_INIT_PRIVATE(Class)
#define QX_DECLARE_PUBLIC(Class)
#define Q_NULLPTR
Copyleft (C) 2025 maminjie canpool@163.com SPDX-License-Identifier: MulanPSL-2.0
#define QX_FINI_PRIVATE()
#define QX_RIBBON_END_NAMESPACE
#define QX_RIBBON_BEGIN_NAMESPACE
#define RibbonElementStyleOpt