#include "SARibbonBar.h" #include #include #include #include #include #include #include #include #include #include #include #include #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=12,lineSpacing=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 添加一个category,category的位置在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为准,因为存在contextcategory,contextcategory正常是不显示的 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顺序和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); } } // 第二行,开始布局applicationButton,tabbar,tabBarRightSizeButtonGroupWidget,TopRightCorner 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