Add unlockable icon

This commit is contained in:
GriffinR 2025-12-07 20:18:08 -05:00
parent dcef773a33
commit df98d62339
7 changed files with 266 additions and 1 deletions

View File

@ -34,6 +34,7 @@
#include "newlayoutdialog.h"
#include "message.h"
#include "resizelayoutpopup.h"
#include "unlockableicon.h"
#if __has_include(<QJSValue>)
#include <QJSValue>
@ -354,6 +355,8 @@ private:
MapNavigation forwardNavigation;
bool ignoreNavigationRecords = false;
UnlockableIcon unlockableMainTabIcon;
QAction *copyAction = nullptr;
QAction *pasteAction = nullptr;
@ -460,7 +463,8 @@ private:
MapListToolBar* getCurrentMapListToolBar();
MapTree* getCurrentMapList();
void setLocationComboBoxes(const QStringList &locations);
void overrideMainTabIcons(const QIcon& icon);
void tryUnlockMainTabIcon(const Map* map);
QObjectList shortcutableObjects() const;
void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false);

View File

@ -0,0 +1,57 @@
#ifndef UNLOCKABLEICON_H
#define UNLOCKABLEICON_H
// Manages an icon loaded from an obfuscated data file containing the icon's image data and a key.
// The icon can only be accessed by inputting the correct key.
#include <QObject>
#include <QIcon>
#include <QString>
#include <QSet>
class UnlockableIcon : public QObject
{
Q_OBJECT
public:
UnlockableIcon(QObject* parent = nullptr);
UnlockableIcon(const QString& dataFilepath, QObject* parent = nullptr);
~UnlockableIcon() {};
// Create the obfuscated data file to load an unlockable icon from.
// Normally unused, this is only needed to update the resource data file.
static bool createDataFile(const QString& inputFilepath, const QString& outputFilepath, const QString& key);
bool load(const QString& dataFilepath);
void clear();
// Try to unlock the icon by matching the next character in the key.
// Progress resets if the character is not a match.
void tryUnlock(const QChar& c);
// Try to unlock the icon by matching the next character in the key.
// Progress resets if none of the characters in the set are a match.
void tryUnlock(const QSet<QChar>& cSet);
// Try to unlock the icon by matching the remaining characters in the key.
// Progress resets if any character in the string is not a match.
void tryUnlock(const QString& key);
bool isUnlocked() const;
QIcon icon() const;
signals:
void unlocked(const QIcon& icon);
private:
QIcon m_icon;
QString m_key;
quint32 m_keyIndex = 0;
bool m_loaded = false;
bool canUnlock() const;
bool tryKeyMatch(const QSet<QChar>& cSet);
};
#endif // UNLOCKABLEICON_H

View File

@ -148,6 +148,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/ui/regionmappropertiesdialog.cpp \
src/ui/colorpicker.cpp \
src/ui/loadingscreen.cpp \
src/ui/unlockableicon.cpp \
src/config.cpp \
src/editor.cpp \
src/main.cpp \
@ -268,6 +269,7 @@ HEADERS += include/core/advancemapparser.h \
include/ui/regionmappropertiesdialog.h \
include/ui/colorpicker.h \
include/ui/loadingscreen.h \
include/ui/unlockableicon.h \
include/config.h \
include/editor.h \
include/mainwindow.h \

View File

@ -102,6 +102,7 @@
<file>images/Entities_16x16.png</file>
<file>images/pokemon_icon_placeholder.png</file>
<file>images/porysplash.gif</file>
<file>images/unlockable_tab_icon.dat</file>
<file>icons/clipboard.ico</file>
<file>icons/map_go.ico</file>
</qresource>

View File

@ -0,0 +1 @@
檴蒚<EFBFBD><EFBFBD>𧒆葨輯\憳<><E686B3><EFBFBD><EFBFBD><E88989>悅悅□<E68285><EFBFBD><EFBFBD>悅悅菗恥悅悔鷉<E68294>氹悅<E6B0B9><E68285>姥猺尬管齒言<E9BD92><E8A880><EFBFBD>鷼均播艀悅<E88980><E68285>答祤╮息䰾媞<E4B0BE><E5AA9E><EFBFBD><EFBFBD><EFBFBD>芳搡鼎<E690A1><E9BC8E><EFBFBD><EFBFBD>嗾尺<E597BE><EFBFBD><E999BD><EFBFBD><EFBFBD>妙毽<E5A699><E6AFBD><EFBFBD>漣拓<E6BCA3>完𡾞<E5AE8C>悍𡟺<E6828D><F0A19FBA><EFBFBD><EFBFBD>㓤蹨<E393A4><E8B9A8><EFBFBD><E392A5>恕弩<E68195>𥥖知箕僈縧袢<E7B8A7><E8A2A2>𣶸<EFBFBD>㻛葍滬豪☆<E8B1AA><E29886><EFBFBD>𠱃<EFBFBD>𠶜<EFBFBD><F0A0B69C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E3B5A2><EFBFBD><E28F9B>ㄚ𣺹<E3849A><F0A3BAB9>湗栀<E6B997><E6A080>谷弩撕艦㓈袿麾<E8A2BF><E9BABE><EFBFBD>乖𠼱<E4B996>ˋ<CB8B><EFBFBD><E793BB><EFBFBD>𣽊尬<F0A3BD8A><E5B0AC><EFBFBD>撒秄往<E7A784><E5BE80>憤䬬<E686A4>襔蜈𣳿蛾<F0A3B3BF>嬰楹边<E6A5B9><EFBFBD><EFBFBD>的𠗕扛平首蛹隄䗩暀剏<E69A80><E5898F>𤤯撼<F0A4A4AF><E692BC>𡠺<EFBFBD>朱𧒆翔<F0A79286>枤硍螃<E7A18D><E89E83><EFBFBD>𧞅䠋<F0A79E85><E8BB8A><EFBCAB><EFBFBD>齒堮␞<E5A0AE><EFBFBD><EFBC9D><EFBFBD><E693B0><EFBFBD><E2978E><EFBFBD><EFBFBD><EFBFBD>蔆詭鵞<E8A9AD><E9B59E><EFBFBD><EFBFBD><EFBFBD><EFBFBD>恩▼<E681A9><EFBFBD><E8A2BF><EFBFBD><EFBFBD>燵𨑳鸌◢晻蚸<E699BB><E89AB8>蒈煖<E89288><E78596><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E6A2B3><EFBFBD>嵁飵㓁<E9A3B5><EFBFBD><E7A5A3>罒縐寎鍈<E5AF8E>案左悅悅撩<E68285>瑤冀悄<E58680><EFBFBD><E58490><EFBFBD><E897BB><EFBFBD><EFBFBD>悅息佩<E681AF>蒏悅悌𥋘<E6828C><F0A58B98><EFBFBD><E9998B>悅悅炤郎虞悅梴悅<E6A2B4><E68285>芝食<E88A9D>悅悅媡抅葷蝦須<E89DA6><EFBFBD><E78880><EFBFBD>絳冥啹<E586A5>成夤纒雅悌<E99B85>梄限<E6A284><E99990><EFBFBD><E592AF><EFBFBD>𤩐妙䊢<E5A699><E48AA2><EFBFBD><EFBFBD><E89B83><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E793B0>鳩冥泿<E586A5>ˊ<EFBFBD><CB8A><EFBFBD>馱皒鼎<E79A92><E9BC8E><EFBFBD><EFBFBD><EFBFBD><E5BC9B>詅蛂<E8A985><E89B82><EFBFBD><EFBFBD><EFBFBD>瓡椚<E793A1>蠔譫<E8A094>抅㦛<E68A85><E3A69B><EFBFBD><E58386><EFBFBD><E7A7BA><EFBFBD>廿馭<E5BBBF><EFBFBD><EFBFBD><EFBFBD><E69380>猷黇<E78CB7><E9BB87>𨯙<F0A8AF99><EFBCAB>𥶹<EFBFBD><F0A5B6B9><EFBFBD><E8B49C><EFBFBD><EFBFBD><EFBFBD><E99990><EFBFBD>嵗爰鱝疙鮗<E79699><EFBFBD><EFBFBD>祪珀炒擘𠘑≦<F0A09891><E289A6>凝悅<E5879D>䌫蓿𥋘𥣞賓<F0A5A39E><EFBFBD><EFBFBD><E9A3A7><EFBFBD>𢆡<EFBFBD>悅狙凳膘˙<E88698>葵煍<E891B5><E7858D><EFBFBD><EFBFBD>絳窙㭘悅憐<E68285><EFBFBD><E5A1BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𩜠郎萰<E9838E>疚𦹄<E7969A>欿<EFBFBD>ǜ<EFBFBD>𤀑<EFBFBD>芳㜃権<E39C83><E6A8A9>𨑳◎楈〢<E6A588><E380A2><EFBFBD><E692AE><EFBFBD><EFBFBD>癎階靰<E99A8E><E99DB0><EFBFBD><EFBFBD><EFBFBD>貍勌舝<E58B8C><E8889D><EFBFBD>ㄗ猼煨棫祣痈<E7A5A3><E79788>𪇵<EFBFBD><F0AA87B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E7998D>豑𡠺<E8B191><F0A1A0BA><EFBFBD><EFBFBD><EFBFBD><E5838A><EFBFBD><E690A3><EFBFBD><EFBFBD><E9819C><EFBFBD>𩷶╲<F0A9B7B6><E295B2>萓濯<E89093><E6BFAF>𧚔鿋盲末<E79BB2><E69CAB><EFBFBD>𣫺甅菁<E79485>管㗾腦稄<E885A6><E7A884><EFBFBD><EFBFBD><EFBFBD>䦉緒𧦠<E7B792><EFBFBD><EFBFBD>舚䊢汙榀<E6B199><E6A680>𠯫<EFBFBD><F0A0AFAB>帕縉<E5B895><EFBFBD><E999A9><EFBFBD><EFBFBD><E5B08C><EFBFBD><EFBFBD>楰𨯙睎<F0A8AF99><E79D8E>稱腄𤣩〞<F0A4A3A9><E3809E><EFBFBD><EFBFBD><EFBFBD><EFBFBD>僩樑<E583A9><E6A891><EFBFBD><E6A59E><EFBFBD><EFBFBD><EFBFBD><E8928F><EFBFBD>㵯⏚拐搘尤<E69098><EFBFBD><E5A4A5><EFBFBD><E6AB8A><EFBFBD>抿棌<E68ABF>戍概<E6888D>楃𦜖<E6A583>殤葦<E6AEA4><E891A6><EFBFBD><EFBFBD>𤜯矬㨃<E79FAC>倍鰟<E5808D><E9B09F>筑挪■ㄒ<E296A0><EFBFBD><E7A883><EFBFBD>歒瑌<E6AD92>爾石<E788BE><E79FB3><EFBFBD><EFBFBD><EFBFBD>朴石<E69CB4>防防<E998B2><E998B2><EFBFBD><E4BA95><EFBFBD>龟穴<E9BE9F><EFBFBD><E8A290><EFBFBD><EFBFBD><EFBFBD><E8A998><EFBFBD><E3BF97>㚰鸘市<E9B898>左硉<E5B7A6><E7A189><EFBFBD><EFBFBD><EFBFBD><E4B996>飩黾<E9A3A9><E9BBBE><EFBFBD><E8AAB9><EFBFBD><EFBFBD>齒櫊<E9BD92><E6AB8A><EFBFBD>僮邑鮗<E98291><EFBFBD>𨀉坐<F0A88089>∞䳍瑐蒄<E79190>舅欿陽溶劃誠<E58A83><E8AAA0><EFBFBD>葭𧇍<E891AD><EFBFBD><E4A486><EFBFBD><E38497><EFBFBD><EFBFBD><EFBFBD>ˋ𧋦<CB8B>𤀑<EFBFBD><F0A48091>石瑑<E79191>羲䣳∞蛩<E2889E>𠶖<EFBFBD>策椽<E7AD96><E6A4BD><EFBFBD><EFBFBD>𤨓<EFBFBD><F0A4A893><EFBFBD><EFBFBD><EFBFBD><EFBFBD>㑳萫𢜟葄成<E89184><E68890><EFBFBD><E785A5><EFBFBD>劂疝ㄜ𧊅<E3849C><F0A78A85><EFBFBD>葵櫊丑𧊋<E4B891><F0A78A8B>尾萷嵋𠳿<E5B58B><F0A0B3BF>嚏涤<E59A8F><E6B6A4><EFBCAD>楤技<E6A5A4><E68A80><EFBFBD><EFBFBD><E8949A><EFBFBD>障阸食<E998B8><E9A39F><EFBFBD><EFBFBD><E79CB2>㜜祣<E39C9C><EFBFBD>央憚㨪靼<E3A8AA><EFBFBD><E39AB9>憐睅<E68690><E79D85>躀俤<E8BA80><E4BFA4>礶𤄙<E7A4B6><F0A48499><EFBFBD>ㄨ鸘<E384A8><EFBFBD>𤜯╯<F0A49CAF>蘑婺<E89891>夾剳<E5A4BE>楺𠹭皿<F0A0B9AD><E79ABF>漫擘<E6BCAB><E69398>鱓鮟<E9B193>葾𧡘<E891BE><EFBFBD><E8BB9E>妨蛪媦龬矢酉𢞁<E98589>尼汝絍<E6B19D><EFBFBD><E8869A>罹蛚𣱣𦋐苸𧗾脃撬蚼<E692AC><E89ABC>丹𤪓聛玏悅<E78E8F>撩蔬鼓<E894AC><E9BC93><EFBFBD><E99A8E>洵䐭氈儷捏䲰僊犒𤽜<E78A92><EFBFBD>◥裗<E297A5><E8A397><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𦋐傈炙咿限菁囿猻”儐楊慰葖焩<E89196><E784A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>撫縐<E692AB><E7B890><EFBFBD><EFBFBD>煆恩<E78586><EFBFBD>㭘巨𤨪<E5B7A8><EFBFBD><E690A6><EFBFBD><EFBFBD><E7A4B6><EFBFBD><E7928D>汝袧桓𦴣<F0A6B4A3><EFBCAC>穴㷛␟僴<E2909F><E583B4><EFBFBD>𥅾<EFBFBD>搷戒<E690B7><EFBFBD><E6A387>𪃳<EFBFBD><F0AA83B3><EFBFBD>𦾾<EFBFBD>弩䰻<E5BCA9>帑軜遙<E8BB9C><E98199><EFBFBD><E88F8C>漱縈<E6BCB1>搇暀蝙<E69A80><E89D99><EFBFBD><EFBFBD><E8A1AE><EFBFBD><EFBFBD>岡銾墚蕊<E5A29A><E8958A><EFBFBD><EFBFBD><EFBFBD>悟愇籰黆<E7B1B0><EFBFBD><E5B78D><EFBFBD><EFBFBD><EFBFBD><E695AF><EFBFBD><EFBFBD><EFBFBD><E8A980><EFBFBD><EFBFBD>雁秣斒蚺<E69692><E89ABA>瑀誧岷<E8AAA7><E5B2B7><EFBFBD><EFBFBD>皏塈<E79A8F><EFBFBD><E5B896>𥐙𤽜袧<F0A4BD9C><E8A2A7>煍濤<E7858D><EFBFBD>泿<EFBFBD>蕃㽼<E89583><E3BDBC><EFBFBD><EFBFBD><EFBFBD>𣇪心笣眉<E7ACA3><E79C89><EFBFBD><EFBFBD><EFBFBD>遞飵<E9819E><E9A3B5><EFBFBD>遘壕<E98198><EFBFBD>𡆀狎<F0A18680><E78B8E><EFBFBD><EFBFBD><EFBFBD><EFBFBD>葾𤌍悅<F0A48C8D><E68285><EFBFBD>嫦𧊋<E5ABA6><EFBFBD><E8A2BF><EFBFBD><EFBFBD>𥺁蛻<F0A5BA81><EFBFBD>岷噢<E5B2B7><E599A2>婹坾<E5A9B9><E59DBE><EFBFBD>𡠠<EFBFBD><EFBFBD><E9878D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E583B1><EFBFBD><EFBFBD><E895A8><EFBFBD><EFBFBD><EFBFBD><E5BD8D>㗲崵𠼱<E5B4B5>擦袛冀𤊄𪷿叚嶽悅悅<E68285>彿<EFBFBD>𠗕<EFBFBD><F0A09795>

View File

@ -334,6 +334,8 @@ void MainWindow::initCustomUI() {
ui->mainTabBar->setTabIcon(i, mainTabIcons.value(i));
}
this->unlockableMainTabIcon.load(":/images/unlockable_tab_icon.dat");
// Create map header data widget
this->mapHeaderForm = new MapHeaderForm();
ui->layout_HeaderData->addWidget(this->mapHeaderForm);
@ -343,6 +345,12 @@ void MainWindow::initCustomUI() {
ui->graphicsView_Map->setResizeAnchor(QGraphicsView::ViewportAnchor::AnchorUnderMouse);
}
void MainWindow::overrideMainTabIcons(const QIcon& icon) {
for (int i = 1; i < ui->mainTabBar->count(); i++) {
ui->mainTabBar->setTabIcon(i, icon);
}
}
void MainWindow::initExtraSignals() {
connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged);
@ -2285,6 +2293,8 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index)
ui->stackedWidget_MapEvents->setCurrentIndex(1);
} else if (index == MainTab::Connections) {
ui->graphicsView_Connections->setFocus(); // Avoid opening tab with focus on something editable
connect(this, &MainWindow::mapOpened, this, &MainWindow::tryUnlockMainTabIcon, Qt::UniqueConnection);
connect(&this->unlockableMainTabIcon, &UnlockableIcon::unlocked, this, &MainWindow::overrideMainTabIcons, Qt::UniqueConnection);
}
if (!editor->map) return;
@ -2294,6 +2304,18 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index)
}
}
void MainWindow::tryUnlockMainTabIcon(const Map* map) {
if (!map || this->unlockableMainTabIcon.isUnlocked()) return;
const Layout* layout = map->layout();
if (!layout) return;
QSet<QChar> chars;
if (!layout->name.isEmpty()) chars.insert(layout->name.at(0));
const QString tilesetName = Tileset::stripPrefix(layout->tileset_secondary_label);
if (!tilesetName.isEmpty()) chars.insert(tilesetName.at(0));
this->unlockableMainTabIcon.tryUnlock(chars);
}
void MainWindow::on_actionZoom_In_triggered() {
editor->scaleMapView(1);
}

178
src/ui/unlockableicon.cpp Normal file
View File

@ -0,0 +1,178 @@
#include "unlockableicon.h"
#include <QFile>
#include <QBuffer>
#include <QRandomGenerator>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
constexpr int Version = QDataStream::Qt_6_8;
#endif
UnlockableIcon::UnlockableIcon(QObject* parent) : QObject(parent) {};
UnlockableIcon::UnlockableIcon(const QString& dataFilepath, QObject* parent) : UnlockableIcon(parent) {
load(dataFilepath);
};
#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0))
bool UnlockableIcon::createDataFile(const QString&, const QString&, const QString&) { return false; }
#else
bool UnlockableIcon::createDataFile(const QString& inputFilepath, const QString& outputFilepath, const QString& key) {
if (inputFilepath.isEmpty() || outputFilepath.isEmpty() || key.isEmpty()) return false;
if (key.length() >= std::numeric_limits<quint8>::max()) return false;
QByteArray key64 = key.toUtf8().toBase64();
if (key64.length() >= std::numeric_limits<quint8>::max()) return false;
QImage iconImage(inputFilepath);
if (iconImage.isNull()) return false;
QByteArray iconData;
QBuffer buffer(&iconData);
buffer.open(QIODevice::WriteOnly);
iconImage.save(&buffer, "PNG");
buffer.close();
if (iconData.length() >= std::numeric_limits<quint16>::max()) return false;
QByteArray iconData64 = iconData.toBase64();
if (iconData64.length() >= std::numeric_limits<quint16>::max()) return false;
QFile file(outputFilepath);
if (!file.open(QIODevice::WriteOnly)) return false;
QDataStream out(&file);
out.setVersion(Version);
quint8 r = QRandomGenerator::global()->bounded(std::numeric_limits<quint8>::max());
out << r;
out << static_cast<quint8>(key.length() ^ r);
out << static_cast<quint8>(key64.length() ^ r);
for (const auto& byte : key64) out << static_cast<quint8>(byte ^ r);
out << static_cast<quint16>(iconData.length() ^ (r | (r << 8)));
out << static_cast<quint16>(iconData64.length() ^ (r | (r << 8)));
for (const auto& byte : iconData64) out << static_cast<quint8>(byte ^ r);
file.close();
return true;
}
#endif
#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0))
bool UnlockableIcon::load(const QString&) { return false; }
#else
bool UnlockableIcon::load(const QString& dataFilepath) {
clear();
QFile file(dataFilepath);
if (!file.open(QIODevice::ReadOnly)) return false;
QDataStream in(&file);
in.setVersion(Version);
quint8 r = 0;
in >> r;
quint8 keyLength = 0;
in >> keyLength;
keyLength ^= r;
if (keyLength == 0) return false;
quint8 key64Length = 0;
in >> key64Length;
key64Length ^= r;
if (key64Length == 0) return false;
QByteArray key64(key64Length,0);
for (quint8 i = 0; i < key64Length; i++) {
in >> key64[i];
key64[i] ^= r;
}
QString key = QString(QByteArray::fromBase64(key64));
if (key.length() != keyLength) return false;
quint16 iconDataLength = 0;
in >> iconDataLength;
iconDataLength ^= (r | r << 8);
if (iconDataLength == 0) return false;
quint16 iconData64Length = 0;
in >> iconData64Length;
iconData64Length ^= (r | r << 8);
if (iconData64Length == 0) return false;
QByteArray iconData64(iconData64Length,0);
for (quint16 i = 0; i < iconData64Length; i++) {
in >> iconData64[i];
iconData64[i] ^= r;
}
QByteArray iconData = QByteArray::fromBase64(iconData64);
if (iconData.length() != iconDataLength) return false;
QPixmap iconPixmap;
iconPixmap.loadFromData(iconData);
if (iconPixmap.isNull()) return false;
m_icon = QIcon(iconPixmap);
m_key = key;
m_loaded = true;
return true;
}
#endif
void UnlockableIcon::clear() {
m_icon = QIcon();
m_key = QString();
m_keyIndex = 0;
m_loaded = false;
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0))
bool UnlockableIcon::isUnlocked() const { return false; }
#else
bool UnlockableIcon::isUnlocked() const {
return m_loaded && m_keyIndex >= m_key.length();
}
#endif
#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0))
bool UnlockableIcon::canUnlock() const { return false; }
#else
bool UnlockableIcon::canUnlock() const {
return m_loaded && m_keyIndex < m_key.length();
}
#endif
QIcon UnlockableIcon::icon() const {
return isUnlocked() ? m_icon : QIcon();
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0))
bool UnlockableIcon::tryKeyMatch(const QSet<QChar>&) { return false; }
#else
bool UnlockableIcon::tryKeyMatch(const QSet<QChar>& cSet) {
if (m_keyIndex >= m_key.length()) return false;
if (!cSet.contains(m_key.at(m_keyIndex))) {
m_keyIndex = 0;
return false;
}
if (++m_keyIndex == m_key.length()) {
emit unlocked(m_icon);
}
return true;
}
#endif
void UnlockableIcon::tryUnlock(const QSet<QChar>& cSet) {
if (canUnlock()) tryKeyMatch(cSet);
}
void UnlockableIcon::tryUnlock(const QChar& c) {
tryUnlock(QSet<QChar>{c});
}
void UnlockableIcon::tryUnlock(const QString& key) {
if (!canUnlock()) return;
for (const QChar& c : key) {
if (!tryKeyMatch(QSet<QChar>{c})) return;
}
}