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 f72612d28f..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" @@ -390,7 +391,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) // labels and progressbar d->status = new StatusBarObserver(); - d->actionLabel = new QLabel(statusBar()); + d->actionLabel = new StatusBarLabel(statusBar()); d->actionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->sizeLabel = new DimensionWidget(statusBar()); @@ -401,12 +402,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 StatusBarLabel(statusBar(), "QuickMeasureEnabled"); 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"); @@ -2252,6 +2260,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/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 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); } 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; }