[Click here for English](./readme.md)

<div align="center">
<p>
<img src="https://img.shields.io/badge/C++-17-blue"/>
<img src="https://img.shields.io/badge/Qt-5.14+-green"/>
<img src="https://img.shields.io/badge/Qt-6-green"/>
<img src="https://img.shields.io/badge/license-MIT-yellow"/>
</p>
<p>
<img src="https://img.shields.io/badge/windows-0077d6"/>
<img src="https://img.shields.io/badge/ubuntu-ed6432"/>
<img src="https://img.shields.io/badge/macos-000"/>
</p>
</div>

QQ交流群:434014314

<div align="center">
<img src="./doc/SARibbon-qq%E4%BA%A4%E6%B5%81%E7%BE%A4.jpg"/>
</div>


||Windows(2019, latest)|Linux ubuntu(20.04, latest)|Mac(11-latest)|
|:-|:-|:-|:-|
|Qt5.12|[![cmake-win-qt5.12](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.12.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.12.yml)|[![CMake-Linux-Qt5.12](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.12.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.12.yml)|[![cmake-mac-qt5.12](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.12.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.12.yml)|
|Qt5.13|[![cmake-win-qt5.13](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.13.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.13.yml)|[![CMake-Linux-Qt5.13](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.13.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.13.yml)|[![cmake-mac-qt5.13](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.13.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.13.yml)|
|Qt5.14|[![cmake-win-qt5.14](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.14.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.14.yml)|[![CMake-Linux-Qt5.14](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.14.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.14.yml)|[![cmake-mac-qt5.14](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.14.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.14.yml)|
|Qt5.15|[![cmake-win-qt5.15](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.15.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt5.15.yml)|[![CMake-Linux-Qt5.15](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.15.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt5.15.yml)|[![cmake-mac-qt5.15](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.15.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt5.15.yml)|
|Qt6.0|[![cmake-win-qt6.0](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.0.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.0.yml)|[![CMake-Linux-Qt6.0](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.0.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.0.yml)|[![cmake-mac-qt6.0](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.0.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.0.yml)|
|Qt6.1|[![cmake-win-qt6.1](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.1.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.1.yml)|[![CMake-Linux-Qt6.1](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.1.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.1.yml)|[![cmake-mac-qt6.1](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.1.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.1.yml)|
|Qt6.2|[![cmake-win-qt6.2](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.2.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.2.yml)|[![CMake-Linux-Qt6.2](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.2.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.2.yml)|[![cmake-mac-qt6.2](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.2.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.2.yml)|
|Qt6.3|[![cmake-win-qt6.3](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.3.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.3.yml)|[![CMake-Linux-Qt6.3](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.3.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.3.yml)|[![cmake-mac-qt6.3](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.3.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.3.yml)|
|Qt6.4|[![cmake-win-qt6.4](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.4.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.4.yml)|[![CMake-Linux-Qt6.4](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.4.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.4.yml)|[![cmake-mac-qt6.4](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.4.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.4.yml)|
|Qt6.5|[![cmake-win-qt6.5](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.5.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.5.yml)|[![CMake-Linux-Qt6.5](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.5.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.5.yml)|[![cmake-mac-qt6.5](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.5.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.5.yml)|
|Qt6.6|[![cmake-win-qt6.6](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.6.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-win-qt6.6.yml)|[![CMake-Linux-Qt6.6](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.6.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-linux-qt6.6.yml)|[![cmake-mac-qt6.6](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.6.yml/badge.svg)](https://github.com/czyt1988/SARibbon/actions/workflows/cmake-mac-qt6.6.yml)|

# SARibbon简介

这是一个`Qt`下的`Ribbon`界面控件，提供了类似微软Office系列软件的操作界面。

- `SARibbon`适用于大型软件、工业软件、复杂软件的ui
- `SARibbon`在设计时参考了MFC Ribbon接口的命名风格
- `SARibbon`的界面样式参考了微软Office系列软件以及WPS软件的Ribbon界面，并结合了两者的优点
- `SARibbon`是一个可定义多种主题风格的Ribbon控件，它能通过qss快速的定义出自己想要的主题风格

为了方便大型软件的开发，`SARibbon`对常用的一些功能性控件进行了封装，例如：[颜色选择按钮和颜色选择画板](https://github.com/czyt1988/SAColorWidgets)

## 功能特点

- 针对Ribbon的布局和显示

![Ribbon的布局和显示](./doc/screenshot/SARibbonBar-overview.gif)

- 支持最小化模式，ribbon只显示标签（默认双击标签会进行切换）,支持上下文标签tab

![SARibbon最小化模式](./doc/screenshot/SARibbonBar-minMode.gif)

- 支持quickAccessBar（word快速菜单），在不同布局模式下会有不同的显示效果

![SARibbon-quickAccessBar](./doc/screenshot/SARibbonBar-quickAccessBar.gif)

- 支持4种不同的ribbon button，普通按钮，延迟弹出菜单按钮，菜单按钮，action菜单按钮（action菜单按钮是此ribbon控件最主要解决的问题之一）

![SARibbon-4种不同的ribbon button](./doc/screenshot/SARibbonBar-ribbonbutton.gif)

- 支持4种不同风格的布局样式

![SARibbon-4种不同风格的布局样式](./doc/screenshot/SARibbonBar-style.gif)

- 支持qss对ribbon进行自定义设置，可实时切换主题,内置了5种不同风格的主题

win7主题：
![SARibbon-theme-win7](./doc/screenshot/SARibbon-theme-win7.png)
office2013主题：
![SARibbon-theme-office2013](./doc/screenshot/SARibbon-theme-office2013.png)
office2016主题：
![SARibbon-theme-office2016](./doc/screenshot/SARibbon-theme-office2016.png)
office2021主题：
![SARibbon-theme-office2021](./doc/screenshot/SARibbon-theme-office2021.png)
dark主题：
![SARibbon-theme-dark](./doc/screenshot/SARibbon-theme-dark.png)

- 提供Gallery控件

![](./doc/screenshot/SARibbonBar-gallery.gif)

- 支持超长滚动和Option Action

![](./doc/screenshot/SARibbonBar-option-action.gif)

- 提供居中对齐模式

![](./doc/screenshot/SARibbon-aligment-center.png)

- 支持4K屏和多屏幕扩展
- 支持linux和MacOS（界面未做深度适配）


MIT协议，欢迎大家使用并提出意见

[gitee(码云) - https://gitee.com/czyt1988/SARibbon](https://gitee.com/czyt1988/SARibbon)

[github - https://github.com/czyt1988/SARibbon](https://github.com/czyt1988/SARibbon)


# 构建

这里建议使用cmake进行构建，cmake构建完成后并进行安装（install），使用cmake构建，你的工程既可以用cmake引入SARibbonBar，也可以使用qmake引入SARibbonBar

SARibbon提供qmake和cmake两种构建方式，同时提供了一个集成的SARibbon.h和SARibbon.cpp文件方便静态的嵌入到单一工程

> SARibbon支持第三方无边框库[QWindowkit](https://github.com/stdware/qwindowkit)，同时也支持简单的无边框方案，如果你需要操作系统原生的窗口支持，如windows7以后的贴边处理，windows11的最大化按钮悬停的效果，建议开启[QWindowkit](https://github.com/stdware/qwindowkit)库，[QWindowkit](https://github.com/stdware/qwindowkit)库还能较好解决多屏幕移动问题

开启`QWindowkit`后的效果如下：

![](./doc/pic/set-qwindowkit-on-snap.gif)

如果你要依赖[QWindowkit](https://github.com/stdware/qwindowkit)库，需要先编译[QWindowkit](https://github.com/stdware/qwindowkit)库，[QWindowkit](https://github.com/stdware/qwindowkit)库作为SARibbon项目的submodules，如果在`git clone`时没有附带`--recursive`参数，需要执行`submodule update`命令:

```shell
git submodule update --init --recursive
```

> 用户指定使用[QWindowkit](https://github.com/stdware/qwindowkit)后，要求C++标准最低为C++17否则最低要求为c++14

## 编译为动态库

具体构建过程，见文档：[SARibbon构建](./doc/how-to-build-cn.md)

## 直接引入工程（静态）

你可以不对SARibbon库进行构建，直接引入，通过cmake构建安装后，也提供静态文件引入的内容

SARibbon提供了合并好的`SARibbon.h`文件和`SARibbon.cpp`文件，只需要在自己的工程中引入这两个文件，同时把资源文件和第三方库文件引入就可以使用，无需编译为动态库或者静态库，可以参考StaticExample例子（位于`example/StaticExample`），静态嵌入将使用到`SARibbon.h`、`SARibbon.cpp`、`SARibbon.pri`、`SARibbonBar/resource.qrc`这4个文件，以及`SARibbonBar/resource`这个文件夹：

你的工程目录将如下所示：

```
|-you-project-dir
|  |-you-project.pro
|  |-SARibbon.h
|  |-SARibbon.cpp
|  |-SARibbon.pri
|  |-SARibbonBar
|     |-resource.qrc
|     |-resource(直接把SARibbonBar下的resource完整复制过来)
|        |-resource files
```

### 使用qmake

使用qmake编译，有如下步骤：

- 1. 把`SARibbon.h`、`SARibbon.cpp`、`SARibbon.pri`拷贝到自己工程目录下
- 2. 在自己工程目录下创建`SARibbonBar`文件夹
- 3. 把源码中的`src/SARibbonBar/resource.qrc`文件拷贝到自己工程目录下的`SARibbonBar`文件夹
- 4. 把源码`src/SARibbonBar`下的`resource`文件夹和`3rdparty`文件夹拷贝到自己工程目录下的`SARibbonBar`文件夹中
- 5. 在自己工程的pro文件中引入`SARibbon.pri`文件，如：`include($$PWD/SARibbon.pri)`

qmake可以通过`SARibbon.pri`文件设置是否开启第三方无边框库

### 使用cmake

使用cmake的话参考StaticExample（位于`example/StaticExample`）例子的cmake编写方式编写，主要把上诉的几个文件引入工程即可

```cmake
SET(SARIBBON_DIR {SARibbon.h和SARibbon.cpp所在目录})
set(SARIBBON_STATIC_FILE
    ${SARIBBON_DIR}/SARibbon.h
    ${SARIBBON_DIR}/SARibbon.cpp
    ${SARIBBON_DIR}/SARibbonBar/resource.qrc
)
add_executable({you-target} 
            {你项目的cpp和头文件}
            ${SARIBBON_STATIC_FILE}
)
```

基于cmake在静态引入方案中，如果你需要使用`QWindowkit`,你的工程首先要添加好`QWindowkit`库，并且增加预定义宏：

```cmake
find_package(QWindowKit)
target_link_libraries({you-target} PRIVATE QWindowKit::Widgets)
target_compile_definitions({you-target} PRIVATE SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1)
```

否则你需要把它设置为0

```cmake
target_compile_definitions({you-target} PRIVATE SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=0)
```

# 使用方法

## 引入库

在编译完成后，按照如下方法引入SARibbon

### qmake（不推荐）

就算你的工程使用的是qmake，但构建过程还是建议使用cmake，并通过install命令，形成标准的库安装，通过cmake安装的SARibbon，也可以通过qmake引入

#### 通过qmake构建的引入

如果你的SARibbon是通过qmake构建，那么会在SARibbon目录下生成`bin_qt{Qt version}_{MSVC/GNU}_x{32/64}`文件夹，简单的引入，你只需把如下文件按目录结构拷贝到你的工程中

先在你的工程中建立一个3rdparty文件夹，再把整个SARibbon文件夹拷贝过去，SARibbon内部已经有几个pri文件可以很方便的让你把工程引入到自己目录中，`./importSARibbonBarLib.pri`文件是用于引入SARibbon库的

在自己的Qt工程pro文件中加入如下语句即可

```shell
include($$PWD/3rdparty/SARibbon/importSARibbonBarLib.pri)
```

qmake的编译过程会在SARibbon下生成`bin_qt{Qt version}_{MSVC/GNU}_x{32/64}`文件夹，库文件和dll文件都在此文件夹下，importSARibbonBarLib.pri会自动把这个文件夹下的库引用进来

此时你的工程目录结构大致如下：

```
|-[you-project-dir]
|  |-you-project.pro
|  |-[3rdparty]
|     |-[SARibbon](直接把SARibbon完整复制过来)
|        |-importSARibbonBarLib.pri
|        |-SARibbonBar.pri
|        |-common.pri
|        |-[bin_qtx.x.x_{MSVC/GNU}_x{32/64}]
|        |-[src]
|        |   |-[SARibbonBar]
```

几个pri文件的说明：

- common.pri
 qmake配置文件，里面定义配置信息，在编译库的时候可以通过此文件改动配置，引入的时候要和编译的时候配置一致

 - importSARibbonBarLib.pri
  用于引入库，实际就是include(SARibbonBar.pri)

 - SARibbonBar.pri
  用于引入库的具体实现

- src/SARibbon.pri
  针对SARibbon.h和SARibbon.cpp的pri文件，如果你用静态集成模式，使用此pri文件
---

你自己可以把所有头文件复制到一个文件夹，并引入SARibbon库，具体操作和引入其它库方式一致，但这里需要注意的是，你如果自己引入，需要指定一下以下几个宏：

- SARIBBON_USE_3RDPARTY_FRAMELESSHELPER

 这个宏指定你是否使用第三方无边框库`QWindowkit`,如果编译时，使用了`QWindowkit`，那么，需要定义SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1，否则为0，在你的pro文件中，你可以这样写：

```shell
# 编译使用了QWindowkit
DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1
# 编译没有使用QWindowkit
DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1
```

#### 通过cmake构建的引入

就算你的项目使用qmake管理，也建议SARibbon的构建使用cmake构建并进行安装，因为cmake构建并进行安装后，也提供了qmake的引入方案，位于安装位置的`lib/qmake/SARibbonBar`目录下，你仅仅需要在你pro文件中加入如下语句即可引入：

```shell
include({SARibbon安装目录}/lib/qmake/SARibbonBar/SARibbonBar.pri)
```

### cmake（推荐）

建议在执行install后使用此库

cmake引入方法：

```cmake
find_package(SARibbonBar REQUIRED)
...
target_link_libraries({your_target_name} PUBLIC SARibbonBar::SARibbonBar)
```

如果find_package找不到`SARibbonBar`，你需要把`SARibbon`安装位置告诉cmake工程

```
set(SARibbonBar_DIR "[你的SARibbonBar安装根目录]/lib/cmake")
```

如果你编译时，`SARIBBON_INSTALL_IN_CURRENT_DIR`选项设置为`ON`(默认)，那会在SARibbon工程根目录下下生成`bin_qt{Qt version}_{MSVC/GNU}_x{32/64}`文件夹作为安装目录，这是为了和qmake统一，也是为了方便一个操作系统进行多个不同版本qt和编译器进行区分安装，否则，windows系统会默认安装在`C:\Program Files\SARibbonBar`文件夹下

通过cmake引入SARibbon可参考`example/MainWindowExample/CMakeLists.txt`

具体见文档：[SARibbon构建](./doc/how-to-build-cn.md)

## 快速开始

Ribbon是把菜单栏和工具栏合并了，并通过一个tab控件进行展示，Ribbon是无法简单的使用Tab+Toolbar替代的，涉及到很多细节问题，`SARibbon`在设计时参考了MFC Ribbon接口的命名风格，标签页称之为`Category`(种类)，每个`Category`下面有多个`pannel`（面板），面板下面管理着toolbutton，`pannel`有点类似传统的`Toolbar`，其层次结构如下图所示，这些命名参考了MFC的ribbon界面类

![msdn ribbon 介绍页](./doc/pic/msdn-ribbon.png)

一些常见的名词解释如下

- **Category 类别**，代表一个标签所呈现的内容，对应`SARibbonCategory`
- **Context Category 上下文类别**，这个是一种特殊的类别，它正常不显示，需要基于上下文判断是否应该显示，最常用的就是word中插入图片后，会有图片修改相关的标签出现，如果没选中图片，这个标签就消失，这个就是上下文类别，对应`SARibbonContextCategory`
- **Pannel 面板**，这个是一组菜单的集合，类似一个Toolbar，对应`SARibbonPannel`
- **Application Button 应用按钮**，标签栏最左边的按钮（word就是对应文件按钮），这个按钮会触发一些特殊的页面或菜单，对应`SARibbonApplicationButton`,可以隐藏
- **Quick Access Bar 快速响应栏**，位于最顶部的一个简单工具栏，用于放置一些常用的action，对应`SARibbonQuickAccessBar`
- **Gallery 预览控件**,这是Ribbon最吸引眼球的控件，用直观的图像把功能显示出来，甚至有些会根据上下文进行实时渲染，典型的就是word开始标签下的样式选择，对应`SARibbonGallery`

SARibbonBar的层次如下图所示：

![](./doc/pic/saribbonbar-level.png)

> 注：不同的布局方案影响着`Category`和`Quick Access Bar`的摆放方式，具体可见[SARibbonBar布局方案](#SARibbonBar布局方案)

### 在MainWindow中使用Ribbon

要MainWindow中使用SARibbon，需要把`QMainWindow`替换为`SARibbonMainWindow`，`SARibbonMainWindow`修改了`QMainWindow`对menubar的渲染方式

> 注意，如果使用ui文件，要把原来ui文件的菜单删除，否则可能引起一些异常，如下图所示：
> ![](./doc/pic/remove-menu-bar-at-uifile.png)

示例代码如下:

```cpp
#include "SARibbonMainWindow.h"
class MainWindow : public SARibbonMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget* par = nullptr);
    ...
}
```

`SARibbonMainWindow`也支持普通模式的渲染，SARibbonMainWindow的构造函数第二个参数用于设定是否用ribbon模式：

```cpp
SARibbonMainWindow(QWidget *parent = nullptr, bool useRibbon = true);
```

第二个参数如果设置为false，将会使用普通的菜单工具栏模式，预留这个接口是为了一些项目需要能在ribbon和经典菜单工具栏切换的场景设计的，Ribbon状态和经典状态不支持热切换，如果需要切换，用户可以设置一个配置文件或者注册表，在应用重启时给第二个参数传入false即可进入到经典菜单工具栏模式

### 在QWidget或QDialog中使用SARibbonBar

SARibbonBar支持在QWidget或者QDialog上使用，具体可见例子：`example/WidgetWithRibbon`

项目提供了`SARibbonWidget`类，widget窗口继承`SARibbonWidget`即可实现ribbon效果的窗口

```cpp
#include "SARibbonWidget.h"

class RibbonWidget : public SARibbonWidget
{
	Q_OBJECT
public:
    RibbonWidget(QWidget* parent = nullptr);
};
```

`SARibbonWidget`类提供了`setWidget`方法，可以嵌入任意的widget

```cpp
RibbonWidget::RibbonWidget(QWidget* parent) : SARibbonWidget(parent)
{
    // 直接创建SARibbonBar
    SARibbonBar* ribbonbar = ribbonBar();
    // QWidget模式下，没有必要再显示标题
    ribbonbar->setTitleVisible(false);
    // QWidget模式下，直接使用紧凑模式效果更好
    ribbonbar->setRibbonStyle(SARibbonBar::RibbonStyleCompactThreeRow);
    // 取消applicationbutton
    ribbonbar->setApplicationButton(nullptr);

    //
    //buildRibbon(ribbonbar);
    //
    setWidget(new InnerWidget());
}
```

效果如下：

![Ribbon用在QWidget上](./doc/screenshot/ribbonbar-use-in-qwidget.png)


### 创建Category和Pannel

创建ribbon的顺序是：先创建类别(Category)，再创建面板(Pannel)，最后创建对应的toolbutton（action）

使用`SARibbonBar::addCategoryPage`把Category添加到SARibbonBar中，使用`SARibbonCategory::addPannel`把`Pannel`添加到`Category`中,使用`SARibbonPannel::addAction`可以在Pannel上添加action

下面的代码，演示了添加一个action的示例：

```cpp
//添加主标签页 - 通过addCategoryPage工厂函数添加
SARibbonCategory* categoryMain = ribbon->addCategoryPage(tr("Main"));
 //使用addPannel函数来创建SARibbonPannel，效果和new SARibbonPannel再addPannel一样
SARibbonPannel* pannel1 = categoryMain->addPannel("Panel 1");
QAction* actSave = new QAction(this);
actSave->setText("save");
actSave->setIcon(QIcon(":/icon/icon/save.svg"));
actSave->setObjectName("actSave");
actSave->setShortcut(QKeySequence(QLatin1String("Ctrl+S")));
pannel1->addLargeAction(actSave);
```

上面的操作添加了一个按钮，效果如下图所示：

![](./doc/pic/add-large-action.png)

用户也可以直接new出SARibbonCategory，并添加到pannel中，下面的代码效果和上面的一样：

```cpp
SARibbonCategory* categoryMain = new SARibbonCategory(tr("Main"));
ribbon->addCategoryPage(categoryMain);
SARibbonPannel* pannel1 = new SARibbonPannel("Panel 1");
categoryMain->addPannel(pannel1);
QAction* actSave = new QAction(this);
...
pannel1->addLargeAction(actSave);
```

Ribbon的图标有大有小，通过`addLargeAction`、`addMediumAction`、`addSmallAction`可以组合出不同的布局样式

具体可见[./example/MainWindowExample/mainwindow.cpp](./example/MainWindowExample/mainwindow.cpp)

### ContextCategory 上下文标签

所谓上下文标签是指在特殊情况下才出现的标签/标签组，例如office word在选中图片时会出现图片编辑的上下文标签，如下图所示：

![上下文标签](doc/pic/word-contextcategory.png)

SARibbon中上下文标签对应的类为`SARibbonContextCategory`

上下文标签一般在程序初始化的时候就创建好，平时隐藏，等待需要显示的时候再显示，创建上下文标签如下：

由于上下文标签需要使用时唤起，因此，用一个成员变量保存起来是一个比较好的选择，当然也可以遍历查找（`SARibbonBar::contextCategoryList`可以例举所有的`SARibbonContextCategory`）

头文件：

```cpp
SARibbonContextCategory* m_contextCategory;
```

cpp文件:
```cpp
SARibbonBar* ribbon = ribbonBar();
//创建一个contextCategory，颜色随机
m_contextCategory   = ribbon->addContextCategory(tr("context"), QColor());
SARibbonCategory* contextCategoryPage1 = m_contextCategory->addCategoryPage(tr("Page1"));
//对contextCategoryPage1进行操作
......
SARibbonCategory* contextCategoryPage2 = m_contextCategory->addCategoryPage(tr("Page2"));
//对contextCategoryPage2进行操作
......
```

由`SARibbonContextCategory`创建的`SARibbonCategory`归`SARibbonContextCategory`管理，只有`SARibbonContextCategory`“显示了”,其管理的`SARibbonCategory`才显示，**注意：** `SARibbonContextCategory`并不是一个窗口，所以，它的“显示”打了引号

要显示一个上下文只需要调用`SARibbonBar::showContextCategory`/`SARibbonBar::hideContextCategory`即可:

```cpp
void MainWindow::onShowContextCategory(bool on)
{
    if (on) {
        this->ribbonBar()->showContextCategory(m_contextCategory);
    } else {
        this->ribbonBar()->hideContextCategory(m_contextCategory);
    }
}
```

**注意：** 如果要删除`contextCategory`需要调用`SARibbonBar::destroyContextCategory`，而不是直接delete，调用`SARibbonBar::destroyContextCategory`之后无需再对ContextCategory的指针delete

不同样式的contextCategory有不一样的风格，具体可见：[SARibbon样式](#SARibbon样式)以及[不同样式下的显示对比](#不同样式下的显示对比)

### ApplicationButton

ribbon界面左上角有个特殊且明显的按钮，称之为`applicationButton`，这个按钮一般用于调出菜单，SARibbonBar在构造时默认就创建了`applicationButton`，可以通过如下方式设置其文字：

```cpp
SARibbonBar* ribbon = ribbonBar();
ribbon->applicationButton()->setText(("File"));
```

默认的applicationButton继承自`SARibbonApplicationButton`,而`SARibbonApplicationButton`继承自`QPushButton`，因此你可以对其进行`QPushButton`所有的操作，当然如果想设置自己的Button作为applicationButton也是可以的，只需要调用`SARibbonBar::setApplicationButton`函数即可

### QuickAccessBar和rightButtonGroup

QuickAccessBar是左上角的快速工具栏，rightButtonGroup是右上角的快速工具栏，在office模式下分左右两边，在wps模式下，左右将合起来，统一放到右边

![QuickAccessBar And RightButtonGroup](./doc/screenshot/QuickAccessBarAndRightButtonGroup.png)

SARibbon中：

- QuickAccessBar对应`SARibbonQuickAccessBar`类
- rightButtonGroup对应`SARibbonButtonGroupWidget`类

SARibbonBar在初始化时会默认创建QuickAccessBar和RightButtonGroup，通过`SARibbonBar::quickAccessBar`和`SARibbonBar::rightButtonGroup`即可获取其指针进行操作，示例如下：

```cpp
QAction* MainWindow::createAction(const QString& text, const QString& iconurl, const QString& objName)
{
    QAction* act = new QAction(this);
    act->setText(text);
    act->setIcon(QIcon(iconurl));
    act->setObjectName(objName);
    return act;
}

void MainWindow::initQuickAccessBar(){
    SARibbonBar* ribbon = ribbonBar();
    SARibbonQuickAccessBar* quickAccessBar = ribbon->quickAccessBar();
    quickAccessBar->addAction(createAction("save", ":/icon/icon/save.svg", "save-quickbar"));
    quickAccessBar->addSeparator();
    quickAccessBar->addAction(createAction("undo", ":/icon/icon/undo.svg"),"undo");
    quickAccessBar->addAction(createAction("redo", ":/icon/icon/redo.svg"),"redo");
    quickAccessBar->addSeparator();
}
void MainWindow::initRightButtonGroup(){
    SARibbonBar* ribbon = ribbonBar();
    SARibbonButtonGroupWidget* rightBar = ribbon->rightButtonGroup();
    QAction* actionHelp = createAction("help", ":/icon/icon/help.svg","help");
    connect(actionHelp, &QAction::triggered, this, &MainWindow::onActionHelpTriggered);
    rightBar->addAction(actionHelp);
}
```

### SARibbonBar布局方案

 `SARibbon`支持4种ribbon布局方案，布局方案参考了`office`的ribbon风格和`WPS`的ribbon风格，布局方案的切换可
通过`void SARibbonBar::setRibbonStyle(RibbonStyle v)`实现

`office`模式是最常见的ribbon模式，`tab`和标题栏占用位置较多，`WPS`设计的ribbon模式进行了改良，它为了减小ribbon的高度，把标签和标题栏设置在一起，这样减少了一个标题栏高度，有效利用了垂直空间，同时还把pannel的按钮布局由最大摆放3个变为摆放两个，进一步压缩了垂直空间

office的word界面和WPS Word界面截图对比

![office 界面截图](./doc/pic/office-screenshot.png)

![Wps 改动了ribbon的布局方式](./doc/pic/wps-change-ribbon-1.jpg)

在正常屏幕下，WPS 样式会比 Office 样式减少至少30像素左右的垂直高度，相比1920*1080的屏幕来说，相当于节约了接近3%的垂直空间

SARibbon中把带有标题栏的称之为宽松布局（Loose），宽松布局的各个元素如下图排列：

![宽松布局](./doc/pic/ribbonbar-geometry-office3.png)

这个布局和office的默认布局是一致的

SARibbon中把带有标题栏和tab结合一起的布局方式称之为紧凑布局（Compact），紧凑布局的各个元素如下图排列：

![紧凑布局](./doc/pic/ribbonbar-geometry-wps3.png)

SARibbonBar提供了`setRibbonStyle`函数，可以定义当前的布局方案，枚举`SARibbonBar::RibbonStyle`定义了四种布局方案：

- `SARibbonBar::RibbonStyleLooseThreeRow`宽松结构，3行模式(v0.x版本为`SARibbonBar::OfficeStyle`)

![SARibbonBar::RibbonStyleLooseThreeRow](doc/screenshot/office-3-style.png)

- `SARibbonBar::RibbonStyleLooseTwoRow`宽松结构，2行模式(v0.x版本为`SARibbonBar::OfficeStyleTwoRow`)

![SARibbonBar::RibbonStyleLooseTwoRow](doc/screenshot/office-2-style.png)

- `SARibbonBar::RibbonStyleCompactThreeRow`紧凑结构，3行模式(v0.x版本为`SARibbonBar::WpsLiteStyle`)

![SARibbonBar::RibbonStyleCompactThreeRow](doc/screenshot/wps-3-style.png)

- `SARibbonBar::RibbonStyleCompactTwoRow`紧凑结构，2行模式(v0.x版本为`SARibbonBar::WpsLiteStyleTwoRow`)

![SARibbonBar::RibbonStyleCompactTwoRow](doc/screenshot/wps-2-style.png)

### SARibbonBar文字换行，及图标大小

通过`SARibbonBar::setEnableWordWrap`函数可以控制SARibbonBar的文字是否换行，`SARibbonBar`的高度是固定的，文字是否换行会影响图标显示的大小，因此，如果你想图标看起来更大，可以设置文字不换行

在`SARibbonBar::RibbonStyleCompactTwoRow`的布局模式下，文字不换行的显示效果如下：

![](doc/screenshot/wps-2-style-nowrap.png)

SARibbonBar文字设置为不换行后，会使图标的显示空间变得更大

### 不同的“按钮”布局方式

`SARibbonPannel`提供了三个添加action的方法：
- `addLargeAction`
- `addMediumAction`
- `addSmallAction`

在标准的pannel中，一个action（按钮）有3种布局，以office word为例，pannel的三种布局其实是所占行数:

- 第一种，占满整个pannel，称之为`large`
- 第二种，一个pannel下可以放置2个按钮，称之为`medium`
- 第三种，一个pannel放置3个按钮，称之为`samll`

![word pannel 布局示例](./doc/pic/pannelLayout3row-example.png)

枚举`SARibbonPannelItem::RowProportion`是为了表征每个窗体在pannel所占行数的情况，在pannel布局中会常用到，这个枚举定义如下：

```cpp
/**
  * @brief 定义了行的占比，ribbon中有large，media和small三种占比
  */
enum RowProportion {
    None            ///< 为定义占比，这时候将会依据expandingDirections来判断，如果能有Qt::Vertical，就等同于Large，否则就是Small
    , Large         ///< 大占比，一个widget的高度会充满整个pannel
    , Medium        ///< 中占比，在@ref SARibbonPannel::pannelLayoutMode 为 @ref SARibbonPannel::ThreeRowMode 时才会起作用，且要同一列里两个都是Medium时，会在三行中占据两行
    , Small         ///< 小占比，占SARibbonPannel的一行，Medium在不满足条件时也会变为Small，但不会变为Large
};
```

`SARibbonPannel`里管理的每个action都会带有一个私有的属性（`SARibbonPannelItem::RowProportion`），这个属性决定了这个action在pannel里的布局

### SARibbonPannel的布局模式

#### 3行模式

三行模式是传统的pannel布局方式，如下图所示：

![3行模式ribbon布局示例](./doc/pic/pannelLayout3row.png)

3行模式下有三种占位(`SARibbonPannelItem::RowProportion`)，分别为large、medium和small

3行模式下的pannel会显示pannel的标题在`Pannel Title`区域，另外还有一个`OptionAction`的区域，这个是给这个action添加特殊触发使用的，如果没有设置`OptionAction`，这个区域是隐藏。

#### 2行模式

2行模式是WPS的改进布局法（具体是否是WPS首先这样做的不清楚，我是按照WPS的布局进行参考的），如下图所示：

![2行模式ribbon布局示例](./doc/pic/pannelLayout2row.png)

2行模式下medium和small占位(`SARibbonPannelItem::RowProportion`)是一样的，不做区分。

2行模式下pannel是不显示标题的

### SARibbon的自定义功能

ribbon的自定义是ribbon的一个特色，参考了office和wps的自定义界面，用户可以为自己的ribbon定义非常多的内容，甚至可以定义出一个完全和原来不一样的界面。

以下是office的自定义界面

![office的自定义界面](./doc/screenshot/customize/customization-office-ui.png)

SARibbon参考office和wps的界面，封装了方便使用的`SARibbonCustomize**`类，包括如下5个类：

> - SARibbonCustomizeDialog
> - SARibbonCustomizeWidget
> - SARibbonCustomizeData
> - SARibbonActionsManager
> - SARibbonActionsManagerModel

实际用户使用仅会面对`SARibbonActionsManager`和`SARibbonCustomizeDialog`/`SARibbonCustomizeWidget`，其余类用户正常不会使用。

`SARibbonActionsManager`是用来管理`QAction`，把想要自定义的`QAction`添加到`SARibbonActionsManager`中管理，并可以对`QAction`进行分类，以便在`SARibbonCustomizeDialog`/`SARibbonCustomizeWidget`中显示

`SARibbonCustomizeDialog`/`SARibbonCustomizeWidget`是具体的显示窗口，`SARibbonCustomizeDialog`把`SARibbonCustomizeWidget`封装为对话框，如果要实现office那样集成到配置对话框中可以使用`SARibbonCustomizeWidget`，`SARibbonCustomizeDialog`的效果如下图所示：

![SARibbon的自定义界面](./doc/screenshot/customize/customization-saribbon-ui.png)


#### 给界面添加自定义功能

这里演示如何添加自定义功能

首先定义`SARibbonActionsManager`作为MainWindow的成员变量

```cpp
//MainWindow.h 中定义成员变量
SARibbonActionsManager* m_ribbonActionMgr;///< 用于管理所有action
```

在MainWindow的初始化过程中，还需要创建大量的`QAction`，`QAction`的父对象指定为MainWindow，另外还会生成ribbon布局，例如添加category，添加pannel等操作，在上述操作完成后添加如下步骤，自动让`SARibbonActionsManager`管理所有的`QAction`

```cpp
//MainWindow的初始化，生成QAction
//生成ribbon布局
m_ribbonActionMgr = new SARibbonActionsManager(mainWinowPtr);
m_ribbonActionMgr->autoRegisteActions(mainWinowPtr);
```

`SARibbonActionsManager`的关键函数`autoRegisteActions`可以遍历 `SARibbonMainWindow`下的所有子object，找到action并注册，并会遍历所有`SARibbonCategory`,把`SARibbonCategory`下的action按`SARibbonCategory`的title name进行分类，此函数还会把`SARibbonMainWindow`下面的action，但不在任何一个category下的作为NotInRibbonCategoryTag标签注册，默认名字会赋予not in ribbon

在需要调用`SARibbonCustomizeDialog`的地方如下操作：

```cpp
QString cfgpath = "customization.xml";
SARibbonCustomizeDialog dlg(this, this);

dlg.setupActionsManager(m_ribbonActionMgr);
dlg.fromXml(cfgpath);//调用这一步是为了把已经存在的自定义步骤加载进来，在保存时能基于原有的自定义步骤上追加
if (QDialog::Accepted == dlg.exec()) {
    dlg.applys();//应用自定义步骤
    dlg.toXml(cfgpath);//把自定义步骤保存到文件中
}
```

在MainWindow生成前还需要把自定义的内容加载，因此在构造函数最后应该加入如下语句：

```cpp
//MainWindow的构造函数最后
sa_apply_customize_from_xml_file("customization.xml", this, m_ribbonActionMgr);
```

`sa_apply_customize_from_xml_file`是`SARibbonCustomizeWidget.h`中提供的函数，直接把配置文件中的自定义内容应用到MainWindow中。

这样软件每次启动都会按照配置文件加载。


# 更多截图

- 这是使用SARibbon构建的软件截图

![](./doc/screenshot/data-workbench-screenshot1-cn.gif)
![](./doc/screenshot/data-workbench-screenshot01-en.png)

[github - https://github.com/czyt1988/data-workbench](https://github.com/czyt1988/data-workbench)

[gitee - https://gitee.com/czyt1988/data-workbench](https://gitee.com/czyt1988/data-workbench)

具体Ribbon的生成代码可见：

[https://github.com/czyt1988/data-workbench/blob/master/src/APP/DAAppRibbonArea.cpp](https://github.com/czyt1988/data-workbench/blob/master/src/APP/DAAppRibbonArea.cpp)

# 文档生成

你可以通过doxygen生成qch和html类型的文档，`doc/Doxyfile-qch-cn`文件用于生成`.qch`格式的qt帮助文档,你可以把它集成到qt creator当中，`doc/Doxyfile-wiki-cn`文件用于生成html格式的文档，方便你在浏览器中查阅

# 常见问题

## 1、高分屏显示问题

针对高分屏显示，有如下两个方面准备

1 - 在main函数中为QApplication设置`Qt::AA_EnableHighDpiScaling`属性

这个属性使得应用程序自动检测显示器的像素密度来实现自动缩放，示例代码如下：

```cpp
int main(int argc, char* argv[])
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
    QApplication a(argc, argv);
    ......
}
```

2 - 在main函数中为QApplication设置缩放策略：`QApplication::setHighDpiScaleFactorRoundingPolicy`

Qt5.6提供了`Qt::AA_EnableHighDpiScaling`，但不能完全解决，Qt5.14开始提供了高分屏缩放策略设置`QApplication::setHighDpiScaleFactorRoundingPolicy`，同`AA_EnableHighDpiScaling`一样需要在main函数前面设置

```cpp
int main(int argc, char* argv[])
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
    QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
    QApplication a(argc, argv);
    ......
}
```

## 2、快捷键问题

经常有人反馈使用SARibbonBar后，没有被激活的tab页的快捷键没有响应，只有激活的标签页的快捷键才有反应，如果是传统的toolbar模式，由于action所在的toolbar一直在最前端，因此快捷键一直生效，但如果是SARibbonBar，action所在的pannel是会隐藏的，隐藏后快捷键就不生效，如果想快捷键无论Pannel是否隐藏都生效，设置快捷键的`shortcutContext`属性为`Qt::ApplicationShortcut`也无效，这时，可以在创建Category的地方手动创建快捷键

例如：

```cpp
    ribbon构建
    ...
    QShortcut* shortCut = new QShortcut(QKeySequence(QLatin1String("Ctrl+S")), this);
    connect(shortCut, &QShortcut::activated, this, [ actSave ]() {
        actSave->trigger();
    });
```

这个快捷键的创建位置在Mainwidnow，这样快捷键就随着mainwindow周期

# 给我一个鼓励

如果项目对你有用，请你给我一个鼓励：

![](./doc/pic/赞赏码.png)