instAdmin/Cpp/GisDes/SaRibbon/SARibbonBar/SARibbonCategoryLayout.cpp
2024-10-29 22:24:50 +08:00

719 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "SARibbonCategoryLayout.h"
#include <QLayoutItem>
#include "SARibbonPannel.h"
#include "SARibbonElementManager.h"
#include "SARibbonSeparatorWidget.h"
#include <QApplication>
#include <QDebug>
#ifndef SARibbonCategoryLayout_DEBUG_PRINT
#define SARibbonCategoryLayout_DEBUG_PRINT 1
#endif
/**
* @brief The SARibbonCategoryLayoutPrivate class
*/
class SARibbonCategoryLayout::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonCategoryLayout)
public:
PrivateData(SARibbonCategoryLayout* p);
// 计算所有元素的sizehint总宽度
int totalSizeHintWidth() const;
public:
bool mDirty { true };
bool mIsRightScrollBtnShow { false }; ///< 标记右滚动按钮是否需要显示
bool mIsLeftScrollBtnShow { false }; ///< 标记左滚动按钮是否需要显示
SARibbonCategoryScrollButton* mLeftScrollBtn { nullptr }; ///< 在区域无法显示时显示的按钮
SARibbonCategoryScrollButton* mRightScrollBtn { nullptr }; ///< 在区域无法显示时显示的按钮
int mTotalWidth { 0 };
int mXBase { 0 };
QSize mSizeHint;
QSize mMinSizeHint;
QList< SARibbonCategoryLayoutItem* > mItemList;
SARibbonAlignment mCategoryAlignment { SARibbonAlignment::AlignLeft }; ///< 对齐方式
};
//=============================================================
// SARibbonCategoryLayoutPrivate
//=============================================================
SARibbonCategoryLayout::PrivateData::PrivateData(SARibbonCategoryLayout* p) : q_ptr(p)
{
}
/**
* @brief 计算所有元素的SizeHint宽度总和
* @return
*/
int SARibbonCategoryLayout::PrivateData::totalSizeHintWidth() const
{
int total = 0;
QMargins mag = q_ptr->contentsMargins();
#if SA_DEBUG_PRINT_SIZE_HINT
int debug_i__ = 0;
QString debug_totalSizeHintWidth__;
#endif
if (!mag.isNull()) {
total += (mag.left() + mag.right());
}
// 先计算总长
for (SARibbonCategoryLayoutItem* item : qAsConst(mItemList)) {
if (item->isEmpty()) {
// 如果是hide就直接跳过
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
++debug_i__;
debug_totalSizeHintWidth__ += QString(" [%1](%2)is empty skip\n")
.arg(debug_i__)
.arg(item->toPannelWidget()->pannelName());
#endif
continue;
}
// 这里要使用widget()->sizeHint()因为pannel的标题会影总体布局此处需要修改
// TODO
QSize pannelSize = item->widget()->sizeHint();
QSize SeparatorSize(0, 0);
if (item->separatorWidget) {
SeparatorSize = item->separatorWidget->sizeHint();
}
total += pannelSize.width();
total += SeparatorSize.width();
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
++debug_i__;
debug_totalSizeHintWidth__ += QString("|-[%1]pannelSize=(%2,%3),SeparatorSize=(%4,%5),name=(%6) \n")
.arg(debug_i__)
.arg(pannelSize.width())
.arg(pannelSize.height())
.arg(SeparatorSize.width())
.arg(SeparatorSize.height())
.arg(item->toPannelWidget()->pannelName());
#endif
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategoryLayout.totalSizeHintWidth=" << total;
qDebug().noquote() << debug_totalSizeHintWidth__;
#endif
return (total);
}
//=============================================================
// SARibbonCategoryLayout
//=============================================================
SARibbonCategoryLayout::SARibbonCategoryLayout(SARibbonCategory* parent)
: QLayout(parent), d_ptr(new SARibbonCategoryLayout::PrivateData(this))
{
setContentsMargins(1, 1, 1, 1);
d_ptr->mLeftScrollBtn = new SARibbonCategoryScrollButton(Qt::LeftArrow, parent);
d_ptr->mRightScrollBtn = new SARibbonCategoryScrollButton(Qt::RightArrow, parent);
d_ptr->mLeftScrollBtn->setVisible(false);
d_ptr->mRightScrollBtn->setVisible(false);
connect(d_ptr->mLeftScrollBtn, &QToolButton::clicked, this, &SARibbonCategoryLayout::onLeftScrollButtonClicked);
connect(d_ptr->mRightScrollBtn, &QToolButton::clicked, this, &SARibbonCategoryLayout::onRightScrollButtonClicked);
}
SARibbonCategoryLayout::~SARibbonCategoryLayout()
{
while (auto item = takePannelItem(0)) {
delete item;
}
}
SARibbonCategory* SARibbonCategoryLayout::ribbonCategory() const
{
return (qobject_cast< SARibbonCategory* >(parentWidget()));
}
void SARibbonCategoryLayout::addItem(QLayoutItem* item)
{
Q_UNUSED(item);
qWarning() << tr("in SARibbonCategoryLayout cannot addItem,use addPannel instead");
invalidate();
}
/**
* @brief 返回pannel的layout
* @param index 索引
* @return
* @note 注意pannel是和分割线一起的但这个只返回一个pannel对应的layout
*/
QLayoutItem* SARibbonCategoryLayout::itemAt(int index) const
{
SARibbonCategoryLayoutItem* item = d_ptr->mItemList.value(index, nullptr);
return (item);
}
/**
* @brief 提取layout
* @param index
* @return
*/
QLayoutItem* SARibbonCategoryLayout::takeAt(int index)
{
QLayoutItem* r = takePannelItem(index);
invalidate();
return r;
}
SARibbonCategoryLayoutItem* SARibbonCategoryLayout::takePannelItem(int index)
{
if ((index >= 0) && (index < d_ptr->mItemList.size())) {
SARibbonCategoryLayoutItem* item = d_ptr->mItemList.takeAt(index);
if (item->widget()) {
item->widget()->hide();
}
if (item->separatorWidget) {
item->separatorWidget->hide();
}
return (item);
}
return (nullptr);
}
SARibbonCategoryLayoutItem* SARibbonCategoryLayout::takePannelItem(SARibbonPannel* pannel)
{
for (int i = 0; i < d_ptr->mItemList.size(); ++i) {
SARibbonCategoryLayoutItem* item = d_ptr->mItemList[ i ];
if (item->widget() == pannel) {
return (takePannelItem(i));
}
}
return (nullptr);
}
/**
* @brief 移除pannel对应的分割线也会删除
* @param pannel
* @return
*/
bool SARibbonCategoryLayout::takePannel(SARibbonPannel* pannel)
{
SARibbonCategoryLayoutItem* i = takePannelItem(pannel);
if (i) {
SARibbonSeparatorWidget* sp = i->separatorWidget;
if (sp) {
sp->deleteLater();
}
delete i;
invalidate();
return true;
}
return false;
}
int SARibbonCategoryLayout::count() const
{
return (d_ptr->mItemList.size());
}
void SARibbonCategoryLayout::addPannel(SARibbonPannel* pannel)
{
insertPannel(d_ptr->mItemList.count(), pannel);
}
/**
* @brief 插入一个pannel
* @param index 索引
* @param pannel
* @return 返回对应的分割线SARibbonSeparatorWidget
* @note 在SARibbonCategoryLayout的布局中一个pannel会携带一个分割线
*/
void SARibbonCategoryLayout::insertPannel(int index, SARibbonPannel* pannel)
{
index = qMax(0, index);
index = qMin(d_ptr->mItemList.count(), index);
SARibbonCategoryLayoutItem* item = new SARibbonCategoryLayoutItem(pannel);
// 分割线
item->separatorWidget = RibbonSubElementFactory->createRibbonSeparatorWidget(parentWidget());
// 插入list中
d_ptr->mItemList.insert(index, item);
// 标记需要重新计算尺寸
invalidate();
}
QSize SARibbonCategoryLayout::sizeHint() const
{
if (d_ptr->mSizeHint.isNull()) {
SARibbonCategoryLayout* that = const_cast< SARibbonCategoryLayout* >(this);
that->updateGeometryArr();
}
return (d_ptr->mSizeHint);
}
QSize SARibbonCategoryLayout::minimumSize() const
{
if (d_ptr->mMinSizeHint.isNull()) {
SARibbonCategoryLayout* that = const_cast< SARibbonCategoryLayout* >(this);
that->updateGeometryArr();
}
return (d_ptr->mMinSizeHint);
}
/**
* @brief SARibbonCategory充满整个stacked widget
* @return
*/
Qt::Orientations SARibbonCategoryLayout::expandingDirections() const
{
return (Qt::Horizontal | Qt::Vertical);
}
void SARibbonCategoryLayout::invalidate()
{
d_ptr->mDirty = true;
QLayout::invalidate();
}
/**
* @brief category的内容尺寸把margins减去
* @return
*/
QSize SARibbonCategoryLayout::categoryContentSize() const
{
SARibbonCategory* category = ribbonCategory();
QSize s = category->size();
QMargins mag = contentsMargins();
if (!mag.isNull()) {
s.rheight() -= (mag.top() + mag.bottom());
s.rwidth() -= (mag.right() + mag.left());
}
return (s);
}
/**
* @brief 更新尺寸
*/
void SARibbonCategoryLayout::updateGeometryArr()
{
SARibbonCategory* category = ribbonCategory();
if (nullptr == category) {
return;
}
int categoryWidth = category->width();
QMargins mag = contentsMargins();
int height = category->height();
int y = 0;
if (!mag.isNull()) {
y = mag.top();
height -= (mag.top() + mag.bottom());
categoryWidth -= (mag.right() + mag.left());
}
// total 是总宽不是x坐标系x才是坐标系
int total = d_ptr->totalSizeHintWidth();
// 扩展的宽度
int expandWidth = 0;
// 如果total < categoryWidth,m_d->mXBase可以设置为0
// 判断是否超过总长度
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategoryLayout::updateGeometryArr"
<< "\n|-category name=" << category->categoryName() //
<< "\n|-category height=" << height //
<< "\n|-totalSizeHintWidth=" << total //
<< "\n|-y=" << y //
<< "\n|-expandWidth:" << expandWidth //
<< "\n|-mag=" << mag;
#endif
if (total > categoryWidth) {
// 超过总长度,需要显示滚动按钮
if (0 == d_ptr->mXBase) {
// 已经移动到最左,需要可以向右移动
d_ptr->mIsRightScrollBtnShow = true;
d_ptr->mIsLeftScrollBtnShow = false;
} else if (d_ptr->mXBase <= (categoryWidth - total)) {
// 已经移动到最右,需要可以向左移动
d_ptr->mIsRightScrollBtnShow = false;
d_ptr->mIsLeftScrollBtnShow = true;
} else {
// 移动到中间两边都可以动
d_ptr->mIsRightScrollBtnShow = true;
d_ptr->mIsLeftScrollBtnShow = true;
}
} else {
// 说明total 小于 categoryWidth
// 记录可以扩展的数量
int canExpandingCount = 0;
d_ptr->mIsRightScrollBtnShow = false;
d_ptr->mIsLeftScrollBtnShow = false;
// 这个是避免一开始totalWidth > categorySize.width()通过滚动按钮调整了m_d->mBaseX
// 随之调整了窗体尺寸调整后totalWidth < categorySize.width()导致category在原来位置
// 无法显示必须这里把mBaseX设置为0
d_ptr->mXBase = 0;
//
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (SARibbonPannel* p = qobject_cast< SARibbonPannel* >(item->widget())) {
if (p->isExpanding()) {
// pannel可扩展
++canExpandingCount;
}
}
}
// 计算可扩展的宽度
if (canExpandingCount > 0) {
expandWidth = (categoryWidth - total) / canExpandingCount;
} else {
expandWidth = 0;
}
}
int x = d_ptr->mXBase;
if ((categoryAlignment() == SARibbonAlignment::AlignCenter) && (total < categoryWidth) && (0 == expandWidth)) {
// 如果是居中对齐同时没有伸缩的pannel同时总宽度没有超过category的宽度
x = (categoryWidth - total) / 2;
}
total = 0; // total重新计算
// 先按照sizeHint设置所有的尺寸
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (item->isEmpty()) {
// 如果是hide就直接跳过
if (item->separatorWidget) {
// pannel hide分割线也要hide
item->separatorWidget->hide();
}
item->mWillSetGeometry = QRect(0, 0, 0, 0);
item->mWillSetSeparatorGeometry = QRect(0, 0, 0, 0);
continue;
}
SARibbonPannel* p = item->toPannelWidget();
if (nullptr == p) {
qDebug() << "unknow widget in SARibbonCategoryLayout";
continue;
}
// p->layout()->update();
QSize pannelSize = p->sizeHint();
QSize SeparatorSize(0, 0);
if (item->separatorWidget) {
SeparatorSize = item->separatorWidget->sizeHint();
}
if (p->isExpanding()) {
// 可扩展就把pannel扩展到最大
pannelSize.setWidth(pannelSize.width() + expandWidth);
}
int w = pannelSize.width();
item->mWillSetGeometry = QRect(x, y, w, height);
x += w;
total += w;
w = SeparatorSize.width();
item->mWillSetSeparatorGeometry = QRect(x, y, w, height);
x += w;
total += w;
}
d_ptr->mTotalWidth = total;
d_ptr->mSizeHint = QSize(d_ptr->mTotalWidth, height);
d_ptr->mMinSizeHint = QSize(categoryWidth, height);
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategoryLayout updateGeometryArr,SizeHint=" << d_ptr->mSizeHint
<< ",Category name=" << category->categoryName();
#endif
}
/**
* @brief 执行layout调整
*/
void SARibbonCategoryLayout::doLayout()
{
if (d_ptr->mDirty) {
updateGeometryArr();
}
SARibbonCategory* category = ribbonCategory();
// 两个滚动按钮的位置永远不变
d_ptr->mLeftScrollBtn->setGeometry(0, 0, 12, category->height());
d_ptr->mRightScrollBtn->setGeometry(category->width() - 12, 0, 12, category->height());
QList< QWidget* > showWidgets, hideWidgets;
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
int debug_i__(0);
qDebug() << "SARibbonCategoryLayout::doLayout(),name=" << category->categoryName();
#endif
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (item->isEmpty()) {
hideWidgets << item->widget();
if (item->separatorWidget) {
hideWidgets << item->separatorWidget;
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "|-[" << debug_i__ << "]pannelName(" << item->toPannelWidget()->pannelName() << ",will hide";
++debug_i__;
#endif
} else {
//! 这里不能用item->setGeometry(item->mWillSetGeometry);这样会得到一个很奇怪的显示效果
//! 就是窗口的最左边不会移出去,而是把最右边压缩,
item->widget()->setGeometry(item->mWillSetGeometry);
// item->widget()->setFixedSize(item->mWillSetGeometry.size());
// item->widget()->move(item->mWillSetGeometry.topLeft());
// item->setGeometry(item->mWillSetGeometry);
showWidgets << item->widget();
if (item->separatorWidget) {
item->separatorWidget->setGeometry(item->mWillSetSeparatorGeometry);
showWidgets << item->separatorWidget;
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "|-[" << debug_i__ << "]pannelName(" << item->toPannelWidget()->pannelName()
<< "),willSetGeometry:" << item->mWillSetGeometry
<< ",WillSetSeparatorGeometry:" << item->mWillSetSeparatorGeometry;
++debug_i__;
#endif
}
}
d_ptr->mRightScrollBtn->setVisible(d_ptr->mIsRightScrollBtnShow);
d_ptr->mLeftScrollBtn->setVisible(d_ptr->mIsLeftScrollBtnShow);
if (d_ptr->mIsRightScrollBtnShow) {
d_ptr->mRightScrollBtn->raise();
}
if (d_ptr->mIsLeftScrollBtnShow) {
d_ptr->mLeftScrollBtn->raise();
}
// 不在上面那里进行show和hide因为这会触发SARibbonPannelLayout的重绘导致循环绘制非常影响效率
for (QWidget* w : qAsConst(showWidgets)) {
if (!w->isVisible()) {
w->show();
}
}
for (QWidget* w : qAsConst(hideWidgets)) {
if (w->isVisible()) {
w->hide();
}
}
}
/**
* @brief 返回所有pannels
* @return
*/
QList< SARibbonPannel* > SARibbonCategoryLayout::pannels() const
{
QList< SARibbonPannel* > res;
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
SARibbonPannel* p = item->toPannelWidget();
res.append(p);
}
return (res);
}
/**
* @brief 通过ObjectName查找pannel
* @param objname
* @return
*/
SARibbonPannel* SARibbonCategoryLayout::pannelByObjectName(const QString& objname) const
{
for (SARibbonCategoryLayoutItem* item : d_ptr->mItemList) {
if (SARibbonPannel* pannel = item->toPannelWidget()) {
if (pannel->objectName() == objname) {
return pannel;
}
}
}
return nullptr;
}
/**
* @brief 通过名字查找pannel
* @param title
* @return 如果有重名,只会返回第一个符合条件的
*/
SARibbonPannel* SARibbonCategoryLayout::pannelByName(const QString& pannelname) const
{
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (SARibbonPannel* pannel = item->toPannelWidget()) {
if (pannel->pannelName() == pannelname) {
return (pannel);
}
}
}
return (nullptr);
}
/**
* @brief 通过索引找到pannel如果超过索引范围会返回nullptr
* @param i
* @return
*/
SARibbonPannel* SARibbonCategoryLayout::pannelByIndex(int i) const
{
if (i >= 0 && i < d_ptr->mItemList.size()) {
return d_ptr->mItemList[ i ]->toPannelWidget();
}
return nullptr;
}
/**
* @brief 移动pannel
* @param from
* @param to
*/
void SARibbonCategoryLayout::movePannel(int from, int to)
{
d_ptr->mItemList.move(from, to);
doLayout();
}
/**
* @brief 返回pannel的个数
* @return
*/
int SARibbonCategoryLayout::pannelCount() const
{
return d_ptr->mItemList.size();
}
/**
* @brief 查找pannel对应的索引
* @param p
* @return 如果找不到,返回-1
*/
int SARibbonCategoryLayout::pannelIndex(SARibbonPannel* p) const
{
int c = pannelCount();
for (int i = 0; i < c; ++i) {
if (d_ptr->mItemList[ i ]->toPannelWidget() == p) {
return (i);
}
}
return (-1);
}
/**
* @brief 获取所有的pannel
* @return
*/
QList< SARibbonPannel* > SARibbonCategoryLayout::pannelList() const
{
QList< SARibbonPannel* > res;
for (SARibbonCategoryLayoutItem* i : qAsConst(d_ptr->mItemList)) {
if (SARibbonPannel* p = i->toPannelWidget()) {
res.append(p);
}
}
return (res);
}
/**
* @brief 执行滚动
* @param px
*/
void SARibbonCategoryLayout::scroll(int px)
{
QSize contentSize = categoryContentSize();
d_ptr->mXBase += px;
if (d_ptr->mXBase > 0) {
d_ptr->mXBase = 0;
} else if ((d_ptr->mXBase + d_ptr->mTotalWidth) < contentSize.width()) {
d_ptr->mXBase = contentSize.width() - d_ptr->mTotalWidth;
}
invalidate();
}
/**
* @brief 判断是否滚动过
* @return
*/
bool SARibbonCategoryLayout::isScrolled() const
{
return (d_ptr->mXBase != 0);
}
/**
* @brief 这个宽度是实际内容的宽度有可能大于size.width也有可能小于
* @return
*/
int SARibbonCategoryLayout::categoryTotalWidth() const
{
return d_ptr->mTotalWidth;
}
/**
@brief 设置Category的对齐方式
居中对齐会让pannel以居中进行对齐
@param al
*/
void SARibbonCategoryLayout::setCategoryAlignment(SARibbonAlignment al)
{
d_ptr->mCategoryAlignment = al;
}
/**
@brief Category的对齐方式
@return
*/
SARibbonAlignment SARibbonCategoryLayout::categoryAlignment() const
{
return d_ptr->mCategoryAlignment;
}
void SARibbonCategoryLayout::onLeftScrollButtonClicked()
{
SARibbonCategory* category = qobject_cast< SARibbonCategory* >(parentWidget());
int width = category->width();
// 求总宽
int totalWidth = d_ptr->mTotalWidth;
if (totalWidth > width) {
int tmp = d_ptr->mXBase + width;
if (tmp > 0) {
tmp = 0;
}
d_ptr->mXBase = tmp;
} else {
d_ptr->mXBase = 0;
}
invalidate();
}
void SARibbonCategoryLayout::onRightScrollButtonClicked()
{
SARibbonCategory* category = qobject_cast< SARibbonCategory* >(parentWidget());
int width = category->width();
// 求总宽
int totalWidth = d_ptr->mTotalWidth;
if (totalWidth > width) {
int tmp = d_ptr->mXBase - width;
if (tmp < (width - totalWidth)) {
tmp = width - totalWidth;
}
d_ptr->mXBase = tmp;
} else {
d_ptr->mXBase = 0;
}
invalidate();
}
void SARibbonCategoryLayout::setGeometry(const QRect& rect)
{
QRect old = geometry();
if (old == rect) {
return;
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "===========SARibbonCategoryLayout.setGeometry(" << rect << "(" << ribbonCategory()->categoryName()
<< ")=======";
#endif
QLayout::setGeometry(rect);
d_ptr->mDirty = false;
updateGeometryArr();
doLayout();
}
//=============================================================
// SARibbonCategoryLayoutItem
//=============================================================
SARibbonCategoryLayoutItem::SARibbonCategoryLayoutItem(SARibbonPannel* w) : QWidgetItem(w)
{
separatorWidget = nullptr;
}
SARibbonPannel* SARibbonCategoryLayoutItem::toPannelWidget()
{
return qobject_cast< SARibbonPannel* >(widget());
}