diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp
index 1fbef607ed..35a37e9e64 100644
--- a/src/Gui/Application.cpp
+++ b/src/Gui/Application.cpp
@@ -77,6 +77,7 @@
#include "FileDialog.h"
#include "GuiApplication.h"
#include "GuiInitScript.h"
+#include "InputHintPy.h"
#include "LinkViewPy.h"
#include "MainWindow.h"
#include "Macro.h"
@@ -502,6 +503,8 @@ Application::Application(bool GUIenabled)
Py::Object(Gui::TaskView::ControlPy::getInstance(), true));
Gui::TaskView::TaskDialogPy::init_type();
+ registerUserInputEnumInPython(module);
+
CommandActionPy::init_type();
Base::Interpreter().addType(CommandActionPy::type_object(), module, "CommandAction");
diff --git a/src/Gui/BitmapFactory.cpp b/src/Gui/BitmapFactory.cpp
index dfbe670102..d212c5f6ef 100644
--- a/src/Gui/BitmapFactory.cpp
+++ b/src/Gui/BitmapFactory.cpp
@@ -325,8 +325,8 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const QByteArray& originalContents, con
for ( const auto &colorToColor : colorMapping ) {
ulong fromColor = colorToColor.first;
ulong toColor = colorToColor.second;
- QString fromColorString = QStringLiteral(":#%1;").arg(fromColor, 6, 16, QChar::fromLatin1('0'));
- QString toColorString = QStringLiteral(":#%1;").arg(toColor, 6, 16, QChar::fromLatin1('0'));
+ QString fromColorString = QStringLiteral("#%1").arg(fromColor, 6, 16, QChar::fromLatin1('0'));
+ QString toColorString = QStringLiteral("#%1").arg(toColor, 6, 16, QChar::fromLatin1('0'));
stringContents = stringContents.replace(fromColorString, toColorString);
}
QByteArray contents = stringContents.toUtf8();
diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt
index 17d0f38a69..b3fff2e403 100644
--- a/src/Gui/CMakeLists.txt
+++ b/src/Gui/CMakeLists.txt
@@ -1305,6 +1305,8 @@ SET(FreeCADGui_CPP_SRCS
GuiApplication.cpp
GuiApplicationNativeEventAware.cpp
GuiConsole.cpp
+ InputHintWidget.cpp
+ InputHintPy.cpp
Macro.cpp
MergeDocuments.cpp
ModuleIO.cpp
@@ -1340,6 +1342,9 @@ SET(FreeCADGui_SRCS
3Dconnexion/GuiAbstractNativeEvent.h
GuiConsole.h
InventorAll.h
+ InputHint.h
+ InputHintPy.h
+ InputHintWidget.h
Macro.h
MergeDocuments.h
MetaTypes.h
diff --git a/src/Gui/FreeCADGuiInit.py b/src/Gui/FreeCADGuiInit.py
index 5c027d838e..505cdcfcc5 100644
--- a/src/Gui/FreeCADGuiInit.py
+++ b/src/Gui/FreeCADGuiInit.py
@@ -20,6 +20,7 @@
#* USA *
#* *
#***************************************************************************/
+from dataclasses import dataclass
# FreeCAD gui init module
#
@@ -129,6 +130,63 @@ class NoneWorkbench ( Workbench ):
"""Return the name of the associated C++ class."""
return "Gui::NoneWorkbench"
+
+@dataclass
+class InputHint:
+ """
+ Represents a single input hint (shortcut suggestion).
+
+ The message is a Qt formatting string with placeholders like %1, %2, ...
+ The placeholders are replaced with input representations - be it keys, mouse buttons etc.
+ Each placeholder corresponds to one input sequence. Sequence can either be:
+ - one input from Gui.UserInput enum
+ - tuple of mentioned enum values representing the input sequence
+
+ >>> InputHint("%1 change mode", Gui.UserInput.KeyM)
+ will result in a hint displaying `[M] change mode`
+
+ >>> InputHint("%1 new line", (Gui.UserInput.KeyControl, Gui.UserInput.KeyEnter))
+ will result in a hint displaying `[ctrl][enter] new line`
+
+ >>> InputHint("%1/%2 increase/decrease ...", Gui.UserInput.KeyU, Gui.UserInput.KeyJ)
+ will result in a hint displaying `[U]/[J] increase / decrease ...`
+ """
+
+ InputSequence = Gui.UserInput | tuple[Gui.UserInput, ...]
+
+ message: str
+ sequences: list[InputSequence]
+
+ def __init__(self, message: str, *sequences: InputSequence):
+ self.message = message
+ self.sequences = list(sequences)
+
+
+class HintManager:
+ """
+ A convenience class for managing input hints (shortcut suggestions) displayed to the user.
+ It is here mostly to provide well-defined and easy to reach API from python without developers needing
+ to call low-level functions on the main window directly.
+ """
+
+ def show(self, *hints: InputHint):
+ """
+ Displays the specified input hints to the user.
+
+ :param hints: List of hints to show.
+ """
+ Gui.getMainWindow().showHint(*hints)
+
+ def hide(self):
+ """
+ Hides all currently displayed input hints.
+ """
+ Gui.getMainWindow().hideHint()
+
+
+Gui.InputHint = InputHint
+Gui.HintManager = HintManager()
+
def InitApplications():
import sys,os,traceback
import io as cStringIO
diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc
index 694dfbd191..67895c44cf 100644
--- a/src/Gui/Icons/resource.qrc
+++ b/src/Gui/Icons/resource.qrc
@@ -305,6 +305,13 @@
overlay_error.svg
feature_suppressed.svg
forbidden.svg
+ user-input/mouse-left.svg
+ user-input/mouse-right.svg
+ user-input/mouse-move.svg
+ user-input/mouse-middle.svg
+ user-input/mouse-scroll.svg
+ user-input/mouse-scroll-up.svg
+ user-input/mouse-scroll-down.svg
index.theme
diff --git a/src/Gui/Icons/user-input/mouse-left.svg b/src/Gui/Icons/user-input/mouse-left.svg
new file mode 100644
index 0000000000..d151576f52
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-left.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/Gui/Icons/user-input/mouse-middle.svg b/src/Gui/Icons/user-input/mouse-middle.svg
new file mode 100644
index 0000000000..8b9a9d9aa3
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-middle.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/src/Gui/Icons/user-input/mouse-move.svg b/src/Gui/Icons/user-input/mouse-move.svg
new file mode 100644
index 0000000000..d9a4ad9996
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-move.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/Gui/Icons/user-input/mouse-right.svg b/src/Gui/Icons/user-input/mouse-right.svg
new file mode 100644
index 0000000000..22665af461
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-right.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/Gui/Icons/user-input/mouse-scroll-down.svg b/src/Gui/Icons/user-input/mouse-scroll-down.svg
new file mode 100644
index 0000000000..42eec1d1a4
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-scroll-down.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/src/Gui/Icons/user-input/mouse-scroll-up.svg b/src/Gui/Icons/user-input/mouse-scroll-up.svg
new file mode 100644
index 0000000000..b042cf6375
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-scroll-up.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/src/Gui/Icons/user-input/mouse-scroll.svg b/src/Gui/Icons/user-input/mouse-scroll.svg
new file mode 100644
index 0000000000..a98de3a924
--- /dev/null
+++ b/src/Gui/Icons/user-input/mouse-scroll.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/Gui/InputHint.h b/src/Gui/InputHint.h
new file mode 100644
index 0000000000..43e763454e
--- /dev/null
+++ b/src/Gui/InputHint.h
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/****************************************************************************
+ * *
+ * Copyright (c) 2024 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 GUI_INPUTHINT_H
+#define GUI_INPUTHINT_H
+
+#include "FCGlobal.h"
+
+#include
+#include
+
+#include
+
+namespace Gui
+{
+struct InputHint
+{
+ enum class UserInput
+ {
+ // Modifier
+ ModifierShift = Qt::KeyboardModifier::ShiftModifier,
+ ModifierCtrl = Qt::KeyboardModifier::ControlModifier,
+ ModifierAlt = Qt::KeyboardModifier::AltModifier,
+ ModifierMeta = Qt::KeyboardModifier::MetaModifier,
+
+ // Keyboard Keys
+ KeySpace = Qt::Key_Space,
+ KeyExclam = Qt::Key_Exclam,
+ KeyQuoteDbl = Qt::Key_QuoteDbl,
+ KeyNumberSign = Qt::Key_NumberSign,
+ KeyDollar = Qt::Key_Dollar,
+ KeyPercent = Qt::Key_Percent,
+ KeyAmpersand = Qt::Key_Ampersand,
+ KeyApostrophe = Qt::Key_Apostrophe,
+ KeyParenLeft = Qt::Key_ParenLeft,
+ KeyParenRight = Qt::Key_ParenRight,
+ KeyAsterisk = Qt::Key_Asterisk,
+ KeyPlus = Qt::Key_Plus,
+ KeyComma = Qt::Key_Comma,
+ KeyMinus = Qt::Key_Minus,
+ KeyPeriod = Qt::Key_Period,
+ KeySlash = Qt::Key_Slash,
+ Key0 = Qt::Key_0,
+ Key1 = Qt::Key_1,
+ Key2 = Qt::Key_2,
+ Key3 = Qt::Key_3,
+ Key4 = Qt::Key_4,
+ Key5 = Qt::Key_5,
+ Key6 = Qt::Key_6,
+ Key7 = Qt::Key_7,
+ Key8 = Qt::Key_8,
+ Key9 = Qt::Key_9,
+ KeyColon = Qt::Key_Colon,
+ KeySemicolon = Qt::Key_Semicolon,
+ KeyLess = Qt::Key_Less,
+ KeyEqual = Qt::Key_Equal,
+ KeyGreater = Qt::Key_Greater,
+ KeyQuestion = Qt::Key_Question,
+ KeyAt = Qt::Key_At,
+ KeyA = Qt::Key_A,
+ KeyB = Qt::Key_B,
+ KeyC = Qt::Key_C,
+ KeyD = Qt::Key_D,
+ KeyE = Qt::Key_E,
+ KeyF = Qt::Key_F,
+ KeyG = Qt::Key_G,
+ KeyH = Qt::Key_H,
+ KeyI = Qt::Key_I,
+ KeyJ = Qt::Key_J,
+ KeyK = Qt::Key_K,
+ KeyL = Qt::Key_L,
+ KeyM = Qt::Key_M,
+ KeyN = Qt::Key_N,
+ KeyO = Qt::Key_O,
+ KeyP = Qt::Key_P,
+ KeyQ = Qt::Key_Q,
+ KeyR = Qt::Key_R,
+ KeyS = Qt::Key_S,
+ KeyT = Qt::Key_T,
+ KeyU = Qt::Key_U,
+ KeyV = Qt::Key_V,
+ KeyW = Qt::Key_W,
+ KeyX = Qt::Key_X,
+ KeyY = Qt::Key_Y,
+ KeyZ = Qt::Key_Z,
+ KeyBracketLeft = Qt::Key_BracketLeft,
+ KeyBackslash = Qt::Key_Backslash,
+ KeyBracketRight = Qt::Key_BracketRight,
+ KeyAsciiCircum = Qt::Key_AsciiCircum,
+ KeyUnderscore = Qt::Key_Underscore,
+ KeyQuoteLeft = Qt::Key_QuoteLeft,
+ KeyBraceLeft = Qt::Key_BraceLeft,
+ KeyBar = Qt::Key_Bar,
+ KeyBraceRight = Qt::Key_BraceRight,
+ KeyAsciiTilde = Qt::Key_AsciiTilde,
+
+ // misc keys
+ KeyEscape = Qt::Key_Escape,
+ KeyTab = Qt::Key_Tab,
+ KeyBacktab = Qt::Key_Backtab,
+ KeyBackspace = Qt::Key_Backspace,
+ KeyReturn = Qt::Key_Return,
+ KeyEnter = Qt::Key_Enter,
+ KeyInsert = Qt::Key_Insert,
+ KeyDelete = Qt::Key_Delete,
+ KeyPause = Qt::Key_Pause,
+ KeyPrintScr = Qt::Key_Print,
+ KeySysReq = Qt::Key_SysReq,
+ KeyClear = Qt::Key_Clear,
+
+ // cursor movement
+ KeyHome = Qt::Key_Home,
+ KeyEnd = Qt::Key_End,
+ KeyLeft = Qt::Key_Left,
+ KeyUp = Qt::Key_Up,
+ KeyRight = Qt::Key_Right,
+ KeyDown = Qt::Key_Down,
+ KeyPageUp = Qt::Key_PageUp,
+ KeyPageDown = Qt::Key_PageDown,
+
+ // modifiers
+ KeyShift = Qt::Key_Shift,
+ KeyControl = Qt::Key_Control,
+ KeyMeta = Qt::Key_Meta,
+ KeyAlt = Qt::Key_Alt,
+ KeyCapsLock = Qt::Key_CapsLock,
+ KeyNumLock = Qt::Key_NumLock,
+ KeyScrollLock = Qt::Key_ScrollLock,
+
+ // function keys
+ KeyF1 = Qt::Key_F1,
+ KeyF2 = Qt::Key_F2,
+ KeyF3 = Qt::Key_F3,
+ KeyF4 = Qt::Key_F4,
+ KeyF5 = Qt::Key_F5,
+ KeyF6 = Qt::Key_F6,
+ KeyF7 = Qt::Key_F7,
+ KeyF8 = Qt::Key_F8,
+ KeyF9 = Qt::Key_F9,
+ KeyF10 = Qt::Key_F10,
+ KeyF11 = Qt::Key_F11,
+ KeyF12 = Qt::Key_F12,
+ KeyF13 = Qt::Key_F13,
+ KeyF14 = Qt::Key_F14,
+ KeyF15 = Qt::Key_F15,
+ KeyF16 = Qt::Key_F16,
+ KeyF17 = Qt::Key_F17,
+ KeyF18 = Qt::Key_F18,
+ KeyF19 = Qt::Key_F19,
+ KeyF20 = Qt::Key_F20,
+ KeyF21 = Qt::Key_F21,
+ KeyF22 = Qt::Key_F22,
+ KeyF23 = Qt::Key_F23,
+ KeyF24 = Qt::Key_F24,
+ KeyF25 = Qt::Key_F25,
+ KeyF26 = Qt::Key_F26,
+ KeyF27 = Qt::Key_F27,
+ KeyF28 = Qt::Key_F28,
+ KeyF29 = Qt::Key_F29,
+ KeyF30 = Qt::Key_F30,
+ KeyF31 = Qt::Key_F31,
+ KeyF32 = Qt::Key_F32,
+ KeyF33 = Qt::Key_F33,
+ KeyF34 = Qt::Key_F34,
+ KeyF35 = Qt::Key_F35,
+
+ // Mouse Keys
+ MouseMove = 1 << 16,
+ MouseLeft = 2 << 16,
+ MouseRight = 3 << 16,
+ MouseMiddle = 4 << 16,
+ MouseScroll = 5 << 16,
+ MouseScrollUp = 6 << 16,
+ MouseScrollDown = 7 << 16,
+ };
+
+ struct InputSequence
+ {
+ std::list keys {};
+
+ // This is intentionally left as an implicit conversion. For the intended use one UserInput
+ // is de facto InputSequence of length one. Therefore, there is no need to make that explicit.
+ explicit(false) InputSequence(UserInput key)
+ : InputSequence({key})
+ {}
+
+ explicit InputSequence(std::list keys)
+ : keys(std::move(keys))
+ {}
+
+ InputSequence(const std::initializer_list keys)
+ : keys(keys)
+ {}
+ };
+
+ QString message;
+ std::list sequences;
+};
+
+} // namespace Gui
+
+#endif // GUI_INPUTHINT_H
diff --git a/src/Gui/InputHintPy.cpp b/src/Gui/InputHintPy.cpp
new file mode 100644
index 0000000000..069a734281
--- /dev/null
+++ b/src/Gui/InputHintPy.cpp
@@ -0,0 +1,218 @@
+// 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 "PreCompiled.h"
+
+#include "InputHint.h"
+#include "InputHintPy.h"
+
+void Gui::registerUserInputEnumInPython(PyObject* module)
+{
+ using enum Gui::InputHint::UserInput;
+
+ constexpr const char* name = "UserInput";
+
+ PyObject* py_enum_module = PyImport_ImportModule("enum");
+ if (!py_enum_module) {
+ return;
+ }
+
+ PyObject* py_constants_dict = PyDict_New();
+
+ // clang-format off
+ // this structure is repetition of each and every UserInput enum case
+ // it can be regenerated by copying and pasting all entries and performing one substitution:
+ // regex search: `(\w+) = .+?,` substitution: `{"$1", $1},`
+ const std::map userInputEntries {
+ // Modifier
+ {"ModifierShift", ModifierShift},
+ {"ModifierCtrl", ModifierCtrl},
+ {"ModifierAlt", ModifierAlt},
+ {"ModifierMeta", ModifierMeta},
+
+ // Keyboard Keys
+ {"KeySpace", KeySpace},
+ {"KeyExclam", KeyExclam},
+ {"KeyQuoteDbl", KeyQuoteDbl},
+ {"KeyNumberSign", KeyNumberSign},
+ {"KeyDollar", KeyDollar},
+ {"KeyPercent", KeyPercent},
+ {"KeyAmpersand", KeyAmpersand},
+ {"KeyApostrophe", KeyApostrophe},
+ {"KeyParenLeft", KeyParenLeft},
+ {"KeyParenRight", KeyParenRight},
+ {"KeyAsterisk", KeyAsterisk},
+ {"KeyPlus", KeyPlus},
+ {"KeyComma", KeyComma},
+ {"KeyMinus", KeyMinus},
+ {"KeyPeriod", KeyPeriod},
+ {"KeySlash", KeySlash},
+ {"Key0", Key0},
+ {"Key1", Key1},
+ {"Key2", Key2},
+ {"Key3", Key3},
+ {"Key4", Key4},
+ {"Key5", Key5},
+ {"Key6", Key6},
+ {"Key7", Key7},
+ {"Key8", Key8},
+ {"Key9", Key9},
+ {"KeyColon", KeyColon},
+ {"KeySemicolon", KeySemicolon},
+ {"KeyLess", KeyLess},
+ {"KeyEqual", KeyEqual},
+ {"KeyGreater", KeyGreater},
+ {"KeyQuestion", KeyQuestion},
+ {"KeyAt", KeyAt},
+ {"KeyA", KeyA},
+ {"KeyB", KeyB},
+ {"KeyC", KeyC},
+ {"KeyD", KeyD},
+ {"KeyE", KeyE},
+ {"KeyF", KeyF},
+ {"KeyG", KeyG},
+ {"KeyH", KeyH},
+ {"KeyI", KeyI},
+ {"KeyJ", KeyJ},
+ {"KeyK", KeyK},
+ {"KeyL", KeyL},
+ {"KeyM", KeyM},
+ {"KeyN", KeyN},
+ {"KeyO", KeyO},
+ {"KeyP", KeyP},
+ {"KeyQ", KeyQ},
+ {"KeyR", KeyR},
+ {"KeyS", KeyS},
+ {"KeyT", KeyT},
+ {"KeyU", KeyU},
+ {"KeyV", KeyV},
+ {"KeyW", KeyW},
+ {"KeyX", KeyX},
+ {"KeyY", KeyY},
+ {"KeyZ", KeyZ},
+ {"KeyBracketLeft", KeyBracketLeft},
+ {"KeyBackslash", KeyBackslash},
+ {"KeyBracketRight", KeyBracketRight},
+ {"KeyAsciiCircum", KeyAsciiCircum},
+ {"KeyUnderscore", KeyUnderscore},
+ {"KeyQuoteLeft", KeyQuoteLeft},
+ {"KeyBraceLeft", KeyBraceLeft},
+ {"KeyBar", KeyBar},
+ {"KeyBraceRight", KeyBraceRight},
+ {"KeyAsciiTilde", KeyAsciiTilde},
+
+ // misc keys
+ {"KeyEscape", KeyEscape},
+ {"KeyTab", KeyTab},
+ {"KeyBacktab", KeyBacktab},
+ {"KeyBackspace", KeyBackspace},
+ {"KeyReturn", KeyReturn},
+ {"KeyEnter", KeyEnter},
+ {"KeyInsert", KeyInsert},
+ {"KeyDelete", KeyDelete},
+ {"KeyPause", KeyPause},
+ {"KeyPrintScr", KeyPrintScr},
+ {"KeySysReq", KeySysReq},
+ {"KeyClear", KeyClear},
+
+ // cursor movement
+ {"KeyHome", KeyHome},
+ {"KeyEnd", KeyEnd},
+ {"KeyLeft", KeyLeft},
+ {"KeyUp", KeyUp},
+ {"KeyRight", KeyRight},
+ {"KeyDown", KeyDown},
+ {"KeyPageUp", KeyPageUp},
+ {"KeyPageDown", KeyPageDown},
+
+ // modifiers
+ {"KeyShift", KeyShift},
+ {"KeyControl", KeyControl},
+ {"KeyMeta", KeyMeta},
+ {"KeyAlt", KeyAlt},
+ {"KeyCapsLock", KeyCapsLock},
+ {"KeyNumLock", KeyNumLock},
+ {"KeyScrollLock", KeyScrollLock},
+
+ // function keys
+ {"KeyF1", KeyF1},
+ {"KeyF2", KeyF2},
+ {"KeyF3", KeyF3},
+ {"KeyF4", KeyF4},
+ {"KeyF5", KeyF5},
+ {"KeyF6", KeyF6},
+ {"KeyF7", KeyF7},
+ {"KeyF8", KeyF8},
+ {"KeyF9", KeyF9},
+ {"KeyF10", KeyF10},
+ {"KeyF11", KeyF11},
+ {"KeyF12", KeyF12},
+ {"KeyF13", KeyF13},
+ {"KeyF14", KeyF14},
+ {"KeyF15", KeyF15},
+ {"KeyF16", KeyF16},
+ {"KeyF17", KeyF17},
+ {"KeyF18", KeyF18},
+ {"KeyF19", KeyF19},
+ {"KeyF20", KeyF20},
+ {"KeyF21", KeyF21},
+ {"KeyF22", KeyF22},
+ {"KeyF23", KeyF23},
+ {"KeyF24", KeyF24},
+ {"KeyF25", KeyF25},
+ {"KeyF26", KeyF26},
+ {"KeyF27", KeyF27},
+ {"KeyF28", KeyF28},
+ {"KeyF29", KeyF29},
+ {"KeyF30", KeyF30},
+ {"KeyF31", KeyF31},
+ {"KeyF32", KeyF32},
+ {"KeyF33", KeyF33},
+ {"KeyF34", KeyF34},
+ {"KeyF35", KeyF35},
+
+ // Mouse Keys
+ {"MouseMove", MouseMove},
+ {"MouseLeft", MouseLeft},
+ {"MouseRight", MouseRight},
+ {"MouseMiddle", MouseMiddle},
+ {"MouseScroll", MouseScroll},
+ {"MouseScrollUp", MouseScrollUp},
+ {"MouseScrollDown", MouseScrollDown},
+ };
+ // clang-format on
+
+ // Populate dictionary
+ for (const auto& [key, value] : userInputEntries) {
+ PyDict_SetItemString(py_constants_dict, key, PyLong_FromLong(static_cast(value)));
+ }
+
+ PyObject* py_enum_class = PyObject_CallMethod(py_enum_module, "IntEnum", "sO", name, py_constants_dict);
+
+ Py_CLEAR(py_constants_dict);
+ Py_CLEAR(py_enum_module);
+
+ if (py_enum_class && PyModule_AddObject(module, name, py_enum_class) < 0) {
+ Py_CLEAR(py_enum_class);
+ }
+}
diff --git a/src/Gui/InputHintPy.h b/src/Gui/InputHintPy.h
new file mode 100644
index 0000000000..7c00722dcc
--- /dev/null
+++ b/src/Gui/InputHintPy.h
@@ -0,0 +1,34 @@
+// 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 INPUTHINTPY_H
+#define INPUTHINTPY_H
+
+#include
+
+namespace Gui
+{
+ void registerUserInputEnumInPython(PyObject* module);
+} // Namespace Gui
+
+#endif //INPUTHINTPY_H
diff --git a/src/Gui/InputHintWidget.cpp b/src/Gui/InputHintWidget.cpp
new file mode 100644
index 0000000000..146cc9f1bc
--- /dev/null
+++ b/src/Gui/InputHintWidget.cpp
@@ -0,0 +1,319 @@
+// 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 "PreCompiled.h"
+#ifndef _PreComp_
+# include
+# include
+#endif
+
+#include
+
+#include "InputHint.h"
+#include "InputHintWidget.h"
+
+Gui::InputHintWidget::InputHintWidget(QWidget* parent) : QLabel(parent)
+{}
+
+void Gui::InputHintWidget::showHints(const std::list& hints)
+{
+ if (hints.empty()) {
+ clearHints();
+ return;
+ }
+
+ const auto getKeyImage = [this](InputHint::UserInput key) {
+ const auto& factory = BitmapFactory();
+
+ QPixmap image = [&] {
+ QColor color = palette().text().color();
+
+ if (auto iconPath = getCustomIconPath(key)) {
+ return factory.pixmapFromSvg(*iconPath,
+ QSize(24, 24),
+ {{0xFFFFFF, color.rgb() & RGB_MASK}});
+ }
+
+ return generateKeyIcon(key, color);
+ }();
+
+
+ QBuffer buffer;
+ image.save(&buffer, "png");
+
+ return QStringLiteral("
")
+ .arg(QLatin1String(buffer.data().toBase64()))
+ .arg(image.width());
+ };
+
+ const auto getHintHTML = [&](const InputHint& hint) {
+ QString message = QStringLiteral("%1 | ").arg(hint.message);
+
+ for (const auto& sequence : hint.sequences) {
+ QList keyImages;
+
+ for (const auto key : sequence.keys) {
+ keyImages.append(getKeyImage(key));
+ }
+
+ message = message.arg(keyImages.join(QString {}));
+ }
+
+ return message;
+ };
+
+ QStringList messages;
+ for (const auto& hint : hints) {
+ messages.append(getHintHTML(hint));
+ }
+
+ QString html = QStringLiteral("");
+
+ setText(html.arg(messages.join(QStringLiteral(" | "))));
+}
+
+void Gui::InputHintWidget::clearHints()
+{
+ setText({});
+}
+
+std::optional Gui::InputHintWidget::getCustomIconPath(const InputHint::UserInput key)
+{
+ switch (key) {
+ case InputHint::UserInput::MouseLeft:
+ return ":/icons/user-input/mouse-left.svg";
+ case InputHint::UserInput::MouseRight:
+ return ":/icons/user-input/mouse-right.svg";
+ case InputHint::UserInput::MouseMove:
+ return ":/icons/user-input/mouse-move.svg";
+ case InputHint::UserInput::MouseMiddle:
+ return ":/icons/user-input/mouse-middle.svg";
+ case InputHint::UserInput::MouseScroll:
+ return ":/icons/user-input/mouse-scroll.svg";
+ case InputHint::UserInput::MouseScrollDown:
+ return ":/icons/user-input/mouse-scroll-down.svg";
+ case InputHint::UserInput::MouseScrollUp:
+ return ":/icons/user-input/mouse-scroll-up.svg";
+ default:
+ return std::nullopt;
+ }
+}
+
+QPixmap Gui::InputHintWidget::generateKeyIcon(const InputHint::UserInput key, const QColor color)
+{
+ constexpr int margin = 3;
+ constexpr int padding = 4;
+ constexpr int radius = 2;
+ constexpr int iconTotalHeight = 24;
+ constexpr int iconSymbolHeight = iconTotalHeight - 2 * margin;
+
+ const QFont font(QStringLiteral("sans"), 10, QFont::Bold);
+ const QFontMetrics fm(font);
+ const QString text = inputRepresentation(key);
+ const QRect textBoundingRect = fm.tightBoundingRect(text);
+
+ const int symbolWidth = std::max(textBoundingRect.width() + padding * 2, iconSymbolHeight);
+
+ const QRect keyRect(margin, margin, symbolWidth, 18);
+
+ QPixmap pixmap(symbolWidth + margin * 2, iconTotalHeight);
+ pixmap.fill(Qt::transparent);
+
+ QPainter painter(&pixmap);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(QPen(color, 2));
+ painter.setFont(font);
+ painter.drawRoundedRect(keyRect, radius, radius);
+ painter.drawText(
+ // adjust the rectangle so it is visually centered
+ // this is important for characters that are below baseline
+ keyRect.translated(0, -(textBoundingRect.y() + textBoundingRect.height()) / 2),
+ Qt::AlignHCenter,
+ text);
+
+ return pixmap;
+}
+
+QString Gui::InputHintWidget::inputRepresentation(const InputHint::UserInput key)
+{
+ using enum InputHint::UserInput;
+
+ // clang-format off
+ switch (key) {
+ // Keyboard Keys
+ case KeySpace: return tr("Space");
+ case KeyExclam: return QStringLiteral("!");
+ case KeyQuoteDbl: return QStringLiteral("\"");
+ case KeyNumberSign: return QStringLiteral("-/+");
+ case KeyDollar: return QStringLiteral("$");
+ case KeyPercent: return QStringLiteral("%");
+ case KeyAmpersand: return QStringLiteral("&");
+ case KeyApostrophe: return QStringLiteral("\'");
+ case KeyParenLeft: return QStringLiteral("(");
+ case KeyParenRight: return QStringLiteral(")");
+ case KeyAsterisk: return QStringLiteral("*");
+ case KeyPlus: return QStringLiteral("+");
+ case KeyComma: return QStringLiteral(",");
+ case KeyMinus: return QStringLiteral("-");
+ case KeyPeriod: return QStringLiteral(".");
+ case KeySlash: return QStringLiteral("/");
+ case Key0: return QStringLiteral("0");
+ case Key1: return QStringLiteral("1");
+ case Key2: return QStringLiteral("2");
+ case Key3: return QStringLiteral("3");
+ case Key4: return QStringLiteral("4");
+ case Key5: return QStringLiteral("5");
+ case Key6: return QStringLiteral("6");
+ case Key7: return QStringLiteral("7");
+ case Key8: return QStringLiteral("8");
+ case Key9: return QStringLiteral("9");
+ case KeyColon: return QStringLiteral(":");
+ case KeySemicolon: return QStringLiteral(";");
+ case KeyLess: return QStringLiteral("<");
+ case KeyEqual: return QStringLiteral("=");
+ case KeyGreater: return QStringLiteral(">");
+ case KeyQuestion: return QStringLiteral("?");
+ case KeyAt: return QStringLiteral("@");
+ case KeyA: return QStringLiteral("A");
+ case KeyB: return QStringLiteral("B");
+ case KeyC: return QStringLiteral("C");
+ case KeyD: return QStringLiteral("D");
+ case KeyE: return QStringLiteral("E");
+ case KeyF: return QStringLiteral("F");
+ case KeyG: return QStringLiteral("G");
+ case KeyH: return QStringLiteral("H");
+ case KeyI: return QStringLiteral("I");
+ case KeyJ: return QStringLiteral("J");
+ case KeyK: return QStringLiteral("K");
+ case KeyL: return QStringLiteral("L");
+ case KeyM: return QStringLiteral("M");
+ case KeyN: return QStringLiteral("N");
+ case KeyO: return QStringLiteral("O");
+ case KeyP: return QStringLiteral("P");
+ case KeyQ: return QStringLiteral("Q");
+ case KeyR: return QStringLiteral("R");
+ case KeyS: return QStringLiteral("S");
+ case KeyT: return QStringLiteral("T");
+ case KeyU: return QStringLiteral("U");
+ case KeyV: return QStringLiteral("V");
+ case KeyW: return QStringLiteral("W");
+ case KeyX: return QStringLiteral("X");
+ case KeyY: return QStringLiteral("Y");
+ case KeyZ: return QStringLiteral("Z");
+ case KeyBracketLeft: return QStringLiteral("[");
+ case KeyBackslash: return QStringLiteral("\\");
+ case KeyBracketRight: return QStringLiteral("]");
+ case KeyUnderscore: return QStringLiteral("_");
+ case KeyQuoteLeft: return QStringLiteral("\"");
+ case KeyBraceLeft: return QStringLiteral("{");
+ case KeyBar: return QStringLiteral("|");
+ case KeyBraceRight: return QStringLiteral("}");
+ case KeyAsciiTilde: return QStringLiteral("~");
+
+ // misc keys
+ case KeyEscape: return tr("Escape");
+ case KeyTab: return tr("tab ⭾");
+ case KeyBacktab: return tr("Backtab");
+ case KeyBackspace: return tr("⌫");
+ case KeyReturn: return tr("↵ Enter");
+ case KeyEnter: return tr("Enter");
+ case KeyInsert: return tr("Insert");
+ case KeyDelete: return tr("Delete");
+ case KeyPause: return tr("Pause");
+ case KeyPrintScr: return tr("Print");
+ case KeySysReq: return tr("SysReq");
+ case KeyClear: return tr("Clear");
+
+ // cursor movement
+ case KeyHome: return tr("Home");
+ case KeyEnd: return tr("End");
+ case KeyLeft: return QStringLiteral("←");
+ case KeyUp: return QStringLiteral("↑");
+ case KeyRight: return QStringLiteral("→");
+ case KeyDown: return QStringLiteral("↓");
+ case KeyPageUp: return tr("PgDown");
+ case KeyPageDown: return tr("PgUp");
+
+ // modifiers
+#ifdef FC_OS_MACOSX
+ case KeyShift: return QStringLiteral("⇧");
+ case KeyControl: return QStringLiteral("⌘");
+ case KeyMeta: return QStringLiteral("⌃");
+ case KeyAlt: return QStringLiteral("⌥");
+#else
+ case KeyShift: return tr("Shift");
+ case KeyControl: return tr("Ctrl");
+#ifdef FC_OS_WIN32
+ case KeyMeta: return tr("⊞ Win");
+#else
+ case KeyMeta: return tr("❖ Meta");
+#endif
+ case KeyAlt: return tr("Alt");
+#endif
+ case KeyCapsLock: return tr("Caps Lock");
+ case KeyNumLock: return tr("Num Lock");
+ case KeyScrollLock: return tr("Scroll Lock");
+
+ // function
+ case KeyF1: return QStringLiteral("F1");
+ case KeyF2: return QStringLiteral("F2");
+ case KeyF3: return QStringLiteral("F3");
+ case KeyF4: return QStringLiteral("F4");
+ case KeyF5: return QStringLiteral("F5");
+ case KeyF6: return QStringLiteral("F6");
+ case KeyF7: return QStringLiteral("F7");
+ case KeyF8: return QStringLiteral("F8");
+ case KeyF9: return QStringLiteral("F9");
+ case KeyF10: return QStringLiteral("F10");
+ case KeyF11: return QStringLiteral("F11");
+ case KeyF12: return QStringLiteral("F12");
+ case KeyF13: return QStringLiteral("F13");
+ case KeyF14: return QStringLiteral("F14");
+ case KeyF15: return QStringLiteral("F15");
+ case KeyF16: return QStringLiteral("F16");
+ case KeyF17: return QStringLiteral("F17");
+ case KeyF18: return QStringLiteral("F18");
+ case KeyF19: return QStringLiteral("F19");
+ case KeyF20: return QStringLiteral("F20");
+ case KeyF21: return QStringLiteral("F21");
+ case KeyF22: return QStringLiteral("F22");
+ case KeyF23: return QStringLiteral("F23");
+ case KeyF24: return QStringLiteral("F24");
+ case KeyF25: return QStringLiteral("F25");
+ case KeyF26: return QStringLiteral("F26");
+ case KeyF27: return QStringLiteral("F27");
+ case KeyF28: return QStringLiteral("F28");
+ case KeyF29: return QStringLiteral("F29");
+ case KeyF30: return QStringLiteral("F30");
+ case KeyF31: return QStringLiteral("F31");
+ case KeyF32: return QStringLiteral("F32");
+ case KeyF33: return QStringLiteral("F33");
+ case KeyF34: return QStringLiteral("F34");
+ case KeyF35: return QStringLiteral("F35");
+
+ default: return tr("???");
+ }
+ // clang-format on
+}
diff --git a/src/Gui/InputHintWidget.h b/src/Gui/InputHintWidget.h
new file mode 100644
index 0000000000..c9e86c1d44
--- /dev/null
+++ b/src/Gui/InputHintWidget.h
@@ -0,0 +1,53 @@
+// 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 INPUTHINTWIDGET_H
+#define INPUTHINTWIDGET_H
+
+#include
+#include
+
+#include
+
+#include "InputHint.h"
+
+namespace Gui
+{
+class GuiExport InputHintWidget : public QLabel
+{
+ Q_OBJECT
+
+public:
+ explicit InputHintWidget(QWidget *parent);
+
+ void showHints(const std::list& hints);
+ void clearHints();
+
+private:
+ static std::optional getCustomIconPath(InputHint::UserInput key);
+ static QPixmap generateKeyIcon(InputHint::UserInput key, QColor color);
+ static QString inputRepresentation(InputHint::UserInput key);
+};
+
+} // Namespace Gui
+#endif //INPUTHINTWIDGET_H
diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp
index c2a664d48f..ab2cf7184c 100644
--- a/src/Gui/MainWindow.cpp
+++ b/src/Gui/MainWindow.cpp
@@ -93,6 +93,7 @@
#include "DockWindowManager.h"
#include "DownloadManager.h"
#include "FileDialog.h"
+#include "InputHintWidget.h"
#include "MenuManager.h"
#include "ModuleIO.h"
#include "NotificationArea.h"
@@ -117,6 +118,7 @@
#include "View3DInventor.h"
#include "View3DInventorViewer.h"
#include "Dialogs/DlgObjectSelection.h"
+
#include
FC_LOG_LEVEL_INIT("MainWindow",false,true,true)
@@ -280,6 +282,7 @@ struct MainWindowP
{
DimensionWidget* sizeLabel;
QLabel* actionLabel;
+ InputHintWidget* hintLabel;
QLabel* rightSideLabel;
QTimer* actionTimer;
QTimer* statusTimer;
@@ -396,6 +399,11 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
statusBar()->addPermanentWidget(progressBar, 0);
statusBar()->addPermanentWidget(d->sizeLabel, 0);
+ // hint label
+ d->hintLabel = new InputHintWidget(statusBar());
+ statusBar()->addWidget(d->hintLabel);
+
+ // right side label
d->rightSideLabel = new QLabel(statusBar());
d->rightSideLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
statusBar()->addPermanentWidget(d->rightSideLabel);
@@ -2255,6 +2263,15 @@ void MainWindow::showStatus(int type, const QString& message)
statusBar()->showMessage(msg.simplified(), timeout);
}
+void MainWindow::showHints(const std::list& hints)
+{
+ d->hintLabel->showHints(hints);
+}
+
+void MainWindow::hideHints()
+{
+ d->hintLabel->clearHints();
+}
// set text to the pane
void MainWindow::setPaneText(int i, QString text)
diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h
index ac49d55a2d..f6ea2791f1 100644
--- a/src/Gui/MainWindow.h
+++ b/src/Gui/MainWindow.h
@@ -29,6 +29,7 @@
#include
#include "Window.h"
+#include "InputHint.h"
class QMimeData;
class QUrl;
@@ -201,7 +202,10 @@ public:
void updateActions(bool delay = false);
enum StatusType {None, Err, Wrn, Pane, Msg, Log, Tmp, Critical};
- void showStatus(int type, const QString & message);
+ void showStatus(int type, const QString& message);
+
+ void showHints(const std::list& hints = {});
+ void hideHints();
void initDockWindows(bool show);
diff --git a/src/Gui/MainWindowPy.cpp b/src/Gui/MainWindowPy.cpp
index 3a9a12e844..845fe6cb32 100644
--- a/src/Gui/MainWindowPy.cpp
+++ b/src/Gui/MainWindowPy.cpp
@@ -56,6 +56,8 @@ void MainWindowPy::init_type()
add_varargs_method("getActiveWindow", &MainWindowPy::getActiveWindow, "getActiveWindow()");
add_varargs_method("addWindow", &MainWindowPy::addWindow, "addWindow(MDIView)");
add_varargs_method("removeWindow", &MainWindowPy::removeWindow, "removeWindow(MDIView)");
+ add_varargs_method("showHint",&MainWindowPy::showHint,"showHint(hint)");
+ add_varargs_method("hideHint",&MainWindowPy::hideHint,"hideHint()");
}
PyObject *MainWindowPy::extension_object_new(struct _typeobject * /*type*/, PyObject * /*args*/, PyObject * /*kwds*/)
@@ -87,7 +89,16 @@ Py::Object MainWindowPy::createWrapper(MainWindow *mw)
}
// copy attributes
- std::list attr = {"getWindows", "getWindowsOfType", "setActiveWindow", "getActiveWindow", "addWindow", "removeWindow"};
+ static constexpr std::initializer_list attr = {
+ "getWindows",
+ "getWindowsOfType",
+ "setActiveWindow",
+ "getActiveWindow",
+ "addWindow",
+ "removeWindow",
+ "showHint",
+ "hideHint",
+ };
Py::Object py = wrap.fromQWidget(mw, "QMainWindow");
Py::ExtensionObject inst(create(mw));
@@ -215,3 +226,57 @@ Py::Object MainWindowPy::removeWindow(const Py::Tuple& args)
}
return Py::None();
}
+
+Py::Object MainWindowPy::showHint(const Py::Tuple& args)
+{
+ static auto userInputFromPyObject = [](const Py::Object& object) -> InputHint::UserInput {
+ Py::Long value(object.getAttr("value"));
+
+ return static_cast(value.as_long());
+ };
+
+ static auto inputSequenceFromPyObject = [](const Py::Object& sequence) -> InputHint::InputSequence {
+ if (sequence.isTuple()) {
+ Py::Tuple pyInputs(sequence);
+
+ std::list inputs;
+ for (auto pyInput : pyInputs) {
+ inputs.push_back(userInputFromPyObject(pyInput));
+ }
+
+ return InputHint::InputSequence(inputs);
+ }
+
+ return userInputFromPyObject(sequence);
+ };
+
+ static auto hintFromPyObject = [](const Py::Object& object) -> InputHint {
+ Py::List pySequences = object.getAttr("sequences");
+
+ std::list sequences;
+ for (auto pySequence : pySequences) {
+ sequences.push_back(inputSequenceFromPyObject(pySequence));
+ }
+
+ return InputHint {
+ .message = QString::fromStdString(object.getAttr("message").as_string()),
+ .sequences = sequences
+ };
+ };
+
+ std::list hints;
+ for (auto arg : args) {
+ hints.push_back(hintFromPyObject(arg));
+ }
+
+ _mw->showHints(hints);
+
+ return Py::None();
+}
+
+Py::Object MainWindowPy::hideHint(const Py::Tuple&)
+{
+ _mw->hideHints();
+
+ return Py::None();
+}
diff --git a/src/Gui/MainWindowPy.h b/src/Gui/MainWindowPy.h
index 398dc1a0a3..c28571a8ef 100644
--- a/src/Gui/MainWindowPy.h
+++ b/src/Gui/MainWindowPy.h
@@ -54,6 +54,9 @@ public:
Py::Object addWindow(const Py::Tuple&);
Py::Object removeWindow(const Py::Tuple&);
+ Py::Object showHint(const Py::Tuple&);
+ Py::Object hideHint(const Py::Tuple&);
+
private:
QPointer _mw;
};
diff --git a/src/Gui/ToolHandler.cpp b/src/Gui/ToolHandler.cpp
index 182fa8c6d2..23c7302ef8 100644
--- a/src/Gui/ToolHandler.cpp
+++ b/src/Gui/ToolHandler.cpp
@@ -41,6 +41,7 @@
#include "View3DInventorViewer.h"
#include "ToolHandler.h"
+#include "InputHint.h"
using namespace Gui;
@@ -59,6 +60,7 @@ bool ToolHandler::activate()
oldCursor = cw->cursor();
updateCursor();
+ updateHint();
this->preActivated();
this->activated();
@@ -74,6 +76,8 @@ void ToolHandler::deactivate()
this->postDeactivated();
unsetCursor();
+
+ Gui::MainWindow::getInstance()->hideHints();
}
//**************************************************************************
@@ -233,6 +237,16 @@ void ToolHandler::updateCursor()
}
}
+std::list ToolHandler::getToolHints() const
+{
+ return {};
+}
+
+void ToolHandler::updateHint() const
+{
+ Gui::getMainWindow()->showHints(getToolHints());
+}
+
void ToolHandler::applyCursor()
{
applyCursor(actCursor);
diff --git a/src/Gui/ToolHandler.h b/src/Gui/ToolHandler.h
index 11803c7561..88189c8b0c 100644
--- a/src/Gui/ToolHandler.h
+++ b/src/Gui/ToolHandler.h
@@ -35,7 +35,7 @@
namespace Gui
{
class View3DInventorViewer;
-
+struct InputHint;
class GuiExport ToolHandler
{
@@ -53,6 +53,9 @@ public:
/// enabling to set data member dependent icons (i.e. for different construction methods)
void updateCursor();
+ virtual std::list getToolHints() const;
+ void updateHint() const;
+
private: // NVI
virtual void preActivated()
{}
diff --git a/src/Mod/Sketcher/Gui/DrawSketchControllableHandler.h b/src/Mod/Sketcher/Gui/DrawSketchControllableHandler.h
index 6b07bd3baf..c1fa7151a2 100644
--- a/src/Mod/Sketcher/Gui/DrawSketchControllableHandler.h
+++ b/src/Mod/Sketcher/Gui/DrawSketchControllableHandler.h
@@ -160,7 +160,10 @@ private:
bool onModeChanged() override
{
DrawSketchHandler::resetPositionText();
+ DrawSketchHandler::updateHint();
+
toolWidgetManager.onHandlerModeChanged();
+
if (DSDefaultHandler::onModeChanged()) {
// If onModeChanged returns false, then the handler has been purged.
toolWidgetManager.afterHandlerModeChanged();
diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h
index 37b0f6834e..f113e59e81 100644
--- a/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h
+++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h
@@ -96,6 +96,17 @@ public:
SNAP_MODE_45Degree
};
+ std::list getToolHints() const override
+ {
+ using UserInput = Gui::InputHint::UserInput;
+
+ return {
+ {QWidget::tr("%1 change mode"), {UserInput::KeyM}},
+ {QWidget::tr("%1 start drawing"), {UserInput::MouseLeft}},
+ {QWidget::tr("%1 stop drawing"), {UserInput::MouseRight}},
+ };
+ }
+
void registerPressedKey(bool pressed, int key) override
{
if (Mode == STATUS_SEEK_Second && key == SoKeyboardEvent::M && pressed
diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h
index 54ce11d364..cf2d23d69e 100644
--- a/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h
+++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h
@@ -74,6 +74,29 @@ public:
{}
~DrawSketchHandlerPolygon() override = default;
+ std::list getToolHints() const override
+ {
+ using UserInput = Gui::InputHint::UserInput;
+
+ switch (state()) {
+ case SelectMode::SeekFirst:
+ return {
+ {QWidget::tr("%1 pick polygon center"), {UserInput::MouseLeft}},
+ {QWidget::tr("%1/%2 increase / decrease number of sides"),
+ {UserInput::KeyU, UserInput::KeyJ}},
+ };
+ case SelectMode::SeekSecond:
+ return {
+ {QWidget::tr("%1 pick rotation and size"), {UserInput::MouseMove}},
+ {QWidget::tr("%1 confirm"), {UserInput::MouseLeft}},
+ {QWidget::tr("%1/%2 increase / decrease number of sides"),
+ {UserInput::KeyU, UserInput::KeyJ}},
+ };
+ default:
+ return {};
+ }
+ }
+
private:
void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override
{