From e82b81c8c04fadb38fbb2db0598970090d90926c Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Tue, 2 Sep 2025 23:14:34 +0200 Subject: [PATCH 1/4] Gui: Make it possible to hide/show info labels in status bar --- src/Gui/MainWindow.cpp | 67 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index f72612d28f..58a3b917d8 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -308,6 +308,62 @@ struct MainWindowP void restoreWindowState(const QByteArray &); }; +/** + * @brief Label for displaying information in the status bar + * + * A QLabel subclass that provides a context menu with additional actions + * similar to the standard status bar widgets. + */ +class StatusBarInfoLabel : public QLabel +{ + Q_OBJECT +public: + explicit StatusBarInfoLabel(QWidget *parent = nullptr) + : QLabel(parent) + { + } +protected: + void contextMenuEvent(QContextMenuEvent *event) override + { + QMenu menu(this); + + // Reproduce standard status bar widget menu + if (auto *bar = qobject_cast(parentWidget())) { + for (QObject *child : bar->children()) { + QWidget *w = qobject_cast(child); + if (!w) { + continue; + } + auto title = w->windowTitle(); + if (title.isEmpty()) { + continue; + } + + QAction *action = menu.addAction(title); + action->setCheckable(true); + action->setChecked(w->isVisible()); + QObject::connect(action, &QAction::toggled, w, &QWidget::setVisible); + } + } + + menu.addSeparator(); // ---------- + + // Copy + Select All + menu.addAction(tr("Copy"), [this]() { + QApplication::clipboard()->setText(this->selectedText()); + }); + menu.addAction(tr("Select All"), [this]() { + this->setSelection(0, this->text().length()); + }); + + menu.exec(event->globalPos()); + } + void hideEvent(QHideEvent *event) override { + clear(); // Clear text + QLabel::hideEvent(event); + } +}; + } // namespace Gui /* TRANSLATOR Gui::MainWindow */ @@ -390,7 +446,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) // labels and progressbar d->status = new StatusBarObserver(); - d->actionLabel = new QLabel(statusBar()); + d->actionLabel = new StatusBarInfoLabel(statusBar()); d->actionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->sizeLabel = new DimensionWidget(statusBar()); @@ -401,12 +457,19 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) // hint label d->hintLabel = new InputHintWidget(statusBar()); + d->hintLabel->setObjectName(QStringLiteral("hintLabel")); + //: A context menu action used to show or hide the input hints in the status bar + d->hintLabel->setWindowTitle(tr("Input hints")); + statusBar()->addWidget(d->hintLabel); // right side label - d->rightSideLabel = new QLabel(statusBar()); + d->rightSideLabel = new StatusBarInfoLabel(statusBar()); d->rightSideLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); statusBar()->addPermanentWidget(d->rightSideLabel); + d->rightSideLabel->setObjectName(QStringLiteral("rightSideLabel")); + //: A context menu action used to enable or disable quick measure in the status bar + d->rightSideLabel->setWindowTitle(tr("Quick measure")); auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NotificationArea"); From 6aff735482b25164df894396be93234c65755feb Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Tue, 2 Sep 2025 23:15:57 +0200 Subject: [PATCH 2/4] Gui: Only measure with Quick Measure if showing in status bar --- src/Gui/MainWindow.cpp | 5 +++++ src/Gui/MainWindow.h | 1 + src/Mod/Measure/Gui/QuickMeasure.cpp | 25 +++++++++++++++---------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 58a3b917d8..04e76a8bfa 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -2315,6 +2315,11 @@ void MainWindow::setRightSideMessage(const QString& message) d->rightSideLabel->setText(message.simplified()); } +bool MainWindow::isRightSideMessageVisible() const +{ + return d->rightSideLabel->isVisible(); +} + void MainWindow::showStatus(int type, const QString& message) { if(QApplication::instance()->thread() != QThread::currentThread()) { diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index fbe998526b..5da0b81386 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -266,6 +266,7 @@ public Q_SLOTS: void showMessage (const QString & message, int timeout = 0); void setRightSideMessage(const QString & message); + bool isRightSideMessageVisible() const; // Set main window title void setWindowTitle(const QString& string); diff --git a/src/Mod/Measure/Gui/QuickMeasure.cpp b/src/Mod/Measure/Gui/QuickMeasure.cpp index a12fff8703..82bc344c34 100644 --- a/src/Mod/Measure/Gui/QuickMeasure.cpp +++ b/src/Mod/Measure/Gui/QuickMeasure.cpp @@ -114,18 +114,23 @@ void QuickMeasure::tryMeasureSelection() bool QuickMeasure::shouldMeasure(const Gui::SelectionChanges& msg) const { + if (!Gui::getMainWindow()->isRightSideMessageVisible()) { + // don't measure if there's no where to show the result + return false; + } - // measure only IF Gui::Document* doc = Gui::Application::Instance->activeDocument(); - if (doc) { - // we have a document - if (msg.Type == Gui::SelectionChanges::AddSelection - || msg.Type == Gui::SelectionChanges::RmvSelection - || msg.Type == Gui::SelectionChanges::SetSelection - || msg.Type == Gui::SelectionChanges::ClrSelection) { - // the event is about a change in selected objects - return true; - } + if (!doc) { + // no active document + return false; + } + + if (msg.Type == Gui::SelectionChanges::AddSelection + || msg.Type == Gui::SelectionChanges::RmvSelection + || msg.Type == Gui::SelectionChanges::SetSelection + || msg.Type == Gui::SelectionChanges::ClrSelection) { + // the event is about a change in selected objects + return true; } return false; } From 99c24f1c2bc965f8434c0da66aea16db96140dc2 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Tue, 2 Sep 2025 23:41:32 +0200 Subject: [PATCH 3/4] Measure: Remove from menu + transfer ownership back As discussed in 23399, discoverability is really poor as users doesn't know what Quick Measure is, neither is it easy to find out as there's no visual feedback when toggling this feature. The intension was to keep the command and only remove it from the menu, but that wasn't possible due to ownership of quick measure object was inside the command. In addition the parameter had to be renamed as well as mashing the qm button made them end up in an unknown state. As the measurement can be disabled now by simply hiding the info text from context menu, we don't need to keep the old toggling functionality. --- src/Gui/Workbench.cpp | 1 - src/Mod/Measure/Gui/AppMeasureGui.cpp | 3 ++ src/Mod/Measure/Gui/Command.cpp | 65 --------------------------- 3 files changed, 3 insertions(+), 66 deletions(-) diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 71990367e1..7b4d27e559 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -725,7 +725,6 @@ MenuItem* StdWorkbench::setupMenuBar() const #endif *tool << "Std_Measure" << "Std_ClarifySelection" - << "Std_QuickMeasure" << "Std_UnitsCalculator" << "Separator" << "Std_ViewLoadImage" diff --git a/src/Mod/Measure/Gui/AppMeasureGui.cpp b/src/Mod/Measure/Gui/AppMeasureGui.cpp index 135126fbc0..245c953d6f 100644 --- a/src/Mod/Measure/Gui/AppMeasureGui.cpp +++ b/src/Mod/Measure/Gui/AppMeasureGui.cpp @@ -110,5 +110,8 @@ PyMOD_INIT_FUNC(MeasureGui) Base::Interpreter().addType(&MeasureGui::QuickMeasurePy::Type, mod, "QuickMeasure"); + // Create a QuickMeasure instance + new MeasureGui::QuickMeasure(QApplication::instance()); + PyMOD_Return(mod); } diff --git a/src/Mod/Measure/Gui/Command.cpp b/src/Mod/Measure/Gui/Command.cpp index 1cb8dd019f..996e832543 100644 --- a/src/Mod/Measure/Gui/Command.cpp +++ b/src/Mod/Measure/Gui/Command.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -32,7 +31,6 @@ #include #include -#include "QuickMeasure.h" #include "TaskMeasure.h" @@ -79,68 +77,6 @@ bool StdCmdMeasure::isActive() return false; } - -class StdCmdQuickMeasure: public Gui::Command -{ -public: - StdCmdQuickMeasure() - : Command("Std_QuickMeasure") - { - sGroup = "Measure"; - sMenuText = QT_TR_NOOP("&Quick measure"); - sToolTipText = QT_TR_NOOP("Toggle quick measure"); - sWhatsThis = "Std_QuickMeasure"; - sStatusTip = QT_TR_NOOP("Toggle quick measure"); - accessParameter(); - } - ~StdCmdQuickMeasure() override = default; - StdCmdQuickMeasure(const StdCmdQuickMeasure&) = delete; - StdCmdQuickMeasure(StdCmdQuickMeasure&&) = delete; - StdCmdQuickMeasure& operator=(const StdCmdQuickMeasure&) = delete; - StdCmdQuickMeasure& operator=(StdCmdQuickMeasure&&) = delete; - - const char* className() const override - { - return "StdCmdQuickMeasure"; - } - -protected: - void activated(int iMsg) override - { - if (parameter.isValid()) { - parameter->SetBool("EnableQuickMeasure", iMsg > 0); - } - - if (iMsg == 0) { - if (quickMeasure) { - quickMeasure->print(QString()); - } - quickMeasure.reset(); - } - else { - quickMeasure = std::make_unique(QApplication::instance()); - } - } - Gui::Action* createAction() override - { - Gui::Action* action = Gui::Command::createAction(); - action->setCheckable(true); - action->setChecked(parameter->GetBool("EnableQuickMeasure", true)); - return action; - } - void accessParameter() - { - // clang-format off - parameter = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp/Preferences/Mod/Measure"); - // clang-format on - } - -private: - std::unique_ptr quickMeasure; - ParameterGrp::handle parameter; -}; - void CreateMeasureCommands() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); @@ -148,5 +84,4 @@ void CreateMeasureCommands() auto cmd = new StdCmdMeasure(); cmd->initAction(); rcCmdMgr.addCommand(cmd); - rcCmdMgr.addCommand(new StdCmdQuickMeasure); } From c4213b21ad02fe0c3588e6f7e8484f67a8afa059 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Wed, 3 Sep 2025 21:23:58 +0200 Subject: [PATCH 4/4] Gui: Store visibility of status labels (quick measure and input hints) --- src/Gui/CMakeLists.txt | 2 + src/Gui/InputHintWidget.cpp | 2 +- src/Gui/InputHintWidget.h | 4 +- src/Gui/MainWindow.cpp | 61 ++-------------------- src/Gui/StatusBarLabel.cpp | 101 ++++++++++++++++++++++++++++++++++++ src/Gui/StatusBarLabel.h | 63 ++++++++++++++++++++++ 6 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 src/Gui/StatusBarLabel.cpp create mode 100644 src/Gui/StatusBarLabel.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 6f0bfbbe0c..ac74cbe8cb 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1382,6 +1382,7 @@ SET(FreeCADGui_CPP_SRCS resource.cpp Control.cpp SpaceballEvent.cpp + StatusBarLabel.cpp PreferencePackManager.cpp Thumbnail.cpp Utilities.cpp @@ -1431,6 +1432,7 @@ SET(FreeCADGui_SRCS WaitCursor.h ManualAlignment.h StartupProcess.h + StatusBarLabel.h TransactionObject.h ToolHandler.h StyleParameters/Parser.h diff --git a/src/Gui/InputHintWidget.cpp b/src/Gui/InputHintWidget.cpp index 6fa5b62361..fe82d60a20 100644 --- a/src/Gui/InputHintWidget.cpp +++ b/src/Gui/InputHintWidget.cpp @@ -31,7 +31,7 @@ #include "InputHint.h" #include "InputHintWidget.h" -Gui::InputHintWidget::InputHintWidget(QWidget* parent) : QLabel(parent) +Gui::InputHintWidget::InputHintWidget(QWidget* parent) : StatusBarLabel(parent, "InputHintEnabled") {} void Gui::InputHintWidget::showHints(const std::list& hints) diff --git a/src/Gui/InputHintWidget.h b/src/Gui/InputHintWidget.h index e9a91c5507..356f42924d 100644 --- a/src/Gui/InputHintWidget.h +++ b/src/Gui/InputHintWidget.h @@ -24,16 +24,16 @@ #ifndef INPUTHINTWIDGET_H #define INPUTHINTWIDGET_H -#include #include #include +#include "StatusBarLabel.h" #include "InputHint.h" namespace Gui { -class GuiExport InputHintWidget : public QLabel +class GuiExport InputHintWidget : public StatusBarLabel { Q_OBJECT diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 04e76a8bfa..19d4546faf 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -103,6 +103,7 @@ #include "ReportView.h" #include "SelectionView.h" #include "SplashScreen.h" +#include "StatusBarLabel.h" #include "ToolBarManager.h" #include "ToolBoxManager.h" #include "Tree.h" @@ -308,62 +309,6 @@ struct MainWindowP void restoreWindowState(const QByteArray &); }; -/** - * @brief Label for displaying information in the status bar - * - * A QLabel subclass that provides a context menu with additional actions - * similar to the standard status bar widgets. - */ -class StatusBarInfoLabel : public QLabel -{ - Q_OBJECT -public: - explicit StatusBarInfoLabel(QWidget *parent = nullptr) - : QLabel(parent) - { - } -protected: - void contextMenuEvent(QContextMenuEvent *event) override - { - QMenu menu(this); - - // Reproduce standard status bar widget menu - if (auto *bar = qobject_cast(parentWidget())) { - for (QObject *child : bar->children()) { - QWidget *w = qobject_cast(child); - if (!w) { - continue; - } - auto title = w->windowTitle(); - if (title.isEmpty()) { - continue; - } - - QAction *action = menu.addAction(title); - action->setCheckable(true); - action->setChecked(w->isVisible()); - QObject::connect(action, &QAction::toggled, w, &QWidget::setVisible); - } - } - - menu.addSeparator(); // ---------- - - // Copy + Select All - menu.addAction(tr("Copy"), [this]() { - QApplication::clipboard()->setText(this->selectedText()); - }); - menu.addAction(tr("Select All"), [this]() { - this->setSelection(0, this->text().length()); - }); - - menu.exec(event->globalPos()); - } - void hideEvent(QHideEvent *event) override { - clear(); // Clear text - QLabel::hideEvent(event); - } -}; - } // namespace Gui /* TRANSLATOR Gui::MainWindow */ @@ -446,7 +391,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) // labels and progressbar d->status = new StatusBarObserver(); - d->actionLabel = new StatusBarInfoLabel(statusBar()); + d->actionLabel = new StatusBarLabel(statusBar()); d->actionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->sizeLabel = new DimensionWidget(statusBar()); @@ -464,7 +409,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) statusBar()->addWidget(d->hintLabel); // right side label - d->rightSideLabel = new StatusBarInfoLabel(statusBar()); + d->rightSideLabel = new StatusBarLabel(statusBar(), "QuickMeasureEnabled"); d->rightSideLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); statusBar()->addPermanentWidget(d->rightSideLabel); d->rightSideLabel->setObjectName(QStringLiteral("rightSideLabel")); diff --git a/src/Gui/StatusBarLabel.cpp b/src/Gui/StatusBarLabel.cpp new file mode 100644 index 0000000000..c787b27ba6 --- /dev/null +++ b/src/Gui/StatusBarLabel.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Benjamin Nauck * + * * + * 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 +#include +#include +#include +#include +#include +#include + +#include "StatusBarLabel.h" +#include + +namespace Gui { + +StatusBarLabel::StatusBarLabel(QWidget *parent, const std::string& parameterName) + : QLabel(parent) +{ + if (!parameterName.empty()) { + hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/MainWindow"); + + // set visibility before storing parameterName to avoid saving it immediately + setVisible(hGrp->GetBool(parameterName.c_str(), false)); + + // now we can store parameterName + this->parameterName = parameterName; + } +} + +void StatusBarLabel::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + + // Reproduce standard status bar widget menu + if (auto *statusBar = qobject_cast(parentWidget())) { + for (QObject *child : statusBar->children()) { + QWidget *widget = qobject_cast(child); + if (!widget) { + continue; + } + auto title = widget->windowTitle(); + if (title.isEmpty()) { + continue; + } + + QAction *action = menu.addAction(title); + action->setCheckable(true); + action->setChecked(widget->isVisible()); + QObject::connect(action, &QAction::toggled, widget, &QWidget::setVisible); + } + } + + if (textInteractionFlags() & Qt::TextSelectableByMouse) { + menu.addSeparator(); // ---------- + + // Copy + Select All + menu.addAction(tr("Copy"), [this]() { + QApplication::clipboard()->setText(this->selectedText()); + }); + menu.addAction(tr("Select All"), [this]() { + this->setSelection(0, this->text().length()); + }); + } + + menu.exec(event->globalPos()); +} + +void StatusBarLabel::setVisible(bool visible) +{ + if (!parameterName.empty() && hGrp) { + hGrp->SetBool(parameterName.c_str(), visible); + } + if (!visible) { + clear(); // Clear text + } + QLabel::setVisible(visible); +} + +} // namespace Gui diff --git a/src/Gui/StatusBarLabel.h b/src/Gui/StatusBarLabel.h new file mode 100644 index 0000000000..33d0b0e7ca --- /dev/null +++ b/src/Gui/StatusBarLabel.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Benjamin Nauck * + * * + * 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 STATUSBARLABEL_H +#define STATUSBARLABEL_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace Gui +{ +/** + * @brief Label for displaying information in the status bar + * + * A QLabel subclass that provides a context menu with additional actions + * similar to the standard status bar widgets. + */ +class GuiExport StatusBarLabel : public QLabel +{ + Q_OBJECT +public: + explicit StatusBarLabel(QWidget *parent, const std::string& parameterName = {}); +protected: + void contextMenuEvent(QContextMenuEvent *event) override; + void setVisible(bool visible) override; +private: + ParameterGrp::handle hGrp; + std::string parameterName; +}; + +} // Namespace Gui +#endif //STATUSBARLABEL_H