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

2637 lines
80 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 "SARibbonBar.h"
#include <QPointer>
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QHoverEvent>
#include <QLinearGradient>
#include <QPainter>
#include <QResizeEvent>
#include <QSet>
#include <QStyleOptionMenuItem>
#include <QTimer>
#include <QVariant>
#include "SARibbonButtonGroupWidget.h"
#include "SARibbonElementManager.h"
#include "SARibbonQuickAccessBar.h"
#include "SARibbonStackedWidget.h"
#include "SARibbonTabBar.h"
#include "SARibbonApplicationButton.h"
#define HELP_DRAW_RECT(p, rect) \
do { \
p.save(); \
QPen _pen(Qt::DashDotDotLine); \
_pen.setColor(QColor(219, 26, 59)); \
p.setPen(_pen); \
p.setBrush(QBrush()); \
p.drawRect(rect); \
p.restore(); \
} while (0)
class _SAContextCategoryManagerData
{
public:
SARibbonContextCategory* contextCategory;
QList< int > tabPageIndex;
bool operator==(const SARibbonContextCategory* contextPage)
{
return (this->contextCategory == contextPage);
}
};
/**
* @todo 此处要修改,此方式容易异常
*/
class _SARibbonTabData
{
public:
SARibbonCategory* category;
int index;
_SARibbonTabData() : category(nullptr), index(-1)
{
}
};
Q_DECLARE_METATYPE(_SARibbonTabData)
class SARibbonBar::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonBar)
public:
QPointer< QAbstractButton > mApplicationButton;
QPointer< SARibbonTabBar > mRibbonTabBar;
QPointer< SARibbonStackedWidget > mStackedContainerWidget;
QPointer< SARibbonButtonGroupWidget > mRightButtonGroup; ///< 在tab bar右边的按钮群
QPointer< SARibbonQuickAccessBar > mQuickAccessBar; ///< 快速响应栏
QAction* mMinimumCategoryButtonAction { nullptr }; ///< 隐藏面板按钮action
QList< _SAContextCategoryManagerData > mCurrentShowingContextCategory;
QList< SARibbonContextCategory* > mContextCategoryList; ///< 存放所有的上下文标签
QList< _SARibbonTabData > mHidedCategory;
int mIconRightBorderPosition { 1 }; ///< 标题栏x值得最小值在有图标和快捷启动按钮此值都需要变化
SARibbonBar::RibbonStyles mRibbonStyle { SARibbonBar::RibbonStyleLooseThreeRow }; ///< ribbon的风格
SARibbonBar::RibbonMode mCurrentRibbonMode { SARibbonBar::NormalRibbonMode }; ///< 记录当前模式
QSize mWindowButtonSize; ///< 由SARibbonMainWindow告诉的windowbutton的尺寸
QList< QColor > mContextCategoryColorList; ///< contextCategory的色系
int mContextCategoryColorListIndex { -1 }; ///< 记录contextCategory色系索引
QColor mTitleTextColor; ///< 标题文字颜色,默认无效无效的情况下和SARibbonBar的qss:color属性一致
QColor mTabBarBaseLineColor { QColor(186, 201, 219) }; ///< tabbar 底部会绘制一条线条,定义线条颜色
QColor mContextCategoryTitleTextColor { Qt::black }; ///< 记录Context category的标题字体颜色
Qt::Alignment mTitleAligment { Qt::AlignCenter }; ///< 标题对齐方式
bool mIsTitleVisible { true }; ///< 标题是否显示
SARibbonAlignment mRibbonAlignment { SARibbonAlignment::AlignLeft }; ///< 对齐方式
SARibbonPannel::PannelLayoutMode mDefaulePannelLayoutMode { SARibbonPannel::ThreeRowMode }; ///< 默认的PannelLayoutMode
bool mEnableShowPannelTitle { true }; ///< 是否运行pannel的标题栏显示
bool mIsTabOnTitle { false }; ///< 是否tab在标题栏上
int mTitleBarHeight { 30 }; ///< 标题栏高度
int mTabBarHeight { 28 }; ///< tabbar高度
int mPannelTitleHeight { 15 }; ///< pannel的标题栏默认高度
int mCategoryHeight { 60 }; ///< Category的高度
int mPannelSpacing { 0 }; ///< pannel的spacing
QSize mPannelToolButtonSize { 22, 22 }; ///< 记录pannel的默认图标大小
std::unique_ptr< int > mUserDefTitleBarHeight; ///< 用户定义的标题栏高度,正常不使用用户设定的高度,而是使用自动计算的高度
std::unique_ptr< int > mUserDefTabBarHeight; ///< 用户定义的tabbar高度正常不使用用户设定的高度而是使用自动计算的高度
std::unique_ptr< int > mUserDefCategoryHeight; ///< 用户定义的Category的高度正常不使用用户设定的高度而是使用自动计算的高度
public:
PrivateData(SARibbonBar* par) : q_ptr(par)
{
mContextCategoryColorList = SARibbonBar::defaultContextCategoryColorList();
}
void init();
int systemTabBarHeight() const;
// 计算tabbar高度
int calcTabBarHeight();
// 根据字体信息计算标题栏高度
int calcTitleBarHeight();
// 根据字体信息计算category的高度
int calcCategoryHeight();
// 计算tabbar高度
static int calcMainBarHeight(int tabHegith, int titleHeight, int categoryHeight, bool tabOnTitle, SARibbonBar::RibbonMode rMode);
// 获取当前最小模式下的高度
int getCurrentMinimumModeMainBarHeight() const;
// 获取当前正常模式下的高度
int getCurrentNormalModeMainBarHeight() const;
// 重置尺寸
void resetSize();
// 获取标题栏高度
int titleBarHeight() const;
// tabbar 高度
int tabBarHeigth() const;
// category高度
int categoryHeight() const;
// 更新推荐的尺寸值
void updateHintSize();
void setApplicationButton(QAbstractButton* btn);
bool isContainContextCategoryInList(SARibbonContextCategory* contextCategory);
void setMinimumMode();
void setNormalMode();
QColor getContextCategoryColor();
void updateTabData();
/**
* @brief 通过输入高度计算iconSize
* @param h
* @return
*/
static QSize calcIconSizeByHeight(int h);
};
void SARibbonBar::PrivateData::init()
{
mApplicationButton = RibbonSubElementFactory->createRibbonApplicationButton(q_ptr);
q_ptr->connect(mApplicationButton.data(), &QAbstractButton::clicked, q_ptr, &SARibbonBar::applicationButtonClicked);
mRibbonTabBar = RibbonSubElementFactory->createRibbonTabBar(q_ptr);
mRibbonTabBar->setObjectName(QStringLiteral("objSARibbonTabBar"));
mRibbonTabBar->setDrawBase(false);
q_ptr->connect(mRibbonTabBar.data(), &QTabBar::currentChanged, q_ptr, &SARibbonBar::onCurrentRibbonTabChanged);
q_ptr->connect(mRibbonTabBar.data(), &QTabBar::tabBarClicked, q_ptr, &SARibbonBar::onCurrentRibbonTabClicked);
q_ptr->connect(mRibbonTabBar.data(), &QTabBar::tabBarDoubleClicked, q_ptr, &SARibbonBar::onCurrentRibbonTabDoubleClicked);
q_ptr->connect(mRibbonTabBar.data(), &QTabBar::tabMoved, q_ptr, &SARibbonBar::onTabMoved);
//
mStackedContainerWidget = RibbonSubElementFactory->createRibbonStackedWidget(q_ptr);
mStackedContainerWidget->setObjectName(QStringLiteral("objSARibbonStackedContainerWidget"));
q_ptr->connect(mStackedContainerWidget.data(), &SARibbonStackedWidget::hidWindow, q_ptr, &SARibbonBar::onStackWidgetHided);
// 捕获事件在popmode时必须用到
mStackedContainerWidget->installEventFilter(q_ptr);
//
mQuickAccessBar = RibbonSubElementFactory->createQuickAccessBar(q_ptr);
mQuickAccessBar->setObjectName(QStringLiteral("objSARibbonQuickAccessBar"));
mQuickAccessBar->setIcon(q_ptr->windowIcon());
//
mRightButtonGroup = RibbonSubElementFactory->craeteButtonGroupWidget(q_ptr);
//
setNormalMode();
}
int SARibbonBar::PrivateData::systemTabBarHeight() const
{
return q_ptr->style()->pixelMetric(QStyle::PM_TabBarBaseHeight)
+ q_ptr->style()->pixelMetric(QStyle::PM_TabBarTabHSpace)
+ q_ptr->style()->pixelMetric(QStyle::PM_TabBarTabOverlap);
}
/**
* @brief 估算tabbar的高度
* @param fm
* @return
*/
int SARibbonBar::PrivateData::calcTabBarHeight()
{
int defaultHeight = systemTabBarHeight();
int fontHeight = q_ptr->fontMetrics().lineSpacing(); // 不要用height像宋体这种字体height=12lineSpacing=14有些就无法显示
int defaultHeight2 = fontHeight * 1.6;
if (defaultHeight2 < fontHeight + 10) {
defaultHeight2 = fontHeight + 10; // 主要为了满足office2021主题tab下有个4px的横杠
}
int r = qMax(defaultHeight, defaultHeight2);
if (r < 20) {
r = 20;
}
return r;
}
/**
* @brief 估算标题栏的高度
* @param fm
* @return
*/
int SARibbonBar::PrivateData::calcTitleBarHeight()
{
int defaultHeight = q_ptr->style()->pixelMetric(QStyle::PM_TitleBarHeight);
int defaultHeight2 = q_ptr->fontMetrics().height() * 1.8;
int r = qMax(defaultHeight, defaultHeight2);
if (r < 25) {
r = 25;
}
return r;
}
/**
* @brief 估算category的高度
* @note 经过对照1.6行高和office的高度比较接近
* @param fm
* @param s
* @return
*/
int SARibbonBar::PrivateData::calcCategoryHeight()
{
int textH = q_ptr->fontMetrics().lineSpacing(); // 这里用linespace因为在换行的情况下行距是不可忽略的ribbon的大按钮默认是2行
if (SARibbonPannel::ThreeRowMode == mDefaulePannelLayoutMode) {
// 5.5=3*1.6+1 (三行),1是给panneltitle预留的
return textH * 4.8 + mPannelTitleHeight;
} else {
// 3=2*1.6
return textH * 3.2 + mPannelTitleHeight;
}
return (textH * 4.8 + mPannelTitleHeight);
}
/**
* @brief 计算总高
*
* @note 此总高是普通模式下的总高
*
* @param tabHegith
* @param titleHeight
* @param categoryHeight
* @return
*/
int SARibbonBar::PrivateData::calcMainBarHeight(int tabHegith,
int titleHeight,
int categoryHeight,
bool tabOnTitle,
SARibbonBar::RibbonMode rMode)
{
if (rMode == MinimumRibbonMode) {
// 最小模式没有categoryHeight
if (tabOnTitle) {
return titleHeight;
} else {
return titleHeight + tabHegith;
}
} else {
if (tabOnTitle) {
return titleHeight + categoryHeight;
} else {
return tabHegith + titleHeight + categoryHeight;
}
}
return tabHegith + titleHeight + categoryHeight;
}
int SARibbonBar::PrivateData::getCurrentMinimumModeMainBarHeight() const
{
return calcMainBarHeight(tabBarHeigth(), titleBarHeight(), categoryHeight(), mIsTabOnTitle, SARibbonBar::MinimumRibbonMode);
}
int SARibbonBar::PrivateData::getCurrentNormalModeMainBarHeight() const
{
return calcMainBarHeight(tabBarHeigth(), titleBarHeight(), categoryHeight(), mIsTabOnTitle, SARibbonBar::NormalRibbonMode);
}
/**
* @brief 重新计算尺寸
*/
void SARibbonBar::PrivateData::resetSize()
{
updateHintSize();
const int th = tabBarHeigth();
int mainBarHeight = calcMainBarHeight(th, titleBarHeight(), categoryHeight(), mIsTabOnTitle, mCurrentRibbonMode);
// 处于最小模式下时bar的高度为tabbar的bottom,这个调整必须在resize event之后
q_ptr->setFixedHeight(mainBarHeight);
}
/**
* @brief 标题栏高度
* @return
*/
int SARibbonBar::PrivateData::titleBarHeight() const
{
if (mUserDefTitleBarHeight) {
return *mUserDefTitleBarHeight;
}
return mTitleBarHeight;
}
/**
* @brief tab高度
* @return
*/
int SARibbonBar::PrivateData::tabBarHeigth() const
{
if (mUserDefTabBarHeight) {
return *mUserDefTabBarHeight;
}
return mTabBarHeight;
}
/**
* @brief category高度
* @return
*/
int SARibbonBar::PrivateData::categoryHeight() const
{
if (mUserDefCategoryHeight) {
return *mUserDefCategoryHeight;
} else {
return mCategoryHeight;
}
}
/**
* @brief 更新推荐的尺寸值
*/
void SARibbonBar::PrivateData::updateHintSize()
{
mTitleBarHeight = calcTitleBarHeight();
// mTabBarHeight有大于0的值说明用户设置了就使用用户设置的值
mTabBarHeight = calcTabBarHeight();
mCategoryHeight = calcCategoryHeight();
}
void SARibbonBar::PrivateData::setApplicationButton(QAbstractButton* btn)
{
if (mApplicationButton) {
mApplicationButton->hide();
mApplicationButton->deleteLater();
}
if (btn) {
if (btn->parent() != q_ptr) {
btn->setParent(q_ptr);
}
btn->move(0, q_ptr->titleBarHeight());
btn->show();
q_ptr->connect(btn, &QAbstractButton::clicked, q_ptr, &SARibbonBar::applicationButtonClicked);
}
mApplicationButton = btn;
}
bool SARibbonBar::PrivateData::isContainContextCategoryInList(SARibbonContextCategory* contextCategory)
{
for (int i = 0; i < mCurrentShowingContextCategory.size(); ++i) {
if (mCurrentShowingContextCategory[ i ] == contextCategory) {
return (true);
}
}
return (false);
}
void SARibbonBar::PrivateData::setMinimumMode()
{
mCurrentRibbonMode = SARibbonBar::MinimumRibbonMode;
mStackedContainerWidget->setPopupMode();
mStackedContainerWidget->setFocusPolicy(Qt::NoFocus);
mStackedContainerWidget->clearFocus();
mRibbonTabBar->setFocus();
mStackedContainerWidget->hide();
resetSize();
}
void SARibbonBar::PrivateData::setNormalMode()
{
mCurrentRibbonMode = SARibbonBar::NormalRibbonMode;
mStackedContainerWidget->setNormalMode();
mStackedContainerWidget->setFocus();
mStackedContainerWidget->show();
resetSize();
}
QColor SARibbonBar::PrivateData::getContextCategoryColor()
{
if (mContextCategoryColorList.isEmpty()) {
mContextCategoryColorListIndex = -1;
return (QColor());
}
++mContextCategoryColorListIndex;
if ((mContextCategoryColorListIndex >= mContextCategoryColorList.size()) || (mContextCategoryColorListIndex < 0)) {
mContextCategoryColorListIndex = 0;
}
return (mContextCategoryColorList.at(mContextCategoryColorListIndex));
}
void SARibbonBar::PrivateData::updateTabData()
{
int tabcount = mRibbonTabBar->count();
for (int i = 0; i < tabcount; ++i) {
QVariant var = mRibbonTabBar->tabData(i);
if (var.isValid()) {
_SARibbonTabData p = var.value< _SARibbonTabData >();
p.index = i;
mRibbonTabBar->setTabData(i, QVariant::fromValue(p));
}
}
// 刷新完tabdata信息也要接着刷新ContextCategory信息
for (_SAContextCategoryManagerData& cd : mCurrentShowingContextCategory) {
cd.tabPageIndex.clear();
for (int i = 0; i < cd.contextCategory->categoryCount(); ++i) {
SARibbonCategory* category = cd.contextCategory->categoryPage(i);
for (int t = 0; t < tabcount; ++t) {
QVariant v = mRibbonTabBar->tabData(t);
if (v.isValid()) {
_SARibbonTabData d = v.value< _SARibbonTabData >();
if (d.category == category) {
cd.tabPageIndex.append(t);
}
} else {
cd.tabPageIndex.append(-1);
}
}
}
}
}
QSize SARibbonBar::PrivateData::calcIconSizeByHeight(int h)
{
if (h - 8 >= 20) {
return QSize(h - 8, h - 8);
}
return QSize(h - 4, h - 4);
}
//===================================================
// SARibbonBar
//===================================================
/**
* @brief SARibbonBar构造函数
* @param parent
*/
SARibbonBar::SARibbonBar(QWidget* parent) : QMenuBar(parent), d_ptr(new SARibbonBar::PrivateData(this))
{
d_ptr->init();
ensurePolished();
setNativeMenuBar(false);
// #ifdef Q_OS_MACOS
// setNativeMenuBar(false);
// #endif
// #ifdef Q_OS_LINUX
// setNativeMenuBar(false);
// #endif
if (parent) {
connect(parent, &QWidget::windowTitleChanged, this, &SARibbonBar::onWindowTitleChanged);
connect(parent, &QWidget::windowIconChanged, this, &SARibbonBar::onWindowIconChanged);
}
setRibbonStyle(RibbonStyleLooseThreeRow);
}
SARibbonBar::~SARibbonBar()
{
}
/**
* @brief 判断样式是否为2行
* @param s
* @return 2行返回true返回false代表当前是3行
*/
bool SARibbonBar::isTwoRowStyle(SARibbonBar::RibbonStyles s)
{
return s.testFlag(SARibbonBar::RibbonStyleTwoRow);
}
/**
* @brief 判断样式是否为3行
* @param s
* @return 3行返回true返回false代表当前是2行
*/
bool SARibbonBar::isThreeRowStyle(RibbonStyles s)
{
return s.testFlag(SARibbonBar::RibbonStyleThreeRow);
}
/**
* @brief 判断是否是宽松样式
* @param s
* @return 宽松样式带标题栏返回true否则就是紧凑样式
*/
bool SARibbonBar::isLooseStyle(SARibbonBar::RibbonStyles s)
{
return s.testFlag(SARibbonBar::RibbonStyleLoose);
}
/**
* @brief 判断是否是紧凑样式
* @param s
* @return 紧凑样式不带标题栏返回true否则就是宽松样式
*/
bool SARibbonBar::isCompactStyle(RibbonStyles s)
{
return s.testFlag(SARibbonBar::RibbonStyleCompact);
}
/**
* @brief 获取版本信息
* @return {SA_RIBBON_BAR_VERSION_MAJ}.{SA_RIBBON_BAR_VERSION_MIN}.{SA_RIBBON_BAR_VERSION_PAT}
*/
QString SARibbonBar::versionString()
{
return QString("%1.%2.%3").arg(SA_RIBBON_BAR_VERSION_MAJ).arg(SA_RIBBON_BAR_VERSION_MIN).arg(SA_RIBBON_BAR_VERSION_PAT);
}
/**
@brief 获取默认的上下文标签颜色列表
@return
*/
QList< QColor > SARibbonBar::defaultContextCategoryColorList()
{
QList< QColor > res;
res //
<< QColor(206, 232, 252) // 蓝
<< QColor(253, 238, 179) // 黄
<< QColor(212, 255, 174) // 绿
<< QColor(255, 196, 214) // 红
<< QColor(255, 216, 153) // 橙
<< QColor(255, 224, 243) // 玫红
;
return res;
}
/**
* @brief 提供高分屏的支持静态函数
*
* @note 此函数需要在main函数QApplication生成之前调用
* @code
* int main(int argc, char* argv[]){
* SARibbonBar::initHighDpi();
* QApplication a(argc, argv);
* ...
* }
* @endcode
*/
void SARibbonBar::initHighDpi()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
// 在有些时候无法显示分割线是由于PassThrough导致的
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
}
/**
* @brief 返回applicationButton
* @return 默认的applicationButton是@ref SARibbonApplicationButton 生成,通过@ref setApplicationButton 可设置为其他button
*/
QAbstractButton* SARibbonBar::applicationButton()
{
return (d_ptr->mApplicationButton);
}
/**
* @brief 设置applicationButton,如果想隐藏可以传入nullptr
*
* 默认会有一个SARibbonApplicationButton如果想取消可传入nullptr或者自定义的button也可以传入
*
* @note applicationButton的所有权归SARibbonBar所有不要在外部对applicationButton进行delete操作
* @param btn applicationButton指针可以传入@ref SARibbonApplicationButton
* SA已经对SARibbonApplicationButton进行了样式设置
*/
void SARibbonBar::setApplicationButton(QAbstractButton* btn)
{
d_ptr->setApplicationButton(btn);
if (btn) {
if (btn->objectName().isEmpty()) {
btn->setObjectName(QStringLiteral("SARibbonApplicationButton"));
}
btn->setVisible(true);
// btn->setGeometry(applicationButtonGeometry());
}
// 无论设置为什么都触发resize
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
/**
* @brief 返回tabbar
* @return SARibbonTabBar指针
* @sa SARibbonTabBar
*/
SARibbonTabBar* SARibbonBar::ribbonTabBar()
{
return (d_ptr->mRibbonTabBar);
}
/**
* @brief 添加一个标签
* 如果需要删除直接delete即可SARibbonBar会对其进行处理
* @param title 标签名字默认情况下SARibbonCategory的object name也被设置为title
* @return 返回一个窗口容器在Category里可以添加其他控件
* @sa SARibbonCategory
*/
SARibbonCategory* SARibbonBar::addCategoryPage(const QString& title)
{
SARibbonCategory* category = RibbonSubElementFactory->createRibbonCategory(this);
category->setObjectName(title);
category->setCategoryName(title);
addCategoryPage(category);
return (category);
}
/**
* @brief 添加一个标签
* @param category
*/
void SARibbonBar::addCategoryPage(SARibbonCategory* category)
{
if (nullptr == category) {
return;
}
int index = d_ptr->mRibbonTabBar->count();
insertCategoryPage(category, index);
}
/**
* @brief qtdesigner专用
* @param category
*/
void SARibbonBar::addCategoryPage(QWidget* category)
{
SARibbonCategory* c = qobject_cast< SARibbonCategory* >(category);
if (c) {
addCategoryPage(c);
}
}
/**
* @brief 添加一个categorycategory的位置在index如果当前category数量少于index将插入到最后
* @param title category的标题
* @param index category的位置
* @return
*/
SARibbonCategory* SARibbonBar::insertCategoryPage(const QString& title, int index)
{
SARibbonCategory* category = RibbonSubElementFactory->createRibbonCategory(this);
category->setObjectName(title);
category->setCategoryName(title);
insertCategoryPage(category, index);
return (category);
}
/**
* @brief 插入一个category
* @param category SARibbonCategory指针
* @param index 插入的位置,如果超出范围,将默认插入到最后
*/
void SARibbonBar::insertCategoryPage(SARibbonCategory* category, int index)
{
if (nullptr == category) {
return;
}
category->setPannelLayoutMode(d_ptr->mDefaulePannelLayoutMode);
category->setPannelSpacing(d_ptr->mPannelSpacing);
category->setPannelToolButtonIconSize(d_ptr->mPannelToolButtonSize);
int i = d_ptr->mRibbonTabBar->insertTab(index, category->categoryName());
_SARibbonTabData tabdata;
tabdata.category = category;
tabdata.index = i;
d_ptr->mRibbonTabBar->setTabData(i, QVariant::fromValue(tabdata));
d_ptr->mStackedContainerWidget->insertWidget(index, category);
// 更新index信息
d_ptr->updateTabData();
QApplication::postEvent(this, new QResizeEvent(size(), size()));
connect(category, &QWidget::windowTitleChanged, this, &SARibbonBar::onCategoryWindowTitleChanged);
connect(category, &SARibbonCategory::actionTriggered, this, &SARibbonBar::actionTriggered);
}
/**
* @brief 通过名字查找Category
* @param title Category的名字既标签的标题
* @return 如果没有找到将返回nullptr如果有重名将返回第一个查询到的名字因此尽量避免重名标签
* @note 由于翻译等原因可能title会变化因此如果想通过固定内容查找category应该使用 @ref categoryByObjectName
* @see categoryByObjectName
*/
SARibbonCategory* SARibbonBar::categoryByName(const QString& title) const
{
int c = d_ptr->mStackedContainerWidget->count();
for (int i = 0; i < c; ++i) {
SARibbonCategory* w = qobject_cast< SARibbonCategory* >(d_ptr->mStackedContainerWidget->widget(i));
if (w) {
if (w->categoryName() == title) {
return (w);
}
}
}
return (nullptr);
}
/**
* @brief 通过ObjectName查找Category
* @param objname
* @return 如果没有找到将返回nullptr如果有同样的ObjectName将返回第一个查询到的名字因此尽量避免ObjectName重名
* @see categoryByName
*/
SARibbonCategory* SARibbonBar::categoryByObjectName(const QString& objname) const
{
int c = d_ptr->mStackedContainerWidget->count();
for (int i = 0; i < c; ++i) {
SARibbonCategory* w = qobject_cast< SARibbonCategory* >(d_ptr->mStackedContainerWidget->widget(i));
if (w) {
if (w->objectName() == objname) {
return (w);
}
}
}
return (nullptr);
}
/**
* @brief 通过索引找到category如果超过索引范围会返回nullptr
* @param index 索引
* @return 如果超过索引范围会返回nullptr
* @note 如果此时有上下文标签,上下文的标签也会返回
* @note 通过索引查找的category必须是visible状态的category如果通过@ref hideCategory 隐藏的标签,通过索引是找不到的
* @note 通过@ref categoryByObjectName 可以找到所有加入过的标签,
* 如果想得到ribbonbar管理的所有标签可以通过函数@ref categoryPages 得到
* @see categoryIndex categoryByObjectName categoryByName
*/
SARibbonCategory* SARibbonBar::categoryByIndex(int index) const
{
QVariant var = d_ptr->mRibbonTabBar->tabData(index);
if (var.isValid()) {
_SARibbonTabData p = var.value< _SARibbonTabData >();
return (p.category);
}
return (nullptr);
}
/**
* @brief 隐藏category,并不会删除或者取走,只是隐藏
* @param category
*/
void SARibbonBar::hideCategory(SARibbonCategory* category)
{
int tabcount = d_ptr->mRibbonTabBar->count();
for (int i = 0; i < tabcount; ++i) {
QVariant var = d_ptr->mRibbonTabBar->tabData(i);
if (var.isValid()) {
_SARibbonTabData p = var.value< _SARibbonTabData >();
if (p.category == category) {
d_ptr->mHidedCategory.append(p);
d_ptr->mRibbonTabBar->removeTab(i); // 仅仅把tab移除
// 注意Category隐藏后contex的位置就会发生变化需要更新
d_ptr->updateTabData();
return;
}
}
}
}
/**
* @brief 显示被隐藏的category
* @param category
*/
void SARibbonBar::showCategory(SARibbonCategory* category)
{
for (auto i = d_ptr->mHidedCategory.begin(); i != d_ptr->mHidedCategory.end(); ++i) {
if (i->category == category) {
// 说明要显示
int index = d_ptr->mRibbonTabBar->insertTab(i->index, i->category->categoryName());
i->index = index;
d_ptr->mRibbonTabBar->setTabData(index, QVariant::fromValue(*i));
d_ptr->mHidedCategory.erase(i); // 移除
// 更新index信息
d_ptr->updateTabData();
raiseCategory(category);
return;
}
}
raiseCategory(category);
}
/**
* @brief 判断这个category是否在显示状态也就是tabbar有这个category
* @param category
* @return
*/
bool SARibbonBar::isCategoryVisible(const SARibbonCategory* c) const
{
int tabindex = categoryIndex(c);
return (tabindex >= 0);
}
/**
* @brief 获取category的索引
* @param c
* @return 如果找不到,返回-1
*/
int SARibbonBar::categoryIndex(const SARibbonCategory* c) const
{
// category的顺序不能以stackedwidget为准因为存在contextcategorycontextcategory正常是不显示的
int tabcount = d_ptr->mRibbonTabBar->count();
for (int i = 0; i < tabcount; ++i) {
QVariant var = d_ptr->mRibbonTabBar->tabData(i);
if (var.isValid()) {
_SARibbonTabData p = var.value< _SARibbonTabData >();
if (p.category == c) {
return (i);
}
}
}
return (-1);
}
/**
* @brief 移动一个Category从from index到to index
* @param from
* @param to
*/
void SARibbonBar::moveCategory(int from, int to)
{
d_ptr->mRibbonTabBar->moveTab(from, to);
// 这时要刷新所有tabdata的index信息
d_ptr->updateTabData();
// 这里会触发tabMoved信号在tabMoved信号中调整stacked里窗口的位置
}
/**
* @brief 获取当前显示的所有的SARibbonCategory包含未显示的SARibbonContextCategory的SARibbonCategory也一并返回
*
* @return
*/
QList< SARibbonCategory* > SARibbonBar::categoryPages(bool getAll) const
{
int c = d_ptr->mStackedContainerWidget->count();
QList< SARibbonCategory* > res;
for (int i = 0; i < c; ++i) {
SARibbonCategory* w = qobject_cast< SARibbonCategory* >(d_ptr->mStackedContainerWidget->widget(i));
if (w) {
if (!getAll && w->isContextCategory()) {
// 不是getall且是上下文时跳过
continue;
}
res.append(w);
}
}
return (res);
}
/**
* @brief 移除SARibbonCategory
*
* SARibbonBar不会delete SARibbonCategory*但这个SARibbonCategory会脱离SARibbonBar的管理
* 表现在tabbar会移除面板会移除使用此函数后可以对SARibbonCategory进行delete
* @param category
*/
void SARibbonBar::removeCategory(SARibbonCategory* category)
{
int index = tabIndex(category);
bool isupdate = false;
if (index >= 0) {
d_ptr->mRibbonTabBar->removeTab(index);
isupdate = true;
}
d_ptr->mStackedContainerWidget->removeWidget(category);
// 同时验证这个category是否是contexcategory里的
for (SARibbonContextCategory* c : qAsConst(d_ptr->mContextCategoryList)) {
c->takeCategory(category);
}
// 这时要刷新所有tabdata的index信息
if (isupdate) {
d_ptr->updateTabData();
}
// 移除完后需要重绘
repaint();
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
/**
* @brief 添加上下文标签
*
* 上下文标签是特殊时候触发的标签,需要用户手动触发
*
* 调用@ref SARibbonContextCategory::addCategoryPage 可在上下文标签中添加SARibbonCategory
* 在上下文标签添加的SARibbonCategory只有在上下文标签显示的时候才会显示
* @param title 上下文标签的标题在Office模式下会显示在wps模式下不显示。默认情况下SARibbonContextCategory的object name也被设置为title
* @param color 上下文标签的颜色如果指定为空QColor(),将会使用SARibbonBar的默认色系
* @param id 上下文标签的id以便进行查找
* @return 返回上下文标签指针
* @note SARibbonBar拥有SARibbonContextCategory的管理权用户避免在外部直接delete,如果要删除,调用@ref destroyContextCategory 函数
*/
SARibbonContextCategory* SARibbonBar::addContextCategory(const QString& title, const QColor& color, const QVariant& id)
{
SARibbonContextCategory* context = RibbonSubElementFactory->createRibbonContextCategory(this);
context->setObjectName(title);
context->setContextTitle(title);
context->setId(id);
context->setContextColor(color.isValid() ? color : d_ptr->getContextCategoryColor());
addContextCategory(context);
return (context);
}
/**
* @brief 添加上下文标签
* @param context
*/
void SARibbonBar::addContextCategory(SARibbonContextCategory* context)
{
if (nullptr == context) {
return;
}
connect(context, &SARibbonContextCategory::categoryPageAdded, this, &SARibbonBar::onContextsCategoryPageAdded);
connect(context, &SARibbonContextCategory::categoryTitleChanged, this, &SARibbonBar::onContextsCategoryCategoryNameChanged);
// remove并没有绑定主要是remove后在stacked里也不会显示remove且delete的话stacked里也会删除
d_ptr->mContextCategoryList.append(context);
}
/**
* @brief 显示上下文标签
* @param context 上下文标签指针
*/
void SARibbonBar::showContextCategory(SARibbonContextCategory* context)
{
if (isContextCategoryVisible(context)) {
return;
}
_SAContextCategoryManagerData contextCategoryData;
contextCategoryData.contextCategory = context;
for (int i = 0; i < context->categoryCount(); ++i) {
SARibbonCategory* category = context->categoryPage(i);
// 此句如果模式重复设置不会进行多余操作
category->setPannelLayoutMode(d_ptr->mDefaulePannelLayoutMode);
// 切换模式后会改变高度,上下文标签显示时要保证显示出来
int index = d_ptr->mRibbonTabBar->addTab(category->categoryName());
contextCategoryData.tabPageIndex.append(index);
_SARibbonTabData tabdata;
tabdata.category = category;
tabdata.index = index;
d_ptr->mRibbonTabBar->setTabData(index, QVariant::fromValue(tabdata));
}
d_ptr->mCurrentShowingContextCategory.append(contextCategoryData);
// 由于上下文都是在最后追加不需要调用updateTabData();
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
/**
* @brief 隐藏上下文标签
* @param context 上下文标签指针
*/
void SARibbonBar::hideContextCategory(SARibbonContextCategory* context)
{
bool needResize = false;
for (int i = 0; i < d_ptr->mCurrentShowingContextCategory.size(); ++i) {
if (d_ptr->mCurrentShowingContextCategory[ i ].contextCategory == context) {
const QList< int >& indexs = d_ptr->mCurrentShowingContextCategory[ i ].tabPageIndex;
for (int j = indexs.size() - 1; j >= 0; --j) {
d_ptr->mRibbonTabBar->removeTab(indexs[ j ]);
}
// 注意再删除ContextCategory后tab的序号就会改变这时这个tab后面的都要调整它的序号
needResize = true;
d_ptr->mCurrentShowingContextCategory.removeAt(i);
// 移除了ContextCategory后需要break
break;
}
}
if (needResize) {
d_ptr->updateTabData();
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
}
/**
* @brief 判断上下文是否在显示状态
* @param context
* @return 在显示状态返回true
* @sa setContextCategoryVisible
*/
bool SARibbonBar::isContextCategoryVisible(SARibbonContextCategory* context)
{
return (d_ptr->isContainContextCategoryInList(context));
}
/**
* @brief 设置上下文标签的显示状态
*
* 上下文标签的当前显示状态可通过 @ref isContextCategoryVisible 进行判断
* @param context 上下文标签
* @param visible 显示状态true为显示
*/
void SARibbonBar::setContextCategoryVisible(SARibbonContextCategory* context, bool visible)
{
if (nullptr == context) {
return;
}
if (visible) {
showContextCategory(context);
} else {
hideContextCategory(context);
}
}
/**
* @brief 获取所有的上下文标签
* @return 返回上下文标签列表
*/
QList< SARibbonContextCategory* > SARibbonBar::contextCategoryList() const
{
return (d_ptr->mContextCategoryList);
}
/**
* @brief 销毁上下文标签上下文标签的SARibbonCategory也会随之销毁
* @param context 需要销毁的上下文标签指针
*/
void SARibbonBar::destroyContextCategory(SARibbonContextCategory* context)
{
if (nullptr == context) {
return;
}
//! 1、如果上下文标签显示中先隐藏
if (isContextCategoryVisible(context)) {
hideContextCategory(context);
}
//! 2、删除上下文标签的相关内容
d_ptr->mContextCategoryList.removeAll(context);
//!
QList< SARibbonCategory* > res = context->categoryList();
for (SARibbonCategory* c : qAsConst(res)) {
c->hide();
c->deleteLater();
}
context->deleteLater();
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
/**
* @brief 设置为最小/正常模式
*
* 隐藏模式下只会显示tabbar不会显示内容默认状态是显示模式
*
* 默认下双击tabbar会切换隐藏显示模式如果想禁用此功能可以重载 @ref onCurrentRibbonTabDoubleClicked
* 函数,不对函数进行任何处理即可
*
* @param isMinimum 参数为true时切换为Minimum模式
* @see 此函数会改变@ref RibbonState 状态,通过@ref currentRibbonState 函数可以查看当前状态
*/
void SARibbonBar::setMinimumMode(bool isMinimum)
{
#ifdef SA_RIBBON_DEBUG_HELP_DRAW
qDebug() << "SARibbonBar::setHideMode " << isMinimum;
#endif
if (isMinimum) {
d_ptr->setMinimumMode();
} else {
d_ptr->setNormalMode();
}
QResizeEvent resizeEvent(size(), size());
QApplication::sendEvent(this, &resizeEvent);
// 发射信号
Q_EMIT ribbonModeChanged(isMinimum ? MinimumRibbonMode : NormalRibbonMode);
}
///
/// \brief 当前ribbon是否是隐藏模式
/// \return
///
bool SARibbonBar::isMinimumMode() const
{
return (d_ptr->mStackedContainerWidget->isPopupMode());
}
///
/// \brief 设置显示隐藏ribbon按钮
///
void SARibbonBar::showMinimumModeButton(bool isShow)
{
if (isShow) {
activeRightButtonGroup();
d_ptr->mMinimumCategoryButtonAction = new QAction(this);
d_ptr->mMinimumCategoryButtonAction->setIcon(
style()->standardIcon(isMinimumMode() ? QStyle::SP_TitleBarUnshadeButton : QStyle::SP_TitleBarShadeButton,
nullptr));
connect(d_ptr->mMinimumCategoryButtonAction, &QAction::triggered, this, [ this ]() {
this->setMinimumMode(!isMinimumMode());
this->d_ptr->mMinimumCategoryButtonAction->setIcon(
style()->standardIcon(isMinimumMode() ? QStyle::SP_TitleBarUnshadeButton : QStyle::SP_TitleBarShadeButton,
nullptr));
});
d_ptr->mRightButtonGroup->addAction(d_ptr->mMinimumCategoryButtonAction);
} else {
if (nullptr != d_ptr->mMinimumCategoryButtonAction) {
d_ptr->mMinimumCategoryButtonAction->deleteLater();
d_ptr->mMinimumCategoryButtonAction = nullptr;
}
}
QResizeEvent resizeEvent(size(), size());
QApplication::sendEvent(this, &resizeEvent);
}
/**
* @brief 是否显示隐藏ribbon按钮
* @return
*/
bool SARibbonBar::haveShowMinimumModeButton() const
{
return (nullptr != d_ptr->mMinimumCategoryButtonAction);
}
/**
@brief 隐藏ribbon对应的action
@return
*/
QAction* SARibbonBar::minimumModeAction() const
{
return d_ptr->mMinimumCategoryButtonAction;
}
/**
* @brief 当前ribbon的状态正常|最小化)
* @return
*/
SARibbonBar::RibbonMode SARibbonBar::currentRibbonState() const
{
return (d_ptr->mCurrentRibbonMode);
}
/**
@brief tabBar的高度
@return
*/
int SARibbonBar::tabBarHeight() const
{
return d_ptr->tabBarHeigth();
// return d_ptr->mRibbonTabBar->height();
}
/**
* @brief 设置tabbar的高度
*
* 用户调用setTabBarHeight后将使用用户设定的高度而不使用自动计算的高度这时tabbar高度不会跟随字体等信息重新计算
*
* @note 注意在RibbonStyleCompact**模式下tabbar高度要保证小于等于titlebar高度否则会显示异常
* @note 此函数不会自动刷新,如果需要刷新调用此函数后需要调用@ref updateRibbonGeometry
* @param h
*/
void SARibbonBar::setTabBarHeight(int h, bool resizeByNow)
{
if (nullptr == d_ptr->mUserDefTabBarHeight) {
d_ptr->mUserDefTabBarHeight = std::make_unique< int >(h);
} else {
*(d_ptr->mUserDefTabBarHeight) = h;
}
if (resizeByNow) {
updateRibbonGeometry();
}
}
/**
@brief 返回标题栏高度
@sa setTitleBarHeight
@return
*/
int SARibbonBar::titleBarHeight() const
{
return d_ptr->titleBarHeight();
}
/**
@brief 设置标题栏的高度
@sa titleBarHeight
@note 此操作会发射@ref titleBarHeightChanged 信号
@param h
*/
void SARibbonBar::setTitleBarHeight(int h, bool resizeByNow)
{
int oldHeight = d_ptr->mTitleBarHeight;
if (nullptr == d_ptr->mUserDefTitleBarHeight) {
d_ptr->mUserDefTitleBarHeight = std::make_unique< int >(h);
} else {
*(d_ptr->mUserDefTitleBarHeight) = h;
//
oldHeight = *(d_ptr->mUserDefTitleBarHeight);
}
if (resizeByNow) {
updateRibbonGeometry();
}
Q_EMIT titleBarHeightChanged(oldHeight, h);
}
/**
* @brief category的高度
* @return
*/
int SARibbonBar::categoryHeight() const
{
return d_ptr->mStackedContainerWidget->height();
}
/**
* @brief 设置category的高度
* @param h
* @param resizeByNow
*/
void SARibbonBar::setCategoryHeight(int h, bool resizeByNow)
{
if (nullptr == d_ptr->mUserDefCategoryHeight) {
d_ptr->mUserDefCategoryHeight = std::make_unique< int >(h);
} else {
*(d_ptr->mUserDefCategoryHeight) = h;
}
resizeStackedContainerWidget();
if (resizeByNow) {
updateRibbonGeometry();
}
}
void SARibbonBar::onWindowTitleChanged(const QString& title)
{
Q_UNUSED(title);
update();
}
void SARibbonBar::onWindowIconChanged(const QIcon& i)
{
if (quickAccessBar()) {
quickAccessBar()->setIcon(i);
}
}
/**
* @brief category的名字发生改变触发
* @param title
*/
void SARibbonBar::onCategoryWindowTitleChanged(const QString& title)
{
// 全部更新一遍
Q_UNUSED(title);
updateCategoryTitleToTabName();
}
///
/// \brief ribbon的显示界面隐藏
///
void SARibbonBar::onStackWidgetHided()
{
}
/**
* @brief 标签切换触发槽函数
* @param index
*/
void SARibbonBar::onCurrentRibbonTabChanged(int index)
{
QVariant var = d_ptr->mRibbonTabBar->tabData(index);
SARibbonCategory* category = nullptr;
if (var.isValid()) {
_SARibbonTabData p = var.value< _SARibbonTabData >();
category = p.category;
}
if (!category) {
return;
}
if (d_ptr->mStackedContainerWidget->currentWidget() != category) {
d_ptr->mStackedContainerWidget->setCurrentWidget(category);
}
Q_EMIT currentRibbonTabChanged(index);
if (isMinimumMode()) {
d_ptr->mRibbonTabBar->clearFocus();
if (!d_ptr->mStackedContainerWidget->isVisible()) {
if (d_ptr->mStackedContainerWidget->isPopupMode()) {
// 在stackedContainerWidget弹出前先给tabbar一个QHoverEvent,让tabbar知道鼠标已经移开
QHoverEvent ehl(QEvent::HoverLeave,
d_ptr->mRibbonTabBar->mapToGlobal(QCursor::pos()),
d_ptr->mRibbonTabBar->mapToGlobal(QCursor::pos()));
QApplication::sendEvent(d_ptr->mRibbonTabBar, &ehl);
resizeStackedContainerWidget();
d_ptr->mStackedContainerWidget->setFocus();
// exec之前先发射信息号否则会被exec阻塞
d_ptr->mStackedContainerWidget->show();
}
}
}
}
/**
* @brief ribbon tab bar单击
*
* 此实现必须在eventfilter中传递stackedwidget的QEvent::MouseButtonDblClick事件到tabbar中否则会导致双击变两次单击
*
* 单击事件仅用于响应点击同一个tab时
* @param index
*/
void SARibbonBar::onCurrentRibbonTabClicked(int index)
{
if (index != d_ptr->mRibbonTabBar->currentIndex()) {
// 点击的标签不一致通过changed槽去处理
return;
}
if (this->isMinimumMode()) {
if (!this->d_ptr->mStackedContainerWidget->isVisible()) {
if (this->d_ptr->mStackedContainerWidget->isPopupMode()) {
// 在stackedContainerWidget弹出前先给tabbar一个QHoverEvent,让tabbar知道鼠标已经移开
QHoverEvent ehl(QEvent::HoverLeave,
d_ptr->mRibbonTabBar->mapToGlobal(QCursor::pos()),
d_ptr->mRibbonTabBar->mapToGlobal(QCursor::pos()));
QApplication::sendEvent(d_ptr->mRibbonTabBar, &ehl);
// 弹出前都调整一下位置,避免移动后位置异常
resizeStackedContainerWidget();
this->d_ptr->mStackedContainerWidget->setFocus();
this->d_ptr->mStackedContainerWidget->exec();
}
}
}
}
/**
* @brief ribbon tab bar双击
*
* 默认情况下双击会切换最小和正常模式
* @param index
*/
void SARibbonBar::onCurrentRibbonTabDoubleClicked(int index)
{
Q_UNUSED(index);
setMinimumMode(!isMinimumMode());
}
void SARibbonBar::onContextsCategoryPageAdded(SARibbonCategory* category)
{
Q_ASSERT_X(category != nullptr, "onContextsCategoryPageAdded", "add nullptr page");
d_ptr->mStackedContainerWidget->addWidget(category); // 这里stackedWidget用append其他地方都应该使用insert
}
/**
* @brief 上下文标签管理的标签的名字发生变换
* @param category
* @param title
*/
void SARibbonBar::onContextsCategoryCategoryNameChanged(SARibbonCategory* category, const QString& title)
{
Q_UNUSED(category);
Q_UNUSED(title);
updateCategoryTitleToTabName();
}
/**
* @brief 标签移动的信号
* @param from
* @param to
*/
void SARibbonBar::onTabMoved(int from, int to)
{
const QSignalBlocker blocker(d_ptr->mStackedContainerWidget);
// 调整stacked widget的顺序调整顺序是为了调用categoryPages函数返回的QList<SARibbonCategory *>顺序和tabbar一致
d_ptr->mStackedContainerWidget->moveWidget(from, to);
}
/**
* @brief 根据SARibbonCategory*指针查找tabbar的index
*
* @param c SARibbonCategory对应的QObject指针
* @return 如果没有找到,返回-1
* @note 此函数不会调用SARibbonCategory*的任何方法因此可以在SARibbonCategory的destroyed槽中调用
*/
int SARibbonBar::tabIndex(SARibbonCategory* obj)
{
const int size = d_ptr->mRibbonTabBar->count();
for (int i = 0; i < size; ++i) {
QVariant v = d_ptr->mRibbonTabBar->tabData(i);
if (v.isValid()) {
_SARibbonTabData innervalue = v.value< _SARibbonTabData >();
if (innervalue.category == obj) {
return (i);
}
}
}
// 如果找不到就从stackedwidget中找
return (-1);
}
void SARibbonBar::resizeAll()
{
if (isLooseStyle()) {
resizeInLooseStyle();
} else {
resizeInCompactStyle();
}
update();
}
/**
* @brief 把ribbonbar的内容同步进各个category中
* @param autoUpdate
*/
void SARibbonBar::synchronousCategoryData(bool autoUpdate)
{
iterate([ this ](SARibbonCategory* c) -> bool {
c->setEnableShowPannelTitle(this->isEnableShowPannelTitle());
c->setPannelTitleHeight(this->pannelTitleHeight());
c->setCategoryAlignment(this->ribbonAlignment());
c->setPannelLayoutMode(this->pannelLayoutMode());
return true;
});
//! 直接给一个resizeevent让所有刷新
if (autoUpdate) {
QResizeEvent* e = new QResizeEvent(size(), QSize());
QApplication::postEvent(this, e);
}
}
/**
@brief 激活tabbar右边的按钮群
@return 返回右边的按钮群指针
*/
SARibbonButtonGroupWidget* SARibbonBar::activeRightButtonGroup()
{
if (nullptr == d_ptr->mRightButtonGroup) {
d_ptr->mRightButtonGroup = RibbonSubElementFactory->craeteButtonGroupWidget(this);
}
d_ptr->mRightButtonGroup->show();
return d_ptr->mRightButtonGroup;
}
/**
@brief 返回右边的按钮群指针
@return 如果没有创建返回nullptr
*/
SARibbonButtonGroupWidget* SARibbonBar::rightButtonGroup()
{
return d_ptr->mRightButtonGroup;
}
/**
@brief 激活QuickAccessBar
@return
*/
SARibbonQuickAccessBar* SARibbonBar::activeQuickAccessBar()
{
if (nullptr == d_ptr->mQuickAccessBar) {
d_ptr->mQuickAccessBar = RibbonSubElementFactory->createQuickAccessBar(this);
d_ptr->mQuickAccessBar->setObjectName(QStringLiteral("objSARibbonQuickAccessBar"));
d_ptr->mQuickAccessBar->setIcon(windowIcon());
}
d_ptr->mQuickAccessBar->show();
return d_ptr->mQuickAccessBar;
}
SARibbonQuickAccessBar* SARibbonBar::quickAccessBar()
{
return (d_ptr->mQuickAccessBar);
}
/**
* @brief 设置ribbonbar的风格此函数会重新设置所有元素包括button的布局方式
* 尤其是从三行变到两行的过程,重设的内容较多
* @note 此函数会自动触发ResizeEvent不需要手动调用
* @note 默认情况下2行模式不换行,如果想在两行模式换行在调用SARibbonBar::setRibbonStyle后再SARibbonToolButton::setEnableWordWrap(true)
* ,同时再调用updateRibbonElementGeometry()刷新布局
* @param v 样式,见@ref SARibbonBar::RibbonStyle
*/
void SARibbonBar::setRibbonStyle(SARibbonBar::RibbonStyles v)
{
// 先幅值给变量
d_ptr->mRibbonStyle = v;
#if SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "setRibbonStyle(" << v << ")" //
<< "\n isThreeRowStyle=" << isThreeRowStyle() //
<< "\n isTwoRowStyle=" << isTwoRowStyle() //
<< "\n isLooseStyle=" << isLooseStyle() //
<< "\n isCompactStyle=" << isCompactStyle() //
;
#endif
// 执行判断
setEnableWordWrap(isThreeRowStyle(v));
setTabOnTitle(isCompactStyle());
d_ptr->mQuickAccessBar->setEnableShowIcon(isLooseStyle());
setEnableShowPannelTitle(isThreeRowStyle(v));
setPannelLayoutMode(isThreeRowStyle(v) ? SARibbonPannel::ThreeRowMode : SARibbonPannel::TwoRowMode);
// 此函数会调用setFixedHeight
synchronousCategoryData(false); // 这里不急着刷新,下面会继续刷新
d_ptr->resetSize();
Q_EMIT ribbonStyleChanged(d_ptr->mRibbonStyle);
}
/**
@brief 返回当前ribbon的风格
@return 返回当前ribbon的风格
*/
SARibbonBar::RibbonStyles SARibbonBar::currentRibbonStyle() const
{
return (d_ptr->mRibbonStyle);
}
/**
@brief 切换到对应标签
@param index 标签索引
*/
void SARibbonBar::setCurrentIndex(int index)
{
d_ptr->mRibbonTabBar->setCurrentIndex(index);
// onCurrentRibbonTabChanged(index);
}
/**
* @brief 返回当前的tab索引
* @return 当前的索引
*/
int SARibbonBar::currentIndex()
{
return (d_ptr->mRibbonTabBar->currentIndex());
}
/**
* @brief 确保标签显示出来tab并切换到对应页
* @param category 标签指针
*/
void SARibbonBar::raiseCategory(SARibbonCategory* category)
{
int index = d_ptr->mStackedContainerWidget->indexOf(category);
if (index >= 0) {
setCurrentIndex(index);
}
}
/**
* @brief 判断当前的样式是否为2行
* @return
*/
bool SARibbonBar::isTwoRowStyle() const
{
return (d_ptr->mDefaulePannelLayoutMode == SARibbonPannel::TwoRowMode);
}
/**
* @brief 判断当前的样式是否为3行
* @return
*/
bool SARibbonBar::isThreeRowStyle() const
{
return (d_ptr->mDefaulePannelLayoutMode == SARibbonPannel::ThreeRowMode);
}
/**
* @brief 判断当前的样式是否为宽松样式
* @return
*/
bool SARibbonBar::isLooseStyle() const
{
return (SARibbonBar::isLooseStyle(currentRibbonStyle()));
}
/**
* @brief 判断当前的样式是否为紧凑样式
* @return
*/
bool SARibbonBar::isCompactStyle() const
{
return (SARibbonBar::isCompactStyle(currentRibbonStyle()));
}
/**
* @brief 设置标题的文字颜色
*
* 标题时mainwindow的windowTitle如果要设置标题直接调用SARibbonMainWindow::setWindowTitle 进行设置
*
* 如果不设置标题颜色默认是SARibbonBar的qss的color属性
* @param clr
* @note 此函数不会刷新刷新请自行调用repaint
*/
void SARibbonBar::setWindowTitleTextColor(const QColor& clr)
{
d_ptr->mTitleTextColor = clr;
}
/**
* @brief 获取标题的文字颜色
* @return 如果返回的是无效颜色,!QColor::isValid()说明没有手动设置颜色颜色将跟随SARibbonBar的qss的文字颜色
*/
QColor SARibbonBar::windowTitleTextColor() const
{
return d_ptr->mTitleTextColor;
}
/**
* @brief tabbar 底部会绘制一条线条,此接口定义线条颜色
* @param clr
*/
void SARibbonBar::setTabBarBaseLineColor(const QColor& clr)
{
d_ptr->mTabBarBaseLineColor = clr;
}
/**
* @brief 获取tabbar底部基线颜色
* @return
*/
QColor SARibbonBar::tabBarBaseLineColor() const
{
return d_ptr->mTabBarBaseLineColor;
}
/**
* @brief 更新ribbon的布局数据此函数适用于一些关键性尺寸变化换起ribbon下面元素的布局
*
* @note 此函数调用较慢,避免在高速要求场合调用
*/
void SARibbonBar::updateRibbonGeometry()
{
d_ptr->resetSize();
iterate([](SARibbonCategory* c) -> bool {
c->updateItemGeometry();
return true;
});
// 主题变更后tabbar的长度需要进行刷新
//! 直接给一个resizeevent让所有刷新
QResizeEvent* e = new QResizeEvent(size(), QSize());
QApplication::postEvent(this, e);
}
/**
* @brief SARibbonPannel的布局模式
* @return
*/
SARibbonPannel::PannelLayoutMode SARibbonBar::pannelLayoutMode() const
{
return d_ptr->mDefaulePannelLayoutMode;
}
/**
* @brief SARibbonPannel的布局模式设置
* @param m
*/
void SARibbonBar::setPannelLayoutMode(SARibbonPannel::PannelLayoutMode m)
{
d_ptr->mDefaulePannelLayoutMode = m;
iterate([ m ](SARibbonCategory* c) -> bool {
c->setPannelLayoutMode(m);
return true;
});
}
/**
* @brief 设置tab在title上面这样可以省略title区域
* @param on
*/
void SARibbonBar::setTabOnTitle(bool on)
{
if (d_ptr->mIsTabOnTitle != on) {
d_ptr->mIsTabOnTitle = on;
d_ptr->resetSize();
}
}
/**
* @brief tab是否在title上面
* @return
*/
bool SARibbonBar::isTabOnTitle() const
{
return d_ptr->mIsTabOnTitle;
}
/**
* @brief 设置标题的对齐方式
* @param al
*/
void SARibbonBar::setWindowTitleAligment(Qt::Alignment al)
{
d_ptr->mTitleAligment = al;
}
/**
* @brief 获取标题的对齐方式
* @return
*/
Qt::Alignment SARibbonBar::windowTitleAligment() const
{
return d_ptr->mTitleAligment;
}
/**
* @brief 设置ribbonbar的按钮文字允许换行
* @param on
*/
void SARibbonBar::setEnableWordWrap(bool on)
{
SARibbonToolButton::setEnableWordWrap(on);
updateRibbonGeometry();
}
/**
* @brief 判断是否允许换行
* @return
*/
bool SARibbonBar::isEnableWordWrap() const
{
return SARibbonToolButton::isEnableWordWrap();
}
/**
* @brief pannel标题栏的高度
* @return
*/
int SARibbonBar::pannelTitleHeight() const
{
return d_ptr->mPannelTitleHeight;
}
/**
* @brief 设置pannel的高度
* @param h
*/
void SARibbonBar::setPannelTitleHeight(int h)
{
d_ptr->mPannelTitleHeight = h;
iterate([ h ](SARibbonCategory* c) -> bool {
c->setPannelTitleHeight(h);
return true;
});
}
/**
* @brief 是否pannel显示标题栏
* @return
*/
bool SARibbonBar::isEnableShowPannelTitle() const
{
return d_ptr->mEnableShowPannelTitle;
}
/**
* @brief 设置显示pannel标题
* @param on
*/
void SARibbonBar::setEnableShowPannelTitle(bool on)
{
d_ptr->mEnableShowPannelTitle = on;
iterate([ on ](SARibbonCategory* c) -> bool {
c->setEnableShowPannelTitle(on);
return true;
});
}
/**
* @brief 设置pannel的spacing
* @param n
*/
void SARibbonBar::setPannelSpacing(int n)
{
d_ptr->mPannelSpacing = n;
// 同步当前被SARibbonBar管理的SARibbonCategory的PannelSpacing
iterate([ n ](SARibbonCategory* category) -> bool {
if (category) {
category->setPannelSpacing(n);
}
return true;
});
}
/**
* @brief pannel的spacing
* @return
*/
int SARibbonBar::pannelSpacing() const
{
return d_ptr->mPannelSpacing;
}
/**
* @brief 设置pannel按钮的icon尺寸large action不受此尺寸影响
* @param s
*/
void SARibbonBar::setPannelToolButtonIconSize(const QSize& s)
{
d_ptr->mPannelToolButtonSize = s;
// 同步当前被SARibbonBar管理的SARibbonCategory的PannelSpacing
iterate([ s ](SARibbonCategory* category) -> bool {
if (category) {
category->setPannelToolButtonIconSize(s);
}
return true;
});
}
/**
* @brief pannel按钮的icon尺寸large action不受此尺寸影响
* @return
*/
QSize SARibbonBar::pannelToolButtonIconSize() const
{
return d_ptr->mPannelToolButtonSize;
}
/**
* @brief ribbonbar内部的StackedWidget
* 所有的category都放置在StackedWidget中
* @return
*/
SARibbonStackedWidget* SARibbonBar::ribbonStackedWidget()
{
return d_ptr->mStackedContainerWidget;
}
/**
* @brief 设置是否显示标题
* @param on
*/
void SARibbonBar::setTitleVisible(bool on)
{
d_ptr->mIsTitleVisible = on;
}
/**
* @brief 判断标题是否显示
* @return
*/
bool SARibbonBar::isTitleVisible() const
{
return d_ptr->mIsTitleVisible;
}
/**
@brief 设置上下文标签的颜色列表
上下文标签显示的时候,会从颜色列表中取颜色进行标签的渲染
@param cls
*/
void SARibbonBar::setContextCategoryColorList(const QList< QColor >& cls)
{
d_ptr->mContextCategoryColorList = cls;
if (d_ptr->mContextCategoryColorList.isEmpty()) {
d_ptr->mContextCategoryColorList = defaultContextCategoryColorList();
}
d_ptr->mContextCategoryColorListIndex = 0;
// 这时需要对已经显示的contextCategoryData的颜色进行重新设置
for (SARibbonContextCategory* c : d_ptr->mContextCategoryList) {
c->setContextColor(d_ptr->getContextCategoryColor());
}
}
/**
@brief 获取上下文标签的颜色列表
@return
*/
QList< QColor > SARibbonBar::contextCategoryColorList() const
{
return d_ptr->mContextCategoryColorList;
}
/**
* @brief 设置contextCategory 标题的颜色
* @param clr
*/
void SARibbonBar::setContextCategoryTitleTextColor(const QColor& clr)
{
d_ptr->mContextCategoryTitleTextColor = clr;
}
/**
* @brief contextCategory 标题的颜色
* @return
*/
QColor SARibbonBar::contextCategoryTitleTextColor() const
{
return d_ptr->mContextCategoryTitleTextColor;
}
/**
@brief 设置ribbon的对齐方式
@param al
*/
void SARibbonBar::setRibbonAlignment(SARibbonAlignment al)
{
d_ptr->mRibbonAlignment = al;
synchronousCategoryData();
}
/**
@brief ribbon的对齐方式
@return
*/
SARibbonAlignment SARibbonBar::ribbonAlignment() const
{
return d_ptr->mRibbonAlignment;
}
/**
* @brief 此函数会遍历SARibbonBar下的所有Category执行函数指针bool(SARibbonCategory*)
* @param fp 函数指针返回false则停止迭代
* @return 返回false代表没有迭代完所有的category中途接收到回调函数的false返回而中断迭代
*/
bool SARibbonBar::iterate(FpCategoryIterate fp)
{
const QList< SARibbonCategory* > cs = categoryPages(true);
for (SARibbonCategory* c : cs) {
if (!fp(c)) {
return false;
}
}
return true;
}
/**
* @brief 此函数会遍历SARibbonBar下的所有Category,并迭代所有的pannel执行函数指针bool(SARibbonPannel*)
* @param fp 函数指针返回false则停止迭代
* @return 返回false代表没有迭代完所有的category中途接收到回调函数的false返回而中断迭代
*/
bool SARibbonBar::iterate(FpPannelIterate fp)
{
const QList< SARibbonCategory* > cs = categoryPages(true);
for (SARibbonCategory* c : cs) {
if (!c->iterate(fp)) {
return false;
}
}
return true;
}
/**
* @brief 设置边角widget可见性对于mdi窗口会出现TopLeftCorner和TopRightCorner两个corner widget
* @param on
* @param c
*/
void SARibbonBar::setCornerWidgetVisible(bool on, Qt::Corner c)
{
if (QWidget* w = cornerWidget(c)) {
w->setVisible(on);
}
}
/**
* @brief SARibbonBar::eventFilter
* @param obj
* @param e
* @return
*/
bool SARibbonBar::eventFilter(QObject* obj, QEvent* e)
{
if (obj) {
// 调整多文档时在窗口模式下的按钮更新
if ((obj == cornerWidget(Qt::TopLeftCorner)) || (obj == cornerWidget(Qt::TopRightCorner))) {
if ((QEvent::UpdateLater == e->type()) || (QEvent::MouseButtonRelease == e->type())
|| (QEvent::WindowActivate == e->type())) {
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
} else if (obj == d_ptr->mStackedContainerWidget) {
// 在stack 是popup模式时点击的是stackedContainerWidget区域外的时候如果是在ribbonTabBar上点击
// 那么忽略掉这次点击把点击下发到ribbonTabBar,这样可以避免stackedContainerWidget在点击ribbonTabBar后
// 隐藏又显示,出现闪烁
if ((QEvent::MouseButtonPress == e->type()) || (QEvent::MouseButtonDblClick == e->type())) {
if (d_ptr->mStackedContainerWidget->isPopupMode()) {
QMouseEvent* mouseEvent = static_cast< QMouseEvent* >(e);
if (!d_ptr->mStackedContainerWidget->rect().contains(mouseEvent->pos())) {
QWidget* clickedWidget = QApplication::widgetAt(mouseEvent->globalPos());
if (clickedWidget == d_ptr->mRibbonTabBar) {
const QPoint targetPoint = clickedWidget->mapFromGlobal(mouseEvent->globalPos());
QMouseEvent* evPress = new QMouseEvent(mouseEvent->type(),
targetPoint,
mouseEvent->globalPos(),
mouseEvent->button(),
mouseEvent->buttons(),
mouseEvent->modifiers());
QApplication::postEvent(clickedWidget, evPress);
return (true);
}
}
}
}
}
}
return (QMenuBar::eventFilter(obj, e));
}
/**
* @brief 根据情况重置tabbar的宽度主要针对wps模式
*/
int SARibbonBar::calcMinTabBarWidth() const
{
// 20200831
// tabBarWidth的宽度原来为endX - x;,现需要根据实际进行调整
// 为了把tabbar没有tab的部分不占用这里的宽度需要根据tab的size来进行设置让tabbar的长度刚刚好这样能让出
// mainwindow的空间接受鼠标事件从而实现拖动等操作否则tabbar占用整个顶栏鼠标无法点击到mainwindow
// 计算tab所占用的宽度
const QMargins& mg = d_ptr->mRibbonTabBar->tabMargin();
return d_ptr->mRibbonTabBar->sizeHint().width() + (mg.left() + mg.right());
}
/**
* @brief 正常模式下的高度
*
* 有可能SARibbonBar::height和mainBarHeight不相等这种情况发生在RibbonState::MinimumRibbonMode状态下
* @return 高度
*/
int SARibbonBar::normalModeMainBarHeight() const
{
return d_ptr->getCurrentNormalModeMainBarHeight();
}
/**
@brief 最小模式下的高度
@return
*/
int SARibbonBar::minimumModeMainBarHeight() const
{
return d_ptr->getCurrentMinimumModeMainBarHeight();
}
/**
* @brief 更新所有的category title对应的tab名
*
* 此函数会对所有的category的名字和tab进行匹配如果匹配不上会重新设置tab名
*/
void SARibbonBar::updateCategoryTitleToTabName()
{
SARibbonTabBar* tab = d_ptr->mRibbonTabBar;
for (int i = 0; i < tab->count(); ++i) {
// 鉴于tab不会很多不考虑效率问题
QVariant var = tab->tabData(i);
if (var.isValid()) {
_SARibbonTabData p = var.value< _SARibbonTabData >();
if (p.category && p.category->categoryName() != tab->tabText(i)) {
tab->setTabText(i, p.category->categoryName());
}
}
}
repaint();
}
/**
* @brief 告知WindowButtonGroup的尺寸
* @param s
*/
void SARibbonBar::setWindowButtonGroupSize(const QSize& s)
{
d_ptr->mWindowButtonSize = s;
}
void SARibbonBar::paintEvent(QPaintEvent* e)
{
Q_UNUSED(e);
if (isLooseStyle()) {
paintInLooseStyle();
} else {
paintInCompactStyle();
}
#ifdef SA_RIBBON_DEBUG_HELP_DRAW
QPainter p(this);
HELP_DRAW_RECT(p, m_d->quickAccessBar->geometry());
HELP_DRAW_RECT(p, m_d->ribbonTabBar->geometry());
HELP_DRAW_RECT(p, m_d->stackedContainerWidget->geometry());
#endif
}
void SARibbonBar::paintInLooseStyle()
{
QPainter p(this);
//!绘制tabbar下的基线这个函数仅仅对office2013主题有用大部分主题都不绘制基线
paintTabbarBaseLine(p);
//! 显示上下文标签
p.save();
QList< _SAContextCategoryManagerData > contextCategoryDataList = d_ptr->mCurrentShowingContextCategory;
// bool isCurrentSelectContextCategoryPage = false;
QPoint contextCategoryRegion(width(), -1);
QMargins border = contentsMargins();
for (int i = 0; i < contextCategoryDataList.size(); ++i) {
QRect contextTitleRect;
QList< int > indexs = contextCategoryDataList[ i ].tabPageIndex;
QColor clr = contextCategoryDataList[ i ].contextCategory->contextColor();
if (!indexs.isEmpty()) {
contextTitleRect = d_ptr->mRibbonTabBar->tabRect(indexs.first());
QRect endRect = d_ptr->mRibbonTabBar->tabRect(indexs.last());
contextTitleRect.setRight(endRect.right());
contextTitleRect.translate(d_ptr->mRibbonTabBar->x(), d_ptr->mRibbonTabBar->y());
contextTitleRect.setHeight(d_ptr->mRibbonTabBar->height() - 1); // 减1像素避免tabbar基线覆盖
contextTitleRect -= d_ptr->mRibbonTabBar->tabMargin();
// 把区域顶部扩展到窗口顶部
contextTitleRect.setTop(border.top());
// 绘制
paintContextCategoryTab(p, contextCategoryDataList[ i ].contextCategory->contextTitle(), contextTitleRect, clr);
// 更新上下文标签的范围,用于控制标题栏的显示
if (contextTitleRect.left() < contextCategoryRegion.x()) {
contextCategoryRegion.setX(contextTitleRect.left());
}
if (contextTitleRect.right() > contextCategoryRegion.y()) {
contextCategoryRegion.setY(contextTitleRect.right());
}
}
// isCurrentSelectContextCategoryPage = indexs.contains(d_ptr->mRibbonTabBar->currentIndex());
// if (isCurrentSelectContextCategoryPage) {
// QPen pen;
// pen.setColor(clr);
// pen.setWidth(1);
// p.setPen(pen);
// p.setBrush(Qt::NoBrush);
// p.drawRect(d_ptr->mStackedContainerWidget->geometry());
// isCurrentSelectContextCategoryPage = false;
// }
}
p.restore();
//! 显示标题等
QWidget* parWindow = parentWidget();
if (parWindow) {
QRect titleRegion;
if (contextCategoryRegion.y() < 0) {
titleRegion.setRect(d_ptr->mQuickAccessBar->geometry().right() + 1,
border.top(),
width() - d_ptr->mIconRightBorderPosition - border.right()
- d_ptr->mWindowButtonSize.width() - d_ptr->mQuickAccessBar->geometry().right() - 1,
titleBarHeight());
} else {
int leftwidth = contextCategoryRegion.x() - d_ptr->mQuickAccessBar->geometry().right()
- d_ptr->mIconRightBorderPosition;
int rightwidth = width() - contextCategoryRegion.y() - d_ptr->mWindowButtonSize.width();
// if (width() - contextCategoryRegion.y() > contextCategoryRegion.x()-x) {
if (rightwidth > leftwidth) {
// 说明右边的区域大一点标题显示在右显示在右边需要减去windowbutton宽度
titleRegion.setRect(contextCategoryRegion.y(), border.top(), rightwidth, titleBarHeight());
} else {
// 说明左边的大一点
titleRegion.setRect(d_ptr->mIconRightBorderPosition + d_ptr->mQuickAccessBar->geometry().right(),
border.top(),
leftwidth,
titleBarHeight());
}
}
#ifdef SA_RIBBON_DEBUG_HELP_DRAW
p.save();
p.setBrush(QColor(255, 0, 0, 120));
p.drawRect(titleRegion);
p.restore();
#endif
paintWindowTitle(p, parWindow->windowTitle(), titleRegion);
}
}
void SARibbonBar::paintInCompactStyle()
{
QPainter p(this);
//!
paintTabbarBaseLine(p);
//! 显示上下文标签
p.save();
QList< _SAContextCategoryManagerData > contextCategoryDataList = d_ptr->mCurrentShowingContextCategory;
QMargins border = contentsMargins();
for (int i = 0; i < contextCategoryDataList.size(); ++i) {
QRect contextTitleRect;
QList< int > indexs = contextCategoryDataList[ i ].tabPageIndex;
QColor clr = contextCategoryDataList[ i ].contextCategory->contextColor();
if (!indexs.isEmpty()) {
contextTitleRect = d_ptr->mRibbonTabBar->tabRect(indexs.first());
QRect endRect = d_ptr->mRibbonTabBar->tabRect(indexs.last());
contextTitleRect.setRight(endRect.right());
contextTitleRect.translate(d_ptr->mRibbonTabBar->x(), d_ptr->mRibbonTabBar->y());
contextTitleRect.setHeight(d_ptr->mRibbonTabBar->height() - 1);
contextTitleRect -= d_ptr->mRibbonTabBar->tabMargin();
// 把区域顶部扩展到窗口顶部
contextTitleRect.setTop(border.top());
// 绘制
paintContextCategoryTab(p, QString(), contextTitleRect, clr);
}
}
p.restore();
//! 显示标题等
QWidget* parWindow = parentWidget();
if (parWindow) {
int start = d_ptr->mRibbonTabBar->x() + d_ptr->mRibbonTabBar->width();
int width = d_ptr->mQuickAccessBar->x() - start;
if (width > 20) {
QRect titleRegion(start, border.top(), width, titleBarHeight());
#ifdef SA_RIBBON_DEBUG_HELP_DRAW
p.save();
p.setBrush(QColor(255, 0, 0, 120));
p.drawRect(titleRegion);
p.restore();
#endif
paintWindowTitle(p, parWindow->windowTitle(), titleRegion);
}
}
}
void SARibbonBar::resizeStackedContainerWidget()
{
QMargins border = contentsMargins();
const QRect& ribbonTabBarGeometry = d_ptr->mRibbonTabBar->geometry();
int x = border.left();
int y = ribbonTabBarGeometry.bottom() + 1;
int w = width() - border.left() - border.right();
int h = d_ptr->categoryHeight();
if (d_ptr->mStackedContainerWidget->isPopupMode()) {
// 弹出模式时,位置为全局位置
QPoint absPosition = mapToGlobal(QPoint(x, y));
x = absPosition.x();
y = absPosition.y();
}
d_ptr->mStackedContainerWidget->setFixedSize(QSize(w, h));
d_ptr->mStackedContainerWidget->setGeometry(x, y, w, h);
}
/**
* @brief 刷新所有ContextCategoryManagerData这个在单独一个Category删除时调用
*/
void SARibbonBar::updateContextCategoryManagerData()
{
const int c = d_ptr->mRibbonTabBar->count();
for (_SAContextCategoryManagerData& cd : d_ptr->mCurrentShowingContextCategory) {
cd.tabPageIndex.clear();
for (int i = 0; i < cd.contextCategory->categoryCount(); ++i) {
SARibbonCategory* category = cd.contextCategory->categoryPage(i);
for (int t = 0; t < c; ++t) {
QVariant v = d_ptr->mRibbonTabBar->tabData(t);
if (v.isValid()) {
_SARibbonTabData d = v.value< _SARibbonTabData >();
if (d.category == category) {
cd.tabPageIndex.append(t);
}
} else {
cd.tabPageIndex.append(-1);
}
}
}
}
}
/**
* @brief 绘制上下文标签的背景
* @param painter 绘图QPainter
* @param title 上下文标签的title
* @param contextRect 上下文标签的绘制区域
* @param color 上下文标签赋予的颜色
*/
void SARibbonBar::paintContextCategoryTab(QPainter& painter, const QString& title, const QRect& contextRect, const QColor& color)
{
// 绘制上下文标签
// 首先有5像素的实体粗线位于顶部
QMargins border = contentsMargins();
painter.save();
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawRect(contextRect);
const int contextLineWidth = 5;
// 绘制很线
QColor gColor = color.darker();
painter.fillRect(QRect(contextRect.x(), contextRect.y(), contextRect.width(), contextLineWidth), gColor);
// 只有在office模式下才需要绘制标题
if (isLooseStyle()) {
if (!title.isEmpty()) {
QRect textRect = QRect(contextRect.x(),
contextRect.y() + contextLineWidth,
contextRect.width(),
contextRect.height() - contextLineWidth - d_ptr->mRibbonTabBar->height());
painter.setPen(contextCategoryTitleTextColor());
painter.drawText(textRect, Qt::AlignCenter, title);
}
}
painter.restore();
}
void SARibbonBar::resizeEvent(QResizeEvent* e)
{
Q_UNUSED(e);
if (isLooseStyle()) {
resizeInLooseStyle();
} else {
resizeInCompactStyle();
}
update();
}
/**
* @brief 重写moveevent是为了在移动时调整isPopupMode状态下的stackedContainerWidget位置
* @param event
*/
void SARibbonBar::moveEvent(QMoveEvent* e)
{
if (d_ptr->mStackedContainerWidget) {
if (d_ptr->mStackedContainerWidget->isPopupMode()) {
// 弹出模式时窗口发生了移动同步调整StackedContainerWidget的位置
resizeStackedContainerWidget();
}
}
QMenuBar::moveEvent(e);
}
/**
* @brief 跟踪字体改变
* @param event
*/
void SARibbonBar::changeEvent(QEvent* e)
{
if (nullptr == e) {
return;
}
switch (e->type()) {
case QEvent::FontChange: {
QFont f = font();
QList< QWidget* > listWidgets = findChildren< QWidget* >();
for (QWidget* w : listWidgets) {
w->setFont(f);
}
updateRibbonGeometry();
} break;
case QEvent::StyleChange: {
updateRibbonGeometry();
} break;
default:
break;
}
QMenuBar::changeEvent(e);
}
bool SARibbonBar::event(QEvent* e)
{
switch (e->type()) {
case QEvent::Show:
// 第一次显示刷新
updateRibbonGeometry();
break;
default:
break;
}
return QMenuBar::event(e);
}
void SARibbonBar::resizeInLooseStyle()
{
synchronousCategoryData(false);
QMargins border = contentsMargins();
int x = border.left();
int y = border.top();
// cornerWidget - TopLeftCorner
const int validTitleBarHeight = d_ptr->titleBarHeight();
const int tabH = d_ptr->tabBarHeigth();
// tabbar的标签不会因为tabbar的高度而铺满tabbar的标签是固定高度的tabbar拉高只会把底部露出来因此systab是理论合理的高度
// 布局corner widget
x += d_ptr->mIconRightBorderPosition + 5;
if (QWidget* connerL = cornerWidget(Qt::TopLeftCorner)) {
if (connerL->isVisibleTo(this)) {
QSize connerSize = connerL->sizeHint();
if (connerSize.height() < validTitleBarHeight) {
int detal = (validTitleBarHeight - connerSize.height()) / 2;
connerL->setGeometry(x, y + detal, connerSize.width(), connerSize.height());
} else {
connerL->setGeometry(x, y, connerSize.width(), validTitleBarHeight);
}
x = connerL->geometry().right() + 5;
}
}
// quick access bar定位
if (d_ptr->mQuickAccessBar) {
if (d_ptr->mQuickAccessBar->isVisibleTo(this)) {
if (d_ptr->mQuickAccessBar->height() != validTitleBarHeight) {
d_ptr->mQuickAccessBar->setFixedHeight(validTitleBarHeight);
}
QSize quickAccessBarSize = d_ptr->mQuickAccessBar->sizeHint();
// 上下留1px的边线
d_ptr->mQuickAccessBar->setGeometry(x, y + 1, quickAccessBarSize.width(), validTitleBarHeight);
}
}
// 第二行开始布局applicationButtontabbartabBarRightSizeButtonGroupWidgetTopRightCorner
x = border.left();
y += validTitleBarHeight; // 此时y值在titlebar下面
// applicationButton 定位
if (d_ptr->mApplicationButton) {
if (d_ptr->mApplicationButton->isVisibleTo(this)) {
// 保证
d_ptr->mApplicationButton->setGeometry(x, y, d_ptr->mApplicationButton->sizeHint().width(), tabH);
x = d_ptr->mApplicationButton->geometry().right();
}
}
// top right是一定要配置的对于多文档窗口子窗口的缩放等按钮就是通过这个窗口实现
// 由于这个窗口一定要在最右,因此先对这个窗口进行布局
// cornerWidget - TopRightCorner
// 获取最右边的位置
int endX = width() - border.right();
if (QWidget* connerW = cornerWidget(Qt::TopRightCorner)) {
if (connerW->isVisibleTo(this)) {
QSize connerSize = connerW->sizeHint();
endX -= connerSize.width();
if (connerSize.height() < tabH) {
int detal = (tabH - connerSize.height()) / 2;
connerW->setGeometry(endX, y + detal, connerSize.width(), connerSize.height());
} else {
connerW->setGeometry(endX, y, connerSize.width(), tabH);
}
}
}
// applicationButton和TopRightCorner完成定位才可以定位tab bar
// tab bar 定位
// tabBar 右边的附加按钮组,这里一般会附加一些类似登录等按钮组
// 20231106 把visible的判断去掉 && d_ptr->mRightButtonGroup->isVisible()
if (d_ptr->mRightButtonGroup) {
QSize wSize = d_ptr->mRightButtonGroup->sizeHint();
endX -= wSize.width();
// 上下留1px的边线
d_ptr->mRightButtonGroup->setGeometry(endX, y + 1, wSize.width(), tabH - 2);
}
// 最后确定tabbar宽度
int tabBarAllowedWidth = endX - x;
if (ribbonAlignment() == SARibbonAlignment::AlignLeft) {
d_ptr->mRibbonTabBar->setGeometry(x, y, tabBarAllowedWidth, tabH);
} else {
// 居中对齐的情况下Tab要居中显示
// 得到tab的推荐尺寸
int mintabBarWidth = calcMinTabBarWidth();
if (mintabBarWidth >= tabBarAllowedWidth) {
// 这时tabbar没有居中对齐的必要性空间位置不够了
d_ptr->mRibbonTabBar->setGeometry(x, y, tabBarAllowedWidth, tabH);
} else {
// 说明tabbar的宽度有居中的可能性
int xoffset = (tabBarAllowedWidth - mintabBarWidth) / 2;
d_ptr->mRibbonTabBar->setGeometry(x + xoffset, y, mintabBarWidth, tabH);
}
}
resizeStackedContainerWidget();
}
void SARibbonBar::resizeInCompactStyle()
{
synchronousCategoryData(false);
QMargins border = contentsMargins();
int x = border.left();
int y = border.top();
const int validTitleBarHeight = titleBarHeight();
// 先布局右边内容
// cornerWidget - TopRightCorner
int endX = width() - border.right() - d_ptr->mWindowButtonSize.width();
if (QWidget* connerW = cornerWidget(Qt::TopRightCorner)) {
if (connerW->isVisibleTo(this)) {
QSize connerSize = connerW->sizeHint();
endX -= connerSize.width();
if (connerSize.height() < validTitleBarHeight) {
int detal = (validTitleBarHeight - connerSize.height()) / 2;
connerW->setGeometry(endX, y + detal, connerSize.width(), connerSize.height());
} else {
connerW->setGeometry(endX, y, connerSize.width(), validTitleBarHeight);
}
}
}
// tabBar 右边的附加按钮组
// 20231106 把visible的判断去掉 && d_ptr->mRightButtonGroup->isVisible()
if (d_ptr->mRightButtonGroup) {
QSize wSize = d_ptr->mRightButtonGroup->sizeHint();
endX -= wSize.width();
// 上下留1px的边线
d_ptr->mRightButtonGroup->setGeometry(endX, y + 1, wSize.width(), validTitleBarHeight - 2);
}
// quick access bar定位
if (d_ptr->mQuickAccessBar) {
if (d_ptr->mQuickAccessBar->isVisibleTo(this)) {
QSize quickAccessBarSize = d_ptr->mQuickAccessBar->sizeHint();
endX -= quickAccessBarSize.width();
// 上下留1px的边线
d_ptr->mQuickAccessBar->setGeometry(endX, y + 1, quickAccessBarSize.width(), validTitleBarHeight - 2);
}
}
// cornerWidget - TopLeftCorner
if (QWidget* connerL = cornerWidget(Qt::TopLeftCorner)) {
if (connerL->isVisibleTo(this)) {
QSize connerSize = connerL->sizeHint();
endX -= connerSize.width();
if (connerSize.height() < validTitleBarHeight) {
int detal = (validTitleBarHeight - connerSize.height()) / 2;
connerL->setGeometry(endX, y + detal, connerSize.width(), connerSize.height());
} else {
connerL->setGeometry(endX, y, connerSize.width(), validTitleBarHeight);
}
}
}
// tab 的y值需要重新计算
// 紧凑模式下tabbarHeight不生效
int tabH = tabBarHeight();
if (tabH > validTitleBarHeight) {
// 这种直接把tabH设置为validTitleBarHeight
tabH = validTitleBarHeight;
}
y = y + validTitleBarHeight - tabH; // 如果tabH较小则下以让tab底部和title的底部对齐
// applicationButton 定位与TabBar同高
if (d_ptr->mApplicationButton) {
if (d_ptr->mApplicationButton->isVisibleTo(this)) {
d_ptr->mApplicationButton->setGeometry(x, y, d_ptr->mApplicationButton->sizeHint().width(), tabH);
x = d_ptr->mApplicationButton->geometry().right() + 2;
}
}
// tab bar 定位 wps模式下applicationButton的右边就是tab bar
int tabBarAllowedWidth = endX - x;
// 20200831
// tabBarWidth的宽度原来为endX - x;,现需要根据实际进行调整
// 为了把tabbar没有tab的部分不占用这里的宽度需要根据tab的size来进行设置让tabbar的长度刚刚好这样能让出
// mainwindow的空间接受鼠标事件从而实现拖动等操作否则tabbar占用整个顶栏鼠标无法点击到mainwindow
// 计算tab所占用的宽度
int mintabBarWidth = calcMinTabBarWidth();
if (ribbonAlignment() == SARibbonAlignment::AlignLeft) {
if (mintabBarWidth < tabBarAllowedWidth) {
tabBarAllowedWidth = mintabBarWidth;
}
d_ptr->mRibbonTabBar->setGeometry(x, y, tabBarAllowedWidth, tabH);
} else {
// 居中对齐
if (mintabBarWidth >= tabBarAllowedWidth) {
// 这时tabbar没有居中对齐的必要性空间位置不够了
d_ptr->mRibbonTabBar->setGeometry(x, y, tabBarAllowedWidth, tabH);
} else {
// 说明tabbar的宽度有居中的可能性
int xoffset = (tabBarAllowedWidth - mintabBarWidth) / 2;
d_ptr->mRibbonTabBar->setGeometry(x + xoffset, y, mintabBarWidth, tabH);
}
}
// 调整整个stackedContainer
resizeStackedContainerWidget();
}
/**
* @brief 绘制tabbar下的基准线这个方法仅仅在office2013模式下需要
* @param painter
*/
void SARibbonBar::paintTabbarBaseLine(QPainter& painter)
{
if (!d_ptr->mTabBarBaseLineColor.isValid()) {
return;
}
painter.save();
// 在tabbar下绘制一条线
const int lineY = d_ptr->mRibbonTabBar->geometry().bottom();
QPen pen(d_ptr->mTabBarBaseLineColor);
QMargins border = contentsMargins();
pen.setWidth(1);
pen.setStyle(Qt::SolidLine);
painter.setPen(pen);
painter.drawLine(QPoint(border.left(), lineY), QPoint(width() - border.right() - 1, lineY));
painter.restore();
}
///
/// \brief 绘制标题栏
/// \param painter
/// \param title 标题
/// \param contextCategoryRegion 当前显示的上下文标签的范围,上下文标签是可以遮挡标题栏的,因此需要知道上下文标签的范围
/// x表示左边界y表示右边界
///
void SARibbonBar::paintWindowTitle(QPainter& painter, const QString& title, const QRect& titleRegion)
{
// 如果标题不显示直接跳出
if (!isTitleVisible()) {
return;
}
painter.save();
if (d_ptr->mTitleTextColor.isValid()) {
painter.setPen(d_ptr->mTitleTextColor);
}
painter.drawText(titleRegion, d_ptr->mTitleAligment, title);
painter.restore();
}
#if SA_DEBUG_PRINT_SARIBBONBAR
QDebug operator<<(QDebug debug, const SARibbonBar& ribbon)
{
QDebugStateSaver saver(debug);
QFontMetrics fm = ribbon.fontMetrics();
debug.nospace() << "SARibbonBar(" << ribbon.versionString() << ")" //
<< "\nribbon font metrics info:" //
<< "\n - lineSpacing:" << fm.lineSpacing() //
<< "\n - height:" << fm.height() //
<< "\n - em:" << fm.boundingRect("M").width() //
<< "\n - ex:" << fm.boundingRect("X").height() //
<< "\nribbon info:" //
<< "\n -mTitleBarHeight=" << ribbon.d_ptr->mTitleBarHeight //
<< "\n -mTabBarHeight=" << ribbon.d_ptr->mTabBarHeight //
<< "\n -mPannelTitleHeight=" << ribbon.d_ptr->mPannelTitleHeight //
<< "\n -mCategoryHeight=" << ribbon.d_ptr->mCategoryHeight //
<< "\n -mIsTabOnTitle=" << ribbon.d_ptr->mIsTabOnTitle //
<< "\n -mEnableShowPannelTitle=" << ribbon.d_ptr->mEnableShowPannelTitle //
<< "\n -mWindowButtonSize=" << ribbon.d_ptr->mWindowButtonSize //
<< "\n -mIconRightBorderPosition=" << ribbon.d_ptr->mIconRightBorderPosition //
;
return debug;
}
#endif