From c500408a6d30ae244190dbb3e36e16589cb95ce7 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Wed, 26 Nov 2025 23:35:36 +0100 Subject: [PATCH] Gui: Add SplitButton widget This adds SplitButton widget that has one primary action and possibly more secondary ones accessible via menu. --- src/Gui/Application.cpp | 18 ++++++-- src/Gui/CMakeLists.txt | 2 + src/Gui/SplitButton.cpp | 62 +++++++++++++++++++++++++++ src/Gui/SplitButton.h | 69 ++++++++++++++++++++++++++++++ src/Gui/Stylesheets/CMakeLists.txt | 9 ++-- src/Gui/Stylesheets/FreeCAD.qss | 18 ++++++++ src/Gui/Stylesheets/defaults.qss | 20 +++++++++ 7 files changed, 191 insertions(+), 7 deletions(-) create mode 100644 src/Gui/SplitButton.cpp create mode 100644 src/Gui/SplitButton.h create mode 100644 src/Gui/Stylesheets/defaults.qss diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index b83539f006..9c22b5bc28 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -2603,6 +2603,18 @@ void Application::setStyleSheet(const QString& qssFile, bool tiledBackground) mw->setProperty("fc_currentStyleSheet", qssFile); mw->setProperty("fc_tiledBackground", tiledBackground); + QString defaultStyleSheet = [this]() { + QFile f("qss:defaults.qss"); + + if (!f.open(QFile::ReadOnly)) { + return QString(); + } + + QTextStream in(&f); + + return replaceVariablesInQss(in.readAll()); + }(); + if (!qssFile.isEmpty()) { // Search for stylesheet in user-defined search paths. // For qss they are set-up in runApplication() with the prefix "qss" @@ -2622,7 +2634,7 @@ void Application::setStyleSheet(const QString& qssFile, bool tiledBackground) QString styleSheetContent = replaceVariablesInQss(str.readAll()); - qApp->setStyleSheet(styleSheetContent); + qApp->setStyleSheet(defaultStyleSheet + QStringLiteral("\n") + styleSheetContent); ActionStyleEvent e(ActionStyleEvent::Clear); qApp->sendEvent(mw, &e); @@ -2650,13 +2662,13 @@ void Application::setStyleSheet(const QString& qssFile, bool tiledBackground) } else { if (tiledBackground) { - qApp->setStyleSheet(QString()); + qApp->setStyleSheet(defaultStyleSheet); ActionStyleEvent e(ActionStyleEvent::Restore); qApp->sendEvent(getMainWindow(), &e); mdi->setBackground(QPixmap(QLatin1String("images:background.png"))); } else { - qApp->setStyleSheet(QString()); + qApp->setStyleSheet(defaultStyleSheet); ActionStyleEvent e(ActionStyleEvent::Restore); qApp->sendEvent(getMainWindow(), &e); mdi->setBackground(QBrush(QColor(160, 160, 160))); diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 8791a8b745..a21133643a 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1226,6 +1226,7 @@ SET(Widget_CPP_SRCS ElideLabel.cpp ElideCheckBox.cpp FontScaledSVG.cpp + SplitButton.cpp ) SET(Widget_HPP_SRCS ComboLinks.h @@ -1251,6 +1252,7 @@ SET(Widget_HPP_SRCS ElideLabel.h ElideCheckBox.h FontScaledSVG.h + SplitButton.h ) SET(Widget_SRCS ${Widget_CPP_SRCS} diff --git a/src/Gui/SplitButton.cpp b/src/Gui/SplitButton.cpp new file mode 100644 index 0000000000..28d51378a6 --- /dev/null +++ b/src/Gui/SplitButton.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "SplitButton.h" + +#include + +using namespace Gui; + +SplitButton::SplitButton(QWidget* parent) + : SplitButton(QStringLiteral(""), parent) +{} + +SplitButton::SplitButton(const QString& text, QWidget* parent) + : QWidget(parent) + , m_main(new QPushButton(text, this)) + , m_menuButton(new QToolButton(this)) + , m_menu(new QMenu(this)) +{ + auto* layout = new QHBoxLayout(this); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(m_main); + layout->addWidget(m_menuButton); + + // Behavior + m_main->setAutoDefault(false); + m_main->setDefault(false); + + m_menuButton->setMenu(m_menu); + m_menuButton->setPopupMode(QToolButton::InstantPopup); + m_menuButton->setArrowType(Qt::DownArrow); + m_menuButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + + connect(m_main, &QPushButton::clicked, this, &SplitButton::defaultClicked); + connect(m_menu, &QMenu::triggered, this, &SplitButton::triggered); + + // Styling to make it look like a single split button + m_main->setProperty("splitRole", "main"); + m_menuButton->setProperty("splitRole", "menu"); +} diff --git a/src/Gui/SplitButton.h b/src/Gui/SplitButton.h new file mode 100644 index 0000000000..afe4d66437 --- /dev/null +++ b/src/Gui/SplitButton.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef FREECAD_FCSPLITBUTTON_H +#define FREECAD_FCSPLITBUTTON_H + +#include +#include +#include +#include + +namespace Gui +{ + +class SplitButton: public QWidget +{ + Q_OBJECT +public: + explicit SplitButton(QWidget* parent = nullptr); + explicit SplitButton(const QString& text, QWidget* parent = nullptr); + + QPushButton* mainButton() const + { + return m_main; + } + + QToolButton* menuButton() const + { + return m_menuButton; + } + + QMenu* menu() const + { + return m_menu; + } + +Q_SIGNALS: + void defaultClicked(); + void triggered(QAction*); + +private: + QPushButton* m_main; + QToolButton* m_menuButton; + QMenu* m_menu; +}; + +} // namespace Gui + +#endif // FREECAD_FCSPLITBUTTON_H diff --git a/src/Gui/Stylesheets/CMakeLists.txt b/src/Gui/Stylesheets/CMakeLists.txt index 11ca09cb0a..3a5c7f5ac8 100644 --- a/src/Gui/Stylesheets/CMakeLists.txt +++ b/src/Gui/Stylesheets/CMakeLists.txt @@ -1,9 +1,10 @@ SET(Stylesheets_Files - "FreeCAD.qss" - #remove below after testing new stylesheet system 8/6/2025 - "FreeCAD Dark.qss" - "FreeCAD Light.qss" + "FreeCAD.qss" + "defaults.qss" + #remove below after testing new stylesheet system 8/6/2025 + "FreeCAD Dark.qss" + "FreeCAD Light.qss" ) SET(Parameters_Files diff --git a/src/Gui/Stylesheets/FreeCAD.qss b/src/Gui/Stylesheets/FreeCAD.qss index 141da9bf41..1c56e25dbf 100644 --- a/src/Gui/Stylesheets/FreeCAD.qss +++ b/src/Gui/Stylesheets/FreeCAD.qss @@ -1369,6 +1369,24 @@ QToolButton::menu-arrow:hover { image: url(qss:@IconsLocationFolderName/arrow-down-@StylesheetIconsColor.svg); } +/* Gui::SplitButton --------------------------------------------------------- */ + +Gui--SplitButton QPushButton[splitRole="main"] { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +Gui--SplitButton QToolButton[splitRole="menu"] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +Gui--SplitButton QToolButton::menu-indicator { + image: none; + border-image: none; +} + /* QCommandLinkButton ----------------------------------------------------- --------------------------------------------------------------------------- */ diff --git a/src/Gui/Stylesheets/defaults.qss b/src/Gui/Stylesheets/defaults.qss new file mode 100644 index 0000000000..e4a75d1fde --- /dev/null +++ b/src/Gui/Stylesheets/defaults.qss @@ -0,0 +1,20 @@ +/* + * This is a file with default QSS styles that will be preincluded for all themes. It should cover only the styles for + * our own controls and not alter visuals of stuff like buttons etc. This is created so we can use QSS to add color + * to links or control certain aspects of program looks that should be shared across all styles, including classic. + */ + +/* Gui::SplitButton --------------------------------------------------------- */ + +Gui--SplitButton QToolButton[splitRole="menu"] { + width: 24px; + margin: 0; + padding: 0; + min-height: 0px; + max-height: none; +} + +Gui--SplitButton QToolButton::menu-indicator { + image: none; + border-image: none; +}