Merge pull request #18961 from kadet1090/tool-hints
@@ -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");
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -305,6 +305,13 @@
|
||||
<file>overlay_error.svg</file>
|
||||
<file>feature_suppressed.svg</file>
|
||||
<file>forbidden.svg</file>
|
||||
<file>user-input/mouse-left.svg</file>
|
||||
<file>user-input/mouse-right.svg</file>
|
||||
<file>user-input/mouse-move.svg</file>
|
||||
<file>user-input/mouse-middle.svg</file>
|
||||
<file>user-input/mouse-scroll.svg</file>
|
||||
<file>user-input/mouse-scroll-up.svg</file>
|
||||
<file>user-input/mouse-scroll-down.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icons/FreeCAD-default">
|
||||
<file>index.theme</file>
|
||||
|
||||
4
src/Gui/Icons/user-input/mouse-left.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v7m-6 0h12M6 7a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V7Z"/>
|
||||
<path fill="#ffffff" d="M12 3v7H6V6l3-3h3Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
43
src/Gui/Icons/user-input/mouse-middle.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="mouse-middle.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="25.104167"
|
||||
inkscape:cx="11.213278"
|
||||
inkscape:cy="12.487967"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1414"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M 12,7.0481328 V 11.155187 M 6,7 C 6,4.790861 7.790861,3 10,3 h 4 c 2.209139,0 4,1.790861 4,4 v 10 c 0,2.209139 -1.790861,4 -4,4 H 10 C 7.790861,21 6,19.209139 6,17 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccsssssssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
src/Gui/Icons/user-input/mouse-move.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v7m-6 0h12M6 7a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 285 B |
4
src/Gui/Icons/user-input/mouse-right.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v7m-6 0h12M6 7a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V7Z"/>
|
||||
<path fill="#ffffff" d="M12 3v7h6V6l-3-3h-3Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 335 B |
43
src/Gui/Icons/user-input/mouse-scroll-down.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="mouse-scroll-down.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="50.208333"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1414"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M 12,5.873029 V 10 M 13.5,12 12,13.5 10.5,12 M 6,7 C 6,4.790861 7.790861,3 10,3 h 4 c 2.209139,0 4,1.790861 4,4 v 10 c 0,2.209139 -1.790861,4 -4,4 H 10 C 7.790861,21 6,19.209139 6,17 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cccccsssssssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
43
src/Gui/Icons/user-input/mouse-scroll-up.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="mouse-scroll-up.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="50.208333"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1414"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m 12,9 v 3 M 10.5,7 12,5.5 13.5,7 M 6,7 C 6,4.790861 7.790861,3 10,3 h 4 c 2.209139,0 4,1.790861 4,4 v 10 c 0,2.209139 -1.790861,4 -4,4 H 10 C 7.790861,21 6,19.209139 6,17 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cccccsssssssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
src/Gui/Icons/user-input/mouse-scroll.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v1m1.5 2L12 13.5 10.5 12m0-5L12 5.5 13.5 7M6 7a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 317 B |
222
src/Gui/InputHint.h
Normal file
@@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2024 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef GUI_INPUTHINT_H
|
||||
#define GUI_INPUTHINT_H
|
||||
|
||||
#include "FCGlobal.h"
|
||||
|
||||
#include <Qt>
|
||||
#include <QString>
|
||||
|
||||
#include <list>
|
||||
|
||||
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<UserInput> 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<UserInput> keys)
|
||||
: keys(std::move(keys))
|
||||
{}
|
||||
|
||||
InputSequence(const std::initializer_list<UserInput> keys)
|
||||
: keys(keys)
|
||||
{}
|
||||
};
|
||||
|
||||
QString message;
|
||||
std::list<InputSequence> sequences;
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#endif // GUI_INPUTHINT_H
|
||||
218
src/Gui/InputHintPy.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#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<const char*, InputHint::UserInput> 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<int>(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);
|
||||
}
|
||||
}
|
||||
34
src/Gui/InputHintPy.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef INPUTHINTPY_H
|
||||
#define INPUTHINTPY_H
|
||||
|
||||
#include <Base/PyObjectBase.h>
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
void registerUserInputEnumInPython(PyObject* module);
|
||||
} // Namespace Gui
|
||||
|
||||
#endif //INPUTHINTPY_H
|
||||
319
src/Gui/InputHintWidget.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
# include <QBuffer>
|
||||
# include <QPainter>
|
||||
#endif
|
||||
|
||||
#include <BitmapFactory.h>
|
||||
|
||||
#include "InputHint.h"
|
||||
#include "InputHintWidget.h"
|
||||
|
||||
Gui::InputHintWidget::InputHintWidget(QWidget* parent) : QLabel(parent)
|
||||
{}
|
||||
|
||||
void Gui::InputHintWidget::showHints(const std::list<InputHint>& 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("<img src=\"data:image/png;base64,%1\" width=%2 height=24 />")
|
||||
.arg(QLatin1String(buffer.data().toBase64()))
|
||||
.arg(image.width());
|
||||
};
|
||||
|
||||
const auto getHintHTML = [&](const InputHint& hint) {
|
||||
QString message = QStringLiteral("<td valign=bottom>%1</td>").arg(hint.message);
|
||||
|
||||
for (const auto& sequence : hint.sequences) {
|
||||
QList<QString> 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("<table style=\"line-height: 28px\" height=28>"
|
||||
"<tr>%1</tr>"
|
||||
"</table>");
|
||||
|
||||
setText(html.arg(messages.join(QStringLiteral("<td width=10></td>"))));
|
||||
}
|
||||
|
||||
void Gui::InputHintWidget::clearHints()
|
||||
{
|
||||
setText({});
|
||||
}
|
||||
|
||||
std::optional<const char*> 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
|
||||
}
|
||||
53
src/Gui/InputHintWidget.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef INPUTHINTWIDGET_H
|
||||
#define INPUTHINTWIDGET_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <optional>
|
||||
|
||||
#include <FCGlobal.h>
|
||||
|
||||
#include "InputHint.h"
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class GuiExport InputHintWidget : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InputHintWidget(QWidget *parent);
|
||||
|
||||
void showHints(const std::list<InputHint>& hints);
|
||||
void clearHints();
|
||||
|
||||
private:
|
||||
static std::optional<const char*> getCustomIconPath(InputHint::UserInput key);
|
||||
static QPixmap generateKeyIcon(InputHint::UserInput key, QColor color);
|
||||
static QString inputRepresentation(InputHint::UserInput key);
|
||||
};
|
||||
|
||||
} // Namespace Gui
|
||||
#endif //INPUTHINTWIDGET_H
|
||||
@@ -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 <Base/Color.h>
|
||||
|
||||
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<InputHint>& hints)
|
||||
{
|
||||
d->hintLabel->showHints(hints);
|
||||
}
|
||||
|
||||
void MainWindow::hideHints()
|
||||
{
|
||||
d->hintLabel->clearHints();
|
||||
}
|
||||
|
||||
// set text to the pane
|
||||
void MainWindow::setPaneText(int i, QString text)
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <QMdiArea>
|
||||
|
||||
#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<InputHint>& hints = {});
|
||||
void hideHints();
|
||||
|
||||
void initDockWindows(bool show);
|
||||
|
||||
|
||||
@@ -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<std::string> attr = {"getWindows", "getWindowsOfType", "setActiveWindow", "getActiveWindow", "addWindow", "removeWindow"};
|
||||
static constexpr std::initializer_list<const char*> attr = {
|
||||
"getWindows",
|
||||
"getWindowsOfType",
|
||||
"setActiveWindow",
|
||||
"getActiveWindow",
|
||||
"addWindow",
|
||||
"removeWindow",
|
||||
"showHint",
|
||||
"hideHint",
|
||||
};
|
||||
|
||||
Py::Object py = wrap.fromQWidget(mw, "QMainWindow");
|
||||
Py::ExtensionObject<MainWindowPy> 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<InputHint::UserInput>(value.as_long());
|
||||
};
|
||||
|
||||
static auto inputSequenceFromPyObject = [](const Py::Object& sequence) -> InputHint::InputSequence {
|
||||
if (sequence.isTuple()) {
|
||||
Py::Tuple pyInputs(sequence);
|
||||
|
||||
std::list<InputHint::UserInput> 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<InputHint::InputSequence> sequences;
|
||||
for (auto pySequence : pySequences) {
|
||||
sequences.push_back(inputSequenceFromPyObject(pySequence));
|
||||
}
|
||||
|
||||
return InputHint {
|
||||
.message = QString::fromStdString(object.getAttr("message").as_string()),
|
||||
.sequences = sequences
|
||||
};
|
||||
};
|
||||
|
||||
std::list<InputHint> 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();
|
||||
}
|
||||
|
||||
@@ -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<MainWindow> _mw;
|
||||
};
|
||||
|
||||
@@ -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<InputHint> ToolHandler::getToolHints() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void ToolHandler::updateHint() const
|
||||
{
|
||||
Gui::getMainWindow()->showHints(getToolHints());
|
||||
}
|
||||
|
||||
void ToolHandler::applyCursor()
|
||||
{
|
||||
applyCursor(actCursor);
|
||||
|
||||
@@ -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<InputHint> getToolHints() const;
|
||||
void updateHint() const;
|
||||
|
||||
private: // NVI
|
||||
virtual void preActivated()
|
||||
{}
|
||||
|
||||
@@ -291,6 +291,36 @@ class Arc(gui_base_original.Creator):
|
||||
self.step = 4
|
||||
self.drawArc()
|
||||
|
||||
self.updateHints()
|
||||
|
||||
def getHints(self):
|
||||
hint_global = Gui.InputHint(translate("draft", "%1 toggle global"), Gui.UserInput.KeyG)
|
||||
hint_continue = Gui.InputHint(translate("draft", "%1 toggle continue"), Gui.UserInput.KeyN)
|
||||
|
||||
if self.step == 0:
|
||||
return [
|
||||
Gui.InputHint(translate("draft", "%1 pick center"), Gui.UserInput.MouseLeft),
|
||||
hint_global,
|
||||
hint_continue,
|
||||
]
|
||||
elif self.step == 1:
|
||||
return [
|
||||
Gui.InputHint(translate("draft", "%1 pick radius"), Gui.UserInput.MouseLeft),
|
||||
hint_continue,
|
||||
]
|
||||
elif self.step == 2:
|
||||
return [
|
||||
Gui.InputHint(translate("draft", "%1 pick staring angle"), Gui.UserInput.MouseLeft),
|
||||
hint_continue,
|
||||
]
|
||||
elif self.step == 3:
|
||||
return [
|
||||
Gui.InputHint(translate("draft", "%1 pick aperture"), Gui.UserInput.MouseLeft),
|
||||
hint_continue,
|
||||
]
|
||||
else:
|
||||
return []
|
||||
|
||||
def drawArc(self):
|
||||
"""Actually draw the arc object."""
|
||||
rot, sup, pts, fil = self.getStrings()
|
||||
|
||||
@@ -136,6 +136,17 @@ class DraftTool:
|
||||
_toolmsg("{}".format(16*"-"))
|
||||
_toolmsg("GuiCommand: {}".format(self.featureName))
|
||||
|
||||
# update hints after the tool is fully initialized
|
||||
QtCore.QTimer.singleShot(0, self.updateHints)
|
||||
|
||||
def updateHints(self):
|
||||
Gui.HintManager.show(*self.getHints())
|
||||
|
||||
def getHints(self):
|
||||
return [
|
||||
Gui.InputHint("%1 constrain", Gui.UserInput.KeyShift)
|
||||
]
|
||||
|
||||
def end_callbacks(self, call):
|
||||
try:
|
||||
self.view.removeEventCallback("SoEvent", call)
|
||||
@@ -184,6 +195,8 @@ class DraftTool:
|
||||
todo.ToDo.delayCommit(self.commitList)
|
||||
self.commitList = []
|
||||
|
||||
Gui.HintManager.hide()
|
||||
|
||||
def commit(self, name, func):
|
||||
"""Store actions in the commit list to be run later.
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -96,6 +96,17 @@ public:
|
||||
SNAP_MODE_45Degree
|
||||
};
|
||||
|
||||
std::list<Gui::InputHint> 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
|
||||
|
||||
@@ -74,6 +74,29 @@ public:
|
||||
{}
|
||||
~DrawSketchHandlerPolygon() override = default;
|
||||
|
||||
std::list<Gui::InputHint> 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
|
||||
{
|
||||
|
||||