Initial commit of BudgetPro

This commit is contained in:
Javi
2026-05-24 23:21:33 +02:00
commit f3096faee6
575 changed files with 90288 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
add_subdirectory(SARibbonBar)
#if(BUILD_DESIGNERPLUGIN)
# add_subdirectory(DesignerPlugin)
#endif()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
#-------------------------------------------------
#
# 此文件给SARibbon静态编译使用
# 适用于SARibbon.h/SARibbon.cpp这两个文件
# 如果使用动态库,不要使用此文件
#
#
# 使用方法见例子:src\example\SimpleExample\
#
#-------------------------------------------------
######################################
# Config | 配置
######################################
#SA_RIBBON_CONFIG 用于定义一些编译选项:
# SA_RIBBON_CONFIG+=use_frameless
# 此选项将使用frameless第三方库,这个选项在SARibbonBar.pri中会自动判断,如果,达到frameless的使用要求将会自动定义
# frameless第三方库必须C++17且只有几个版本的qt可用,目前支持(qt5.14,qt5.15,qt6.4以上)
# 除了上诉版本SA_RIBBON_CONFIG中不会加入use_frameless
# frameless库能实现Ubuntu下和mac下的显示,同时多屏幕的支持也较好
# 使用frameless库,需要定义QWindowKit的安装目录,默认在SARIBBON_BIN_DIR
# SA_RIBBON_QWindowKit_Install_DIR = $$SARIBBON_BIN_DIR
######################################
# 集成模式默认不使用frameless,如果使用,需要自己引入qwk依赖
SA_RIBBON_CONFIG -= use_frameless
# 这里判断SA_RIBBON_CONFIG是否包含use_frameless,如果包含将引入frameless库,并定义SARIBBON_USE_3RDPARTY_FRAMELESSHELPER为1
contains( SA_RIBBON_CONFIG, use_frameless ) {
!contains(CONFIG,C++17){
CONFIG += c++17
}
# 定义SARIBBON_USE_3RDPARTY_FRAMELESSHELPER为1
DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1
}else{
DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=0
}
SOURCES += \
$$PWD/SARibbon.cpp
HEADERS += \
$$PWD/SARibbon.h
RESOURCES += \
$$PWD/SARibbonBar/resource.qrc
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
+59
View File
@@ -0,0 +1,59 @@
# Cmake的命令不区分打下写,例如message,set等命令;但Cmake的变量区分大小写
# 为统一风格,本项目的Cmake命令全部采用小写,变量全部采用大写加下划线组合。
cmake_minimum_required(VERSION 3.5)
project(SARibbon-3rdparty-build
LANGUAGES CXX
DESCRIPTION "SARibbon : 3rdparty build"
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(MSVC)
# CMAKE_CXX_STANDARD对有些版本的msvc无效
set(CMAKE_CXX_FLAGS"${CMAKE_CXX_FLAGS} /std:c++17")
endif()
# 编译错误error C2039: “max”: 不是“std”的成员
# 避免和min/max宏冲突
add_definitions(-DNOMINMAX)
########################################################
# 安装路径设置
########################################################
# load Qt library, minimum version required is 5.12
# cn:Qt库加载,最低版本要求为5.12
set(SARIBBON_MIN_QT_VERSION 5.12)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} ${SARIBBON_MIN_QT_VERSION} COMPONENTS
Core
Gui
Widgets
REQUIRED
)
# 平台判断
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set(SARIBBON_PLATFORM "x86")
else()
set(SARIBBON_PLATFORM "x64")
endif()
# The bin file directory is one level above the current directory
# cn:bin文件目录在当前目录的上上一级
set(SARIBBON_BIN_NAME bin_qt${QT_VERSION}_${CMAKE_CXX_COMPILER_ID}_${SARIBBON_PLATFORM})
set(SARIBBON_BIN_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../${SARIBBON_BIN_NAME})
# windows系统下,默认直接安装到当前文件夹下
if(WIN32)
set(CMAKE_INSTALL_PREFIX "${SARIBBON_BIN_DIR}")
endif()
########################################################
# 第三方库参数设置
########################################################
#
set(QWINDOWKIT_BUILD_WIDGETS ON CACHE BOOL "force set QWINDOWKIT_BUILD_WIDGETS ON" FORCE)
set(QWINDOWKIT_BUILD_EXAMPLES OFF CACHE BOOL "force set QWINDOWKIT_BUILD_EXAMPLES OFF" FORCE)
set(QWINDOWKIT_BUILD_QUICK OFF CACHE BOOL "force set QWINDOWKIT_BUILD_QUICK OFF" FORCE)
set(QWINDOWKIT_INSTALL ON CACHE BOOL "force set QWINDOWKIT_INSTALL ON" FORCE)
########################################################
# 安装第三方库
########################################################
add_subdirectory(qwindowkit)
@@ -0,0 +1,6 @@
# 这里演示如何再windows下通过命令行编译
# 如果你的操作系统安装了很多版本的qt和vs,你可以参考下面的命令,这里演示使用vs2017 qt5.14.2来编译qwk
# qwk要求的cmake版本比较高,vs2017的默认cmake是不支持,这里做个演示如何通过命令行编译,具体路径需要实际使用时更改
"C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe" -B build -S . -G "Visual Studio 15 2017" -A x64 -DQt5_DIR="C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\lib\cmake\Qt5"
"C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe" --build build --target install --config Debug
"C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe" --build build --target install --config Release
+289
View File
@@ -0,0 +1,289 @@
cmake_minimum_required(VERSION 3.5)
project(SARibbonBar LANGUAGES CXX VERSION ${SARIBBON_VERSION})
set(SARIBBON_LIB_NAME SARibbonBar)
message(STATUS "SARIBBON_LIB_NAME=${SARIBBON_LIB_NAME}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#################################################
# SAColorWidgets相关
# https://github.com/czyt1988/SAColorWidgets
#################################################
SET(SACOLOR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/colorWidgets)
SET(SACOLOR_HEADER_FILES
${SACOLOR_DIR}/SAColorWidgetsGlobal.h
${SACOLOR_DIR}/SAColorToolButton.h
${SACOLOR_DIR}/SAColorGridWidget.h
${SACOLOR_DIR}/SAColorPaletteGridWidget.h
${SACOLOR_DIR}/SAColorMenu.h
)
SET(SACOLOR_SOURCE_FILES
${SACOLOR_DIR}/SAColorToolButton.cpp
${SACOLOR_DIR}/SAColorGridWidget.cpp
${SACOLOR_DIR}/SAColorPaletteGridWidget.cpp
${SACOLOR_DIR}/SAColorMenu.cpp
)
#################################################
# SARibbonBar相关
#################################################
# header files
# cn:头文件
SET(SARIBBON_HEADER_FILES
SARibbonBarVersionInfo.h
SARibbonGlobal.h
SAFramelessHelper.h
SARibbonActionsManager.h
SARibbonBar.h
SARibbonCustomizeData.h
SARibbonCustomizeDialog.h
SARibbonCustomizeWidget.h
SARibbonMainWindow.h
SARibbonWidget.h
SARibbonSystemButtonBar.h
SARibbonApplicationButton.h
SARibbonTabBar.h
SARibbonCategory.h
SARibbonContextCategory.h
SARibbonPannel.h
SARibbonToolButton.h
SARibbonMenu.h
SARibbonPannelOptionButton.h
SARibbonSeparatorWidget.h
SARibbonCategoryLayout.h
SARibbonGallery.h
SARibbonControlButton.h
SARibbonGalleryGroup.h
SARibbonGalleryItem.h
SARibbonComboBox.h
SARibbonElementFactory.h
SARibbonElementManager.h
SARibbonLineEdit.h
SARibbonCheckBox.h
SARibbonButtonGroupWidget.h
SARibbonStackedWidget.h
SARibbonQuickAccessBar.h
SARibbonCtrlContainer.h
SARibbonPannelLayout.h
SARibbonPannelItem.h
SARibbonLineWidgetContainer.h
SARibbonColorToolButton.h
)
# source files
# cn:cpp文件
SET(SARIBBON_SOURCE_FILES
SAFramelessHelper.cpp
SARibbonActionsManager.cpp
SARibbonBar.cpp
SARibbonCustomizeData.cpp
SARibbonCustomizeDialog.cpp
SARibbonCustomizeWidget.cpp
SARibbonMainWindow.cpp
SARibbonWidget.cpp
SARibbonSystemButtonBar.cpp
SARibbonApplicationButton.cpp
SARibbonTabBar.cpp
SARibbonCategory.cpp
SARibbonContextCategory.cpp
SARibbonPannel.cpp
SARibbonToolButton.cpp
SARibbonMenu.cpp
SARibbonPannelOptionButton.cpp
SARibbonSeparatorWidget.cpp
SARibbonCategoryLayout.cpp
SARibbonGallery.cpp
SARibbonControlButton.cpp
SARibbonGalleryGroup.cpp
SARibbonGalleryItem.cpp
SARibbonComboBox.cpp
SARibbonElementFactory.cpp
SARibbonElementManager.cpp
SARibbonLineEdit.cpp
SARibbonCheckBox.cpp
SARibbonButtonGroupWidget.cpp
SARibbonStackedWidget.cpp
SARibbonQuickAccessBar.cpp
SARibbonCtrlContainer.cpp
SARibbonPannelLayout.cpp
SARibbonPannelItem.cpp
SARibbonLineWidgetContainer.cpp
SARibbonColorToolButton.cpp
)
# resource files
# cn:资源文件
SET(SARIBBON_RESOURCE_FILES
resource.qrc
)
################################################################
add_library(${SARIBBON_LIB_NAME} SHARED
${SARIBBON_HEADER_FILES}
${SARIBBON_SOURCE_FILES}
${SARIBBON_RESOURCE_FILES}
${SACOLOR_HEADER_FILES}
${SACOLOR_SOURCE_FILES}
)
add_library(${SARIBBON_LIB_NAME}::${SARIBBON_LIB_NAME} ALIAS ${SARIBBON_LIB_NAME})
#################################################
# Qt相关的依赖
#################################################
target_link_libraries(${SARIBBON_LIB_NAME} PUBLIC
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets)
message(STATUS "link ${SARIBBON_LIB_NAME} PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets")
#################################################
# frameless相关的依赖
# qwindowkit库 https://github.com/stdware/qwindowkit
#################################################
# Qt X11Extras was first introduced in 5.1 and got removed in 6.0
# But it was again brought back as a private feature of QtGui in 6.2
if(_SARIBBON_USE_FRAMELESS_LIB)
find_package(QWindowKit REQUIRED)
target_link_libraries(${SARIBBON_LIB_NAME} PRIVATE QWindowKit::Widgets)
message(STATUS "link ${SARIBBON_LIB_NAME} PRIVATE QWindowKit::Widgets")
endif()
#################################################
include(GNUInstallDirs)
set_target_properties(${SARIBBON_LIB_NAME} PROPERTIES
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
CXX_EXTENSIONS OFF
DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}
VERSION ${SARIBBON_VERSION}
EXPORT_NAME ${SARIBBON_LIB_NAME}
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}"
)
# 构建库定义的宏,此宏必须为PRIVATE
target_compile_definitions(${SARIBBON_LIB_NAME} PRIVATE SA_RIBBON_BAR_MAKE_LIB)
message(STATUS "macros predefined:#define SA_RIBBON_BAR_MAKE_LIB")
# 使用SAColorWidgets但不作为库使用定义的宏,此宏必须为PRIVATE
target_compile_definitions(${SARIBBON_LIB_NAME} PRIVATE SA_COLOR_WIDGETS_MAKE_LIB)
message(STATUS "macros predefined:#define SA_COLOR_WIDGETS_MAKE_LIB")
if(_SARIBBON_USE_FRAMELESS_LIB)
#使用frameless必须设置SARIBBON_USE_3RDPARTY_FRAMELESSHELPER宏为1,此宏必须为PUBLIC
target_compile_definitions(${SARIBBON_LIB_NAME} PUBLIC SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1)
message(STATUS "macros predefined:#define SARIBBON_USE_3RDPARTY_FRAMELESSHELPER 1")
if(SARIBBON_ENABLE_SNAPLAYOUT)
# 是否开启windows11的snap layout效果,目前这个在不同qt版本下有bug
target_compile_definitions(${SARIBBON_LIB_NAME} PUBLIC SARIBBON_ENABLE_SNAP_LAYOUT=1)
message(STATUS "macros predefined:#define SARIBBON_ENABLE_SNAP_LAYOUT 1")
else()
target_compile_definitions(${SARIBBON_LIB_NAME} PUBLIC SARIBBON_ENABLE_SNAP_LAYOUT=0)
message(STATUS "macros predefined:#define SARIBBON_ENABLE_SNAP_LAYOUT 0")
endif()
else()
#不使用frameless必须设置SARIBBON_USE_3RDPARTY_FRAMELESSHELPER宏为0,此宏必须为PUBLIC
target_compile_definitions(${SARIBBON_LIB_NAME} PUBLIC SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=0)
message(STATUS "macros predefined:#define SARIBBON_USE_3RDPARTY_FRAMELESSHELPER 0")
endif()
target_include_directories(${SARIBBON_LIB_NAME} PUBLIC
$<INSTALL_INTERFACE:include/${SARIBBON_LIB_NAME}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
if(MSVC)
# To unbreak std::min/max
target_compile_definitions(${SARIBBON_LIB_NAME} PRIVATE NOMINMAX)
message(STATUS "macros predefined:#define NOMINMAX")
endif()
# ------------------------------- install script -------------------------------
# 头文件
install(FILES
${SARIBBON_HEADER_FILES}
DESTINATION include/${SARIBBON_LIB_NAME}
COMPONENT headers
)
# SAColorWidget的头文件
install(FILES
${SACOLOR_HEADER_FILES}
DESTINATION include/${SARIBBON_LIB_NAME}/colorWidgets
COMPONENT headers
)
# 生成单一文件
# 单一文件是把整个SARibbon打包为一个h和一个cpp,方便集成
set(SARIBBON_AMALGAMATE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/../SARibbon.h
${CMAKE_CURRENT_SOURCE_DIR}/../SARibbon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../SARibbon.pri
)
install(FILES
${SARIBBON_AMALGAMATE_FILES}
DESTINATION ${SARIBBON_LIB_NAME}_amalgamate
)
# 复制resource目录到install/amalgamate目录
install(DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/resource
DESTINATION ${SARIBBON_LIB_NAME}_amalgamate/SARibbonBar
)
# resource.qrc
install(FILES
${SARIBBON_RESOURCE_FILES}
DESTINATION ${SARIBBON_LIB_NAME}_amalgamate/SARibbonBar
)
# 复制3rdparty目录到install/amalgamate目录
install(DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty
DESTINATION ${SARIBBON_LIB_NAME}_amalgamate/SARibbonBar
)
include(CMakePackageConfigHelpers)
# SARibbonBarConfig.cmake.in中,会让此变量和“${PACKAGE_PREFIX_DIR}/”进行拼接,也就是${PACKAGE_PREFIX_DIR}/@SARIBBON_LIB_INCLUDE_INSTALL_DIR@
# PACKAGE_PREFIX_DIR等于${CMAKE_CURRENT_LIST_DIR}/../..
# 最终变为${CMAKE_CURRENT_LIST_DIR}/../../include
# 注意此处是替换,不要出现引号
set(SARIBBON_LIB_INCLUDE_INSTALL_DIR include/${SARIBBON_LIB_NAME})
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/${SARIBBON_LIB_NAME}ConfigVersion.cmake
VERSION ${SARIBBON_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/SARibbonBarConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${SARIBBON_LIB_NAME}Config.cmake"
INSTALL_DESTINATION lib/cmake/${SARIBBON_LIB_NAME}
PATH_VARS SARIBBON_LIB_INCLUDE_INSTALL_DIR
)
install(TARGETS ${SARIBBON_LIB_NAME}
EXPORT ${SARIBBON_LIB_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SARIBBON_LIB_NAME}
)
install(EXPORT ${SARIBBON_LIB_NAME}Targets
FILE ${SARIBBON_LIB_NAME}Targets.cmake
NAMESPACE ${SARIBBON_LIB_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${SARIBBON_LIB_NAME}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${SARIBBON_LIB_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${SARIBBON_LIB_NAME}ConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${SARIBBON_LIB_NAME}
)
# 资源文件生成
if(WIN32)
create_win32_resource_version(
TARGET ${SARIBBON_LIB_NAME}
FILENAME ${SARIBBON_LIB_NAME}
EXT "dll"
DESCRIPTION "Ribbon control library for Qt"
)
endif()
@@ -0,0 +1,616 @@
#include "SAFramelessHelper.h"
#include <QRect>
#include <QRubberBand>
#include <QMouseEvent>
#include <QHoverEvent>
#include <QApplication>
#include <QDebug>
#include "SARibbonMainWindow.h"
class SAPrivateFramelessWidgetData;
/*****
* FramelessHelperPrivate
* 存储界面对应的数据集合,以及是否可移动、可缩放属性
*****/
class SAFramelessHelper::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SAFramelessHelper)
friend class SAPrivateFramelessWidgetData;
public:
PrivateData(SAFramelessHelper* p);
QHash< QWidget*, SAPrivateFramelessWidgetData* > m_widgetDataHash;
bool m_bWidgetMovable { true };
bool m_bWidgetResizable { true };
bool m_bRubberBandOnResize { true };
bool m_bRubberBandOnMove { true };
};
SAFramelessHelper::PrivateData::PrivateData(SAFramelessHelper* p) : q_ptr(p)
{
}
/*****
* CursorPosCalculator
* 计算鼠标是否位于左、上、右、下、左上角、左下角、右上角、右下角
*****/
class SAPrivateFramelessCursorPosCalculator
{
public:
explicit SAPrivateFramelessCursorPosCalculator();
void reset();
void recalculate(const QPoint& globalMousePos, const QRect& frameRect);
public:
bool mIsOnEdges { true };
bool mIsOnLeftEdge { true };
bool mIsOnRightEdge { true };
bool mIsOnTopEdge { true };
bool mIsOnBottomEdge { true };
bool mIsOnTopLeftEdge { true };
bool mIsOnBottomLeftEdge { true };
bool mIsOnTopRightEdge { true };
bool mIsOnBottomRightEdge { true };
static int s_borderWidth;
static int s_titleHeight;
};
int SAPrivateFramelessCursorPosCalculator::s_borderWidth = 5;
int SAPrivateFramelessCursorPosCalculator::s_titleHeight = 30;
/***** CursorPosCalculator *****/
SAPrivateFramelessCursorPosCalculator::SAPrivateFramelessCursorPosCalculator()
{
reset();
}
void SAPrivateFramelessCursorPosCalculator::reset()
{
mIsOnEdges = false;
mIsOnLeftEdge = false;
mIsOnRightEdge = false;
mIsOnTopEdge = false;
mIsOnBottomEdge = false;
mIsOnTopLeftEdge = false;
mIsOnBottomLeftEdge = false;
mIsOnTopRightEdge = false;
mIsOnBottomRightEdge = false;
}
void SAPrivateFramelessCursorPosCalculator::recalculate(const QPoint& gMousePos, const QRect& frameRect)
{
int globalMouseX = gMousePos.x();
int globalMouseY = gMousePos.y();
int frameX = frameRect.x();
int frameY = frameRect.y();
int frameWidth = frameRect.width();
int frameHeight = frameRect.height();
mIsOnLeftEdge = (globalMouseX >= frameX && globalMouseX <= frameX + s_borderWidth);
mIsOnRightEdge = (globalMouseX >= frameX + frameWidth - s_borderWidth && globalMouseX <= frameX + frameWidth);
mIsOnTopEdge = (globalMouseY >= frameY && globalMouseY <= frameY + s_borderWidth);
mIsOnBottomEdge = (globalMouseY >= frameY + frameHeight - s_borderWidth && globalMouseY <= frameY + frameHeight);
mIsOnTopLeftEdge = mIsOnTopEdge && mIsOnLeftEdge;
mIsOnBottomLeftEdge = mIsOnBottomEdge && mIsOnLeftEdge;
mIsOnTopRightEdge = mIsOnTopEdge && mIsOnRightEdge;
mIsOnBottomRightEdge = mIsOnBottomEdge && mIsOnRightEdge;
mIsOnEdges = mIsOnLeftEdge || mIsOnRightEdge || mIsOnTopEdge || mIsOnBottomEdge;
}
/*****
* WidgetData
* 更新鼠标样式、移动窗体、缩放窗体
*****/
class SAPrivateFramelessWidgetData
{
public:
explicit SAPrivateFramelessWidgetData(SAFramelessHelper::PrivateData* pd, QWidget* pTopLevelWidget);
~SAPrivateFramelessWidgetData();
QWidget* widget();
// 处理鼠标事件-划过、按下、释放、移动
bool handleWidgetEvent(QEvent* event);
// 更新橡皮筋状态
void updateRubberBandStatus();
private:
// 更新鼠标样式
void updateCursorShape(const QPoint& gMousePos);
// 重置窗体大小
void resizeWidget(const QPoint& gMousePos);
// 移动窗体
void moveWidget(const QPoint& gMousePos);
// 处理鼠标按下
bool handleMousePressEvent(QMouseEvent* event);
// 处理鼠标释放
bool handleMouseReleaseEvent(QMouseEvent* event);
// 处理鼠标移动
bool handleMouseMoveEvent(QMouseEvent* event);
// 处理鼠标离开
bool handleLeaveEvent(QEvent* event);
// 处理鼠标进入
bool handleHoverMoveEvent(QHoverEvent* event);
// 处理鼠标双击事件
bool handleDoubleClickedMouseEvent(QMouseEvent* event);
private:
SAFramelessHelper::PrivateData* d;
QRubberBand* m_pRubberBand;
QWidget* m_pWidget;
QPoint m_ptDragPos;
SAPrivateFramelessCursorPosCalculator m_pressedMousePos;
SAPrivateFramelessCursorPosCalculator m_moveMousePos;
bool m_bLeftButtonPressed;
bool m_bCursorShapeChanged;
bool m_bLeftButtonTitlePressed;
Qt::WindowFlags m_windowFlags;
};
/***** WidgetData *****/
SAPrivateFramelessWidgetData::SAPrivateFramelessWidgetData(SAFramelessHelper::PrivateData* pd, QWidget* pTopLevelWidget)
{
d = pd;
m_pWidget = pTopLevelWidget;
m_bLeftButtonPressed = false;
m_bCursorShapeChanged = false;
m_bLeftButtonTitlePressed = false;
m_pRubberBand = NULL;
m_windowFlags = m_pWidget->windowFlags();
m_pWidget->setMouseTracking(true);
m_pWidget->setAttribute(Qt::WA_Hover, true);
updateRubberBandStatus();
}
SAPrivateFramelessWidgetData::~SAPrivateFramelessWidgetData()
{
m_pWidget->setMouseTracking(false);
m_pWidget->setWindowFlags(m_windowFlags);
m_pWidget->setAttribute(Qt::WA_Hover, false);
delete m_pRubberBand;
m_pRubberBand = NULL;
}
QWidget* SAPrivateFramelessWidgetData::widget()
{
return (m_pWidget);
}
bool SAPrivateFramelessWidgetData::handleWidgetEvent(QEvent* event)
{
switch (event->type()) {
case QEvent::MouseButtonPress:
return (handleMousePressEvent(static_cast< QMouseEvent* >(event)));
case QEvent::MouseButtonRelease:
return (handleMouseReleaseEvent(static_cast< QMouseEvent* >(event)));
case QEvent::MouseMove:
return (handleMouseMoveEvent(static_cast< QMouseEvent* >(event)));
case QEvent::Leave:
return (handleLeaveEvent(static_cast< QMouseEvent* >(event)));
case QEvent::HoverMove:
return (handleHoverMoveEvent(static_cast< QHoverEvent* >(event)));
case QEvent::MouseButtonDblClick:
return (handleDoubleClickedMouseEvent(static_cast< QMouseEvent* >(event)));
default:
break;
}
return (false);
}
void SAPrivateFramelessWidgetData::updateRubberBandStatus()
{
if (d->m_bRubberBandOnMove || d->m_bRubberBandOnResize) {
if (NULL == m_pRubberBand) {
m_pRubberBand = new QRubberBand(QRubberBand::Rectangle);
}
} else {
delete m_pRubberBand;
m_pRubberBand = NULL;
}
}
void SAPrivateFramelessWidgetData::updateCursorShape(const QPoint& gMousePos)
{
if (m_pWidget->isFullScreen() || m_pWidget->isMaximized()) {
if (m_bCursorShapeChanged) {
m_pWidget->unsetCursor();
}
return;
}
m_moveMousePos.recalculate(gMousePos, m_pWidget->frameGeometry());
if (m_moveMousePos.mIsOnTopLeftEdge || m_moveMousePos.mIsOnBottomRightEdge) {
m_pWidget->setCursor(Qt::SizeFDiagCursor);
m_bCursorShapeChanged = true;
} else if (m_moveMousePos.mIsOnTopRightEdge || m_moveMousePos.mIsOnBottomLeftEdge) {
m_pWidget->setCursor(Qt::SizeBDiagCursor);
m_bCursorShapeChanged = true;
} else if (m_moveMousePos.mIsOnLeftEdge || m_moveMousePos.mIsOnRightEdge) {
m_pWidget->setCursor(Qt::SizeHorCursor);
m_bCursorShapeChanged = true;
} else if (m_moveMousePos.mIsOnTopEdge || m_moveMousePos.mIsOnBottomEdge) {
m_pWidget->setCursor(Qt::SizeVerCursor);
m_bCursorShapeChanged = true;
} else {
if (m_bCursorShapeChanged) {
m_pWidget->unsetCursor();
m_bCursorShapeChanged = false;
}
}
}
void SAPrivateFramelessWidgetData::resizeWidget(const QPoint& gMousePos)
{
QRect origRect;
if (d->m_bRubberBandOnResize) {
origRect = m_pRubberBand->frameGeometry();
} else {
origRect = m_pWidget->frameGeometry();
}
int left = origRect.left();
int top = origRect.top();
int right = origRect.right();
int bottom = origRect.bottom();
origRect.getCoords(&left, &top, &right, &bottom);
int minWidth = m_pWidget->minimumWidth();
int minHeight = m_pWidget->minimumHeight();
if (m_pressedMousePos.mIsOnTopLeftEdge) {
left = gMousePos.x();
top = gMousePos.y();
} else if (m_pressedMousePos.mIsOnBottomLeftEdge) {
left = gMousePos.x();
bottom = gMousePos.y();
} else if (m_pressedMousePos.mIsOnTopRightEdge) {
right = gMousePos.x();
top = gMousePos.y();
} else if (m_pressedMousePos.mIsOnBottomRightEdge) {
right = gMousePos.x();
bottom = gMousePos.y();
} else if (m_pressedMousePos.mIsOnLeftEdge) {
left = gMousePos.x();
} else if (m_pressedMousePos.mIsOnRightEdge) {
right = gMousePos.x();
} else if (m_pressedMousePos.mIsOnTopEdge) {
top = gMousePos.y();
} else if (m_pressedMousePos.mIsOnBottomEdge) {
bottom = gMousePos.y();
}
QRect newRect(QPoint(left, top), QPoint(right, bottom));
if (newRect.isValid()) {
if (minWidth > newRect.width()) {
if (left != origRect.left()) {
newRect.setLeft(origRect.left());
} else {
newRect.setRight(origRect.right());
}
}
if (minHeight > newRect.height()) {
if (top != origRect.top()) {
newRect.setTop(origRect.top());
} else {
newRect.setBottom(origRect.bottom());
}
}
if (d->m_bRubberBandOnResize) {
m_pRubberBand->setGeometry(newRect);
} else {
m_pWidget->setGeometry(newRect);
}
}
}
void SAPrivateFramelessWidgetData::moveWidget(const QPoint& gMousePos)
{
if (d->m_bRubberBandOnMove) {
m_pRubberBand->move(gMousePos - m_ptDragPos);
} else {
m_pWidget->move(gMousePos - m_ptDragPos);
}
}
bool SAPrivateFramelessWidgetData::handleMousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_bLeftButtonPressed = true;
m_bLeftButtonTitlePressed = event->pos().y() < m_moveMousePos.s_titleHeight;
QRect frameRect = m_pWidget->frameGeometry();
auto gp = SA_MOUSEEVENT_GLOBALPOS_POINT(event);
m_pressedMousePos.recalculate(gp, frameRect);
m_ptDragPos = gp - frameRect.topLeft();
if (m_pressedMousePos.mIsOnEdges) {
if (m_pWidget->isMaximized()) {
// 窗口在最大化状态时,点击边界不做任何处理
return (false);
}
if (d->m_bRubberBandOnResize) {
m_pRubberBand->setGeometry(frameRect);
m_pRubberBand->show();
return (true);
}
} else if (d->m_bRubberBandOnMove && m_bLeftButtonTitlePressed) {
if (m_pWidget->isMaximized()) {
// 窗口在最大化状态时,点击标题栏不做任何处理
return (false);
}
m_pRubberBand->setGeometry(frameRect);
m_pRubberBand->show();
return (true);
}
}
return (false);
}
bool SAPrivateFramelessWidgetData::handleMouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_bLeftButtonPressed = false;
m_bLeftButtonTitlePressed = false;
m_pressedMousePos.reset();
if (m_pRubberBand && m_pRubberBand->isVisible()) {
m_pRubberBand->hide();
m_pWidget->setGeometry(m_pRubberBand->geometry());
return (true);
}
}
return (false);
}
bool SAPrivateFramelessWidgetData::handleMouseMoveEvent(QMouseEvent* event)
{
QPoint p = SA_MOUSEEVENT_GLOBALPOS_POINT(event);
if (m_bLeftButtonPressed) {
if (d->m_bWidgetResizable && m_pressedMousePos.mIsOnEdges) {
if (m_pWidget->isMaximized()) {
// 窗口在最大化状态时,点击边界不做任何处理
return (false);
}
resizeWidget(p);
return (true);
} else if (d->m_bWidgetMovable && m_bLeftButtonTitlePressed) {
if (m_pWidget->isMaximized()) {
// 先求出窗口到鼠标的相对位置
QRect normalGeometry = m_pWidget->normalGeometry();
m_pWidget->showNormal();
p.ry() -= 10;
p.rx() -= (normalGeometry.width() / 2);
m_pWidget->move(p);
// 这时要重置m_ptDragPos
m_ptDragPos = QPoint(normalGeometry.width() / 2, 10);
return (true);
}
moveWidget(p);
return (true);
}
return (false);
} else if (d->m_bWidgetResizable) {
updateCursorShape(p);
}
return (false);
}
bool SAPrivateFramelessWidgetData::handleLeaveEvent(QEvent* event)
{
Q_UNUSED(event)
if (!m_bLeftButtonPressed) {
m_pWidget->unsetCursor();
return (true);
}
return (false);
}
bool SAPrivateFramelessWidgetData::handleHoverMoveEvent(QHoverEvent* event)
{
if (d->m_bWidgetResizable) {
updateCursorShape(m_pWidget->mapToGlobal(SA_HOVEREVENT_POS_POINT(event)));
}
return (false);
}
bool SAPrivateFramelessWidgetData::handleDoubleClickedMouseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
if (m_pWidget) {
SARibbonMainWindow* mainwindow = qobject_cast< SARibbonMainWindow* >(m_pWidget);
if (mainwindow) {
if (mainwindow->windowFlags() & Qt::WindowMaximizeButtonHint) {
// 在最大化按钮显示时才进行shownormal处理
bool titlePressed = event->pos().y() < m_moveMousePos.s_titleHeight;
if (titlePressed) {
if (m_pWidget->isMaximized()) {
m_pWidget->showNormal();
} else {
m_pWidget->showMaximized();
}
return (true);
}
}
}
}
}
return (false);
}
//===================================================
// SAFramelessHelper
//===================================================
SAFramelessHelper::SAFramelessHelper(QObject* parent) : QObject(parent), d_ptr(new SAFramelessHelper::PrivateData(this))
{
d_ptr->m_bWidgetMovable = true;
d_ptr->m_bWidgetResizable = true;
d_ptr->m_bRubberBandOnResize = false;
d_ptr->m_bRubberBandOnMove = false;
if (parent) {
QWidget* w = qobject_cast< QWidget* >(parent);
if (w) {
w->setWindowFlags(w->windowFlags() | Qt::FramelessWindowHint);
setWidgetMovable(true); // 设置窗体可移动
setWidgetResizable(true); // 设置窗体可缩放
setRubberBandOnMove(false); // 设置橡皮筋效果-可移动
setRubberBandOnResize(true); // 设置橡皮筋效果-可缩放
activateOn(w); // 激活当前窗体
}
}
}
SAFramelessHelper::~SAFramelessHelper()
{
QList< QWidget* > keys = d_ptr->m_widgetDataHash.keys();
int size = keys.size();
for (int i = 0; i < size; ++i) {
delete d_ptr->m_widgetDataHash.take(keys[ i ]);
}
}
bool SAFramelessHelper::eventFilter(QObject* obj, QEvent* event)
{
switch (event->type()) {
case QEvent::MouseMove:
case QEvent::HoverMove:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::Leave: {
SAPrivateFramelessWidgetData* data = d_ptr->m_widgetDataHash.value(static_cast< QWidget* >(obj));
if (data) {
return (data->handleWidgetEvent(event));
}
break;
}
default:
break;
}
return (QObject::eventFilter(obj, event));
}
void SAFramelessHelper::activateOn(QWidget* topLevelWidget)
{
if (!d_ptr->m_widgetDataHash.contains(topLevelWidget)) {
SAPrivateFramelessWidgetData* data = new SAPrivateFramelessWidgetData(d_ptr.get(), topLevelWidget);
d_ptr->m_widgetDataHash.insert(topLevelWidget, data);
topLevelWidget->installEventFilter(this);
}
}
void SAFramelessHelper::removeFrom(QWidget* topLevelWidget)
{
SAPrivateFramelessWidgetData* data = d_ptr->m_widgetDataHash.take(topLevelWidget);
if (data) {
topLevelWidget->removeEventFilter(this);
delete data;
}
}
void SAFramelessHelper::setRubberBandOnMove(bool movable)
{
d_ptr->m_bRubberBandOnMove = movable;
QList< SAPrivateFramelessWidgetData* > list = d_ptr->m_widgetDataHash.values();
foreach (SAPrivateFramelessWidgetData* data, list) {
data->updateRubberBandStatus();
}
}
void SAFramelessHelper::setWidgetMovable(bool movable)
{
d_ptr->m_bWidgetMovable = movable;
}
void SAFramelessHelper::setWidgetResizable(bool resizable)
{
d_ptr->m_bWidgetResizable = resizable;
}
void SAFramelessHelper::setRubberBandOnResize(bool resizable)
{
d_ptr->m_bRubberBandOnResize = resizable;
QList< SAPrivateFramelessWidgetData* > list = d_ptr->m_widgetDataHash.values();
foreach (SAPrivateFramelessWidgetData* data, list) {
data->updateRubberBandStatus();
}
}
void SAFramelessHelper::setBorderWidth(int width)
{
if (width > 0) {
SAPrivateFramelessCursorPosCalculator::s_borderWidth = width;
}
}
void SAFramelessHelper::setTitleHeight(int height)
{
if (height > 0) {
SAPrivateFramelessCursorPosCalculator::s_titleHeight = height;
}
}
bool SAFramelessHelper::widgetMovable()
{
return (d_ptr->m_bWidgetMovable);
}
bool SAFramelessHelper::widgetResizable()
{
return (d_ptr->m_bWidgetResizable);
}
bool SAFramelessHelper::rubberBandOnMove()
{
return (d_ptr->m_bRubberBandOnMove);
}
bool SAFramelessHelper::rubberBandOnResisze()
{
return (d_ptr->m_bRubberBandOnResize);
}
uint SAFramelessHelper::borderWidth()
{
return (SAPrivateFramelessCursorPosCalculator::s_borderWidth);
}
uint SAFramelessHelper::titleHeight()
{
return (SAPrivateFramelessCursorPosCalculator::s_titleHeight);
}
@@ -0,0 +1,53 @@
#ifndef SAFRAMELESSHELPER_H
#define SAFRAMELESSHELPER_H
#include "SARibbonGlobal.h"
#include <QObject>
class QWidget;
class SA_RIBBON_EXPORT SAFramelessHelper : public QObject
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SAFramelessHelper)
friend class SAPrivateFramelessWidgetData;
public:
explicit SAFramelessHelper(QObject* parent);
~SAFramelessHelper();
// 激活窗体
void activateOn(QWidget* topLevelWidget);
// 移除窗体
void removeFrom(QWidget* topLevelWidget);
// 设置窗体移动
void setWidgetMovable(bool movable);
// 设置窗体缩放
void setWidgetResizable(bool resizable);
// 设置橡皮筋移动
void setRubberBandOnMove(bool movable);
// 设置橡皮筋缩放
void setRubberBandOnResize(bool resizable);
// 设置边框的宽度
void setBorderWidth(int width);
// 设置标题栏高度
void setTitleHeight(int height);
bool widgetResizable();
bool widgetMovable();
bool rubberBandOnMove();
bool rubberBandOnResisze();
uint borderWidth();
uint titleHeight();
protected:
// 事件过滤,进行移动、缩放等
virtual bool eventFilter(QObject* obj, QEvent* event);
};
#endif // FRAMELESSHELPER_H
@@ -0,0 +1,638 @@
#include "SARibbonActionsManager.h"
#include <QMap>
#include <QHash>
#include <QDebug>
#include <QWidgetAction>
#include "SARibbonBar.h"
class SARibbonActionsManager::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonActionsManager)
public:
PrivateData(SARibbonActionsManager* p);
void clear();
QMap< int, QList< QAction* > > mTagToActions; ///< tag : QList<QAction*>
QMap< int, QString > mTagToName; ///< tag对应的名字
QHash< QString, QAction* > mKeyToAction; ///< key对应action
QMap< QAction*, QString > mActionToKey; ///< action对应key
QMap< int, SARibbonCategory* > mTagToCategory; ///< 仅仅在autoRegisteActions函数会有用
int mSale; ///< 盐用于生成固定的id,在用户不主动设置key时,id基于msale生成,只要SARibbonActionsManager的调用registeAction顺序不变,生成的id都不变,因为它是基于自增实现的
};
SARibbonActionsManager::PrivateData::PrivateData(SARibbonActionsManager* p) : q_ptr(p), mSale(0)
{
}
void SARibbonActionsManager::PrivateData::clear()
{
mTagToActions.clear();
mTagToName.clear();
mKeyToAction.clear();
mActionToKey.clear();
mTagToCategory.clear();
mSale = 0;
}
SARibbonActionsManager::SARibbonActionsManager(SARibbonBar* bar)
: QObject(bar), d_ptr(new SARibbonActionsManager::PrivateData(this))
{
autoRegisteActions(bar);
}
SARibbonActionsManager::~SARibbonActionsManager()
{
}
/**
* @brief 设置tag对应的名字,通过这个可以得到tag和文本的映射
* @param tag
* @param name
* @note 在支持多语言的环境下,在语言切换时需要重新设置,以更新名字
*/
void SARibbonActionsManager::setTagName(int tag, const QString& name)
{
d_ptr->mTagToName[ tag ] = name;
}
/**
* @brief 获取tag对应的中文名字
* @param tag
* @return
*/
QString SARibbonActionsManager::tagName(int tag) const
{
return (d_ptr->mTagToName.value(tag, ""));
}
/**
* @brief 移除tag
* @note 注意,这个函数非常耗时
* @param tag
*/
void SARibbonActionsManager::removeTag(int tag)
{
QList< QAction* > oldacts = actions(tag);
// 开始移除
d_ptr->mTagToActions.remove(tag);
d_ptr->mTagToName.remove(tag);
// 开始查找需要移出总表的action
QList< QAction* > needRemoveAct;
QList< QAction* > total;
for (auto i = d_ptr->mTagToActions.begin(); i != d_ptr->mTagToActions.end(); ++i) {
total += i.value();
}
for (QAction* a : qAsConst(oldacts)) {
if (!total.contains(a)) {
needRemoveAct.append(a);
}
}
// 从总表移除action
for (QAction* a : qAsConst(needRemoveAct)) {
auto i = d_ptr->mActionToKey.find(a);
if (i != d_ptr->mActionToKey.end()) {
d_ptr->mKeyToAction.remove(i.value());
d_ptr->mActionToKey.erase(i);
}
}
}
/**
* @brief 把action注册到管理器中,实现action的管理
* @param act
* @param tag tag是可以按照位进行叠加,见 @ref ActionTag 如果
* 要定义自己的标签,建议定义大于@ref ActionTag::UserDefineActionTag 的值,
* registeAction的tag是直接记录进去的,如果要多个标签并存,在registe之前先或好tag
* @param key key是action对应的key,一个key只对应一个action,是查找action的关键
* ,默认情况为一个QString(),这时key是QAction的objectName
* @param enableEmit 控制是否发射@ref actionTagChanged 信号
* @note 同一个action多次注册不同的tag可以通过tag索引到action,但通过action只能索引到最后一个注册的tag
* @note tag的新增会触发actionTagChanged信号
*/
bool SARibbonActionsManager::registeAction(QAction* act, int tag, const QString& key, bool enableEmit)
{
if (nullptr == act) {
return (false);
}
QString k = key;
if (k.isEmpty()) {
k = QString("id_%1_%2").arg(d_ptr->mSale++).arg(act->objectName());
}
if (d_ptr->mKeyToAction.contains(k)) {
qWarning() << "key: "
<< k << " have been exist,you can set key in an unique value when use SARibbonActionsManager::registeAction";
return (false);
}
d_ptr->mKeyToAction[ k ] = act;
d_ptr->mActionToKey[ act ] = k;
// 记录tag 对 action
bool isneedemit = !(d_ptr->mTagToActions.contains(tag)); // 记录是否需要发射信号
d_ptr->mTagToActions[ tag ].append(act);
// 绑定槽
connect(act, &QObject::destroyed, this, &SARibbonActionsManager::onActionDestroyed);
if (isneedemit && enableEmit) {
// 说明新增tag
Q_EMIT actionTagChanged(tag, false);
}
return (true);
}
/**
* @brief 取消action的注册
*
* 如果tag对应的最后一个action被撤销,tag也将一块删除
* @param act
* @note tag的删除会触发actionTagChanged信号
* @note 如果action关联了多个tag,这些tag里的action都会被删除,对应的key也同理
*/
void SARibbonActionsManager::unregisteAction(QAction* act, bool enableEmit)
{
if (nullptr == act) {
return;
}
// 绑定槽
disconnect(act, &QObject::destroyed, this, &SARibbonActionsManager::onActionDestroyed);
removeAction(act, enableEmit);
}
/**
* @brief 移除action
*
* 仅移除内存内容
* @param act
* @param enableEmit
*/
void SARibbonActionsManager::removeAction(QAction* act, bool enableEmit)
{
QList< int > deletedTags; // 记录删除的tag,用于触发actionTagChanged
QMap< int, QList< QAction* > > tagToActions; ///< tag : QList<QAction*>
for (auto i = d_ptr->mTagToActions.begin(); i != d_ptr->mTagToActions.end(); ++i) {
// 把不是act的内容转移到tagToActions和tagToActionKeys中,之后再和m_d里的替换
auto tmpi = tagToActions.insert(i.key(), QList< QAction* >());
int count = 0;
for (int j = 0; j < i.value().size(); ++j) {
if (i.value()[ j ] != act) {
tmpi.value().append(act);
++count;
}
}
if (0 == count) {
// 说明这个tag没有内容
tagToActions.erase(tmpi);
deletedTags.append(i.key());
}
}
// 删除mKeyToAction
QString key = d_ptr->mActionToKey.value(act);
d_ptr->mActionToKey.remove(act);
d_ptr->mKeyToAction.remove(key);
// 置换
d_ptr->mTagToActions.swap(tagToActions);
// 发射信号
if (enableEmit) {
for (int tagdelete : qAsConst(deletedTags)) {
Q_EMIT actionTagChanged(tagdelete, true);
}
}
}
/**
* @brief 等同actions
* @param tag
* @return
*/
QList< QAction* >& SARibbonActionsManager::filter(int tag)
{
return (actions(tag));
}
/**
* @brief 根据tag得到actions
* @param tag
* @return
*/
QList< QAction* >& SARibbonActionsManager::actions(int tag)
{
return (d_ptr->mTagToActions[ tag ]);
}
const QList< QAction* > SARibbonActionsManager::actions(int tag) const
{
return (d_ptr->mTagToActions[ tag ]);
}
/**
* @brief 获取所有的标签
* @return
*/
QList< int > SARibbonActionsManager::actionTags() const
{
return (d_ptr->mTagToActions.keys());
}
/**
* @brief 通过key获取action
* @param key
* @return 如果没有key,返回nullptr
*/
QAction* SARibbonActionsManager::action(const QString& key) const
{
return (d_ptr->mKeyToAction.value(key, nullptr));
}
/**
* @brief 通过action找到key
* @param act
* @return 如果找不到,返回QString()
*/
QString SARibbonActionsManager::key(QAction* act) const
{
return (d_ptr->mActionToKey.value(act, QString()));
}
/**
* @brief 返回所有管理的action数
* @return
*/
int SARibbonActionsManager::count() const
{
return (d_ptr->mKeyToAction.size());
}
/**
* @brief 返回所有管理的actions
* @return
*/
QList< QAction* > SARibbonActionsManager::allActions() const
{
return (d_ptr->mKeyToAction.values());
}
/**
* @brief 自动加载SARibbonBar的action
* 此函数会遍历@ref SARibbonBar的父窗口(一般是SARibbonMainWindow)下的所有子object,找到action注册,
* 并会遍历所有@ref SARibbonCategory,把SARibbonCategory下的action按SARibbonCategory的title name进行分类
*
* 此函数会把所有category下的action生成tag并注册,返回的QMap<int, SARibbonCategory *>是记录了category对应的tag
*
* 此函数还会把SARibbonBar的父窗口(一般是SARibbonMainWindow)下面的action,但不在任何一个category下的作为NotInRibbonCategoryTag标签注册,默认名字会赋予not
* in ribbon 可以通过@ref setTagName 改变
*
* @param w
* @return
* @note 此函数的调用最好在category设置了标题后调用,因为会以category的标题作为标签的命名
*/
QMap< int, SARibbonCategory* > SARibbonActionsManager::autoRegisteActions(SARibbonBar* bar)
{
QMap< int, SARibbonCategory* > res;
// 先遍历SARibbonBar的父窗口(一般是SARibbonMainWindow)下的所有子对象,把所有action找到
QWidget* parWidget = bar->parentWidget();
QSet< QAction* > mainwindowActions;
if (parWidget) {
for (QObject* o : qAsConst(parWidget->children())) {
if (QAction* a = qobject_cast< QAction* >(o)) {
// 说明是action
if (!a->objectName().isEmpty()) {
mainwindowActions.insert(a);
}
}
}
}
// 开始遍历每个category,加入action
if (nullptr == bar) {
// 非ribbon模式,直接退出
return (res);
}
QSet< QAction* > categoryActions;
QList< SARibbonCategory* > categorys = bar->categoryPages();
int tag = AutoCategoryDistinguishBeginTag;
for (SARibbonCategory* c : qAsConst(categorys)) {
QList< SARibbonPannel* > pannels = c->pannelList();
for (SARibbonPannel* p : qAsConst(pannels)) {
categoryActions += autoRegisteWidgetActions(p, tag, false);
}
setTagName(tag, c->categoryName());
res[ tag ] = c;
++tag;
}
// 找到不在功能区的actions
QSet< QAction* > notincategory = mainwindowActions - categoryActions;
for (QAction* a : qAsConst(notincategory)) {
if (!a->objectName().isEmpty()) {
registeAction(a, NotInRibbonCategoryTag, a->objectName(), false);
}
}
if (notincategory.size() > 0) {
setTagName(NotInRibbonCategoryTag, tr("not in ribbon"));
}
for (auto i = res.begin(); i != res.end(); ++i) {
connect(i.value(), &SARibbonCategory::categoryNameChanged, this, &SARibbonActionsManager::onCategoryTitleChanged);
}
d_ptr->mTagToCategory = res;
return (res);
}
/**
* @brief 自动加载action
* @param w
* @param tag
* @param enableEmit
* @return 返回成功加入SARibbonActionsManager管理的action
*/
QSet< QAction* > SARibbonActionsManager::autoRegisteWidgetActions(QWidget* w, int tag, bool enableEmit)
{
QSet< QAction* > res;
QList< QAction* > was = w->actions();
for (QAction* a : qAsConst(was)) {
if (res.contains(a) || a->objectName().isEmpty()) {
// 重复内容不重复加入
// 没有object name不加入
continue;
}
if (registeAction(a, tag, a->objectName(), enableEmit)) {
res.insert(a);
}
}
return (res);
}
/**
* @brief 根据标题查找action
* @param text
* @return
*/
QList< QAction* > SARibbonActionsManager::search(const QString& text)
{
QList< QAction* > res;
if (text.isEmpty()) {
return (res);
}
QStringList kws = text.split(" ");
if (kws.isEmpty()) {
kws.append(text);
}
QList< QAction* > acts = d_ptr->mActionToKey.keys();
for (const QString& k : qAsConst(kws)) {
for (auto i = d_ptr->mActionToKey.begin(); i != d_ptr->mActionToKey.end(); ++i) {
if (i.key()->text().contains(k, Qt::CaseInsensitive)) {
res.append(i.key());
}
}
}
return (res);
}
void SARibbonActionsManager::clear()
{
d_ptr->clear();
}
/**
* @brief action 被delete时,将触发此槽把管理的action删除
* @param o
* @note 这个函数不会触发actionTagChanged信号
*/
void SARibbonActionsManager::onActionDestroyed(QObject* o)
{
QAction* act = static_cast< QAction* >(o);
removeAction(act, false);
}
/**
* @brief autoRegisteActions函数会关联此槽,在标签内容改变时改变tag 对应 文本
* @param title
*/
void SARibbonActionsManager::onCategoryTitleChanged(const QString& title)
{
SARibbonCategory* c = qobject_cast< SARibbonCategory* >(sender());
if (nullptr == c) {
return;
}
int tag = d_ptr->mTagToCategory.key(c, -1);
if (tag == -1) {
return;
}
setTagName(tag, title);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SARibbonActionsModel
////////////////////////////////////////////////////////////////////////////////////////////////////////
class SARibbonActionsManagerModel::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonActionsManagerModel)
public:
PrivateData(SARibbonActionsManagerModel* p);
void updateRef();
int count() const;
QAction* at(int index);
bool isNull() const;
public:
SARibbonActionsManager* mMgr { nullptr };
int mTag { SARibbonActionsManager::CommonlyUsedActionTag };
QString mSeatchText;
QList< QAction* > mActions;
};
SARibbonActionsManagerModel::PrivateData::PrivateData(SARibbonActionsManagerModel* p) : q_ptr(p)
{
}
void SARibbonActionsManagerModel::PrivateData::updateRef()
{
if (isNull()) {
return;
}
if (!mSeatchText.isEmpty()) {
mActions = mMgr->search(mSeatchText);
} else {
mActions = mMgr->actions(mTag);
}
}
int SARibbonActionsManagerModel::PrivateData::count() const
{
if (isNull()) {
return (0);
}
return (mActions.size());
}
QAction* SARibbonActionsManagerModel::PrivateData::at(int index)
{
if (isNull()) {
return (nullptr);
}
if (index >= count()) {
return (nullptr);
}
return (mActions.at(index));
}
bool SARibbonActionsManagerModel::PrivateData::isNull() const
{
return (mMgr == nullptr);
}
//===================================================
// SARibbonActionsManagerModel
//===================================================
SARibbonActionsManagerModel::SARibbonActionsManagerModel(QObject* p)
: QAbstractListModel(p), d_ptr(new SARibbonActionsManagerModel::PrivateData(this))
{
}
SARibbonActionsManagerModel::SARibbonActionsManagerModel(SARibbonActionsManager* m, QObject* p)
: QAbstractListModel(p), d_ptr(new SARibbonActionsManagerModel::PrivateData(this))
{
setupActionsManager(m);
}
SARibbonActionsManagerModel::~SARibbonActionsManagerModel()
{
}
int SARibbonActionsManagerModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid()) { // 非顶层
return (0);
}
// 顶层
return (d_ptr->count());
}
QVariant SARibbonActionsManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(section);
if (role != Qt::DisplayRole) {
return (QVariant());
}
if (Qt::Horizontal == orientation) {
return (tr("action name"));
}
return (QVariant());
}
Qt::ItemFlags SARibbonActionsManagerModel::flags(const QModelIndex& index) const
{
if (!index.isValid()) {
return (Qt::NoItemFlags);
}
return (Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
QVariant SARibbonActionsManagerModel::data(const QModelIndex& index, int role) const
{
QAction* act = indexToAction(index);
if (nullptr == act) {
return (QVariant());
}
switch (role) {
case Qt::DisplayRole: {
QString str = act->text();
if (!str.isEmpty()) {
return str;
}
// 如果文本为空,一般是没有设置文本,或者是窗口,如果是窗口,那么用windowtitle作为文本
if (QWidgetAction* wa = qobject_cast< QWidgetAction* >(act)) {
if (QWidget* w = wa->defaultWidget()) {
str = w->windowTitle();
}
}
return str;
}
case Qt::DecorationRole:
return (act->icon());
default:
break;
}
return (QVariant());
}
void SARibbonActionsManagerModel::setFilter(int tag)
{
d_ptr->mTag = tag;
update();
}
void SARibbonActionsManagerModel::update()
{
beginResetModel();
d_ptr->updateRef();
endResetModel();
}
void SARibbonActionsManagerModel::setupActionsManager(SARibbonActionsManager* m)
{
d_ptr->mMgr = m;
d_ptr->mTag = SARibbonActionsManager::CommonlyUsedActionTag;
d_ptr->mActions = m->filter(d_ptr->mTag);
connect(m, &SARibbonActionsManager::actionTagChanged, this, &SARibbonActionsManagerModel::onActionTagChanged);
update();
}
void SARibbonActionsManagerModel::uninstallActionsManager()
{
if (!d_ptr->isNull()) {
disconnect(
d_ptr->mMgr, &SARibbonActionsManager::actionTagChanged, this, &SARibbonActionsManagerModel::onActionTagChanged);
d_ptr->mMgr = nullptr;
d_ptr->mTag = SARibbonActionsManager::CommonlyUsedActionTag;
}
update();
}
QAction* SARibbonActionsManagerModel::indexToAction(QModelIndex index) const
{
if (!index.isValid()) {
return (nullptr);
}
if (index.row() >= d_ptr->count()) {
return (nullptr);
}
return (d_ptr->at(index.row()));
}
/**
* @brief 查找
* @param text
*/
void SARibbonActionsManagerModel::search(const QString& text)
{
d_ptr->mSeatchText = text;
update();
}
void SARibbonActionsManagerModel::onActionTagChanged(int tag, bool isdelete)
{
if (isdelete && (tag == d_ptr->mTag)) {
d_ptr->mTag = SARibbonActionsManager::UnknowActionTag;
update();
} else {
if (tag == d_ptr->mTag) {
update();
}
}
}
@@ -0,0 +1,147 @@
#ifndef SARIBBONACTIONSMANAGER_H
#define SARIBBONACTIONSMANAGER_H
#include "SARibbonGlobal.h"
#include <QObject>
#include <QAbstractListModel>
#include <QAction>
#include <QMap>
#include <QString>
#include <QSet>
class SARibbonBar;
class SARibbonCategory;
/**
* @brief 用于管理SARibbon的所有Action
*
* SARibbonActionsManager维护着两个表,一个是tag(标签)对应的Action list
* 一个是所有接受SARibbonActionsManager管理的action list。
*
* SARibbonActionsManager的标签对应一组actions,每个标签对应的action可以重复出现,
* 但SARibbonActionsManager维护的action list里只有一份action,不会重复出现。
*
* tag用于对action list分组,每个tag的实体名字通过@ref setTagName 进行设置,在语言变化时需要及时调用
* setTagName设置新的标签对应的文本。
*
* SARibbonActionsManager默认预设了6个常用标签见@ref SARibbonActionsManager::ActionTag ,用户自定义标签需要在
* SARibbonActionsManager::UserDefineActionTag值的基础上进行累加。
*
* @ref filter (等同@ref actions )函数用于提取标签管理的action list@ref allActions 函数返回SARibbonActionsManager
* 管理的所有标签。
*
* 通过@ref autoRegisteActions 函数可以快速的建立action的管理,此函数会遍历@ref SARibbonBar下所有@ref SARibbonPannel 添加的action,并给予Category建立tag,正常使用用户仅需关注此autoRegisteActions函数即可
*
*
*/
class SA_RIBBON_EXPORT SARibbonActionsManager : public QObject
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonActionsManager)
friend class SARibbonActionsManagerModel;
public:
/**
* @brief 定义action的标签
*/
enum ActionTag
{
UnknowActionTag = 0, ///< 未知的tag
CommonlyUsedActionTag = 0x01, ///< 预设tag-常用命令
NotInFunctionalAreaActionTag = 0x02, ///< 预设tag-不在功能区命令
AutoCategoryDistinguishBeginTag = 0x1000, ///< 自动按Category划分的标签起始,在@ref autoRegisteActions 函数会用到
AutoCategoryDistinguishEndTag = 0x2000, ///< 自动按Category划分的标签结束,在@ref autoRegisteActions 函数会用到
NotInRibbonCategoryTag = 0x2001, ///< 不在功能区的标签@ref autoRegisteActions 函数会遍历所有category的action
UserDefineActionTag = 0x8000 ///< 自定义标签,所有用户自定义tag要大于此tag
};
explicit SARibbonActionsManager(SARibbonBar* bar);
~SARibbonActionsManager();
//设置tag对应的名字
void setTagName(int tag, const QString& name);
//获取tag对应的名字
QString tagName(int tag) const;
//移除tag,注意,这个函数非常耗时
void removeTag(int tag);
//注册action
bool registeAction(QAction* act, int tag, const QString& key = QString(), bool enableEmit = true);
//取消action的注册
void unregisteAction(QAction* act, bool enableEmit = true);
//过滤得到actions对应的引用,实际是一个迭代器
QList< QAction* >& filter(int tag);
//通过tag筛选出系列action
QList< QAction* >& actions(int tag);
const QList< QAction* > actions(int tag) const;
//获取所有的标签
QList< int > actionTags() const;
//通过key获取action
QAction* action(const QString& key) const;
//通过action找到key
QString key(QAction* act) const;
//返回所有管理的action数
int count() const;
//返回所有管理的actions
QList< QAction* > allActions() const;
//自动加载action,返回tag对应的Category指针
QMap< int, SARibbonCategory* > autoRegisteActions(SARibbonBar* bar);
//自动加载widget下的actions函数返回的action,返回加载的数量,这些
QSet< QAction* > autoRegisteWidgetActions(QWidget* w, int tag, bool enableEmit = false);
//根据标题查找action
QList< QAction* > search(const QString& text);
//清除
void clear();
Q_SIGNALS:
/**
* @brief 标签变化触发的信号,变化包括新增和删除
*/
void actionTagChanged(int tag, bool isdelete);
private Q_SLOTS:
void onActionDestroyed(QObject* o);
void onCategoryTitleChanged(const QString& title);
private:
void removeAction(QAction* act, bool enableEmit = true);
};
/**
* @brief SARibbonActionsManager 对应的model
*/
class SA_RIBBON_EXPORT SARibbonActionsManagerModel : public QAbstractListModel
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonActionsManagerModel)
public:
explicit SARibbonActionsManagerModel(QObject* p = nullptr);
explicit SARibbonActionsManagerModel(SARibbonActionsManager* m, QObject* p = nullptr);
~SARibbonActionsManagerModel();
virtual int rowCount(const QModelIndex& parent) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual QVariant data(const QModelIndex& index, int role) const override;
void setFilter(int tag);
void update();
void setupActionsManager(SARibbonActionsManager* m);
void uninstallActionsManager();
QAction* indexToAction(QModelIndex index) const;
void search(const QString& text);
private Q_SLOTS:
void onActionTagChanged(int tag, bool isdelete);
};
#endif // SARIBBONACTIONSMANAGER_H
@@ -0,0 +1,33 @@
#include "SARibbonApplicationButton.h"
SARibbonApplicationButton::SARibbonApplicationButton(QWidget* parent) : QToolButton(parent)
{
setFocusPolicy(Qt::NoFocus);
setAutoRaise(true);
setPopupMode(QToolButton::InstantPopup);
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
}
SARibbonApplicationButton::SARibbonApplicationButton(const QString& text, QWidget* parent) : QToolButton(parent)
{
setFocusPolicy(Qt::NoFocus);
setAutoRaise(true);
setPopupMode(QToolButton::InstantPopup);
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setText(text);
}
SARibbonApplicationButton::SARibbonApplicationButton(const QIcon& icon, const QString& text, QWidget* parent)
: QToolButton(parent)
{
setFocusPolicy(Qt::NoFocus);
setAutoRaise(true);
setPopupMode(QToolButton::InstantPopup);
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setIcon(icon);
setText(text);
}
SARibbonApplicationButton::~SARibbonApplicationButton()
{
}
@@ -0,0 +1,21 @@
#ifndef SARIBBONAPPLICATIONBUTTON_H
#define SARIBBONAPPLICATIONBUTTON_H
#include <QToolButton>
#include "SARibbonGlobal.h"
/**
* @brief The SARibbonApplicationButton class
*
* 默认的plicationButton,可以通过样式指定不一样的ApplicationButton
*/
class SA_RIBBON_EXPORT SARibbonApplicationButton : public QToolButton
{
Q_OBJECT
public:
explicit SARibbonApplicationButton(QWidget* parent = nullptr);
explicit SARibbonApplicationButton(const QString& text, QWidget* parent = nullptr);
explicit SARibbonApplicationButton(const QIcon& icon, const QString& text, QWidget* parent = nullptr);
~SARibbonApplicationButton();
};
#endif // SARIBBONAPPLICATIONBUTTON_H
File diff suppressed because it is too large Load Diff
+459
View File
@@ -0,0 +1,459 @@
#ifndef SARIBBONBAR_H
#define SARIBBONBAR_H
#include "SARibbonCategory.h"
#include "SARibbonContextCategory.h"
#include "SARibbonGlobal.h"
#include <QMenuBar>
#include <QScopedPointer>
#include <QVariant>
class QAction;
class QAbstractButton;
class SARibbonElementFactory;
class SARibbonTabBar;
class SARibbonButtonGroupWidget;
class SARibbonQuickAccessBar;
class SARibbonStackedWidget;
/**
@brief SARibbonBar继承于QMenuBar,在SARibbonMainWindow中直接替换了原来的QMenuBar
通过setRibbonStyle函数设置ribbon的风格:
@code
void setRibbonStyle(RibbonStyles v);
@endcode
SARibbonBar参考office和wps,提供了四种风格的Ribbon模式,@ref SARibbonBar::RibbonStyles
如果想ribbon占用的空间足够小,WpsLiteStyleTwoRow模式能比OfficeStyle节省35%的高度空间
如何生成ribbon?先看看一个传统的Menu/ToolBar是如何生成的:
@code
void MainWindow::MainWindow()
{
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QToolBar *fileToolBar = addToolBar(tr("File"));
//生成action
QAction *newAct = new QAction(newIcon, tr("&New"), this);
fileMenu->addAction(newAct);
fileToolBar->addAction(newAct);
QAction *openAct = new QAction(openIcon, tr("&Open..."), this);
fileMenu->addAction(openAct);
fileToolBar->addAction(openAct);
}
@endcode
传统的Menu/ToolBar主要通过QMenu的addMenu添加菜单,通过QMainWindow::addToolBar生成QToolBar,
再把QAction设置进QMenu和QToolBar中
SARibbonBar和传统方法相似,不过相对于传统的Menu/ToolBar QMenu和QToolBar是平级的,
Ribbon是有明显的层级关系,SARibbonBar下面是 @ref SARibbonCategory
SARibbonCategory下面是@ref SARibbonPannel SARibbonPannel下面是@ref SARibbonToolButton
SARibbonToolButton管理着QAction
因此,生成一个ribbon只需以下几个函数:
@code
SARibbonCategory * SARibbonBar::addCategoryPage(const QString& title);
SARibbonPannel * SARibbonCategory::addPannel(const QString& title);
SARibbonToolButton * SARibbonPannel::addLargeAction(QAction *action);
SARibbonToolButton * SARibbonPannel::addSmallAction(QAction *action);
@endcode
因此生成步骤如下:
@code
//成员变量
SARibbonCategory* categoryMain;
SARibbonPannel* FilePannel;
//建立ui
void setupRibbonUi()
{
......
//ribbonwindow为SARibbonMainWindow
SARibbonBar* ribbon = ribbonwindow->ribbonBar();
ribbon->setRibbonStyle(SARibbonBar::WpsLiteStyle);
//添加一个Main标签
categoryMain = ribbon->addCategoryPage(QStringLiteral("Main"));
//Main标签下添加一个File Pannel
FilePannel = categoryMain->addPannel(QStringLiteral("FilePannel"));
//开始为File Pannel添加action
FilePannel->addLargeAction(actionNew);
FilePannel->addLargeAction(actionOpen);
FilePannel->addLargeAction(actionSave);
FilePannel->addSmallAction(actionImportMesh);
FilePannel->addSmallAction(actionImportGeometry);
}
@endcode
*/
class SA_RIBBON_EXPORT SARibbonBar : public QMenuBar
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonBar)
friend class SARibbonMainWindow;
friend class SARibbonSystemButtonBar;
Q_PROPERTY(RibbonStyles ribbonStyle READ currentRibbonStyle WRITE setRibbonStyle)
Q_PROPERTY(bool minimumMode READ isMinimumMode WRITE setMinimumMode)
Q_PROPERTY(bool minimumModeButton READ haveShowMinimumModeButton WRITE showMinimumModeButton)
Q_PROPERTY(QColor windowTitleTextColor READ windowTitleTextColor WRITE setWindowTitleTextColor)
Q_PROPERTY(QColor tabBarBaseLineColor READ tabBarBaseLineColor WRITE setTabBarBaseLineColor)
Q_PROPERTY(Qt::Alignment windowTitleAligment READ windowTitleAligment WRITE setWindowTitleAligment)
Q_PROPERTY(bool enableWordWrap READ isEnableWordWrap WRITE setEnableWordWrap)
Q_PROPERTY(bool enableShowPannelTitle READ isEnableShowPannelTitle WRITE setEnableShowPannelTitle)
Q_PROPERTY(bool tabOnTitle READ isTabOnTitle WRITE setTabOnTitle)
Q_PROPERTY(SARibbonPannel::PannelLayoutMode pannelLayoutMode READ pannelLayoutMode WRITE setPannelLayoutMode)
public:
enum RibbonStyleFlag
{
RibbonStyleLoose = 0x0001, // bit:0000 0001
RibbonStyleCompact = 0x0002, // bit:0000 0010
RibbonStyleThreeRow = 0x0010, // bit:0001 0000
RibbonStyleTwoRow = 0x0020, // bit:0010 0000
RibbonStyleLooseThreeRow = RibbonStyleLoose | RibbonStyleThreeRow, ///< 宽松结构,3行模式
RibbonStyleCompactThreeRow = RibbonStyleCompact | RibbonStyleThreeRow, ///< 紧凑结构,3行模式
RibbonStyleLooseTwoRow = RibbonStyleLoose | RibbonStyleTwoRow, ///< 宽松结构,2行模式
RibbonStyleCompactTwoRow = RibbonStyleCompact | RibbonStyleTwoRow ///< 紧凑结构,2行模式
};
Q_ENUM(RibbonStyleFlag)
Q_DECLARE_FLAGS(RibbonStyles, RibbonStyleFlag)
Q_FLAG(RibbonStyles)
/**
* @brief 定义当前ribbon 的状态
*/
enum RibbonMode
{
MinimumRibbonMode, ///< 缩小模式
NormalRibbonMode ///< 正常模式
};
Q_ENUM(RibbonMode)
using FpCategoryIterate = std::function< bool(SARibbonCategory*) >;
using FpPannelIterate = SARibbonCategory::FpPannelIterate;
public:
// 判断RibbonStyle是否为2行模式
static bool isTwoRowStyle(RibbonStyles s);
static bool isThreeRowStyle(RibbonStyles s);
// 判断是否是office样式
static bool isLooseStyle(RibbonStyles s);
static bool isCompactStyle(RibbonStyles s);
// 获取版本信息
static QString versionString();
// 获取默认的上下文标签颜色列表
static QList< QColor > defaultContextCategoryColorList();
//
static void initHighDpi();
public:
// 构造函数
explicit SARibbonBar(QWidget* parent = nullptr);
~SARibbonBar();
// 获取applicationButton
QAbstractButton* applicationButton();
// 设置applicationButton
void setApplicationButton(QAbstractButton* btn);
// 获取tabbar
SARibbonTabBar* ribbonTabBar();
// 添加一个标签
SARibbonCategory* addCategoryPage(const QString& title);
void addCategoryPage(SARibbonCategory* category);
// 为了支持Qt designer,添加的一个重载函数
Q_INVOKABLE void addCategoryPage(QWidget* category);
// 添加一个categorycategory的位置在index,如果当前category数量少于index,将插入到最后
SARibbonCategory* insertCategoryPage(const QString& title, int index);
void insertCategoryPage(SARibbonCategory* category, int index);
// 通过名字查找Category
SARibbonCategory* categoryByName(const QString& title) const;
// 通过ObjectName查找Category
SARibbonCategory* categoryByObjectName(const QString& objname) const;
// 通过索引找到category,如果超过索引范围,会返回nullptr
SARibbonCategory* categoryByIndex(int index) const;
// 隐藏category,并不会删除或者取走,只是隐藏
void hideCategory(SARibbonCategory* category);
// 显示被隐藏的category
void showCategory(SARibbonCategory* category);
// 判断这个category是否在显示状态,也就是tabbar有这个category
bool isCategoryVisible(const SARibbonCategory* c) const;
// 获取category的索引
int categoryIndex(const SARibbonCategory* c) const;
// 移动一个Category从from index到to index
void moveCategory(int from, int to);
// 获取当前显示的所有的SARibbonCategory,包含未显示的SARibbonContextCategory的SARibbonCategory也一并返回
QList< SARibbonCategory* > categoryPages(bool getAll = true) const;
// 移除SARibbonCategory
void removeCategory(SARibbonCategory* category);
// 添加一个上下文标签
SARibbonContextCategory* addContextCategory(const QString& title,
const QColor& color = QColor(),
const QVariant& id = QVariant());
void addContextCategory(SARibbonContextCategory* context);
// 显示一个上下文标签
void showContextCategory(SARibbonContextCategory* context);
// 隐藏一个上下文标签
void hideContextCategory(SARibbonContextCategory* context);
// 判断上下文是否是在显示状态
bool isContextCategoryVisible(SARibbonContextCategory* context);
// 设置上下文标签的显示或隐藏
void setContextCategoryVisible(SARibbonContextCategory* context, bool visible);
// 获取所有的上下文标签
QList< SARibbonContextCategory* > contextCategoryList() const;
// 移除ContextCategory
void destroyContextCategory(SARibbonContextCategory* context);
// 设置为隐藏模式
void setMinimumMode(bool isHide);
// 当前Ribbon是否是隐藏模式
bool isMinimumMode() const;
// 设置显示隐藏ribbon按钮
void showMinimumModeButton(bool isShow = true);
// 是否显示隐藏ribbon按钮
bool haveShowMinimumModeButton() const;
// 隐藏ribbon对应的action
QAction* minimumModeAction() const;
// 当前的模式
RibbonMode currentRibbonState() const;
// ribbon tab的高度
int tabBarHeight() const;
void setTabBarHeight(int h, bool resizeByNow = true);
// 标题栏的高度
int titleBarHeight() const;
void setTitleBarHeight(int h, bool resizeByNow = true);
// category的高度
int categoryHeight() const;
void setCategoryHeight(int h, bool resizeByNow = true);
// 获取正常模式下的mainBar的高度
int normalModeMainBarHeight() const;
// 最小模式下的MainBar高度
int minimumModeMainBarHeight() const;
// 激活tabbar右边的按钮群
SARibbonButtonGroupWidget* activeRightButtonGroup();
// 右侧按钮群
SARibbonButtonGroupWidget* rightButtonGroup();
// 激活QuickAccessBar
SARibbonQuickAccessBar* activeQuickAccessBar();
// 快速响应栏
SARibbonQuickAccessBar* quickAccessBar();
// 设置ribbon的风格
void setRibbonStyle(RibbonStyles v);
RibbonStyles currentRibbonStyle() const;
// 设置当前ribbon的index
void setCurrentIndex(int index);
// 返回当前的tab索引
int currentIndex();
// 确保标签显示出来
void raiseCategory(SARibbonCategory* category);
// 判断当前的行数
bool isTwoRowStyle() const;
bool isThreeRowStyle() const;
// 判断当前的样式
bool isLooseStyle() const;
bool isCompactStyle() const;
// 更新ribbon的布局数据,此函数适用于一些关键性尺寸变化,换起ribbon下面元素的布局,在发现刷新问题时,可以调用此函数
void updateRibbonGeometry();
// 设置pannel的模式
SARibbonPannel::PannelLayoutMode pannelLayoutMode() const;
void setPannelLayoutMode(SARibbonPannel::PannelLayoutMode m);
// 设置tab在title上面,这样可以省略title区域
void setTabOnTitle(bool on);
bool isTabOnTitle() const;
// tabbar 底部会绘制一条线条,此接口定义线条颜色
void setTabBarBaseLineColor(const QColor& clr);
QColor tabBarBaseLineColor() const;
// 设置标题颜色,如果不设置标题颜色,默认是SARibbonBar的qss的color属性
void setWindowTitleTextColor(const QColor& clr);
QColor windowTitleTextColor() const;
// 设置标题的对齐方式
void setWindowTitleAligment(Qt::Alignment al);
Qt::Alignment windowTitleAligment() const;
// 设置按钮允许换行,注意图标大小是由文字决定的,两行文字会让图标变小,如果想图标变大,文字不换行是最好的
void setEnableWordWrap(bool on);
bool isEnableWordWrap() const;
// 设置pannel的标题栏高度
int pannelTitleHeight() const;
void setPannelTitleHeight(int h);
// 设置pannel是否显示标题栏
bool isEnableShowPannelTitle() const;
void setEnableShowPannelTitle(bool on);
// 设置pannel的spacing
void setPannelSpacing(int n);
int pannelSpacing() const;
// 设置pannel按钮的icon尺寸,large action不受此尺寸影响
void setPannelToolButtonIconSize(const QSize& s);
QSize pannelToolButtonIconSize() const;
// 获取SARibbonStackedWidget,谨慎使用此函数
SARibbonStackedWidget* ribbonStackedWidget();
// 设置是否显示标题
void setTitleVisible(bool on = false);
bool isTitleVisible() const;
// 上下文标签的颜色列表,上下文标签显示的时候,会从颜色列表中取颜色进行标签的渲染
void setContextCategoryColorList(const QList< QColor >& cls);
QList< QColor > contextCategoryColorList() const;
// 设置context category 标题的文字颜色
void setContextCategoryTitleTextColor(const QColor& clr);
QColor contextCategoryTitleTextColor() const;
// 设置ribbon的对齐方式
void setRibbonAlignment(SARibbonAlignment al);
SARibbonAlignment ribbonAlignment() const;
// 此函数会遍历SARibbonBar下的所有Category,执行函数指针(bool(SARibbonCategory*)),函数指针返回false则停止迭代
bool iterate(FpCategoryIterate fp);
// 此函数会遍历SARibbonBar下的所有Category,并迭代所有的pannel,执行函数指针(bool(SARibbonPannel*)),函数指针返回false则停止迭代
bool iterate(FpPannelIterate fp);
// 设置边角widget可见性,对于mdi窗口,会出现TopLeftCorner和TopRightCorner两个corner widget
void setCornerWidgetVisible(bool on, Qt::Corner c = Qt::TopLeftCorner);
Q_SIGNALS:
/**
@brief 应用按钮点击响应 - 左上角的按钮,通过关联此信号触发应用按钮点击的效果
例如想点击按钮后弹出一个全屏的窗口(如office这些)
*/
void applicationButtonClicked();
/**
@brief 标签页变化触发的信号
@param index
*/
void currentRibbonTabChanged(int index);
/**
@brief ribbon的状态发生了变化后触发此信号
@param nowState 变更之后的ribbon状态
*/
void ribbonModeChanged(SARibbonBar::RibbonMode nowState);
/**
@brief ribbon的状态发生了变化后触发此信号
@param nowStyle 变更之后的ribbon样式
*/
void ribbonStyleChanged(SARibbonBar::RibbonStyles nowStyle);
/**
@brief 标题栏高度发生了变化的信号
@param oldHeight
@param newHeight
*/
void titleBarHeightChanged(int oldHeight, int newHeight);
/**
* @brief 参考QToolBar::actionTriggered的信号
* @param action
*/
void actionTriggered(QAction* action);
protected:
bool eventFilter(QObject* obj, QEvent* e) override;
// 根据情况重置tabbar的宽度,主要针对wps模式
int calcMinTabBarWidth() const;
// 更新
void updateCategoryTitleToTabName();
// 告知WindowButtonGroup的尺寸
void setWindowButtonGroupSize(const QSize& s);
protected Q_SLOTS:
void onWindowTitleChanged(const QString& title);
void onWindowIconChanged(const QIcon& i);
void onCategoryWindowTitleChanged(const QString& title);
void onStackWidgetHided();
virtual void onCurrentRibbonTabChanged(int index);
virtual void onCurrentRibbonTabClicked(int index);
virtual void onCurrentRibbonTabDoubleClicked(int index);
void onContextsCategoryPageAdded(SARibbonCategory* category);
void onContextsCategoryCategoryNameChanged(SARibbonCategory* category, const QString& title);
void onTabMoved(int from, int to);
private:
int tabIndex(SARibbonCategory* obj);
void resizeAll();
void resizeInLooseStyle();
void resizeInCompactStyle();
void paintInLooseStyle();
void paintInCompactStyle();
void resizeStackedContainerWidget();
// 刷新所有ContextCategoryManagerData,这个在单独一个Category删除时调用
void updateContextCategoryManagerData();
void synchronousCategoryData(bool autoUpdate = true);
protected:
virtual void paintEvent(QPaintEvent* e) override;
virtual void resizeEvent(QResizeEvent* e) override;
virtual void moveEvent(QMoveEvent* e) override;
virtual void changeEvent(QEvent* e) override;
virtual bool event(QEvent* e) override;
virtual void paintTabbarBaseLine(QPainter& painter);
virtual void paintWindowTitle(QPainter& painter, const QString& title, const QRect& titleRegion);
virtual void paintContextCategoryTab(QPainter& painter, const QString& title, const QRect& contextRect, const QColor& color);
#if SA_DEBUG_PRINT_SARIBBONBAR
SA_RIBBON_EXPORT friend QDebug operator<<(QDebug debug, const SARibbonBar& ribbon);
#endif
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SARibbonBar::RibbonStyles)
#endif // SARIBBONBAR_H
@@ -0,0 +1,123 @@
# 这里要做如下改动:
# 1.framelesshelper/qmake/inc/core/framelesshelper.config中的FRAMELESSHELPER_FEATURE_static_build设置为-1
# 2.要预定义FRAMELESSHELPER_CORE_LIBRARY宏,这样才能导出符号
# 3.要预定义FRAMELESSHELPER_WIDGETS_LIBRARY宏,这样才能导出符号
contains( SA_RIBBON_CONFIG, use_frameless ) {
message("use frameless lib,compile with c+=17")
# 引入第三方库frameless后,必须要求c++17
!contains(CONFIG,C++17){
CONFIG += c++17
}
!exists($$SA_RIBBON_QWindowKit_Install_DIR){
message("SA_RIBBON_QWindowKit_Install_DIR not define,set to $$SARIBBON_BIN_DIR")
SA_RIBBON_QWindowKit_Install_DIR = $$SARIBBON_BIN_DIR
}
SA_RIBBON_QWindowKit_QMake_DIR = $$SA_RIBBON_QWindowKit_Install_DIR/share/QWindowKit/qmake
include($$SA_RIBBON_QWindowKit_QMake_DIR/QWKCore.pri)
include($$SA_RIBBON_QWindowKit_QMake_DIR/QWKWidgets.pri)
# 定义SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1是的使用framelss库
DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1
contains( SA_RIBBON_CONFIG, enable_snap_layout ) {
DEFINES += SARIBBON_ENABLE_SNAP_LAYOUT=1
}
}else{
message("do not use_frameless lib,compile with c+=14")
!contains(CONFIG,C++14){
CONFIG += c++14
}
DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=0
}
message("SA_RIBBON_CONFIG="$${SA_RIBBON_CONFIG})
#-------------------------------------------------
#
# SARibbon的文件
#
#-------------------------------------------------
SOURCES += \
$$PWD/SAFramelessHelper.cpp \
$$PWD/SARibbonActionsManager.cpp \
$$PWD/SARibbonBar.cpp \
$$PWD/SARibbonColorToolButton.cpp \
$$PWD/SARibbonCustomizeData.cpp \
$$PWD/SARibbonCustomizeDialog.cpp \
$$PWD/SARibbonCustomizeWidget.cpp \
$$PWD/SARibbonElementFactory.cpp \
$$PWD/SARibbonMainWindow.cpp \
$$PWD/SARibbonSystemButtonBar.cpp \
$$PWD/SARibbonApplicationButton.cpp \
$$PWD/SARibbonTabBar.cpp \
$$PWD/SARibbonCategory.cpp \
$$PWD/SARibbonContextCategory.cpp \
$$PWD/SARibbonPannel.cpp \
$$PWD/SARibbonToolButton.cpp \
$$PWD/SARibbonMenu.cpp \
$$PWD/SARibbonPannelOptionButton.cpp \
$$PWD/SARibbonSeparatorWidget.cpp \
$$PWD/SARibbonCategoryLayout.cpp \
$$PWD/SARibbonGallery.cpp \
$$PWD/SARibbonControlButton.cpp \
$$PWD/SARibbonGalleryGroup.cpp \
$$PWD/SARibbonGalleryItem.cpp \
$$PWD/SARibbonComboBox.cpp \
$$PWD/SARibbonElementManager.cpp \
$$PWD/SARibbonLineEdit.cpp \
$$PWD/SARibbonCheckBox.cpp \
$$PWD/SARibbonButtonGroupWidget.cpp \
$$PWD/SARibbonStackedWidget.cpp \
$$PWD/SARibbonQuickAccessBar.cpp \
$$PWD/SARibbonCtrlContainer.cpp \
$$PWD/SARibbonPannelLayout.cpp \
$$PWD/SARibbonPannelItem.cpp \
$$PWD/SARibbonLineWidgetContainer.cpp \
$$PWD/SARibbonWidget.cpp
HEADERS += \
$$PWD/SAFramelessHelper.h \
$$PWD/SARibbonActionsManager.h \
$$PWD/SARibbonBar.h \
$$PWD/SARibbonColorToolButton.h \
$$PWD/SARibbonCustomizeData.h \
$$PWD/SARibbonCustomizeDialog.h \
$$PWD/SARibbonCustomizeWidget.h \
$$PWD/SARibbonElementFactory.h \
$$PWD/SARibbonMainWindow.h \
$$PWD/SARibbonSystemButtonBar.h \
$$PWD/SARibbonApplicationButton.h \
$$PWD/SARibbonTabBar.h \
$$PWD/SARibbonCategory.h \
$$PWD/SARibbonContextCategory.h \
$$PWD/SARibbonPannel.h \
$$PWD/SARibbonToolButton.h \
$$PWD/SARibbonMenu.h \
$$PWD/SARibbonGlobal.h \
$$PWD/SARibbonPannelOptionButton.h \
$$PWD/SARibbonSeparatorWidget.h \
$$PWD/SARibbonCategoryLayout.h \
$$PWD/SARibbonGallery.h \
$$PWD/SARibbonControlButton.h \
$$PWD/SARibbonGalleryGroup.h \
$$PWD/SARibbonGalleryItem.h \
$$PWD/SARibbonComboBox.h \
$$PWD/SARibbonElementManager.h \
$$PWD/SARibbonLineEdit.h \
$$PWD/SARibbonCheckBox.h \
$$PWD/SARibbonButtonGroupWidget.h \
$$PWD/SARibbonStackedWidget.h \
$$PWD/SARibbonQuickAccessBar.h \
$$PWD/SARibbonCtrlContainer.h \
$$PWD/SARibbonPannelLayout.h \
$$PWD/SARibbonPannelItem.h \
$$PWD/SARibbonLineWidgetContainer.h \
$$PWD/SARibbonWidget.h
RESOURCES += \
$$PWD/resource.qrc
# 加入SAColorWidgets组件
include($$PWD/colorWidgets/SAColorWidgets.pri)
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
@@ -0,0 +1,22 @@
#-------------------------------------------------
#
# Project created by QtCreator 2017-09-21T14:04:12
#
#-------------------------------------------------
QT += core gui
TEMPLATE = lib
DEFINES += SA_RIBBON_BAR_MAKE_LIB #定义此宏将构建库
DEFINES += SA_COLOR_WIDGETS_MAKE_LIB #定义此宏将构建ColorWidgets库
#DEFINES += SA_RIBBON_DEBUG_HELP_DRAW # 此宏将绘制辅助线用于调试
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
include($$PWD/../../common.pri)
TARGET = $${SARIBBON_LIB_NAME}
DESTDIR = $${SARIBBON_BIN_DIR}/bin
include($$PWD/SARibbonBar.pri)
@@ -0,0 +1,15 @@
# This module defines
# @PROJECT_NAME@_FOUND, if false, do not try to link to @PROJECT_NAME@
# @PROJECT_NAME@_INCLUDE_DIR, where to find the headers
# @PROJECT_NAME@_LIBRARIES, where to find the libs
@PACKAGE_INIT@
set (PackageName @SARIBBON_LIB_NAME@)
set (@SARIBBON_LIB_NAME@_VERSION @SARIBBON_VERSION@)
include ( ${CMAKE_CURRENT_LIST_DIR}/${PackageName}Targets.cmake )
set_and_check ( ${PackageName}_INCLUDE_DIR ${PACKAGE_PREFIX_DIR}/@SARIBBON_LIB_INCLUDE_INSTALL_DIR@ )
set ( ${PackageName}_LIBRARIES)
list ( APPEND ${PackageName}_LIBRARIES ${PackageName})
check_required_components(${PackageName})
@@ -0,0 +1,33 @@
#ifndef SARIBBONVERSIONINFO_H
#define SARIBBONVERSIONINFO_H
/**
* @file 此文档由cmake根据SARibbonBarVersionInfo.h.in自动生成,任何在此文件上的改动都将覆写
*/
/**
* @def ribbon的数字版本 {MAJ}.MIN.PAT
*/
#ifndef SA_RIBBON_BAR_VERSION_MAJ
#define SA_RIBBON_BAR_VERSION_MAJ 2
#endif
/**
* @def ribbon的数字版本 MAJ.{MIN}.PAT
*/
#ifndef SA_RIBBON_BAR_VERSION_MIN
#define SA_RIBBON_BAR_VERSION_MIN 2
#endif
/**
* @def ribbon的数字版本 MAJ.MIN.{PAT}
*/
#ifndef SA_RIBBON_BAR_VERSION_PAT
#define SA_RIBBON_BAR_VERSION_PAT 5
#endif
/**
* @def 版本号(字符串)
*/
#ifndef SARIBBON_VERSION
#define SARIBBON_VERSION "2.2.5"
#endif
#endif // SARIBBONVERSIONINFO_H
@@ -0,0 +1,33 @@
#ifndef SARIBBONVERSIONINFO_H
#define SARIBBONVERSIONINFO_H
/**
* @file 此文档由cmake根据SARibbonBarVersionInfo.h.in自动生成,任何在此文件上的改动都将覆写
*/
/**
* @def ribbon的数字版本 {MAJ}.MIN.PAT
*/
#ifndef SA_RIBBON_BAR_VERSION_MAJ
#define SA_RIBBON_BAR_VERSION_MAJ @SARIBBON_VERSION_MAJOR@
#endif
/**
* @def ribbon的数字版本 MAJ.{MIN}.PAT
*/
#ifndef SA_RIBBON_BAR_VERSION_MIN
#define SA_RIBBON_BAR_VERSION_MIN @SARIBBON_VERSION_MINOR@
#endif
/**
* @def ribbon的数字版本 MAJ.MIN.{PAT}
*/
#ifndef SA_RIBBON_BAR_VERSION_PAT
#define SA_RIBBON_BAR_VERSION_PAT @SARIBBON_VERSION_PATCH@
#endif
/**
* @def 版本号(字符串)
*/
#ifndef SARIBBON_VERSION
#cmakedefine SARIBBON_VERSION "@SARIBBON_VERSION@"
#endif
#endif // SARIBBONVERSIONINFO_H
@@ -0,0 +1,260 @@
#include "SARibbonButtonGroupWidget.h"
#include <QHBoxLayout>
#include <QDebug>
#include <QMargins>
#include <QChildEvent>
#include <QActionEvent>
#include <QWidgetAction>
#include <QApplication>
#include "SARibbonControlButton.h"
#include "SARibbonElementManager.h"
#include "SARibbonSeparatorWidget.h"
#include "SARibbonPannel.h"
//===================================================
// SARibbonButtonGroupWidget::PrivateData
//===================================================
class SARibbonButtonGroupWidget::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonButtonGroupWidget)
public:
PrivateData(SARibbonButtonGroupWidget* p);
void init();
void removeAction(QAction* a);
public:
QSize mIconSize { 20, 20 };
};
SARibbonButtonGroupWidget::PrivateData::PrivateData(SARibbonButtonGroupWidget* p) : q_ptr(p)
{
}
void SARibbonButtonGroupWidget::PrivateData::init()
{
QHBoxLayout* layout = new QHBoxLayout(q_ptr);
// 上下保留一点间隙
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(1);
q_ptr->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
}
void SARibbonButtonGroupWidget::PrivateData::removeAction(QAction* a)
{
QLayout* lay = q_ptr->layout();
int c = lay->count();
QList< QLayoutItem* > willRemoveItems;
for (int i = 0; i < c; ++i) {
QLayoutItem* item = lay->itemAt(i);
SARibbonControlButton* btn = qobject_cast< SARibbonControlButton* >(item->widget());
if (nullptr == btn) {
continue;
}
if (a == btn->defaultAction()) {
willRemoveItems.push_back(item);
}
}
// 从尾部删除
for (auto i = willRemoveItems.rbegin(); i != willRemoveItems.rend(); ++i) {
lay->removeItem(*i);
}
}
//===================================================
// SARibbonButtonGroupWidget
//===================================================
SARibbonButtonGroupWidget::SARibbonButtonGroupWidget(QWidget* parent)
: QFrame(parent), d_ptr(new SARibbonButtonGroupWidget::PrivateData(this))
{
d_ptr->init();
}
SARibbonButtonGroupWidget::~SARibbonButtonGroupWidget()
{
}
/**
* @brief 设置图标尺寸
* @param iconSize
*/
void SARibbonButtonGroupWidget::setIconSize(const QSize& ic)
{
d_ptr->mIconSize = ic;
iterate([ ic ](SARibbonControlButton* btn) -> bool {
btn->setIconSize(ic);
return true;
});
}
/**
* @brief 图标尺寸
* @return
*/
QSize SARibbonButtonGroupWidget::iconSize() const
{
return d_ptr->mIconSize;
}
QAction* SARibbonButtonGroupWidget::addAction(QAction* a, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
SARibbonPannel::setActionToolButtonStyleProperty(a, buttonStyle);
SARibbonPannel::setActionToolButtonPopupModeProperty(a, popMode);
QWidget::addAction(a);
return (a);
}
/**
* @brief 生成action
* @note action的所有权归SARibbonButtonGroupWidget
* @param text
* @param icon
* @param popMode
* @return
*/
QAction* SARibbonButtonGroupWidget::addAction(const QString& text, const QIcon& icon, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
QAction* a = new QAction(icon, text, this);
addAction(a, buttonStyle, popMode);
return (a);
}
QAction* SARibbonButtonGroupWidget::addMenu(QMenu* menu, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
QAction* a = menu->menuAction();
addAction(a, buttonStyle, popMode);
return (a);
}
QAction* SARibbonButtonGroupWidget::addSeparator()
{
QAction* a = new QAction(this);
a->setSeparator(true);
addAction(a);
return (a);
}
QAction* SARibbonButtonGroupWidget::addWidget(QWidget* w)
{
QWidgetAction* a = new QWidgetAction(this);
a->setDefaultWidget(w);
w->setAttribute(Qt::WA_Hover);
addAction(a);
return (a);
}
SARibbonControlButton* SARibbonButtonGroupWidget::actionToRibbonControlToolButton(QAction* action)
{
SARibbonControlButton* res = nullptr;
iterate([ &res, action ](SARibbonControlButton* btn) -> bool {
if (btn->defaultAction() == action) {
res = btn;
return false; // 返回false退出迭代
}
return true;
});
return (res);
}
QSize SARibbonButtonGroupWidget::sizeHint() const
{
return (layout()->sizeHint());
}
QSize SARibbonButtonGroupWidget::minimumSizeHint() const
{
return (layout()->minimumSize());
}
/**
* @brief 此函数会遍历SARibbonButtonGroupWidget下的所有SARibbonControlButton,执行函数指针(bool(SARibbonControlButton*)),函数指针返回false则停止迭代
* @param fp
* @return 中途迭代退出返回false
*/
bool SARibbonButtonGroupWidget::iterate(SARibbonButtonGroupWidget::FpButtonIterate fp)
{
QLayout* lay = layout();
int c = lay->count();
for (int i = 0; i < c; ++i) {
auto item = lay->itemAt(i);
if (!item) {
continue;
}
SARibbonControlButton* btn = qobject_cast< SARibbonControlButton* >(item->widget());
if (!btn) {
continue;
}
if (!fp(btn)) {
return false;
}
}
return true;
}
/**
* @brief 处理action的事件
*
* 这里处理了ActionAddedActionChangedActionRemoved三个事件
* ActionAdded时会生成窗口
* @param e
*/
void SARibbonButtonGroupWidget::actionEvent(QActionEvent* e)
{
QAction* a = e->action();
if (!a) {
return;
}
switch (e->type()) {
case QEvent::ActionAdded: {
QWidget* w = nullptr;
if (QWidgetAction* widgetAction = qobject_cast< QWidgetAction* >(a)) {
widgetAction->setParent(this);
w = widgetAction->requestWidget(this);
if (w != nullptr) {
w->setAttribute(Qt::WA_LayoutUsesWidgetRect);
w->show();
}
} else if (a->isSeparator()) {
SARibbonSeparatorWidget* sp = RibbonSubElementFactory->createRibbonSeparatorWidget(this);
w = sp;
}
// 不是widget,自动生成ButtonTyle
if (!w) {
SARibbonControlButton* button = RibbonSubElementFactory->createRibbonControlButton(this);
button->setAutoRaise(true);
button->setIconSize(d_ptr->mIconSize);
button->setFocusPolicy(Qt::NoFocus);
button->setDefaultAction(a);
// 属性设置
QToolButton::ToolButtonPopupMode popMode = SARibbonPannel::getActionToolButtonPopupModeProperty(a);
button->setPopupMode(popMode);
Qt::ToolButtonStyle buttonStyle = SARibbonPannel::getActionToolButtonStyleProperty(a);
button->setToolButtonStyle(buttonStyle);
// 根据QAction的属性设置按钮的大小
connect(button, &SARibbonToolButton::triggered, this, &SARibbonButtonGroupWidget::actionTriggered);
w = button;
}
layout()->addWidget(w);
updateGeometry();
} break;
case QEvent::ActionChanged: {
// 让布局重新绘制
layout()->invalidate();
updateGeometry();
} break;
case QEvent::ActionRemoved: {
d_ptr->removeAction(e->action());
updateGeometry();
} break;
default:
break;
}
QFrame::actionEvent(e);
}
@@ -0,0 +1,58 @@
#ifndef SARIBBONBUTTONGROUPWIDGET_H
#define SARIBBONBUTTONGROUPWIDGET_H
#include "SARibbonGlobal.h"
#include <QToolButton>
#include <QMenu>
#include <QFrame>
class SARibbonControlButton;
/**
* @brief 用于管理一组Action,类似于QToolBar
*/
class SA_RIBBON_EXPORT SARibbonButtonGroupWidget : public QFrame
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonButtonGroupWidget)
public:
using FpButtonIterate = std::function< bool(SARibbonControlButton*) >;
public:
explicit SARibbonButtonGroupWidget(QWidget* parent = nullptr);
~SARibbonButtonGroupWidget();
// 图标尺寸
void setIconSize(const QSize& ic);
QSize iconSize() const;
// 生成并添加一个action
QAction* addAction(QAction* a,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::DelayedPopup);
QAction* addAction(const QString& text,
const QIcon& icon,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::DelayedPopup);
QAction* addMenu(QMenu* menu,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::InstantPopup);
QAction* addSeparator();
QAction* addWidget(QWidget* w);
// 从ButtonGroupWidget中把action对应的button提取出来,如果action没有对应的button,就返回nullptr
SARibbonControlButton* actionToRibbonControlToolButton(QAction* action);
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
public:
bool iterate(FpButtonIterate fp);
Q_SIGNALS:
/**
* @brief 参考QToolBar::actionTriggered的信号
* @param action
*/
void actionTriggered(QAction* action);
protected:
virtual void actionEvent(QActionEvent* e) override;
};
#endif // SARIBBONBUTTONGROUPWIDGET_H
@@ -0,0 +1,741 @@
#include "SARibbonCategory.h"
#include <QList>
#include <QResizeEvent>
#include <QPainter>
#include <QLinearGradient>
#include <QDebug>
#include <QApplication>
#include <QHBoxLayout>
#include <QList>
#include <QMap>
#include <QResizeEvent>
#include "SARibbonCategoryLayout.h"
#include "SARibbonElementManager.h"
///
/// \brief ribbon页的代理类
/// 如果需要修改重绘SARibbonCategory,可以通过设置SARibbonCategory::setProxy
///
class SARibbonCategory::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonCategory)
public:
PrivateData(SARibbonCategory* p);
SARibbonPannel* addPannel(const QString& title);
SARibbonPannel* insertPannel(const QString& title, int index);
void addPannel(SARibbonPannel* pannel);
void insertPannel(int index, SARibbonPannel* pannel);
// 把pannel从Category中移除,不会销毁,此时pannel的所有权归还操作者
bool takePannel(SARibbonPannel* pannel);
// 移除PannelCategory会直接回收SARibbonPannel内存
bool removePannel(SARibbonPannel* pannel);
SARibbonCategory* ribbonCategory();
const SARibbonCategory* ribbonCategory() const;
// 返回所有的Pannel
QList< SARibbonPannel* > pannelList();
// 更新item的布局,此函数会调用doItemLayout
void updateItemGeometry();
void doWheelEvent(QWheelEvent* event);
// 初始化
void init(SARibbonCategory* c);
public:
bool mEnableShowPannelTitle { true }; ///< 是否运行pannel的标题栏显示
int mPannelTitleHeight { 15 }; ///< pannel的标题栏默认高度
bool mIsContextCategory { false }; ///< 标记是否是上下文标签
bool mIsCanCustomize { true }; ///< 标记是否可以自定义
int mPannelSpacing { 0 }; ///< pannel的spacing
QSize mPannelToolButtonSize { 22, 22 }; ///< 记录pannel的默认图标大小
SARibbonPannel::PannelLayoutMode mDefaultPannelLayoutMode { SARibbonPannel::ThreeRowMode };
};
SARibbonCategory::PrivateData::PrivateData(SARibbonCategory* p) : q_ptr(p)
{
}
SARibbonPannel* SARibbonCategory::PrivateData::addPannel(const QString& title)
{
if (SARibbonCategoryLayout* lay = q_ptr->categoryLayout()) {
SARibbonPannel* p = insertPannel(title, lay->pannelCount());
return p;
}
return nullptr;
}
SARibbonPannel* SARibbonCategory::PrivateData::insertPannel(const QString& title, int index)
{
SARibbonPannel* pannel = RibbonSubElementFactory->createRibbonPannel(ribbonCategory());
pannel->setPannelName(title);
pannel->setObjectName(title);
insertPannel(index, pannel);
return (pannel);
}
void SARibbonCategory::PrivateData::addPannel(SARibbonPannel* pannel)
{
if (SARibbonCategoryLayout* lay = q_ptr->categoryLayout()) {
insertPannel(lay->pannelCount(), pannel);
}
}
/**
* @brief 插入pannel到layout
*
* 所有的添加操作最终会调用此函数
* @param index
* @param pannel
*/
void SARibbonCategory::PrivateData::insertPannel(int index, SARibbonPannel* pannel)
{
if (nullptr == pannel) {
return;
}
SARibbonCategoryLayout* lay = qobject_cast< SARibbonCategoryLayout* >(q_ptr->layout());
if (nullptr == lay) {
return;
}
if (pannel->parentWidget() != q_ptr) {
pannel->setParent(q_ptr);
}
// 同步状态
pannel->setEnableShowTitle(mEnableShowPannelTitle);
pannel->setTitleHeight(mPannelTitleHeight);
pannel->setPannelLayoutMode(mDefaultPannelLayoutMode);
pannel->setSpacing(mPannelSpacing);
pannel->setToolButtonIconSize(mPannelToolButtonSize);
index = qMax(0, index);
index = qMin(lay->pannelCount(), index);
lay->insertPannel(index, pannel);
pannel->setVisible(true);
QObject::connect(pannel, &SARibbonPannel::actionTriggered, ribbonCategory(), &SARibbonCategory::actionTriggered);
}
bool SARibbonCategory::PrivateData::takePannel(SARibbonPannel* pannel)
{
SARibbonCategoryLayout* lay = qobject_cast< SARibbonCategoryLayout* >(q_ptr->layout());
if (nullptr == lay) {
return false;
}
return lay->takePannel(pannel);
}
bool SARibbonCategory::PrivateData::removePannel(SARibbonPannel* pannel)
{
if (takePannel(pannel)) {
pannel->hide();
pannel->deleteLater();
return (true);
}
return (false);
}
QList< SARibbonPannel* > SARibbonCategory::PrivateData::pannelList()
{
if (SARibbonCategoryLayout* lay = q_ptr->categoryLayout()) {
return lay->pannelList();
}
return QList< SARibbonPannel* >();
}
SARibbonCategory* SARibbonCategory::PrivateData::ribbonCategory()
{
return (q_ptr);
}
const SARibbonCategory* SARibbonCategory::PrivateData::ribbonCategory() const
{
return (q_ptr);
}
void SARibbonCategory::PrivateData::updateItemGeometry()
{
#if SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategory::PrivateData::updateItemGeometry,categoryName=" << q_ptr->categoryName();
#endif
SARibbonCategoryLayout* lay = qobject_cast< SARibbonCategoryLayout* >(q_ptr->layout());
if (!lay) {
return;
}
const QList< SARibbonPannel* > pannels = lay->pannelList();
for (auto pannel : pannels) {
pannel->updateItemGeometry();
}
lay->updateGeometryArr();
return;
}
void SARibbonCategory::PrivateData::doWheelEvent(QWheelEvent* event)
{
SARibbonCategoryLayout* lay = q_ptr->categoryLayout();
if (nullptr == lay) {
return;
}
QSize contentSize = lay->categoryContentSize();
// 求总宽
int totalWidth = lay->categoryTotalWidth();
if (totalWidth > contentSize.width()) {
// 这个时候滚动有效
int scrollpix = 40;
// Qt6 取消了QWheelEvent::delta函数
// 是要下面方法可兼容qt5/6
QPoint numPixels = event->pixelDelta();
QPoint numDegrees = event->angleDelta() / 8;
if (!numPixels.isNull()) {
if (numDegrees.y() < 0) {
scrollpix = -scrollpix;
}
} else if (!numDegrees.isNull()) {
if (numDegrees.y() < 0) {
scrollpix = -scrollpix;
}
}
lay->scroll(scrollpix);
} else {
// 这时候无需处理事件,把滚动事件上发让父级也能接收
event->ignore();
// 如滚动过就还原
if (lay->isScrolled()) {
lay->scroll(0);
}
}
}
void SARibbonCategory::PrivateData::init(SARibbonCategory* c)
{
c->setLayout(new SARibbonCategoryLayout(c));
c->connect(c, &SARibbonCategory::windowTitleChanged, c, &SARibbonCategory::categoryNameChanged);
}
//----------------------------------------------------
// SARibbonCategory
//----------------------------------------------------
SARibbonCategory::SARibbonCategory(QWidget* p) : QFrame(p), d_ptr(new SARibbonCategory::PrivateData(this))
{
d_ptr->init(this);
}
SARibbonCategory::SARibbonCategory(const QString& name, QWidget* p)
: QFrame(p), d_ptr(new SARibbonCategory::PrivateData(this))
{
setCategoryName(name);
d_ptr->init(this);
}
SARibbonCategory::~SARibbonCategory()
{
}
/**
* @brief category的名字,等同windowTitle函数
* @return
*/
QString SARibbonCategory::categoryName() const
{
return (windowTitle());
}
/**
* @brief 设置category名字,等同setWindowTitle
* @param title
*/
void SARibbonCategory::setCategoryName(const QString& title)
{
setWindowTitle(title);
}
bool SARibbonCategory::event(QEvent* e)
{
#if SA_DEBUG_PRINT_EVENT
if (e->type() != QEvent::Paint) {
qDebug() << "SARibbonCategory event(" << e->type() << "),name=" << categoryName();
}
#endif
return QWidget::event(e);
}
/**
* @brief pannel的模式
* @return
*/
SARibbonPannel::PannelLayoutMode SARibbonCategory::pannelLayoutMode() const
{
return (d_ptr->mDefaultPannelLayoutMode);
}
/**
* @brief 设置pannel的模式
*
* 在@ref SARibbonBar 调用@ref SARibbonBar::setRibbonStyle 函数时,会对所有的SARibbonCategory调用此函数
* 把新的SARibbonPannel::PannelLayoutMode设置进去
* @param m
*/
void SARibbonCategory::setPannelLayoutMode(SARibbonPannel::PannelLayoutMode m)
{
d_ptr->mDefaultPannelLayoutMode = m;
iterate([ m ](SARibbonPannel* p) -> bool {
p->setPannelLayoutMode(m);
return true;
});
updateItemGeometry();
}
/**
* @brief 添加pannel
*
* @note pannel的所有权由SARibbonCategory来管理,请不要在外部对其进行销毁
* @param title pannel的标题,在office/wps的三行模式下会显示在pannel的下方
* @return 返回生成的@ref SARibbonPannel 指针
* @see 对SARibbonPannel的其他操作,参考 @ref SARibbonCategory::takePannel
*/
SARibbonPannel* SARibbonCategory::addPannel(const QString& title)
{
return (d_ptr->addPannel(title));
}
/**
* @brief 添加pannel
* @param pannel pannel的所有权SARibbonCategory来管理
*/
void SARibbonCategory::addPannel(SARibbonPannel* pannel)
{
d_ptr->addPannel(pannel);
}
/**
* @brief qt designer专用
* @param pannel
*/
void SARibbonCategory::addPannel(QWidget* pannel)
{
SARibbonPannel* p = qobject_cast< SARibbonPannel* >(pannel);
if (p) {
addPannel(p);
}
}
/**
* @brief 新建一个pannel,并插入到index位置
* @param title pannel的title
* @param index 插入的位置,如果index超出category里pannel的个数,将插入到最后
* @return 返回生成的@ref SARibbonPannel 指针
* @note 如果
*/
SARibbonPannel* SARibbonCategory::insertPannel(const QString& title, int index)
{
return (d_ptr->insertPannel(title, index));
}
/**
* @brief 通过名字查找pannel
* @param title
* @return 如果有重名,只会返回第一个符合条件的
*/
SARibbonPannel* SARibbonCategory::pannelByName(const QString& title) const
{
if (SARibbonCategoryLayout* lay = categoryLayout()) {
return lay->pannelByName(title);
}
return nullptr;
}
/**
* @brief 通过ObjectName查找pannel
* @param objname
* @return
*/
SARibbonPannel* SARibbonCategory::pannelByObjectName(const QString& objname) const
{
if (SARibbonCategoryLayout* lay = categoryLayout()) {
return lay->pannelByObjectName(objname);
}
return nullptr;
}
/**
* @brief 通过索引找到pannel,如果超过索引范围,会返回nullptr
* @param index
* @return 如果超过索引范围,会返回nullptr
*/
SARibbonPannel* SARibbonCategory::pannelByIndex(int index) const
{
if (SARibbonCategoryLayout* lay = categoryLayout()) {
return lay->pannelByIndex(index);
}
return nullptr;
}
/**
* @brief 查找pannel对应的索引
* @param p
* @return 如果找不到,返回-1
*/
int SARibbonCategory::pannelIndex(SARibbonPannel* p) const
{
if (SARibbonCategoryLayout* lay = categoryLayout()) {
return lay->pannelIndex(p);
}
return -1;
}
/**
* @brief 移动一个Pannel从from index到to index
* @param from 要移动pannel的index
* @param to 要移动到的位置
*/
void SARibbonCategory::movePannel(int from, int to)
{
if (from == to) {
return;
}
if (to < 0) {
to = 0;
}
if (to >= pannelCount()) {
to = pannelCount() - 1;
}
if (SARibbonCategoryLayout* lay = categoryLayout()) {
lay->movePannel(from, to);
}
}
/**
* @brief 把pannel脱离SARibbonCategory的管理
* @param pannel 需要提取的pannel
* @return 成功返回true,否则返回false
*/
bool SARibbonCategory::takePannel(SARibbonPannel* pannel)
{
return (d_ptr->takePannel(pannel));
}
/**
* @brief 移除PannelCategory会直接回收SARibbonPannel内存
* @param pannel 需要移除的pannel
* @note 移除后pannel为野指针,一般操作完建议把pannel指针设置为nullptr
*
* 此操作等同于:
*
* @code
* SARibbonPannel* pannel;
* ...
* category->takePannel(pannel);
* pannel->hide();
* pannel->deleteLater();
* @endcode
*/
bool SARibbonCategory::removePannel(SARibbonPannel* pannel)
{
return (d_ptr->removePannel(pannel));
}
/**
* @brief 移除pannel
* @param index pannel的索引,如果超出会返回false
* @return 成功返回true
*/
bool SARibbonCategory::removePannel(int index)
{
SARibbonPannel* p = pannelByIndex(index);
if (nullptr == p) {
return (false);
}
return (removePannel(p));
}
/**
* @brief 返回Category下的所有pannel
* @return
*/
QList< SARibbonPannel* > SARibbonCategory::pannelList() const
{
return (d_ptr->pannelList());
}
QSize SARibbonCategory::sizeHint() const
{
if (SARibbonCategoryLayout* lay = categoryLayout()) {
return lay->sizeHint();
}
return QSize(1000, 100);
}
/**
* @brief 如果是ContextCategory,此函数返回true
* @return
*/
bool SARibbonCategory::isContextCategory() const
{
return (d_ptr->mIsContextCategory);
}
/**
* @brief 返回pannel的个数
* @return
*/
int SARibbonCategory::pannelCount() const
{
if (SARibbonCategoryLayout* lay = categoryLayout()) {
return lay->pannelCount();
}
return -1;
}
/**
* @brief 判断是否可以自定义
* @return
*/
bool SARibbonCategory::isCanCustomize() const
{
return (d_ptr->mIsCanCustomize);
}
/**
* @brief 设置是否可以自定义
* @param b
*/
void SARibbonCategory::setCanCustomize(bool b)
{
d_ptr->mIsCanCustomize = b;
}
/**
* @brief pannel标题栏的高度
* @return
*/
int SARibbonCategory::pannelTitleHeight() const
{
return d_ptr->mPannelTitleHeight;
}
/**
* @brief 设置pannel的高度
* @param h
*/
void SARibbonCategory::setPannelTitleHeight(int h)
{
d_ptr->mPannelTitleHeight = h;
iterate([ h ](SARibbonPannel* p) -> bool {
p->setTitleHeight(h);
return true;
});
}
/**
* @brief 是否pannel显示标题栏
* @return
*/
bool SARibbonCategory::isEnableShowPannelTitle() const
{
return d_ptr->mEnableShowPannelTitle;
}
/**
* @brief 设置显示pannel标题
* @param on
*/
void SARibbonCategory::setEnableShowPannelTitle(bool on)
{
d_ptr->mEnableShowPannelTitle = on;
iterate([ on ](SARibbonPannel* p) -> bool {
p->setEnableShowTitle(on);
return true;
});
}
/**
* @brief 获取对应的ribbonbar
* @return 如果没有加入ribbonbar的管理,此值为null
*/
SARibbonBar* SARibbonCategory::ribbonBar() const
{
// 第一个par是stackwidget
if (QWidget* par = parentWidget()) {
// 理论此时是ribbonbar
par = par->parentWidget();
while (par) {
if (SARibbonBar* ribbon = qobject_cast< SARibbonBar* >(par)) {
return ribbon;
}
par = par->parentWidget();
}
}
return nullptr;
}
/**
* @brief 刷新category的布局,适用于改变ribbon的模式之后调用
*/
void SARibbonCategory::updateItemGeometry()
{
#if SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategory name=" << categoryName() << " updateItemGeometry";
#endif
d_ptr->updateItemGeometry();
}
/**
* @brief 此函数会遍历Category下的所有pannel,执行函数指针
* @param fp 函数指针返回false则停止迭代
* @return 返回false代表没有迭代完所有的category,中途接收到回调函数的false返回而中断迭代
*/
bool SARibbonCategory::iterate(FpPannelIterate fp)
{
const QList< SARibbonPannel* > ps = pannelList();
for (SARibbonPannel* p : ps) {
if (!fp(p)) {
return false;
}
}
return true;
}
/**
@brief 设置Category的对齐方式
@param al
*/
void SARibbonCategory::setCategoryAlignment(SARibbonAlignment al)
{
SARibbonCategoryLayout* lay = qobject_cast< SARibbonCategoryLayout* >(layout());
if (lay) {
lay->setCategoryAlignment(al);
}
}
/**
@brief Category的对齐方式
@return
*/
SARibbonAlignment SARibbonCategory::categoryAlignment() const
{
SARibbonCategoryLayout* lay = qobject_cast< SARibbonCategoryLayout* >(layout());
if (lay) {
return lay->categoryAlignment();
}
return SARibbonAlignment::AlignLeft;
}
/**
* @brief 设置pannel的spacing
* @param n
*/
void SARibbonCategory::setPannelSpacing(int n)
{
d_ptr->mPannelSpacing = n;
iterate([ n ](SARibbonPannel* pannel) -> bool {
if (pannel) {
pannel->setSpacing(n);
}
return true;
});
}
/**
* @brief pannel的spacing
* @return
*/
int SARibbonCategory::pannelSpacing() const
{
return d_ptr->mPannelSpacing;
}
/**
* @brief 设置pannel按钮的icon尺寸,large action不受此尺寸影响
* @param s
*/
void SARibbonCategory::setPannelToolButtonIconSize(const QSize& s)
{
d_ptr->mPannelToolButtonSize = s;
iterate([ s ](SARibbonPannel* pannel) -> bool {
if (pannel) {
pannel->setToolButtonIconSize(s);
}
return true;
});
}
/**
* @brief pannel按钮的icon尺寸,large action不受此尺寸影响
* @return
*/
QSize SARibbonCategory::pannelToolButtonIconSize() const
{
return d_ptr->mPannelToolButtonSize;
}
/**
* @brief 在超出边界情况下,滚轮可滚动pannel
* @param event
*/
void SARibbonCategory::wheelEvent(QWheelEvent* event)
{
d_ptr->doWheelEvent(event);
}
void SARibbonCategory::changeEvent(QEvent* event)
{
switch (event->type()) {
case QEvent::StyleChange: {
if (layout()) {
#if SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategory changeEvent(StyleChange),categoryName=" << categoryName();
#endif
layout()->invalidate();
}
} break;
case QEvent::FontChange: {
#if SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategory changeEvent(FontChange),categoryName=" << categoryName();
#endif
QFont f = font();
iterate([ f ](SARibbonPannel* p) -> bool {
p->setFont(f);
return true;
});
if (layout()) {
layout()->invalidate();
}
} break;
default:
break;
};
return QWidget::changeEvent(event);
}
/**
* @brief 标记这个是上下文标签
* @param isContextCategory
*/
void SARibbonCategory::markIsContextCategory(bool isContextCategory)
{
d_ptr->mIsContextCategory = isContextCategory;
}
/**
* @brief 获取SARibbonCategoryLayoutlayout
* @return
*/
SARibbonCategoryLayout* SARibbonCategory::categoryLayout() const
{
return qobject_cast< SARibbonCategoryLayout* >(layout());
}
//===================================================
// SARibbonCategoryScrollButton
//===================================================
SARibbonCategoryScrollButton::SARibbonCategoryScrollButton(Qt::ArrowType arr, QWidget* p) : QToolButton(p)
{
setArrowType(arr);
}
SARibbonCategoryScrollButton::~SARibbonCategoryScrollButton()
{
}
@@ -0,0 +1,164 @@
#ifndef SARIBBONCATEGORY_H
#define SARIBBONCATEGORY_H
#include "SARibbonGlobal.h"
#include <QFrame>
#include "SARibbonPannel.h"
#include <QScopedPointer>
#include <QPushButton>
#include <QWheelEvent>
class QAction;
class QHBoxLayout;
class QWheelEvent;
class SARibbonBar;
class SARibbonCategoryLayout;
/**
* @brief 一项ribbon tab页
* @note SARibbonCategory的windowTitle影响了其在SARibbonBar的标签显示,
* 如果要改标签名字,直接调用SARibbonCategory的setWindowTitle函数
*/
class SA_RIBBON_EXPORT SARibbonCategory : public QFrame
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonCategory)
friend class SARibbonBar;
friend class SARibbonContextCategory;
Q_PROPERTY(bool isCanCustomize READ isCanCustomize WRITE setCanCustomize)
Q_PROPERTY(QString categoryName READ categoryName WRITE setCategoryName)
public:
using FpPannelIterate = std::function< bool(SARibbonPannel*) >;
public:
explicit SARibbonCategory(QWidget* p = nullptr);
explicit SARibbonCategory(const QString& name, QWidget* p = nullptr);
~SARibbonCategory();
// category的名字
QString categoryName() const;
// 设置category名字,等同setWindowTitle
void setCategoryName(const QString& title);
// 设置pannel的模式
SARibbonPannel::PannelLayoutMode pannelLayoutMode() const;
void setPannelLayoutMode(SARibbonPannel::PannelLayoutMode m);
// 添加pannel
SARibbonPannel* addPannel(const QString& title);
// 添加pannel
void addPannel(SARibbonPannel* pannel);
// qt designer专用
Q_INVOKABLE void addPannel(QWidget* pannel);
// 插入pannel
SARibbonPannel* insertPannel(const QString& title, int index);
// 通过名字查找pannel
SARibbonPannel* pannelByName(const QString& title) const;
// 通过ObjectName查找pannel
SARibbonPannel* pannelByObjectName(const QString& objname) const;
// 通过索引找到pannel,如果超过索引范围,会返回nullptr
SARibbonPannel* pannelByIndex(int index) const;
// 查找pannel的index
int pannelIndex(SARibbonPannel* p) const;
// 移动一个Pannel从from index到to index
void movePannel(int from, int to);
// 把pannel从Category中移除,不会销毁,此时pannel的所有权归还操作者
bool takePannel(SARibbonPannel* pannel);
// 移除PannelCategory会直接回收SARibbonPannel内存
bool removePannel(SARibbonPannel* pannel);
bool removePannel(int index);
// 返回所有的Pannel
QList< SARibbonPannel* > pannelList() const;
//
QSize sizeHint() const Q_DECL_OVERRIDE;
// 如果是ContextCategory,此函数返回true
bool isContextCategory() const;
// pannel的个数
int pannelCount() const;
// 判断是否可以自定义
bool isCanCustomize() const;
void setCanCustomize(bool b);
// 设置pannel的标题栏高度
int pannelTitleHeight() const;
void setPannelTitleHeight(int h);
// 设置pannel是否显示标题栏
bool isEnableShowPannelTitle() const;
void setEnableShowPannelTitle(bool on);
// 设置Category的对齐方式
void setCategoryAlignment(SARibbonAlignment al);
SARibbonAlignment categoryAlignment() const;
// 设置pannel的spacing
void setPannelSpacing(int n);
int pannelSpacing() const;
// 设置pannel按钮的icon尺寸,large action不受此尺寸影响
void setPannelToolButtonIconSize(const QSize& s);
QSize pannelToolButtonIconSize() const;
// 获取对应的ribbonbar,如果没有加入ribbonbar的管理,此值为null
SARibbonBar* ribbonBar() const;
// 刷新category的尺寸布局
void updateItemGeometry();
// 此函数会遍历Category下的所有pannel,执行函数指针,函数指针返回false则停止迭代
bool iterate(FpPannelIterate fp);
Q_SIGNALS:
/**
* @brief category标题发生了改变信号
* @param n
*/
void categoryNameChanged(const QString& n);
/**
* @brief 参考QToolBar::actionTriggered的信号
* @param action
*/
void actionTriggered(QAction* action);
protected:
virtual bool event(QEvent* e) override;
// 处理滚轮事件
void wheelEvent(QWheelEvent* event) override;
//
void changeEvent(QEvent* event) override;
// 标记这个是上下文标签
void markIsContextCategory(bool isContextCategory = true);
// 获取SARibbonCategoryLayoutlayout
SARibbonCategoryLayout* categoryLayout() const;
};
/**
* @brief SARibbonCategory无法完全显示时,显示的调整按钮
*
* 重新定义是为了防止被外部的样式影响,同时可以使用SARibbonCategoryScrollButton的样式定义
*/
class SA_RIBBON_EXPORT SARibbonCategoryScrollButton : public QToolButton
{
Q_OBJECT
public:
explicit SARibbonCategoryScrollButton(Qt::ArrowType arr, QWidget* p = nullptr);
~SARibbonCategoryScrollButton();
};
#endif // SARIBBONCATEGORY_H
@@ -0,0 +1,723 @@
#include "SARibbonCategoryLayout.h"
#include <QLayoutItem>
#include "SARibbonPannel.h"
#include "SARibbonElementManager.h"
#include "SARibbonSeparatorWidget.h"
#include <QApplication>
#include <QDebug>
#ifndef SARibbonCategoryLayout_DEBUG_PRINT
#define SARibbonCategoryLayout_DEBUG_PRINT 1
#endif
/**
* @brief The SARibbonCategoryLayoutPrivate class
*/
class SARibbonCategoryLayout::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonCategoryLayout)
public:
PrivateData(SARibbonCategoryLayout* p);
// 计算所有元素的sizehint总宽度
int totalSizeHintWidth() const;
public:
bool mDirty { true };
bool mIsRightScrollBtnShow { false }; ///< 标记右滚动按钮是否需要显示
bool mIsLeftScrollBtnShow { false }; ///< 标记左滚动按钮是否需要显示
SARibbonCategoryScrollButton* mLeftScrollBtn { nullptr }; ///< 在区域无法显示时显示的按钮
SARibbonCategoryScrollButton* mRightScrollBtn { nullptr }; ///< 在区域无法显示时显示的按钮
int mTotalWidth { 0 };
int mXBase { 0 };
QSize mSizeHint;
QSize mMinSizeHint;
QList< SARibbonCategoryLayoutItem* > mItemList;
SARibbonAlignment mCategoryAlignment { SARibbonAlignment::AlignLeft }; ///< 对齐方式
};
//=============================================================
// SARibbonCategoryLayoutPrivate
//=============================================================
SARibbonCategoryLayout::PrivateData::PrivateData(SARibbonCategoryLayout* p) : q_ptr(p)
{
}
/**
* @brief 计算所有元素的SizeHint宽度总和
* @return
*/
int SARibbonCategoryLayout::PrivateData::totalSizeHintWidth() const
{
int total = 0;
QMargins mag = q_ptr->contentsMargins();
#if SA_DEBUG_PRINT_SIZE_HINT
int debug_i__ = 0;
QString debug_totalSizeHintWidth__;
#endif
if (!mag.isNull()) {
total += (mag.left() + mag.right());
}
// 先计算总长
for (SARibbonCategoryLayoutItem* item : qAsConst(mItemList)) {
if (item->isEmpty()) {
// 如果是hide就直接跳过
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
++debug_i__;
debug_totalSizeHintWidth__ += QString(" [%1](%2)is empty skip\n")
.arg(debug_i__)
.arg(item->toPannelWidget()->pannelName());
#endif
continue;
}
// 这里要使用widget()->sizeHint(),因为pannel的标题会影总体布局,此处需要修改
// TODO
QSize pannelSize = item->widget()->sizeHint();
QSize SeparatorSize(0, 0);
if (item->separatorWidget) {
SeparatorSize = item->separatorWidget->sizeHint();
}
total += pannelSize.width();
total += SeparatorSize.width();
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
++debug_i__;
debug_totalSizeHintWidth__ += QString("|-[%1]pannelSize=(%2,%3),SeparatorSize=(%4,%5),name=(%6) \n")
.arg(debug_i__)
.arg(pannelSize.width())
.arg(pannelSize.height())
.arg(SeparatorSize.width())
.arg(SeparatorSize.height())
.arg(item->toPannelWidget()->pannelName());
#endif
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategoryLayout.totalSizeHintWidth=" << total;
qDebug().noquote() << debug_totalSizeHintWidth__;
#endif
return (total);
}
//=============================================================
// SARibbonCategoryLayout
//=============================================================
SARibbonCategoryLayout::SARibbonCategoryLayout(SARibbonCategory* parent)
: QLayout(parent), d_ptr(new SARibbonCategoryLayout::PrivateData(this))
{
setContentsMargins(1, 1, 1, 1);
d_ptr->mLeftScrollBtn = new SARibbonCategoryScrollButton(Qt::LeftArrow, parent);
d_ptr->mRightScrollBtn = new SARibbonCategoryScrollButton(Qt::RightArrow, parent);
d_ptr->mLeftScrollBtn->setVisible(false);
d_ptr->mRightScrollBtn->setVisible(false);
connect(d_ptr->mLeftScrollBtn, &QToolButton::clicked, this, &SARibbonCategoryLayout::onLeftScrollButtonClicked);
connect(d_ptr->mRightScrollBtn, &QToolButton::clicked, this, &SARibbonCategoryLayout::onRightScrollButtonClicked);
}
SARibbonCategoryLayout::~SARibbonCategoryLayout()
{
while (auto item = takePannelItem(0)) {
delete item;
}
}
SARibbonCategory* SARibbonCategoryLayout::ribbonCategory() const
{
return (qobject_cast< SARibbonCategory* >(parentWidget()));
}
void SARibbonCategoryLayout::addItem(QLayoutItem* item)
{
Q_UNUSED(item);
qWarning() << tr("in SARibbonCategoryLayout cannot addItem,use addPannel instead");
invalidate();
}
/**
* @brief 返回pannel的layout
* @param index 索引
* @return
* @note 注意,pannel是和分割线一起的,但这个只返回一个pannel对应的layout
*/
QLayoutItem* SARibbonCategoryLayout::itemAt(int index) const
{
SARibbonCategoryLayoutItem* item = d_ptr->mItemList.value(index, nullptr);
return (item);
}
/**
* @brief 提取layout
* @param index
* @return
*/
QLayoutItem* SARibbonCategoryLayout::takeAt(int index)
{
QLayoutItem* r = takePannelItem(index);
invalidate();
return r;
}
SARibbonCategoryLayoutItem* SARibbonCategoryLayout::takePannelItem(int index)
{
if ((index >= 0) && (index < d_ptr->mItemList.size())) {
SARibbonCategoryLayoutItem* item = d_ptr->mItemList.takeAt(index);
if (item->widget()) {
item->widget()->hide();
}
if (item->separatorWidget) {
item->separatorWidget->hide();
}
return (item);
}
return (nullptr);
}
SARibbonCategoryLayoutItem* SARibbonCategoryLayout::takePannelItem(SARibbonPannel* pannel)
{
for (int i = 0; i < d_ptr->mItemList.size(); ++i) {
SARibbonCategoryLayoutItem* item = d_ptr->mItemList[ i ];
if (item->widget() == pannel) {
return (takePannelItem(i));
}
}
return (nullptr);
}
/**
* @brief 移除pannel,对应的分割线也会删除
* @param pannel
* @return
*/
bool SARibbonCategoryLayout::takePannel(SARibbonPannel* pannel)
{
SARibbonCategoryLayoutItem* i = takePannelItem(pannel);
if (i) {
SARibbonSeparatorWidget* sp = i->separatorWidget;
if (sp) {
sp->deleteLater();
}
delete i;
invalidate();
return true;
}
return false;
}
int SARibbonCategoryLayout::count() const
{
return (d_ptr->mItemList.size());
}
void SARibbonCategoryLayout::addPannel(SARibbonPannel* pannel)
{
insertPannel(d_ptr->mItemList.count(), pannel);
}
/**
* @brief 插入一个pannel
* @param index 索引
* @param pannel
* @return 返回对应的分割线SARibbonSeparatorWidget
* @note 在SARibbonCategoryLayout的布局中,一个pannel会携带一个分割线
*/
void SARibbonCategoryLayout::insertPannel(int index, SARibbonPannel* pannel)
{
index = qMax(0, index);
index = qMin(d_ptr->mItemList.count(), index);
SARibbonCategoryLayoutItem* item = new SARibbonCategoryLayoutItem(pannel);
// 分割线
item->separatorWidget = RibbonSubElementFactory->createRibbonSeparatorWidget(parentWidget());
// 插入list中
d_ptr->mItemList.insert(index, item);
// 标记需要重新计算尺寸
invalidate();
}
QSize SARibbonCategoryLayout::sizeHint() const
{
if (d_ptr->mSizeHint.isNull()) {
SARibbonCategoryLayout* that = const_cast< SARibbonCategoryLayout* >(this);
that->updateGeometryArr();
}
return (d_ptr->mSizeHint);
}
QSize SARibbonCategoryLayout::minimumSize() const
{
if (d_ptr->mMinSizeHint.isNull()) {
SARibbonCategoryLayout* that = const_cast< SARibbonCategoryLayout* >(this);
that->updateGeometryArr();
}
return (d_ptr->mMinSizeHint);
}
/**
* @brief SARibbonCategory充满整个stacked widget
* @return
*/
Qt::Orientations SARibbonCategoryLayout::expandingDirections() const
{
return (Qt::Horizontal | Qt::Vertical);
}
void SARibbonCategoryLayout::invalidate()
{
d_ptr->mDirty = true;
QLayout::invalidate();
}
/**
* @brief category的内容尺寸(把margins减去)
* @return
*/
QSize SARibbonCategoryLayout::categoryContentSize() const
{
SARibbonCategory* category = ribbonCategory();
QSize s = category->size();
QMargins mag = contentsMargins();
if (!mag.isNull()) {
s.rheight() -= (mag.top() + mag.bottom());
s.rwidth() -= (mag.right() + mag.left());
}
return (s);
}
/**
* @brief 更新尺寸
*/
void SARibbonCategoryLayout::updateGeometryArr()
{
SARibbonCategory* category = ribbonCategory();
if (nullptr == category) {
return;
}
int categoryWidth = category->width();
QMargins mag = contentsMargins();
int height = category->height();
int y = 0;
if (!mag.isNull()) {
y = mag.top();
height -= (mag.top() + mag.bottom());
// categoryWidth不能把mag减去,减去后会导致categoryWidth不是实际的categoryWidth
// categoryWidth -= (mag.right() + mag.left());
}
// total 是总宽,不是x坐标系,x才是坐标系
int total = d_ptr->totalSizeHintWidth();
// 扩展的宽度
int expandWidth = 0;
// 如果total < categoryWidth,m_d->mXBase可以设置为0
// 判断是否超过总长度
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategoryLayout::updateGeometryArr"
<< "\n|-category name=" << category->categoryName() //
<< "\n|-category height=" << height //
<< "\n|-totalSizeHintWidth=" << total //
<< "\n|-y=" << y //
<< "\n|-expandWidth:" << expandWidth //
<< "\n|-mag=" << mag;
#endif
if (total > categoryWidth) {
// 超过总长度,需要显示滚动按钮
if (0 == d_ptr->mXBase) {
// 已经移动到最左,需要可以向右移动
d_ptr->mIsRightScrollBtnShow = true;
d_ptr->mIsLeftScrollBtnShow = false;
} else if (d_ptr->mXBase <= (categoryWidth - total)) {
// 已经移动到最右,需要可以向左移动
d_ptr->mIsRightScrollBtnShow = false;
d_ptr->mIsLeftScrollBtnShow = true;
} else {
// 移动到中间两边都可以动
d_ptr->mIsRightScrollBtnShow = true;
d_ptr->mIsLeftScrollBtnShow = true;
}
} else {
// 说明total 小于 categoryWidth
// 记录可以扩展的数量
int canExpandingCount = 0;
d_ptr->mIsRightScrollBtnShow = false;
d_ptr->mIsLeftScrollBtnShow = false;
// 这个是避免一开始totalWidth > categorySize.width(),通过滚动按钮调整了m_d->mBaseX
// 随之调整了窗体尺寸,调整后totalWidth < categorySize.width()导致category在原来位置
// 无法显示,必须这里把mBaseX设置为0
d_ptr->mXBase = 0;
//
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (SARibbonPannel* p = qobject_cast< SARibbonPannel* >(item->widget())) {
if (p->isExpanding()) {
// pannel可扩展
++canExpandingCount;
}
}
}
// 计算可扩展的宽度
if (canExpandingCount > 0) {
expandWidth = (categoryWidth - total) / canExpandingCount;
} else {
expandWidth = 0;
}
}
int x = d_ptr->mXBase;
if ((categoryAlignment() == SARibbonAlignment::AlignCenter) && (total < categoryWidth) && (0 == expandWidth)) {
// 如果是居中对齐,同时没有伸缩的pannel,同时总宽度没有超过category的宽度
x = (categoryWidth - total) / 2;
}
total = 0; // total重新计算
// 先按照sizeHint设置所有的尺寸
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (item->isEmpty()) {
// 如果是hide就直接跳过
if (item->separatorWidget) {
// pannel hide分割线也要hide
item->separatorWidget->hide();
}
item->mWillSetGeometry = QRect(0, 0, 0, 0);
item->mWillSetSeparatorGeometry = QRect(0, 0, 0, 0);
continue;
}
SARibbonPannel* p = item->toPannelWidget();
if (nullptr == p) {
qDebug() << "unknow widget in SARibbonCategoryLayout";
continue;
}
// p->layout()->update();
QSize pannelSize = p->sizeHint();
QSize SeparatorSize(0, 0);
if (item->separatorWidget) {
SeparatorSize = item->separatorWidget->sizeHint();
}
if (p->isExpanding()) {
// 可扩展,就把pannel扩展到最大
pannelSize.setWidth(pannelSize.width() + expandWidth);
}
int w = pannelSize.width();
item->mWillSetGeometry = QRect(x, y, w, height);
x += w;
total += w;
w = SeparatorSize.width();
item->mWillSetSeparatorGeometry = QRect(x, y, w, height);
x += w;
total += w;
}
d_ptr->mTotalWidth = total;
d_ptr->mSizeHint = QSize(d_ptr->mTotalWidth, height);
d_ptr->mMinSizeHint = QSize(categoryWidth, height);
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "SARibbonCategoryLayout updateGeometryArr,SizeHint=" << d_ptr->mSizeHint
<< ",Category name=" << category->categoryName();
#endif
}
/**
* @brief 执行layout调整
*/
void SARibbonCategoryLayout::doLayout()
{
if (d_ptr->mDirty) {
updateGeometryArr();
}
SARibbonCategory* category = ribbonCategory();
// 两个滚动按钮的位置永远不变
d_ptr->mLeftScrollBtn->setGeometry(0, 0, 12, category->height());
d_ptr->mRightScrollBtn->setGeometry(category->width() - 12, 0, 12, category->height());
QList< QWidget* > showWidgets, hideWidgets;
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
int debug_i__(0);
qDebug() << "SARibbonCategoryLayout::doLayout(),name=" << category->categoryName();
#endif
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (item->isEmpty()) {
hideWidgets << item->widget();
if (item->separatorWidget) {
hideWidgets << item->separatorWidget;
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "|-[" << debug_i__ << "]pannelName(" << item->toPannelWidget()->pannelName() << ",will hide";
++debug_i__;
#endif
} else {
//! 这里不能用item->setGeometry(item->mWillSetGeometry);这样会得到一个很奇怪的显示效果
//! 就是窗口的最左边不会移出去,而是把最右边压缩,
item->widget()->setGeometry(item->mWillSetGeometry);
// item->widget()->setFixedSize(item->mWillSetGeometry.size());
// item->widget()->move(item->mWillSetGeometry.topLeft());
// item->setGeometry(item->mWillSetGeometry);
showWidgets << item->widget();
if (item->separatorWidget) {
item->separatorWidget->setGeometry(item->mWillSetSeparatorGeometry);
showWidgets << item->separatorWidget;
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "|-[" << debug_i__ << "]pannelName(" << item->toPannelWidget()->pannelName()
<< "),willSetGeometry:" << item->mWillSetGeometry
<< ",WillSetSeparatorGeometry:" << item->mWillSetSeparatorGeometry;
++debug_i__;
#endif
}
}
d_ptr->mRightScrollBtn->setVisible(d_ptr->mIsRightScrollBtnShow);
d_ptr->mLeftScrollBtn->setVisible(d_ptr->mIsLeftScrollBtnShow);
if (d_ptr->mIsRightScrollBtnShow) {
d_ptr->mRightScrollBtn->raise();
}
if (d_ptr->mIsLeftScrollBtnShow) {
d_ptr->mLeftScrollBtn->raise();
}
// 不在上面那里进行show和hide因为这会触发SARibbonPannelLayout的重绘,导致循环绘制,非常影响效率
for (QWidget* w : qAsConst(showWidgets)) {
if (!w->isVisible()) {
w->show();
}
}
for (QWidget* w : qAsConst(hideWidgets)) {
if (w->isVisible()) {
w->hide();
}
}
}
/**
* @brief 返回所有pannels
* @return
*/
QList< SARibbonPannel* > SARibbonCategoryLayout::pannels() const
{
QList< SARibbonPannel* > res;
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
SARibbonPannel* p = item->toPannelWidget();
res.append(p);
}
return (res);
}
/**
* @brief 通过ObjectName查找pannel
* @param objname
* @return
*/
SARibbonPannel* SARibbonCategoryLayout::pannelByObjectName(const QString& objname) const
{
for (SARibbonCategoryLayoutItem* item : d_ptr->mItemList) {
if (SARibbonPannel* pannel = item->toPannelWidget()) {
if (pannel->objectName() == objname) {
return pannel;
}
}
}
return nullptr;
}
/**
* @brief 通过名字查找pannel
* @param title
* @return 如果有重名,只会返回第一个符合条件的
*/
SARibbonPannel* SARibbonCategoryLayout::pannelByName(const QString& pannelname) const
{
for (SARibbonCategoryLayoutItem* item : qAsConst(d_ptr->mItemList)) {
if (SARibbonPannel* pannel = item->toPannelWidget()) {
if (pannel->pannelName() == pannelname) {
return (pannel);
}
}
}
return (nullptr);
}
/**
* @brief 通过索引找到pannel,如果超过索引范围,会返回nullptr
* @param i
* @return
*/
SARibbonPannel* SARibbonCategoryLayout::pannelByIndex(int i) const
{
if (i >= 0 && i < d_ptr->mItemList.size()) {
return d_ptr->mItemList[ i ]->toPannelWidget();
}
return nullptr;
}
/**
* @brief 移动pannel
* @param from
* @param to
*/
void SARibbonCategoryLayout::movePannel(int from, int to)
{
d_ptr->mItemList.move(from, to);
doLayout();
}
/**
* @brief 返回pannel的个数
* @return
*/
int SARibbonCategoryLayout::pannelCount() const
{
return d_ptr->mItemList.size();
}
/**
* @brief 查找pannel对应的索引
* @param p
* @return 如果找不到,返回-1
*/
int SARibbonCategoryLayout::pannelIndex(SARibbonPannel* p) const
{
int c = pannelCount();
for (int i = 0; i < c; ++i) {
if (d_ptr->mItemList[ i ]->toPannelWidget() == p) {
return (i);
}
}
return (-1);
}
/**
* @brief 获取所有的pannel
* @return
*/
QList< SARibbonPannel* > SARibbonCategoryLayout::pannelList() const
{
QList< SARibbonPannel* > res;
for (SARibbonCategoryLayoutItem* i : qAsConst(d_ptr->mItemList)) {
if (SARibbonPannel* p = i->toPannelWidget()) {
res.append(p);
}
}
return (res);
}
/**
* @brief 执行滚动
* @param px
*/
void SARibbonCategoryLayout::scroll(int px)
{
QSize contentSize = categoryContentSize();
d_ptr->mXBase += px;
if (d_ptr->mXBase > 0) {
d_ptr->mXBase = 0;
} else if ((d_ptr->mXBase + d_ptr->mTotalWidth) < contentSize.width()) {
d_ptr->mXBase = contentSize.width() - d_ptr->mTotalWidth;
}
invalidate();
}
/**
* @brief 判断是否滚动过
* @return
*/
bool SARibbonCategoryLayout::isScrolled() const
{
return (d_ptr->mXBase != 0);
}
/**
* @brief 这个宽度是实际内容的宽度,有可能大于size.width,也有可能小于
* @return
*/
int SARibbonCategoryLayout::categoryTotalWidth() const
{
return d_ptr->mTotalWidth;
}
/**
@brief 设置Category的对齐方式
居中对齐会让pannel以居中进行对齐
@param al
*/
void SARibbonCategoryLayout::setCategoryAlignment(SARibbonAlignment al)
{
d_ptr->mCategoryAlignment = al;
}
/**
@brief Category的对齐方式
@return
*/
SARibbonAlignment SARibbonCategoryLayout::categoryAlignment() const
{
return d_ptr->mCategoryAlignment;
}
void SARibbonCategoryLayout::onLeftScrollButtonClicked()
{
SARibbonCategory* category = qobject_cast< SARibbonCategory* >(parentWidget());
int width = category->width();
// 求总宽
int totalWidth = d_ptr->mTotalWidth;
if (totalWidth > width) {
int tmp = d_ptr->mXBase + width;
if (tmp > 0) {
tmp = 0;
}
d_ptr->mXBase = tmp;
} else {
d_ptr->mXBase = 0;
}
invalidate();
}
void SARibbonCategoryLayout::onRightScrollButtonClicked()
{
SARibbonCategory* category = qobject_cast< SARibbonCategory* >(parentWidget());
int width = category->width();
// 求总宽
int totalWidth = d_ptr->mTotalWidth;
if (totalWidth > width) {
int tmp = d_ptr->mXBase - width;
if (tmp < (width - totalWidth)) {
tmp = width - totalWidth;
}
d_ptr->mXBase = tmp;
} else {
d_ptr->mXBase = 0;
}
invalidate();
}
void SARibbonCategoryLayout::setGeometry(const QRect& rect)
{
QRect old = geometry();
if (old == rect) {
return;
}
#if SARibbonCategoryLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "===========SARibbonCategoryLayout.setGeometry(" << rect << "(" << ribbonCategory()->categoryName()
<< ")=======";
#endif
QLayout::setGeometry(rect);
d_ptr->mDirty = false;
updateGeometryArr();
doLayout();
}
//=============================================================
// SARibbonCategoryLayoutItem
//=============================================================
SARibbonCategoryLayoutItem::SARibbonCategoryLayoutItem(SARibbonPannel* w) : QWidgetItem(w)
{
separatorWidget = nullptr;
}
SARibbonCategoryLayoutItem::~SARibbonCategoryLayoutItem()
{
}
SARibbonPannel* SARibbonCategoryLayoutItem::toPannelWidget()
{
return qobject_cast< SARibbonPannel* >(widget());
}
@@ -0,0 +1,95 @@
#ifndef SARIBBONCATEGORYLAYOUT_H
#define SARIBBONCATEGORYLAYOUT_H
#include "SARibbonGlobal.h"
#include <QLayout>
#include <QList>
#include <QMap>
#include "SARibbonCategory.h"
class SARibbonPannel;
class SARibbonCategoryLayoutItem;
class SARibbonSeparatorWidget;
/**
* @brief The SARibbonCategoryLayout class
*/
class SA_RIBBON_EXPORT SARibbonCategoryLayout : public QLayout
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonCategoryLayout)
public:
explicit SARibbonCategoryLayout(SARibbonCategory* parent);
~SARibbonCategoryLayout();
SARibbonCategory* ribbonCategory() const;
virtual void addItem(QLayoutItem* item) override;
virtual QLayoutItem* itemAt(int index) const override;
virtual QLayoutItem* takeAt(int index) override;
SARibbonCategoryLayoutItem* takePannelItem(int index);
SARibbonCategoryLayoutItem* takePannelItem(SARibbonPannel* pannel);
bool takePannel(SARibbonPannel* pannel);
virtual int count() const override;
void setGeometry(const QRect& rect) override;
QSize sizeHint() const override;
QSize minimumSize() const override;
Qt::Orientations expandingDirections() const override;
void invalidate() override;
// 追加一个pannel
void addPannel(SARibbonPannel* pannel);
// 插入一个pannel
void insertPannel(int index, SARibbonPannel* pannel);
//
QSize categoryContentSize() const;
// 更新尺寸
void updateGeometryArr();
// 执行位置调整
void doLayout();
// 返回所有pannels
QList< SARibbonPannel* > pannels() const;
// 通过obj name获取pannel
SARibbonPannel* pannelByObjectName(const QString& objname) const;
// 通过pannel name获取pannel
SARibbonPannel* pannelByName(const QString& pannelname) const;
// 通过索引找到pannel,如果超过索引范围,会返回nullptr
SARibbonPannel* pannelByIndex(int i) const;
// 移动pannel
void movePannel(int from, int to);
// pannel的数量
int pannelCount() const;
// pannel的索引
int pannelIndex(SARibbonPannel* p) const;
// 获取所有的pannel
QList< SARibbonPannel* > pannelList() const;
// 执行滚轮事件
void scroll(int px);
// 判断是否有滚动过
bool isScrolled() const;
// category的总宽度
int categoryTotalWidth() const;
// 设置Category的对齐方式
void setCategoryAlignment(SARibbonAlignment al);
SARibbonAlignment categoryAlignment() const;
private Q_SLOTS:
void onLeftScrollButtonClicked();
void onRightScrollButtonClicked();
};
/**
* @brief SARibbonCategoryLayoutItem,用于标识SARibbonCategoryLayout的item
*/
class SA_RIBBON_EXPORT SARibbonCategoryLayoutItem : public QWidgetItem
{
public:
explicit SARibbonCategoryLayoutItem(SARibbonPannel* w);
~SARibbonCategoryLayoutItem();
SARibbonSeparatorWidget* separatorWidget;
// 把内部的widget转换为pannel
SARibbonPannel* toPannelWidget();
QRect mWillSetGeometry; ///< pannel将要设置的Geometry
QRect mWillSetSeparatorGeometry; ///< pannel将要设置的Separator的Geometry
};
#endif // SARIBBONCATEGORYLAYOUT_H
@@ -0,0 +1,17 @@
#include "SARibbonCheckBox.h"
#include <QStyleOption>
SARibbonCheckBox::SARibbonCheckBox(QWidget *parent)
: QCheckBox(parent)
{
}
SARibbonCheckBox::SARibbonCheckBox(const QString &text, QWidget *parent)
: QCheckBox(text, parent)
{
}
SARibbonCheckBox::~SARibbonCheckBox()
{
}
@@ -0,0 +1,18 @@
#ifndef SARIBBONCHECKBOX_H
#define SARIBBONCHECKBOX_H
#include "SARibbonGlobal.h"
#include <QCheckBox>
/**
* @brief The SARibbonCheckBox class
*/
class SA_RIBBON_EXPORT SARibbonCheckBox : public QCheckBox
{
Q_OBJECT
public:
explicit SARibbonCheckBox(QWidget *parent = nullptr);
explicit SARibbonCheckBox(const QString &text, QWidget *parent = nullptr);
~SARibbonCheckBox();
};
#endif // SARIBBONCHECKBOX_H
@@ -0,0 +1,208 @@
#include "SARibbonColorToolButton.h"
#include <QStylePainter>
#include <QStyleOptionToolButton>
#include <QDebug>
#include "colorWidgets/SAColorMenu.h"
//===================================================
// SARibbonColorToolButton::PrivateData
//===================================================
const int c_ribbonbutton_color_height = 5; ///< 颜色块的高度
class SARibbonColorToolButton::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonColorToolButton)
public:
PrivateData(SARibbonColorToolButton* p);
QPixmap createIconPixmap(const QStyleOptionToolButton& opt, const QSize& iconsize) const;
QIcon createColorIcon(const QColor& c, const QSize& size) const;
public:
QColor mColor; ///< 记录颜色
SARibbonColorToolButton::ColorStyle mColorStyle { SARibbonColorToolButton::ColorUnderIcon }; ///< 颜色显示样式
QIcon mOldIcon; ///< 记录旧的icon
};
SARibbonColorToolButton::PrivateData::PrivateData(SARibbonColorToolButton* p) : q_ptr(p)
{
}
QPixmap SARibbonColorToolButton::PrivateData::createIconPixmap(const QStyleOptionToolButton& opt, const QSize& iconsize) const
{
if (opt.icon.isNull()) { // 没有有图标
return QPixmap();
}
// 有icon,在icon下方加入颜色
QIcon::State state = (opt.state & QStyle::State_On) ? QIcon::On : QIcon::Off;
QIcon::Mode mode;
if (!(opt.state & QStyle::State_Enabled)) {
mode = QIcon::Disabled;
} else if ((opt.state & QStyle::State_MouseOver) && (opt.state & QStyle::State_AutoRaise)) {
mode = QIcon::Active;
} else {
mode = QIcon::Normal;
}
QSize realIconSize = iconsize - QSize(0, c_ribbonbutton_color_height + 1);
QPixmap pixmap = opt.icon.pixmap(q_ptr->window()->windowHandle(), realIconSize, mode, state);
QPixmap res(pixmap.size() + QSize(4, c_ribbonbutton_color_height + 4)); // 宽度上,颜色块多出2px
res.fill(Qt::transparent);
QPainter painter(&res);
int xpixmap = (res.width() - pixmap.width()) / 2;
int ypixmap = (res.height() - c_ribbonbutton_color_height - 2 - pixmap.height()) / 2; // 这里要减去2而不是1,这样奇数偶数都不会影响
int w = pixmap.width();
int h = pixmap.height();
QRect rpixmap = QRect(xpixmap, ypixmap, w, h);
painter.drawPixmap(rpixmap, pixmap);
QRect colorRect = rpixmap.adjusted(0, h + 1, 0, c_ribbonbutton_color_height + 1);
if (mColor.isValid()) {
painter.fillRect(colorRect, mColor);
} else {
QPen pen(Qt::red, 1, Qt::SolidLine, Qt::RoundCap);
painter.setPen(pen);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.setRenderHint(QPainter::Antialiasing, true);
int ss = colorRect.width() / 3;
painter.drawLine(QPoint(colorRect.x() + ss, colorRect.bottom()), QPoint(colorRect.right() - ss, colorRect.top()));
pen.setColor(Qt::black);
painter.setPen(pen);
painter.drawRect(colorRect);
}
return res;
}
QIcon SARibbonColorToolButton::PrivateData::createColorIcon(const QColor& c, const QSize& size) const
{
QPixmap res(size);
res.fill(Qt::transparent);
QPainter painter(&res);
if (c.isValid()) {
painter.fillRect(QRect(1, 1, res.height() - 2, res.width() - 2), c);
} else {
QPen pen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap);
painter.setPen(pen);
painter.drawRect(QRect(1, 1, res.height() - 2, res.width() - 2));
pen.setColor(Qt::red);
painter.setPen(pen);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.drawLine(QPoint(1, size.height()), QPoint(size.width() - 1, 1));
}
return QIcon(res);
}
//===================================================
// SARibbonColorToolButton
//===================================================
SARibbonColorToolButton::SARibbonColorToolButton(QWidget* parent)
: SARibbonToolButton(parent), d_ptr(new SARibbonColorToolButton::PrivateData(this))
{
connect(this, &QAbstractButton::clicked, this, &SARibbonColorToolButton::onButtonClicked);
}
SARibbonColorToolButton::SARibbonColorToolButton(QAction* defaultAction, QWidget* parent)
: SARibbonToolButton(defaultAction, parent), d_ptr(new SARibbonColorToolButton::PrivateData(this))
{
connect(this, &QAbstractButton::clicked, this, &SARibbonColorToolButton::onButtonClicked);
}
SARibbonColorToolButton::~SARibbonColorToolButton()
{
}
/**
* @brief 获取按钮维护的颜色
* @return
*/
QColor SARibbonColorToolButton::color() const
{
return d_ptr->mColor;
}
/**
* @brief 设置颜色显示的样式
* @param s
*/
void SARibbonColorToolButton::setColorStyle(SARibbonColorToolButton::ColorStyle s)
{
if (d_ptr->mColorStyle == s) {
return;
}
d_ptr->mColorStyle = s;
if (ColorUnderIcon == s) {
setIcon(d_ptr->mOldIcon);
} else {
d_ptr->mOldIcon = icon();
setIcon(d_ptr->createColorIcon(d_ptr->mColor, QSize(32, 32)));
}
repaint();
}
/**
* @brief 颜色显示的样式
* @return
*/
SARibbonColorToolButton::ColorStyle SARibbonColorToolButton::colorStyle() const
{
return d_ptr->mColorStyle;
}
/**
* @brief 建立标准的颜色菜单
* @return
*/
SAColorMenu* SARibbonColorToolButton::setupStandardColorMenu()
{
setPopupMode(QToolButton::MenuButtonPopup);
SAColorMenu* m = new SAColorMenu(this);
m->enableNoneColorAction(true);
QAction* customColor = m->customColorAction();
if (customColor) {
customColor->setIcon(QIcon(":/image/resource/define-color.svg"));
}
connect(m, &SAColorMenu::selectedColor, this, &SARibbonColorToolButton::setColor);
setMenu(m);
updateRect();
return m;
}
/**
* @brief 设置按钮的颜色
*
* 此时会生成一个新的icon
* @note 会发射@sa colorChanged 信号
* @param c
*/
void SARibbonColorToolButton::setColor(const QColor& c)
{
if (d_ptr->mColor != c) {
d_ptr->mColor = c;
if (ColorFillToIcon == colorStyle()) {
setIcon(d_ptr->createColorIcon(c, QSize(32, 32)));
}
repaint();
Q_EMIT colorChanged(c);
}
}
void SARibbonColorToolButton::onButtonClicked(bool checked)
{
Q_EMIT colorClicked(d_ptr->mColor, checked);
}
/**
* @brief 重写paintIcon函数,把颜色加到icon下面
* @param p
* @param opt
* @param iconDrawRect
*/
void SARibbonColorToolButton::paintIcon(QPainter& p, const QStyleOptionToolButton& opt, const QRect& iconDrawRect)
{
if (ColorUnderIcon == colorStyle()) {
// 有icon
QPixmap pm = d_ptr->createIconPixmap(opt, iconDrawRect.size());
style()->drawItemPixmap(&p, iconDrawRect, Qt::AlignCenter, pm);
} else {
SARibbonToolButton::paintIcon(p, opt, iconDrawRect);
}
}
@@ -0,0 +1,55 @@
#ifndef SARIBBONCOLORTOOLBUTTON_H
#define SARIBBONCOLORTOOLBUTTON_H
#include "SARibbonToolButton.h"
#include "SARibbonGlobal.h"
class SAColorMenu;
/**
* @brief Refer to the color setting button in the office, which can display the color below the icon(参考office的颜色设置按钮,可以显示颜色在图标下方)
*/
class SA_RIBBON_EXPORT SARibbonColorToolButton : public SARibbonToolButton
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonColorToolButton)
public:
/**
* @brief 颜色样式
*/
enum ColorStyle
{
ColorUnderIcon, ///< 颜色在icon下方,这个要求必须设置icon
ColorFillToIcon ///< 颜色作为icon,这个模式下在setColor会自动生成一个颜色icon替换掉原来的icon,因此setIcon函数没有作用
};
public:
explicit SARibbonColorToolButton(QWidget* parent = nullptr);
explicit SARibbonColorToolButton(QAction* defaultAction, QWidget* parent = nullptr);
~SARibbonColorToolButton();
// 获取颜色
QColor color() const;
// 设置颜色显示方案
void setColorStyle(ColorStyle s);
ColorStyle colorStyle() const;
// 建立标准的颜色菜单
SAColorMenu* setupStandardColorMenu();
public Q_SLOTS:
// 设置颜色,会发射colorChanged信号
void setColor(const QColor& c);
private Q_SLOTS:
void onButtonClicked(bool checked = false);
Q_SIGNALS:
/**
* @brief 颜色被点击的响应
* @param color
*/
void colorClicked(const QColor& color, bool checked = false);
/**
* @brief 颜色改变信号
* @param color
*/
void colorChanged(const QColor& color);
protected:
void paintIcon(QPainter& p, const QStyleOptionToolButton& opt, const QRect& iconDrawRect);
};
#endif // SARIBBONCOLORTOOLBUTTON_H
@@ -0,0 +1,11 @@
#include "SARibbonComboBox.h"
SARibbonComboBox::SARibbonComboBox(QWidget *parent)
: QComboBox(parent)
{
}
SARibbonComboBox::~SARibbonComboBox()
{
}
@@ -0,0 +1,19 @@
#ifndef SARIBBONCOMBOBOX_H
#define SARIBBONCOMBOBOX_H
#include "SARibbonGlobal.h"
#include <QComboBox>
/**
* @brief QComboBox的Ribbon显示,可以显示QIcon和windowTitle在左侧
*/
class SA_RIBBON_EXPORT SARibbonComboBox : public QComboBox
{
Q_OBJECT
public:
explicit SARibbonComboBox(QWidget *parent = nullptr);
~SARibbonComboBox();
};
#endif // SARIBBONCOMBOBOX_H
@@ -0,0 +1,212 @@
#include "SARibbonContextCategory.h"
#include <QList>
#include <QVariant>
#include "SARibbonElementManager.h"
#include <QDebug>
/**
* @brief The SARibbonCategoryData class
*/
class SAPrivateRibbonCategoryData
{
public:
SARibbonCategory* categoryPage;
};
/**
* @brief The SARibbonContextCategoryPrivate class
*/
class SARibbonContextCategory::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonContextCategory)
public:
PrivateData(SARibbonContextCategory* p);
public:
QList< SAPrivateRibbonCategoryData > mCategoryDataList;
QVariant mContextID;
QColor mContextColor;
QString mContextTitle;
};
SARibbonContextCategory::PrivateData::PrivateData(SARibbonContextCategory* p) : q_ptr(p)
{
}
//===================================================
// SARibbonContextCategory
//===================================================
SARibbonContextCategory::SARibbonContextCategory(QWidget* parent)
: QObject(parent), d_ptr(new SARibbonContextCategory::PrivateData(this))
{
}
SARibbonContextCategory::~SARibbonContextCategory()
{
}
/**
* @brief 添加标签
* @param title 标签名字
*/
SARibbonCategory* SARibbonContextCategory::addCategoryPage(const QString& title)
{
SARibbonCategory* category = RibbonSubElementFactory->createRibbonCategory(parentWidget());
category->setCategoryName(title);
addCategoryPage(category);
return (category);
}
/**
* @brief 添加标签
* @param page
*/
void SARibbonContextCategory::addCategoryPage(SARibbonCategory* category)
{
if (isHaveCategory(category)) {
// cn:SARibbonContextCategory已经持有标签:%1,将跳过
qWarning() << tr("SARibbonContextCategory have category %1,will skip").arg(category->categoryName());
return;
}
category->markIsContextCategory(true);
connect(category, &SARibbonCategory::categoryNameChanged, this, &SARibbonContextCategory::onCategoryTitleChanged);
SAPrivateRibbonCategoryData catData;
catData.categoryPage = category;
d_ptr->mCategoryDataList.append(catData);
category->installEventFilter(this);
Q_EMIT categoryPageAdded(category);
}
int SARibbonContextCategory::categoryCount() const
{
return (d_ptr->mCategoryDataList.count());
}
void SARibbonContextCategory::setId(const QVariant& id)
{
d_ptr->mContextID = id;
}
QVariant SARibbonContextCategory::id() const
{
return (d_ptr->mContextID);
}
void SARibbonContextCategory::setContextColor(const QColor color)
{
d_ptr->mContextColor = color;
}
QColor SARibbonContextCategory::contextColor() const
{
return (d_ptr->mContextColor);
}
QWidget* SARibbonContextCategory::parentWidget() const
{
return (qobject_cast< QWidget* >(parent()));
}
bool SARibbonContextCategory::eventFilter(QObject* watched, QEvent* e)
{
if (nullptr == watched) {
return (false);
}
switch (e->type()) {
case QEvent::Close: {
SARibbonCategory* c = qobject_cast< SARibbonCategory* >(watched);
if (c) {
#ifdef SA_RIBBON_DEBUG_HELP_DRAW
qDebug() << " -----------> close event";
#endif
takeCategory(c);
}
} break;
default:
break;
}
return (false);
}
/**
* @brief 获取上下文标签的标题
* @return
*/
QString SARibbonContextCategory::contextTitle() const
{
return (d_ptr->mContextTitle);
}
/**
* @brief 设置上下文标签的标题,标题仅在office模式下显示 @ref SARibbonBar::RibbonStyle
* @param contextTitle
* @note 此函数会触发信号@sa contextTitleChanged
*/
void SARibbonContextCategory::setContextTitle(const QString& contextTitle)
{
d_ptr->mContextTitle = contextTitle;
Q_EMIT contextTitleChanged(contextTitle);
}
/**
* @brief 获取对应的tab页
* @param index
* @return
*/
SARibbonCategory* SARibbonContextCategory::categoryPage(int index)
{
return (d_ptr->mCategoryDataList[ index ].categoryPage);
}
/**
* @brief 获取所有的SARibbonCategory*
* @return
*/
QList< SARibbonCategory* > SARibbonContextCategory::categoryList() const
{
QList< SARibbonCategory* > res;
for (const SAPrivateRibbonCategoryData& c : qAsConst(d_ptr->mCategoryDataList)) {
res.append(c.categoryPage);
}
return (res);
}
/**
* @brief 移除这个category,这时SARibbonContextCategory不再管理这个category
* @param category
* @return 成功移除返回true
*/
bool SARibbonContextCategory::takeCategory(SARibbonCategory* category)
{
for (int i = 0; i < d_ptr->mCategoryDataList.size(); ++i) {
if (d_ptr->mCategoryDataList[ i ].categoryPage == category) {
d_ptr->mCategoryDataList.takeAt(i);
return (true);
}
}
return (false);
}
/**
* @brief 判断上下文是否维护了此SARibbonCategory
* @param category
* @return
*/
bool SARibbonContextCategory::isHaveCategory(SARibbonCategory* category) const
{
for (int i = 0; i < d_ptr->mCategoryDataList.size(); ++i) {
if (d_ptr->mCategoryDataList[ i ].categoryPage == category) {
return (true);
}
}
return (false);
}
void SARibbonContextCategory::onCategoryTitleChanged(const QString& title)
{
SARibbonCategory* category = qobject_cast< SARibbonCategory* >(sender());
if (category) {
Q_EMIT categoryTitleChanged(category, title);
}
}
@@ -0,0 +1,80 @@
#ifndef SARIBBONCONTEXTCATEGORY_H
#define SARIBBONCONTEXTCATEGORY_H
#include "SARibbonGlobal.h"
#include "SARibbonCategory.h"
#include <QWidget>
/**
* @brief 管理上下文标签的类
*/
class SA_RIBBON_EXPORT SARibbonContextCategory : public QObject
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonContextCategory)
public:
explicit SARibbonContextCategory(QWidget* parent = nullptr);
~SARibbonContextCategory();
//上下文目录添加下属目录
SARibbonCategory* addCategoryPage(const QString& title);
void addCategoryPage(SARibbonCategory* category);
//获取上下文标签下管理的标签个数
int categoryCount() const;
//设置id
void setId(const QVariant& id);
QVariant id() const;
//设置上下文颜色
void setContextColor(const QColor color);
QColor contextColor() const;
//上下文标签的内容
QString contextTitle() const;
void setContextTitle(const QString& contextTitle);
//获取对应的tab页
SARibbonCategory* categoryPage(int index);
//获取所有的SARibbonCategory*
QList< SARibbonCategory* > categoryList() const;
//移除category
bool takeCategory(SARibbonCategory* category);
//判断上下文是否维护了此SARibbonCategory
bool isHaveCategory(SARibbonCategory* category) const;
Q_SIGNALS:
/**
* @brief 标签加入上下文
* @param category
*/
void categoryPageAdded(SARibbonCategory* category);
/**
* @brief 标签从上下文移除
* @param category
*/
void categoryPageRemoved(SARibbonCategory* category);
/**
* @brief 上下文的标题发生改变
* @param title
*/
void contextTitleChanged(const QString& title);
/**
* @brief 上下文标签维护的标签页名字发生了改变
* @param category 发生改变的上下文标签页
* @param title 新名字
*/
void categoryTitleChanged(SARibbonCategory* category, const QString& title);
private Q_SLOTS:
void onCategoryTitleChanged(const QString& title);
protected:
//获取父级窗口
QWidget* parentWidget() const;
virtual bool eventFilter(QObject* watched, QEvent* e) override;
};
#endif // SARIBBONCONTEXTCATEGORY_H
@@ -0,0 +1,16 @@
#include "SARibbonControlButton.h"
SARibbonControlButton::SARibbonControlButton(QWidget* parent) : QToolButton(parent)
{
}
SARibbonControlButton::~SARibbonControlButton()
{
}
SARibbonControlToolButton::SARibbonControlToolButton(QWidget* parent) : QToolButton(parent)
{
}
SARibbonControlToolButton::~SARibbonControlToolButton()
{
}
@@ -0,0 +1,33 @@
#ifndef SARIBBONCONTROLBUTTON_H
#define SARIBBONCONTROLBUTTON_H
#include <QToolButton>
#include "SARibbonGlobal.h"
#include "SARibbonToolButton.h"
/**
* @brief 用于SARibbonGallery的control button
*
* 为了防止外部qss的影响,单独继承一个类
*/
class SA_RIBBON_EXPORT SARibbonControlButton : public QToolButton
{
Q_OBJECT
public:
explicit SARibbonControlButton(QWidget* parent = nullptr);
~SARibbonControlButton();
};
/**
* @brief 用于SARibbonButtonGroupWidget的control button
*
* 为了防止SARibbonToolButton qss的影响,单独继承一个类
*/
class SA_RIBBON_EXPORT SARibbonControlToolButton : public QToolButton
{
Q_OBJECT
public:
explicit SARibbonControlToolButton(QWidget* parent = nullptr);
~SARibbonControlToolButton();
};
#endif // SARIBBONPANNELTOOLBUTTON_H
@@ -0,0 +1,169 @@
#include "SARibbonCtrlContainer.h"
#include <QHBoxLayout>
#include <QPainter>
#include <QPaintEvent>
#include <QStylePainter>
#include <QDebug>
#include <QLabel>
#include <QApplication>
#include <QScreen>
/**
* @brief The SARibbonCtrlContainerPrivate class
*/
class SARibbonCtrlContainer::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonCtrlContainer)
public:
QWidget* mContainerWidget { nullptr };
QHBoxLayout* mHorizontalLayout { nullptr };
QLabel* mLabelPixmap { nullptr };
QLabel* mLabelText { nullptr };
QSize mIconSize { 24, 24 };
QIcon mIcon;
PrivateData(SARibbonCtrlContainer* p) : q_ptr(p)
{
mHorizontalLayout = new QHBoxLayout(p);
mHorizontalLayout->setSpacing(0);
mHorizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
mHorizontalLayout->setContentsMargins(0, 0, 0, 0);
mLabelPixmap = new QLabel(p);
mLabelPixmap->setObjectName(QString::fromUtf8("labelPixmap"));
mLabelPixmap->setAlignment(Qt::AlignCenter);
mHorizontalLayout->addWidget(mLabelPixmap);
mLabelText = new QLabel(p);
mLabelText->setObjectName(QString::fromUtf8("labelText"));
mHorizontalLayout->addWidget(mLabelText);
}
void setContainerWidget(QWidget* w)
{
if (mContainerWidget) {
// 原来有widget
QWidget* oldwidget = mContainerWidget;
takeContainerWidget(oldwidget);
oldwidget->deleteLater();
}
mContainerWidget = w;
if (!w) {
return;
}
if (w->parent() != q_ptr) {
w->setParent(q_ptr);
}
// ContainerWidget永远在最右边
mHorizontalLayout->insertWidget(mHorizontalLayout->count(), w);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
w->setSizePolicy(sizePolicy);
}
void takeContainerWidget(QWidget* w)
{
int i = mHorizontalLayout->indexOf(w);
if (i >= 0) {
QLayoutItem* item = mHorizontalLayout->takeAt(i);
delete item;
}
}
};
//===================================================
// SARibbonCtrlContainer
//===================================================
SARibbonCtrlContainer::SARibbonCtrlContainer(QWidget* parent)
: QWidget(parent), d_ptr(new SARibbonCtrlContainer::PrivateData(this))
{
}
SARibbonCtrlContainer::~SARibbonCtrlContainer()
{
}
QWidget* SARibbonCtrlContainer::containerWidget()
{
return (d_ptr->mContainerWidget);
}
const QWidget* SARibbonCtrlContainer::containerWidget() const
{
return (d_ptr->mContainerWidget);
}
void SARibbonCtrlContainer::setEnableShowIcon(bool b)
{
d_ptr->mLabelPixmap->setVisible(b);
}
void SARibbonCtrlContainer::setEnableShowTitle(bool b)
{
d_ptr->mLabelText->setVisible(b);
}
/**
* @brief 判断是否存在容器窗口
* @return
*/
bool SARibbonCtrlContainer::hasContainerWidget() const
{
return (d_ptr->mContainerWidget != nullptr);
}
/**
* @brief 设置图标
* @param i
*/
void SARibbonCtrlContainer::setIcon(const QIcon& i)
{
d_ptr->mIcon = i;
QPixmap pixmap = i.pixmap(d_ptr->mIconSize);
d_ptr->mLabelPixmap->setPixmap(pixmap);
}
void SARibbonCtrlContainer::setIcon(const QPixmap& pixmap)
{
d_ptr->mLabelPixmap->setPixmap(pixmap);
}
/**
* @brief 获取图标
* @return
*/
QIcon SARibbonCtrlContainer::icon() const
{
return d_ptr->mIcon;
}
/**
* @brief 设置文字
* @param t
*/
void SARibbonCtrlContainer::setText(const QString& t)
{
d_ptr->mLabelText->setText(t);
}
/**
* @brief 获取文字
* @return
*/
QString SARibbonCtrlContainer::text() const
{
return d_ptr->mLabelText->text();
}
void SARibbonCtrlContainer::setContainerWidget(QWidget* w)
{
d_ptr->setContainerWidget(w);
}
/**
* @brief 获取显示icon的窗口
* @return
*/
QWidget* SARibbonCtrlContainer::iconWidget() const
{
return d_ptr->mLabelPixmap;
}
@@ -0,0 +1,42 @@
#ifndef SARIBBONCTROLCONTAINER_H
#define SARIBBONCTROLCONTAINER_H
#include "SARibbonGlobal.h"
#include <QWidget>
#include <QScopedPointer>
class QStyleOption;
/**
* @brief 用于装载一个窗体的容器,这个窗体会布满整个SARibbonCtrlContainer,但会预留空间显示icon或者title
*
* ----------------------
* |icon|text| widget |
* ----------------------
*/
class SA_RIBBON_EXPORT SARibbonCtrlContainer : public QWidget
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonCtrlContainer)
public:
explicit SARibbonCtrlContainer(QWidget* parent = nullptr);
~SARibbonCtrlContainer();
void setEnableShowIcon(bool b);
void setEnableShowTitle(bool b);
// 判断是否存在容器窗口
bool hasContainerWidget() const;
// 图标
void setIcon(const QIcon& i);
void setIcon(const QPixmap& pixmap);
QIcon icon() const;
// 图标
void setText(const QString& t);
QString text() const;
// 设置窗口
void setContainerWidget(QWidget* w);
QWidget* containerWidget();
const QWidget* containerWidget() const;
// 获取线索icon的窗口
QWidget* iconWidget() const;
};
#endif // SARIBBONCTROLCONTAINER_H
@@ -0,0 +1,704 @@
#include "SARibbonCustomizeData.h"
#include "SARibbonBar.h"
#include <QDebug>
#include <QObject>
////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SARibbonCustomizeData
////////////////////////////////////////////////////////////////////////////////////////////////////////
SARibbonCustomizeData::SARibbonCustomizeData()
: indexValue(-1)
, actionRowProportionValue(SARibbonPannelItem::Large)
, mType(UnknowActionType)
, mActionsManagerPointer(nullptr)
{
}
SARibbonCustomizeData::SARibbonCustomizeData(ActionType type, SARibbonActionsManager* mgr)
: indexValue(-1), actionRowProportionValue(SARibbonPannelItem::Large), mType(type), mActionsManagerPointer(mgr)
{
}
/**
* @brief 获取CustomizeData的action type
* @return
*/
SARibbonCustomizeData::ActionType SARibbonCustomizeData::actionType() const
{
return (mType);
}
/**
* @brief 设置CustomizeData的action type
* @param a
*/
void SARibbonCustomizeData::setActionType(SARibbonCustomizeData::ActionType a)
{
mType = a;
}
/**
* @brief 判断是否是一个正常的CustomizeData
*
* 实际逻辑actionType() != UnknowActionType
* @return 有用的CustomizeData返回true
*/
bool SARibbonCustomizeData::isValid() const
{
return (actionType() != UnknowActionType);
}
/**
* @brief 应用SARibbonCustomizeData到SARibbonBar
* @param m
* @return 如果应用失败,返回false,如果actionType==UnknowActionType直接返回false
*/
bool SARibbonCustomizeData::apply(SARibbonBar* bar) const
{
if (nullptr == bar) {
return (false);
}
switch (actionType()) {
case UnknowActionType:
return (false);
case AddCategoryActionType: {
// 添加标签
SARibbonCategory* c = bar->insertCategoryPage(keyValue, indexValue);
if (nullptr == c) {
return (false);
}
c->setObjectName(categoryObjNameValue);
SARibbonCustomizeData::setCanCustomize(c);
return (true);
}
case AddPannelActionType: {
// 添加pannel
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* p = c->insertPannel(keyValue, indexValue);
p->setObjectName(pannelObjNameValue);
SARibbonCustomizeData::setCanCustomize(p);
return (true);
}
case AddActionActionType: {
if (nullptr == mActionsManagerPointer) {
return (false);
}
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* pannel = c->pannelByObjectName(pannelObjNameValue);
if (nullptr == pannel) {
return (false);
}
QAction* act = mActionsManagerPointer->action(keyValue);
if (nullptr == act) {
return (false);
}
SARibbonCustomizeData::setCanCustomize(act);
pannel->addAction(act, actionRowProportionValue);
return (true);
}
case RemoveCategoryActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
bar->removeCategory(c);
return (true);
}
case RemovePannelActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* pannel = c->pannelByObjectName(pannelObjNameValue);
if (nullptr == pannel) {
return (false);
}
c->removePannel(pannel);
return (true);
}
case RemoveActionActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* pannel = c->pannelByObjectName(pannelObjNameValue);
if (nullptr == pannel) {
return (false);
}
QAction* act = mActionsManagerPointer->action(keyValue);
if (nullptr == act) {
return (false);
}
pannel->removeAction(act);
return (true);
}
case ChangeCategoryOrderActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
int currentindex = bar->categoryIndex(c);
if (-1 == currentindex) {
return (false);
}
int toindex = currentindex + indexValue;
bar->moveCategory(currentindex, toindex);
return (true);
}
case ChangePannelOrderActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* pannel = c->pannelByObjectName(pannelObjNameValue);
if (nullptr == pannel) {
return (false);
}
int pannelIndex = c->pannelIndex(pannel);
if (-1 == pannelIndex) {
return (false);
}
c->movePannel(pannelIndex, pannelIndex + indexValue);
return (true);
}
case ChangeActionOrderActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* pannel = c->pannelByObjectName(pannelObjNameValue);
if (nullptr == pannel) {
return (false);
}
QAction* act = mActionsManagerPointer->action(keyValue);
if (nullptr == act) {
return (false);
}
int actindex = pannel->actionIndex(act);
if (actindex <= -1) {
return (false);
}
pannel->moveAction(actindex, actindex + indexValue);
return (true);
}
case RenameCategoryActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
c->setCategoryName(keyValue);
return (true);
}
case RenamePannelActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
SARibbonPannel* pannel = c->pannelByObjectName(pannelObjNameValue);
if (nullptr == pannel) {
return (false);
}
pannel->setPannelName(keyValue);
return (true);
}
case VisibleCategoryActionType: {
SARibbonCategory* c = bar->categoryByObjectName(categoryObjNameValue);
if (nullptr == c) {
return (false);
}
if (1 == indexValue) {
bar->showCategory(c);
} else {
bar->hideCategory(c);
}
return (true);
}
default:
break;
}
return (false);
}
/**
* @brief 获取actionmanager指针
* @return
*/
SARibbonActionsManager* SARibbonCustomizeData::actionManager()
{
return (mActionsManagerPointer);
}
/**
* @brief 设置ActionsManager
* @param mgr
*/
void SARibbonCustomizeData::setActionsManager(SARibbonActionsManager* mgr)
{
mActionsManagerPointer = mgr;
}
/**
* @brief 创建一个AddCategoryActionType的SARibbonCustomizeData
* @param title category 的标题
* @param index category要插入的位置
* @param objName category的object name
* @return 返回AddCategoryActionType的SARibbonCustomizeData
*/
SARibbonCustomizeData SARibbonCustomizeData::makeAddCategoryCustomizeData(const QString& title, int index, const QString& objName)
{
SARibbonCustomizeData d(AddCategoryActionType);
d.indexValue = index;
d.keyValue = title;
d.categoryObjNameValue = objName;
return (d);
}
/**
* @brief 创建一个AddPannelActionType的SARibbonCustomizeData
* @param title pannel的标题
* @param index pannel的index
* @param categoryobjName pannel的category的objectname
* @param objName pannel的objname
* @return 返回AddPannelActionType的SARibbonCustomizeData
*/
SARibbonCustomizeData SARibbonCustomizeData::makeAddPannelCustomizeData(const QString& title,
int index,
const QString& categoryobjName,
const QString& objName)
{
SARibbonCustomizeData d(AddPannelActionType);
d.indexValue = index;
d.keyValue = title;
d.pannelObjNameValue = objName;
d.categoryObjNameValue = categoryobjName;
return (d);
}
/**
* @brief 添加action
* @param key action的索引
* @param mgr action管理器
* @param rp 定义action的占位情况
* @param categoryObjName action添加到的category的objname
* @param pannelObjName action添加到的category下的pannel的objname
* @param index action添加到的pannel的索引
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeAddActionCustomizeData(const QString& key,
SARibbonActionsManager* mgr,
SARibbonPannelItem::RowProportion rp,
const QString& categoryObjName,
const QString& pannelObjName)
{
SARibbonCustomizeData d(AddActionActionType, mgr);
d.keyValue = key;
d.categoryObjNameValue = categoryObjName;
d.pannelObjNameValue = pannelObjName;
d.actionRowProportionValue = rp;
return (d);
}
/**
* @brief 创建一个RenameCategoryActionType的SARibbonCustomizeData
* @param newname 新名字
* @param index category的索引
* @return 返回RenameCategoryActionType的SARibbonCustomizeData
*/
SARibbonCustomizeData SARibbonCustomizeData::makeRenameCategoryCustomizeData(const QString& newname,
const QString& categoryobjName)
{
SARibbonCustomizeData d(RenameCategoryActionType);
if (categoryobjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize rename category,"
"but get an empty category object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.keyValue = newname;
d.categoryObjNameValue = categoryobjName;
return (d);
}
/**
* @brief 创建一个RenamePannelActionType的SARibbonCustomizeData
* @param newname pannel的名字
* @param indexValue pannel的索引
* @param categoryobjName pannel对应的category的object name
* @return 返回RenamePannelActionType的SARibbonCustomizeData
*/
SARibbonCustomizeData SARibbonCustomizeData::makeRenamePannelCustomizeData(const QString& newname,
const QString& categoryobjName,
const QString& pannelObjName)
{
SARibbonCustomizeData d(RenamePannelActionType);
if (pannelObjName.isEmpty() || categoryobjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize rename pannel,"
"but get an empty category/pannel object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.keyValue = newname;
d.pannelObjNameValue = pannelObjName;
d.categoryObjNameValue = categoryobjName;
return (d);
}
/**
* @brief 对应ChangeCategoryOrderActionType
* @param categoryobjName 需要移动的categoryobjName
* @param moveindex 移动位置,-1代表向上(向左)移动一个位置,1带表向下(向右)移动一个位置
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeChangeCategoryOrderCustomizeData(const QString& categoryobjName,
int moveindex)
{
SARibbonCustomizeData d(ChangeCategoryOrderActionType);
if (categoryobjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize change category order,"
"but get an empty category object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
d.indexValue = moveindex;
return (d);
}
/**
* @brief 对应ChangePannelOrderActionType
* @param categoryobjName 需要移动的pannel对应的categoryobjName
* @param pannelObjName 需要移动的pannelObjName
* @param moveindex 移动位置,-1代表向上(向左)移动一个位置,1带表向下(向右)移动一个位置
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeChangePannelOrderCustomizeData(const QString& categoryobjName,
const QString& pannelObjName,
int moveindex)
{
SARibbonCustomizeData d(ChangePannelOrderActionType);
if (categoryobjName.isEmpty() || pannelObjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize change pannel order,"
"but get an empty category/pannel object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
d.pannelObjNameValue = pannelObjName;
d.indexValue = moveindex;
return (d);
}
/**
* @brief 对应ChangeActionOrderActionType
* @param categoryobjName 需要移动的pannel对应的categoryobjName
* @param pannelObjName 需要移动的pannelObjName
* @param key SARibbonActionsManager管理的key名
* @param mgr SARibbonActionsManager指针
* @param moveindex 移动位置,-1代表向上(向左)移动一个位置,1带表向下(向右)移动一个位置
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeChangeActionOrderCustomizeData(const QString& categoryobjName,
const QString& pannelObjName,
const QString& key,
SARibbonActionsManager* mgr,
int moveindex)
{
SARibbonCustomizeData d(ChangeActionOrderActionType, mgr);
if (categoryobjName.isEmpty() || pannelObjName.isEmpty() || key.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize change action order,"
"but get an empty category/pannel/action object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
d.pannelObjNameValue = pannelObjName;
d.keyValue = key;
d.indexValue = moveindex;
return (d);
}
/**
* @brief 对应RemoveCategoryActionType
* @param categoryobjName 需要移除的objname
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeRemoveCategoryCustomizeData(const QString& categoryobjName)
{
SARibbonCustomizeData d(RemoveCategoryActionType);
if (categoryobjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize remove category,"
"but get an empty category object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
return (d);
}
/**
* @brief 对应RemovePannelActionType
* @param categoryobjName pannel对应的category的obj name
* @param pannelObjName pannel对应的 obj name
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeRemovePannelCustomizeData(const QString& categoryobjName,
const QString& pannelObjName)
{
SARibbonCustomizeData d(RemovePannelActionType);
if (categoryobjName.isEmpty() || pannelObjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize remove pannel,"
"but get an empty category/pannel object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
d.pannelObjNameValue = pannelObjName;
return (d);
}
/**
* @brief 对应RemoveActionActionType
* @param categoryobjName pannel对应的category的obj name
* @param pannelObjName pannel对应的 obj name
* @param key SARibbonActionsManager管理的key名
* @param mgr SARibbonActionsManager指针
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeRemoveActionCustomizeData(const QString& categoryobjName,
const QString& pannelObjName,
const QString& key,
SARibbonActionsManager* mgr)
{
SARibbonCustomizeData d(RemoveActionActionType, mgr);
if (categoryobjName.isEmpty() || pannelObjName.isEmpty() || key.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize remove action,"
"but get an empty category/pannel/action object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
d.pannelObjNameValue = pannelObjName;
d.keyValue = key;
return (d);
}
/**
* @brief SARibbonCustomizeData::makeVisibleCategoryCustomizeData
* @param categoryobjName
* @param isShow
* @return
*/
SARibbonCustomizeData SARibbonCustomizeData::makeVisibleCategoryCustomizeData(const QString& categoryobjName, bool isShow)
{
SARibbonCustomizeData d(VisibleCategoryActionType);
if (categoryobjName.isEmpty()) {
qDebug() << QObject::tr("SARibbon Warning !!! customize visible category,"
"but get an empty category object name,"
"if you want to customize SARibbon,"
"please make sure every element has been set object name.");
}
d.categoryObjNameValue = categoryobjName;
d.indexValue = isShow ? 1 : 0;
return (d);
}
/**
* @brief 判断外置属性,是否允许自定义
* @param obj
* @return
*/
bool SARibbonCustomizeData::isCanCustomize(QObject* obj)
{
QVariant v = obj->property(SA_RIBBON_BAR_PROP_CAN_CUSTOMIZE);
if (v.isValid()) {
return (v.toBool());
}
return (false);
}
/**
* @brief 设置外置属性允许自定义
* @param obj
* @param canbe
*/
void SARibbonCustomizeData::setCanCustomize(QObject* obj, bool canbe)
{
obj->setProperty(SA_RIBBON_BAR_PROP_CAN_CUSTOMIZE, canbe);
}
QList< SARibbonCustomizeData > remove_indexs(const QList< SARibbonCustomizeData >& csd, const QList< int >& willremoveIndex);
QList< SARibbonCustomizeData > remove_indexs(const QList< SARibbonCustomizeData >& csd, const QList< int >& willremoveIndex)
{
QList< SARibbonCustomizeData > res;
for (int i = 0; i < csd.size(); ++i) {
if (!willremoveIndex.contains(i)) {
res << csd[ i ];
}
}
return (res);
}
/**
* @brief 对QList<SARibbonCustomizeData>进行简化操作
*
* 此函数会执行如下操作:
* 1、针对同一个category/pannel连续出现的添加和删除操作进行移除(前一步添加,后一步删除)
*
* 2、针对VisibleCategoryActionType,对于连续出现的操作只保留最后一步
*
* 3、针对RenameCategoryActionType和RenamePannelActionType操作,只保留最后一个
*
* 4、针对连续的ChangeCategoryOrderActionTypeChangePannelOrderActionTypeChangeActionOrderActionType进行合并为一个动作,
* 如果合并后原地不动,则删除
*
* @param csd
* @return 返回简化的QList<SARibbonCustomizeData>
*/
QList< SARibbonCustomizeData > SARibbonCustomizeData::simplify(const QList< SARibbonCustomizeData >& csd)
{
int size = csd.size();
if (size <= 1) {
return (csd);
}
QList< SARibbonCustomizeData > res;
QList< int > willremoveIndex; // 记录要删除的index
//! 首先针对连续出现的添加和删除操作进行优化
for (int i = 1; i < size; ++i) {
if ((csd[ i - 1 ].actionType() == AddCategoryActionType) && (csd[ i ].actionType() == RemoveCategoryActionType)) {
if (csd[ i - 1 ].categoryObjNameValue == csd[ i ].categoryObjNameValue) {
willremoveIndex << i - 1 << i;
}
} else if ((csd[ i - 1 ].actionType() == AddPannelActionType) && (csd[ i ].actionType() == RemovePannelActionType)) {
if ((csd[ i - 1 ].pannelObjNameValue == csd[ i ].pannelObjNameValue)
&& (csd[ i - 1 ].categoryObjNameValue == csd[ i ].categoryObjNameValue)) {
willremoveIndex << i - 1 << i;
}
} else if ((csd[ i - 1 ].actionType() == AddActionActionType) && (csd[ i ].actionType() == RemoveActionActionType)) {
if ((csd[ i - 1 ].keyValue == csd[ i ].keyValue)
&& (csd[ i - 1 ].pannelObjNameValue == csd[ i ].pannelObjNameValue)
&& (csd[ i - 1 ].categoryObjNameValue == csd[ i ].categoryObjNameValue)) {
willremoveIndex << i - 1 << i;
}
}
}
res = remove_indexs(csd, willremoveIndex);
willremoveIndex.clear();
//! 筛选VisibleCategoryActionType,对于连续出现的操作只保留最后一步
size = res.size();
for (int i = 1; i < size; ++i) {
if ((res[ i - 1 ].actionType() == VisibleCategoryActionType)
&& (res[ i ].actionType() == VisibleCategoryActionType)) {
if (res[ i - 1 ].categoryObjNameValue == res[ i ].categoryObjNameValue) {
// 要保证操作的是同一个内容
willremoveIndex << i - 1; // 删除前一个只保留最后一个
}
}
}
res = remove_indexs(res, willremoveIndex);
willremoveIndex.clear();
//! 针对RenameCategoryActionType和RenamePannelActionType操作,只需保留最后一个
size = res.size();
for (int i = 0; i < size; ++i) {
if (res[ i ].actionType() == RenameCategoryActionType) {
// 向后查询,如果查询到有同一个Category改名,把这个索引加入删除队列
for (int j = i + 1; j < size; ++j) {
if ((res[ j ].actionType() == RenameCategoryActionType)
&& (res[ i ].categoryObjNameValue == res[ j ].categoryObjNameValue)) {
willremoveIndex << i;
}
}
} else if (res[ i ].actionType() == RenamePannelActionType) {
// 向后查询,如果查询到有同一个pannel改名,把这个索引加入删除队列
for (int j = i + 1; j < size; ++j) {
if ((res[ j ].actionType() == RenamePannelActionType)
&& (res[ i ].pannelObjNameValue == res[ j ].pannelObjNameValue)
&& (res[ i ].categoryObjNameValue == res[ j ].categoryObjNameValue)) {
willremoveIndex << i;
}
}
}
}
res = remove_indexs(res, willremoveIndex);
willremoveIndex.clear();
//! 针对连续的ChangeCategoryOrderActionTypeChangePannelOrderActionTypeChangeActionOrderActionType进行合并
size = res.size();
for (int i = 1; i < size; ++i) {
if ((res[ i - 1 ].actionType() == ChangeCategoryOrderActionType)
&& (res[ i ].actionType() == ChangeCategoryOrderActionType)
&& (res[ i - 1 ].categoryObjNameValue == res[ i ].categoryObjNameValue)) {
// 说明连续两个顺序调整,把前一个indexvalue和后一个indexvalue相加,前一个删除
res[ i ].indexValue += res[ i - 1 ].indexValue;
willremoveIndex << i - 1;
} else if ((res[ i - 1 ].actionType() == ChangePannelOrderActionType)
&& (res[ i ].actionType() == ChangePannelOrderActionType)
&& (res[ i - 1 ].pannelObjNameValue == res[ i ].pannelObjNameValue)
&& (res[ i - 1 ].categoryObjNameValue == res[ i ].categoryObjNameValue)) {
// 说明连续两个顺序调整,把前一个indexvalue和后一个indexvalue相加,前一个删除
res[ i ].indexValue += res[ i - 1 ].indexValue;
willremoveIndex << i - 1;
} else if ((res[ i - 1 ].actionType() == ChangeActionOrderActionType)
&& (res[ i ].actionType() == ChangeActionOrderActionType) && (res[ i - 1 ].keyValue == res[ i ].keyValue)
&& (res[ i - 1 ].pannelObjNameValue == res[ i ].pannelObjNameValue)
&& (res[ i - 1 ].categoryObjNameValue == res[ i ].categoryObjNameValue)) {
// 说明连续两个顺序调整,把前一个indexvalue和后一个indexvalue相加,前一个删除
res[ i ].indexValue += res[ i - 1 ].indexValue;
willremoveIndex << i - 1;
}
}
res = remove_indexs(res, willremoveIndex);
willremoveIndex.clear();
//! 上一步操作可能会产生indexvalue为0的情况,此操作把indexvalue为0的删除
size = res.size();
for (int i = 0; i < size; ++i) {
if ((res[ i ].actionType() == ChangeCategoryOrderActionType) || (res[ i ].actionType() == ChangePannelOrderActionType)
|| (res[ i ].actionType() == ChangeActionOrderActionType)) {
if (0 == res[ i ].indexValue) {
willremoveIndex << i;
}
}
}
res = remove_indexs(res, willremoveIndex);
willremoveIndex.clear();
return (res);
}
@@ -0,0 +1,152 @@
#ifndef SARIBBONCUSTOMIZEDATA_H
#define SARIBBONCUSTOMIZEDATA_H
#include "SARibbonGlobal.h"
#include "SARibbonActionsManager.h"
#include "SARibbonPannel.h"
#include <QList>
class SARibbonBar;
class SARibbonMainWindow;
/**
* @brief 记录所有自定义操作的数据类
* @note 此数据依赖于@ref SARibbonActionsManager 要在SARibbonActionsManager之后使用此类
*/
class SA_RIBBON_EXPORT SARibbonCustomizeData
{
public:
enum ActionType
{
UnknowActionType = 0, ///< 未知操作
AddCategoryActionType, ///< 添加category操作(1)
AddPannelActionType, ///< 添加pannel操作(2)
AddActionActionType, ///< 添加action操作(3)
RemoveCategoryActionType, ///< 删除category操作(4)
RemovePannelActionType, ///< 删除pannel操作(5)
RemoveActionActionType, ///< 删除action操作(6)
ChangeCategoryOrderActionType, ///< 改变category顺序的操作(7)
ChangePannelOrderActionType, ///< 改变pannel顺序的操作(8)
ChangeActionOrderActionType, ///< 改变action顺序的操作(9)
RenameCategoryActionType, ///< 对category更名操作(10)
RenamePannelActionType, ///< 对Pannel更名操作(11)
VisibleCategoryActionType ///< 对category执行隐藏/显示操作(12)
};
SARibbonCustomizeData();
SARibbonCustomizeData(ActionType type, SARibbonActionsManager* mgr = nullptr);
// 获取CustomizeData的action type
ActionType actionType() const;
// 设置CustomizeData的action type
void setActionType(ActionType a);
// 判断是否是一个正常的CustomizeData
bool isValid() const;
// 应用SARibbonCustomizeData
bool apply(SARibbonBar* bar) const;
// 获取actionmanager指针
SARibbonActionsManager* actionManager();
// 设置ActionsManager
void setActionsManager(SARibbonActionsManager* mgr);
// 对应AddCategoryActionType
static SARibbonCustomizeData makeAddCategoryCustomizeData(const QString& title, int index, const QString& objName);
// 对应AddPannelActionType
static SARibbonCustomizeData makeAddPannelCustomizeData(const QString& title,
int index,
const QString& categoryobjName,
const QString& objName);
// 对应AddActionActionType
static SARibbonCustomizeData makeAddActionCustomizeData(const QString& key,
SARibbonActionsManager* mgr,
SARibbonPannelItem::RowProportion rp,
const QString& categoryObjName,
const QString& pannelObjName);
// 对应RenameCategoryActionType
static SARibbonCustomizeData makeRenameCategoryCustomizeData(const QString& newname, const QString& categoryobjName);
// 对应RenamePannelActionType
static SARibbonCustomizeData makeRenamePannelCustomizeData(const QString& newname,
const QString& categoryobjName,
const QString& pannelObjName);
// 对应RemoveCategoryActionType
static SARibbonCustomizeData makeRemoveCategoryCustomizeData(const QString& categoryobjName);
// 对应ChangeCategoryOrderActionType
static SARibbonCustomizeData makeChangeCategoryOrderCustomizeData(const QString& categoryobjName, int moveindex);
// 对应ChangePannelOrderActionType
static SARibbonCustomizeData makeChangePannelOrderCustomizeData(const QString& categoryobjName,
const QString& pannelObjName,
int moveindex);
// 对应ChangeActionOrderActionType
static SARibbonCustomizeData makeChangeActionOrderCustomizeData(const QString& categoryobjName,
const QString& pannelObjName,
const QString& key,
SARibbonActionsManager* mgr,
int moveindex);
// 对应RemovePannelActionType
static SARibbonCustomizeData makeRemovePannelCustomizeData(const QString& categoryobjName, const QString& pannelObjName);
// 对应RemoveActionActionType
static SARibbonCustomizeData makeRemoveActionCustomizeData(const QString& categoryobjName,
const QString& pannelObjName,
const QString& key,
SARibbonActionsManager* mgr);
// 对应VisibleCategoryActionType
static SARibbonCustomizeData makeVisibleCategoryCustomizeData(const QString& categoryobjName, bool isShow);
// 判断是否可以自定义,如果某个action不想被编辑,可以通过此函数设置
static bool isCanCustomize(QObject* obj);
static void setCanCustomize(QObject* obj, bool canbe = true);
// 对QList<SARibbonCustomizeData>进行简化
static QList< SARibbonCustomizeData > simplify(const QList< SARibbonCustomizeData >& csd);
public:
/**
* @brief 记录顺序的参数
*
* 在actionType==AddCategoryActionType时,此参数记录Category的insert位置,
* 在actionType==AddPannelActionType时,此参数记录pannel的insert位置,
* 在actionType==AddActionActionType时,此参数记录pannel的insert位置
*/
int indexValue;
/**
* @brief 记录标题、索引等参数
*
* 在actionType==AddCategoryActionType时,key为category标题,
* 在actionType==AddPannelActionType时,key为pannel标题,
* 在actionType==AddActionActionType时,key为action的查询依据,基于SARibbonActionsManager::action查询
*/
QString keyValue;
/**
* @brief 记录categoryObjName,用于定位Category
*/
QString categoryObjNameValue;
/**
* @brief 记录pannelObjNamesaribbon的Customize索引大部分基于objname
*/
QString pannelObjNameValue;
SARibbonPannelItem::RowProportion actionRowProportionValue; ///< 行的占比,ribbon中有largemedia和small三种占比,见@ref RowProportion
private:
ActionType mType; ///< 标记这个data是category还是pannel亦或是action
SARibbonActionsManager* mActionsManagerPointer;
};
Q_DECLARE_METATYPE(SARibbonCustomizeData)
typedef QList< SARibbonCustomizeData > SARibbonCustomizeDataList;
#endif // SARIBBONCUSTOMIZEDATA_H
@@ -0,0 +1,187 @@
#include "SARibbonCustomizeDialog.h"
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QSpacerItem>
/**
* @brief The SARibbonCustomizeDialogUi class
*/
class SARibbonCustomizeDialogUi {
public:
SARibbonCustomizeWidget *customWidget;
QVBoxLayout *verticalLayoutMain;
QHBoxLayout *horizontalLayoutButtonGroup;
QPushButton *pushButtonCancel;
QPushButton *pushButtonOk;
QSpacerItem *spacerItemleft;
void setupUi(SARibbonMainWindow *ribbonWindow, QWidget *customizeDialog)
{
if (customizeDialog->objectName().isEmpty()) {
customizeDialog->setObjectName(QStringLiteral("SARibbonCustomizeDialog"));
}
customizeDialog->resize(800, 600);
verticalLayoutMain = new QVBoxLayout(customizeDialog);
verticalLayoutMain->setObjectName(QStringLiteral("verticalLayoutMain"));
customWidget = new SARibbonCustomizeWidget(ribbonWindow, customizeDialog);
customWidget->setObjectName(QStringLiteral("customWidget"));
verticalLayoutMain->addWidget(customWidget);
horizontalLayoutButtonGroup = new QHBoxLayout();
horizontalLayoutButtonGroup->setObjectName(QStringLiteral("horizontalLayoutButtonGroup"));
spacerItemleft = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayoutButtonGroup->addItem(spacerItemleft);
pushButtonCancel = new QPushButton(customizeDialog);
pushButtonCancel->setObjectName(QStringLiteral("pushButtonCancel"));
horizontalLayoutButtonGroup->addWidget(pushButtonCancel);
pushButtonOk = new QPushButton(customizeDialog);
pushButtonOk->setObjectName(QStringLiteral("pushButtonCancel"));
horizontalLayoutButtonGroup->addWidget(pushButtonOk);
verticalLayoutMain->addItem(horizontalLayoutButtonGroup);
retranslateUi(customizeDialog);
}
void retranslateUi(QWidget *customizeDialog)
{
customizeDialog->setWindowTitle(QApplication::translate("SARibbonCustomizeDialog", "Customize Dialog", Q_NULLPTR));
pushButtonCancel->setText(QApplication::translate("SARibbonCustomizeDialog", "Cancel", Q_NULLPTR));
pushButtonOk->setText(QApplication::translate("SARibbonCustomizeDialog", "OK", Q_NULLPTR));
}
};
////////////////////////////////////////////////////////////////////
SARibbonCustomizeDialog::SARibbonCustomizeDialog(SARibbonMainWindow *ribbonWindow, QWidget *p, Qt::WindowFlags f)
: QDialog(p, f)
, ui(new SARibbonCustomizeDialogUi)
{
ui->setupUi(ribbonWindow, this);
initConnection();
}
/**
* @brief 设置action管理器
*
* 等同@ref SARibbonCustomizeWidget::setupActionsManager
* @param mgr
*/
SARibbonCustomizeDialog::~SARibbonCustomizeDialog()
{
}
void SARibbonCustomizeDialog::setupActionsManager(SARibbonActionsManager *mgr)
{
ui->customWidget->setupActionsManager(mgr);
}
/**
* @brief //判断用户是否有要存储的内容,对应save动作
* @return
*/
bool SARibbonCustomizeDialog::isApplied() const
{
return ui->customWidget->isApplied();
}
/**
* @brief 判断用户是否有改动内容,对应apply动作
* @return
*/
bool SARibbonCustomizeDialog::isCached() const
{
return ui->customWidget->isCached();
}
void SARibbonCustomizeDialog::initConnection()
{
connect(ui->pushButtonOk, &QPushButton::clicked, this, &QDialog::accept);
connect(ui->pushButtonCancel, &QPushButton::clicked, this, &QDialog::reject);
}
/**
* @brief 等同SARibbonCustomizeWidget::applys
*
* @ref SARibbonCustomizeWidget::applys
* @return
*/
bool SARibbonCustomizeDialog::applys()
{
return ui->customWidget->applys();
}
/**
* @brief 清除所有动作
*
* @ref SARibbonCustomizeWidget::clear
*/
void SARibbonCustomizeDialog::clear()
{
ui->customWidget->clear();
}
/**
* @brief 转换为xml
*
* @ref SARibbonCustomizeWidget::toXml
* @param xml
* @return
*/
bool SARibbonCustomizeDialog::toXml(QXmlStreamWriter *xml) const
{
return (ui->customWidget->toXml(xml));
}
/**
* @brief 等同SARibbonCustomizeWidget::toXml
* @ref SARibbonCustomizeWidget::toXml
* @param xmlpath
* @return
*/
bool SARibbonCustomizeDialog::toXml(const QString& xmlpath) const
{
return (ui->customWidget->toXml(xmlpath));
}
/**
* @brief 等同SARibbonCustomizeWidget::fromXml
* @param xml
*/
void SARibbonCustomizeDialog::fromXml(QXmlStreamReader *xml)
{
ui->customWidget->fromXml(xml);
}
/**
* @brief 等同SARibbonCustomizeWidget::fromXml
* @param xmlpath
*/
void SARibbonCustomizeDialog::fromXml(const QString& xmlpath)
{
ui->customWidget->fromXml(xmlpath);
}
/**
* @brief 返回SARibbonCustomizeWidget窗口指针
*
* 通过SARibbonCustomizeWidget窗口可以操作更多的内容
*
* @return SARibbonCustomizeWidget指针,参考@ref SARibbonCustomizeWidget
*/
SARibbonCustomizeWidget *SARibbonCustomizeDialog::customizeWidget() const
{
return (ui->customWidget);
}
@@ -0,0 +1,58 @@
#ifndef SARIBBONCUSTOMIZEDIALOG_H
#define SARIBBONCUSTOMIZEDIALOG_H
#include "SARibbonGlobal.h"
#include <QDialog>
#include "SARibbonCustomizeWidget.h"
class SARibbonActionsManager;
class SARibbonCustomizeDialogUi;
class QXmlStreamWriter;
/**
* @brief SARibbonCustomizeWidget的对话框封装
*
* 此功能依赖于@ref SARibbonActionsManager SARibbonActionsManager建议作为mianwindow的成员变量,
* SARibbonActionsManager可以快速绑定所有QAction,详细见SARibbonActionsManager的说明
*
* @note SARibbon的自定义是基于步骤的,如果在窗口生成前调用了@ref sa_apply_customize_from_xml_file 类似函数
* 那么在对话框生成前为了保证同步需要调用@ref SARibbonCustomizeDialog::fromXml 同步配置文件,这样再次修改后的配置文件就一致
*/
class SA_RIBBON_EXPORT SARibbonCustomizeDialog : public QDialog
{
Q_OBJECT
public:
explicit SARibbonCustomizeDialog(SARibbonMainWindow *ribbonWindow, QWidget *p = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~SARibbonCustomizeDialog();
//设置action管理器
void setupActionsManager(SARibbonActionsManager *mgr);
//判断用户是否有要存储的内容,对应save动作
bool isApplied() const;
//判断用户是否有改动内容,对应apply动作
bool isCached() const;
//应用所有的设定
bool applys();
//清除所有动作
void clear();
//转换为xml
bool toXml(QXmlStreamWriter *xml) const;
bool toXml(const QString& xmlpath) const;
//从xml中加载QList<SARibbonCustomizeData>,对于基于配置文件的设置,对话框显示前建议调用此函数,保证叠加设置的正确记录
void fromXml(QXmlStreamReader *xml);
void fromXml(const QString& xmlpath);
//返回SARibbonCustomizeWidget窗口指针
SARibbonCustomizeWidget *customizeWidget() const;
private:
void initConnection();
SARibbonCustomizeDialogUi *ui;
};
#endif // SARIBBONCUSTOMIZEDIALOG_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,209 @@
#ifndef SARIBBONCUSTOMIZEWIDGET_H
#define SARIBBONCUSTOMIZEWIDGET_H
#include "SARibbonGlobal.h"
#include <QWidget>
#include "SARibbonActionsManager.h"
#include "SARibbonPannel.h"
#include "SARibbonCustomizeData.h"
// SARibbonCustomizeWidget 特有
class SARibbonCustomizeWidgetUi;
class SARibbonMainWindow;
class SARibbonBar;
//
class QStandardItemModel;
class QStandardItem;
class QAbstractButton;
//
class QXmlStreamWriter;
class QXmlStreamReader;
/**
* @brief 自定义界面窗口
*
* @note SARibbon的自定义是基于步骤的,如果在窗口生成前调用了@ref sa_apply_customize_from_xml_file 类似函数
* 那么在对话框生成前为了保证同步需要调用@ref SARibbonCustomizeWidget::fromXml 同步配置文件,这样再次修改后的配置文件就一致
*/
class SA_RIBBON_EXPORT SARibbonCustomizeWidget : public QWidget
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonCustomizeWidget)
public:
//保留接口
explicit SARibbonCustomizeWidget(SARibbonMainWindow* ribbonWindow, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
//对于不使用SARibbonMainWindow的情况,使用此构造函数
explicit SARibbonCustomizeWidget(SARibbonBar* ribbonbar, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~SARibbonCustomizeWidget();
/**
* @brief 定义ribbon树的显示类型
*/
enum RibbonTreeShowType
{
ShowAllCategory, ///< 显示所有Category,包括contextcategory
ShowMainCategory ///< 显示主要的category,不包含上下文
};
/**
* @brief QStandardItem对应的role
*/
enum ItemRole
{
LevelRole = Qt::UserRole + 1, ///< 代表这是层级,有0category 1pannel 2item
PointerRole = Qt::UserRole + 2, ///< 代表这是存放指针。根据LevelRole来进行转
CanCustomizeRole = Qt::UserRole + 3, ///< 代表个item是可以自定义的.bool
CustomizeRole = Qt::UserRole + 4, ///< 代表这个是自定义的item,bool,主要用于那些自己添加的标签和pannel,有此角色必有CanCustomizeRole
CustomizeObjNameRole = Qt::UserRole + 5 ///< 记录了临时的自定义内容的obj名 QString
};
//设置action管理器
void setupActionsManager(SARibbonActionsManager* mgr);
//判断用户是否有要存储的内容,对应save动作
bool isApplied() const;
//判断用户是否有改动内容,对应apply动作
bool isCached() const;
//获取model
const QStandardItemModel* model() const;
//根据当前的radiobutton选项来更新model
void updateModel();
//更新model
void updateModel(RibbonTreeShowType type);
//应用所有的设定
bool applys();
//转换为xml
bool toXml(QXmlStreamWriter* xml) const;
bool toXml(const QString& xmlpath) const;
//从xml中加载QList<SARibbonCustomizeData>,对于基于配置文件的设置,对话框显示前建议调用此函数,保证叠加设置的正确记录
void fromXml(QXmlStreamReader* xml);
void fromXml(const QString& xmlpath);
//应用xml配置,可以结合customize_datas_from_xml和customize_datas_apply函数
static bool fromXml(QXmlStreamReader* xml, SARibbonBar* bar, SARibbonActionsManager* mgr);
//缓存应用的动作,这些动作不会被clear清除,用于本地存储
void makeActionsApplied();
//清除applied的动作,cancel操作后需要清空已应用的动作
void clearApplied();
//清除缓存动作,在执行applys函数后,如果要继续调用,应该clear,否则会导致异常
void clearCache();
//清除所有动作,不包含本地读取的数据
void clear();
protected:
//把QList<SARibbonCustomizeData>进行裁剪,把一些动作合并
void simplify();
SARibbonPannelItem::RowProportion selectedRowProportion() const;
QAction* selectedAction() const;
QAction* itemToAction(QStandardItem* item) const;
QStandardItem* selectedItem() const;
//获取选中的ribbon tree 的level
int selectedRibbonLevel() const;
//根据选中的item判断
int itemLevel(QStandardItem* item) const;
//设置某个item被选中
void setSelectItem(QStandardItem* item, bool ensureVisible = true);
//判断itemn能否改动,可以改动返回true
bool isItemCanCustomize(QStandardItem* item) const;
bool isSelectedItemCanCustomize() const;
//判断item是否是自定义的item
bool isCustomizeItem(QStandardItem* item) const;
bool isSelectedItemIsCustomize() const;
//删除一个item
void removeItem(QStandardItem* item);
private Q_SLOTS:
void onComboBoxActionIndexCurrentIndexChanged(int index);
void onRadioButtonGroupButtonClicked(QAbstractButton* b);
void onPushButtonNewCategoryClicked();
void onPushButtonNewPannelClicked();
void onPushButtonRenameClicked();
void onPushButtonAddClicked();
void onPushButtonDeleteClicked();
void onListViewSelectClicked(const QModelIndex& index);
void onTreeViewResultClicked(const QModelIndex& index);
void onToolButtonUpClicked();
void onToolButtonDownClicked();
void onItemChanged(QStandardItem* item);
void onLineEditSearchActionTextEdited(const QString& text);
void onPushButtonResetClicked();
private:
void init(SARibbonBar* ribbonbar);
void initConnection();
private:
SARibbonCustomizeWidgetUi* ui;
};
/**
* @brief 转换为xml
*
* 此函数仅会写element,不会写document相关内容,因此如果需要写document,
* 需要在此函数前调用QXmlStreamWriter::writeStartDocument(),在此函数后调用QXmlStreamWriter::writeEndDocument()
* @param xml QXmlStreamWriter指针
* @note 注意,在传入QXmlStreamWriter之前,需要设置编码为utf-8:xml->setCodec("utf-8");
* @note 由于QXmlStreamWriter在QString作为io时,是不支持编码的,而此又无法保证自定义过程不出现中文字符,
* 因此,QXmlStreamWriter不应该通过QString进行构造,如果需要用到string,也需要通过QByteArray构造,如:
* @param cds 基于QList<SARibbonCustomizeData>生成的步骤
* @return 如果出现异常,返回false,如果没有自定义数据也会返回false
*/
bool SA_RIBBON_EXPORT sa_customize_datas_to_xml(QXmlStreamWriter* xml, const QList< SARibbonCustomizeData >& cds);
/**
* @brief 通过xml获取QList<SARibbonCustomizeData>
* @param xml
* @return QList<SARibbonCustomizeData>
*/
QList< SARibbonCustomizeData > SA_RIBBON_EXPORT sa_customize_datas_from_xml(QXmlStreamReader* xml, SARibbonActionsManager* mgr);
/**
* @brief 应用QList<SARibbonCustomizeData>
* @param cds
* @param w SARibbonBar指针
* @return 成功应用的个数
*/
int SA_RIBBON_EXPORT sa_customize_datas_apply(const QList< SARibbonCustomizeData >& cds, SARibbonBar* w);
/**
* @brief 反向取消应用
* @param cds
* @param w SARibbonBar指针
* @return 成功应用的个数
*/
int SA_RIBBON_EXPORT sa_customize_datas_reverse(const QList< SARibbonCustomizeData >& cds, SARibbonBar* w);
/**
* @brief 直接加载xml自定义ribbon配置文件用于ribbon的自定义显示
* @param filePath xml配置文件
* @param w 主窗体
* @param mgr action管理器
* @return 成功返回true
* @note 重复加载一个配置文件会发生异常,为了避免此类事件发生,一般通过一个变量保证只加载一次,如:
* @code
* static bool has_call = false;
* if (!has_call) {
* has_call = sa_apply_customize_from_xml_file("customize.xml", this, m_actMgr);
* }
* @endcode
*/
bool SA_RIBBON_EXPORT sa_apply_customize_from_xml_file(const QString& filePath, SARibbonBar* bar, SARibbonActionsManager* mgr);
#endif // SARIBBONCUSTOMIZEWIDGET_H
@@ -0,0 +1,118 @@
#include "SARibbonElementFactory.h"
#include <QApplication>
#include <QFontMetrics>
#include "SARibbonApplicationButton.h"
#include "SARibbonBar.h"
#include "SARibbonTabBar.h"
#include "SARibbonCategory.h"
#include "SARibbonContextCategory.h"
#include "SARibbonSeparatorWidget.h"
#include "SARibbonGallery.h"
#include "SARibbonGalleryGroup.h"
#include "SARibbonToolButton.h"
#include "SARibbonControlButton.h"
#include "SARibbonButtonGroupWidget.h"
#include "SARibbonStackedWidget.h"
#include "SARibbonQuickAccessBar.h"
#include "SARibbonPannelOptionButton.h"
#include "SARibbonPannelLayout.h"
#include "SARibbonSystemButtonBar.h"
SARibbonElementFactory::SARibbonElementFactory()
{
}
SARibbonElementFactory::~SARibbonElementFactory()
{
}
SARibbonBar* SARibbonElementFactory::createRibbonBar(QWidget* parent)
{
return (new SARibbonBar(parent));
}
SARibbonTabBar* SARibbonElementFactory::createRibbonTabBar(QWidget* parent)
{
return (new SARibbonTabBar(parent));
}
SARibbonApplicationButton* SARibbonElementFactory::createRibbonApplicationButton(QWidget* parent)
{
return (new SARibbonApplicationButton(parent));
}
SARibbonCategory* SARibbonElementFactory::createRibbonCategory(QWidget* parent)
{
return (new SARibbonCategory(parent));
}
SARibbonContextCategory* SARibbonElementFactory::createRibbonContextCategory(QWidget* parent)
{
return (new SARibbonContextCategory(parent));
}
SARibbonPannel* SARibbonElementFactory::createRibbonPannel(QWidget* parent)
{
return (new SARibbonPannel(parent));
}
SARibbonSeparatorWidget* SARibbonElementFactory::createRibbonSeparatorWidget(QWidget* parent)
{
return (new SARibbonSeparatorWidget(parent));
}
SARibbonGallery* SARibbonElementFactory::createRibbonGallery(QWidget* parent)
{
return (new SARibbonGallery(parent));
}
SARibbonGalleryGroup* SARibbonElementFactory::createRibbonGalleryGroup(QWidget* parent)
{
return (new SARibbonGalleryGroup(parent));
}
SARibbonToolButton* SARibbonElementFactory::createRibbonToolButton(QWidget* parent)
{
return (new SARibbonToolButton(parent));
}
SARibbonControlButton* SARibbonElementFactory::createRibbonControlButton(QWidget* parent)
{
return (new SARibbonControlButton(parent));
}
SARibbonControlToolButton* SARibbonElementFactory::createRibbonControlToolButton(QWidget* parent)
{
return (new SARibbonControlToolButton(parent));
}
SARibbonStackedWidget* SARibbonElementFactory::createRibbonStackedWidget(SARibbonBar* parent)
{
return (new SARibbonStackedWidget(parent));
}
SARibbonButtonGroupWidget* SARibbonElementFactory::craeteButtonGroupWidget(QWidget* parent)
{
return (new SARibbonButtonGroupWidget(parent));
}
SARibbonQuickAccessBar* SARibbonElementFactory::createQuickAccessBar(QWidget* parent)
{
return (new SARibbonQuickAccessBar(parent));
}
SARibbonSystemButtonBar* SARibbonElementFactory::createWindowButtonGroup(QWidget* parent)
{
return (new SARibbonSystemButtonBar(parent));
}
/**
* @brief 创建SARibbonPannelOptionButton
* @param pannel 附属的pannel
* @return
* @sa SARibbonPannelOptionButton
*/
SARibbonPannelOptionButton* SARibbonElementFactory::createRibbonPannelOptionButton(SARibbonPannel* pannel)
{
return (new SARibbonPannelOptionButton(pannel));
}
@@ -0,0 +1,59 @@
#ifndef SARIBBONELEMENTFACTORY_H
#define SARIBBONELEMENTFACTORY_H
#include "SARibbonGlobal.h"
#include <QColor>
#include <QMargins>
#include <QSize>
#include <QScopedPointer>
#include "SARibbonBar.h"
#include "SARibbonPannel.h"
class QWidget;
class SARibbonBar;
class SARibbonTabBar;
class SARibbonApplicationButton;
class SARibbonCategory;
class SARibbonContextCategory;
class SARibbonPannel;
class SARibbonSeparatorWidget;
class SARibbonGallery;
class SARibbonGalleryGroup;
class SARibbonToolButton;
class SARibbonControlButton;
class SARibbonControlToolButton;
class SARibbonButtonGroupWidget;
class SARibbonStackedWidget;
class SARibbonQuickAccessBar;
class SARibbonPannelOptionButton;
class SARibbonSystemButtonBar;
///
/// \brief SARibbon的子元素创建的工厂,SARibbon内部创建子元素都通过SARibbonElementFactory来创建
///
/// 由于SARibbonBar是一个复合控件,很多子窗口组合而成,有些部件的创建如果想继承,那么就需要这个工厂类来处理
/// 如SARibbonCategory,可以重载此类的createRibbonCategory,返回重载的类的实例
///
class SA_RIBBON_EXPORT SARibbonElementFactory
{
public:
SARibbonElementFactory();
virtual ~SARibbonElementFactory();
virtual SARibbonBar* createRibbonBar(QWidget* parent);
virtual SARibbonTabBar* createRibbonTabBar(QWidget* parent);
virtual SARibbonApplicationButton* createRibbonApplicationButton(QWidget* parent);
virtual SARibbonCategory* createRibbonCategory(QWidget* parent);
virtual SARibbonContextCategory* createRibbonContextCategory(QWidget* parent);
virtual SARibbonPannel* createRibbonPannel(QWidget* parent);
virtual SARibbonSeparatorWidget* createRibbonSeparatorWidget(QWidget* parent);
virtual SARibbonGallery* createRibbonGallery(QWidget* parent);
virtual SARibbonGalleryGroup* createRibbonGalleryGroup(QWidget* parent);
virtual SARibbonToolButton* createRibbonToolButton(QWidget* parent);
virtual SARibbonControlButton* createRibbonControlButton(QWidget* parent);
virtual SARibbonControlToolButton* createRibbonControlToolButton(QWidget* parent);
virtual SARibbonStackedWidget* createRibbonStackedWidget(SARibbonBar* parent);
virtual SARibbonButtonGroupWidget* craeteButtonGroupWidget(QWidget* parent);
virtual SARibbonQuickAccessBar* createQuickAccessBar(QWidget* parent);
virtual SARibbonSystemButtonBar* createWindowButtonGroup(QWidget* parent);
virtual SARibbonPannelOptionButton* createRibbonPannelOptionButton(SARibbonPannel* pannel);
};
#endif // SARIBBONELEMENTFACTORY_H
@@ -0,0 +1,27 @@
#include "SARibbonElementManager.h"
SARibbonElementManager::SARibbonElementManager()
{
mFactory.reset(new SARibbonElementFactory());
}
SARibbonElementManager::~SARibbonElementManager()
{
}
SARibbonElementManager* SARibbonElementManager::instance()
{
static SARibbonElementManager s_instance;
return (&(s_instance));
}
SARibbonElementFactory* SARibbonElementManager::factory()
{
return (mFactory.data());
}
void SARibbonElementManager::setupFactory(SARibbonElementFactory* fac)
{
mFactory.reset(fac);
}
@@ -0,0 +1,52 @@
#ifndef SARIBBONELEMENTMANAGER_H
#define SARIBBONELEMENTMANAGER_H
#include "SARibbonElementFactory.h"
#include "SARibbonGlobal.h"
/**
@brief 此类是一个全局单例,用于管理SARibbonElementFactory
如果你有自己的某个部件要重写,首先你需要有自己的ElementFactory
@code
class MyRibbonElementFactory : public SARibbonElementFactory{
public:
...
virtual SARibbonPannel* createRibbonPannel(QWidget* parent){
return new MyRibbonPannel(parent);
}
};
@endcode
然后,你需要在ribbonbar创建之前把自己的ElementFactory设置进去
这个一般会在main函数中进行
@code
SARibbonElementManager::instance()->setupFactory(new MyRibbonElementFactory);
@endcode
这样,SARibbon创建的pannel就是你自己重写的MyRibbonPannel
*/
class SA_RIBBON_EXPORT SARibbonElementManager
{
protected:
SARibbonElementManager();
public:
virtual ~SARibbonElementManager();
static SARibbonElementManager* instance();
SARibbonElementFactory* factory();
void setupFactory(SARibbonElementFactory* fac);
private:
QScopedPointer< SARibbonElementFactory > mFactory;
};
#ifndef RibbonSubElementMgr
#define RibbonSubElementMgr SARibbonElementManager::instance()
#endif
#ifndef RibbonSubElementFactory
#define RibbonSubElementFactory SARibbonElementManager::instance()->factory()
#endif
#endif // SARIBBONELEMENTMANAGER_H
@@ -0,0 +1,421 @@
#include "SARibbonGallery.h"
#include "SARibbonControlButton.h"
#include <QIcon>
#include <QApplication>
#define ICON_ARROW_UP QIcon(":/image/resource/ArrowUp.png")
#define ICON_ARROW_DOWN QIcon(":/image/resource/ArrowDown.png")
#define ICON_ARROW_MORE QIcon(":/image/resource/ArrowMore.png")
#include <QResizeEvent>
#include <QDebug>
#include <QVBoxLayout>
#include <QScrollBar>
#include <QLabel>
#include "SARibbonElementManager.h"
#include <QActionGroup>
/**
* @brief The SARibbonGalleryPrivate class
*/
class SARibbonGallery::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonGallery)
public:
static int sGalleryButtonMaximumWidth;
SARibbonGalleryButton* mButtonUp { nullptr };
SARibbonGalleryButton* mButtonDown { nullptr };
SARibbonGalleryButton* mButtonMore { nullptr };
#if 0
SARibbonMenu *popupWidget;
#else
SARibbonGalleryViewport* mPopupWidget { nullptr };
#endif
SARibbonGalleryGroup* mViewportGroup { nullptr };
QBoxLayout* mButtonLayout { nullptr };
QBoxLayout* mLayout { nullptr };
PrivateData(SARibbonGallery* p) : q_ptr(p)
{
}
void init()
{
mButtonUp = new SARibbonGalleryButton(q_ptr);
mButtonDown = new SARibbonGalleryButton(q_ptr);
mButtonMore = new SARibbonGalleryButton(q_ptr);
mButtonUp->setToolButtonStyle(Qt::ToolButtonIconOnly);
mButtonDown->setToolButtonStyle(Qt::ToolButtonIconOnly);
mButtonMore->setToolButtonStyle(Qt::ToolButtonIconOnly);
mButtonUp->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
mButtonDown->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
mButtonMore->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
mButtonUp->setObjectName(QStringLiteral("SARibbonGalleryButtonUp"));
mButtonDown->setObjectName(QStringLiteral("SARibbonGalleryButtonDown"));
mButtonMore->setObjectName(QStringLiteral("SARibbonGalleryButtonMore"));
mButtonUp->setMaximumWidth(sGalleryButtonMaximumWidth);
mButtonDown->setMaximumWidth(sGalleryButtonMaximumWidth);
mButtonMore->setMaximumWidth(sGalleryButtonMaximumWidth);
mButtonUp->setIcon(ICON_ARROW_UP);
mButtonDown->setIcon(ICON_ARROW_DOWN);
mButtonMore->setIcon(ICON_ARROW_MORE);
q_ptr->connect(mButtonUp, &QAbstractButton::clicked, q_ptr, &SARibbonGallery::pageUp);
q_ptr->connect(mButtonDown, &QAbstractButton::clicked, q_ptr, &SARibbonGallery::pageDown);
q_ptr->connect(mButtonMore, &QAbstractButton::clicked, q_ptr, &SARibbonGallery::showMoreDetail);
// 信号转发
q_ptr->connect(q_ptr, &SARibbonGallery::triggered, q_ptr, &SARibbonGallery::onTriggered);
mPopupWidget = nullptr;
mViewportGroup = nullptr;
mButtonLayout = new QBoxLayout(QBoxLayout::TopToBottom);
mButtonLayout->setSpacing(0);
mButtonLayout->setContentsMargins(0, 0, 0, 0);
mButtonLayout->addWidget(mButtonUp);
mButtonLayout->addWidget(mButtonDown);
mButtonLayout->addWidget(mButtonMore);
mLayout = new QBoxLayout(QBoxLayout::RightToLeft);
mLayout->setSpacing(0);
mLayout->setContentsMargins(0, 0, 0, 0);
mLayout->addLayout(mButtonLayout);
mLayout->addStretch();
q_ptr->setLayout(mLayout);
}
bool isValid() const
{
return (q_ptr != nullptr);
}
void createPopupWidget()
{
if (nullptr == mPopupWidget) {
#if 0
popupWidget = new SARibbonMenu(Parent);
#else
mPopupWidget = new SARibbonGalleryViewport(q_ptr);
#endif
}
}
void setViewPort(SARibbonGalleryGroup* v)
{
if (nullptr == mViewportGroup) {
mViewportGroup = RibbonSubElementFactory->createRibbonGalleryGroup(q_ptr);
mLayout->addWidget(mViewportGroup, 1);
}
mViewportGroup->setRecalcGridSizeBlock(true);
mViewportGroup->setGalleryGroupStyle(v->galleryGroupStyle());
mViewportGroup->setDisplayRow(v->displayRow());
mViewportGroup->setSpacing(v->spacing());
mViewportGroup->setGridMaximumWidth(v->gridMaximumWidth());
mViewportGroup->setGridMinimumWidth(v->gridMinimumWidth());
mViewportGroup->setRecalcGridSizeBlock(false);
mViewportGroup->recalcGridSize(mViewportGroup->height());
mViewportGroup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mViewportGroup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mViewportGroup->setModel(v->model());
mViewportGroup->show();
}
};
// 静态变量初始化
/**
* @brief SARibbonGalleryPrivate::sGalleryButtonMaximumWidth
*/
int SARibbonGallery::PrivateData::sGalleryButtonMaximumWidth = 15;
//===================================================
// SARibbonGalleryButton
//===================================================
SARibbonGalleryButton::SARibbonGalleryButton(QWidget* parent) : QToolButton(parent)
{
}
SARibbonGalleryButton::~SARibbonGalleryButton()
{
}
//===================================================
// SARibbonGalleryViewport
//===================================================
SARibbonGalleryViewport::SARibbonGalleryViewport(QWidget* parent) : QWidget(parent)
{
setWindowFlags(Qt::Popup);
QPalette pl = palette();
pl.setBrush(QPalette::Window, pl.brush(QPalette::Base));
setPalette(pl);
m_layout = new QVBoxLayout(this);
m_layout->setSpacing(0);
m_layout->setContentsMargins(0, 0, 0, 0);
}
/**
* @brief 添加窗口不带标题
* @param w
*/
void SARibbonGalleryViewport::addWidget(QWidget* w)
{
w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_layout->addWidget(w);
}
/**
* @brief 添加窗口,带标题
* @param w
* @param title
*/
void SARibbonGalleryViewport::addWidget(QWidget* w, const QString& title)
{
QLabel* label = new QLabel(this);
label->setText(title);
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_layout->addWidget(label);
w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_layout->addWidget(w);
_widgetToTitleLable[ w ] = label;
}
/**
* @brief 通过SARibbonGalleryGroup获取对应的标题,用户可以通过此函数设置QLabel的属性
* @param g
* @return 如果没有管理group,将返回nullptr
*/
QLabel* SARibbonGalleryViewport::titleLabel(QWidget* w)
{
return _widgetToTitleLable.value(w, nullptr);
}
/**
* @brief 移除窗口
* @param w
*/
void SARibbonGalleryViewport::removeWidget(QWidget* w)
{
QLabel* label = titleLabel(w);
if (label) {
m_layout->removeWidget(label);
}
m_layout->removeWidget(w);
}
/**
* @brief widget的标题改变
* @param g
* @param title
*/
void SARibbonGalleryViewport::onTitleChanged(QWidget* w, const QString& title)
{
QLabel* l = titleLabel(w);
if (l) {
l->setText(title);
}
}
//===================================================
// SARibbonGallery
//===================================================
SARibbonGallery::SARibbonGallery(QWidget* parent) : QFrame(parent), d_ptr(new SARibbonGallery::PrivateData(this))
{
d_ptr->init();
setFrameShape(QFrame::Box);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setMinimumWidth(200);
}
SARibbonGallery::~SARibbonGallery()
{
}
QSize SARibbonGallery::sizeHint() const
{
return (QSize(100, 62));
}
/**
* @brief 获取一个空白SARibbonGalleryGroup
* @return
*/
SARibbonGalleryGroup* SARibbonGallery::addGalleryGroup()
{
SARibbonGalleryGroup* group = RibbonSubElementFactory->createRibbonGalleryGroup(this);
addGalleryGroup(group);
return (group);
}
/**
* @brief 添加一组SARibbonGalleryGroup
* @param group
*/
void SARibbonGallery::addGalleryGroup(SARibbonGalleryGroup* group)
{
group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
SARibbonGalleryViewport* viewport = ensureGetPopupViewPort();
viewport->addWidget(group, group->groupTitle());
connect(group, &QAbstractItemView::clicked, this, &SARibbonGallery::onItemClicked);
connect(group, &SARibbonGalleryGroup::groupTitleChanged, this, [ group, viewport ](const QString& t) {
viewport->onTitleChanged(group, t);
});
connect(group, &SARibbonGalleryGroup::triggered, this, &SARibbonGallery::triggered);
connect(group, &SARibbonGalleryGroup::hovered, this, &SARibbonGallery::hovered);
setCurrentViewGroup(group);
}
/**
* @brief 添加一组actions
* @param title actions组的名字
* @param actions
* @return 返回SARibbonGalleryGroup,用户可以通过修改SARibbonGalleryGroup属性控制其显示方法
*/
SARibbonGalleryGroup* SARibbonGallery::addCategoryActions(const QString& title, QList< QAction* > actions)
{
SARibbonGalleryGroup* group = RibbonSubElementFactory->createRibbonGalleryGroup(this);
if (!title.isEmpty()) {
group->setGroupTitle(title);
}
group->addActionItemList(actions);
addGalleryGroup(group);
return (group);
}
void SARibbonGallery::setCurrentViewGroup(SARibbonGalleryGroup* group)
{
d_ptr->setViewPort(group);
QApplication::postEvent(this, new QResizeEvent(size(), size()));
}
SARibbonGalleryGroup* SARibbonGallery::currentViewGroup() const
{
return (d_ptr->mViewportGroup);
}
/**
* @brief 获取弹出窗口
* @return
*/
SARibbonGalleryViewport* SARibbonGallery::getPopupViewPort() const
{
return d_ptr->mPopupWidget;
}
/**
* @brief 设置最右边三个控制按钮的最大宽度(默认15)
* @param w
*/
void SARibbonGallery::setGalleryButtonMaximumWidth(int w)
{
SARibbonGallery::PrivateData::sGalleryButtonMaximumWidth = w;
}
/**
* @brief 上翻页
*/
void SARibbonGallery::pageDown()
{
if (d_ptr->mViewportGroup) {
QScrollBar* vscrollBar = d_ptr->mViewportGroup->verticalScrollBar();
int v = vscrollBar->value();
v += vscrollBar->singleStep();
vscrollBar->setValue(v);
}
}
/**
* @brief 下翻页
*/
void SARibbonGallery::pageUp()
{
if (d_ptr->mViewportGroup) {
QScrollBar* vscrollBar = d_ptr->mViewportGroup->verticalScrollBar();
int v = vscrollBar->value();
v -= vscrollBar->singleStep();
vscrollBar->setValue(v);
}
}
/**
* @brief 显示更多触发,默认弹出内部管理的SARibbonGalleryViewport,用户可重载此函数实现自定义的弹出
*/
void SARibbonGallery::showMoreDetail()
{
if (nullptr == d_ptr->mPopupWidget) {
return;
}
QSize popupMenuSize = d_ptr->mPopupWidget->sizeHint();
QPoint start = mapToGlobal(QPoint(0, 0));
int width = d_ptr->mViewportGroup->width(); // viewport
width += qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); // scrollbar
d_ptr->mPopupWidget->setGeometry(start.x(), start.y(), width, popupMenuSize.height());
d_ptr->mPopupWidget->show();
}
void SARibbonGallery::onItemClicked(const QModelIndex& index)
{
QObject* obj = sender();
SARibbonGalleryGroup* group = qobject_cast< SARibbonGalleryGroup* >(obj);
if (group) {
setCurrentViewGroup(group);
SARibbonGalleryGroup* curGroup = currentViewGroup();
curGroup->scrollTo(index);
curGroup->setCurrentIndex(index);
}
}
void SARibbonGallery::onTriggered(QAction* action)
{
Q_UNUSED(action);
// 点击后关闭弹出窗口
if (d_ptr->mPopupWidget) {
if (d_ptr->mPopupWidget->isVisible()) {
d_ptr->mPopupWidget->hide();
}
}
}
SARibbonGalleryViewport* SARibbonGallery::ensureGetPopupViewPort()
{
if (nullptr == d_ptr->mPopupWidget) {
d_ptr->createPopupWidget();
}
return (d_ptr->mPopupWidget);
}
void SARibbonGallery::resizeEvent(QResizeEvent* event)
{
QFrame::resizeEvent(event);
// 对SARibbonGalleryViewport所有SARibbonGalleryGroup重置尺寸
int h = layout()->contentsRect().height();
if (d_ptr->mViewportGroup) {
h = d_ptr->mViewportGroup->height();
d_ptr->mViewportGroup->recalcGridSize();
}
if (d_ptr->mPopupWidget) {
QLayout* lay = d_ptr->mPopupWidget->layout();
if (!lay) {
return;
}
int c = lay->count();
for (int i = 0; i < c; ++i) {
QLayoutItem* item = lay->itemAt(i);
if (!item) {
continue;
}
QWidget* w = item->widget();
if (!w) {
continue;
}
SARibbonGalleryGroup* g = qobject_cast< SARibbonGalleryGroup* >(w);
if (!g) {
continue;
}
g->recalcGridSize(h);
}
}
}
void SARibbonGallery::paintEvent(QPaintEvent* event)
{
QFrame::paintEvent(event);
}
@@ -0,0 +1,125 @@
#ifndef SARIBBONGALLERY_H
#define SARIBBONGALLERY_H
#include "SARibbonGlobal.h"
#include <QFrame>
#include <QToolButton>
#include "SARibbonGalleryGroup.h"
#include <QSizeGrip>
class QLabel;
class QVBoxLayout;
class SARibbonGalleryViewport;
/**
* @brief 针对SARibbonGallery控件的按钮
*/
class SA_RIBBON_EXPORT SARibbonGalleryButton : public QToolButton
{
Q_OBJECT
public:
explicit SARibbonGalleryButton(QWidget* parent = nullptr);
~SARibbonGalleryButton();
};
/**
* @brief Gallery控件
*
* Gallery控件是由一个当前激活的@sa SARibbonGalleryGroup 和弹出的 @sa SARibbonGalleryViewport 组成
*
* @sa SARibbonGalleryGroup 是继承@sa QListView actions通过icon展示出来,相关的属性可以按照QListView设置
*
* @sa SARibbonGalleryViewport 是一个内部有垂直布局的窗体,在弹出激活时,把管理的SARibbonGalleryGroup都展示出来
*
* 示例如下:
* @code
* SARibbonGallery* gallery = pannel1->addGallery();
* QList< QAction* > galleryActions;
* ...create many actions ...
* SARibbonGalleryGroup* group1 = gallery->addCategoryActions(tr("Files"), galleryActions);
* galleryActions.clear();
* ...create many actions ...
* gallery->addCategoryActions(tr("Apps"), galleryActions);
* gallery->setCurrentViewGroup(group1);
* @endcode
*/
class SA_RIBBON_EXPORT SARibbonGallery : public QFrame
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonGallery)
public:
explicit SARibbonGallery(QWidget* parent = nullptr);
virtual ~SARibbonGallery();
virtual QSize sizeHint() const override;
// 添加一个GalleryGroup
SARibbonGalleryGroup* addGalleryGroup();
// 添加一个GalleryGroup
void addGalleryGroup(SARibbonGalleryGroup* group);
// 快速添加一组actions
SARibbonGalleryGroup* addCategoryActions(const QString& title, QList< QAction* > actions);
// 设置当前显示的SARibbonGalleryGroup
void setCurrentViewGroup(SARibbonGalleryGroup* group);
// 获取当前显示的SARibbonGalleryGroup
SARibbonGalleryGroup* currentViewGroup() const;
// 获取弹出窗口指针
SARibbonGalleryViewport* getPopupViewPort() const;
public:
// 设置最右边三个控制按钮的最大宽度(默认15)
static void setGalleryButtonMaximumWidth(int w);
Q_SIGNALS:
/**
* @brief 转发管理的SARibbonGalleryGroup::triggered
* 所有加入SARibbonGallery的action都会被一个QActionGroup管理,可以通过@sa getActionGroup 获取到对应的actiongroup
* @param action
*/
void triggered(QAction* action);
/**
* @brief 转发管理的SARibbonGalleryGroup::hovered
* @note 此属性需要确保SARibbonGalleryGroup::setMouseTracking(true)
* @param action
*/
void hovered(QAction* action);
public Q_SLOTS:
// 上翻页
virtual void pageUp();
// 下翻页
virtual void pageDown();
// 显示更多触发,默认弹出内部管理的SARibbonGalleryViewport,用户可重载此函数实现自定义的弹出
virtual void showMoreDetail();
protected Q_SLOTS:
void onItemClicked(const QModelIndex& index);
virtual void onTriggered(QAction* action);
private:
SARibbonGalleryViewport* ensureGetPopupViewPort();
protected:
void resizeEvent(QResizeEvent* event) override;
void paintEvent(QPaintEvent* event) override;
};
///
/// \brief SARibbonGallery的Viewport class
///
class SARibbonGalleryViewport : public QWidget
{
Q_OBJECT
public:
SARibbonGalleryViewport(QWidget* parent);
// 添加窗口不带标题
void addWidget(QWidget* w);
// 添加窗口,带标题
void addWidget(QWidget* w, const QString& title);
// 通过SARibbonGalleryGroup获取对应的标题,用户可以通过此函数设置QLabel的属性
QLabel* titleLabel(QWidget* w);
// 移除窗口
void removeWidget(QWidget* w);
public Q_SLOTS:
void onTitleChanged(QWidget* w, const QString& title);
private:
QVBoxLayout* m_layout;
QMap< QWidget*, QLabel* > _widgetToTitleLable; ///< QWidget和lable的对应
};
#endif // SARIBBONGALLERY_H
@@ -0,0 +1,496 @@
#include "SARibbonGalleryGroup.h"
#include <QPainter>
#include <QDebug>
#include <QActionGroup>
#include <QItemSelectionModel>
#include "SARibbonElementManager.h"
/**
* @brief The SARibbonGalleryGroupPrivate class
*/
class SARibbonGalleryGroup::PrivateData
{
public:
SARibbonGalleryGroup* q_ptr;
QString mGroupTitle;
SARibbonGalleryGroup::GalleryGroupStyle mPreStyle { SARibbonGalleryGroup::IconWithText };
SARibbonGalleryGroup::DisplayRow mDisplayRow { SARibbonGalleryGroup::DisplayOneRow };
bool mBlockRecalc { false };
int mGridMinimumWidth { 0 }; ///< grid最小宽度
int mGridMaximumWidth { 0 }; ///< grid最大宽度
QActionGroup* mActionGroup { nullptr }; ///< 所有GalleryGroup管理的actions都由这个actiongroup管理
public:
PrivateData(SARibbonGalleryGroup* p) : q_ptr(p)
{
mActionGroup = new QActionGroup(p);
p->connect(mActionGroup, &QActionGroup::triggered, p, &SARibbonGalleryGroup::triggered);
p->connect(mActionGroup, &QActionGroup::hovered, p, &SARibbonGalleryGroup::hovered);
}
};
//===================================================
// SARibbonGalleryGroupItemDelegate
//===================================================
SARibbonGalleryGroupItemDelegate::SARibbonGalleryGroupItemDelegate(SARibbonGalleryGroup* group, QObject* parent)
: QStyledItemDelegate(parent), mGroup(group)
{
}
SARibbonGalleryGroupItemDelegate::~SARibbonGalleryGroupItemDelegate()
{
}
void SARibbonGalleryGroupItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (nullptr == mGroup) {
return;
}
switch (mGroup->galleryGroupStyle()) {
case SARibbonGalleryGroup::IconWithText:
paintIconWithText(painter, option, index);
break;
case SARibbonGalleryGroup::IconWithWordWrapText:
paintIconWithTextWordWrap(painter, option, index);
break;
case SARibbonGalleryGroup::IconOnly:
paintIconOnly(painter, option, index);
break;
default:
paintIconWithText(painter, option, index);
break;
}
}
void SARibbonGalleryGroupItemDelegate::paintIconOnly(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyle* style = mGroup->style();
int sp = mGroup->spacing();
sp += 3;
painter->save();
painter->setClipRect(option.rect);
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, mGroup);
// draw the icon
QRect iconRect = option.rect;
iconRect.adjust(sp, sp, -sp, -sp);
QIcon ico = qvariant_cast< QIcon >(index.data(Qt::DecorationRole));
ico.paint(painter, iconRect, Qt::AlignCenter, QIcon::Normal, QIcon::On);
painter->restore();
}
void SARibbonGalleryGroupItemDelegate::paintIconWithText(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyledItemDelegate::paint(painter, option, index);
}
void SARibbonGalleryGroupItemDelegate::paintIconWithTextWordWrap(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyledItemDelegate::paint(painter, option, index);
}
QSize SARibbonGalleryGroupItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
Q_UNUSED(index);
Q_UNUSED(option);
return mGroup->gridSize();
}
//===================================================
// SARibbonGalleryGroupModel
//===================================================
SARibbonGalleryGroupModel::SARibbonGalleryGroupModel(QObject* parent) : QAbstractListModel(parent)
{
}
SARibbonGalleryGroupModel::~SARibbonGalleryGroupModel()
{
clear();
}
int SARibbonGalleryGroupModel::rowCount(const QModelIndex& parent) const
{
return (parent.isValid() ? 0 : mItems.size());
}
Qt::ItemFlags SARibbonGalleryGroupModel::flags(const QModelIndex& index) const
{
if (!index.isValid() || (index.row() >= mItems.size())) {
return (Qt::NoItemFlags);
}
return (mItems.at(index.row())->flags());
}
QVariant SARibbonGalleryGroupModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || (index.row() >= mItems.count())) {
return (QVariant());
}
return (mItems.at(index.row())->data(role));
}
QModelIndex SARibbonGalleryGroupModel::index(int row, int column, const QModelIndex& parent) const
{
if (hasIndex(row, column, parent)) {
return (createIndex(row, column, mItems.at(row)));
}
return (QModelIndex());
}
bool SARibbonGalleryGroupModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || (index.row() >= mItems.count())) {
return (false);
}
mItems.at(index.row())->setData(role, value);
return (true);
}
void SARibbonGalleryGroupModel::clear()
{
beginResetModel();
for (int i = 0; i < mItems.count(); ++i) {
if (mItems.at(i)) {
delete mItems.at(i);
}
}
mItems.clear();
endResetModel();
}
SARibbonGalleryItem* SARibbonGalleryGroupModel::at(int row) const
{
return (mItems.value(row));
}
void SARibbonGalleryGroupModel::insert(int row, SARibbonGalleryItem* item)
{
beginInsertRows(QModelIndex(), row, row);
mItems.insert(row, item);
endInsertRows();
}
SARibbonGalleryItem* SARibbonGalleryGroupModel::take(int row)
{
if ((row < 0) || (row >= mItems.count())) {
return (0);
}
beginRemoveRows(QModelIndex(), row, row);
SARibbonGalleryItem* item = mItems.takeAt(row);
endRemoveRows();
return (item);
}
void SARibbonGalleryGroupModel::append(SARibbonGalleryItem* item)
{
beginInsertRows(QModelIndex(), mItems.count(), mItems.count() + 1);
mItems.append(item);
endInsertRows();
}
//===================================================
// SARibbonGalleryGroup
//===================================================
SARibbonGalleryGroup::SARibbonGalleryGroup(QWidget* w)
: QListView(w), d_ptr(new SARibbonGalleryGroup::PrivateData(this))
{
setViewMode(QListView::IconMode);
setResizeMode(QListView::Adjust);
setSelectionRectVisible(true);
setUniformItemSizes(true);
setSpacing(1);
setItemDelegate(new SARibbonGalleryGroupItemDelegate(this, this));
connect(this, &QAbstractItemView::clicked, this, &SARibbonGalleryGroup::onItemClicked);
SARibbonGalleryGroupModel* m = new SARibbonGalleryGroupModel(this);
setModel(m);
}
SARibbonGalleryGroup::~SARibbonGalleryGroup()
{
}
/**
* @brief 是否禁止计算
* @param on
*/
void SARibbonGalleryGroup::setRecalcGridSizeBlock(bool on)
{
d_ptr->mBlockRecalc = on;
}
bool SARibbonGalleryGroup::isRecalcGridSizeBlock() const
{
return d_ptr->mBlockRecalc;
}
/**
* @brief 重新计算grid和icon的尺寸
*/
void SARibbonGalleryGroup::recalcGridSize()
{
recalcGridSize(height());
}
void SARibbonGalleryGroup::recalcGridSize(int galleryHeight)
{
if (isRecalcGridSizeBlock()) {
return;
}
// 首先通过DisplayRow计算GridSize
int dr = static_cast< int >(displayRow());
if (dr < 1) {
dr = 1;
} else if (dr > 3) {
dr = 3;
}
int h = galleryHeight / dr;
if (h <= 1) {
h = galleryHeight;
}
int w = h;
if (gridMinimumWidth() > 0) {
if (w < gridMinimumWidth()) {
w = gridMinimumWidth();
}
}
if (gridMaximumWidth() > 0) {
if (w > gridMaximumWidth()) {
w = gridMaximumWidth();
}
}
setGridSize(QSize(w, h));
// 在通过GalleryGroupStyle确定icon的尺寸
const int shiftpix = 4; // 这个是移动像素,qt在鼠标移动到图标上时会移动一下,给用户明确的动态,导致如果布局很满会超出显示范围,因此要在此基础上缩放一点
switch (galleryGroupStyle()) {
case IconWithText: {
int textHeight = fontMetrics().lineSpacing();
int iconHeight = h - textHeight - 2 * spacing() - shiftpix;
if (iconHeight > 0) {
setIconSize(QSize(w - 2 * spacing() - shiftpix, iconHeight));
} else {
setIconSize(QSize(w - 2 * spacing() - shiftpix, h - 2 * spacing() - shiftpix));
}
break;
}
case IconWithWordWrapText: {
int textHeight = fontMetrics().lineSpacing() * 2;
int iconHeight = h - textHeight;
if (iconHeight > 0) {
setIconSize(QSize(w - 2 * spacing() - shiftpix, iconHeight - 2 * spacing() - shiftpix));
} else {
setIconSize(QSize(w - 2 * spacing() - shiftpix, h - 2 * spacing() - shiftpix));
}
break;
}
case IconOnly: {
setIconSize(QSize(w - 2 * spacing() - shiftpix, h - 2 * spacing() - shiftpix));
break;
}
default: {
setIconSize(QSize(w - 2 * spacing() - shiftpix, h - 2 * spacing() - shiftpix));
break;
}
}
#if 0
qDebug() << "SARibbonGalleryGroup::recalcGridSize(" << galleryHeight << "): gridSize=" << gridSize()
<< " iconSize=" << iconSize();
#endif
}
///
/// \brief 设置默认的预设样式
/// \param style
///
void SARibbonGalleryGroup::setGalleryGroupStyle(SARibbonGalleryGroup::GalleryGroupStyle style)
{
d_ptr->mPreStyle = style;
if (style == IconWithWordWrapText) {
setWordWrap(true);
}
recalcGridSize();
}
SARibbonGalleryGroup::GalleryGroupStyle SARibbonGalleryGroup::galleryGroupStyle() const
{
return d_ptr->mPreStyle;
}
void SARibbonGalleryGroup::addItem(const QString& text, const QIcon& icon)
{
if (nullptr == groupModel()) {
return;
}
addItem(new SARibbonGalleryItem(text, icon));
}
/**
* @brief 添加一个条目
*
* @param item 条目的内存所有权归属SARibbonGalleryGroup管理
*/
void SARibbonGalleryGroup::addItem(SARibbonGalleryItem* item)
{
if (nullptr == groupModel()) {
return;
}
groupModel()->append(item);
}
void SARibbonGalleryGroup::addActionItem(QAction* act)
{
if (nullptr == groupModel()) {
return;
}
d_ptr->mActionGroup->addAction(act);
groupModel()->append(new SARibbonGalleryItem(act));
}
void SARibbonGalleryGroup::addActionItemList(const QList< QAction* >& acts)
{
SARibbonGalleryGroupModel* model = groupModel();
if (nullptr == model) {
return;
}
for (QAction* a : acts) {
d_ptr->mActionGroup->addAction(a);
}
for (int i = 0; i < acts.size(); ++i) {
model->append(new SARibbonGalleryItem(acts[ i ]));
}
}
///
/// \brief 构建一个model,这个model的父类是SARibbonGalleryGroup,如果要共享model,需要手动处理model的父类
///
void SARibbonGalleryGroup::setupGroupModel()
{
setModel(new SARibbonGalleryGroupModel(this));
}
SARibbonGalleryGroupModel* SARibbonGalleryGroup::groupModel()
{
return (qobject_cast< SARibbonGalleryGroupModel* >(model()));
}
void SARibbonGalleryGroup::setGroupTitle(const QString& title)
{
d_ptr->mGroupTitle = title;
Q_EMIT groupTitleChanged(d_ptr->mGroupTitle);
}
QString SARibbonGalleryGroup::groupTitle() const
{
return (d_ptr->mGroupTitle);
}
void SARibbonGalleryGroup::selectByIndex(int i)
{
SARibbonGalleryGroupModel* model = groupModel();
if (nullptr == model) {
return;
}
QModelIndex ind = model->index(i, 0, QModelIndex());
QItemSelectionModel* selmodel = selectionModel();
if (selmodel) {
selmodel->select(ind, QItemSelectionModel::SelectCurrent);
}
}
/**
* @brief 设置显示的行数
* @param r
*/
void SARibbonGalleryGroup::setDisplayRow(DisplayRow r)
{
d_ptr->mDisplayRow = r;
recalcGridSize();
}
/**
* @brief Gallery显示的行数
* @return
*/
SARibbonGalleryGroup::DisplayRow SARibbonGalleryGroup::displayRow() const
{
return d_ptr->mDisplayRow;
}
/**
* @brief 设置grid最小的宽度,默认为0(不限制)
* @param w
*/
void SARibbonGalleryGroup::setGridMinimumWidth(int w)
{
d_ptr->mGridMinimumWidth = w;
}
/**
* @brief grid最小的宽度,默认为0(不限制)
* @return
*/
int SARibbonGalleryGroup::gridMinimumWidth() const
{
return d_ptr->mGridMinimumWidth;
}
/**
* @brief 设置grid最大的宽度,默认为0(不限制)
* @param w
*/
void SARibbonGalleryGroup::setGridMaximumWidth(int w)
{
d_ptr->mGridMaximumWidth = w;
}
/**
* @brief grid最大的的宽度,默认为0(不限制)
* @param w
*/
int SARibbonGalleryGroup::gridMaximumWidth() const
{
return d_ptr->mGridMaximumWidth;
}
/**
* @brief 获取SARibbonGalleryGroup管理的actiongroup
* @return
*/
QActionGroup* SARibbonGalleryGroup::actionGroup() const
{
return d_ptr->mActionGroup;
}
void SARibbonGalleryGroup::onItemClicked(const QModelIndex& index)
{
if (index.isValid()) {
SARibbonGalleryItem* item = (SARibbonGalleryItem*)index.internalPointer();
if (item) {
QAction* act = item->action();
if (act) {
act->activate(QAction::Trigger);
}
}
}
}
void SARibbonGalleryGroup::onItemEntered(const QModelIndex& index)
{
if (index.isValid()) {
SARibbonGalleryItem* item = (SARibbonGalleryItem*)index.internalPointer();
if (item) {
QAction* act = item->action();
if (act) {
act->activate(QAction::Hover);
}
}
}
}
@@ -0,0 +1,138 @@
#ifndef SARIBBONGALLERYGROUP_H
#define SARIBBONGALLERYGROUP_H
#include "SARibbonGlobal.h"
#include <QList>
#include <QListView>
#include <QStyledItemDelegate>
#include "SARibbonGalleryItem.h"
///
/// \brief SARibbonGalleryGroup对应的显示代理
///
class SA_RIBBON_EXPORT SARibbonGalleryGroupItemDelegate : public QStyledItemDelegate
{
public:
explicit SARibbonGalleryGroupItemDelegate(SARibbonGalleryGroup* group, QObject* parent = nullptr);
~SARibbonGalleryGroupItemDelegate();
virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
virtual void paintIconOnly(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual void paintIconWithText(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual void paintIconWithTextWordWrap(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
private:
SARibbonGalleryGroup* mGroup;
};
///
/// \brief SARibbonGalleryGroup对应的model
///
class SA_RIBBON_EXPORT SARibbonGalleryGroupModel : public QAbstractListModel
{
Q_OBJECT
public:
SARibbonGalleryGroupModel(QObject* parent = Q_NULLPTR);
~SARibbonGalleryGroupModel();
virtual int rowCount(const QModelIndex& parent) const Q_DECL_OVERRIDE;
virtual Qt::ItemFlags flags(const QModelIndex& index) const Q_DECL_OVERRIDE;
virtual QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE;
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const Q_DECL_OVERRIDE;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) Q_DECL_OVERRIDE;
void clear();
SARibbonGalleryItem* at(int row) const;
void insert(int row, SARibbonGalleryItem* item);
SARibbonGalleryItem* take(int row);
void append(SARibbonGalleryItem* item);
private:
QList< SARibbonGalleryItem* > mItems;
};
/**
* @brief Gallery的组
*
* 组负责显示管理Gallery Item
*/
class SA_RIBBON_EXPORT SARibbonGalleryGroup : public QListView
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonGalleryGroup)
public:
/**
* @brief GalleryGroup显示的样式
*/
enum GalleryGroupStyle
{
IconWithText, ///< 图标带文字
IconWithWordWrapText, ///< 图标带文字,文字会换行显示,此模式只会对DisplayOneRow生效,如果不是DisplayOneRow,等同IconWithText
IconOnly ///< 只有图标
};
/**
* @brief 定义Gallery在一个pannel下面显示的图标行数
*/
enum DisplayRow
{
DisplayOneRow = 1, ///< 显示1行,默认
DisplayTwoRow = 2,
DisplayThreeRow = 3
};
SARibbonGalleryGroup(QWidget* w = 0);
virtual ~SARibbonGalleryGroup();
// 重新计算grid尺寸
void setRecalcGridSizeBlock(bool on = true);
bool isRecalcGridSizeBlock() const;
void recalcGridSize();
void recalcGridSize(int galleryHeight);
// 设置显示的样式
void setGalleryGroupStyle(GalleryGroupStyle style);
GalleryGroupStyle galleryGroupStyle() const;
// 添加一个item
void addItem(const QString& text, const QIcon& icon);
void addItem(SARibbonGalleryItem* item);
// 以一个aciton作为item添加
void addActionItem(QAction* act);
void addActionItemList(const QList< QAction* >& acts);
// 构建一个model,这个model的父类是SARibbonGalleryGroup,如果要共享model,需要手动处理model的父类
void setupGroupModel();
SARibbonGalleryGroupModel* groupModel();
// 标题
void setGroupTitle(const QString& title);
QString groupTitle() const;
void selectByIndex(int i);
// 设置显示的行数
void setDisplayRow(DisplayRow r);
DisplayRow displayRow() const;
// 设置grid最小的宽度,默认为0(不限制),可以限定grid的宽度
void setGridMinimumWidth(int w);
int gridMinimumWidth() const;
// 设置grid最大的宽度,默认为0(不限制),可以限定grid的宽度
void setGridMaximumWidth(int w);
int gridMaximumWidth() const;
// 获取SARibbonGalleryGroup管理的actiongroup
QActionGroup* actionGroup() const;
private Q_SLOTS:
void onItemClicked(const QModelIndex& index);
void onItemEntered(const QModelIndex& index);
Q_SIGNALS:
void groupTitleChanged(const QString& title);
/**
* @brief 等同QActionGroup的triggered
* 所有加入SARibbonGalleryGroup的action都会被一个QActionGroup管理,可以通过@sa getActionGroup 获取到对应的actiongroup
* @param action
*/
void triggered(QAction* action);
/**
* @brief 等同QActionGroup的triggered
* 所有加入SARibbonGalleryGroup的action都会被一个QActionGroup管理,可以通过@sa getActionGroup 获取到对应的actiongroup
* @note 此属性需要通过QAbstractItemView::entered(const QModelIndex &index)激活,因此要保证设置了setMouseTracking(true)
* @param action
*/
void hovered(QAction* action);
};
#endif // SARIBBONGALLERYGROUP_H
@@ -0,0 +1,163 @@
#include "SARibbonGalleryItem.h"
#include "SARibbonGalleryGroup.h"
SARibbonGalleryItem::SARibbonGalleryItem() : mFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable), mAction(nullptr)
{
}
SARibbonGalleryItem::SARibbonGalleryItem(const QString& text, const QIcon& icon)
: mFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable), mAction(nullptr)
{
setText(text);
setIcon(icon);
setTextAlignment(Qt::AlignTop | Qt::AlignHCenter);
}
SARibbonGalleryItem::SARibbonGalleryItem(QAction* act) : mFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable)
{
setTextAlignment(Qt::AlignTop | Qt::AlignHCenter);
setAction(act);
}
SARibbonGalleryItem::~SARibbonGalleryItem()
{
}
void SARibbonGalleryItem::setData(int role, const QVariant& data)
{
mDatas[ role ] = data;
}
QVariant SARibbonGalleryItem::data(int role) const
{
if (mAction) {
switch (role) {
case Qt::DisplayRole:
return (mAction->text());
case Qt::ToolTipRole:
return (mAction->toolTip());
case Qt::DecorationRole:
return (mAction->icon());
default:
break;
}
}
return (mDatas.value(role));
}
void SARibbonGalleryItem::setText(const QString& text)
{
setData(Qt::DisplayRole, text);
}
QString SARibbonGalleryItem::text() const
{
if (mAction) {
return (mAction->text());
}
return (data(Qt::DisplayRole).toString());
}
void SARibbonGalleryItem::setToolTip(const QString& text)
{
setData(Qt::ToolTipRole, text);
}
QString SARibbonGalleryItem::toolTip() const
{
if (mAction) {
return (mAction->toolTip());
}
return (data(Qt::ToolTipRole).toString());
}
void SARibbonGalleryItem::setIcon(const QIcon& ico)
{
setData(Qt::DecorationRole, ico);
}
QIcon SARibbonGalleryItem::icon() const
{
if (mAction) {
return (mAction->icon());
}
return (qvariant_cast< QIcon >(data(Qt::DecorationRole)));
}
bool SARibbonGalleryItem::isSelectable() const
{
return (mFlags & Qt::ItemIsSelectable);
}
void SARibbonGalleryItem::setSelectable(bool isSelectable)
{
if (isSelectable) {
mFlags |= Qt::ItemIsSelectable;
} else {
mFlags = (mFlags & (~Qt::ItemIsSelectable));
}
}
bool SARibbonGalleryItem::isEnable() const
{
if (mAction) {
return (mAction->isEnabled());
}
return (mFlags & Qt::ItemIsEnabled);
}
void SARibbonGalleryItem::setEnable(bool isEnable)
{
if (mAction) {
mAction->setEnabled(isEnable);
}
if (isEnable) {
mFlags |= Qt::ItemIsEnabled;
} else {
mFlags = (mFlags & (~Qt::ItemIsEnabled));
}
}
void SARibbonGalleryItem::setFlags(Qt::ItemFlags flag)
{
mFlags = flag;
if (mAction) {
mAction->setEnabled(flag & Qt::ItemIsEnabled);
}
}
Qt::ItemFlags SARibbonGalleryItem::flags() const
{
return (mFlags);
}
void SARibbonGalleryItem::setAction(QAction* act)
{
mAction = act;
if (nullptr == mAction) {
return;
}
if (act->isEnabled()) {
mFlags |= Qt::ItemIsEnabled;
} else {
mFlags = (mFlags & (~Qt::ItemIsEnabled));
}
}
QAction* SARibbonGalleryItem::action()
{
return (mAction);
}
void SARibbonGalleryItem::setTextAlignment(Qt::Alignment a)
{
setData(Qt::TextAlignmentRole, (int)a);
}
Qt::Alignment SARibbonGalleryItem::textAlignment() const
{
return qvariant_cast< Qt::Alignment >(data(Qt::TextAlignmentRole));
}
@@ -0,0 +1,64 @@
#ifndef SARIBBONGALLERYITEM_H
#define SARIBBONGALLERYITEM_H
#include "SARibbonGlobal.h"
#include <QIcon>
#include <QVariant>
#include <QMap>
#include <QAction>
class SARibbonGalleryGroup;
///
/// \brief 类似QStandardItem的GalleryItem
///
class SA_RIBBON_EXPORT SARibbonGalleryItem
{
friend class SARibbonGalleryGroupModel;
public:
explicit SARibbonGalleryItem();
explicit SARibbonGalleryItem(const QString& text, const QIcon& icon);
explicit SARibbonGalleryItem(QAction* act);
virtual ~SARibbonGalleryItem();
// 设置角色
void setData(int role, const QVariant& data);
virtual QVariant data(int role) const;
// 设置文字描述
void setText(const QString& text);
QString text() const;
// 设置tooltip
void setToolTip(const QString& text);
QString toolTip() const;
// 设置图标
void setIcon(const QIcon& ico);
QIcon icon() const;
// 设置是否可见
bool isSelectable() const;
void setSelectable(bool isSelectable);
// 设置是否可选
bool isEnable() const;
void setEnable(bool isEnable);
// 设置item的flag
void setFlags(Qt::ItemFlags flag);
virtual Qt::ItemFlags flags() const;
// 设置action
void setAction(QAction* act);
QAction* action();
// 文字对齐方式
void setTextAlignment(Qt::Alignment a);
Qt::Alignment textAlignment() const;
private:
QMap< int, QVariant > mDatas;
Qt::ItemFlags mFlags;
QAction* mAction;
};
#endif // SARIBBONGALLERYITEM_H
@@ -0,0 +1,149 @@
#ifndef SARIBBONGLOBAL_H
#define SARIBBONGLOBAL_H
#include <memory>
#include <QtGlobal>
#include <QObject>
#include "SARibbonBarVersionInfo.h"
class QWidget;
#ifndef SA_RIBBON_BAR_NO_EXPORT
#if defined(SA_RIBBON_BAR_MAKE_LIB) // 定义此宏将构建library
#define SA_RIBBON_EXPORT Q_DECL_EXPORT
#else
#define SA_RIBBON_EXPORT Q_DECL_IMPORT
#endif
#endif
#ifndef SA_RIBBON_EXPORT
#define SA_RIBBON_EXPORT
#endif
/**
* @def 模仿Q_DECLARE_PRIVATE,但不用前置声明而是作为一个内部类
*/
#ifndef SA_RIBBON_DECLARE_PRIVATE
#define SA_RIBBON_DECLARE_PRIVATE(classname) \
class PrivateData; \
friend class classname::PrivateData; \
std::unique_ptr< PrivateData > d_ptr;
#endif
/**
* @def 模仿Q_DECLARE_PUBLIC
*/
#ifndef SA_RIBBON_DECLARE_PUBLIC
#define SA_RIBBON_DECLARE_PUBLIC(classname) \
friend class classname; \
classname* q_ptr { nullptr }; \
PrivateData(const PrivateData&) = delete; \
PrivateData& operator=(const PrivateData&) = delete;
#endif
/**
* @brief 定义Ribbon的对其方式,目前支持左对齐和居中对其
*
* @note 如果你编译器提示:成员声明的限定名称非法,那么留意一下文件换行是否为LF,如果是把文件换行改为CRLF
*/
enum class SARibbonAlignment
{
AlignLeft, ///< 左对齐,tab栏左对齐,同时category也是左对齐
AlignCenter ///< 居中对其,tab栏居中对齐,同时category也是居中对齐
};
/**
* @brief ribbon主题
*
* 注意,由于有些qss的尺寸,在C++代码中无法获取到,因此针对用户自定义的qss主题,有些尺寸是需要手动设置进去的
*
* 例如ribbon tab的margin信息,在QTabBar是无法获取到,而这个影响了SARibbonContextCategory的绘制,
* 因此,在设置qss后需要针对margin信息重新设置进SARibbonTabBar中
*/
enum class SARibbonTheme
{
RibbonThemeOffice2013, ///< office2013主题
RibbonThemeOffice2016Blue, ///< office2016-蓝色主题
RibbonThemeOffice2021Blue, ///< office2021-蓝色主题
RibbonThemeWindows7, ///< win7主题
RibbonThemeDark, ///< 暗色主题
RibbonThemeDark2
};
/**
* @def 属性,用于标记是否可以进行自定义,用于动态设置到@ref SARibbonCategory 和@ref SARibbonPannel
* 值为bool,在为true时,可以通过@ref SARibbonCustomizeWidget 改变这个SARibbonCategory和SARibbonPannel的布局,
* 默认不会有此属性,仅在有此属性且为true时才会在SARibbonCustomizeWidget中能显示为可设置
*/
#ifndef SA_RIBBON_BAR_PROP_CAN_CUSTOMIZE
#define SA_RIBBON_BAR_PROP_CAN_CUSTOMIZE "_sa_isCanCustomize"
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
#ifndef SA_FONTMETRICS_WIDTH
#define SA_FONTMETRICS_WIDTH(fm, str) fm.horizontalAdvance(str)
#endif
#else
#ifndef SA_FONTMETRICS_WIDTH
#define SA_FONTMETRICS_WIDTH(fm, str) fm.width(str)
#endif
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#ifndef SA_MOUSEEVENT_GLOBALPOS_POINT
#define SA_MOUSEEVENT_GLOBALPOS_POINT(MouseEventPtr) MouseEventPtr->globalPosition().toPoint()
#endif
#else
#ifndef SA_MOUSEEVENT_GLOBALPOS_POINT
#define SA_MOUSEEVENT_GLOBALPOS_POINT(MouseEventPtr) MouseEventPtr->globalPos()
#endif
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#ifndef SA_HOVEREVENT_POS_POINT
#define SA_HOVEREVENT_POS_POINT(HoverEventPtr) HoverEventPtr->position().toPoint()
#endif
#else
#ifndef SA_HOVEREVENT_POS_POINT
#define SA_HOVEREVENT_POS_POINT(HoverEventPtr) HoverEventPtr->pos()
#endif
#endif
/**
@def 定义此宏,将打印和尺寸刷新相关的信息
仅用于调试
*/
#ifndef SA_DEBUG_PRINT_SIZE_HINT
#define SA_DEBUG_PRINT_SIZE_HINT 0
#endif
/**
@def 定义此宏,将打印事件
仅用于调试
*/
#ifndef SA_DEBUG_PRINT_EVENT
#define SA_DEBUG_PRINT_EVENT 0
#endif
/**
@def 定义此宏,qDebug将支持SARibbonBar的属性打印
仅用于调试
*/
#ifndef SA_DEBUG_PRINT_SARIBBONBAR
#define SA_DEBUG_PRINT_SARIBBONBAR 0
#endif
/**
* @brief 全局的设置ribbon theme函数
*
* 之所以提供此函数,是因为在某些情况下,SARibbonBar不用在SARibbonMainWindow情况下的时候,也需要设置主题,
* 但主题设置是在SARibbonMainWindow下的为了能在非SARibbonMainWindow下也能设置主题,这里提供@sa sa_set_ribbon_theme函数,
* 可以这样使用:
* @code
* @endcode
*
* @param w
* @param theme
*/
void SA_RIBBON_EXPORT sa_set_ribbon_theme(QWidget* w, SARibbonTheme theme);
#endif // SARIBBONGLOBAL_H
@@ -0,0 +1,17 @@
#include "SARibbonLineEdit.h"
#include <QStyleOption>
SARibbonLineEdit::SARibbonLineEdit(QWidget *parent)
: QLineEdit(parent)
{
}
SARibbonLineEdit::SARibbonLineEdit(const QString &text, QWidget *parent)
: QLineEdit(text, parent)
{
}
SARibbonLineEdit::~SARibbonLineEdit()
{
}
@@ -0,0 +1,19 @@
#ifndef SARIBBONLINEEDIT_H
#define SARIBBONLINEEDIT_H
#include "SARibbonGlobal.h"
#include <QLineEdit>
/**
* @brief The SARibbonLineEdit class
*/
class SA_RIBBON_EXPORT SARibbonLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit SARibbonLineEdit(QWidget *parent = nullptr);
explicit SARibbonLineEdit(const QString &text, QWidget *parent = nullptr);
~SARibbonLineEdit();
};
#endif // SARIBBONLINEEDIT_H
@@ -0,0 +1,57 @@
#include "SARibbonLineWidgetContainer.h"
#include <QHBoxLayout>
SARibbonLineWidgetContainer::SARibbonLineWidgetContainer(QWidget *par) : QWidget(par)
, m_innerWidget(nullptr)
{
m_labelPrefix = new QLabel(this);
m_labelSuffix = new QLabel(this);
QHBoxLayout *lay = new QHBoxLayout();
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(0);
lay->addWidget(m_labelPrefix);
lay->addWidget(m_labelSuffix);
setLayout(lay);
}
SARibbonLineWidgetContainer::~SARibbonLineWidgetContainer()
{
}
void SARibbonLineWidgetContainer::setWidget(QWidget *innerWidget)
{
QHBoxLayout *lay = static_cast<QHBoxLayout *>(layout());
if (m_innerWidget) {
lay->replaceWidget(m_innerWidget, innerWidget);
}else{
lay->insertWidget(1, innerWidget);
}
m_innerWidget = innerWidget;
}
void SARibbonLineWidgetContainer::setPrefix(const QString& str)
{
m_labelPrefix->setText(str);
}
void SARibbonLineWidgetContainer::setSuffix(const QString& str)
{
m_labelSuffix->setText(str);
}
QLabel *SARibbonLineWidgetContainer::labelPrefix() const
{
return (m_labelPrefix);
}
QLabel *SARibbonLineWidgetContainer::labelSuffix() const
{
return (m_labelSuffix);
}
@@ -0,0 +1,44 @@
#ifndef SARIBBONLINEWIDGETCONTAINER_H
#define SARIBBONLINEWIDGETCONTAINER_H
#include <QtCore/qglobal.h>
#include <QWidget>
#include <QLabel>
#include "SARibbonGlobal.h"
/**
* @brief 一个窗口容器,把窗口放置中间,前面后面都可以设置文本,主要用于放置在pannel上的小窗口
*
* 实现如下效果:
*
* PrefixLabel|_Widget_|SuffixLabel
*
*/
class SA_RIBBON_EXPORT SARibbonLineWidgetContainer : public QWidget
{
public:
explicit SARibbonLineWidgetContainer(QWidget* par = nullptr);
~SARibbonLineWidgetContainer();
//设置widget,不允许设置一个nullptr
void setWidget(QWidget* innerWidget);
//设置前缀
void setPrefix(const QString& str);
//设置后缀
void setSuffix(const QString& str);
//前缀文本框
QLabel* labelPrefix() const;
//后缀文本框
QLabel* labelSuffix() const;
private:
//两个文本
QLabel* m_labelPrefix;
QLabel* m_labelSuffix;
QWidget* m_innerWidget;
};
#endif // SARIBBONWIDGETCONTAINER_H
@@ -0,0 +1,382 @@
#include "SARibbonMainWindow.h"
#include "SARibbonBar.h"
#include "SARibbonElementManager.h"
#include "SARibbonTabBar.h"
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QHash>
#include <QWindowStateChangeEvent>
#include <QScreen>
#include "SARibbonSystemButtonBar.h"
#if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
#include <QWKWidgets/widgetwindowagent.h>
#include "SARibbonButtonGroupWidget.h"
#include "SARibbonQuickAccessBar.h"
#include "SARibbonStackedWidget.h"
#else
#include "SAFramelessHelper.h"
#endif
/**
* @brief The SARibbonMainWindowPrivate class
*/
class SARibbonMainWindow::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonMainWindow)
public:
PrivateData(SARibbonMainWindow* p);
void installFrameless(SARibbonMainWindow* p);
public:
SARibbonTheme mCurrentRibbonTheme { SARibbonTheme::RibbonThemeOffice2021Blue };
SARibbonSystemButtonBar* mWindowButtonGroup { nullptr };
#if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
QWK::WidgetWindowAgent* mFramelessHelper { nullptr };
#else
SAFramelessHelper* mFramelessHelper { nullptr };
#endif
SARibbonMainWindowEventFilter* mEventFilter { nullptr };
};
SARibbonMainWindow::PrivateData::PrivateData(SARibbonMainWindow* p) : q_ptr(p)
{
}
void SARibbonMainWindow::PrivateData::installFrameless(SARibbonMainWindow* p)
{
#if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
mFramelessHelper = new QWK::WidgetWindowAgent(p);
mFramelessHelper->setup(p);
#else
mFramelessHelper = new SAFramelessHelper(p);
#endif
}
//===================================================
// SARibbonMainWindow
//===================================================
SARibbonMainWindow::SARibbonMainWindow(QWidget* parent, bool useRibbon, const Qt::WindowFlags flags)
: QMainWindow(parent, flags), d_ptr(new SARibbonMainWindow::PrivateData(this))
{
connect(qApp, &QApplication::primaryScreenChanged, this, &SARibbonMainWindow::onPrimaryScreenChanged);
if (useRibbon) {
d_ptr->installFrameless(this);
setRibbonBar(createRibbonBar());
setRibbonTheme(ribbonTheme());
}
}
SARibbonMainWindow::~SARibbonMainWindow()
{
}
/**
* @brief 返回ribbonbar,如果不是使用ribbon模式,返回nullptr
* @return
*/
SARibbonBar* SARibbonMainWindow::ribbonBar() const
{
return qobject_cast< SARibbonBar* >(menuWidget());
}
/**
* @brief 设置ribbonbar
* @param bar
*/
void SARibbonMainWindow::setRibbonBar(SARibbonBar* ribbon)
{
QWidget* old = QMainWindow::menuWidget();
if (old) {
// 如果之前已经设置了menubar,要把之前的删除
old->deleteLater();
}
QMainWindow::setMenuWidget(ribbon);
const int th = ribbon->titleBarHeight();
// 设置window按钮
if (nullptr == d_ptr->mWindowButtonGroup) {
d_ptr->mWindowButtonGroup = RibbonSubElementFactory->createWindowButtonGroup(this);
// SARibbonSystemButtonBar的eventfilter捕获mainwindow的事件
// 通过eventerfilter来处理mainwindow的事件,避免用户错误的继承resizeEvent导致systembar的位置异常
installEventFilter(d_ptr->mWindowButtonGroup);
}
SARibbonSystemButtonBar* sysBar = d_ptr->mWindowButtonGroup;
sysBar->setWindowStates(windowState());
sysBar->setWindowTitleHeight(th);
sysBar->raise(); // 确保sysbar在最顶层,避免第二次设置ribbonbar的时候,被ribbonbar覆盖了sysbar
sysBar->show();
#if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
auto helper = d_ptr->mFramelessHelper;
helper->setTitleBar(ribbon);
// 以下这些窗口,需要允许点击
helper->setHitTestVisible(sysBar); // IMPORTANT!
helper->setHitTestVisible(ribbon->ribbonTabBar()); // IMPORTANT!
helper->setHitTestVisible(ribbon->rightButtonGroup()); // IMPORTANT!
helper->setHitTestVisible(ribbon->applicationButton()); // IMPORTANT!
helper->setHitTestVisible(ribbon->quickAccessBar()); // IMPORTANT!
helper->setHitTestVisible(ribbon->ribbonStackedWidget()); // IMPORTANT!
#if SARIBBON_ENABLE_SNAP_LAYOUT
if (sysBar->closeButton()) {
helper->setSystemButton(QWK::WindowAgentBase::Close, sysBar->closeButton());
}
if (sysBar->minimizeButton()) {
helper->setSystemButton(QWK::WindowAgentBase::Minimize, sysBar->minimizeButton());
}
if (sysBar->maximizeButton()) {
helper->setSystemButton(QWK::WindowAgentBase::Maximize, sysBar->maximizeButton());
}
#endif
#else
// 捕获ribbonbar的事件
ribbon->installEventFilter(this);
// 设置窗体的标题栏高度
d_ptr->mFramelessHelper->setTitleHeight(th);
d_ptr->mFramelessHelper->setRubberBandOnResize(false);
#endif
if (!d_ptr->mEventFilter) {
d_ptr->mEventFilter = new SARibbonMainWindowEventFilter(this);
installEventFilter(d_ptr->mEventFilter);
}
// 最后要提升,否则新加入的会被覆盖
if (d_ptr->mWindowButtonGroup) {
d_ptr->mWindowButtonGroup->raise();
}
}
#if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
/**
* @brief 如果ribbon中有自定义的窗口在标题栏等非点击区域加入后,想能点击,需要调用此接口告知可点击
* @param w
* @param visible
*/
void SARibbonMainWindow::setFramelessHitTestVisible(QWidget* w, bool visible)
{
auto helper = d_ptr->mFramelessHelper;
helper->setHitTestVisible(const_cast<QWidget*>(w), visible);
}
#else
SAFramelessHelper* SARibbonMainWindow::framelessHelper()
{
return (d_ptr->mFramelessHelper);
}
#endif
bool SARibbonMainWindow::eventFilter(QObject* obj, QEvent* e)
{
#if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
#else
// 这个过滤是为了把ribbonBar上的动作传递到mainwindow,再传递到frameless
// 由于ribbonbar会遮挡掉frameless的区域,导致frameless无法捕获这些消息
// 因此必须ribbonBar()->installEventFilter(this);
// 20240101发现installEventFilter后,SARibbonMainWindow没有执行这个回调
if (obj == ribbonBar()) {
switch (e->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
case QEvent::Leave:
case QEvent::HoverMove:
case QEvent::MouseButtonDblClick:
QApplication::sendEvent(this, e);
default:
break;
}
}
#endif
return (QMainWindow::eventFilter(obj, e));
}
/**
* @brief 获取系统按钮组,可以在此基础上添加其他按钮
* @return
*/
SARibbonSystemButtonBar* SARibbonMainWindow::windowButtonBar() const
{
return d_ptr->mWindowButtonGroup;
}
/**
* @brief 此函数仅用于控制最小最大化和关闭按钮的显示
*/
void SARibbonMainWindow::updateWindowFlag(Qt::WindowFlags flags)
{
if (SARibbonSystemButtonBar* g = d_ptr->mWindowButtonGroup) {
g->updateWindowFlag(flags);
}
}
/**
* @brief SARibbonMainWindow::setRibbonTheme
*
* 注意主题在构造函数设置主题会不完全生效,使用QTimer投放到队列最后执行即可
* @code
* QTimer::singleShot(0, this, [ this ]() { this->setRibbonTheme(SARibbonMainWindow::RibbonThemeDark); });
* @endcode
* @param theme
*/
void SARibbonMainWindow::setRibbonTheme(SARibbonTheme theme)
{
sa_set_ribbon_theme(this, theme);
d_ptr->mCurrentRibbonTheme = theme;
if (SARibbonBar* bar = ribbonBar()) {
auto theme = ribbonTheme();
// 尺寸修正
switch (theme) {
case SARibbonTheme::RibbonThemeWindows7:
case SARibbonTheme::RibbonThemeOffice2013:
case SARibbonTheme::RibbonThemeOffice2016Blue:
case SARibbonTheme::RibbonThemeDark:
case SARibbonTheme::RibbonThemeDark2: {
//! 在设置qss后需要针对margin信息重新设置进SARibbonTabBar中
//! office2013.qss的margin信息如下设置
//! margin-top: 0px;
//! margin-right: 0px;
//! margin-left: 5px;
//! margin-bottom: 0px;
SARibbonTabBar* tab = bar->ribbonTabBar();
if (!tab) {
break;
}
tab->setTabMargin(QMargins(5, 0, 0, 0));
} break;
case SARibbonTheme::RibbonThemeOffice2021Blue: {
SARibbonTabBar* tab = bar->ribbonTabBar();
if (!tab) {
break;
}
//! 在设置qss后需要针对margin信息重新设置进SARibbonTabBar中
//! office2021.qss的margin信息如下设置
//! margin-top: 0px;
//! margin-right: 5px;
//! margin-left: 5px;
//! margin-bottom: 0px;
tab->setTabMargin(QMargins(5, 0, 5, 0));
}
default:
break;
}
// 上下文标签颜色设置,以及基线颜色设置
switch (theme) {
case SARibbonTheme::RibbonThemeWindows7:
case SARibbonTheme::RibbonThemeOffice2013:
case SARibbonTheme::RibbonThemeDark:
bar->setContextCategoryColorList(QList< QColor >()); //< 设置空颜色列表会重置为默认色系
break;
case SARibbonTheme::RibbonThemeOffice2016Blue:
bar->setContextCategoryColorList(QList< QColor >() << QColor(18, 64, 120)); //< 设置空颜色列表会重置为默认色系
break;
case SARibbonTheme::RibbonThemeOffice2021Blue:
bar->setContextCategoryColorList(QList< QColor >() << QColor(209, 207, 209)); //< 设置空颜色列表会重置为默认色系
break;
default:
break;
}
// 基线颜色设置
if (SARibbonTheme::RibbonThemeOffice2013 == theme) {
bar->setTabBarBaseLineColor(QColor(186, 201, 219));
} else {
bar->setTabBarBaseLineColor(QColor());
}
}
}
SARibbonTheme SARibbonMainWindow::ribbonTheme() const
{
return (d_ptr->mCurrentRibbonTheme);
}
bool SARibbonMainWindow::isUseRibbon() const
{
return (nullptr != ribbonBar());
}
/**
* @brief 创建ribbonbar的工厂函数
*
* 用户如果重写了SARibbonBar,可以通过重新此虚函数返回自己的Ribbon实例
* @return
*/
SARibbonBar* SARibbonMainWindow::createRibbonBar()
{
SARibbonBar* bar = RibbonSubElementFactory->createRibbonBar(this);
bar->setContentsMargins(3, 0, 3, 0);
return bar;
}
/**
* @brief 主屏幕切换触发的信号
* @param screen
*/
void SARibbonMainWindow::onPrimaryScreenChanged(QScreen* screen)
{
Q_UNUSED(screen);
// 主屏幕切换后,从新计算所有尺寸
if (SARibbonBar* bar = ribbonBar()) {
qDebug() << "Primary Screen Changed";
bar->updateRibbonGeometry();
}
}
void sa_set_ribbon_theme(QWidget* w, SARibbonTheme theme)
{
QFile file;
switch (theme) {
case SARibbonTheme::RibbonThemeWindows7:
file.setFileName(":/theme/resource/theme-win7.qss");
break;
case SARibbonTheme::RibbonThemeOffice2013:
file.setFileName(":/theme/resource/theme-office2013.qss");
break;
case SARibbonTheme::RibbonThemeOffice2016Blue:
file.setFileName(":/theme/resource/theme-office2016-blue.qss");
break;
case SARibbonTheme::RibbonThemeOffice2021Blue:
file.setFileName(":/theme/resource/theme-office2021-blue.qss");
break;
case SARibbonTheme::RibbonThemeDark:
file.setFileName(":/theme/resource/theme-dark.qss");
break;
case SARibbonTheme::RibbonThemeDark2:
file.setFileName(":/theme/resource/theme-dark2.qss");
break;
default:
file.setFileName(":/theme/resource/theme-office2013.qss");
break;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
// 有反馈用qstring接住文件内容,再设置进去才能生效(qt5.7版本)
QString qss = QString::fromUtf8(file.readAll());
w->setStyleSheet(qss);
}
//----------------------------------------------------
// SARibbonMainWindowEventFilter
//----------------------------------------------------
SARibbonMainWindowEventFilter::SARibbonMainWindowEventFilter(QObject* par) : QObject(par)
{
}
SARibbonMainWindowEventFilter::~SARibbonMainWindowEventFilter()
{
}
bool SARibbonMainWindowEventFilter::eventFilter(QObject* obj, QEvent* e)
{
if (e) {
if (e->type() == QEvent::Resize) {
if (SARibbonMainWindow* m = qobject_cast< SARibbonMainWindow* >(obj)) {
if (SARibbonBar* ribbon = m->ribbonBar()) {
ribbon->setFixedWidth(m->size().width());
}
}
}
}
return QObject::eventFilter(obj, e);
}
@@ -0,0 +1,114 @@
#ifndef SARIBBONMAINWINDOW_H
#define SARIBBONMAINWINDOW_H
#include "SARibbonGlobal.h"
#include <QMainWindow>
#if !SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
class SAFramelessHelper;
#endif
class QAction;
class SARibbonBar;
class SARibbonSystemButtonBar;
class QScreen;
/**
* @brief 如果要使用SARibbonBar,必须使用此类代替QMainWindow
*
* 由于ribbon的风格和传统的Toolbar风格差异较大,
* SARibbonBar使用需要把原有的QMainWindow替换为SARibbonMainWindow,
* SARibbonMainWindow是个无边框窗体,继承自QMainWindow(目前使用第三方的无边框方案https://github.com/wangwenx190/framelesshelper),
* 其构造函数的参数useRibbon用于指定是否使用ribbon风格,默认为true
*
* @code
* SARibbonMainWindow(QWidget* parent = nullptr,bool useRibbon = true);
* @endcode
*
* 如果想换回非ribbon风格,只需要把useRibbon设置为false即可,
* 成员函数isUseRibbon用于判断当前是否为ribbon模式,这个函数在兼容传统Toolbar风格和ribbon风格时非常有用
*
* 但这个不支持动态切换,因此,实际工程中,你若要进行切换,需要写配置文件,程序在构造时传入模式,
* 并且,你的程序要做好判断,因为非Ribbon模式下,所有Ribbon相关的接口都会返回空指针
*
* @code
* bool isUseRibbon() const;
* @endcode
*
* @ref SARibbonMainWindow 提供了几种常用的ribbon样式
* 通过@ref setRibbonTheme 可改变ribbon的样式,用户也可通过qss自己定义自己的样式
*
* 如果你自己继承了SARibbonBar,你可以通过@ref setRibbonBar 设置自己的ribbonbar进去
*
* 另外有个一个更加高效的方法,来实现自定义的Ribbon,就是继承一个@ref SARibbonElementFactory
*
* @code
* class MyRibbonFactory:public SARibbonElementFactory{
* ...
* virtual SARibbonBar* createRibbonBar(QWidget* parent){
* return new MyRibbonBar(parent);
* }
* };
* @endcode
*
* SARibbonMainWindow生成之前(一般在main函数),设置元件工厂:
*
* @code
* SARibbonElementManager::instance()->setupFactory(new MyRibbonFactory());
* @endcode
*
* 此时,Ribbon的元素会通过MyRibbonFactory的接口来生成
*/
class SA_RIBBON_EXPORT SARibbonMainWindow : public QMainWindow
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonMainWindow)
friend class SARibbonBar;
Q_PROPERTY(SARibbonTheme ribbonTheme READ ribbonTheme WRITE setRibbonTheme)
public:
explicit SARibbonMainWindow(QWidget* parent = nullptr, bool useRibbon = true, const Qt::WindowFlags flags = {});
~SARibbonMainWindow() override;
// 返回SARibbonBar
SARibbonBar* ribbonBar() const;
// 设置ribbonbar
void setRibbonBar(SARibbonBar* ribbon);
#if !SARIBBON_USE_3RDPARTY_FRAMELESSHELPER
// 返回SAFramelessHelper
SAFramelessHelper* framelessHelper();
#else
// 如果ribbon中有自定义的窗口在标题栏等非点击区域加入后,想能点击,需要调用此接口告知可点击
void setFramelessHitTestVisible(QWidget* w, bool visible = true);
#endif
// 此函数仅用于控制最小最大化和关闭按钮的显示
void updateWindowFlag(Qt::WindowFlags flags);
// 注意主题在构造函数设置主题会不完全生效,使用QTimer投放到队列最后执行即可
// QTimer::singleShot(0, this, [ this ]() { this->setRibbonTheme(SARibbonMainWindow::RibbonThemeDark); });
void setRibbonTheme(SARibbonTheme theme);
SARibbonTheme ribbonTheme() const;
// 判断当前是否使用ribbon模式
bool isUseRibbon() const;
// 把ribbonbar的事件传递到frameless
virtual bool eventFilter(QObject* obj, QEvent* e) Q_DECL_OVERRIDE;
// 获取最大化,最小化,关闭按钮所在的bar。可以通过此函数在最大最小化按钮旁边设置内容
SARibbonSystemButtonBar* windowButtonBar() const;
protected:
// 创建ribbonbar的工厂函数
SARibbonBar* createRibbonBar();
private Q_SLOTS:
void onPrimaryScreenChanged(QScreen* screen);
};
/**
* @brief 针对SARibbonMainWindow的事件处理器,主要处理systembar的位置调整
*/
class SA_RIBBON_EXPORT SARibbonMainWindowEventFilter : public QObject
{
Q_OBJECT
public:
explicit SARibbonMainWindowEventFilter(QObject* par);
~SARibbonMainWindowEventFilter();
virtual bool eventFilter(QObject* obj, QEvent* e) override;
};
#endif // SARIBBONMAINWINDOW_H
@@ -0,0 +1,50 @@
#include "SARibbonMenu.h"
#include <QWidgetAction>
SARibbonMenu::SARibbonMenu(QWidget *parent) : QMenu(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
SARibbonMenu::SARibbonMenu(const QString& title, QWidget *parent) : QMenu(title, parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
SARibbonMenu::~SARibbonMenu()
{
}
QAction *SARibbonMenu::addRibbonMenu(SARibbonMenu *menu)
{
return (QMenu::addMenu(menu));
}
SARibbonMenu *SARibbonMenu::addRibbonMenu(const QString& title)
{
SARibbonMenu *menu = new SARibbonMenu(title, this);
return (menu);
}
SARibbonMenu *SARibbonMenu::addRibbonMenu(const QIcon& icon, const QString& title)
{
SARibbonMenu *menu = new SARibbonMenu(title, this);
menu->setIcon(icon);
return (menu);
}
QAction *SARibbonMenu::addWidget(QWidget *w)
{
QWidgetAction *action = new QWidgetAction(this);
action->setDefaultWidget(w);
addAction(action);
return (action);
}
@@ -0,0 +1,23 @@
#ifndef SARIBBONMENU_H
#define SARIBBONMENU_H
#include "SARibbonGlobal.h"
#include <QMenu>
///
/// \brief 用在ribbon的menu
/// 可以通过StyleSheet设置样式而不影响QMenu
///
class SA_RIBBON_EXPORT SARibbonMenu : public QMenu
{
Q_OBJECT
public:
explicit SARibbonMenu(QWidget* parent = nullptr);
explicit SARibbonMenu(const QString& title, QWidget* parent = nullptr);
~SARibbonMenu();
QAction* addRibbonMenu(SARibbonMenu* menu);
SARibbonMenu* addRibbonMenu(const QString& title);
SARibbonMenu* addRibbonMenu(const QIcon& icon, const QString& title);
QAction* addWidget(QWidget* w);
};
#endif // SARIBBONMENU_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,237 @@
#ifndef SARIBBONPANNEL_H
#define SARIBBONPANNEL_H
#include "SARibbonGlobal.h"
#include "SARibbonPannelItem.h"
#include "SARibbonPannelLayout.h"
#include "SARibbonToolButton.h"
#include <QLayout>
#include <QWidget>
#include <QLabel>
class SARibbonMenu;
class SARibbonGallery;
class QGridLayout;
class SARibbonPannelOptionButton;
class SARibbonPannelLayout;
class SARibbonCategory;
class SARibbonBar;
/**
* @brief SARibbonPannel的标题label,此类用于qss
*/
class SA_RIBBON_EXPORT SARibbonPannelLabel : public QLabel
{
Q_OBJECT
public:
SARibbonPannelLabel(QWidget* parent = nullptr);
};
/**
* @brief pannel页窗口,pannel是ribbon的面板用于承放控件
*
* ribbon的pannel分为两行模式和三行模式,以office为代表的ribbon为3行模式,以WPS为代表的“紧凑派”就是2行模式,
* SARibbon可通过SARibbonBar的@ref SARibbonBar::RibbonStyle 来指定模式(通过函数@ref SARibbonBar::setRibbonStyle )
*
* 在pannel中,可以通过@ref setExpanding 函数指定pannel水平扩展,如果pannel里面没有能水平扩展的控件,将会留白,因此,
* 建议在pannel里面有水平扩展的控件如(@ref SARibbonGallery )才指定这个函数
*
* pannel的布局通过@ref SARibbonPannelLayout 来实现,如果有其他布局,可以通过继承@ref
* SARibbonElementCreateDelegate::createRibbonPannel 函数返回带有自己布局的pannel,但你必须继承对应的虚函数
*/
class SA_RIBBON_EXPORT SARibbonPannel : public QFrame
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonPannel)
friend class SARibbonCategory;
friend class SARibbonCategoryPrivate;
friend class SARibbonCustomizeWidgetPrivate;
friend class SARibbonPannelLayout;
Q_PROPERTY(bool isCanCustomize READ isCanCustomize WRITE setCanCustomize)
Q_PROPERTY(bool isExpanding READ isExpanding WRITE setExpanding)
Q_PROPERTY(QString pannelName READ pannelName WRITE setPannelName)
public:
enum PannelLayoutMode
{
ThreeRowMode, ///< 三行布局模式,office就是三行布局模式,pannel能布置3行小toolbutton,默认模式
TwoRowMode ///< 两行布局模式,wps的后续布局模式就是两行布局模式,pannel能布置2行小toolbutton
};
Q_ENUM(PannelLayoutMode)
public:
using FpRibbonToolButtonIterate = std::function< bool(SARibbonToolButton*) >;
public:
explicit SARibbonPannel(QWidget* parent = nullptr);
explicit SARibbonPannel(const QString& name, QWidget* parent = nullptr);
~SARibbonPannel() Q_DECL_OVERRIDE;
using QWidget::addAction;
// 把action加入到pannel
void addAction(QAction* action, SARibbonPannelItem::RowProportion rp);
// 生成并添加一个action
void addAction(QAction* act,
QToolButton::ToolButtonPopupMode popMode,
SARibbonPannelItem::RowProportion rp = SARibbonPannelItem::Large);
// 把action加入到pannel,并以大图标显示
void addLargeAction(QAction* action);
// 把action加入到pannel,在三行模式下会以中图标显示
void addMediumAction(QAction* action);
// 把action加入到pannel,并以小图标显示
void addSmallAction(QAction* action);
// 把action加入到pannel,并以小图标显示
void addSmallAction(QAction* action, QToolButton::ToolButtonPopupMode popMode);
// 把action加入到pannel,并以大图标显示
void addLargeAction(QAction* action, QToolButton::ToolButtonPopupMode popMode);
// 把action加入到pannel,在三行模式下会以中图标显示
void addMediumAction(QAction* action, QToolButton::ToolButtonPopupMode popMode);
QAction* addAction(const QString& text,
const QIcon& icon,
QToolButton::ToolButtonPopupMode popMode,
SARibbonPannelItem::RowProportion rp = SARibbonPannelItem::Large);
// 添加menu
void addMenu(QMenu* menu,
SARibbonPannelItem::RowProportion rp,
QToolButton::ToolButtonPopupMode popMode = QToolButton::InstantPopup);
// 添加普通大菜单
void addLargeMenu(QMenu* menu, QToolButton::ToolButtonPopupMode popMode = QToolButton::InstantPopup);
// 添加普通小按钮菜单
void addSmallMenu(QMenu* menu, QToolButton::ToolButtonPopupMode popMode = QToolButton::InstantPopup);
// 添加窗口
QAction* addWidget(QWidget* w, SARibbonPannelItem::RowProportion rp);
// 添加窗口,占用ribbon的一行
QAction* addSmallWidget(QWidget* w);
// 添加窗口,占用ribbon的一行
QAction* addMediumWidget(QWidget* w);
// 添加窗口,占用所有行
QAction* addLargeWidget(QWidget* w);
// 添加一个Gallery
SARibbonGallery* addGallery(bool expanding = true);
// 添加分割线
QAction* addSeparator();
// 从pannel中把action对应的button提取出来,如果action没有对应的button,就返回nullptr
SARibbonToolButton* actionToRibbonToolButton(QAction* action);
// 设置操作action,如果要去除,传入nullptr指针即可,SARibbonPannel不会对QAction的所有权进行管理
// OptionAction也会触发actionTriggered信号
void setOptionAction(QAction* action);
// 判断是否存在OptionAction
bool isHaveOptionAction() const;
// 获取所有的buttons
QList< SARibbonToolButton* > ribbonToolButtons() const;
// 获取PannelLayoutMode
PannelLayoutMode pannelLayoutMode() const;
void setPannelLayoutMode(PannelLayoutMode mode);
// 更新按钮的尺寸,这个函数在pannel的布局状态变换后需要调用刷新
void resetToolButtonSize();
// 判断是否为2行模式
bool isTwoRow() const
{
return (TwoRowMode == pannelLayoutMode());
}
// 把pannel设置为扩展模式,此时会撑大水平区域
void setExpanding(bool isExpanding = true);
// 是否是扩展模式
bool isExpanding() const;
// 标题栏高度
void setTitleHeight(int h);
int titleHeight() const;
// 是否显示标题,显示标题后,标题的高度需要设置,默认高度为15
bool isEnableShowTitle() const;
void setEnableShowTitle(bool on);
// action对应的布局index,此操作一般用于移动moveAction,其他意义不大
int actionIndex(QAction* act) const;
// 移动action
void moveAction(int from, int to);
// 判断是否可以自定义
bool isCanCustomize() const;
void setCanCustomize(bool b);
// 标题
QString pannelName() const;
void setPannelName(const QString& title);
// 大图标的高度
int largeButtonHeight() const;
// 获取布局对应的item,此函数目前仅仅在自定义过程中用到
const QList< SARibbonPannelItem* >& ribbonPannelItem() const;
// 获取pannel layout
SARibbonPannelLayout* pannelLayout() const;
// 更新布局
void updateItemGeometry();
// 获取category指针,如果没有parent,或者不在category管理,返回nullptr
SARibbonCategory* category() const;
// 获取ribbonBar指针,如果没有返回nullptr
SARibbonBar* ribbonBar() const;
// 设置按钮之间的间隔
void setSpacing(int n);
int spacing() const;
//
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
virtual QSize minimumSizeHint() const Q_DECL_OVERRIDE;
// 设置按钮的icon尺寸,large action不受此尺寸影响
void setToolButtonIconSize(const QSize& s);
QSize toolButtonIconSize() const;
// 此函数会遍历SARibbonPannel下的所有RibbonToolButton,执行函数指针(bool(SARibbonRibbonToolButton*)),函数指针返回false则停止迭代
bool iterate(FpRibbonToolButtonIterate fp);
// 获取最近添加的按钮
SARibbonToolButton* lastAddActionButton();
Q_SIGNALS:
/**
* @brief 等同于QToolBar::actionTriggered
* @param action
*/
void actionTriggered(QAction* action);
/**
* @brief pannel的标题发生了改变
* @param n
*/
void pannelNameChanged(const QString& n);
public:
// pannel高度推荐
static int pannelHeightHint(const QFontMetrics& fm, PannelLayoutMode layMode, int pannelTitleHeight);
// 把action的行属性设置进action中,action自身携带了行属性
static void setActionRowProportionProperty(QAction* action, SARibbonPannelItem::RowProportion rp);
// 获取action的行属性
static SARibbonPannelItem::RowProportion getActionRowProportionProperty(QAction* action);
// 把action的PopupMode属性设置进action中,action自身携带了PopupMode属性
static void setActionToolButtonPopupModeProperty(QAction* action, QToolButton::ToolButtonPopupMode popMode);
// 获取action的PopupMode属性
static QToolButton::ToolButtonPopupMode getActionToolButtonPopupModeProperty(QAction* action);
// 把action的ToolButtonStyle属性设置进action中,action自身携带了ToolButtonStyle属性
static void setActionToolButtonStyleProperty(QAction* action, Qt::ToolButtonStyle buttonStyle);
// 获取action的ToolButtonStyle属性
static Qt::ToolButtonStyle getActionToolButtonStyleProperty(QAction* action);
protected:
virtual bool event(QEvent* e) Q_DECL_OVERRIDE;
virtual void actionEvent(QActionEvent* e) Q_DECL_OVERRIDE;
virtual void changeEvent(QEvent* e) Q_DECL_OVERRIDE;
};
#endif // SARIBBONPANNEL_H
@@ -0,0 +1,15 @@
#include "SARibbonPannelItem.h"
SARibbonPannelItem::SARibbonPannelItem(QWidget* widget)
: QWidgetItem(widget), rowIndex(-1), columnIndex(-1), action(nullptr), customWidget(false), rowProportion(Large)
{
}
SARibbonPannelItem::~SARibbonPannelItem()
{
}
bool SARibbonPannelItem::isEmpty() const
{
return (action == nullptr || !action->isVisible());
}
@@ -0,0 +1,50 @@
#ifndef SARIBBONPANNELITEM_H
#define SARIBBONPANNELITEM_H
#include "SARibbonGlobal.h"
#include <QWidgetItem>
#include <QAction>
class SARibbonToolButton;
/**
* @brief 是对pannel所有子窗口的抽象,参考qt的toolbar
*
* 参考qt的toolbarpannel所有子窗口内容都通过QAction进行抽象,包括gallery这些窗口,也是通过QAction进行抽象
* QAction最终会转换为SARibbonPannelItem,每个SARibbonPannelItem都含有一个widgetSARibbonPannel的布局
* 就基于SARibbonPannelItem
*
* 无窗口的action会在内部生成一个SARibbonToolButton
*/
class SA_RIBBON_EXPORT SARibbonPannelItem : public QWidgetItem
{
public:
/**
* @brief 定义了行的占比,ribbon中有largemedia和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
};
explicit SARibbonPannelItem(QWidget* widget);
~SARibbonPannelItem();
bool isEmpty() const Q_DECL_OVERRIDE;
short rowIndex; ///< 记录当前item属于第几行,hide模式下为-1
int columnIndex; ///< 记录当前item属于第几列,hide模式下为-1
QRect itemWillSetGeometry; ///< 在调用SARibbonPannelLayout::updateGeomArray会更新这个此处,实际设置的时候会QWidgetItem::setGeometry设置Geometry
QAction* action; /// < 记录action,参考QToolBarLayoutItem
bool customWidget; ///< 对于没有窗口的action,实际也会有一个SARibbonToolButton,在销毁时要delete掉
SARibbonPannelItem::RowProportion rowProportion; ///< 行的占比,ribbon中有largemedia和small三种占比,见@ref RowProportion
};
#ifndef SA_ActionPropertyName_RowProportion
#define SA_ActionPropertyName_RowProportion "_sa_RowProportion"
#endif
#ifndef SA_ActionPropertyName_ToolButtonPopupMode
#define SA_ActionPropertyName_ToolButtonPopupMode "_sa_ToolButtonPopupMode"
#endif
#ifndef SA_ActionPropertyName_ToolButtonStyle
#define SA_ActionPropertyName_ToolButtonStyle "_sa_ToolButtonStyle"
#endif
#endif // SARIBBONPANNELITEM_H
@@ -0,0 +1,952 @@
#include "SARibbonPannelLayout.h"
#include "SARibbonPannelOptionButton.h"
#include "SARibbonSeparatorWidget.h"
#include "SARibbonElementManager.h"
#include <QWidgetAction>
#include <QQueue>
#include "SARibbonPannel.h"
#include "SARibbonPannelItem.h"
#define SARibbonPannelLayout_DEBUG_PRINT 1
#define HELP_DRAW_RECT(p, rect) \
do { \
p.save(); \
QPen _pen(Qt::DashLine); \
_pen.setColor(Qt::blue); \
p.setPen(_pen); \
p.setBrush(QBrush()); \
p.drawRect(rect); \
p.restore(); \
} while (0)
SARibbonPannelLayout::SARibbonPannelLayout(QWidget* p) : QLayout(p), mColumnCount(0), mExpandFlag(false), mDirty(true)
{
setSpacing(1);
SARibbonPannel* tb = qobject_cast< SARibbonPannel* >(p);
if (!tb) {
return;
}
}
SARibbonPannelLayout::~SARibbonPannelLayout()
{
// 参考QToolBarLayout
while (!mItems.isEmpty()) {
SARibbonPannelItem* item = mItems.takeFirst();
if (QWidgetAction* widgetAction = qobject_cast< QWidgetAction* >(item->action)) {
if (item->customWidget) {
widgetAction->releaseWidget(item->widget());
}
}
delete item;
}
}
/**
* @brief 通过action查找索引,用于actionEvent添加action用
* @param action
* @return 没有查到返回-1
*/
int SARibbonPannelLayout::indexByAction(QAction* action) const
{
for (int i = 0; i < mItems.count(); ++i) {
if (mItems.at(i)->action == action) {
return (i);
}
}
return (-1);
}
/**
* @brief 获取ribbonpannel
* @return
*/
SARibbonPannel* SARibbonPannelLayout::ribbonPannel() const
{
return qobject_cast< SARibbonPannel* >(parentWidget());
}
void SARibbonPannelLayout::addItem(QLayoutItem* item)
{
Q_UNUSED(item);
qWarning("SARibbonPannelLayout::addItem(): please use addAction() instead");
return;
}
/**
* @brief SARibbonPannel主要通过此函数来添加action
* @param act
* @param rp 布局策略
*/
void SARibbonPannelLayout::insertAction(int index, QAction* act, SARibbonPannelItem::RowProportion rp)
{
index = qMax(0, index);
index = qMin(mItems.count(), index);
SARibbonPannelItem* item = createItem(act, rp);
if (item) {
mItems.insert(index, item);
// 标记需要重新计算尺寸
invalidate();
}
}
/**
* @brief 添加操作action,如果要去除,传入nullptr指针即可,SARibbonPannel不会对QAction的所有权进行管理
* @param action
* @note 要去除OptionAction直接传入nullptr即可
* @note SARibbonPannel不对QAction的destroy进行关联,如果外部对action进行delete,需要先传入nullptr给addOptionAction
*/
void SARibbonPannelLayout::setOptionAction(QAction* action)
{
SARibbonPannel* p = ribbonPannel();
if (!p) {
return;
}
if (action) {
// 创建option action
if (nullptr == mOptionActionBtn) {
mOptionActionBtn = RibbonSubElementFactory->createRibbonPannelOptionButton(p);
QObject::connect(mOptionActionBtn, &SARibbonToolButton::triggered, p, &SARibbonPannel::actionTriggered);
// 确保m_optionActionBtn在label之上
if (mTitleLabel) {
mTitleLabel->stackUnder(mOptionActionBtn);
}
}
mOptionActionBtn->setDefaultAction(action);
if (action->icon().isNull()) {
mOptionActionBtn->setIcon(QIcon(":/image/resource/ribbonPannelOptionButton.png"));
}
// 标记需要重新计算尺寸
invalidate();
} else {
// 取消option action
if (mOptionActionBtn) {
mOptionActionBtn->hide();
mOptionActionBtn->deleteLater();
mOptionActionBtn = nullptr;
// 标记需要重新计算尺寸
invalidate();
}
}
}
/**
* @brief 判断是否存在OptionAction
* @return 存在返回true
*/
bool SARibbonPannelLayout::isHaveOptionAction() const
{
return (mOptionActionBtn != nullptr);
}
QLayoutItem* SARibbonPannelLayout::itemAt(int index) const
{
if ((index < 0) || (index >= mItems.count())) {
return (nullptr);
}
return (mItems.at(index));
}
QLayoutItem* SARibbonPannelLayout::takeAt(int index)
{
if ((index < 0) || (index >= mItems.count())) {
return (nullptr);
}
SARibbonPannelItem* item = mItems.takeAt(index);
QWidgetAction* widgetAction = qobject_cast< QWidgetAction* >(item->action);
if ((widgetAction != 0) && item->customWidget) {
widgetAction->releaseWidget(item->widget());
} else {
// destroy the QToolButton/QToolBarSeparator
item->widget()->hide();
item->widget()->deleteLater();
}
invalidate();
return (item);
}
int SARibbonPannelLayout::count() const
{
return (mItems.count());
}
bool SARibbonPannelLayout::isEmpty() const
{
return (mItems.isEmpty());
}
void SARibbonPannelLayout::invalidate()
{
mDirty = true;
QLayout::invalidate();
}
Qt::Orientations SARibbonPannelLayout::expandingDirections() const
{
return (Qt::Horizontal);
}
QSize SARibbonPannelLayout::minimumSize() const
{
return (mSizeHint);
}
QSize SARibbonPannelLayout::sizeHint() const
{
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
if (SARibbonPannel* pannel = ribbonPannel()) {
qDebug() << "| |-SARibbonPannelLayout sizeHint,sizeHint = " << m_sizeHint;
}
#endif
return (mSizeHint);
}
/**
* @brief 通过action获取SARibbonPannelItem
* @param action
* @return 如果没有返回nullptr
*/
SARibbonPannelItem* SARibbonPannelLayout::pannelItem(QAction* action) const
{
int index = indexByAction(action);
if (index >= 0) {
return (mItems[ index ]);
}
return (nullptr);
}
/**
* @brief 获取最后一个添加的item
* @return 如果没有返回nullptr
*/
SARibbonPannelItem* SARibbonPannelLayout::lastItem() const
{
if (mItems.isEmpty()) {
return (nullptr);
}
return (mItems.last());
}
/**
* @brief 获取最后生成的窗口
* @return 如果无窗口或者item为空,返回nullptr
*/
QWidget* SARibbonPannelLayout::lastWidget() const
{
SARibbonPannelItem* item = lastItem();
if (item) {
return (item->widget());
}
return (nullptr);
}
/**
* @brief 移动两个item
* @param from
* @param to
* @note 移动完后所有都失效,需要重新布局
*/
void SARibbonPannelLayout::move(int from, int to)
{
if (from == to) {
return;
}
if (to < 0) {
to = 0;
}
if (to >= count()) {
to = count() - 1;
}
mItems.move(from, to);
invalidate();
}
/**
* @brief 判断是否需要重新布局
* @return
*/
bool SARibbonPannelLayout::isDirty() const
{
return (mDirty);
}
void SARibbonPannelLayout::updateGeomArray()
{
updateGeomArray(geometry());
}
/**
* @brief 布局所有action
*/
void SARibbonPannelLayout::doLayout()
{
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
if (SARibbonPannel* pannel = ribbonPannel()) {
qDebug() << "| |-SARibbonPannelLayout layoutActions,pannel name = " << pannel->pannelName();
}
#endif
if (isDirty()) {
updateGeomArray();
}
QList< QWidget* > showWidgets, hideWidgets;
SARibbonPannel* pannel = ribbonPannel();
for (SARibbonPannelItem* item : qAsConst(mItems)) {
if (item->isEmpty()) {
hideWidgets << item->widget();
} else {
// 在category发现item->setGeometry有点奇怪的现象,这里统一使用item->widget->setgeo
// item->setGeometry(item->itemWillSetGeometry);
if (item->widget()) {
item->widget()->setGeometry(item->itemWillSetGeometry);
}
showWidgets << item->widget();
}
}
// 不在上面那里进行show和hide因为这会触发SARibbonPannelLayout的重绘,导致循环绘制,非常影响效率
for (QWidget* w : qAsConst(showWidgets)) {
if (!w->isVisible())
w->show();
}
for (QWidget* w : qAsConst(hideWidgets)) {
if (w->isVisible())
w->hide();
}
// 布局label
if (mTitleLabel) {
if (isEnableShowPannelTitle()) {
mTitleLabel->setGeometry(mTitleLabelGeometry);
if (!mTitleLabel->isVisibleTo(pannel)) {
mTitleLabel->show();
}
} else {
if (mTitleLabel->isVisibleTo(pannel)) {
mTitleLabel->hide();
}
}
}
// 布局m_optionActionBtn
if (mOptionActionBtn) {
mOptionActionBtn->setGeometry(mOptionActionBtnGeometry);
mOptionActionBtn->setIconSize(QSize(mOptionActionBtnGeometry.width(), mOptionActionBtnGeometry.height()));
}
}
/**
* @brief 把action转换为item
*
* 此函数参考QToolBarItem *QToolBarLayout::createItem(QAction *action)
*
* 对于普通QAction,此函数会创建SARibbonToolButtonSARibbonToolButton的类型参考SARibbonPannelItem::RowProportion
* @param action
* @param rp 行高占比情况
* @return 转换的SARibbonPannelItem
* @note 每个SARibbonPannelItem最终都会携带一个widget,传入的是QWidgetAction的话,会直接使用QWidgetAction带的widget
* 否则会内部生成一个SARibbonToolButton
*
*/
SARibbonPannelItem* SARibbonPannelLayout::createItem(QAction* action, SARibbonPannelItem::RowProportion rp)
{
bool customWidget = false;
QWidget* widget = nullptr;
SARibbonPannel* pannel = qobject_cast< SARibbonPannel* >(parentWidget());
if (!pannel) {
// 在没有pannel这个函数会返回nullptr
return (nullptr);
}
if (QWidgetAction* widgetAction = qobject_cast< QWidgetAction* >(action)) {
widget = widgetAction->requestWidget(pannel);
if (widget != nullptr) {
widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);
customWidget = true; // 标记为true,在移除的时候是不会对这个窗口进行删除,false默认会进行删除如SARibbonSeparatorWidget和SARibbonToolButton
}
} else if (action->isSeparator()) {
SARibbonSeparatorWidget* sep = RibbonSubElementFactory->createRibbonSeparatorWidget(pannel);
widget = sep;
}
// 不是widget,自动生成SARibbonToolbutton
if (!widget) {
SARibbonToolButton::RibbonButtonType buttonType = ((rp == SARibbonPannelItem::Large)
? SARibbonToolButton::LargeButton
: SARibbonToolButton::SmallButton);
SARibbonToolButton* button = RibbonSubElementFactory->createRibbonToolButton(pannel);
button->setFocusPolicy(Qt::NoFocus);
button->setButtonType(buttonType);
button->setDefaultAction(action);
button->setIconSize(mDefaultToolButtonIconSize);
// 属性设置
QToolButton::ToolButtonPopupMode popMode = SARibbonPannel::getActionToolButtonPopupModeProperty(action);
button->setPopupMode(popMode);
// 根据QAction的属性设置按钮的大小
QObject::connect(button, &SARibbonToolButton::triggered, pannel, &SARibbonPannel::actionTriggered);
widget = button;
}
// 这时总会有widget
widget->hide();
SARibbonPannelItem* result = new SARibbonPannelItem(widget);
result->rowProportion = rp;
result->customWidget = customWidget;
result->action = action;
return (result);
}
/**
* @brief 更新尺寸
*/
void SARibbonPannelLayout::updateGeomArray(const QRect& setrect)
{
SARibbonPannel* pannel = qobject_cast< SARibbonPannel* >(parentWidget());
if (!pannel) {
return;
}
const int height = setrect.height();
const QMargins& mag = contentsMargins();
const int spacing = this->spacing();
const int spacingRow = 1; // 高度间距,也就是行间距,不同行之间的距离
int x = mag.left();
const int yBegin = mag.top();
int titleH = (mTitleHeight >= 0) ? mTitleHeight : 0; // 防止负数影响
int titleSpace = (mTitleHeight >= 0) ? mTitleSpace : 0; // 对于没有标题的情况,spacing就不生效
if (!isEnableShowPannelTitle()) {
titleH = 0;
titleSpace = 0;
}
// 获取pannel的布局模式 3行或者2行
// rowcount 是ribbon的行,有2行和3行两种
const short rowCount = (pannel->pannelLayoutMode() == SARibbonPannel::ThreeRowMode) ? 3 : 2;
// largeHeight是对应large占比的高度
const int largeHeight = height - mag.bottom() - mag.top() - titleH - titleSpace;
const int yTitleBegin = height - mag.bottom() - titleH;
mLargeHeight = largeHeight;
// 计算smallHeight的高度
const int smallHeight = (largeHeight - (rowCount - 1) * spacingRow) / rowCount;
// Medium行的y位置
const int yMediumRow0 = (2 == rowCount) ? yBegin : (yBegin + ((largeHeight - 2 * smallHeight) / 3));
const int yMediumRow1 = (2 == rowCount) ? (yBegin + smallHeight + spacingRow)
: (yBegin + ((largeHeight - 2 * smallHeight) / 3) * 2 + smallHeight);
// Small行的y位置
const int ySmallRow0 = yBegin;
const int ySmallRow1 = yBegin + smallHeight + spacingRow;
const int ySmallRow2 = yBegin + 2 * (smallHeight + spacingRow);
// row用于记录下个item应该属于第几行,item->rowIndex用于记录当前处于第几行,
// item->rowIndex主要用于SARibbonPannelItem::Medium
short row = 0;
int column = 0;
// 记录每列最大的宽度
int columMaxWidth = 0;
// 记录总宽度
int totalWidth = 0;
int itemCount = mItems.count();
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
QString debug_print__log__;
#endif
// 本列第一、二行占比
SARibbonPannelItem::RowProportion thisColumnRP0 = SARibbonPannelItem::None;
SARibbonPannelItem* lastGeomItem = nullptr; // 记录最后一个设置位置的item
for (int i = 0; i < itemCount; ++i) {
SARibbonPannelItem* item = mItems.at(i);
if (item->isEmpty()) {
// 如果是hide就直接跳过
item->rowIndex = -1;
item->columnIndex = -1;
continue;
}
QSize hint = item->sizeHint();
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
if (SARibbonToolButton* tb = qobject_cast< SARibbonToolButton* >(item->widget())) {
auto ss__ = tb->sizeHint();
debug_print__log__ += QString("| | |-[%1]SARibbonToolButton.sizeHint=(%2,%3),ButtonText=%4\n")
.arg(i)
.arg(ss__.width())
.arg(ss__.height())
.arg(tb->text());
}
#endif
Qt::Orientations exp = item->expandingDirections();
if (item->widget()) {
// 有窗口是水平扩展,则标记为扩展
if ((item->widget()->sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag)) {
mExpandFlag = true;
}
}
SARibbonPannelItem::RowProportion rp = item->rowProportion;
if (SARibbonPannelItem::None == rp) {
// 为定义行占比但是垂直扩展,就定义为Large占比,否则就是small占比
if (exp & Qt::Vertical) {
rp = SARibbonPannelItem::Large;
} else {
rp = SARibbonPannelItem::Small;
}
}
// 开始根据占比和layoutmode来布局
switch (rp) {
case SARibbonPannelItem::Large: {
// !!在Large,如果不是处于新列的第一行,就需要进行换列处理
// 把large一直设置在下一列的开始
if (row != 0) {
x += (columMaxWidth + spacing);
++column;
}
//
item->rowIndex = 0;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, yBegin, hint.width(), largeHeight);
columMaxWidth = hint.width();
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
row = 0;
columMaxWidth = 0;
++column;
} break;
case SARibbonPannelItem::Medium: {
// 2行模式下Medium和small等价
if (2 == rowCount) {
if (0 == row) {
item->rowIndex = 0;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, yMediumRow0, hint.width(), smallHeight);
thisColumnRP0 = SARibbonPannelItem::Medium;
columMaxWidth = hint.width();
// 下个row为1
row = 1;
// x不变
} else {
item->rowIndex = 1;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, yMediumRow1, hint.width(), smallHeight);
// 和上个进行比较得到最长宽度
columMaxWidth = qMax(columMaxWidth, hint.width());
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
row = 0;
columMaxWidth = 0;
++column;
}
} else {
// 3行模式
if (0 == row) {
item->rowIndex = 0;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, yMediumRow0, hint.width(), smallHeight);
thisColumnRP0 = SARibbonPannelItem::Medium;
columMaxWidth = hint.width();
row = 1;
// x不变
} else if (1 == row) {
item->rowIndex = 1;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, yMediumRow1, hint.width(), smallHeight);
columMaxWidth = qMax(columMaxWidth, hint.width());
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
row = 0;
columMaxWidth = 0;
++column;
} else {
// 这种模式一般情况会发生在当前列前两行是Small,添加了一个Medium
// 这时需要先换列
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
++column;
// 换列后此时等价于0 == row
item->rowIndex = 0;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, yMediumRow0, hint.width(), smallHeight);
thisColumnRP0 = SARibbonPannelItem::Medium;
columMaxWidth = hint.width();
row = 1;
}
}
} break;
case SARibbonPannelItem::Small: {
if (0 == row) {
// 第一行
item->rowIndex = 0;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, ySmallRow0, hint.width(), smallHeight);
thisColumnRP0 = SARibbonPannelItem::Small;
columMaxWidth = hint.width();
// 下个row为1
row = 1;
// x不变
} else if (1 == row) {
// 第二行
item->rowIndex = 1;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, ySmallRow1, hint.width(), smallHeight);
if ((3 == rowCount) && (SARibbonPannelItem::Medium == thisColumnRP0)) {
// 三行模式,并且第一行是Medium
item->itemWillSetGeometry = QRect(x, yMediumRow1, hint.width(), smallHeight);
}
// 和上个进行比较得到最长宽度
columMaxWidth = qMax(columMaxWidth, hint.width());
// 这里要看两行还是三行,确定是否要换列
if (2 == rowCount) {
// 两行模式,换列
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
row = 0;
columMaxWidth = 0;
++column;
} else {
// 三行模式,继续增加行数
row = 2;
// x不变
}
if ((3 == rowCount) && (SARibbonPannelItem::Medium == thisColumnRP0)) {
// 三行模式,并且第一行是Medium,换列
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
row = 0;
columMaxWidth = 0;
++column;
}
} else {
// 第三行
item->rowIndex = 2;
item->columnIndex = column;
item->itemWillSetGeometry = QRect(x, ySmallRow2, hint.width(), smallHeight);
// 和上个进行比较得到最长宽度
columMaxWidth = qMax(columMaxWidth, hint.width());
// 换列,x自动递增到下个坐标,列数增加,行数归零,最大列宽归零
x += (columMaxWidth + spacing);
row = 0;
columMaxWidth = 0;
++column;
}
} break;
default:
// 不可能出现
break;
}
lastGeomItem = item;
}
// 最后一个元素,更新列数
// 2022-06-20 此句本来在循环里面,如果最后一个元素隐藏,会导致无法到达此判断导致异常
if (lastGeomItem) { // 最后一个元素,更新totalWidth
if (lastGeomItem->columnIndex != column) {
// 说明最后一个元素处于最后位置,触发了换列,此时真实列数需要减1,直接等于column索引
mColumnCount = column;
// 由于最后一个元素触发了换列,x值是新一列的位置,直接作为totalWidth要减去已经加入的spacing
totalWidth = x - spacing + mag.right();
} else {
// 说明最后一个元素处于非最后位置,没有触发下一个换列,此时真实列数等于column索引+1
mColumnCount = column + 1;
// 由于最后一个元素未触发换列,需要计算totalWidth
totalWidth = x + columMaxWidth + mag.right();
}
}
// 在设置完所有窗口后,再设置扩展属性的窗口
if (totalWidth < setrect.width() && (setrect.width() - totalWidth) > 10) {
// 说明可以设置扩展属性的窗口
recalcExpandGeomArray(setrect);
}
// 布局label
bool isTitleWidthThanPannel = false;
if (isEnableShowPannelTitle()) {
mTitleLabelGeometry.setRect(mag.left(), yTitleBegin, setrect.width() - mag.left() - mag.right(), titleH);
// 这里要确认标题宽度是否大于totalWidth,如果大于,则要把标题的宽度作为totalwidth
QFontMetrics fm = mTitleLabel->fontMetrics();
int textWidth = SA_FONTMETRICS_WIDTH(fm, pannel->pannelName());
textWidth += 4;
if (totalWidth < textWidth) {
totalWidth = textWidth;
isTitleWidthThanPannel = true; // 说明标题的长度大于按钮布局的长度
}
}
// 布局optionActionButton
if (isHaveOptionAction()) {
QSize optBtnSize = optionActionButtonSize();
if (isEnableShowPannelTitle()) {
// 有标题
mOptionActionBtnGeometry.setRect(mTitleLabelGeometry.right() - mTitleLabelGeometry.height(),
mTitleLabelGeometry.y(),
mTitleLabelGeometry.height(),
mTitleLabelGeometry.height());
// 特殊情况,如果pannel的标题长度大于totalWidth,那么说明totalWidth比较短
// 这时候,optionActionBtn的宽度要加上到标题宽度上
if (isTitleWidthThanPannel) {
// 由于文字是居中对齐,因此要扩展2个按钮的宽度
totalWidth += (2 * titleH);
}
} else {
// 无标题
mOptionActionBtnGeometry.setRect(setrect.right() - optBtnSize.width() - mag.right(),
setrect.bottom() - optBtnSize.height() - mag.bottom(),
optBtnSize.width(),
optBtnSize.height());
totalWidth += optBtnSize.width();
}
}
// 刷新sizeHint
int heightHint = SARibbonPannel::pannelHeightHint(pannel->fontMetrics(), pannel->pannelLayoutMode(), titleH);
this->mSizeHint = QSize(totalWidth, heightHint);
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "| |-SARibbonPannelLayout updateGeomArray(" << setrect << "),pannel name = " << pannel->pannelName()
<< "\n| | |-size hint =" << this->m_sizeHint //
<< "\n| | |-totalWidth=" << totalWidth //
<< "\n| | |-last x=" << x //
<< "\n| | |-columMaxWidth=" << columMaxWidth //
<< "\n| | |-spacing=" << spacing //
<< "\n| | |-mag=" << mag //
<< "\n| | |-largeHeight=" << largeHeight //
<< "\n| | |-smallHeight=" << smallHeight //
;
qDebug().noquote() << debug_print__log__;
#endif
}
void SARibbonPannelLayout::recalcExpandGeomArray(const QRect& setrect)
{
// 计算能扩展的尺寸
int expandwidth = setrect.width() - this->mSizeHint.width();
if (expandwidth <= 0) {
// 没有必要设置
return;
}
// 列扩展信息
struct _columnExpandInfo
{
int oldColumnWidth = 0; ///< 原来的列宽
int columnMaximumWidth = -1; ///< 列的最大宽度
int columnExpandedWidth = 0; ///< 扩展后列的宽度
QList< SARibbonPannelItem* > expandItems;
};
// 此变量用于记录可以水平扩展的列和控件,在布局结束后,如果还有空间,就把水平扩展的控件进行扩展
QMap< int, _columnExpandInfo > columnExpandInfo;
for (SARibbonPannelItem* item : qAsConst(mItems)) {
if ((!item->isEmpty()) && item->expandingDirections() & Qt::Horizontal) {
// 只获取可见的
QMap< int, _columnExpandInfo >::iterator i = columnExpandInfo.find(item->columnIndex);
if (i == columnExpandInfo.end()) {
i = columnExpandInfo.insert(item->columnIndex, _columnExpandInfo());
}
i.value().expandItems.append(item);
}
}
if (columnExpandInfo.size() <= 0) {
// 没有需要扩展的就退出
return;
}
// 获取完可扩展的列和控件后,计算对应的列的尺寸
// 计算能扩展的尺寸
int oneColCanexpandWidth = expandwidth / columnExpandInfo.size();
for (QMap< int, _columnExpandInfo >::iterator i = columnExpandInfo.begin(); i != columnExpandInfo.end();) {
int& oldColumnWidth = i.value().oldColumnWidth;
int& columnMaximumWidth = i.value().columnMaximumWidth;
this->columnWidthInfo(i.key(), oldColumnWidth, columnMaximumWidth);
if ((oldColumnWidth <= 0) || (oldColumnWidth > columnMaximumWidth)) {
// 如果小于0说明没有这个列,这种属于异常,删除继续
// oldColumnWidth > columnMaximumWidth也是异常
i = columnExpandInfo.erase(i);
continue;
}
// 开始调整
int colwidth = oneColCanexpandWidth + oldColumnWidth; // 先扩展了
if (colwidth >= columnMaximumWidth) {
// 过最大宽度要求
i.value().columnExpandedWidth = columnMaximumWidth;
} else {
i.value().columnExpandedWidth = colwidth;
}
++i;
}
// 从新调整尺寸
// 由于会涉及其他列的变更,因此需要所有都遍历一下
for (auto i = columnExpandInfo.begin(); i != columnExpandInfo.end(); ++i) {
int moveXLen = i.value().columnExpandedWidth - i.value().oldColumnWidth;
for (SARibbonPannelItem* item : qAsConst(mItems)) {
if (item->isEmpty() || (item->columnIndex < i.key())) {
// 之前的列不用管
continue;
}
if (item->columnIndex == i.key()) {
// 此列的扩展
if (i.value().expandItems.contains(item)) {
// 此列需要扩展的item才扩展尺寸
item->itemWillSetGeometry.setWidth(i.value().columnExpandedWidth);
} else {
// 此列不扩展的模块保持原来的尺寸
continue;
}
} else {
// 后面的移动
item->itemWillSetGeometry.moveLeft(item->itemWillSetGeometry.x() + moveXLen);
}
}
}
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "| |-SARibbonPannelLayout recalcExpandGeomArray(" << setrect
<< ") pannelName=" << ribbonPannel()->pannelName() //
<< ",expandwidth=" << expandwidth //
;
#endif
}
/**
* @brief 根据列数,计算窗口的宽度,以及最大宽度
* @param colindex
* @param width 如果传入没有这个列,返回-1
* @param maximum 如果传入没有这个列,返回-1
*/
void SARibbonPannelLayout::columnWidthInfo(int colindex, int& width, int& maximum) const
{
width = -1;
maximum = -1;
for (SARibbonPannelItem* item : mItems) {
if (!item->isEmpty() && (item->columnIndex == colindex)) {
width = qMax(width, item->itemWillSetGeometry.width());
maximum = qMax(maximum, item->widget()->maximumWidth());
}
}
}
SARibbonPannelLabel* SARibbonPannelLayout::pannelTitleLabel() const
{
return mTitleLabel;
}
void SARibbonPannelLayout::setToolButtonIconSize(const QSize& s)
{
mDefaultToolButtonIconSize = s;
}
QSize SARibbonPannelLayout::toolButtonIconSize() const
{
return mDefaultToolButtonIconSize;
}
/**
* @brief 获取optionAction 按钮尺寸
* @return
*/
QSize SARibbonPannelLayout::optionActionButtonSize() const
{
return (isEnableShowPannelTitle() ? QSize(12, 12) : QSize(mTitleHeight, mTitleHeight));
}
void SARibbonPannelLayout::setPannelTitleLabel(SARibbonPannelLabel* newTitleLabel)
{
mTitleLabel = newTitleLabel;
// 确保m_optionActionBtn在label之上
if (mOptionActionBtn) {
if (mTitleLabel) {
mTitleLabel->stackUnder(mOptionActionBtn);
}
}
}
/**
* @brief 标题区域和按钮的间隔
* @return
*/
int SARibbonPannelLayout::pannelTitleSpace() const
{
return mTitleSpace;
}
/**
* @brief 设置标题区域和按钮的间隔
* @param newTitleSpace
*/
void SARibbonPannelLayout::setPannelTitleSpace(int newTitleSpace)
{
if (mTitleSpace == newTitleSpace) {
return;
}
mTitleSpace = newTitleSpace;
invalidate();
}
/**
* @brief 标题高度
* @return
*/
int SARibbonPannelLayout::pannelTitleHeight() const
{
return mTitleHeight;
}
/**
* @brief 设置标题高度
* @param newTitleHeight
*/
void SARibbonPannelLayout::setPannelTitleHeight(int newTitleHeight)
{
if (mTitleHeight == newTitleHeight) {
return;
}
mTitleHeight = newTitleHeight;
invalidate();
}
/**
* @brief 判断是否存在标题
* @return
*/
bool SARibbonPannelLayout::isEnableShowPannelTitle() const
{
return mEnableShowTitle;
}
/**
* @brief 设置显示标题
* @param on
*/
void SARibbonPannelLayout::setEnableShowPannelTitle(bool on)
{
if (mEnableShowTitle == on) {
return;
}
mEnableShowTitle = on;
invalidate();
}
/**
* @brief 大按钮的高度
* @return
*/
int SARibbonPannelLayout::largeButtonHeight() const
{
return mLargeHeight;
}
void SARibbonPannelLayout::setGeometry(const QRect& rect)
{
QRect old = geometry();
if (old == rect) {
return;
}
#if SARibbonPannelLayout_DEBUG_PRINT && SA_DEBUG_PRINT_SIZE_HINT
qDebug() << "| |----->SARibbonPannelLayout.setGeometry(" << rect << "(" << ribbonPannel()->pannelName() << ")=======";
#endif
QLayout::setGeometry(rect);
mDirty = false;
updateGeomArray(rect);
doLayout();
}
@@ -0,0 +1,113 @@
#ifndef SARIBBONPANNELLAYOUT_H
#define SARIBBONPANNELLAYOUT_H
#include "SARibbonGlobal.h"
#include <QLayout>
#include "SARibbonPannelItem.h"
class QToolButton;
class SARibbonPannel;
class SARibbonPannelLabel;
/**
* @brief 针对SARibbonPannel的布局
*
* SARibbonPannelLayout实际是一个列布局,每一列有2~3行,看窗口定占几行
*
* 核心函数: @ref SARibbonPannelLayout::createItem
*
* @note QLayout::contentsMargins 函数不会启作用,如果要设置contentsMargins,使用@sa setPannelContentsMargins
*/
class SA_RIBBON_EXPORT SARibbonPannelLayout : public QLayout
{
Q_OBJECT
friend class SARibbonPannel;
public:
explicit SARibbonPannelLayout(QWidget* p = nullptr);
~SARibbonPannelLayout();
// SARibbonPannelLayout additem 无效
void addItem(QLayoutItem* item) Q_DECL_OVERRIDE;
// QLayout 所必须的重载函数
QLayoutItem* itemAt(int index) const Q_DECL_OVERRIDE;
QLayoutItem* takeAt(int index) Q_DECL_OVERRIDE;
int count() const Q_DECL_OVERRIDE;
bool isEmpty() const Q_DECL_OVERRIDE;
void invalidate() Q_DECL_OVERRIDE;
Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE;
void setGeometry(const QRect& rect) Q_DECL_OVERRIDE;
QSize minimumSize() const Q_DECL_OVERRIDE;
QSize sizeHint() const Q_DECL_OVERRIDE;
// 获取ribbonpannel
SARibbonPannel* ribbonPannel() const;
// SARibbonPannel主要通过此函数来添加action
void insertAction(int index, QAction* act, SARibbonPannelItem::RowProportion rp = SARibbonPannelItem::None);
// 设置OptionAction,如果要去除,传入nullptr指针即可
void setOptionAction(QAction* action);
bool isHaveOptionAction() const;
// 通过action获取SARibbonPannelItem
SARibbonPannelItem* pannelItem(QAction* action) const;
// 获取最后一个添加的item
SARibbonPannelItem* lastItem() const;
// 获取最后生成的窗口
QWidget* lastWidget() const;
// 移动两个item
void move(int from, int to);
// 判断是否需要重新布局
bool isDirty() const;
// 更新尺寸
void updateGeomArray();
// 通过action获取SARibbonPannelItem的索引
int indexByAction(QAction* action) const;
// 标题高度
int pannelTitleHeight() const;
void setPannelTitleHeight(int newTitleHeight);
// 判断是否存在标题
bool isEnableShowPannelTitle() const;
void setEnableShowPannelTitle(bool on);
// 返回大按钮的高度
int largeButtonHeight() const;
// 标题区域和按钮的间隔
int pannelTitleSpace() const;
void setPannelTitleSpace(int newTitleSpace);
// pannel 标题的label
SARibbonPannelLabel* pannelTitleLabel() const;
// 设置按钮的icon尺寸,large action不受此尺寸影响
void setToolButtonIconSize(const QSize& s);
QSize toolButtonIconSize() const;
protected:
// 获取optionAction 按钮尺寸
QSize optionActionButtonSize() const;
// 布局action
void doLayout();
// 把action转换为item,对于纯Action,此函数会创建SARibbonToolButton,
// rp用于告诉Layout生成什么样的窗口,详细见SARibbonPannelItem::RowProportion
SARibbonPannelItem* createItem(QAction* action, SARibbonPannelItem::RowProportion rp = SARibbonPannelItem::None);
void updateGeomArray(const QRect& setrect);
// 重新计算扩展条码,此函数必须在updateGeomArray函数之后调用
void recalcExpandGeomArray(const QRect& setrect);
// 返回optionActionButton的尺寸
private:
// 根据列数,计算窗口的宽度,以及最大宽度
void columnWidthInfo(int colindex, int& width, int& maximum) const;
// 设置titlelabel
void setPannelTitleLabel(SARibbonPannelLabel* newTitleLabel);
private:
QList< SARibbonPannelItem* > mItems;
int mColumnCount { 0 }; ///< 记录有多少列
bool mExpandFlag { false }; ///< 标记是否是会扩展的
QSize mSizeHint; ///< sizeHint返回的尺寸
QSize mDefaultToolButtonIconSize { 22, 22 }; ///< 记录默认的按钮图标尺寸
bool mDirty { true }; ///< 用于标记是否需要刷新元素,参考QToolBarLayout源码
int mLargeHeight { 0 }; ///< 记录大图标的高度
int mTitleHeight { 15 }; ///< 标题区域高度
int mTitleSpace { 2 }; ///< 标题区域和按钮的间隔
bool mEnableShowTitle { true }; ///< 是否运行显示pannel标题
SARibbonPannelLabel* mTitleLabel { nullptr }; ///< titlelabel指针
QRect mTitleLabelGeometry; ///< titlelabel的位置
QToolButton* mOptionActionBtn { nullptr }; ///< optionAction对应的button
QRect mOptionActionBtnGeometry; ///< optionAction的位置
};
#endif // SARIBBONPANNELLAYOUT_H
@@ -0,0 +1,16 @@
#include "SARibbonPannelOptionButton.h"
#include <QAction>
SARibbonPannelOptionButton::SARibbonPannelOptionButton(QWidget* parent) : QToolButton(parent)
{
setAutoRaise(true);
setCheckable(false);
setToolButtonStyle(Qt::ToolButtonIconOnly);
setIconSize(QSize(10, 10));
static QIcon s_default_icon = QIcon(":/image/resource/ribbonPannelOptionButton.png");
setIcon(s_default_icon);
}
SARibbonPannelOptionButton::~SARibbonPannelOptionButton()
{
}
@@ -0,0 +1,24 @@
#ifndef SARIBBONPANNELOPTIONBUTTON_H
#define SARIBBONPANNELOPTIONBUTTON_H
#include <QToolButton>
#include "SARibbonGlobal.h"
class QAction;
/**
* @brief Pannel右下角的操作按钮
*
* 此按钮和一个action关联,使用@ref SARibbonPannel::addOptionAction 函数用于生成此按钮,正常来说
* 用户并不需要直接操作此类,仅仅用于样式设计
* 如果一定要重载此按钮,可以通过重载@ref SARibbonElementFactory
* 的 @ref SARibbonElementFactory::createRibbonPannelOptionButton来实现新的OptionButton
*
*/
class SA_RIBBON_EXPORT SARibbonPannelOptionButton : public QToolButton
{
Q_OBJECT
public:
explicit SARibbonPannelOptionButton(QWidget* parent = nullptr);
~SARibbonPannelOptionButton();
};
#endif // SAROBBONPANNELOPTIONBUTTON_H
@@ -0,0 +1,92 @@
#include "SARibbonQuickAccessBar.h"
#include "SARibbonButtonGroupWidget.h"
#include "SARibbonSeparatorWidget.h"
#include <QGuiApplication>
/**
* @brief The SARibbonQuickAccessBarPrivate class
*/
class SARibbonQuickAccessBar::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonQuickAccessBar)
public:
PrivateData(SARibbonQuickAccessBar* p);
public:
SARibbonButtonGroupWidget* mGroupWidget { nullptr };
};
SARibbonQuickAccessBar::PrivateData::PrivateData(SARibbonQuickAccessBar* p) : q_ptr(p)
{
}
//===================================================
// SARibbonQuickAccessBar
//===================================================
SARibbonQuickAccessBar::SARibbonQuickAccessBar(QWidget* parent)
: SARibbonCtrlContainer(parent), d_ptr(new SARibbonQuickAccessBar::PrivateData(this))
{
d_ptr->mGroupWidget = new SARibbonButtonGroupWidget(this);
d_ptr->mGroupWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
setContainerWidget(d_ptr->mGroupWidget);
}
SARibbonQuickAccessBar::~SARibbonQuickAccessBar()
{
}
void SARibbonQuickAccessBar::addSeparator()
{
d_ptr->mGroupWidget->addSeparator();
}
void SARibbonQuickAccessBar::addAction(QAction* act, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
d_ptr->mGroupWidget->addAction(act, buttonStyle, popMode);
}
void SARibbonQuickAccessBar::addWidget(QWidget* w)
{
d_ptr->mGroupWidget->addWidget(w);
}
void SARibbonQuickAccessBar::addMenu(QMenu* m, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
d_ptr->mGroupWidget->addMenu(m, buttonStyle, popMode);
}
/**
* @brief 获取内部管理的ButtonGroupWidget
* @return
*/
SARibbonButtonGroupWidget* SARibbonQuickAccessBar::buttonGroupWidget()
{
return d_ptr->mGroupWidget;
}
/**
* @brief 获取内部管理的ButtonGroupWidget
* @return
*/
const SARibbonButtonGroupWidget* SARibbonQuickAccessBar::buttonGroupWidget() const
{
return d_ptr->mGroupWidget;
}
/**
* @brief 图标尺寸
* @param s
*/
void SARibbonQuickAccessBar::setIconSize(const QSize& s)
{
d_ptr->mGroupWidget->setIconSize(s);
}
/**
* @brief SARibbonQuickAccessBar::iconSize
* @return
*/
QSize SARibbonQuickAccessBar::iconSize() const
{
return d_ptr->mGroupWidget->iconSize();
}
@@ -0,0 +1,35 @@
#ifndef SARIBBONQUICKACCESSBAR_H
#define SARIBBONQUICKACCESSBAR_H
#include "SARibbonGlobal.h"
#include "SARibbonCtrlContainer.h"
#include <QMenu>
#include <QToolButton>
class SARibbonToolButton;
class SARibbonButtonGroupWidget;
///
/// \brief ribbon左上顶部的快速响应栏
///
class SA_RIBBON_EXPORT SARibbonQuickAccessBar : public SARibbonCtrlContainer
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonQuickAccessBar)
public:
explicit SARibbonQuickAccessBar(QWidget* parent = nullptr);
~SARibbonQuickAccessBar();
void addSeparator();
void addAction(QAction* act,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::DelayedPopup);
void addWidget(QWidget* w);
void addMenu(QMenu* m,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::InstantPopup);
// 获取内部管理的ButtonGroupWidget
SARibbonButtonGroupWidget* buttonGroupWidget();
const SARibbonButtonGroupWidget* buttonGroupWidget() const;
// 设置图标尺寸
void setIconSize(const QSize& s);
QSize iconSize() const;
};
#endif // SARIBBONQUICKACCESSBAR_H
@@ -0,0 +1,26 @@
#include "SARibbonSeparatorWidget.h"
#include <QApplication>
#include <QScreen>
#include <QStylePainter>
#include <QPainter>
#include <QDebug>
SARibbonSeparatorWidget::SARibbonSeparatorWidget(QWidget* parent) : QFrame(parent)
{
setFrameShape(QFrame::VLine);
setFrameShadow(QFrame::Plain);
if(QScreen* screen = QApplication::primaryScreen()){
qreal dpr = screen->physicalDotsPerInch() / screen->logicalDotsPerInch();
int scaledLineWidth = qRound(1.0 * dpr); // 假设基础 lineWidth 是 1
setLineWidth(scaledLineWidth);
// qDebug() << "SARibbonSeparatorWidget:" << scaledLineWidth;
}else{
setLineWidth(1);
}
}
SARibbonSeparatorWidget::~SARibbonSeparatorWidget()
{
}
@@ -0,0 +1,18 @@
#ifndef SARIBBONSEPARATORWIDGET_H
#define SARIBBONSEPARATORWIDGET_H
#include "SARibbonGlobal.h"
#include <QFrame>
#include <QStyleOption>
///
/// \brief 用于显示分割线
///
class SA_RIBBON_EXPORT SARibbonSeparatorWidget : public QFrame
{
Q_OBJECT
public:
explicit SARibbonSeparatorWidget(QWidget* parent = nullptr);
~SARibbonSeparatorWidget();
};
#endif // SARIBBONSEPARATORWIDGET_H
@@ -0,0 +1,115 @@
#include "SARibbonStackedWidget.h"
#include <QEventLoop>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QDebug>
#include <QApplication>
/**
* @brief The SARibbonStackedWidgetPrivate class
*/
class SARibbonStackedWidget::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonStackedWidget)
public:
QEventLoop* eventLoop { nullptr };
public:
PrivateData(SARibbonStackedWidget* p) : q_ptr(p)
{
}
void init()
{
// Parent->setFocusPolicy(Qt::StrongFocus);
}
};
SARibbonStackedWidget::SARibbonStackedWidget(QWidget* parent)
: QStackedWidget(parent), d_ptr(new SARibbonStackedWidget::PrivateData(this))
{
d_ptr->init();
setNormalMode();
}
SARibbonStackedWidget::~SARibbonStackedWidget()
{
if (d_ptr->eventLoop) {
d_ptr->eventLoop->exit();
}
}
void SARibbonStackedWidget::setPopupMode()
{
setMouseTracking(true);
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
setFrameShape(QFrame::Panel);
}
bool SARibbonStackedWidget::isPopupMode() const
{
return (windowFlags() & Qt::Popup);
}
void SARibbonStackedWidget::setNormalMode()
{
if (d_ptr->eventLoop) {
d_ptr->eventLoop->exit();
d_ptr->eventLoop = nullptr;
}
setMouseTracking(false);
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
setFrameShape(QFrame::NoFrame);
}
bool SARibbonStackedWidget::isNormalMode() const
{
return (!isPopupMode());
}
void SARibbonStackedWidget::exec()
{
show();
if (!isPopupMode()) {
d_ptr->eventLoop = nullptr;
return;
}
QEventLoop event;
d_ptr->eventLoop = &event;
event.exec();
d_ptr->eventLoop = nullptr;
}
void SARibbonStackedWidget::moveWidget(int from, int to)
{
QWidget* w = widget(from);
removeWidget(w);
insertWidget(to, w);
}
void SARibbonStackedWidget::hideEvent(QHideEvent* e)
{
if (isPopupMode()) {
if (d_ptr->eventLoop) {
d_ptr->eventLoop->exit();
}
}
setFocus();
Q_EMIT hidWindow();
QStackedWidget::hideEvent(e);
}
void SARibbonStackedWidget::resizeEvent(QResizeEvent* e)
{
QStackedWidget::resizeEvent(e);
for (int i = 0; i < count(); ++i) {
if (i == currentIndex()) {
continue;
}
QEvent* layE = new QEvent(QEvent::LayoutRequest);
QApplication::postEvent(widget(i), layE);
}
}
@@ -0,0 +1,43 @@
#ifndef SARIBBONSTACKEDWIDGET_H
#define SARIBBONSTACKEDWIDGET_H
#include <QStackedWidget>
#include "SARibbonGlobal.h"
class QHideEvent;
class QResizeEvent;
/**
* @brief 有qdialog功能的stackwidget,用于在最小化时stack能像dialog那样弹出来
*/
class SA_RIBBON_EXPORT SARibbonStackedWidget : public QStackedWidget
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonStackedWidget)
public:
explicit SARibbonStackedWidget(QWidget* parent);
~SARibbonStackedWidget();
void setPopupMode();
bool isPopupMode() const;
void setNormalMode();
bool isNormalMode() const;
void exec();
/**
* @brief 类似tabbar的moveTab函数,交换两个窗口的index
* @param from
* @param to
* @note 此操作会触发widgetRemoved(int index)信号
*/
void moveWidget(int from, int to);
protected:
// void mouseReleaseEvent(QMouseEvent *e);
void hideEvent(QHideEvent* e) Q_DECL_OVERRIDE;
virtual void resizeEvent(QResizeEvent* e) Q_DECL_OVERRIDE;
Q_SIGNALS:
/**
* @brief hidWindow
*/
void hidWindow();
};
#endif // SARIBBONSTACKEDWIDGET_H
@@ -0,0 +1,467 @@
#include "SARibbonSystemButtonBar.h"
#include <QToolButton>
#include <QResizeEvent>
#include <QStyle>
#include <QDebug>
#include <QScopedPointer>
#include <QWindowStateChangeEvent>
#include "SARibbonMainWindow.h"
#include "SARibbonBar.h"
#include "SARibbonElementManager.h"
#include "SARibbonButtonGroupWidget.h"
// 为了避免使用此框架的app设置了全局的qpushbutton 的 qss样式影响此按钮,定义了一个类
/**
* @brief The SARibbonSystemButtonBar class
*/
class SARibbonSystemButtonBar::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonSystemButtonBar)
public:
SARibbonSystemToolButton* buttonClose { nullptr };
SARibbonSystemToolButton* buttonMinimize { nullptr };
SARibbonSystemToolButton* buttonMaximize { nullptr };
int mCloseStretch { 4 };
int mMaxStretch { 3 };
int mMinStretch { 3 };
int mWindowButtonWidth { 35 };
int mTitleBarHeight { 28 };
Qt::WindowFlags mFlags { Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint };
SARibbonButtonGroupWidget* mButtonGroup;
public:
PrivateData(SARibbonSystemButtonBar* p) : q_ptr(p)
{
mButtonGroup = new SARibbonButtonGroupWidget(p);
mButtonGroup->setObjectName("SASystemButtonGroup");
}
void setupMinimizeButton(bool on)
{
SARibbonSystemButtonBar* par = q_ptr;
if (on) {
if (buttonMinimize) {
buttonMinimize->deleteLater();
buttonMinimize = nullptr;
}
buttonMinimize = new SARibbonSystemToolButton(par);
buttonMinimize->setObjectName(QStringLiteral("SAMinimizeWindowButton"));
buttonMinimize->setFocusPolicy(Qt::NoFocus); // 避免铺抓到
buttonMinimize->show();
par->connect(buttonMinimize, &QAbstractButton::clicked, par, &SARibbonSystemButtonBar::minimizeWindow);
} else {
if (buttonMinimize) {
buttonMinimize->deleteLater();
buttonMinimize = nullptr;
}
}
updateSize();
}
void setupMaximizeButton(bool on)
{
SARibbonSystemButtonBar* par = q_ptr;
if (on) {
if (buttonMaximize) {
buttonMaximize->deleteLater();
buttonMaximize = nullptr;
}
buttonMaximize = new SARibbonSystemToolButton(par);
buttonMaximize->setObjectName(QStringLiteral("SAMaximizeWindowButton"));
buttonMaximize->setCheckable(true);
buttonMaximize->setFocusPolicy(Qt::NoFocus); // 避免铺抓到
// buttonMaximize->setIconSize(buttonMaximize->size() * mIconscale);
buttonMaximize->show();
par->connect(buttonMaximize, &QAbstractButton::clicked, par, &SARibbonSystemButtonBar::maximizeWindow);
} else {
if (buttonMaximize) {
buttonMaximize->deleteLater();
;
buttonMaximize = nullptr;
}
}
updateSize();
}
void setupCloseButton(bool on)
{
SARibbonSystemButtonBar* par = q_ptr;
if (on) {
if (buttonClose) {
buttonClose->deleteLater();
buttonClose = nullptr;
}
buttonClose = new SARibbonSystemToolButton(par);
buttonClose->setObjectName(QStringLiteral("SACloseWindowButton"));
buttonClose->setFocusPolicy(Qt::NoFocus); // 避免铺抓到
// buttonClose->setFlat(true);
par->connect(buttonClose, &QAbstractButton::clicked, par, &SARibbonSystemButtonBar::closeWindow);
// buttonClose->setIconSize(buttonClose->size() * mIconscale);
buttonClose->show();
} else {
if (buttonClose) {
buttonClose->deleteLater();
;
buttonClose = nullptr;
}
}
updateSize();
}
void updateSize()
{
resizeElement(q_ptr->size());
}
void resizeElement(QSize size)
{
int x = size.width();
if (buttonClose) {
int w = closeButtonWidthHint();
x -= w;
buttonClose->setGeometry(x, 0, w, size.height());
}
if (buttonMaximize) {
int w = maxButtonWidthHint();
x -= w;
buttonMaximize->setGeometry(x, 0, w, size.height());
}
if (buttonMinimize) {
int w = minButtonWidthHint();
x -= w;
buttonMinimize->setGeometry(x, 0, w, size.height());
}
if (mButtonGroup) {
mButtonGroup->setGeometry(0, 0, x, size.height());
}
}
int closeButtonWidthHint() const
{
qreal t = mCloseStretch + mMaxStretch + mMinStretch;
return (mCloseStretch * (3 * mWindowButtonWidth)) / t;
}
int maxButtonWidthHint() const
{
qreal t = mCloseStretch + mMaxStretch + mMinStretch;
return (mMaxStretch * (3 * mWindowButtonWidth)) / t;
}
int minButtonWidthHint() const
{
qreal t = mCloseStretch + mMaxStretch + mMinStretch;
return (mMinStretch * (3 * mWindowButtonWidth)) / t;
}
QSize sizeHint() const
{
int height = mTitleBarHeight;
if (height < 20) {
height = 20;
}
QSize res(0, 0);
if (mButtonGroup) {
res = mButtonGroup->sizeHint();
}
res.setHeight(height);
if (buttonClose) {
res.setWidth(res.width() + closeButtonWidthHint());
}
if (buttonMaximize) {
res.setWidth(res.width() + maxButtonWidthHint());
}
if (buttonMinimize) {
res.setWidth(res.width() + minButtonWidthHint());
}
return res;
}
};
//===================================================
// SARibbonSystemToolButton
//===================================================
SARibbonSystemToolButton::SARibbonSystemToolButton(QWidget* p) : QToolButton(p)
{
setAutoRaise(true);
}
//===================================================
// SARibbonSystemButtonBar
//===================================================
SARibbonSystemButtonBar::SARibbonSystemButtonBar(QWidget* parent)
: QFrame(parent), d_ptr(new SARibbonSystemButtonBar::PrivateData(this))
{
updateWindowFlag();
}
/**
* @brief 构造函数,强制使用flags,而不是用parent的flags进行构造
* @param parent
* @param flags
*/
SARibbonSystemButtonBar::SARibbonSystemButtonBar(QWidget* parent, Qt::WindowFlags flags)
: QFrame(parent), d_ptr(new SARibbonSystemButtonBar::PrivateData(this))
{
d_ptr->mFlags = flags;
updateWindowFlag();
}
SARibbonSystemButtonBar::~SARibbonSystemButtonBar()
{
}
void SARibbonSystemButtonBar::setupMinimizeButton(bool on)
{
d_ptr->setupMinimizeButton(on);
}
void SARibbonSystemButtonBar::setupMaximizeButton(bool on)
{
d_ptr->setupMaximizeButton(on);
}
void SARibbonSystemButtonBar::setupCloseButton(bool on)
{
d_ptr->setupCloseButton(on);
}
void SARibbonSystemButtonBar::updateWindowFlag()
{
QWidget* topedWidget = this;
// 找到最顶层窗口
while (topedWidget->parentWidget()) {
topedWidget = topedWidget->parentWidget();
}
Qt::WindowFlags flags = topedWidget->windowFlags();
updateWindowFlag(flags);
}
/**
* @brief 此函数仅用于控制最小最大化和关闭按钮的显示
* @param flags
*/
void SARibbonSystemButtonBar::updateWindowFlag(Qt::WindowFlags flags)
{
d_ptr->mFlags = flags;
setupMinimizeButton(flags & Qt::WindowMinimizeButtonHint);
setupMaximizeButton(flags & Qt::WindowMaximizeButtonHint);
setupCloseButton(flags & Qt::WindowCloseButtonHint);
}
/**
* @brief 设置按钮的宽度比例,最终按钮宽度将按照此比例进行设置
* @param close 关闭按钮比例
* @param max 最大化按钮比例
* @param min 最小化按钮比例
*/
void SARibbonSystemButtonBar::setButtonWidthStretch(int close, int max, int min)
{
d_ptr->mMaxStretch = max;
d_ptr->mMinStretch = min;
d_ptr->mCloseStretch = close;
}
/**
* @brief 标题栏高度
*
* 标题栏高度会影响sizeHint
* @param h
*/
void SARibbonSystemButtonBar::setWindowTitleHeight(int h)
{
d_ptr->mTitleBarHeight = h;
}
/**
* @brief 标题栏高度
* @return
*/
int SARibbonSystemButtonBar::windowTitleHeight() const
{
return d_ptr->mTitleBarHeight;
}
/**
* @brief 系统按钮的宽度
* @param w
*/
void SARibbonSystemButtonBar::setWindowButtonWidth(int w)
{
d_ptr->mWindowButtonWidth = w;
}
/**
* @brief 系统按钮的宽度
* @param w
*/
int SARibbonSystemButtonBar::windowButtonWidth() const
{
return d_ptr->mWindowButtonWidth;
}
/**
* @brief 设置窗口状态(最大最小化按钮状态)
* @param s
*/
void SARibbonSystemButtonBar::setWindowStates(Qt::WindowStates s)
{
if (d_ptr->buttonMaximize) {
bool on = s.testFlag(Qt::WindowMaximized);
d_ptr->buttonMaximize->setChecked(on);
d_ptr->buttonMaximize->setToolTip(on ? tr("Restore") : tr("Maximize"));
}
}
/**
* @brief 此函数返回的flags仅包括 Qt::WindowCloseButtonHintQt::WindowMaximizeButtonHintQt::WindowMinimizeButtonHint
* 三个
*
* @return
*/
Qt::WindowFlags SARibbonSystemButtonBar::windowButtonFlags() const
{
Qt::WindowFlags f = Qt::Widget; // widget是000
if (d_ptr->mFlags & Qt::WindowCloseButtonHint) {
f |= Qt::WindowCloseButtonHint;
}
if (d_ptr->mFlags & Qt::WindowMaximizeButtonHint) {
f |= Qt::WindowMaximizeButtonHint;
}
if (d_ptr->mFlags & Qt::WindowMinimizeButtonHint) {
f |= Qt::WindowMinimizeButtonHint;
}
return (f);
}
QSize SARibbonSystemButtonBar::sizeHint() const
{
return (d_ptr->sizeHint());
}
bool SARibbonSystemButtonBar::eventFilter(QObject* obj, QEvent* event)
{
if (obj == parent()) {
// SARibbonMainWindow的事件
if (event->type() == QEvent::Resize) {
SARibbonMainWindow* mainWindow = qobject_cast< SARibbonMainWindow* >(obj);
if (!mainWindow) {
// 所有事件都不消费
return QFrame::eventFilter(obj, event);
}
SARibbonBar* ribbonBar = mainWindow->ribbonBar();
if (!ribbonBar) {
// 所有事件都不消费
return QFrame::eventFilter(obj, event);
}
const int th = ribbonBar->titleBarHeight();
if (th != height()) {
setWindowTitleHeight(th);
}
QRect fr = mainWindow->geometry();
QSize wgSizeHint = sizeHint();
setGeometry(fr.width() - wgSizeHint.width(), 0, wgSizeHint.width(), wgSizeHint.height());
// 把设置好的尺寸给ribbonbar
ribbonBar->setWindowButtonGroupSize(size());
} else if (event->type() == QEvent::WindowStateChange) {
SARibbonMainWindow* mainWindow = qobject_cast< SARibbonMainWindow* >(obj);
if (!mainWindow) {
// 所有事件都不消费
return QFrame::eventFilter(obj, event);
}
setWindowStates(mainWindow->windowState());
}
}
return QFrame::eventFilter(obj, event);
}
QAbstractButton* SARibbonSystemButtonBar::minimizeButton() const
{
return d_ptr->buttonMinimize;
}
QAbstractButton* SARibbonSystemButtonBar::maximizeButton() const
{
return d_ptr->buttonMaximize;
}
QAbstractButton* SARibbonSystemButtonBar::closeButton() const
{
return d_ptr->buttonClose;
}
void SARibbonSystemButtonBar::setIconSize(const QSize& ic)
{
d_ptr->mButtonGroup->setIconSize(ic);
}
QSize SARibbonSystemButtonBar::iconSize() const
{
return d_ptr->mButtonGroup->iconSize();
}
QAction* SARibbonSystemButtonBar::addAction(QAction* a, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
return d_ptr->mButtonGroup->addAction(a, buttonStyle, popMode);
}
QAction* SARibbonSystemButtonBar::addAction(const QString& text,
const QIcon& icon,
Qt::ToolButtonStyle buttonStyle,
QToolButton::ToolButtonPopupMode popMode)
{
return d_ptr->mButtonGroup->addAction(text, icon, buttonStyle, popMode);
}
QAction* SARibbonSystemButtonBar::addMenu(QMenu* menu, Qt::ToolButtonStyle buttonStyle, QToolButton::ToolButtonPopupMode popMode)
{
return d_ptr->mButtonGroup->addMenu(menu, buttonStyle, popMode);
}
QAction* SARibbonSystemButtonBar::addSeparator()
{
return d_ptr->mButtonGroup->addSeparator();
}
QAction* SARibbonSystemButtonBar::addWidget(QWidget* w)
{
return d_ptr->mButtonGroup->addWidget(w);
}
void SARibbonSystemButtonBar::resizeEvent(QResizeEvent* e)
{
Q_UNUSED(e);
d_ptr->resizeElement(size());
}
void SARibbonSystemButtonBar::closeWindow()
{
if (parentWidget()) {
parentWidget()->close();
}
}
void SARibbonSystemButtonBar::minimizeWindow()
{
if (parentWidget()) {
parentWidget()->showMinimized();
}
}
void SARibbonSystemButtonBar::maximizeWindow()
{
QWidget* par = parentWidget();
if (par) {
if (par->isMaximized()) {
par->showNormal();
} else {
par->showMaximized();
}
}
}
@@ -0,0 +1,88 @@
#ifndef SARIBBONSYSTEMBUTTONBAR_H
#define SARIBBONSYSTEMBUTTONBAR_H
#include "SARibbonGlobal.h"
#include <QFrame>
#include <QToolButton>
/**
* @brief 窗口的最大最小化按钮
* @note 内部有个SARibbonButtonGroupWidget,其ObjectName = SASystemButtonGroup
* 如果需要qss,可以进行特化处理:
*
* @code
* SARibbonButtonGroupWidget#SASystemButtonGroup
* @endcode
*
*/
class SA_RIBBON_EXPORT SARibbonSystemButtonBar : public QFrame
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonSystemButtonBar)
public:
explicit SARibbonSystemButtonBar(QWidget* parent);
explicit SARibbonSystemButtonBar(QWidget* parent, Qt::WindowFlags flags);
~SARibbonSystemButtonBar();
void setupMinimizeButton(bool on);
void setupMaximizeButton(bool on);
void setupCloseButton(bool on);
// 以最顶层的widget的WindowFlag作为WindowFlag
void updateWindowFlag();
void updateWindowFlag(Qt::WindowFlags flags);
// 设置按钮的宽度比例,最终按钮宽度将按照此比例进行设置
void setButtonWidthStretch(int close = 4, int max = 3, int min = 3);
// 标题栏高度
void setWindowTitleHeight(int h);
int windowTitleHeight() const;
// 设置标准系统按钮的宽度
void setWindowButtonWidth(int w);
int windowButtonWidth() const;
// 设置Qt::WindowStates
void setWindowStates(Qt::WindowStates s);
// 仅获取按钮的状态
Qt::WindowFlags windowButtonFlags() const;
// 三个标准系统窗口按钮
QAbstractButton* minimizeButton() const;
QAbstractButton* maximizeButton() const;
QAbstractButton* closeButton() const;
// 图标尺寸
void setIconSize(const QSize& ic);
QSize iconSize() const;
// 生成并添加一个action
QAction* addAction(QAction* a,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::DelayedPopup);
QAction* addAction(const QString& text,
const QIcon& icon,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::DelayedPopup);
QAction* addMenu(QMenu* menu,
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonIconOnly,
QToolButton::ToolButtonPopupMode popMode = QToolButton::InstantPopup);
QAction* addSeparator();
QAction* addWidget(QWidget* w);
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
// 捕获mainwindow的事件
bool eventFilter(QObject* obj, QEvent* event) override;
protected:
virtual void resizeEvent(QResizeEvent* e) Q_DECL_OVERRIDE;
protected Q_SLOTS:
Q_SLOT void closeWindow();
Q_SLOT void minimizeWindow();
Q_SLOT void maximizeWindow();
};
/**
* @brief The SARibbonSystemToolButton class
*/
class SARibbonSystemToolButton : public QToolButton
{
Q_OBJECT
public:
SARibbonSystemToolButton(QWidget* p = nullptr);
};
#endif // SARIBBONSYSTEMBUTTONBAR_H
@@ -0,0 +1,57 @@
#include "SARibbonTabBar.h"
#include <QStyleOptionTab>
#include <QFontMetrics>
SARibbonTabBar::SARibbonTabBar(QWidget* parent) : QTabBar(parent), m_tabMargin(6, 0, 0, 0)
{
setExpanding(false);
}
SARibbonTabBar::~SARibbonTabBar()
{
}
const QMargins& SARibbonTabBar::tabMargin() const
{
return (m_tabMargin);
}
void SARibbonTabBar::setTabMargin(const QMargins& tabMargin)
{
m_tabMargin = tabMargin;
}
/**
* @brief tab的尺寸预估
*
* 有别于系统默认的tabbarSARibbonTabBar的tab高度和tabbar高度一致,且不考虑纵向分布情况
* @param index
* @return
*/
QSize SARibbonTabBar::tabSizeHint(int index) const
{
if (index < 0) {
return QSize();
}
QStyleOptionTab opt;
initStyleOption(&opt, index);
int hframe = style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &opt, this);
const QFontMetrics fm = fontMetrics();
int widgetWidth = 0;
int padding = 0;
if (!opt.leftButtonSize.isEmpty()) {
padding += 4;
widgetWidth += opt.leftButtonSize.width();
}
if (!opt.rightButtonSize.isEmpty()) {
padding += 4;
widgetWidth += opt.rightButtonSize.width();
}
if (!opt.icon.isNull()) {
padding += 4;
}
const int textWidth = fm.size(Qt::TextShowMnemonic, opt.text).width();
QSize csz = QSize(textWidth + opt.iconSize.width() + hframe + widgetWidth + padding, height());
return style()->sizeFromContents(QStyle::CT_TabBarTab, &opt, csz, this);
}
@@ -0,0 +1,26 @@
#ifndef SARIBBONTABBAR_H
#define SARIBBONTABBAR_H
#include "SARibbonGlobal.h"
#include <QTabBar>
#include <QMargins>
/**
* @brief The SARibbonTabBar class
*/
class SA_RIBBON_EXPORT SARibbonTabBar : public QTabBar
{
Q_OBJECT
public:
explicit SARibbonTabBar(QWidget* parent = nullptr);
~SARibbonTabBar();
const QMargins& tabMargin() const;
void setTabMargin(const QMargins& tabMargin);
protected:
QSize tabSizeHint(int index) const;
private:
QMargins m_tabMargin;
};
#endif // SARIBBONTABBAR_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
#ifndef SARIBBONTOOLBUTTON_H
#define SARIBBONTOOLBUTTON_H
#include "SARibbonGlobal.h"
#include <QToolButton>
#include <QDebug>
/**
* @brief Ribbon界面适用的toolButton
*
* 相对于普通toolbutton,主要多了两个类型设置,@ref setButtonType 和 @ref setLargeButtonType
*
* @note @sa setIconSize 函数不在起作用,iconsize是根据当前尺寸动态调整的
*/
class SA_RIBBON_EXPORT SARibbonToolButton : public QToolButton
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonToolButton)
public:
/**
* @brief 按钮样式
*/
enum RibbonButtonType
{
LargeButton,
SmallButton
};
Q_ENUM(RibbonButtonType)
public:
explicit SARibbonToolButton(QWidget* parent = nullptr);
explicit SARibbonToolButton(QAction* defaultAction, QWidget* parent = nullptr);
~SARibbonToolButton();
//标记按钮的样式,按钮的样式有不同的渲染方式
RibbonButtonType buttonType() const;
void setButtonType(const RibbonButtonType& buttonType);
//是否是小按钮
bool isSmallRibbonButton() const;
//是否是大按钮
bool isLargeRibbonButton() const;
//最小尺寸提示
virtual QSize minimumSizeHint() const Q_DECL_OVERRIDE;
//获取间距
int spacing() const;
//更新尺寸
void updateRect();
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
public:
//在lite模式下是否允许文字换行
static void setEnableWordWrap(bool on);
static bool isEnableWordWrap();
protected:
virtual void paintEvent(QPaintEvent* e) Q_DECL_OVERRIDE;
virtual void resizeEvent(QResizeEvent* e) Q_DECL_OVERRIDE;
virtual void mouseMoveEvent(QMouseEvent* e) Q_DECL_OVERRIDE;
virtual void mousePressEvent(QMouseEvent* e) Q_DECL_OVERRIDE;
virtual void mouseReleaseEvent(QMouseEvent* e) Q_DECL_OVERRIDE;
virtual void focusOutEvent(QFocusEvent* e) Q_DECL_OVERRIDE;
virtual void leaveEvent(QEvent* e) Q_DECL_OVERRIDE;
virtual bool hitButton(const QPoint& pos) const Q_DECL_OVERRIDE;
virtual bool event(QEvent* e) Q_DECL_OVERRIDE;
//事件改变 - 主要为了捕获字体的改变
virtual void changeEvent(QEvent* e) Q_DECL_OVERRIDE;
virtual void actionEvent(QActionEvent* e) Q_DECL_OVERRIDE;
protected:
//绘制按钮
virtual void paintButton(QPainter& p, const QStyleOptionToolButton& opt);
//绘制图标
virtual void paintIcon(QPainter& p, const QStyleOptionToolButton& opt, const QRect& iconDrawRect);
//绘制文本
virtual void paintText(QPainter& p, const QStyleOptionToolButton& opt, const QRect& textDrawRect);
//绘制Indicator
virtual void paintIndicator(QPainter& p, const QStyleOptionToolButton& opt, const QRect& indicatorDrawRect);
private:
static void drawArrow(const QStyle* style,
const QStyleOptionToolButton* toolbutton,
const QRect& rect,
QPainter* painter,
const QWidget* widget = 0);
protected:
};
namespace SA
{
QDebug operator<<(QDebug debug, const QStyleOptionToolButton& opt);
}
#endif // SARIBBONTOOLBUTTON_H
@@ -0,0 +1,227 @@
#include "SARibbonWidget.h"
#include "SARibbonBar.h"
#include "SARibbonTabBar.h"
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QScreen>
/**
* @brief The SARibbonWidget::PrivateData class
*/
class SARibbonWidget::PrivateData
{
SA_RIBBON_DECLARE_PUBLIC(SARibbonWidget)
public:
PrivateData(SARibbonWidget* p);
void installFrameless(SARibbonWidget* p);
public:
SARibbonTheme mCurrentRibbonTheme { SARibbonTheme::RibbonThemeOffice2021Blue };
};
SARibbonWidget::PrivateData::PrivateData(SARibbonWidget* p) : q_ptr(p)
{
}
//===================================================
// SARibbonWidget
//===================================================
SARibbonWidget::SARibbonWidget(QWidget* parent) : QWidget(parent), d_ptr(new SARibbonWidget::PrivateData(this))
{
// 直接创建SARibbonBar
QVBoxLayout* verticalLayout = new QVBoxLayout(this);
verticalLayout->setSpacing(1);
verticalLayout->setObjectName("verticalLayout");
verticalLayout->setContentsMargins(0, 0, 0, 0);
SARibbonBar* ribbon = new SARibbonBar(this);
setRibbonBar(ribbon);
connect(qApp, &QApplication::primaryScreenChanged, this, &SARibbonWidget::onPrimaryScreenChanged);
}
SARibbonWidget::~SARibbonWidget()
{
}
/**
* @brief 返回ribbonbar,如果不是使用ribbon模式,返回nullptr
* @return
*/
SARibbonBar* SARibbonWidget::ribbonBar() const
{
QLayout* lay = layout();
if (lay) {
return qobject_cast< SARibbonBar* >(lay->menuBar());
}
return nullptr;
}
/**
* @brief 设置ribbonbar
* @param bar
*/
void SARibbonWidget::setRibbonBar(SARibbonBar* bar)
{
QLayout* lay = layout();
if (lay) {
lay->setMenuBar(bar);
}
}
/**
* @brief SARibbonMainWindow::setRibbonTheme
*
* 注意主题在构造函数设置主题会不完全生效,使用QTimer投放到队列最后执行即可
* @code
* QTimer::singleShot(0, this, [ this ]() { this->setRibbonTheme(SARibbonMainWindow::RibbonThemeDark); });
* @endcode
* @param theme
*/
void SARibbonWidget::setRibbonTheme(SARibbonTheme theme)
{
sa_set_ribbon_theme(this, theme);
d_ptr->mCurrentRibbonTheme = theme;
if (SARibbonBar* bar = ribbonBar()) {
auto theme = ribbonTheme();
// 尺寸修正
switch (theme) {
case SARibbonTheme::RibbonThemeWindows7:
case SARibbonTheme::RibbonThemeOffice2013:
case SARibbonTheme::RibbonThemeOffice2016Blue:
case SARibbonTheme::RibbonThemeDark:
case SARibbonTheme::RibbonThemeDark2: {
//! 在设置qss后需要针对margin信息重新设置进SARibbonTabBar中
//! office2013.qss的margin信息如下设置
//! margin-top: 0px;
//! margin-right: 0px;
//! margin-left: 5px;
//! margin-bottom: 0px;
SARibbonTabBar* tab = bar->ribbonTabBar();
if (!tab) {
break;
}
tab->setTabMargin(QMargins(5, 0, 0, 0));
} break;
case SARibbonTheme::RibbonThemeOffice2021Blue: {
SARibbonTabBar* tab = bar->ribbonTabBar();
if (!tab) {
break;
}
//! 在设置qss后需要针对margin信息重新设置进SARibbonTabBar中
//! office2021.qss的margin信息如下设置
//! margin-top: 0px;
//! margin-right: 5px;
//! margin-left: 5px;
//! margin-bottom: 0px;
tab->setTabMargin(QMargins(5, 0, 5, 0));
}
default:
break;
}
// 上下文标签颜色设置,以及基线颜色设置
switch (theme) {
case SARibbonTheme::RibbonThemeWindows7:
case SARibbonTheme::RibbonThemeOffice2013:
case SARibbonTheme::RibbonThemeDark:
bar->setContextCategoryColorList(QList< QColor >()); //< 设置空颜色列表会重置为默认色系
break;
case SARibbonTheme::RibbonThemeOffice2016Blue:
bar->setContextCategoryColorList(QList< QColor >() << QColor(18, 64, 120)); //< 设置空颜色列表会重置为默认色系
break;
case SARibbonTheme::RibbonThemeOffice2021Blue:
bar->setContextCategoryColorList(QList< QColor >() << QColor(209, 207, 209)); //< 设置空颜色列表会重置为默认色系
break;
default:
break;
}
// 基线颜色设置
if (SARibbonTheme::RibbonThemeOffice2013 == theme) {
bar->setTabBarBaseLineColor(QColor(186, 201, 219));
} else {
bar->setTabBarBaseLineColor(QColor());
}
}
}
SARibbonTheme SARibbonWidget::ribbonTheme() const
{
return (d_ptr->mCurrentRibbonTheme);
}
bool SARibbonWidget::isUseRibbon() const
{
return (nullptr != ribbonBar());
}
/**
@brief 设置窗口
@param 窗口指针
@note 窗口的所有权归SARibbonWidget管理
@sa widget
@note 原来设置的窗口会被delete
*/
void SARibbonWidget::setWidget(QWidget* w)
{
QWidget* oldwidget = takeWidget();
if (oldwidget) {
oldwidget->hide();
oldwidget->deleteLater();
}
if (QLayout* lay = layout()) {
lay->addWidget(w);
}
}
/**
@brief 获取设置的窗口
@return 如果没有,返回nullptr
*/
QWidget* SARibbonWidget::widget() const
{
QLayout* lay = layout();
if (lay) {
if (lay->count() == 0) {
return nullptr;
}
return lay->itemAt(0)->widget();
}
return nullptr;
}
QWidget* SARibbonWidget::takeWidget()
{
QLayout* lay = layout();
if (nullptr == lay) {
return nullptr;
}
if (lay->count() == 0) {
return nullptr;
}
auto item = lay->itemAt(0);
if (nullptr == item) {
return nullptr;
}
lay->removeItem(item);
auto w = item->widget();
delete item;
if (!w) {
return nullptr;
}
return w;
}
/**
* @brief 主屏幕切换触发的信号
* @param screen
*/
void SARibbonWidget::onPrimaryScreenChanged(QScreen* screen)
{
Q_UNUSED(screen);
// 主屏幕切换后,从新计算所有尺寸
if (SARibbonBar* bar = ribbonBar()) {
qDebug() << "Primary Screen Changed";
bar->updateRibbonGeometry();
}
}
@@ -0,0 +1,45 @@
#ifndef SARIBBONWIDGET_H
#define SARIBBONWIDGET_H
#include "SARibbonGlobal.h"
#include <QWidget>
class SARibbonBar;
class QScreen;
/**
* @brief 带SARibbonBar的Widget
*
* @note 注意,SARibbonWidget利用了布局来把ribbonbar放到最顶部,不要给此widget添加布局,否则会导致布局异常,
* 正确的做法是把布局的窗口通过setWidget设置进SARibbonWidget
*
*/
class SA_RIBBON_EXPORT SARibbonWidget : public QWidget
{
Q_OBJECT
SA_RIBBON_DECLARE_PRIVATE(SARibbonWidget)
friend class SARibbonBar;
Q_PROPERTY(SARibbonTheme ribbonTheme READ ribbonTheme WRITE setRibbonTheme)
public:
explicit SARibbonWidget(QWidget* parent = nullptr);
~SARibbonWidget();
// 返回SARibbonBar
SARibbonBar* ribbonBar() const;
// 设置ribbonbar
void setRibbonBar(SARibbonBar* bar);
// 注意主题在构造函数设置主题会不完全生效,使用QTimer投放到队列最后执行即可
// QTimer::singleShot(0, this, [ this ]() { this->setRibbonTheme(SARibbonTheme::RibbonThemeDark); });
void setRibbonTheme(SARibbonTheme theme);
SARibbonTheme ribbonTheme() const;
// 判断当前是否使用ribbon模式
bool isUseRibbon() const;
// 设置窗口
void setWidget(QWidget* w);
QWidget* widget() const;
//
QWidget* takeWidget();
private Q_SLOTS:
void onPrimaryScreenChanged(QScreen* screen);
};
#endif // SARIBBONWIDGET_H
@@ -0,0 +1,504 @@
#include "SAColorGridWidget.h"
#include "SAColorToolButton.h"
#include <QGridLayout>
#include <cmath>
#include <QButtonGroup>
class SAColorGridWidget::PrivateData
{
SA_COLOR_WIDGETS_DECLARE_PUBLIC(SAColorGridWidget)
public:
PrivateData(SAColorGridWidget* p);
// 获取ColorToolButton
SAColorToolButton* getColorToolButtonAt(int index);
SAColorToolButton* getColorToolButtonAt(int r, int c);
SAColorToolButton* getCheckedButton() const;
void updateGridColor(bool isRemoveSpacer = false);
void updateGridColorSize();
void updateGridColorCheckable();
void iterationColorBtns(SAColorGridWidget::FunColorBtn fn);
void removeAt(int r, int c);
void setColorAt(const QColor& clr, int r, int c);
bool isSpacer(int r, int c) const;
public:
QList< QColor > mColors;
QGridLayout* mGridLayout { nullptr };
QButtonGroup* mButtonGroup { nullptr };
QSize mIconSize { 16, 16 };
int mColumnCount { 8 }; ///< 列数,行数量会根据列数量来匹配,如果设置-1或者0,说明不限定列数量,这样会只有一行
bool mColorCheckable; ///< 设置颜色是否是checkable
bool mHorizontalSpacerToRight { false }; ///< 最右边是否有弹簧
};
SAColorGridWidget::PrivateData::PrivateData(SAColorGridWidget* p) : q_ptr(p)
{
mGridLayout = new QGridLayout(p);
p->setLayout(mGridLayout);
mGridLayout->setSpacing(0);
mGridLayout->setContentsMargins(1, 1, 1, 1);
mButtonGroup = new QButtonGroup(p);
mButtonGroup->setExclusive(true);
p->setMinimumHeight(mIconSize.height());
p->setMinimumWidth(mIconSize.width());
}
SAColorToolButton* SAColorGridWidget::PrivateData::getColorToolButtonAt(int index)
{
QLayoutItem* item = mGridLayout->itemAt(index);
if (nullptr == item) {
return nullptr;
}
return qobject_cast< SAColorToolButton* >(item->widget());
}
SAColorToolButton* SAColorGridWidget::PrivateData::getColorToolButtonAt(int r, int c)
{
QLayoutItem* item = mGridLayout->itemAtPosition(r, c);
if (nullptr == item) {
return nullptr;
}
return qobject_cast< SAColorToolButton* >(item->widget());
}
SAColorToolButton* SAColorGridWidget::PrivateData::getCheckedButton() const
{
return qobject_cast< SAColorToolButton* >(mButtonGroup->checkedButton());
}
/**
* @brief 根据mColors更新布局
*/
void SAColorGridWidget::PrivateData::updateGridColor(bool isRemoveSpacer)
{
int row = 1;
int col = mColumnCount;
if (col <= 0) {
col = mColors.size();
} else {
row = std::ceil(mColors.size() / (float)col);
}
int index = 0;
for (int r = 0; r < row; ++r) {
for (int c = 0; c < col; ++c) {
if (index < mColors.size()) {
setColorAt(mColors[ index ], r, c);
++index;
} else {
removeAt(r, c);
}
}
}
// 清除多余单元格
int nowGridRow = mGridLayout->rowCount();
int nowGridCol = mGridLayout->columnCount();
if (nowGridRow > row) {
// 多余的清除
for (int r = row; r < nowGridRow; ++r) {
for (int c = 0; c < nowGridCol; ++c) {
removeAt(r, c);
}
}
}
if (nowGridCol > col) {
// 多余的列清除
for (int r = 0; r < row; ++r) {
for (int c = col; c < nowGridCol; ++c) {
if (isRemoveSpacer) {
removeAt(r, c);
} else {
if (!isSpacer(r, c)) {
removeAt(r, c);
}
}
}
}
}
}
/**
* @brief 更新colorsize
*/
void SAColorGridWidget::PrivateData::updateGridColorSize()
{
QSize s = mIconSize;
iterationColorBtns([ s ](SAColorToolButton* btn) {
if (btn) {
btn->setIconSize(s);
}
});
}
void SAColorGridWidget::PrivateData::updateGridColorCheckable()
{
bool v = mColorCheckable;
iterationColorBtns([ v ](SAColorToolButton* btn) {
if (btn) {
btn->setCheckable(v);
}
});
}
/**
* @brief 遍历所有的button
* @param fn
*/
void SAColorGridWidget::PrivateData::iterationColorBtns(FunColorBtn fn)
{
int cnt = mGridLayout->count();
for (int i = 0; i < cnt; ++i) {
SAColorToolButton* tl = getColorToolButtonAt(i);
fn(tl);
}
}
/**
* @brief 删除网格窗口
* @param r
* @param c
*/
void SAColorGridWidget::PrivateData::removeAt(int r, int c)
{
QLayoutItem* item = mGridLayout->itemAtPosition(r, c);
if (item) {
QWidget* w = item->widget();
mGridLayout->removeItem(item);
delete item;
if (w) {
w->deleteLater();
}
}
}
void SAColorGridWidget::PrivateData::setColorAt(const QColor& clr, int r, int c)
{
QLayoutItem* item = mGridLayout->itemAtPosition(r, c);
if (item) {
SAColorToolButton* tl = qobject_cast< SAColorToolButton* >(item->widget());
if (tl) {
tl->setColor(clr);
}
} else {
SAColorToolButton* tl = new SAColorToolButton(SAColorToolButton::NoColorMenu, q_ptr);
tl->setToolButtonStyle(Qt::ToolButtonIconOnly);
tl->setIconSize(mIconSize);
tl->setMargins(QMargins(4, 4, 4, 4));
tl->setColor(clr);
tl->setCheckable(mColorCheckable);
tl->setAutoRaise(true);
mButtonGroup->addButton(tl, r + c);
mGridLayout->addWidget(tl, r, c);
}
}
bool SAColorGridWidget::PrivateData::isSpacer(int r, int c) const
{
QLayoutItem* item = mGridLayout->itemAtPosition(r, c);
if (item) {
if (QSpacerItem* si = dynamic_cast< QSpacerItem* >(item)) {
return true;
}
}
return false;
}
//==============================================================
// SAColorGridWidget
//==============================================================
SAColorGridWidget::SAColorGridWidget(QWidget* par) : QWidget(par), d_ptr(new SAColorGridWidget::PrivateData(this))
{
connect(d_ptr->mButtonGroup,
QOverload< QAbstractButton* >::of(&QButtonGroup::buttonClicked),
this,
&SAColorGridWidget::onButtonClicked);
connect(d_ptr->mButtonGroup,
QOverload< QAbstractButton* >::of(&QButtonGroup::buttonPressed),
this,
&SAColorGridWidget::onButtonPressed);
connect(d_ptr->mButtonGroup,
QOverload< QAbstractButton* >::of(&QButtonGroup::buttonReleased),
this,
&SAColorGridWidget::onButtonReleased);
connect(d_ptr->mButtonGroup,
QOverload< QAbstractButton*, bool >::of(&QButtonGroup::buttonToggled),
this,
&SAColorGridWidget::onButtonToggled);
}
SAColorGridWidget::~SAColorGridWidget()
{
}
/**
* @brief 设置列数,行数量会根据列数量来匹配,如果设置-1或者0,说明不限定列数量,这样会只有一行
* @param c
*/
void SAColorGridWidget::setColumnCount(int c)
{
d_ptr->mColumnCount = c;
d_ptr->updateGridColor(true);
if (d_ptr->mHorizontalSpacerToRight) {
setHorizontalSpacerToRight();
}
updateGeometry();
}
/**
* @brief 设置颜色列表
* @param c
*/
void SAColorGridWidget::setColorList(const QList< QColor >& cls)
{
d_ptr->mColors = cls;
d_ptr->updateGridColor();
updateGeometry();
}
/**
* @brief 获取颜色列表
* @return
*/
QList< QColor > SAColorGridWidget::getColorList() const
{
return d_ptr->mColors;
}
/**
* @brief 获取间隔
* @return
*/
int SAColorGridWidget::spacing() const
{
return d_ptr->mGridLayout->spacing();
}
/**
* @brief 设置间隔
* @param v
*/
void SAColorGridWidget::setSpacing(int v)
{
d_ptr->mGridLayout->setSpacing(v);
}
/**
* @brief 获取颜色的数量
* @return
*/
int SAColorGridWidget::colorCount() const
{
return d_ptr->mColors.size();
}
/**
* @brief 设置图标 size
* @return
*/
void SAColorGridWidget::setColorIconSize(const QSize& s)
{
d_ptr->mIconSize = s;
setMinimumHeight(s.height());
setMinimumWidth(s.width());
d_ptr->updateGridColorSize();
}
/**
* @brief 获取图标 size
* @return
*/
QSize SAColorGridWidget::colorIconSize() const
{
return d_ptr->mIconSize;
}
/**
* @brief 设置颜色是否是checkable
*
* checkable的颜色按钮是可checked的
* @param on
*/
void SAColorGridWidget::setColorCheckable(bool on)
{
d_ptr->mColorCheckable = on;
d_ptr->updateGridColorCheckable();
}
/**
* @brief 颜色是否是checkable
* @return
*/
bool SAColorGridWidget::isColorCheckable() const
{
return d_ptr->mColorCheckable;
}
/**
* @brief 获取当前选中的颜色
* @return
*/
QColor SAColorGridWidget::currentCheckedColor() const
{
QAbstractButton* btn = d_ptr->mButtonGroup->checkedButton();
if (nullptr == btn) {
return QColor();
}
SAColorToolButton* t = qobject_cast< SAColorToolButton* >(btn);
if (nullptr == t) {
return QColor();
}
return t->color();
}
/**
* @brief 获取index对应的colorbutton
* @param index
* @return 如果没有返回nullptr
*/
SAColorToolButton* SAColorGridWidget::colorButton(int index) const
{
return d_ptr->getColorToolButtonAt(index);
}
/**
* @brief 等同GridLayout的VerticalSpacing属性
* @param v
*/
void SAColorGridWidget::setVerticalSpacing(int v)
{
d_ptr->mGridLayout->setVerticalSpacing(v);
}
/**
* @brief 等同GridLayout的VerticalSpacing属性
* @return
*/
int SAColorGridWidget::verticalSpacing() const
{
return d_ptr->mGridLayout->verticalSpacing();
}
/**
* @brief 等同GridLayout的HorizontalSpacing属性
* @param v
*/
void SAColorGridWidget::setHorizontalSpacing(int v)
{
d_ptr->mGridLayout->setHorizontalSpacing(v);
}
/**
* @brief 等同GridLayout的HorizontalSpacing属性
* @return
*/
int SAColorGridWidget::horizontalSpacing() const
{
return d_ptr->mGridLayout->horizontalSpacing();
}
/**
* @brief 清除选中状态,这时没有颜色是选中的
*/
void SAColorGridWidget::clearCheckedState()
{
if (d_ptr->mButtonGroup->exclusive()) {
SAColorToolButton* btn = d_ptr->getCheckedButton();
if (btn) {
d_ptr->mButtonGroup->setExclusive(false);
btn->setChecked(false);
d_ptr->mButtonGroup->setExclusive(true);
}
} else {
d_ptr->iterationColorBtns([](SAColorToolButton* btn) {
if (btn->isChecked()) {
btn->setChecked(false);
}
});
}
}
void SAColorGridWidget::iterationColorBtns(SAColorGridWidget::FunColorBtn fn)
{
d_ptr->iterationColorBtns(fn);
}
void SAColorGridWidget::setRowMinimumHeight(int row, int minSize)
{
d_ptr->mGridLayout->setRowMinimumHeight(row, minSize);
}
void SAColorGridWidget::setHorizontalSpacerToRight(bool on)
{
if (on) {
QSpacerItem* horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
d_ptr->mGridLayout->addItem(horizontalSpacer, 0, d_ptr->mColumnCount, 1, 1);
} else {
d_ptr->removeAt(0, d_ptr->mColumnCount);
}
d_ptr->mHorizontalSpacerToRight = on;
}
void SAColorGridWidget::onButtonClicked(QAbstractButton* btn)
{
SAColorToolButton* t = qobject_cast< SAColorToolButton* >(btn);
if (t) {
Q_EMIT colorClicked(t->color());
}
}
void SAColorGridWidget::onButtonPressed(QAbstractButton* btn)
{
SAColorToolButton* t = qobject_cast< SAColorToolButton* >(btn);
if (t) {
Q_EMIT colorPressed(t->color());
}
}
void SAColorGridWidget::onButtonToggled(QAbstractButton* btn, bool on)
{
SAColorToolButton* t = qobject_cast< SAColorToolButton* >(btn);
if (t) {
Q_EMIT colorToggled(t->color(), on);
}
}
QSize SAColorGridWidget::sizeHint() const
{
return d_ptr->mGridLayout->sizeHint();
// int w = d_ptr->mIconSize.width() + d_ptr->mGridLayout->verticalSpacing();
// int h = d_ptr->mIconSize.height();
// if (d_ptr->mColumnCount > 0) {
// h *= d_ptr->mColumnCount;
// if (!d_ptr->mColors.empty()) {
// int r = std::ceil(d_ptr->mColors.size() / (float)(d_ptr->mColumnCount));
// if (r > 0) {
// w *= r;
// }
// }
// }
// return QSize(w, h);
}
void SAColorGridWidget::onButtonReleased(QAbstractButton* btn)
{
SAColorToolButton* t = qobject_cast< SAColorToolButton* >(btn);
if (t) {
Q_EMIT colorReleased(t->color());
}
}
namespace SA
{
QList< QColor > getStandardColorList()
{
static QList< QColor > s_standardColorList({ QColor(192, 0, 0),
QColor(255, 0, 0),
QColor(255, 192, 0),
QColor(255, 255, 0),
QColor(146, 208, 80),
QColor(0, 176, 80),
QColor(0, 176, 240),
QColor(0, 112, 192),
QColor(0, 32, 96),
QColor(112, 48, 160) });
return s_standardColorList;
}
}
@@ -0,0 +1,93 @@
#ifndef SACOLORGRIDWIDGET_H
#define SACOLORGRIDWIDGET_H
#include <QWidget>
#include <functional>
#include "SAColorWidgetsGlobal.h"
class QAbstractButton;
class SAColorToolButton;
/**
* @brief 一个grid的Color布局
*
* 一个形如下面的颜色grid
*
* □□□□□□□□□
*
* □□□□□□□□□
*
* □□□□□□□□□
*/
class SA_COLOR_WIDGETS_API SAColorGridWidget : public QWidget
{
Q_OBJECT
SA_COLOR_WIDGETS_DECLARE_PRIVATE(SAColorGridWidget)
Q_PROPERTY(int spacing READ spacing WRITE setSpacing)
public:
using FunColorBtn = std::function< void(SAColorToolButton*) >;
public:
SAColorGridWidget(QWidget* par = nullptr);
~SAColorGridWidget();
// 设置列数,行数量会根据列数量来匹配,如果设置-1或者0,说明不限定列数量,这样会只有一行
void setColumnCount(int c);
int columnCount() const;
// 设置当前的颜色列表
void setColorList(const QList< QColor >& cls);
QList< QColor > getColorList() const;
// 间隔
int spacing() const;
void setSpacing(int v);
// 获取颜色的数量
int colorCount() const;
// 图标的尺寸
void setColorIconSize(const QSize& s);
QSize colorIconSize() const;
// 设置颜色是否是checkable
void setColorCheckable(bool on = true);
bool isColorCheckable() const;
// 获取当前选中的颜色
QColor currentCheckedColor() const;
// 获取index对于的colorbutton
SAColorToolButton* colorButton(int index) const;
// 垂直间距
void setVerticalSpacing(int v);
int verticalSpacing() const;
// 水平间距
void setHorizontalSpacing(int v);
int horizontalSpacing() const;
// 清除当前选中状态,这时没有颜色是选中的
void clearCheckedState();
// 对所有的colorbtn就行遍历处理,可以通过此函数进行tooltip设置等操作
void iterationColorBtns(FunColorBtn fn);
// 设置行最小高度
void setRowMinimumHeight(int row, int minSize);
// 让颜色块左对齐
void setHorizontalSpacerToRight(bool on = true);
private Q_SLOTS:
void onButtonClicked(QAbstractButton* btn);
void onButtonPressed(QAbstractButton* btn);
void onButtonReleased(QAbstractButton* btn);
void onButtonToggled(QAbstractButton* btn, bool on);
Q_SIGNALS:
/**
* @brief 对于check模式,check的颜色触发的信号
* @param c
* @param on
*/
void colorClicked(const QColor& c);
void colorPressed(const QColor& c);
void colorReleased(const QColor& c);
void colorToggled(const QColor& c, bool on);
public:
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
};
namespace SA
{
/**
* @brief 获取标准色列表(一共10种颜色)
* @return
*/
SA_COLOR_WIDGETS_API QList< QColor > getStandardColorList();
}
#endif // SACOLORGRIDWIDGET_H
@@ -0,0 +1,260 @@
#include "SAColorMenu.h"
#include <QWidgetAction>
#include <QColorDialog>
#include <QDebug>
#include <QLabel>
#include <QGridLayout>
#include <QPainter>
#include "SAColorGridWidget.h"
#include "SAColorPaletteGridWidget.h"
#include "SAColorToolButton.h"
class SAColorMenu::PrivateData
{
SA_COLOR_WIDGETS_DECLARE_PUBLIC(SAColorMenu)
public:
PrivateData(SAColorMenu* p);
QColor getColorByDialog();
void recordCustomColor(const QColor& c);
QWidgetAction* addWidget(QWidget* w);
//创建一个无颜色的icon
QIcon createNoneColorIcon(QSize baseSize = QSize(32, 32));
public:
QLabel* mTitleLabel { nullptr }; ///< 主题颜色标题
QWidgetAction* mTitleLabelAction { nullptr };
SAColorPaletteGridWidget* mThemeColorsWidget { nullptr }; ///< 主题颜色
QWidgetAction* mThemeColorPaletteAction { nullptr }; ///< ThemeColorsPalette对应的action
QAction* mCustomColorAction { nullptr }; ///< 自定义颜色action
SAColorGridWidget* mCustomColorsWidget { nullptr }; ///< 自定义颜色记录
QWidgetAction* mCustomColorsWidgetAction { nullptr }; ///< 自定义颜色窗口对应的action
QAction* mNoneColorAction { nullptr }; ///< 无颜色action
QList< QColor > mCustomColors;
int mMaxCustomColorSize { 10 }; ///< 记录最多的自定义颜色数量
QScopedPointer< QColorDialog > mColorDlg { nullptr }; ///<颜色对话框
};
SAColorMenu::PrivateData::PrivateData(SAColorMenu* p) : q_ptr(p)
{
}
QColor SAColorMenu::PrivateData::getColorByDialog()
{
if (nullptr == mColorDlg) {
mColorDlg.reset(new QColorDialog());
}
if (QDialog::Accepted == mColorDlg->exec()) {
return mColorDlg->currentColor();
}
return QColor();
}
void SAColorMenu::PrivateData::recordCustomColor(const QColor& c)
{
if (mCustomColors.size() < mMaxCustomColorSize) {
mCustomColors.push_back(c);
} else {
//超过数量,就左移动
for (int i = 1; i < mCustomColors.size(); ++i) {
mCustomColors[ i - 1 ] = mCustomColors[ i ];
}
mCustomColors.back() = c;
}
}
QWidgetAction* SAColorMenu::PrivateData::addWidget(QWidget* w)
{
QWidgetAction* wa = new QWidgetAction(q_ptr);
wa->setDefaultWidget(w);
q_ptr->addAction(wa);
return wa;
}
QIcon SAColorMenu::PrivateData::createNoneColorIcon(QSize baseSize)
{
QPixmap pixmap(baseSize);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
SAColorToolButton::paintNoneColor(&p, QRect(0, 0, pixmap.width(), pixmap.height()).adjusted(1, 1, -1, -1));
return QIcon(pixmap);
}
//===================================================
// SAColorMenu
//===================================================
SAColorMenu::SAColorMenu(QWidget* parent) : QMenu(parent), d_ptr(new SAColorMenu::PrivateData(this))
{
init(SA::getStandardColorList());
}
SAColorMenu::SAColorMenu(const QString& title, QWidget* parent)
: QMenu(title, parent), d_ptr(new SAColorMenu::PrivateData(this))
{
init(SA::getStandardColorList());
}
SAColorMenu::~SAColorMenu()
{
}
/**
* @brief 快速绑定ColorToolButton
* @param btn
*/
void SAColorMenu::bindToColorToolButton(SAColorToolButton* btn)
{
if (!btn) {
return;
}
if (btn->menu() != this) {
btn->setMenu(this);
}
connect(this, &SAColorMenu::selectedColor, btn, &SAColorToolButton::setColor);
}
/**
* @brief ThemeColorsPalette对应的action
* @return
*/
QWidgetAction* SAColorMenu::themeColorsPaletteAction() const
{
return d_ptr->mThemeColorPaletteAction;
}
/**
* @brief CustomColorsWidget对应的action
* @return
*/
QWidgetAction* SAColorMenu::getCustomColorsWidgetAction() const
{
return d_ptr->mCustomColorsWidgetAction;
}
/**
* @brief 自定义颜色action
* @return
*/
QAction* SAColorMenu::customColorAction() const
{
return d_ptr->mCustomColorAction;
}
/**
* @brief 获取ThemeColorsPalette
* @return
*/
SAColorPaletteGridWidget* SAColorMenu::colorPaletteGridWidget() const
{
return d_ptr->mThemeColorsWidget;
}
/**
* @brief 获取自定义颜色grid
* @return
*/
SAColorGridWidget* SAColorMenu::customColorsWidget() const
{
return d_ptr->mCustomColorsWidget;
}
/**
* @brief 建立没有颜色的action,可以选择无颜色
*
* 无颜色选中会发射selectedColor(QColor())
* @param on
*/
void SAColorMenu::enableNoneColorAction(bool on)
{
//无颜色默认是在自定义颜色的上方
if (on) {
if (d_ptr->mNoneColorAction) {
if (actions().contains(d_ptr->mNoneColorAction)) {
//已经包含了NoneColorAction,退出
return;
} else {
insertAction(d_ptr->mCustomColorAction, d_ptr->mNoneColorAction);
}
} else {
QIcon ic = d_ptr->createNoneColorIcon();
d_ptr->mNoneColorAction = new QAction(ic, tr("None"), this);
connect(d_ptr->mNoneColorAction, &QAction::triggered, this, &SAColorMenu::onNoneColorActionTriggered);
insertAction(d_ptr->mCustomColorAction, d_ptr->mNoneColorAction);
}
} else {
removeAction(d_ptr->mNoneColorAction);
// remove后暂时不删除action
}
}
/**
* @brief 获取None Color Action
*
* @note 注意,enableNoneColorAction(true),之后才不是nullptr
* @return 如果没有建立NoneColorAction,会返回nullptr
*/
QAction* SAColorMenu::noneColorAction() const
{
return d_ptr->mNoneColorAction;
}
/**
* @brief 这是一个辅助槽函数,为了让用户自定义的其他action也能关联menu,可以调用此槽函数,实现selectedColor信号以及menu的隐藏
* @param c
*/
void SAColorMenu::emitSelectedColor(const QColor& c)
{
Q_EMIT selectedColor(c);
hide();
}
void SAColorMenu::init(const QList< QColor >& themeCls)
{
d_ptr->mTitleLabel = new QLabel(this);
d_ptr->mTitleLabel->setText(tr("Theme Colors"));
d_ptr->mTitleLabelAction = d_ptr->addWidget(d_ptr->mTitleLabel);
d_ptr->mThemeColorsWidget = new SAColorPaletteGridWidget(themeCls, this);
d_ptr->mThemeColorsWidget->setColorCheckable(false);
d_ptr->mThemeColorPaletteAction = d_ptr->addWidget(d_ptr->mThemeColorsWidget);
d_ptr->mCustomColorAction = new QAction(tr("Custom Color"), this); // cn:自定义颜色
addAction(d_ptr->mCustomColorAction);
QSize clrSize = d_ptr->mThemeColorsWidget->colorIconSize();
d_ptr->mCustomColorsWidget = new SAColorGridWidget(this);
d_ptr->mCustomColorsWidget->setRowMinimumHeight(0, clrSize.height());
d_ptr->mCustomColorsWidget->setHorizontalSpacerToRight();
d_ptr->mCustomColorsWidget->setColorIconSize(clrSize);
d_ptr->mCustomColorsWidget->setColumnCount(d_ptr->mMaxCustomColorSize);
d_ptr->mCustomColorsWidget->setColorCheckable(false);
d_ptr->mCustomColorsWidgetAction = d_ptr->addWidget(d_ptr->mCustomColorsWidget);
connect(d_ptr->mCustomColorAction, &QAction::triggered, this, &SAColorMenu::onCustomColorActionTriggered);
connect(d_ptr->mThemeColorsWidget, &SAColorPaletteGridWidget::colorClicked, this, &SAColorMenu::emitSelectedColor);
connect(d_ptr->mCustomColorsWidget, &SAColorGridWidget::colorClicked, this, &SAColorMenu::emitSelectedColor);
}
/**
* @brief 自定义颜色
* @param on
*/
void SAColorMenu::onCustomColorActionTriggered(bool on)
{
Q_UNUSED(on);
QColor c = d_ptr->getColorByDialog();
if (c.isValid()) {
d_ptr->recordCustomColor(c);
d_ptr->mCustomColorsWidget->setColorList(d_ptr->mCustomColors);
updateGeometry();
emitSelectedColor(c);
}
}
/**
* @brief 无颜色
* @param on
*/
void SAColorMenu::onNoneColorActionTriggered(bool on)
{
Q_UNUSED(on);
emitSelectedColor(QColor());
}
@@ -0,0 +1,53 @@
#ifndef SACOLORMENU_H
#define SACOLORMENU_H
#include <QMenu>
#include "SAColorWidgetsGlobal.h"
class QWidgetAction;
class SAColorPaletteGridWidget;
class SAColorGridWidget;
class SAColorToolButton;
/**
* @brief 标准颜色菜单
*/
class SA_COLOR_WIDGETS_API SAColorMenu : public QMenu
{
Q_OBJECT
SA_COLOR_WIDGETS_DECLARE_PRIVATE(SAColorMenu)
public:
explicit SAColorMenu(QWidget* parent = nullptr);
explicit SAColorMenu(const QString& title, QWidget* parent = nullptr);
~SAColorMenu();
// 快速绑定colorbtn
void bindToColorToolButton(SAColorToolButton* btn);
// ThemeColorsPalette对应的action
QWidgetAction* themeColorsPaletteAction() const;
// CustomColorsWidget对应的action
QWidgetAction* getCustomColorsWidgetAction() const;
// 自定义颜色action
QAction* customColorAction() const;
// 获取ThemeColorsPalette
SAColorPaletteGridWidget* colorPaletteGridWidget() const;
// 获取自定义颜色grid
SAColorGridWidget* customColorsWidget() const;
// 构建无颜色action,默认无颜色action是没有的
void enableNoneColorAction(bool on = true);
// 获取None Color Action,注意,enableNoneColorAction(true),之后才不是nullptr
QAction* noneColorAction() const;
public Q_SLOTS:
// 这是一个辅助槽函数,为了让用户自定义的其他action也能关联menu,可以调用此槽函数,实现selectedColor信号以及menu的隐藏
void emitSelectedColor(const QColor& c);
Q_SIGNALS:
/**
* @brief 选择了颜色触发的信号
* @param c
*/
void selectedColor(const QColor& c);
private Q_SLOTS:
void onCustomColorActionTriggered(bool on);
void onNoneColorActionTriggered(bool on);
private:
void init(const QList< QColor >& themeCls);
};
#endif // SACOLORMENU_H
@@ -0,0 +1,171 @@
#include "SAColorPaletteGridWidget.h"
// Qt
#include <QMenu>
#include <QWidgetAction>
#include <QVBoxLayout>
#include <QColorDialog>
// SA
#include "SAColorGridWidget.h"
class SAColorPaletteGridWidget::PrivateData
{
SA_COLOR_WIDGETS_DECLARE_PUBLIC(SAColorPaletteGridWidget)
public:
PrivateData(SAColorPaletteGridWidget* p);
//生成color palette
QList< QColor > makeColorPalette(const QList< QColor >& clrList) const;
public:
QList< int > mFactor { 180, 160, 140, 75, 50 }; ///< palette的比例因子,将调用QColor的lighter函数执行
QVBoxLayout* mLayout { nullptr }; ///< 垂直布局
SAColorGridWidget* mMainColorList { nullptr }; ///< 这个用于显示标准颜色
SAColorGridWidget* mPaletteColorGrid { nullptr }; ///< 这个用于生成3行亮色,2行暗色的palette
};
SAColorPaletteGridWidget::PrivateData::PrivateData(SAColorPaletteGridWidget* p) : q_ptr(p)
{
mLayout = new QVBoxLayout(p);
p->setLayout(mLayout);
mMainColorList = new SAColorGridWidget(p);
mPaletteColorGrid = new SAColorGridWidget(p);
mLayout->addWidget(mMainColorList);
mLayout->addWidget(mPaletteColorGrid);
mLayout->setContentsMargins(1, 1, 1, 1);
mLayout->setSpacing(8);
QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
mMainColorList->setSizePolicy(sizePolicy);
mPaletteColorGrid->setSizePolicy(sizePolicy);
mMainColorList->setColumnCount(0);
mPaletteColorGrid->setVerticalSpacing(0);
}
QList< QColor > SAColorPaletteGridWidget::PrivateData::makeColorPalette(const QList< QColor >& clrList) const
{
QList< QColor > res;
for (int f : qAsConst(mFactor)) {
for (const QColor& c : qAsConst(clrList)) {
res.append(c.lighter(f));
}
}
return res;
}
//==============================================================
// name
//==============================================================
/**
* @brief 使用默认的标准颜色生成一个color palette
* @param par
*/
SAColorPaletteGridWidget::SAColorPaletteGridWidget(QWidget* par) : QWidget(par), d_ptr(new PrivateData(this))
{
init();
setColorList(SA::getStandardColorList());
}
/**
* @brief 根据指定的颜色生成一个color palette
* @param cls
* @param par
*/
SAColorPaletteGridWidget::SAColorPaletteGridWidget(const QList< QColor >& cls, QWidget* par)
: QWidget(par), d_ptr(new PrivateData(this))
{
init();
setColorList(cls);
}
SAColorPaletteGridWidget::~SAColorPaletteGridWidget()
{
}
void SAColorPaletteGridWidget::init()
{
connect(d_ptr->mMainColorList, &SAColorGridWidget::colorClicked, this, &SAColorPaletteGridWidget::onMainColorClicked);
connect(d_ptr->mPaletteColorGrid, &SAColorGridWidget::colorClicked, this, &SAColorPaletteGridWidget::onPaletteColorClicked);
QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setSizePolicy(sizePolicy);
setColorIconSize(QSize(10, 10));
}
/**
* @brief 设置颜色系列,颜色系列的个数决定了主色卡的数量,palette色卡会根据factor进行生成
* @param cls
*/
void SAColorPaletteGridWidget::setColorList(const QList< QColor >& cls)
{
d_ptr->mMainColorList->setColumnCount(0);
d_ptr->mMainColorList->setColorList(cls);
d_ptr->mPaletteColorGrid->setColumnCount(cls.size());
d_ptr->mPaletteColorGrid->setColorList(d_ptr->makeColorPalette(cls));
}
/**
* @brief 获取颜色系列
* @return
*/
QList< QColor > SAColorPaletteGridWidget::colorList() const
{
return d_ptr->mMainColorList->getColorList();
}
/**
* @brief 设置颜色深浅比例factorfactor决定了palette的行数,factor有5个,就有5行
*
* 默认为{ 180, 160, 140, 75, 50 },相当于会有5行,每个系数会对标准颜色执行QColor::lighter操作
* @param factor
*/
void SAColorPaletteGridWidget::setFactor(const QList< int >& factor)
{
d_ptr->mFactor = factor;
d_ptr->mPaletteColorGrid->setColorList(d_ptr->makeColorPalette(d_ptr->mMainColorList->getColorList()));
}
/**
* @brief 获取色卡
* @return
*/
QList< int > SAColorPaletteGridWidget::factor() const
{
return d_ptr->mFactor;
}
/**
* @brief 设置颜色块的尺寸,默认为10*10
* @param s
*/
void SAColorPaletteGridWidget::setColorIconSize(const QSize& s)
{
d_ptr->mMainColorList->setColorIconSize(s);
d_ptr->mPaletteColorGrid->setColorIconSize(s);
}
QSize SAColorPaletteGridWidget::colorIconSize() const
{
return d_ptr->mMainColorList->colorIconSize();
}
/**
* @brief 设置颜色块是否能被checked
* @param on
*/
void SAColorPaletteGridWidget::setColorCheckable(bool on)
{
d_ptr->mMainColorList->setColorCheckable(on);
d_ptr->mPaletteColorGrid->setColorCheckable(on);
}
bool SAColorPaletteGridWidget::isColorCheckable() const
{
return d_ptr->mMainColorList->isColorCheckable();
}
void SAColorPaletteGridWidget::onMainColorClicked(const QColor& c)
{
d_ptr->mPaletteColorGrid->clearCheckedState();
Q_EMIT colorClicked(c);
}
void SAColorPaletteGridWidget::onPaletteColorClicked(const QColor& c)
{
d_ptr->mMainColorList->clearCheckedState();
Q_EMIT colorClicked(c);
}
@@ -0,0 +1,45 @@
#ifndef SACOLORPALETTEGRIDWIDGET_H
#define SACOLORPALETTEGRIDWIDGET_H
#include <QWidget>
#include "SAColorWidgetsGlobal.h"
class QMenu;
class SAColorToolButton;
/**
* @brief 类似office的颜色选择窗口,有一排标准色,下面有一个颜色板,有3行浅色,有2行深色
*/
class SA_COLOR_WIDGETS_API SAColorPaletteGridWidget : public QWidget
{
Q_OBJECT
SA_COLOR_WIDGETS_DECLARE_PRIVATE(SAColorPaletteGridWidget)
public:
SAColorPaletteGridWidget(QWidget* par = nullptr);
SAColorPaletteGridWidget(const QList< QColor >& cls, QWidget* par = nullptr);
~SAColorPaletteGridWidget();
// 设置窗口维护的colorList
void setColorList(const QList< QColor >& cls);
QList< QColor > colorList() const;
// 设置颜色深浅比例factor,默认为{ 180, 160, 140, 75, 50 }
void setFactor(const QList< int >& factor);
QList< int > factor() const;
// 设置iconsize
void setColorIconSize(const QSize& s);
QSize colorIconSize() const;
// 设置颜色是否是checkable
void setColorCheckable(bool on = true);
bool isColorCheckable() const;
private Q_SLOTS:
void onMainColorClicked(const QColor& c);
void onPaletteColorClicked(const QColor& c);
private:
void init();
Q_SIGNALS:
/**
* @brief 对于check模式,check的颜色触发的信号
* @param c
* @param on
*/
void colorClicked(const QColor& c);
};
#endif // SACOLORPALETTEGRIDWIDGET_H
@@ -0,0 +1,631 @@
#include "SAColorToolButton.h"
#include <QApplication>
#include <QPaintEvent>
#include <QStylePainter>
#include <QStyleOption>
#include <QStyleOptionToolButton>
#include <QResizeEvent>
#include <QDebug>
#include "SAColorMenu.h"
#define SAColorToolButton_DEBUG_PRINT 0
class SAColorToolButton::PrivateData
{
SA_COLOR_WIDGETS_DECLARE_PUBLIC(SAColorToolButton)
public:
PrivateData(SAColorToolButton* p);
void calcSizeOfToolButtonIconOnly(const QStyleOptionToolButton& opt, QRect& iconRect, QRect& textRect, QRect& colorRect);
void calcSizeOfToolButtonTextOnly(const QStyleOptionToolButton& opt, QRect& iconRect, QRect& textRect, QRect& colorRect);
void calcSizeOfToolButtonTextBesideIcon(const QStyleOptionToolButton& opt, QRect& iconRect, QRect& textRect, QRect& colorRect);
void calcSizeOfToolButtonTextUnderIcon(const QStyleOptionToolButton& opt, QRect& iconRect, QRect& textRect, QRect& colorRect);
QPixmap createIconPixmap(const QStyleOptionToolButton& opt, const QRect& iconRect);
QRect getButtonRect(const QStyleOptionToolButton& opt);
QRect getIndicatorRect(const QStyleOptionToolButton& opt);
QStyle::State getButtonStyleState(const QStyleOptionToolButton& opt);
QStyle::State getButtonMenuStyleState(const QStyleOptionToolButton& opt);
void removeMenu();
public:
QColor mColor { Qt::white };
int mSpacing { 2 }; ///< 间隔
QMargins mMargins { 3, 3, 3, 3 }; ///< box
ColorToolButtonStyle mColorButtonStyle { WithColorMenu }; ///< 样式
static int s_indicatorArrorWidth; ///< 菜单宽度
};
int SAColorToolButton::PrivateData::s_indicatorArrorWidth = 8;
SAColorToolButton::PrivateData::PrivateData(SAColorToolButton* p) : q_ptr(p)
{
}
void SAColorToolButton::PrivateData::calcSizeOfToolButtonIconOnly(const QStyleOptionToolButton& opt,
QRect& iconRect,
QRect& textRect,
QRect& colorRect)
{
// 确定文本区域
textRect = QRect();
QRect buttonRect = getButtonRect(opt);
if (opt.icon.isNull()) {
colorRect = buttonRect;
iconRect = QRect();
} else {
QSize tmpSize = opt.iconSize;
if (tmpSize.isNull()) {
tmpSize = QSize(16, 16);
}
tmpSize = buttonRect.size().boundedTo(tmpSize);
int colorHeight = tmpSize.height() / 4;
int totalHeight = colorHeight + tmpSize.height() + mSpacing;
if (totalHeight > buttonRect.height()) {
// 过高,这时要通过高度反推tmpSize和colorHeight
colorHeight = (buttonRect.height() - mSpacing) / 5;
tmpSize.setHeight(colorHeight * 4);
if (opt.iconSize.height() > 0) {
tmpSize.setWidth(tmpSize.height() * opt.iconSize.width() / opt.iconSize.height()); // 等比例
}
tmpSize = buttonRect.size().boundedTo(tmpSize);
}
// 有icon,颜色位于图标下面
int y = (buttonRect.height() - colorHeight - mSpacing - tmpSize.height()) / 2; //(ButtonHeight-TotalHeight)/2
int x = (buttonRect.width() - tmpSize.width()) / 2;
iconRect = QRect(buttonRect.left() + x, buttonRect.top() + y, tmpSize.width(), tmpSize.height());
colorRect = QRect(iconRect.x(), iconRect.bottom() + mSpacing, iconRect.width(), colorHeight);
}
}
/**
* @brief 纯文本情况,左边有5像素显示颜色
* @param opt
* @param iconRect
* @param textRect
* @param colorRect 左边有5像素显示颜色
*/
void SAColorToolButton::PrivateData::calcSizeOfToolButtonTextOnly(const QStyleOptionToolButton& opt,
QRect& iconRect,
QRect& textRect,
QRect& colorRect)
{
QRect buttonRect = getButtonRect(opt);
iconRect = QRect();
QSize colorSize = opt.iconSize;
if (colorSize.isNull()) {
colorSize = QSize(16, 16);
}
colorSize = buttonRect.size().boundedTo(colorSize);
colorRect = QRect(buttonRect.left(),
buttonRect.top() + (buttonRect.height() - colorSize.height()) / 2,
colorSize.width(),
colorSize.height());
textRect = buttonRect.adjusted(colorRect.right() + mSpacing, 0, 0, 0);
}
void SAColorToolButton::PrivateData::calcSizeOfToolButtonTextBesideIcon(const QStyleOptionToolButton& opt,
QRect& iconRect,
QRect& textRect,
QRect& colorRect)
{
QRect buttonRect = getButtonRect(opt);
if (opt.icon.isNull()) {
QSize colorSize = opt.iconSize;
if (colorSize.isNull()) {
colorSize = QSize(16, 16);
}
// 说明没有icon
// 这时所有都是color
// iconRect=Null
colorSize = buttonRect.size().boundedTo(colorSize);
iconRect = QRect();
colorRect = QRect(buttonRect.left(),
buttonRect.top() + (buttonRect.height() - colorSize.height()) / 2,
colorSize.width(),
colorSize.height());
textRect = buttonRect.adjusted(colorRect.width() + mSpacing, 0, 0, 0);
} else { // 有图标
QSize tmpSize = opt.iconSize;
if (tmpSize.isNull()) {
tmpSize = QSize(16, 16);
}
tmpSize = buttonRect.size().boundedTo(tmpSize);
int colorHeight = tmpSize.height() / 4;
int totalHeight = colorHeight + tmpSize.height() + mSpacing;
if (totalHeight > buttonRect.height()) {
// 过高,这时要通过高度反推tmpSize和colorHeight
colorHeight = (buttonRect.height() - mSpacing) / 5;
tmpSize.setHeight(colorHeight * 4);
tmpSize.setWidth(tmpSize.height()); // 对于异形也设置为正方行
}
// 有icon,颜色位于图标下面
int y = (buttonRect.height() - colorHeight - mSpacing - tmpSize.height()) / 2; //(ButtonHeight-TotalHeight)/2
iconRect = QRect(buttonRect.left(), buttonRect.top() + y, tmpSize.width(), tmpSize.height());
colorRect = QRect(iconRect.x(), iconRect.bottom() + mSpacing, iconRect.width(), colorHeight);
textRect = buttonRect.adjusted(iconRect.right() + mSpacing, 0, 0, 0);
}
}
void SAColorToolButton::PrivateData::calcSizeOfToolButtonTextUnderIcon(const QStyleOptionToolButton& opt,
QRect& iconRect,
QRect& textRect,
QRect& colorRect)
{
QRect buttonRect = getButtonRect(opt);
QSize tmpSize = opt.iconSize;
if (tmpSize.isNull()) {
tmpSize = QSize(16, 16);
}
// 获取字体高度
int textHeight = opt.fontMetrics.height();
tmpSize = buttonRect.size().boundedTo(tmpSize);
if (opt.icon.isNull()) {
int totalHeight = textHeight + opt.iconSize.height() + mSpacing;
if (totalHeight < buttonRect.height()) {
// 足够高
colorRect = QRect(buttonRect.left() + (buttonRect.width() - tmpSize.width()) / 2,
buttonRect.top() + (buttonRect.height() - totalHeight) / 2,
tmpSize.width(),
opt.iconSize.height());
} else {
// 空间不足
colorRect = QRect(buttonRect.left() + (buttonRect.width() - tmpSize.width()) / 2,
buttonRect.top() + mSpacing,
tmpSize.width(),
opt.iconSize.height());
}
iconRect = QRect();
textRect = QRect(buttonRect.left(), colorRect.bottom() + mSpacing, buttonRect.width(), textHeight);
} else {
// 有图标
int colorHeight = tmpSize.height() / 4;
int totalHeight = textHeight + opt.iconSize.height() + colorHeight + 2 * mSpacing;
if (totalHeight < buttonRect.height()) {
// 高度空间足够
// 先布置icon
iconRect = QRect(buttonRect.left() + (buttonRect.width() - tmpSize.width()) / 2,
buttonRect.top() + (buttonRect.height() - totalHeight) / 2,
tmpSize.width(),
opt.iconSize.height());
} else {
// 空间不足
iconRect = QRect(buttonRect.left() + (buttonRect.width() - tmpSize.width()) / 2,
buttonRect.top() + mSpacing,
tmpSize.width(),
opt.iconSize.height());
}
colorRect = QRect(iconRect.x(), iconRect.bottom() + mSpacing, iconRect.width(), colorHeight);
textRect = QRect(buttonRect.left(), colorRect.bottom() + mSpacing, buttonRect.width(), textHeight);
}
}
QPixmap SAColorToolButton::PrivateData::createIconPixmap(const QStyleOptionToolButton& opt, const QRect& iconRect)
{
if (opt.icon.isNull()) {
return (QPixmap());
}
// 有图标
QIcon::State state = opt.state & QStyle::State_On ? QIcon::On : QIcon::Off;
QIcon::Mode mode;
if (!(opt.state & QStyle::State_Enabled)) {
mode = QIcon::Disabled;
} else if ((opt.state & QStyle::State_MouseOver) && (opt.state & QStyle::State_AutoRaise)) {
mode = QIcon::Active;
} else {
mode = QIcon::Normal;
}
// return (opt.icon.pixmap(this->window()->windowHandle(), opt.rect.size().boundedTo(realConSize), mode, state));
return (opt.icon.pixmap(iconRect.size(), mode, state));
}
/**
* @brief 获取按钮的矩形区域
* @param opt
* @return
*/
QRect SAColorToolButton::PrivateData::getButtonRect(const QStyleOptionToolButton& opt)
{
QRect btnRect = q_ptr->style()->subControlRect(QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton, q_ptr);
return btnRect.marginsRemoved(mMargins);
}
/**
* @brief 获取ToolButtonMenu的区域
* @note 注意,如果不是QStyleOptionToolButton::MenuButtonPopup|QStyleOptionToolButton::HasMenu下调用此函数,返回的是QRect()
* @param opt
* @return
*/
QRect SAColorToolButton::PrivateData::getIndicatorRect(const QStyleOptionToolButton& opt)
{
if (opt.features & QStyleOptionToolButton::MenuButtonPopup || opt.features & QStyleOptionToolButton::HasMenu) {
return q_ptr->style()->subControlRect(QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButtonMenu, q_ptr);
}
return QRect();
}
/**
* @brief 基于Qt qcommonStyle源码,对QStyleOptionToolButton的style修正
* @param opt
* @return
*/
QStyle::State SAColorToolButton::PrivateData::getButtonStyleState(const QStyleOptionToolButton& opt)
{
QStyle::State bflags = opt.state & ~QStyle::State_Sunken;
if (bflags & QStyle::State_AutoRaise) {
if (!(bflags & QStyle::State_MouseOver) || !(bflags & QStyle::State_Enabled)) {
bflags &= ~QStyle::State_Raised;
}
}
if (opt.state & QStyle::State_Sunken) {
if (opt.activeSubControls & QStyle::SC_ToolButton) {
bflags |= QStyle::State_Sunken;
}
}
return bflags;
}
/**
* @brief 基于Qt qcommonStyle源码,对QStyleOptionToolButton的style修正
* @param opt
* @return
*/
QStyle::State SAColorToolButton::PrivateData::getButtonMenuStyleState(const QStyleOptionToolButton& opt)
{
QStyle::State mflags = opt.state & ~QStyle::State_Sunken;
if (mflags & QStyle::State_AutoRaise) {
if (!(mflags & QStyle::State_MouseOver) || !(mflags & QStyle::State_Enabled)) {
mflags &= ~QStyle::State_Raised;
}
}
if (opt.state & QStyle::State_Sunken) {
mflags |= QStyle::State_Sunken;
}
return mflags;
}
void SAColorToolButton::PrivateData::removeMenu()
{
QMenu* m = q_ptr->menu();
if (m) {
q_ptr->setMenu(nullptr);
m->deleteLater();
}
}
//==============================================================
// SAColorToolButton
//==============================================================
SAColorToolButton::SAColorToolButton(QWidget* parent) : QToolButton(parent), d_ptr(new PrivateData(this))
{
setColorToolButtonStyle(WithColorMenu);
connect(this, &QToolButton::clicked, this, &SAColorToolButton::onButtonClicked);
}
SAColorToolButton::SAColorToolButton(ColorToolButtonStyle style, QWidget* parent)
: QToolButton(parent), d_ptr(new PrivateData(this))
{
setColorToolButtonStyle(style);
connect(this, &QToolButton::clicked, this, &SAColorToolButton::onButtonClicked);
}
SAColorToolButton::~SAColorToolButton()
{
}
/**
* @brief 获取颜色
* @return
*/
QColor SAColorToolButton::color() const
{
return d_ptr->mColor;
}
/**
* @brief 设置Margins
* @param mg
*/
void SAColorToolButton::setMargins(const QMargins& mg)
{
d_ptr->mMargins = mg;
repaint();
}
QMargins SAColorToolButton::margins() const
{
return d_ptr->mMargins;
}
/**
* @brief 绘制无颜色表示
* @param p
* @param colorRect 绘制的区域
*/
void SAColorToolButton::paintNoneColor(QPainter* p, const QRect& colorRect)
{
p->save();
QPen pen(Qt::red, 1, Qt::SolidLine, Qt::RoundCap);
p->setPen(pen);
p->setRenderHint(QPainter::SmoothPixmapTransform, true);
p->setRenderHint(QPainter::Antialiasing, true);
int ss = colorRect.width() / 3;
p->drawLine(QPoint(colorRect.x() + ss, colorRect.bottom()), QPoint(colorRect.right() - ss, colorRect.top()));
pen.setColor(Qt::black);
p->setPen(pen);
p->drawRect(colorRect);
p->restore();
}
/**
* @brief 设置颜色按钮的样式
* @param s
*/
void SAColorToolButton::setColorToolButtonStyle(ColorToolButtonStyle s)
{
if (NoColorMenu == s) {
d_ptr->removeMenu();
} else {
createColorMenu();
}
d_ptr->mColorButtonStyle = s;
}
/**
* @brief 颜色按钮的样式
* @return
*/
SAColorToolButton::ColorToolButtonStyle SAColorToolButton::colorToolButtonStyle() const
{
return d_ptr->mColorButtonStyle;
}
/**
* @brief 获取colorMenu
*
* @note 注意,这个函数很有可能会返回nullptr,如果ColorToolButtonStyle设置为NoColorMenu或者自己设置了菜单,此函数返回nullptr
* @return SAColorMenu指针,如果没有,返回nullptr
*/
SAColorMenu* SAColorToolButton::colorMenu() const
{
return qobject_cast< SAColorMenu* >(menu());
}
/**
* @brief 建立标准的颜色菜单
* @return
*/
SAColorMenu* SAColorToolButton::createColorMenu()
{
setPopupMode(QToolButton::InstantPopup);
SAColorMenu* m = new SAColorMenu(this);
m->enableNoneColorAction(true);
QAction* customColor = m->customColorAction();
if (customColor) {
customColor->setIcon(QIcon(":/image/resource/define-color.svg"));
}
connect(m, &SAColorMenu::selectedColor, this, &SAColorToolButton::setColor);
setMenu(m);
return m;
}
/**
* @brief 设置颜色
* @note 会发射@sa colorChanged 信号
* @param c
*/
void SAColorToolButton::setColor(const QColor& c)
{
if (d_ptr->mColor != c) {
d_ptr->mColor = c;
repaint();
Q_EMIT colorChanged(c);
}
}
/**
* @brief 计算各个基本组件的位置
* @param opt
* @param iconRect 图标区域
* @param textRect 文本区域
* @param colorRect 颜色区域
*/
void SAColorToolButton::calcRect(const QStyleOptionToolButton& opt, QRect& iconRect, QRect& textRect, QRect& colorRect)
{
switch (opt.toolButtonStyle) {
case Qt::ToolButtonTextOnly:
d_ptr->calcSizeOfToolButtonTextOnly(opt, iconRect, textRect, colorRect);
break;
case Qt::ToolButtonTextBesideIcon:
d_ptr->calcSizeOfToolButtonTextBesideIcon(opt, iconRect, textRect, colorRect);
break;
case Qt::ToolButtonTextUnderIcon:
d_ptr->calcSizeOfToolButtonTextUnderIcon(opt, iconRect, textRect, colorRect);
break;
case Qt::ToolButtonFollowStyle:
case Qt::ToolButtonIconOnly:
d_ptr->calcSizeOfToolButtonIconOnly(opt, iconRect, textRect, colorRect);
break;
default:
d_ptr->calcSizeOfToolButtonIconOnly(opt, iconRect, textRect, colorRect);
break;
}
}
void SAColorToolButton::paintEvent(QPaintEvent* e)
{
Q_UNUSED(e);
QStylePainter p(this);
QStyleOptionToolButton opt;
initStyleOption(&opt);
QRect iconRect, colorRect, textRect;
calcRect(opt, iconRect, textRect, colorRect);
paintButton(&p, opt);
// 绘制图标
paintIcon(&p, iconRect, opt);
// 绘制文字
paintText(&p, textRect, opt);
// 绘制颜色
paintColor(&p, colorRect, d_ptr->mColor, opt);
}
void SAColorToolButton::resizeEvent(QResizeEvent* e)
{
// 在resizeevent计算绘图所需的尺寸,避免在绘图过程中实时绘制提高效率
QToolButton::resizeEvent(e);
}
/**
* @brief sizeHint重载
* @return
*/
QSize SAColorToolButton::sizeHint() const
{
ensurePolished();
QStyleOptionToolButton opt;
initStyleOption(&opt);
int w = 0, h = 0;
if (Qt::ToolButtonIconOnly == opt.toolButtonStyle || Qt::ToolButtonFollowStyle == opt.toolButtonStyle) {
// 和文本无关
w = opt.iconSize.width() + d_ptr->mMargins.left() + d_ptr->mMargins.right();
h = opt.iconSize.height() + d_ptr->mMargins.top() + d_ptr->mMargins.bottom();
} else if (Qt::ToolButtonTextOnly == opt.toolButtonStyle || Qt::ToolButtonTextBesideIcon == opt.toolButtonStyle) {
QSize textSize = opt.fontMetrics.size(Qt::TextSingleLine | Qt::TextShowMnemonic, opt.text);
textSize.setHeight(textSize.height() + 4);
QSize iconSize = opt.iconSize;
if (!opt.icon.isNull()) {
// 有图标,要有iconsize高度的1/4给颜色
iconSize.setHeight(iconSize.height() + iconSize.height() / 4 + d_ptr->mSpacing);
}
w = textSize.width() + d_ptr->mSpacing + iconSize.width() + d_ptr->mMargins.left() + d_ptr->mMargins.right();
h = qMax(textSize.height(), iconSize.height()) + d_ptr->mMargins.top() + d_ptr->mMargins.bottom();
} else if (Qt::ToolButtonTextUnderIcon == opt.toolButtonStyle) {
QSize textSize = opt.fontMetrics.size(Qt::TextSingleLine | Qt::TextShowMnemonic, opt.text);
textSize.setHeight(textSize.height() + 4);
QSize iconSize = opt.iconSize;
if (!opt.icon.isNull()) {
// 有图标,要有iconsize高度的1/4给颜色
iconSize.setHeight(iconSize.height() + iconSize.height() / 4 + d_ptr->mSpacing);
}
w = qMax(textSize.width(), iconSize.width()) + d_ptr->mMargins.left() + d_ptr->mMargins.right();
h = textSize.height() + iconSize.height() + d_ptr->mSpacing + d_ptr->mMargins.top() + d_ptr->mMargins.bottom();
}
opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
if (opt.features & QStyleOptionToolButton::MenuButtonPopup || opt.features & QStyleOptionToolButton::HasMenu) {
w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this);
}
//! Qt6.4 取消了QApplication::globalStrut
return style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this).expandedTo(QSize(2, 2));
}
void SAColorToolButton::onButtonClicked(bool checked)
{
Q_EMIT colorClicked(color(), checked);
}
/**
* @brief 绘制按钮
* @param p
* @param opt
*/
void SAColorToolButton::paintButton(QStylePainter* p, const QStyleOptionToolButton& opt)
{
bool autoRaise = opt.state & QStyle::State_AutoRaise;
// 绘制按钮
if (autoRaise) {
style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, p, this);
} else {
style()->drawPrimitive(QStyle::PE_PanelButtonBevel, &opt, p, this);
}
if (opt.features & QStyleOptionToolButton::MenuButtonPopup) {
QStyleOption tool = opt;
tool.state = d_ptr->getButtonMenuStyleState(opt);
tool.rect = d_ptr->getIndicatorRect(opt);
// if (tool.state & (QStyle::State_Sunken | QStyle::State_On | QStyle::State_Raised)) {
// style()->drawPrimitive(QStyle::PE_IndicatorButtonDropDown, &tool, &p, this);
// }
style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &tool, p, this);
}
// 绘制focus
if (opt.state & QStyle::State_HasFocus) {
QStyleOptionFocusRect fr;
fr.QStyleOption::operator=(opt);
fr.rect.adjust(3, 3, -3, -3);
if (opt.features & QStyleOptionToolButton::MenuButtonPopup) {
fr.rect.adjust(0, 0, style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this), 0);
}
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &fr, p, this);
}
}
/**
* @brief 绘制icon
* @param p
* @param iconRect
* @param opt icon信息从QStyleOptionToolButton获取
*/
void SAColorToolButton::paintIcon(QStylePainter* p, const QRect& iconRect, const QStyleOptionToolButton& opt)
{
if (!iconRect.isNull()) {
QPixmap pm = d_ptr->createIconPixmap(opt, iconRect);
style()->drawItemPixmap(p, iconRect, Qt::AlignCenter, pm);
}
}
/**
* @brief 绘制文本
* @param p
* @param textRect
* @param opt
*/
void SAColorToolButton::paintText(QStylePainter* p, const QRect& textRect, const QStyleOptionToolButton& opt)
{
if (opt.text.isEmpty()) {
return;
}
if (Qt::ToolButtonIconOnly == opt.toolButtonStyle) {
return;
}
p->save();
p->setFont(opt.font);
int alignment = Qt::TextShowMnemonic;
// 快捷键的下划线
if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, this)) {
alignment |= Qt::TextHideMnemonic;
}
alignment |= Qt::AlignHCenter | Qt::AlignVCenter;
style()->drawItemText(p,
QStyle::visualRect(opt.direction, opt.rect, textRect),
alignment,
opt.palette,
opt.state & QStyle::State_Enabled,
opt.text,
QPalette::ButtonText);
p->restore();
}
/**
* @brief 绘制color
* @param p
* @param iconRect
* @param opt
*/
void SAColorToolButton::paintColor(QStylePainter* p,
const QRect& colorRect,
const QColor& color,
const QStyleOptionToolButton& opt)
{
Q_UNUSED(opt);
if (colorRect.isNull()) {
return;
}
// 绘制颜色
if (color.isValid()) {
p->fillRect(colorRect, color);
} else {
paintNoneColor(p, colorRect);
}
}
@@ -0,0 +1,105 @@
#ifndef SACOLORTOOLBUTTON_H
#define SACOLORTOOLBUTTON_H
#include <QToolButton>
#include "SAColorWidgetsGlobal.h"
class QPaintEvent;
class QResizeEvent;
class QPainter;
class QStylePainter;
class SAColorMenu;
/**
* @brief 这是一个只显示颜色的toolbutton
*
*
* 在ToolButtonIconOnly模式下,如果没有setIcon,则颜色占用所有区域,如下图所示
*
* ┌─────┐
* │color│
* └─────┘
*
* 如果在ToolButtonIconOnly模式下有图标,图标在上面显示,下面显示颜色,如下图所示
*
* ┌─────┐
* │icon │
* │color│
* └─────┘
*
* 在ToolButtonTextBesideIconToolButtonTextUnderIcon下,setIconSize 可以指定颜色的大小,
* 但只在ToolButtonTextBesideIconToolButtonTextUnderIcon下有效
*
* 如果没有设置图标,也就是setIcon(QIcon()),iconSize作为颜色块的大小
*
* ┌─────────┐
* │┌─┐ │
* │└─┘ │
* └─────────┘
*
* 如果有图标,颜色条会在图标下方,为图标高度的1/4 为图标宽度一致,如若超过控件的大小,会自动缩小体积
*
*/
class SA_COLOR_WIDGETS_API SAColorToolButton : public QToolButton
{
Q_OBJECT
SA_COLOR_WIDGETS_DECLARE_PRIVATE(SAColorToolButton)
public:
/**
* @brief 颜色按钮的内置样式
* @param parent
* @return
*/
enum ColorToolButtonStyle
{
WithColorMenu, ///< 默认会构建一个SAColorMenu
NoColorMenu ///< 没有ColorMenu
};
public:
explicit SAColorToolButton(QWidget* parent = nullptr);
explicit SAColorToolButton(ColorToolButtonStyle style, QWidget* parent = nullptr);
~SAColorToolButton();
// 获取颜色
QColor color() const;
// 设置Margins
void setMargins(const QMargins& mg);
QMargins margins() const;
// 绘制无颜色
static void paintNoneColor(QPainter* p, const QRect& colorRect);
// 设置颜色按钮的样式
void setColorToolButtonStyle(ColorToolButtonStyle s);
ColorToolButtonStyle colorToolButtonStyle() const;
// 获取colorMenu,注意,这个函数很有可能会返回nullptr,如果ColorToolButtonStyle设置为NoColorMenu或者自己设置了菜单,此函数返回nullptr
SAColorMenu* colorMenu() const;
// 建立标准的颜色菜单
SAColorMenu* createColorMenu();
public Q_SLOTS:
// 设置颜色,会发射colorChanged信号
void setColor(const QColor& c);
protected:
// 获取关键的三个rect位置
virtual void calcRect(const QStyleOptionToolButton& opt, QRect& iconRect, QRect& textRect, QRect& colorRect);
virtual void paintButton(QStylePainter* p, const QStyleOptionToolButton& opt);
virtual void paintIcon(QStylePainter* p, const QRect& iconRect, const QStyleOptionToolButton& opt);
virtual void paintText(QStylePainter* p, const QRect& textRect, const QStyleOptionToolButton& opt);
virtual void paintColor(QStylePainter* p, const QRect& colorRect, const QColor& color, const QStyleOptionToolButton& opt);
protected:
virtual void paintEvent(QPaintEvent* e) Q_DECL_OVERRIDE;
virtual void resizeEvent(QResizeEvent* e) Q_DECL_OVERRIDE;
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
private Q_SLOTS:
void onButtonClicked(bool checked = false);
Q_SIGNALS:
/**
* @brief 颜色被点击的响应
* @param color
*/
void colorClicked(const QColor& color, bool checked = false);
/**
* @brief 颜色改变信号
* @param color
*/
void colorChanged(const QColor& color);
};
#endif // SACOLORTOOLBUTTON_H
@@ -0,0 +1,13 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/SAColorGridWidget.h \
$$PWD/SAColorMenu.h \
$$PWD/SAColorPaletteGridWidget.h \
$$PWD/SAColorToolButton.h \
$$PWD/SAColorWidgetsGlobal.h
SOURCES += \
$$PWD/SAColorGridWidget.cpp \
$$PWD/SAColorMenu.cpp \
$$PWD/SAColorPaletteGridWidget.cpp \
$$PWD/SAColorToolButton.cpp
@@ -0,0 +1,58 @@
#ifndef SACOLORWIDGETSGLOBAL_H
#define SACOLORWIDGETSGLOBAL_H
#include <QScopedPointer>
/**
* @def color-widgets的数字版本 MAJ.MIN.PAT
*/
#ifndef SA_COLOR_WIDGETS_VERSION_MAJ
#define SA_COLOR_WIDGETS_VERSION_MAJ 0
#endif
/**
* @def color-widgets的数字版本 MAJ.MIN.PAT
*/
#ifndef SA_COLOR_WIDGETS_VERSION_MIN
#define SA_COLOR_WIDGETS_VERSION_MIN 1
#endif
/**
* @def color-widgets的数字版本 MAJ.MIN.PAT
*/
#ifndef SA_COLOR_WIDGETS_VERSION_PAT
#define SA_COLOR_WIDGETS_VERSION_PAT 0
#endif
/**
* @def 模仿Q_DECLARE_PRIVATE,但不用前置声明而是作为一个内部类
*/
#ifndef SA_COLOR_WIDGETS_DECLARE_PRIVATE
#define SA_COLOR_WIDGETS_DECLARE_PRIVATE(classname) \
class PrivateData; \
friend class classname::PrivateData; \
QScopedPointer< PrivateData > d_ptr;
#endif
/**
* @def 模仿Q_DECLARE_PUBLIC
*/
#ifndef SA_COLOR_WIDGETS_DECLARE_PUBLIC
#define SA_COLOR_WIDGETS_DECLARE_PUBLIC(classname) \
friend class classname; \
classname* q_ptr { nullptr };
#endif
#ifndef SA_COLOR_WIDGETS_NO_DLL
#if defined(SA_COLOR_WIDGETS_MAKE_LIB) // 定义此宏将构建library
#ifndef SA_COLOR_WIDGETS_API
#define SA_COLOR_WIDGETS_API Q_DECL_EXPORT
#endif
#else
#ifndef SA_COLOR_WIDGETS_API
#define SA_COLOR_WIDGETS_API Q_DECL_IMPORT
#endif
#endif
#else
#ifndef SA_COLOR_WIDGETS_API
#define SA_COLOR_WIDGETS_API
#endif
#endif
#endif // SACOLORWIDGETSGLOBAL_H
@@ -0,0 +1,134 @@
#include "Widget.h"
#include "ui_Widget.h"
#include <QMenu>
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <QDebug>
#include <random>
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
setColor(Qt::blue);
QMenu* menu = new QMenu(this);
menu->addAction("1");
menu->addAction("2");
menu->addAction("3");
menu->addAction("4");
menu->addAction("5");
ui->toolButtonIconOnlyHaveIconMenuPopupMode->setMenu(menu);
ui->toolButtonIconOnlyHaveIconMenuPopupMode2->setMenu(menu);
ui->toolButtonIconOnlyHaveIconInstantPop->setMenu(menu);
ui->toolButtonIconOnlyHaveIconInstantPop2->setMenu(menu);
ui->toolButtonIconOnlyHaveIconDelayedPop->setMenu(menu);
ui->toolButtonIconOnlyHaveIconDelayedPop2->setMenu(menu);
ui->toolButtonIconOnlyTextHaveIconMenuPopupMode->setMenu(menu);
ui->toolButtonIconOnlyTextHaveIconMenuPopupMode_1->setMenu(menu);
ui->toolButtonIconOnlyTextHaveIconDelayedPop->setMenu(menu);
ui->toolButtonIconOnlyTextHaveIconDelayedPop_1->setMenu(menu);
ui->toolButtonIconOnlyTextHaveIconInstantPop->setMenu(menu);
ui->toolButtonIconOnlyTextHaveIconInstantPop_1->setMenu(menu);
//
QList< QColor > clrs = SA::getStandardColorList();
ui->colorGrid1->setColumnCount(0);
ui->colorGrid1->setColorList(clrs);
QList< QColor > clrs2;
for (int i = 0; i < 30; ++i) {
clrs2.append(makeRandColor());
}
ui->colorGrid2->setColumnCount(5);
ui->colorGrid2->setColorList(clrs2);
connect(ui->colorGrid1, &SAColorGridWidget::colorClicked, this, &Widget::onGridColorWidgetClicked);
connect(ui->colorGrid2, &SAColorGridWidget::colorClicked, this, &Widget::onGridColorWidgetClicked);
}
Widget::~Widget()
{
delete ui;
}
QColor Widget::makeRandColor() const
{
static std::random_device s_rd;
static std::mt19937 s_gen(s_rd());
static std::uniform_int_distribution<> s_dis(0, 255);
return QColor(s_dis(s_gen), s_dis(s_gen), s_dis(s_gen));
}
void Widget::setColor(const QColor& clr)
{
ui->toolButtonIconOnlyNoIcon->setColor(clr);
ui->toolButtonIconOnlyNoIcon2->setColor(clr);
ui->toolButtonIconOnlyHaveIcon->setColor(clr);
ui->toolButtonIconOnlyHaveIcon2->setColor(clr);
ui->toolButtonIconOnlyHaveIconSmall->setColor(clr);
ui->toolButtonIconOnlyHaveIconSmall2->setColor(clr);
ui->toolButtonIconOnlyHaveIconLarge->setColor(clr);
ui->toolButtonIconOnlyHaveIconLarge2->setColor(clr);
ui->toolButtonIconOnlyHaveIconMenuPopupMode->setColor(clr);
ui->toolButtonIconOnlyHaveIconMenuPopupMode2->setColor(clr);
ui->toolButtonIconOnlyHaveIconInstantPop->setColor(clr);
ui->toolButtonIconOnlyHaveIconInstantPop2->setColor(clr);
ui->toolButtonIconOnlyHaveIconDelayedPop->setColor(clr);
ui->toolButtonIconOnlyHaveIconDelayedPop2->setColor(clr);
// Text Only
ui->toolButtonIconOnlyText->setColor(clr);
ui->toolButtonIconOnlyText_1->setColor(clr);
ui->toolButtonIconOnlyTextHaveIcon->setColor(clr);
ui->toolButtonIconOnlyTextHaveIcon_1->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconSmall->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconSmall_1->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconLarge->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconLarge_1->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconMenuPopupMode->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconMenuPopupMode_1->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconDelayedPop->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconDelayedPop_1->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconInstantPop->setColor(clr);
ui->toolButtonIconOnlyTextHaveIconInstantPop_1->setColor(clr);
// Text beside
ui->toolButtonIconTextBeside->setColor(clr);
ui->toolButtonIconTextBeside1->setColor(clr);
ui->toolButtonTextBesideHaveIcon->setColor(clr);
ui->toolButtonTextBesideHaveIcon1->setColor(clr);
ui->toolButtonTextBesideHaveIconSmall->setColor(clr);
ui->toolButtonTextBesideHaveIconSmall1->setColor(clr);
ui->toolButtonTextBesideHaveIconLarge->setColor(clr);
ui->toolButtonTextBesideHaveIconLarge1->setColor(clr);
ui->toolButtonTextBesideHaveIconInstantPop->setColor(clr);
ui->toolButtonTextBesideHaveIconInstantPop1->setColor(clr);
ui->toolButtonTextBesideHaveIconDelayedPop->setColor(clr);
ui->toolButtonTextBesideHaveIconDelayedPop1->setColor(clr);
ui->toolButtonTextBesideMenuPopupMode->setColor(clr);
ui->toolButtonTextBesideMenuPopupMode1->setColor(clr);
// TextUnderIcon
ui->toolButtonTextUnderIcon->setColor(clr);
ui->toolButtonTextUnderIcon1->setColor(clr);
ui->toolButtonTextUnderIconHaveIcon->setColor(clr);
ui->toolButtonTextUnderIconHaveIcon1->setColor(clr);
ui->toolButtonTextUnderIconHaveIconSmall->setColor(clr);
ui->toolButtonTextUnderIconHaveIconSmall1->setColor(clr);
ui->toolButtonTextUnderIconHaveIconLarge->setColor(clr);
ui->toolButtonTextUnderIconHaveIconLarge1->setColor(clr);
ui->toolButtonTextUnderIconMenuPopupMode->setColor(clr);
ui->toolButtonTextUnderIconMenuPopupMode1->setColor(clr);
ui->toolButtonTextUnderIconInstantPop->setColor(clr);
ui->toolButtonTextUnderIconInstantPop1->setColor(clr);
ui->toolButtonTextUnderIconDelayedPop->setColor(clr);
ui->toolButtonTextUnderIconDelayedPop1->setColor(clr);
}
void Widget::onGridColorWidgetClicked(const QColor& c)
{
setColor(c);
}
@@ -0,0 +1,29 @@
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr);
~Widget();
QColor makeRandColor() const;
//
void setColor(const QColor& clr);
private Q_SLOTS:
void onGridColorWidgetClicked(const QColor& c);
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
INCLUDEPATH += $$PWD/../
SOURCES += \
../SAColorGridWidget.cpp \
../SAColorPaletteGridWidget.cpp \
../SAColorToolButton.cpp \
main.cpp \
Widget.cpp
HEADERS += \
../SAColorGridWidget.h \
../SAColorPaletteGridWidget.h \
../SAColorToolButton.h \
../SAColorWidgetsGlobal.h \
Widget.h
FORMS += \
Widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
RESOURCES += \
icon.qrc
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/colorWidgetTst">
<file>icon/text.svg</file>
</qresource>
</RCC>

Some files were not shown because too many files have changed in this diff Show More