Merge b24bf1db7616b43d390a9ee11bbc50af6172c8b7 into be6d2251aad2e0a609f63ce8c7e46fefa858ded1

This commit is contained in:
David Capello 2025-02-27 12:54:40 -03:00 committed by GitHub
commit ac4f6df599
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 661 additions and 583 deletions

View File

@ -1696,7 +1696,7 @@ allow_load_lib_access = &Allow Load External Library
give_full_access = Give Script Full &Access
stop_script = &Stop Script
[select_accelerator]
[select_shortcut]
title = Keyboard Shortcut
key = Key:
clear = Clear

View File

@ -1,7 +1,8 @@
<!-- Aseprite -->
<!-- Copyright (C) 2025 by Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<gui>
<window id="select_accelerator" text="@.title">
<window id="select_shortcut" text="@.title">
<vbox expansive="true">
<grid columns="3">
<label text="@.key" />

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2018-2024 Igara Studio S.A.
# Copyright (C) 2018-2025 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
# Generate a ui::Widget for each widget in a XML file
@ -667,7 +667,7 @@ target_sources(app-lib PRIVATE
ui/rgbmap_algorithm_selector.cpp
ui/sampling_selector.cpp
ui/search_entry.cpp
ui/select_accelerator.cpp
ui/select_shortcut.cpp
ui/selection_mode_field.cpp
ui/skin/font_data.cpp
ui/skin/skin_part.cpp

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -116,8 +116,8 @@ bool can_call_global_shortcut(const AppMenuItem::Native* native)
native->keyContext == KeyboardShortcuts::instance()->getCurrentKeyContext());
}
// TODO this should be on "she" library (or we should use
// os::Shortcut instead of ui::Accelerators)
// TODO this should be on laf-os library (or we should use
// os::Shortcut instead of ui::Shortcuts)
int from_scancode_to_unicode(KeyScancode scancode)
{
static int map[] = {
@ -284,19 +284,19 @@ void destroy_menu_item(ui::Widget* item)
os::Shortcut get_os_shortcut_from_key(const Key* key)
{
if (key && !key->accels().empty()) {
const ui::Accelerator& accel = key->accels().front();
if (key && !key->shortcuts().empty()) {
const ui::Shortcut& shortcut = key->shortcuts().front();
#if LAF_MACOS
// Shortcuts with spacebar as modifier do not work well in macOS
// (they will be called when the space bar is unpressed too).
if ((accel.modifiers() & ui::kKeySpaceModifier) == ui::kKeySpaceModifier)
if ((shortcut.modifiers() & ui::kKeySpaceModifier) == ui::kKeySpaceModifier)
return os::Shortcut();
#endif
return os::Shortcut(
(accel.unicodeChar() ? accel.unicodeChar() : from_scancode_to_unicode(accel.scancode())),
accel.modifiers());
return os::Shortcut((shortcut.unicodeChar() ? shortcut.unicodeChar() :
from_scancode_to_unicode(shortcut.scancode())),
shortcut.modifiers());
}
else
return os::Shortcut();

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -54,11 +55,11 @@ void AdvancedModeCommand::onExecute(Context* context)
if (oldMode == MainWindow::NormalMode && pref.advancedMode.showAlert()) {
KeyPtr key = KeyboardShortcuts::instance()->command(this->id().c_str());
if (!key->accels().empty()) {
if (!key->shortcuts().empty()) {
app::gen::AdvancedMode window;
window.warningLabel()->setTextf("You can go back pressing \"%s\" key.",
key->accels().front().toString().c_str());
key->shortcuts().front().toString().c_str());
window.openWindowInForeground();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -23,7 +23,7 @@
#include "app/ui/app_menuitem.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/search_entry.h"
#include "app/ui/select_accelerator.h"
#include "app/ui/select_shortcut.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "base/fs.h"
@ -151,7 +151,7 @@ public:
, m_keyOrig(key ? new Key(*key) : nullptr)
, m_menuitem(menuitem)
, m_level(level)
, m_hotAccel(-1)
, m_hotShortcut(-1)
, m_lockButtons(false)
, m_headerItem(headerItem)
{
@ -204,45 +204,45 @@ public:
}
private:
void onChangeAccel(int index)
void onChangeShortcut(int index)
{
LockButtons lock(this);
Accelerator origAccel = m_key->accels()[index];
SelectAccelerator window(origAccel, m_key->keycontext(), m_keys);
Shortcut origShortcut = m_key->shortcuts()[index];
SelectShortcut window(origShortcut, m_key->keycontext(), m_keys);
window.openWindowInForeground();
if (window.isModified()) {
m_key->disableAccel(origAccel, KeySource::UserDefined);
if (!window.accel().isEmpty())
m_key->add(window.accel(), KeySource::UserDefined, m_keys);
m_key->disableShortcut(origShortcut, KeySource::UserDefined);
if (!window.shortcut().isEmpty())
m_key->add(window.shortcut(), KeySource::UserDefined, m_keys);
}
this->window()->layout();
}
void onDeleteAccel(int index)
void onDeleteShortcut(int index)
{
LockButtons lock(this);
// We need to create a copy of the accelerator because
// Key::disableAccel() will modify the accels() collection itself.
ui::Accelerator accel = m_key->accels()[index];
// We need to create a copy of the shortcut because
// Key::disableShortcut() will modify the shortcuts() collection itself.
ui::Shortcut shortcut = m_key->shortcuts()[index];
if (ui::Alert::show(Strings::alerts_delete_shortcut(accel.toString())) != 1)
if (ui::Alert::show(Strings::alerts_delete_shortcut(shortcut.toString())) != 1)
return;
m_key->disableAccel(accel, KeySource::UserDefined);
m_key->disableShortcut(shortcut, KeySource::UserDefined);
window()->layout();
}
void onAddAccel()
void onAddShortcut()
{
LockButtons lock(this);
ui::Accelerator accel;
SelectAccelerator window(accel, m_key ? m_key->keycontext() : KeyContext::Any, m_keys);
ui::Shortcut shortcut;
SelectShortcut window(shortcut, m_key ? m_key->keycontext() : KeyContext::Any, m_keys);
window.openWindowInForeground();
if ((window.isModified()) ||
// We can assign a "None" accelerator to mouse wheel actions
// We can assign a "None" shortcut to mouse wheel actions
(m_key && m_key->type() == KeyType::WheelAction && window.isOK())) {
if (!m_key) {
ASSERT(m_menuitem);
@ -256,7 +256,7 @@ private:
m_menuKeys[m_menuitem] = m_key;
}
m_key->add(window.accel(), KeySource::UserDefined, m_keys);
m_key->add(window.shortcut(), KeySource::UserDefined, m_keys);
}
this->window()->layout();
@ -273,8 +273,8 @@ private:
size.w = std::max(size.w, w);
}
if (m_key && !m_key->accels().empty()) {
size_t combos = m_key->accels().size();
if (m_key && !m_key->shortcuts().empty()) {
size_t combos = m_key->shortcuts().size();
if (combos > 1)
size.h *= combos;
}
@ -315,7 +315,7 @@ private:
}
}
if (m_key && !m_key->accels().empty()) {
if (m_key && !m_key->shortcuts().empty()) {
if (m_key->keycontext() != KeyContext::Any) {
g->drawText(convertKeyContextToUserFriendlyString(m_key->keycontext()),
fg,
@ -324,13 +324,14 @@ private:
}
const int dh = th + 4 * guiscale();
IntersectClip clip(g,
gfx::Rect(keyXPos, y, contextXPos - keyXPos, dh * m_key->accels().size()));
IntersectClip clip(
g,
gfx::Rect(keyXPos, y, contextXPos - keyXPos, dh * m_key->shortcuts().size()));
if (clip) {
int i = 0;
for (const Accelerator& accel : m_key->accels()) {
if (i != m_hotAccel || !m_changeButton) {
g->drawText(getAccelText(accel), fg, bg, gfx::Point(keyXPos, y));
for (const Shortcut& shortcut : m_key->shortcuts()) {
if (i != m_hotShortcut || !m_changeButton) {
g->drawText(getShortcutText(shortcut), fg, bg, gfx::Point(keyXPos, y));
}
y += dh;
++i;
@ -361,40 +362,41 @@ private:
gfx::Rect bounds = this->bounds();
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
const Accelerators* accels = (m_key ? &m_key->accels() : NULL);
const Shortcuts* shortcuts = (m_key ? &m_key->shortcuts() : NULL);
int y = bounds.y;
int dh = textSize().h + 4 * guiscale();
int maxi = (accels && accels->size() > 1 ? accels->size() : 1);
int maxi = (shortcuts && shortcuts->size() > 1 ? shortcuts->size() : 1);
auto theme = SkinTheme::get(this);
for (int i = 0; i < maxi; ++i, y += dh) {
int w = font()->textLength(
(accels && i < (int)accels->size() ? getAccelText((*accels)[i]) : std::string()));
int w = font()->textLength((shortcuts && i < (int)shortcuts->size() ?
getShortcutText((*shortcuts)[i]) :
std::string()));
gfx::Rect itemBounds(bounds.x + m_headerItem->keyXPos(), y, w, dh);
itemBounds = itemBounds.enlarge(
gfx::Border(4 * guiscale(), 0, 6 * guiscale(), 1 * guiscale()));
if (accels && i < (int)accels->size() && mouseMsg->position().y >= itemBounds.y &&
if (shortcuts && i < (int)shortcuts->size() && mouseMsg->position().y >= itemBounds.y &&
mouseMsg->position().y < itemBounds.y + itemBounds.h) {
if (m_hotAccel != i) {
m_hotAccel = i;
if (m_hotShortcut != i) {
m_hotShortcut = i;
m_changeConn = obs::connection();
m_changeButton.reset(new Button(""));
m_changeConn = m_changeButton->Click.connect([this, i] { onChangeAccel(i); });
m_changeConn = m_changeButton->Click.connect([this, i] { onChangeShortcut(i); });
m_changeButton->setStyle(theme->styles.miniButton());
addChild(m_changeButton.get());
m_deleteConn = obs::connection();
m_deleteButton.reset(new Button(""));
m_deleteConn = m_deleteButton->Click.connect([this, i] { onDeleteAccel(i); });
m_deleteConn = m_deleteButton->Click.connect([this, i] { onDeleteShortcut(i); });
m_deleteButton->setStyle(theme->styles.miniButton());
addChild(m_deleteButton.get());
m_changeButton->setBgColor(gfx::ColorNone);
m_changeButton->setBounds(itemBounds);
m_changeButton->setText(getAccelText((*accels)[i]));
m_changeButton->setText(getShortcutText((*shortcuts)[i]));
const char* label = "x";
m_deleteButton->setBgColor(gfx::ColorNone);
@ -411,7 +413,7 @@ private:
if (i == 0 && !m_addButton && (!m_menuitem || m_menuitem->getCommand())) {
m_addConn = obs::connection();
m_addButton.reset(new Button(""));
m_addConn = m_addButton->Click.connect([this] { onAddAccel(); });
m_addConn = m_addButton->Click.connect([this] { onAddShortcut(); });
m_addButton->setStyle(theme->styles.miniButton());
addChild(m_addButton.get());
@ -452,16 +454,16 @@ private:
m_addButton->setVisible(false);
}
m_hotAccel = -1;
m_hotShortcut = -1;
}
std::string getAccelText(const Accelerator& accel) const
std::string getShortcutText(const Shortcut& shortcut) const
{
if (m_key && m_key->type() == KeyType::WheelAction && accel.isEmpty()) {
if (m_key && m_key->type() == KeyType::WheelAction && shortcut.isEmpty()) {
return Strings::keyboard_shortcuts_default_action();
}
else {
return accel.toString();
return shortcut.toString();
}
}
@ -471,14 +473,14 @@ private:
KeyPtr m_keyOrig;
AppMenuItem* m_menuitem;
int m_level;
ui::Accelerators m_newAccels;
ui::Shortcuts m_newShortcuts;
std::shared_ptr<ui::Button> m_changeButton;
std::shared_ptr<ui::Button> m_deleteButton;
std::shared_ptr<ui::Button> m_addButton;
obs::scoped_connection m_changeConn;
obs::scoped_connection m_deleteConn;
obs::scoped_connection m_addConn;
int m_hotAccel;
int m_hotShortcut;
bool m_lockButtons;
HeaderItem* m_headerItem;
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -164,10 +164,10 @@ void NewBrushCommand::createBrush(const Site& site, const Mask* mask)
params.set("change", "custom");
params.set("slot", base::convert_to<std::string>(slot).c_str());
KeyPtr key = KeyboardShortcuts::instance()->command(CommandId::ChangeBrush(), params);
if (key && !key->accels().empty()) {
if (key && !key->shortcuts().empty()) {
std::string tooltip;
tooltip += Strings::new_brush_shortcut() + " ";
tooltip += key->accels().front().toString();
tooltip += key->shortcuts().front().toString();
StatusBar::instance()->showTip(2000, tooltip);
}
}

View File

@ -94,6 +94,7 @@ protected:
void onInitTheme(InitThemeEvent& ev) override;
LayoutIO* onGetLayoutIO() override { return this; }
void onNewDisplayConfiguration(Display* display) override;
bool onEnqueueMouseDown(MouseMessage* mouseMsg) override;
// LayoutIO implementation
std::string loadLayout(Widget* widget) override;
@ -678,6 +679,23 @@ void CustomizedGuiManager::onNewDisplayConfiguration(Display* display)
}
}
bool CustomizedGuiManager::onEnqueueMouseDown(MouseMessage* mouseMsg)
{
ASSERT(mouseMsg->type() == kMouseDownMessage);
// If there is no modal window running...
App* app = App::instance();
if (app && getForegroundWindow() == app->mainWindow()) {
// Process a mouse button as a shortcut.
if (processKey(mouseMsg)) {
// Don't enqueue this message
return false;
}
}
return true;
}
bool CustomizedGuiManager::processKey(Message* msg)
{
App* app = App::instance();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -19,9 +19,9 @@
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui_context.h"
#include "os/menus.h"
#include "ui/accelerator.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/shortcut.h"
#include "ui/size_hint_event.h"
#include "ui/widget.h"
@ -138,8 +138,8 @@ void AppMenuItem::onSizeHint(SizeHintEvent& ev)
size.h = +textHeight() + border().height();
if (m_key && !m_key->accels().empty()) {
size.w += font()->textLength(m_key->accels().front().toString());
if (m_key && !m_key->shortcuts().empty()) {
size.w += font()->textLength(m_key->shortcuts().front().toString());
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -431,8 +431,8 @@ void BrushPopup::regenerate(ui::Display* display, const gfx::Point& pos)
params.set("change", "custom");
params.set("slot", base::convert_to<std::string>(slot).c_str());
KeyPtr key = KeyboardShortcuts::instance()->command(CommandId::ChangeBrush(), params);
if (key && !key->accels().empty())
shortcut = key->accels().front().toString();
if (key && !key->shortcuts().empty())
shortcut = key->shortcuts().front().toString();
}
m_customBrushes->addItem(new SelectBrushItem(brush, slot));
m_customBrushes->addItem(new BrushShortcutItem(shortcut, slot));

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -41,10 +41,10 @@
#include "doc/layer.h"
#include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/accelerator.h"
#include "ui/alert.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/shortcut.h"
#include "ui/system.h"
#include "ui/view.h"

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -13,7 +13,7 @@
#include "app/ui/key_context.h"
#include "base/convert_to.h"
#include "base/vector2d.h"
#include "ui/accelerator.h"
#include "ui/shortcut.h"
#include <memory>
#include <utility>
@ -106,7 +106,7 @@ inline KeyAction operator&(KeyAction a, KeyAction b)
class Key;
using KeyPtr = std::shared_ptr<Key>;
using Keys = std::vector<KeyPtr>;
using KeySourceAccelList = std::vector<std::pair<KeySource, ui::Accelerator>>;
using KeySourceShortcutList = std::vector<std::pair<KeySource, ui::Shortcut>>;
using DragVector = base::Vector2d<double>;
class Key {
@ -119,29 +119,28 @@ public:
static KeyPtr MakeDragAction(WheelAction dragAction);
KeyType type() const { return m_type; }
const ui::Accelerators& accels() const;
const KeySourceAccelList addsKeys() const { return m_adds; }
const KeySourceAccelList delsKeys() const { return m_dels; }
const ui::Shortcuts& shortcuts() const;
const KeySourceShortcutList addsKeys() const { return m_adds; }
const KeySourceShortcutList delsKeys() const { return m_dels; }
void add(const ui::Accelerator& accel, const KeySource source, KeyboardShortcuts& globalKeys);
const ui::Accelerator* isPressed(const ui::Message* msg,
const KeyboardShortcuts& globalKeys,
const KeyContext keyContext) const;
const ui::Accelerator* isPressed(const ui::Message* msg,
const KeyboardShortcuts& globalKeys) const;
void add(const ui::Shortcut& shortcut, const KeySource source, KeyboardShortcuts& globalKeys);
const ui::Shortcut* isPressed(const ui::Message* msg,
const KeyboardShortcuts& globalKeys,
const KeyContext keyContext) const;
const ui::Shortcut* isPressed(const ui::Message* msg, const KeyboardShortcuts& globalKeys) const;
bool isPressed() const;
bool isLooselyPressed() const;
bool isCommandListed() const;
bool hasAccel(const ui::Accelerator& accel) const;
bool hasUserDefinedAccels() const;
bool hasShortcut(const ui::Shortcut& shortcut) const;
bool hasUserDefinedShortcuts() const;
// The KeySource indicates from where the key was disabled
// (e.g. if it was removed from an extension-defined file, or from
// user-defined).
void disableAccel(const ui::Accelerator& accel, const KeySource source);
void disableShortcut(const ui::Shortcut& shortcut, const KeySource source);
// Resets user accelerators to the original & extension-defined ones.
// Resets user shortcuts to the original & extension-defined ones.
void reset();
void copyOriginalToUser();
@ -164,11 +163,11 @@ public:
private:
KeyType m_type;
KeySourceAccelList m_adds;
KeySourceAccelList m_dels;
// Final list of accelerators after processing the
KeySourceShortcutList m_adds;
KeySourceShortcutList m_dels;
// Final list of shortcuts after processing the
// addition/deletion of extension-defined & user-defined keys.
mutable std::unique_ptr<ui::Accelerators> m_accels;
mutable std::unique_ptr<ui::Shortcuts> m_shortcuts;
KeyContext m_keycontext;
// for KeyType::Command

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -28,8 +28,8 @@
#include "app/xml_document.h"
#include "app/xml_exception.h"
#include "fmt/format.h"
#include "ui/accelerator.h"
#include "ui/message.h"
#include "ui/shortcut.h"
#include "tinyxml2.h"
@ -244,13 +244,13 @@ std::string get_user_friendly_string_for_wheelaction(app::WheelAction wheelActio
return std::string();
}
void erase_accel(app::KeySourceAccelList& kvs,
const app::KeySource source,
const ui::Accelerator& accel)
void erase_shortcut(app::KeySourceShortcutList& kvs,
const app::KeySource source,
const ui::Shortcut& shortcut)
{
for (auto it = kvs.begin(); it != kvs.end();) {
auto& kv = *it;
if (kv.first == source && kv.second == accel) {
if (kv.first == source && kv.second == shortcut) {
it = kvs.erase(it);
}
else
@ -258,7 +258,7 @@ void erase_accel(app::KeySourceAccelList& kvs,
}
}
void erase_accels(app::KeySourceAccelList& kvs, const app::KeySource source)
void erase_shortcuts(app::KeySourceShortcutList& kvs, const app::KeySource source)
{
for (auto it = kvs.begin(); it != kvs.end();) {
auto& kv = *it;
@ -436,92 +436,93 @@ KeyPtr Key::MakeDragAction(WheelAction dragAction)
return k;
}
const ui::Accelerators& Key::accels() const
const ui::Shortcuts& Key::shortcuts() const
{
if (!m_accels) {
m_accels = std::make_unique<ui::Accelerators>();
if (!m_shortcuts) {
m_shortcuts = std::make_unique<ui::Shortcuts>();
// Add default keys
for (const auto& kv : m_adds) {
if (kv.first == KeySource::Original)
m_accels->add(kv.second);
m_shortcuts->add(kv.second);
}
// Delete/add extension-defined keys
for (const auto& kv : m_dels) {
if (kv.first == KeySource::ExtensionDefined)
m_accels->remove(kv.second);
m_shortcuts->remove(kv.second);
else {
ASSERT(kv.first != KeySource::Original);
}
}
for (const auto& kv : m_adds) {
if (kv.first == KeySource::ExtensionDefined)
m_accels->add(kv.second);
m_shortcuts->add(kv.second);
}
// Delete/add user-defined keys
for (const auto& kv : m_dels) {
if (kv.first == KeySource::UserDefined)
m_accels->remove(kv.second);
m_shortcuts->remove(kv.second);
}
for (const auto& kv : m_adds) {
if (kv.first == KeySource::UserDefined)
m_accels->add(kv.second);
m_shortcuts->add(kv.second);
}
}
return *m_accels;
return *m_shortcuts;
}
void Key::add(const ui::Accelerator& accel, const KeySource source, KeyboardShortcuts& globalKeys)
void Key::add(const ui::Shortcut& shortcut, const KeySource source, KeyboardShortcuts& globalKeys)
{
m_adds.emplace_back(source, accel);
m_accels.reset();
m_adds.emplace_back(source, shortcut);
m_shortcuts.reset();
// Remove the accelerator from other commands
// Remove the shortcut from other commands
if (source == KeySource::ExtensionDefined || source == KeySource::UserDefined) {
erase_accel(m_dels, source, accel);
erase_shortcut(m_dels, source, shortcut);
globalKeys.disableAccel(accel, source, m_keycontext, this);
globalKeys.disableShortcut(shortcut, source, m_keycontext, this);
}
}
const ui::Accelerator* Key::isPressed(const Message* msg,
const KeyboardShortcuts& globalKeys,
const KeyContext keyContext) const
const ui::Shortcut* Key::isPressed(const Message* msg,
const KeyboardShortcuts& globalKeys,
const KeyContext keyContext) const
{
if (auto keyMsg = dynamic_cast<const KeyMessage*>(msg)) {
for (const Accelerator& accel : accels()) {
if (accel.isPressed(keyMsg->modifiers(), keyMsg->scancode(), keyMsg->unicodeChar()) &&
for (const Shortcut& shortcut : shortcuts()) {
if (shortcut.isPressed(keyMsg->modifiers(), keyMsg->scancode(), keyMsg->unicodeChar()) &&
(m_keycontext == KeyContext::Any || m_keycontext == keyContext)) {
return &accel;
return &shortcut;
}
}
}
else if (auto mouseMsg = dynamic_cast<const MouseMessage*>(msg)) {
for (const Accelerator& accel : accels()) {
if ((accel.modifiers() == mouseMsg->modifiers()) &&
for (const Shortcut& shortcut : shortcuts()) {
if ((shortcut.modifiers() == mouseMsg->modifiers()) &&
(shortcut.mouseButton() == mouseMsg->button()) &&
(m_keycontext == KeyContext::Any ||
// TODO we could have multiple mouse wheel key-context,
// like "sprite editor" context, or "timeline" context,
// etc.
m_keycontext == KeyContext::MouseWheel)) {
return &accel;
return &shortcut;
}
}
}
return nullptr;
}
const ui::Accelerator* Key::isPressed(const Message* msg, const KeyboardShortcuts& globalKeys) const
const ui::Shortcut* Key::isPressed(const Message* msg, const KeyboardShortcuts& globalKeys) const
{
return isPressed(msg, globalKeys, globalKeys.getCurrentKeyContext());
}
bool Key::isPressed() const
{
for (const Accelerator& accel : this->accels()) {
if (accel.isPressed())
for (const Shortcut& shortcut : this->shortcuts()) {
if (shortcut.isPressed())
return true;
}
return false;
@ -529,8 +530,8 @@ bool Key::isPressed() const
bool Key::isLooselyPressed() const
{
for (const Accelerator& accel : this->accels()) {
if (accel.isLooselyPressed())
for (const Shortcut& shortcut : this->shortcuts()) {
if (shortcut.isLooselyPressed())
return true;
}
return false;
@ -541,12 +542,12 @@ bool Key::isCommandListed() const
return type() == KeyType::Command && command()->isListed(params());
}
bool Key::hasAccel(const ui::Accelerator& accel) const
bool Key::hasShortcut(const ui::Shortcut& shortcut) const
{
return accels().has(accel);
return shortcuts().has(shortcut);
}
bool Key::hasUserDefinedAccels() const
bool Key::hasUserDefinedShortcuts() const
{
for (const auto& kv : m_adds) {
if (kv.first == KeySource::UserDefined)
@ -555,37 +556,37 @@ bool Key::hasUserDefinedAccels() const
return false;
}
void Key::disableAccel(const ui::Accelerator& accel, const KeySource source)
void Key::disableShortcut(const ui::Shortcut& shortcut, const KeySource source)
{
// It doesn't make sense that the default keyboard shortcuts file
// (gui.xml) removes some accelerator.
// (gui.xml) removes some shortcut.
ASSERT(source != KeySource::Original);
erase_accel(m_adds, source, accel);
erase_accel(m_dels, source, accel);
erase_shortcut(m_adds, source, shortcut);
erase_shortcut(m_dels, source, shortcut);
m_dels.emplace_back(source, accel);
m_accels.reset();
m_dels.emplace_back(source, shortcut);
m_shortcuts.reset();
}
void Key::reset()
{
erase_accels(m_adds, KeySource::UserDefined);
erase_accels(m_dels, KeySource::UserDefined);
m_accels.reset();
erase_shortcuts(m_adds, KeySource::UserDefined);
erase_shortcuts(m_dels, KeySource::UserDefined);
m_shortcuts.reset();
}
void Key::copyOriginalToUser()
{
// Erase all user-defined keys
erase_accels(m_adds, KeySource::UserDefined);
erase_accels(m_dels, KeySource::UserDefined);
erase_shortcuts(m_adds, KeySource::UserDefined);
erase_shortcuts(m_dels, KeySource::UserDefined);
// Then copy all original & extension-defined keys as user-defined
auto copy = m_adds;
for (const auto& kv : copy)
m_adds.emplace_back(KeySource::UserDefined, kv.second);
m_accels.reset();
m_shortcuts.reset();
}
std::string Key::triggerString() const
@ -693,21 +694,21 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
// add the keyboard shortcut to the command
KeyPtr key = this->command(command_name, params, keycontext);
if (key && command_key) {
Accelerator accel(command_key);
Shortcut shortcut(command_key);
if (!removed) {
key->add(accel, source, *this);
key->add(shortcut, source, *this);
// Add the shortcut to the menuitems with this command
// (this is only visual, the
// "CustomizedGuiManager::onProcessMessage" is the only
// one that process keyboard shortcuts)
if (key->accels().size() == 1) {
if (key->shortcuts().size() == 1) {
AppMenus::instance()->applyShortcutToMenuitemsWithCommand(command, params, key);
}
}
else
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -729,12 +730,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
KeyPtr key = this->tool(tool);
if (key && tool_key) {
LOG(VERBOSE, "KEYS: Shortcut for tool %s: %s\n", tool_id, tool_key);
Accelerator accel(tool_key);
Shortcut shortcut(tool_key);
if (!removed)
key->add(accel, source, *this);
key->add(shortcut, source, *this);
else
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -755,12 +756,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
KeyPtr key = this->quicktool(tool);
if (key && tool_key) {
LOG(VERBOSE, "KEYS: Shortcut for quicktool %s: %s\n", tool_id, tool_key);
Accelerator accel(tool_key);
Shortcut shortcut(tool_key);
if (!removed)
key->add(accel, source, *this);
key->add(shortcut, source, *this);
else
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -791,12 +792,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
action_id,
(keycontextstr ? keycontextstr : "Any"),
action_key);
Accelerator accel(action_key);
Shortcut shortcut(action_key);
if (!removed)
key->add(accel, source, *this);
key->add(shortcut, source, *this);
else
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -817,12 +818,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
KeyPtr key = this->wheelAction(action);
if (key && action_key) {
LOG(VERBOSE, "KEYS: Shortcut for wheel action %s: %s\n", action_id, action_key);
Accelerator accel(action_key);
Shortcut shortcut(action_key);
if (!removed)
key->add(accel, source, *this);
key->add(shortcut, source, *this);
else
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -854,12 +855,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
}
LOG(VERBOSE, "KEYS: Shortcut for drag action %s: %s\n", action_id, action_key);
Accelerator accel(action_key);
Shortcut shortcut(action_key);
if (!removed)
key->add(accel, source, *this);
key->add(shortcut, source, *this);
else
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -904,24 +905,24 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
void KeyboardShortcuts::exportKeys(XMLElement* parent, KeyType type)
{
for (KeyPtr& key : m_keys) {
// Save only user defined accelerators.
// Save only user defined shortcuts.
if (key->type() != type)
continue;
for (const auto& kv : key->delsKeys())
if (kv.first == KeySource::UserDefined)
exportAccel(parent, key.get(), kv.second, true);
exportShortcut(parent, key.get(), kv.second, true);
for (const auto& kv : key->addsKeys())
if (kv.first == KeySource::UserDefined)
exportAccel(parent, key.get(), kv.second, false);
exportShortcut(parent, key.get(), kv.second, false);
}
}
void KeyboardShortcuts::exportAccel(XMLElement* parent,
const Key* key,
const ui::Accelerator& accel,
bool removed)
void KeyboardShortcuts::exportShortcut(XMLElement* parent,
const Key* key,
const ui::Shortcut& shortcut,
bool removed)
{
XMLElement* elem = parent->InsertNewChildElement("key");
@ -964,7 +965,7 @@ void KeyboardShortcuts::exportAccel(XMLElement* parent,
break;
}
elem->SetAttribute("shortcut", accel.toString().c_str());
elem->SetAttribute("shortcut", shortcut.toString().c_str());
if (removed)
elem->SetAttribute("removed", "true");
@ -1062,20 +1063,20 @@ KeyPtr KeyboardShortcuts::dragAction(const WheelAction dragAction) const
return key;
}
void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel,
const KeySource source,
const KeyContext keyContext,
const Key* newKey)
void KeyboardShortcuts::disableShortcut(const ui::Shortcut& shortcut,
const KeySource source,
const KeyContext keyContext,
const Key* newKey)
{
for (KeyPtr& key : m_keys) {
if (key.get() != newKey && key->keycontext() == keyContext && key->hasAccel(accel) &&
if (key.get() != newKey && key->keycontext() == keyContext && key->hasShortcut(shortcut) &&
// Tools can contain the same keyboard shortcut
(key->type() != KeyType::Tool || newKey == nullptr || newKey->type() != KeyType::Tool) &&
// DragActions can share the same keyboard shortcut (e.g. to
// change different values using different DragVectors)
(key->type() != KeyType::DragAction || newKey == nullptr ||
newKey->type() != KeyType::DragAction)) {
key->disableAccel(accel, source);
key->disableShortcut(shortcut, source);
}
}
}
@ -1168,12 +1169,12 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
const ui::Message* msg)
{
WheelAction wheelAction = WheelAction::None;
const ui::Accelerator* bestAccel = nullptr;
const ui::Shortcut* bestShortcut = nullptr;
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::WheelAction && key->keycontext() == context) {
const ui::Accelerator* accel = key->isPressed(msg, *this);
if ((accel) && (!bestAccel || bestAccel->modifiers() < accel->modifiers())) {
bestAccel = accel;
const ui::Shortcut* shortcut = key->isPressed(msg, *this);
if ((shortcut) && (!bestShortcut || bestShortcut->modifiers() < shortcut->modifiers())) {
bestShortcut = shortcut;
wheelAction = key->wheelAction();
}
}
@ -1188,8 +1189,8 @@ Keys KeyboardShortcuts::getDragActionsFromKeyMessage(const KeyContext context,
Keys keys;
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::DragAction) {
const ui::Accelerator* accel = key->isPressed(msg, *this);
if (accel) {
const ui::Shortcut* shortcut = key->isPressed(msg, *this);
if (shortcut) {
keys.push_back(key);
}
}
@ -1200,7 +1201,7 @@ Keys KeyboardShortcuts::getDragActionsFromKeyMessage(const KeyContext context,
bool KeyboardShortcuts::hasMouseWheelCustomization() const
{
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::WheelAction && key->hasUserDefinedAccels())
if (key->type() == KeyType::WheelAction && key->hasUserDefinedShortcuts())
return true;
}
return false;
@ -1245,38 +1246,38 @@ void KeyboardShortcuts::setDefaultMouseWheelKeys(const bool zoomWithWheel)
KeyPtr key;
key = std::make_shared<Key>(WheelAction::Zoom);
key->add(Accelerator(zoomWithWheel ? kKeyNoneModifier : kKeyCtrlModifier, kKeyNil, 0),
key->add(Shortcut(zoomWithWheel ? kKeyNoneModifier : kKeyCtrlModifier, kKeyNil, 0),
KeySource::Original,
*this);
m_keys.push_back(key);
if (!zoomWithWheel) {
key = std::make_shared<Key>(WheelAction::VScroll);
key->add(Accelerator(kKeyNoneModifier, kKeyNil, 0), KeySource::Original, *this);
key->add(Shortcut(kKeyNoneModifier, kKeyNil, 0), KeySource::Original, *this);
m_keys.push_back(key);
}
key = std::make_shared<Key>(WheelAction::HScroll);
key->add(Accelerator(kKeyShiftModifier, kKeyNil, 0), KeySource::Original, *this);
key->add(Shortcut(kKeyShiftModifier, kKeyNil, 0), KeySource::Original, *this);
m_keys.push_back(key);
key = std::make_shared<Key>(WheelAction::FgColor);
key->add(Accelerator(kKeyAltModifier, kKeyNil, 0), KeySource::Original, *this);
key->add(Shortcut(kKeyAltModifier, kKeyNil, 0), KeySource::Original, *this);
m_keys.push_back(key);
key = std::make_shared<Key>(WheelAction::BgColor);
key->add(Accelerator((KeyModifiers)(kKeyAltModifier | kKeyShiftModifier), kKeyNil, 0),
key->add(Shortcut((KeyModifiers)(kKeyAltModifier | kKeyShiftModifier), kKeyNil, 0),
KeySource::Original,
*this);
m_keys.push_back(key);
if (zoomWithWheel) {
key = std::make_shared<Key>(WheelAction::BrushSize);
key->add(Accelerator(kKeyCtrlModifier, kKeyNil, 0), KeySource::Original, *this);
key->add(Shortcut(kKeyCtrlModifier, kKeyNil, 0), KeySource::Original, *this);
m_keys.push_back(key);
key = std::make_shared<Key>(WheelAction::Frame);
key->add(Accelerator((KeyModifiers)(kKeyCtrlModifier | kKeyShiftModifier), kKeyNil, 0),
key->add(Shortcut((KeyModifiers)(kKeyCtrlModifier | kKeyShiftModifier), kKeyNil, 0),
KeySource::Original,
*this);
m_keys.push_back(key);
@ -1321,9 +1322,9 @@ std::string key_tooltip(const char* str, const app::Key* key)
std::string res;
if (str)
res += str;
if (key && !key->accels().empty()) {
if (key && !key->shortcuts().empty()) {
res += " (";
res += key->accels().front().toString();
res += key->shortcuts().front().toString();
res += ")";
}
return res;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -53,10 +53,10 @@ public:
KeyPtr wheelAction(const WheelAction action) const;
KeyPtr dragAction(const WheelAction action) const;
void disableAccel(const ui::Accelerator& accel,
const KeySource source,
const KeyContext keyContext,
const Key* newKey);
void disableShortcut(const ui::Shortcut& shortcut,
const KeySource source,
const KeyContext keyContext,
const Key* newKey);
KeyContext getCurrentKeyContext() const;
bool getCommandFromKeyMessage(const ui::Message* msg, Command** command, Params* params);
@ -77,10 +77,10 @@ public:
private:
void exportKeys(tinyxml2::XMLElement* parent, KeyType type);
void exportAccel(tinyxml2::XMLElement* parent,
const Key* key,
const ui::Accelerator& accel,
bool removed);
void exportShortcut(tinyxml2::XMLElement* parent,
const Key* key,
const ui::Shortcut& shortcut,
bool removed);
mutable Keys m_keys;
};

View File

@ -1,202 +0,0 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/select_accelerator.h"
#include "app/ui/key.h"
#include "app/ui/keyboard_shortcuts.h"
#include "obs/signal.h"
#include "ui/entry.h"
#include "ui/message.h"
#include <cctype>
namespace app {
using namespace ui;
class SelectAccelerator::KeyField : public ui::Entry {
public:
KeyField(const Accelerator& accel) : ui::Entry(256, "")
{
setTranslateDeadKeys(false);
setExpansive(true);
setFocusMagnet(true);
setAccel(accel);
}
void setAccel(const Accelerator& accel)
{
m_accel = accel;
updateText();
}
obs::signal<void(const ui::Accelerator*)> AccelChange;
protected:
bool onProcessMessage(Message* msg) override
{
switch (msg->type()) {
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
if (!keymsg->scancode() && keymsg->unicodeChar() < 32)
break;
KeyModifiers modifiers = keymsg->modifiers();
if (keymsg->scancode() == kKeySpace)
modifiers = (KeyModifiers)(modifiers & ~kKeySpaceModifier);
m_accel = Accelerator(
modifiers,
keymsg->scancode(),
keymsg->unicodeChar() > 32 ? std::tolower(keymsg->unicodeChar()) : 0);
// Convert the accelerator to a string, and parse it
// again. Just to obtain the exact accelerator we'll read
// when we import the gui.xml file or an .aseprite-keys file.
m_accel = Accelerator(m_accel.toString());
updateText();
AccelChange(&m_accel);
return true;
}
break;
}
return Entry::onProcessMessage(msg);
}
void updateText()
{
setText(
Accelerator(kKeyNoneModifier, m_accel.scancode(), m_accel.unicodeChar()).toString().c_str());
}
Accelerator m_accel;
};
SelectAccelerator::SelectAccelerator(const ui::Accelerator& accel,
const KeyContext keyContext,
const KeyboardShortcuts& currentKeys)
: m_keyField(new KeyField(accel))
, m_keyContext(keyContext)
, m_currentKeys(currentKeys)
, m_accel(accel)
, m_ok(false)
, m_modified(false)
{
updateModifiers();
updateAssignedTo();
keyPlaceholder()->addChild(m_keyField);
alt()->Click.connect([this] { onModifierChange(kKeyAltModifier, alt()); });
cmd()->Click.connect([this] { onModifierChange(kKeyCmdModifier, cmd()); });
ctrl()->Click.connect([this] { onModifierChange(kKeyCtrlModifier, ctrl()); });
shift()->Click.connect([this] { onModifierChange(kKeyShiftModifier, shift()); });
space()->Click.connect([this] { onModifierChange(kKeySpaceModifier, space()); });
win()->Click.connect([this] { onModifierChange(kKeyWinModifier, win()); });
m_keyField->AccelChange.connect(&SelectAccelerator::onAccelChange, this);
clearButton()->Click.connect([this] { onClear(); });
okButton()->Click.connect([this] { onOK(); });
cancelButton()->Click.connect([this] { onCancel(); });
addChild(&m_tooltipManager);
}
void SelectAccelerator::onModifierChange(KeyModifiers modifier, CheckBox* checkbox)
{
bool state = (checkbox->isSelected());
KeyModifiers modifiers = m_accel.modifiers();
KeyScancode scancode = m_accel.scancode();
int unicodeChar = m_accel.unicodeChar();
modifiers = (KeyModifiers)((modifiers & ~modifier) | (state ? modifier : 0));
if (modifiers == kKeySpaceModifier && scancode == kKeySpace)
modifiers = kKeyNoneModifier;
m_accel = Accelerator(modifiers, scancode, unicodeChar);
m_keyField->setAccel(m_accel);
m_keyField->requestFocus();
updateAssignedTo();
}
void SelectAccelerator::onAccelChange(const ui::Accelerator* accel)
{
m_accel = *accel;
updateModifiers();
updateAssignedTo();
}
void SelectAccelerator::onClear()
{
m_accel = Accelerator(kKeyNoneModifier, kKeyNil, 0);
m_keyField->setAccel(m_accel);
updateModifiers();
updateAssignedTo();
m_keyField->requestFocus();
}
void SelectAccelerator::onOK()
{
m_ok = true;
m_modified = (m_origAccel != m_accel);
closeWindow(NULL);
}
void SelectAccelerator::onCancel()
{
closeWindow(NULL);
}
void SelectAccelerator::updateModifiers()
{
alt()->setSelected(m_accel.modifiers() & kKeyAltModifier ? true : false);
ctrl()->setSelected(m_accel.modifiers() & kKeyCtrlModifier ? true : false);
shift()->setSelected(m_accel.modifiers() & kKeyShiftModifier ? true : false);
space()->setSelected(m_accel.modifiers() & kKeySpaceModifier ? true : false);
#if __APPLE__
win()->setVisible(false);
cmd()->setSelected(m_accel.modifiers() & kKeyCmdModifier ? true : false);
#else
#if __linux__
win()->setText(kWinKeyName);
m_tooltipManager.addTooltipFor(
win(),
"Also known as Windows key, logo key,\ncommand key, or system key.",
TOP);
#endif
win()->setSelected(m_accel.modifiers() & kKeyWinModifier ? true : false);
cmd()->setVisible(false);
#endif
}
void SelectAccelerator::updateAssignedTo()
{
std::string res = "None";
for (const KeyPtr& key : m_currentKeys) {
if (key->keycontext() == m_keyContext && key->hasAccel(m_accel)) {
res = key->triggerString();
break;
}
}
assignedTo()->setText(res);
}
} // namespace app

View File

@ -0,0 +1,221 @@
// Aseprite
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/select_shortcut.h"
#include "app/ui/key.h"
#include "app/ui/keyboard_shortcuts.h"
#include "obs/signal.h"
#include "ui/entry.h"
#include "ui/message.h"
#include <cctype>
namespace app {
using namespace ui;
class SelectShortcut::KeyField : public ui::Entry {
public:
KeyField(const Shortcut& shortcut) : ui::Entry(256, "")
{
setTranslateDeadKeys(false);
setExpansive(true);
setFocusMagnet(true);
setShortcut(shortcut);
}
void setShortcut(const Shortcut& shortcut)
{
m_shortcut = shortcut;
updateText();
}
obs::signal<void(const ui::Shortcut*)> ShortcutChange;
protected:
bool onProcessMessage(Message* msg) override
{
switch (msg->type()) {
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
if (!keymsg->scancode() && keymsg->unicodeChar() < 32)
break;
KeyModifiers modifiers = keymsg->modifiers();
if (keymsg->scancode() == kKeySpace)
modifiers = (KeyModifiers)(modifiers & ~kKeySpaceModifier);
setAndParseShortcut(
Shortcut(modifiers,
keymsg->scancode(),
keymsg->unicodeChar() > 32 ? std::tolower(keymsg->unicodeChar()) : 0));
return true;
}
break;
case kMouseDownMessage:
if (!isReadOnly()) {
auto* mouseMsg = static_cast<MouseMessage*>(msg);
const KeyModifiers modifiers = mouseMsg->modifiers();
setAndParseShortcut(Shortcut(modifiers, mouseMsg->button()));
return true;
}
break;
}
return Entry::onProcessMessage(msg);
}
void setAndParseShortcut(const Shortcut& shortcut)
{
// Convert the shortcut to a string, and parse it
// again. Just to obtain the exact shortcut we'll read
// when we import the gui.xml file or an .aseprite-keys file.
m_shortcut = Shortcut(shortcut.toString());
updateText();
ShortcutChange(&m_shortcut);
}
void updateText()
{
Shortcut tmp = m_shortcut;
tmp.removeModifiers();
setText(tmp.toString());
}
Shortcut m_shortcut;
};
SelectShortcut::SelectShortcut(const ui::Shortcut& shortcut,
const KeyContext keyContext,
const KeyboardShortcuts& currentKeys)
: m_keyField(new KeyField(shortcut))
, m_keyContext(keyContext)
, m_currentKeys(currentKeys)
, m_shortcut(shortcut)
, m_ok(false)
, m_modified(false)
{
updateModifiers();
updateAssignedTo();
keyPlaceholder()->addChild(m_keyField);
alt()->Click.connect([this] { onModifierChange(kKeyAltModifier, alt()); });
cmd()->Click.connect([this] { onModifierChange(kKeyCmdModifier, cmd()); });
ctrl()->Click.connect([this] { onModifierChange(kKeyCtrlModifier, ctrl()); });
shift()->Click.connect([this] { onModifierChange(kKeyShiftModifier, shift()); });
space()->Click.connect([this] { onModifierChange(kKeySpaceModifier, space()); });
win()->Click.connect([this] { onModifierChange(kKeyWinModifier, win()); });
m_keyField->ShortcutChange.connect(&SelectShortcut::onShortcutChange, this);
clearButton()->Click.connect([this] { onClear(); });
okButton()->Click.connect([this] { onOK(); });
cancelButton()->Click.connect([this] { onCancel(); });
addChild(&m_tooltipManager);
}
void SelectShortcut::onModifierChange(KeyModifiers modifier, CheckBox* checkbox)
{
bool state = (checkbox->isSelected());
KeyModifiers modifiers = m_shortcut.modifiers();
KeyScancode scancode = m_shortcut.scancode();
int unicodeChar = m_shortcut.unicodeChar();
MouseButton mouseButton = m_shortcut.mouseButton();
modifiers = (KeyModifiers)((modifiers & ~modifier) | (state ? modifier : 0));
if (modifiers == kKeySpaceModifier && scancode == kKeySpace)
modifiers = kKeyNoneModifier;
if (mouseButton != kButtonNone)
m_shortcut = Shortcut(modifiers, mouseButton);
else
m_shortcut = Shortcut(modifiers, scancode, unicodeChar);
m_keyField->setShortcut(m_shortcut);
m_keyField->requestFocus();
updateAssignedTo();
}
void SelectShortcut::onShortcutChange(const ui::Shortcut* shortcut)
{
m_shortcut = *shortcut;
updateModifiers();
updateAssignedTo();
}
void SelectShortcut::onClear()
{
m_shortcut = Shortcut(kKeyNoneModifier, kKeyNil, 0);
m_keyField->setShortcut(m_shortcut);
updateModifiers();
updateAssignedTo();
m_keyField->requestFocus();
}
void SelectShortcut::onOK()
{
m_ok = true;
m_modified = (m_origShortcut != m_shortcut);
closeWindow(NULL);
}
void SelectShortcut::onCancel()
{
closeWindow(NULL);
}
void SelectShortcut::updateModifiers()
{
alt()->setSelected(m_shortcut.modifiers() & kKeyAltModifier ? true : false);
ctrl()->setSelected(m_shortcut.modifiers() & kKeyCtrlModifier ? true : false);
shift()->setSelected(m_shortcut.modifiers() & kKeyShiftModifier ? true : false);
space()->setSelected(m_shortcut.modifiers() & kKeySpaceModifier ? true : false);
#if __APPLE__
win()->setVisible(false);
cmd()->setSelected(m_shortcut.modifiers() & kKeyCmdModifier ? true : false);
#else
#if __linux__
win()->setText(kWinKeyName);
m_tooltipManager.addTooltipFor(
win(),
"Also known as Windows key, logo key,\ncommand key, or system key.",
TOP);
#endif
win()->setSelected(m_shortcut.modifiers() & kKeyWinModifier ? true : false);
cmd()->setVisible(false);
#endif
}
void SelectShortcut::updateAssignedTo()
{
std::string res = "None";
for (const KeyPtr& key : m_currentKeys) {
if (key->keycontext() == m_keyContext && key->hasShortcut(m_shortcut)) {
res = key->triggerString();
break;
}
}
assignedTo()->setText(res);
}
} // namespace app

View File

@ -1,35 +1,36 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_SELECT_ACCELERATOR_H_INCLUDED
#define APP_UI_SELECT_ACCELERATOR_H_INCLUDED
#ifndef APP_UI_SELECT_SHORTCUT_H_INCLUDED
#define APP_UI_SELECT_SHORTCUT_H_INCLUDED
#pragma once
#include "app/ui/key_context.h"
#include "ui/accelerator.h"
#include "ui/shortcut.h"
#include "ui/tooltips.h"
#include "select_accelerator.xml.h"
#include "select_shortcut.xml.h"
namespace app {
class KeyboardShortcuts;
class SelectAccelerator : public app::gen::SelectAccelerator {
class SelectShortcut : public app::gen::SelectShortcut {
public:
SelectAccelerator(const ui::Accelerator& accelerator,
const KeyContext keyContext,
const KeyboardShortcuts& currentKeys);
SelectShortcut(const ui::Shortcut& shortcut,
const KeyContext keyContext,
const KeyboardShortcuts& currentKeys);
bool isOK() const { return m_ok; }
bool isModified() const { return m_modified; }
const ui::Accelerator& accel() const { return m_accel; }
const ui::Shortcut& shortcut() const { return m_shortcut; }
private:
void onModifierChange(ui::KeyModifiers modifier, ui::CheckBox* checkbox);
void onAccelChange(const ui::Accelerator* accel);
void onShortcutChange(const ui::Shortcut* shortcut);
void onClear();
void onOK();
void onCancel();
@ -42,8 +43,8 @@ private:
KeyField* m_keyField;
KeyContext m_keyContext;
const KeyboardShortcuts& m_currentKeys;
ui::Accelerator m_origAccel;
ui::Accelerator m_accel;
ui::Shortcut m_origShortcut;
ui::Shortcut m_shortcut;
bool m_ok;
bool m_modified;
};

View File

@ -1470,13 +1470,13 @@ void SkinTheme::paintMenuItem(ui::PaintEvent& ev)
}
// Draw the keyboard shortcut
else if (AppMenuItem* appMenuItem = dynamic_cast<AppMenuItem*>(widget)) {
if (appMenuItem->key() && !appMenuItem->key()->accels().empty()) {
if (appMenuItem->key() && !appMenuItem->key()->shortcuts().empty()) {
int old_align = appMenuItem->align();
pos = bounds;
pos.w -= widget->childSpacing() / 4;
std::string buf = appMenuItem->key()->accels().front().toString();
std::string buf = appMenuItem->key()->shortcuts().front().toString();
widget->setAlign(RIGHT | MIDDLE);
drawText(g, buf.c_str(), fg, ColorNone, widget, pos, widget->align(), 0);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -534,9 +534,9 @@ public:
// Tool shortcut
KeyPtr key = KeyboardShortcuts::instance()->tool(tool);
if (key && !key->accels().empty()) {
if (key && !key->shortcuts().empty()) {
add(theme->parts.iconKey(), true);
m_indicators->addTextIndicator(key->accels().front().toString().c_str());
m_indicators->addTextIndicator(key->shortcuts().front().toString().c_str());
}
return *this;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -110,11 +110,11 @@ std::string AniControls::getTooltipFor(int index) const
tooltip = cmd->friendlyName();
KeyPtr key = KeyboardShortcuts::instance()->command(cmd->id().c_str());
if (!key || key->accels().empty())
if (!key || key->shortcuts().empty())
key = KeyboardShortcuts::instance()->command(cmd->id().c_str(), Params(), KeyContext::Normal);
if (key && !key->accels().empty()) {
if (key && !key->shortcuts().empty()) {
tooltip += "\n\n" + Strings::ani_controls_shortcut() + " ";
tooltip += key->accels().front().toString();
tooltip += key->shortcuts().front().toString();
}
if (index == ACTION_PLAY) {

View File

@ -571,9 +571,9 @@ void ToolBar::openTipWindow(int group_index, Tool* tool)
// Tool shortcut
KeyPtr key = KeyboardShortcuts::instance()->tool(tool);
if (key && !key->accels().empty()) {
if (key && !key->shortcuts().empty()) {
tooltip += "\n\n";
tooltip += Strings::tools_shortcut(key->accels().front().toString());
tooltip += Strings::tools_shortcut(key->shortcuts().front().toString());
}
}
else if (group_index == PreviewVisibilityIndex) {

View File

@ -1,5 +1,5 @@
# Aseprite UI Library
# Copyright (C) 2019-2024 Igara Studio S.A.
# Copyright (C) 2019-2025 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
if(WIN32)
@ -7,7 +7,6 @@ if(WIN32)
endif()
add_library(ui-lib
accelerator.cpp
alert.cpp
app_state.cpp
box.cpp
@ -44,6 +43,7 @@ add_library(ui-lib
scroll_bar.cpp
scroll_helper.cpp
separator.cpp
shortcut.cpp
size_hint_event.cpp
slider.cpp
splitter.cpp

View File

@ -1,64 +0,0 @@
// Aseprite UI Library
// Copyright (C) 2001-2013, 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#define TEST_GUI
#include "tests/app_test.h"
using namespace ui;
TEST(Accelerator, Parser)
{
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyF1, '\0'), Accelerator("F1"));
EXPECT_EQ(Accelerator(kKeyAltModifier, kKeyQ, 'q'), Accelerator("Alt+Q"));
EXPECT_EQ(Accelerator(kKeyCtrlModifier, kKeyQ, 'q'), Accelerator("Ctrl+Q"));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyMinus, '-'), Accelerator("-"));
EXPECT_EQ(Accelerator(kKeyShiftModifier, kKeyMinus, '-'), Accelerator("Shift+-"));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyEquals, '='), Accelerator("="));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyNil, '+'), Accelerator("+"));
EXPECT_EQ(Accelerator(kKeyShiftModifier, kKeyNil, '+'), Accelerator("Shift++"));
EXPECT_EQ(Accelerator(kKeyCtrlModifier, kKeyNil, '+'), Accelerator("Ctrl++"));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyMinusPad, 0), Accelerator("Minus Pad"));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyMinusPad, 0), Accelerator("- Pad"));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyPlusPad, 0), Accelerator("Plus Pad"));
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyPlusPad, 0), Accelerator("+ Pad"));
EXPECT_EQ(Accelerator(kKeyCtrlModifier, kKeyPlusPad, 0), Accelerator("Ctrl++ Pad"));
}
TEST(Accelerator, ToString)
{
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyF1, '\0').toString(), Accelerator("F1").toString());
EXPECT_EQ(Accelerator(kKeyAltModifier, kKeyQ, 'q').toString(), Accelerator("Alt+Q").toString());
EXPECT_EQ(Accelerator(kKeyCtrlModifier, kKeyQ, 'q').toString(), Accelerator("Ctrl+Q").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyMinus, '-').toString(), Accelerator("-").toString());
EXPECT_EQ(Accelerator(kKeyShiftModifier, kKeyMinus, '-').toString(),
Accelerator("Shift+-").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyEquals, '=').toString(), Accelerator("=").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyNil, '+').toString(), Accelerator("+").toString());
EXPECT_EQ(Accelerator(kKeyShiftModifier, kKeyNil, '+').toString(),
Accelerator("Shift++").toString());
EXPECT_EQ(Accelerator(kKeyCtrlModifier, kKeyNil, '+').toString(),
Accelerator("Ctrl++").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyMinusPad, 0).toString(),
Accelerator("Minus Pad").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyMinusPad, 0).toString(),
Accelerator("- Pad").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyPlusPad, 0).toString(),
Accelerator("Plus Pad").toString());
EXPECT_EQ(Accelerator(kKeyNoneModifier, kKeyPlusPad, 0).toString(),
Accelerator("+ Pad").toString());
EXPECT_EQ(Accelerator(kKeyCtrlModifier, kKeyPlusPad, 0).toString(),
Accelerator("Ctrl++ Pad").toString());
EXPECT_EQ("- Pad", Accelerator(kKeyNoneModifier, kKeyMinusPad, 0).toString());
EXPECT_EQ("- Pad", Accelerator("Minus Pad").toString());
EXPECT_EQ("- Pad", Accelerator("- Pad").toString());
EXPECT_EQ("+ Pad", Accelerator(kKeyNoneModifier, kKeyPlusPad, 0).toString());
EXPECT_EQ("+ Pad", Accelerator("Plus Pad").toString());
EXPECT_EQ("+ Pad", Accelerator("+ Pad").toString());
}

View File

@ -646,16 +646,20 @@ void Manager::handleMouseDown(Display* display,
if (!handleWindowZOrder())
return;
enqueueMessage(newMouseMessage(kMouseDownMessage,
display,
(capture_widget ? capture_widget : mouse_widget),
mousePos,
pointerType,
mouseButton,
modifiers,
gfx::Point(0, 0),
false,
pressure));
std::unique_ptr<MouseMessage> mouseMsg(
newMouseMessage(kMouseDownMessage,
display,
(capture_widget ? capture_widget : mouse_widget),
mousePos,
pointerType,
mouseButton,
modifiers,
gfx::Point(0, 0),
false,
pressure));
if (onEnqueueMouseDown(mouseMsg.get()))
enqueueMessage(mouseMsg.release());
}
void Manager::handleMouseUp(Display* display,
@ -1834,6 +1838,11 @@ void Manager::onNewDisplayConfiguration(Display* display)
container->flushRedraw();
}
bool Manager::onEnqueueMouseDown(MouseMessage* mouseMsg)
{
return true;
}
void Manager::onSizeHint(SizeHintEvent& ev)
{
int w = 0, h = 0;
@ -2280,16 +2289,16 @@ Widget* Manager::findMagneticWidget(Widget* widget)
}
// static
Message* Manager::newMouseMessage(MessageType type,
Display* display,
Widget* widget,
const gfx::Point& mousePos,
PointerType pointerType,
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta,
bool preciseWheel,
float pressure)
MouseMessage* Manager::newMouseMessage(MessageType type,
Display* display,
Widget* widget,
const gfx::Point& mousePos,
PointerType pointerType,
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta,
bool preciseWheel,
float pressure)
{
#ifdef __APPLE__
// Convert Ctrl+left click -> right-click
@ -2300,14 +2309,14 @@ Message* Manager::newMouseMessage(MessageType type,
}
#endif
Message* msg = new MouseMessage(type,
pointerType,
button,
modifiers,
mousePos,
wheelDelta,
preciseWheel,
pressure);
auto* msg = new MouseMessage(type,
pointerType,
button,
modifiers,
mousePos,
wheelDelta,
preciseWheel,
pressure);
if (display)
msg->setDisplay(display);

View File

@ -146,6 +146,7 @@ protected:
void onInitTheme(InitThemeEvent& ev) override;
virtual LayoutIO* onGetLayoutIO();
virtual void onNewDisplayConfiguration(Display* display);
virtual bool onEnqueueMouseDown(MouseMessage* mouseMsg);
private:
void generateSetCursorMessage(Display* display,
@ -202,16 +203,16 @@ private:
static Widget* findLowestCommonAncestor(Widget* a, Widget* b);
static bool someParentIsFocusStop(Widget* widget);
static Widget* findMagneticWidget(Widget* widget);
static Message* newMouseMessage(MessageType type,
Display* display,
Widget* widget,
const gfx::Point& mousePos,
PointerType pointerType,
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta = gfx::Point(0, 0),
bool preciseWheel = false,
float pressure = 0.0f);
static MouseMessage* newMouseMessage(MessageType type,
Display* display,
Widget* widget,
const gfx::Point& mousePos,
PointerType pointerType,
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta = gfx::Point(0, 0),
bool preciseWheel = false,
float pressure = 0.0f);
void broadcastKeyMsg(Message* msg);
static Manager* m_defaultManager;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -9,7 +9,7 @@
#include "config.h"
#endif
#include "ui/accelerator.h"
#include "ui/shortcut.h"
#include "base/debug.h"
#include "base/replace_string.h"
@ -144,21 +144,24 @@ int scancode_to_string_size = sizeof(scancode_to_string) / sizeof(scancode_to_st
} // anonymous namespace
Accelerator::Accelerator() : m_modifiers(kKeyNoneModifier), m_scancode(kKeyNil), m_unicodeChar(0)
Shortcut::Shortcut()
{
}
Accelerator::Accelerator(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar)
Shortcut::Shortcut(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar)
: m_modifiers(modifiers)
, m_scancode(scancode)
, m_unicodeChar(unicodeChar)
{
}
Accelerator::Accelerator(const std::string& str)
: m_modifiers(kKeyNoneModifier)
, m_scancode(kKeyNil)
, m_unicodeChar(0)
Shortcut::Shortcut(KeyModifiers modifiers, MouseButton mouseButton)
: m_modifiers(modifiers)
, m_mouseButton(mouseButton)
{
}
Shortcut::Shortcut(const std::string& str)
{
// Special case: plus sign
if (str == "+") {
@ -272,21 +275,31 @@ Accelerator::Accelerator(const std::string& str)
m_scancode = kKeyDelPad;
else if (tok == "enter pad")
m_scancode = kKeyEnterPad;
else if (tok == "left mouse button")
m_mouseButton = kButtonLeft;
else if (tok == "right mouse button")
m_mouseButton = kButtonRight;
else if (tok == "middle mouse button")
m_mouseButton = kButtonMiddle;
else if (tok == "x1 mouse button")
m_mouseButton = kButtonX1;
else if (tok == "x2 mouse button")
m_mouseButton = kButtonX2;
}
}
bool Accelerator::operator==(const Accelerator& other) const
bool Shortcut::operator==(const Shortcut& other) const
{
// TODO improve this, avoid conversion to std::string
return toString() == other.toString();
}
bool Accelerator::isEmpty() const
bool Shortcut::isEmpty() const
{
return (m_modifiers == kKeyNoneModifier && m_scancode == kKeyNil && m_unicodeChar == 0);
}
std::string Accelerator::toString() const
std::string Shortcut::toString() const
{
std::string buf;
@ -313,21 +326,34 @@ std::string Accelerator::toString() const
wideUnicodeChar.push_back((wchar_t)std::toupper(m_unicodeChar));
buf += base::to_utf8(wideUnicodeChar);
}
else if (m_scancode > 0 && m_scancode < scancode_to_string_size && scancode_to_string[m_scancode])
else if (m_scancode > 0 && m_scancode < scancode_to_string_size &&
scancode_to_string[m_scancode]) {
buf += scancode_to_string[m_scancode];
else if (!buf.empty() && buf[buf.size() - 1] == '+')
}
// Mouse button
else if (m_mouseButton != kButtonNone) {
switch (m_mouseButton) {
case kButtonLeft: buf += "Left Mouse Button"; break;
case kButtonRight: buf += "Right Mouse Button"; break;
case kButtonMiddle: buf += "Middle Mouse Button"; break;
case kButtonX1: buf += "X1 Mouse Button"; break;
case kButtonX2: buf += "X2 Mouse Button"; break;
}
}
else if (!buf.empty() && buf[buf.size() - 1] == '+') {
buf.erase(buf.size() - 1);
}
return buf;
}
bool Accelerator::isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const
bool Shortcut::isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const
{
return ((scancode && *this == Accelerator(modifiers, scancode, 0)) ||
(unicodeChar && *this == Accelerator(modifiers, kKeyNil, unicodeChar)));
return ((scancode && *this == Shortcut(modifiers, scancode, 0)) ||
(unicodeChar && *this == Shortcut(modifiers, kKeyNil, unicodeChar)));
}
bool Accelerator::isPressed() const
bool Shortcut::isPressed() const
{
os::SystemRef sys = os::System::instance();
if (!sys)
@ -336,7 +362,7 @@ bool Accelerator::isPressed() const
KeyModifiers pressedModifiers = sys->keyModifiers();
// Check if this shortcut is only
if (m_scancode == kKeyNil && m_unicodeChar == 0)
if (m_scancode == kKeyNil && m_unicodeChar == 0 && m_mouseButton == kButtonNone)
return (m_modifiers == pressedModifiers);
// Compare with all pressed scancodes
@ -349,7 +375,7 @@ bool Accelerator::isPressed() const
return false;
}
bool Accelerator::isLooselyPressed() const
bool Shortcut::isLooselyPressed() const
{
os::SystemRef sys = os::System::instance();
if (!sys)
@ -361,7 +387,7 @@ bool Accelerator::isLooselyPressed() const
return false;
// Check if this shortcut is only
if (m_scancode == kKeyNil && m_unicodeChar == 0)
if (m_scancode == kKeyNil && m_unicodeChar == 0 && m_mouseButton == kButtonNone)
return true;
// Compare with all pressed scancodes
@ -378,22 +404,22 @@ bool Accelerator::isLooselyPressed() const
}
//////////////////////////////////////////////////////////////////////
// Accelerators
// Shortcuts
bool Accelerators::has(const Accelerator& accel) const
bool Shortcuts::has(const Shortcut& shortcut) const
{
return (std::find(begin(), end(), accel) != end());
return (std::find(begin(), end(), shortcut) != end());
}
void Accelerators::add(const Accelerator& accel)
void Shortcuts::add(const Shortcut& shortcut)
{
if (!has(accel))
m_list.push_back(accel);
if (!has(shortcut))
m_list.push_back(shortcut);
}
void Accelerators::remove(const Accelerator& accel)
void Shortcuts::remove(const Shortcut& shortcut)
{
auto it = std::find(begin(), end(), accel);
auto it = std::find(begin(), end(), shortcut);
if (it != end())
m_list.erase(it);
}

View File

@ -1,29 +1,31 @@
// Aseprite UI Library
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef UI_ACCELERATOR_H_INCLUDED
#define UI_ACCELERATOR_H_INCLUDED
#ifndef UI_SHORTCUT_H_INCLUDED
#define UI_SHORTCUT_H_INCLUDED
#pragma once
#include "ui/keys.h"
#include "ui/mouse_button.h"
#include <string>
#include <vector>
#include "ui/keys.h"
namespace ui {
extern const char* kWinKeyName;
// TODO rename this class to Shortcut
class Accelerator {
class Shortcut {
public:
Accelerator();
Accelerator(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar);
// Convert string like "Ctrl+Q" or "Alt+X" into an accelerator.
explicit Accelerator(const std::string& str);
Shortcut();
Shortcut(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar);
Shortcut(KeyModifiers modifiers, MouseButton mouseButton);
// Convert string like "Ctrl+Q" or "Alt+X" into an shortcut.
explicit Shortcut(const std::string& str);
bool isEmpty() const;
std::string toString() const;
@ -38,23 +40,26 @@ public:
// modifiers are allowed too).
bool isLooselyPressed() const;
bool operator==(const Accelerator& other) const;
bool operator!=(const Accelerator& other) const { return !operator==(other); }
bool operator==(const Shortcut& other) const;
bool operator!=(const Shortcut& other) const { return !operator==(other); }
KeyModifiers modifiers() const { return m_modifiers; }
KeyScancode scancode() const { return m_scancode; }
int unicodeChar() const { return m_unicodeChar; }
MouseButton mouseButton() const { return m_mouseButton; }
void removeModifiers() { m_modifiers = kKeyNoneModifier; }
private:
KeyModifiers m_modifiers;
KeyScancode m_scancode;
int m_unicodeChar;
KeyModifiers m_modifiers = kKeyNoneModifier;
KeyScancode m_scancode = kKeyNil;
int m_unicodeChar = 0;
MouseButton m_mouseButton = kButtonNone;
};
// TODO rename this class to Shortcuts
class Accelerators {
class Shortcuts {
public:
typedef std::vector<Accelerator> List;
typedef std::vector<Shortcut> List;
typedef List::iterator iterator;
typedef List::const_iterator const_iterator;
@ -66,16 +71,16 @@ public:
bool empty() const { return m_list.empty(); }
std::size_t size() const { return m_list.size(); }
const ui::Accelerator& front() const { return m_list.front(); }
const ui::Shortcut& front() const { return m_list.front(); }
const ui::Accelerator& operator[](int index) const { return m_list[index]; }
const ui::Shortcut& operator[](int index) const { return m_list[index]; }
ui::Accelerator& operator[](int index) { return m_list[index]; }
ui::Shortcut& operator[](int index) { return m_list[index]; }
void clear() { m_list.clear(); }
bool has(const Accelerator& accel) const;
void add(const Accelerator& accel);
void remove(const Accelerator& accel);
bool has(const Shortcut& shortcut) const;
void add(const Shortcut& shortcut);
void remove(const Shortcut& shortcut);
private:
List m_list;

View File

@ -0,0 +1,59 @@
// Aseprite UI Library
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#define TEST_GUI
#include "tests/app_test.h"
using namespace ui;
TEST(Shortcut, Parser)
{
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyF1, '\0'), Shortcut("F1"));
EXPECT_EQ(Shortcut(kKeyAltModifier, kKeyQ, 'q'), Shortcut("Alt+Q"));
EXPECT_EQ(Shortcut(kKeyCtrlModifier, kKeyQ, 'q'), Shortcut("Ctrl+Q"));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyMinus, '-'), Shortcut("-"));
EXPECT_EQ(Shortcut(kKeyShiftModifier, kKeyMinus, '-'), Shortcut("Shift+-"));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyEquals, '='), Shortcut("="));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyNil, '+'), Shortcut("+"));
EXPECT_EQ(Shortcut(kKeyShiftModifier, kKeyNil, '+'), Shortcut("Shift++"));
EXPECT_EQ(Shortcut(kKeyCtrlModifier, kKeyNil, '+'), Shortcut("Ctrl++"));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyMinusPad, 0), Shortcut("Minus Pad"));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyMinusPad, 0), Shortcut("- Pad"));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyPlusPad, 0), Shortcut("Plus Pad"));
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyPlusPad, 0), Shortcut("+ Pad"));
EXPECT_EQ(Shortcut(kKeyCtrlModifier, kKeyPlusPad, 0), Shortcut("Ctrl++ Pad"));
}
TEST(Shortcut, ToString)
{
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyF1, '\0').toString(), Shortcut("F1").toString());
EXPECT_EQ(Shortcut(kKeyAltModifier, kKeyQ, 'q').toString(), Shortcut("Alt+Q").toString());
EXPECT_EQ(Shortcut(kKeyCtrlModifier, kKeyQ, 'q').toString(), Shortcut("Ctrl+Q").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyMinus, '-').toString(), Shortcut("-").toString());
EXPECT_EQ(Shortcut(kKeyShiftModifier, kKeyMinus, '-').toString(), Shortcut("Shift+-").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyEquals, '=').toString(), Shortcut("=").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyNil, '+').toString(), Shortcut("+").toString());
EXPECT_EQ(Shortcut(kKeyShiftModifier, kKeyNil, '+').toString(), Shortcut("Shift++").toString());
EXPECT_EQ(Shortcut(kKeyCtrlModifier, kKeyNil, '+').toString(), Shortcut("Ctrl++").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyMinusPad, 0).toString(),
Shortcut("Minus Pad").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyMinusPad, 0).toString(), Shortcut("- Pad").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyPlusPad, 0).toString(), Shortcut("Plus Pad").toString());
EXPECT_EQ(Shortcut(kKeyNoneModifier, kKeyPlusPad, 0).toString(), Shortcut("+ Pad").toString());
EXPECT_EQ(Shortcut(kKeyCtrlModifier, kKeyPlusPad, 0).toString(),
Shortcut("Ctrl++ Pad").toString());
EXPECT_EQ("- Pad", Shortcut(kKeyNoneModifier, kKeyMinusPad, 0).toString());
EXPECT_EQ("- Pad", Shortcut("Minus Pad").toString());
EXPECT_EQ("- Pad", Shortcut("- Pad").toString());
EXPECT_EQ("+ Pad", Shortcut(kKeyNoneModifier, kKeyPlusPad, 0).toString());
EXPECT_EQ("+ Pad", Shortcut("Plus Pad").toString());
EXPECT_EQ("+ Pad", Shortcut("+ Pad").toString());
}

View File

@ -9,7 +9,6 @@
#define UI_UI_H_INCLUDED
#pragma once
#include "ui/accelerator.h"
#include "ui/alert.h"
#include "ui/app_state.h"
#include "ui/base.h"
@ -57,6 +56,7 @@
#include "ui/scroll_bar.h"
#include "ui/scroll_helper.h"
#include "ui/separator.h"
#include "ui/shortcut.h"
#include "ui/size_hint_event.h"
#include "ui/slider.h"
#include "ui/splitter.h"