Files
create/src/Gui/InputHintWidget.cpp
2025-06-08 00:33:31 +02:00

368 lines
15 KiB
C++

// 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;
}
constexpr int iconSize = 22;
constexpr int iconMargin = 2;
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(iconSize, iconSize),
{{0xFFFFFF, color.rgb() & RGB_MASK}});
}
return generateKeyIcon(key, color, iconSize);
}();
QBuffer buffer;
image.save(&buffer, "png");
return QStringLiteral("<img src=\"data:image/png;base64,%1\" height=%2 />")
.arg(QString::fromLatin1(buffer.data().toBase64()))
.arg(iconSize);
};
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: %1px\" height=%1>"
"<tr>%2</tr>"
"</table>")
.arg(iconSize + iconMargin * 2);
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, int height)
{
constexpr int margin = 3;
constexpr int padding = 4;
constexpr int radius = 2;
const int iconSymbolHeight = height - 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 qreal dpr = BitmapFactoryInst::getMaximumDPR();
const int symbolWidth = std::max(textBoundingRect.width() + padding * 2, iconSymbolHeight);
const QRect keyRect(margin, margin, symbolWidth, iconSymbolHeight);
QPixmap pixmap((symbolWidth + margin * 2) * dpr, height * dpr);
pixmap.fill(Qt::transparent);
pixmap.setDevicePixelRatio(dpr);
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 QStringLiteral("");
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
/*: Keyboard key for Escape */
case KeyEscape: return tr("Esc");
/*: Keyboard key for Tab */
case KeyTab: return tr("Tab ⭾");
/*: Keyboard key for Backtab */
case KeyBacktab: return tr("Backtab");
case KeyBackspace: return QStringLiteral("");
case KeyReturn: return QStringLiteral("");
/*: Keyboard key for numpad Enter */
case KeyEnter: return tr("Enter");
/*: Keyboard key for Insert */
case KeyInsert: return tr("Insert");
/*: Keyboard key for Delete */
case KeyDelete: return tr("Del");
/*: Keyboard key for Pause */
case KeyPause: return tr("Pause");
/*: Keyboard key for Print */
case KeyPrintScr: return tr("Print");
/*: Keyboard key for SysReq */
case KeySysReq: return tr("SysReq");
/*: Keyboard key for Clear */
case KeyClear: return tr("Clear");
// cursor movement
/*: Keyboard key for Home */
case KeyHome: return tr("Home");
/*: Keyboard key for End */
case KeyEnd: return tr("End");
case KeyLeft: return QStringLiteral("");
case KeyUp: return QStringLiteral("");
case KeyRight: return QStringLiteral("");
case KeyDown: return QStringLiteral("");
/*: Keyboard key for Page Down */
case KeyPageUp: return tr("PgDown");
/*: Keyboard key for Page Up */
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
/*: Keyboard key for Shift on Windows & Linux */
case KeyShift: return tr("⇧ Shift");
/*: Keyboard key for Control on Windows & Linux */
case KeyControl: return tr("Ctrl");
#ifdef FC_OS_WIN32
case KeyMeta: return QStringLiteral("⊞ Win");
#else
case KeyMeta: return QStringLiteral("❖ Meta");
#endif
/*: Keyboard key for Alt on Windows & Linux */
case KeyAlt: return tr("Alt");
#endif
/*: Keyboard key for Caps Lock */
case KeyCapsLock: return tr("Caps Lock");
/*: Keyboard key for Num Lock */
case KeyNumLock: return tr("Num Lock");
/*: Keyboard key for Scroll 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");
// numpad
/*: Keyboard key for numpad 0 */
case KeyNum0: return tr("Num0");
/*: Keyboard key for numpad 1 */
case KeyNum1: return tr("Num1");
/*: Keyboard key for numpad 2 */
case KeyNum2: return tr("Num2");
/*: Keyboard key for numpad 3 */
case KeyNum3: return tr("Num3");
/*: Keyboard key for numpad 4 */
case KeyNum4: return tr("Num4");
/*: Keyboard key for numpad 5 */
case KeyNum5: return tr("Num5");
/*: Keyboard key for numpad 6 */
case KeyNum6: return tr("Num6");
/*: Keyboard key for numpad 7 */
case KeyNum7: return tr("Num7");
/*: Keyboard key for numpad 8 */
case KeyNum8: return tr("Num8");
/*: Keyboard key for numpad 9 */
case KeyNum9: return tr("Num9");
default: return QStringLiteral("???");
}
// clang-format on
}