2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2022-02-19 06:01:46 +08:00
|
|
|
// Copyright (C) 2018-2022 Igara Studio S.A.
|
2018-02-21 21:39:30 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2014-10-29 22:58:03 +08:00
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
2016-12-02 04:36:10 +08:00
|
|
|
#include "app/app.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "app/app_menus.h"
|
|
|
|
#include "app/commands/command.h"
|
|
|
|
#include "app/context.h"
|
|
|
|
#include "app/file_selector.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "app/i18n/strings.h"
|
2017-03-16 01:24:42 +08:00
|
|
|
#include "app/match_words.h"
|
2018-11-27 05:46:13 +08:00
|
|
|
#include "app/modules/gui.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "app/resource_finder.h"
|
|
|
|
#include "app/tools/tool.h"
|
2016-12-02 04:36:10 +08:00
|
|
|
#include "app/tools/tool_box.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "app/ui/app_menuitem.h"
|
|
|
|
#include "app/ui/keyboard_shortcuts.h"
|
2015-12-01 02:08:18 +08:00
|
|
|
#include "app/ui/search_entry.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "app/ui/select_accelerator.h"
|
2017-07-25 02:13:32 +08:00
|
|
|
#include "app/ui/separator_in_view.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "app/ui/skin/skin_theme.h"
|
|
|
|
#include "base/fs.h"
|
2022-03-09 07:40:11 +08:00
|
|
|
#include "base/pi.h"
|
2015-12-01 02:08:18 +08:00
|
|
|
#include "base/scoped_value.h"
|
|
|
|
#include "base/split_string.h"
|
|
|
|
#include "base/string.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "fmt/format.h"
|
2018-06-09 02:52:10 +08:00
|
|
|
#include "ui/alert.h"
|
2021-03-20 05:57:56 +08:00
|
|
|
#include "ui/fit_bounds.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "ui/graphics.h"
|
|
|
|
#include "ui/listitem.h"
|
2018-06-09 02:52:10 +08:00
|
|
|
#include "ui/message.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
#include "ui/paint_event.h"
|
|
|
|
#include "ui/resize_event.h"
|
2015-12-01 02:08:18 +08:00
|
|
|
#include "ui/separator.h"
|
2016-07-01 23:50:47 +08:00
|
|
|
#include "ui/size_hint_event.h"
|
2018-06-09 02:52:10 +08:00
|
|
|
#include "ui/splitter.h"
|
|
|
|
#include "ui/system.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2015-09-23 03:33:49 +08:00
|
|
|
#include "keyboard_shortcuts.xml.h"
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2020-04-08 23:03:32 +08:00
|
|
|
#include <algorithm>
|
2018-07-24 02:15:04 +08:00
|
|
|
#include <map>
|
2019-08-02 06:14:46 +08:00
|
|
|
#include <memory>
|
2017-12-01 01:51:13 +08:00
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
#define KEYBOARD_FILENAME_EXTENSION "aseprite-keys"
|
|
|
|
|
|
|
|
namespace app {
|
|
|
|
|
|
|
|
using namespace skin;
|
2016-12-02 04:36:10 +08:00
|
|
|
using namespace tools;
|
|
|
|
using namespace ui;
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
namespace {
|
|
|
|
|
2022-04-14 09:46:48 +08:00
|
|
|
using MenuKeys = std::map<AppMenuItem*, KeyPtr>;
|
2018-07-24 02:15:04 +08:00
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
class HeaderSplitter : public Splitter {
|
|
|
|
public:
|
|
|
|
HeaderSplitter() : Splitter(Splitter::ByPixel, HORIZONTAL) {
|
|
|
|
}
|
|
|
|
void onPositionChange() override {
|
|
|
|
Splitter::onPositionChange();
|
|
|
|
|
|
|
|
Widget* p = parent();
|
|
|
|
while (p && p->type() != kViewWidget)
|
|
|
|
p = p->parent();
|
|
|
|
if (p)
|
|
|
|
p->layout();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class HeaderItem : public ListItem {
|
|
|
|
public:
|
|
|
|
HeaderItem()
|
2022-01-03 16:31:46 +08:00
|
|
|
: m_actionLabel(Strings::keyboard_shortcuts_header_action())
|
|
|
|
, m_keyLabel(Strings::keyboard_shortcuts_header_key())
|
|
|
|
, m_contextLabel(Strings::keyboard_shortcuts_header_context()) {
|
2017-10-24 21:20:21 +08:00
|
|
|
setBorder(gfx::Border(0));
|
|
|
|
|
2022-02-19 06:01:46 +08:00
|
|
|
auto theme = SkinTheme::get(this);
|
2017-10-24 21:20:21 +08:00
|
|
|
m_actionLabel.setStyle(theme->styles.listHeaderLabel());
|
|
|
|
m_keyLabel.setStyle(theme->styles.listHeaderLabel());
|
|
|
|
m_contextLabel.setStyle(theme->styles.listHeaderLabel());
|
|
|
|
|
2021-02-18 23:30:14 +08:00
|
|
|
gfx::Size displaySize = display()->size();
|
|
|
|
m_splitter1.setPosition(displaySize.w*3/4 * 4/10);
|
|
|
|
m_splitter2.setPosition(displaySize.w*3/4 * 2/10);
|
2017-10-24 21:20:21 +08:00
|
|
|
|
|
|
|
addChild(&m_splitter1);
|
|
|
|
m_splitter1.addChild(&m_actionLabel);
|
|
|
|
m_splitter1.addChild(&m_splitter2);
|
|
|
|
m_splitter2.addChild(&m_keyLabel);
|
|
|
|
m_splitter2.addChild(&m_contextLabel);
|
|
|
|
}
|
|
|
|
|
|
|
|
int keyXPos() const {
|
|
|
|
return m_keyLabel.bounds().x - bounds().x;
|
|
|
|
}
|
|
|
|
|
|
|
|
int contextXPos() const {
|
|
|
|
return m_contextLabel.bounds().x - bounds().x;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
HeaderSplitter m_splitter1, m_splitter2;
|
|
|
|
Label m_actionLabel;
|
|
|
|
Label m_keyLabel;
|
|
|
|
Label m_contextLabel;
|
|
|
|
};
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2021-05-21 04:08:04 +08:00
|
|
|
class KeyItemBase : public ListItem {
|
|
|
|
public:
|
|
|
|
KeyItemBase(const std::string& text)
|
|
|
|
: ListItem(text) {
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void onSizeHint(SizeHintEvent& ev) override {
|
|
|
|
gfx::Size size = textSize();
|
|
|
|
size.w = size.w + border().width();
|
|
|
|
size.h = size.h + border().height() + 6*guiscale();
|
|
|
|
ev.setSizeHint(size);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
class KeyItem : public KeyItemBase {
|
2016-07-01 23:50:47 +08:00
|
|
|
|
|
|
|
// Used to avoid deleting the Add/Change/Del buttons on
|
|
|
|
// kMouseLeaveMessage when a foreground window is popup on a signal
|
|
|
|
// generated by those same buttons.
|
|
|
|
struct LockButtons {
|
|
|
|
KeyItem* keyItem;
|
|
|
|
LockButtons(KeyItem* keyItem) : keyItem(keyItem) {
|
|
|
|
keyItem->m_lockButtons = true;
|
|
|
|
};
|
|
|
|
~LockButtons() {
|
|
|
|
keyItem->m_lockButtons = false;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
public:
|
2018-07-24 02:15:04 +08:00
|
|
|
KeyItem(KeyboardShortcuts& keys,
|
|
|
|
MenuKeys& menuKeys,
|
|
|
|
const std::string& text,
|
2018-07-18 10:53:08 +08:00
|
|
|
const KeyPtr& key,
|
2017-10-24 21:20:21 +08:00
|
|
|
AppMenuItem* menuitem,
|
|
|
|
const int level,
|
|
|
|
HeaderItem* headerItem)
|
2021-05-21 04:08:04 +08:00
|
|
|
: KeyItemBase(text)
|
2018-07-24 02:15:04 +08:00
|
|
|
, m_keys(keys)
|
|
|
|
, m_menuKeys(menuKeys)
|
2014-10-29 22:58:03 +08:00
|
|
|
, m_key(key)
|
2016-11-17 20:56:44 +08:00
|
|
|
, m_keyOrig(key ? new Key(*key): nullptr)
|
2014-10-29 22:58:03 +08:00
|
|
|
, m_menuitem(menuitem)
|
|
|
|
, m_level(level)
|
2016-07-01 23:50:47 +08:00
|
|
|
, m_hotAccel(-1)
|
2017-10-24 21:20:21 +08:00
|
|
|
, m_lockButtons(false)
|
|
|
|
, m_headerItem(headerItem) {
|
2015-06-24 06:20:49 +08:00
|
|
|
gfx::Border border = this->border();
|
|
|
|
border.top(0);
|
|
|
|
border.bottom(0);
|
|
|
|
setBorder(border);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2018-07-18 10:53:08 +08:00
|
|
|
KeyPtr key() { return m_key; }
|
2016-09-23 22:03:40 +08:00
|
|
|
AppMenuItem* menuitem() const { return m_menuitem; }
|
2015-12-01 02:08:18 +08:00
|
|
|
|
2016-08-20 01:27:45 +08:00
|
|
|
std::string searchableText() const {
|
|
|
|
if (m_menuitem) {
|
|
|
|
Widget* w = m_menuitem;
|
|
|
|
|
|
|
|
// If the menu has a submenu, this item cannot be triggered with a key
|
|
|
|
// TODO make this possible: we should be able to open a menu with a key
|
|
|
|
if (w->type() == kMenuItemWidget &&
|
|
|
|
static_cast<MenuItem*>(w)->getSubmenu())
|
|
|
|
return std::string();
|
|
|
|
|
|
|
|
std::string result;
|
|
|
|
while (w && w->type() == kMenuItemWidget) {
|
|
|
|
if (!result.empty())
|
|
|
|
result.insert(0, " > ");
|
|
|
|
result.insert(0, w->text());
|
|
|
|
|
|
|
|
w = w->parent();
|
2021-05-21 04:08:04 +08:00
|
|
|
if (w && w->type() == kMenuWidget) {
|
|
|
|
auto owner = static_cast<Menu*>(w)->getOwnerMenuItem();
|
|
|
|
|
|
|
|
// Add the text of the menu (useful for the Palette Menu)
|
|
|
|
if (!owner && !w->text().empty()) {
|
|
|
|
result.insert(0, " > ");
|
|
|
|
result.insert(0, w->text());
|
|
|
|
}
|
|
|
|
|
|
|
|
w = owner;
|
|
|
|
}
|
|
|
|
else {
|
2016-08-20 01:27:45 +08:00
|
|
|
w = nullptr;
|
2021-05-21 04:08:04 +08:00
|
|
|
}
|
2016-08-20 01:27:45 +08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return text();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
private:
|
|
|
|
|
2014-10-30 09:06:25 +08:00
|
|
|
void onChangeAccel(int index) {
|
2016-07-01 23:50:47 +08:00
|
|
|
LockButtons lock(this);
|
2014-10-30 09:06:25 +08:00
|
|
|
Accelerator origAccel = m_key->accels()[index];
|
2018-07-21 01:32:55 +08:00
|
|
|
SelectAccelerator window(origAccel,
|
|
|
|
m_key->keycontext(),
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys);
|
2014-10-29 22:58:03 +08:00
|
|
|
window.openWindowInForeground();
|
|
|
|
|
|
|
|
if (window.isModified()) {
|
2022-04-14 09:46:48 +08:00
|
|
|
m_key->disableAccel(origAccel, KeySource::UserDefined);
|
2014-11-16 05:31:12 +08:00
|
|
|
if (!window.accel().isEmpty())
|
2018-07-24 02:15:04 +08:00
|
|
|
m_key->add(window.accel(), KeySource::UserDefined, m_keys);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
this->window()->layout();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2016-05-03 00:03:04 +08:00
|
|
|
void onDeleteAccel(int index) {
|
2016-07-01 23:50:47 +08:00
|
|
|
LockButtons lock(this);
|
2016-05-03 00:03:04 +08:00
|
|
|
// 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];
|
|
|
|
|
2017-10-18 05:00:45 +08:00
|
|
|
if (ui::Alert::show(
|
|
|
|
fmt::format(
|
|
|
|
Strings::alerts_delete_shortcut(),
|
|
|
|
accel.toString())) != 1)
|
2014-10-30 09:06:25 +08:00
|
|
|
return;
|
|
|
|
|
2022-04-14 09:46:48 +08:00
|
|
|
m_key->disableAccel(accel, KeySource::UserDefined);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
window()->layout();
|
2014-10-30 09:06:25 +08:00
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
void onAddAccel() {
|
2016-07-01 23:50:47 +08:00
|
|
|
LockButtons lock(this);
|
2014-10-29 22:58:03 +08:00
|
|
|
ui::Accelerator accel;
|
2018-07-21 01:32:55 +08:00
|
|
|
SelectAccelerator window(accel,
|
|
|
|
m_key ? m_key->keycontext(): KeyContext::Any,
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys);
|
2014-10-29 22:58:03 +08:00
|
|
|
window.openWindowInForeground();
|
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
if ((window.isModified()) ||
|
|
|
|
// We can assign a "None" accelerator to mouse wheel actions
|
|
|
|
(m_key && m_key->type() == KeyType::WheelAction && window.isOK())) {
|
2014-10-29 22:58:03 +08:00
|
|
|
if (!m_key) {
|
|
|
|
ASSERT(m_menuitem);
|
|
|
|
if (!m_menuitem)
|
|
|
|
return;
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
ASSERT(m_menuitem->getCommand());
|
|
|
|
|
|
|
|
m_key = m_keys.command(
|
2022-06-08 01:47:40 +08:00
|
|
|
m_menuitem->getCommandId().c_str(),
|
2014-10-29 22:58:03 +08:00
|
|
|
m_menuitem->getParams());
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
m_menuKeys[m_menuitem] = m_key;
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
m_key->add(window.accel(), KeySource::UserDefined, m_keys);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
this->window()->layout();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2015-12-04 08:50:05 +08:00
|
|
|
void onSizeHint(SizeHintEvent& ev) override {
|
2021-05-21 04:08:04 +08:00
|
|
|
KeyItemBase::onSizeHint(ev);
|
|
|
|
gfx::Size size = ev.sizeHint();
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
if (m_key && m_key->keycontext() != KeyContext::Any) {
|
|
|
|
int w =
|
|
|
|
m_headerItem->contextXPos() +
|
|
|
|
Graphics::measureUITextLength(
|
|
|
|
convertKeyContextToUserFriendlyString(m_key->keycontext()), font());
|
2020-04-08 23:03:32 +08:00
|
|
|
size.w = std::max(size.w, w);
|
2017-10-24 21:20:21 +08:00
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
if (m_key && !m_key->accels().empty()) {
|
|
|
|
size_t combos = m_key->accels().size();
|
|
|
|
if (combos > 1)
|
|
|
|
size.h *= combos;
|
|
|
|
}
|
|
|
|
|
2015-12-04 08:50:05 +08:00
|
|
|
ev.setSizeHint(size);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void onPaint(PaintEvent& ev) override {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
Graphics* g = ev.graphics();
|
2022-02-19 06:01:46 +08:00
|
|
|
auto theme = SkinTheme::get(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Rect bounds = clientBounds();
|
2014-10-29 22:58:03 +08:00
|
|
|
gfx::Color fg, bg;
|
|
|
|
|
|
|
|
if (isSelected()) {
|
2015-02-16 02:29:16 +08:00
|
|
|
fg = theme->colors.listitemSelectedText();
|
|
|
|
bg = theme->colors.listitemSelectedFace();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
else {
|
2015-02-16 02:29:16 +08:00
|
|
|
fg = theme->colors.listitemNormalText();
|
|
|
|
bg = theme->colors.listitemNormalFace();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
g->fillRect(bg, bounds);
|
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
int y = bounds.y + 2*guiscale();
|
|
|
|
const int th = textSize().h;
|
|
|
|
// Position of the second and third columns
|
|
|
|
const int keyXPos = bounds.x + m_headerItem->keyXPos();
|
|
|
|
const int contextXPos = bounds.x + m_headerItem->contextXPos();
|
|
|
|
|
2015-06-24 06:20:49 +08:00
|
|
|
bounds.shrink(border());
|
2017-10-24 21:20:21 +08:00
|
|
|
{
|
|
|
|
int x = bounds.x + m_level*16 * guiscale();
|
|
|
|
IntersectClip clip(g, gfx::Rect(x, y, keyXPos - x, th));
|
2018-07-20 10:05:14 +08:00
|
|
|
if (clip) {
|
2017-10-24 21:20:21 +08:00
|
|
|
g->drawUIText(text(), fg, bg, gfx::Point(x, y), 0);
|
2018-07-20 10:05:14 +08:00
|
|
|
}
|
2017-10-24 21:20:21 +08:00
|
|
|
}
|
2014-10-29 22:58:03 +08:00
|
|
|
|
|
|
|
if (m_key && !m_key->accels().empty()) {
|
2017-10-24 21:20:21 +08:00
|
|
|
if (m_key->keycontext() != KeyContext::Any) {
|
|
|
|
g->drawText(
|
|
|
|
convertKeyContextToUserFriendlyString(m_key->keycontext()), fg, bg,
|
|
|
|
gfx::Point(contextXPos, y));
|
|
|
|
}
|
|
|
|
|
2017-11-30 02:28:04 +08:00
|
|
|
const int dh = th + 4*guiscale();
|
|
|
|
IntersectClip clip(g, gfx::Rect(keyXPos, y,
|
|
|
|
contextXPos - keyXPos,
|
|
|
|
dh * m_key->accels().size()));
|
2017-10-24 21:20:21 +08:00
|
|
|
if (clip) {
|
|
|
|
int i = 0;
|
|
|
|
for (const Accelerator& accel : m_key->accels()) {
|
|
|
|
if (i != m_hotAccel || !m_changeButton) {
|
|
|
|
g->drawText(
|
2018-07-20 10:05:14 +08:00
|
|
|
getAccelText(accel), fg, bg,
|
2017-10-24 21:20:21 +08:00
|
|
|
gfx::Point(keyXPos, y));
|
2017-10-24 00:03:18 +08:00
|
|
|
}
|
2017-10-24 21:20:21 +08:00
|
|
|
y += dh;
|
|
|
|
++i;
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void onResize(ResizeEvent& ev) override {
|
2021-05-21 04:08:04 +08:00
|
|
|
KeyItemBase::onResize(ev);
|
2014-10-29 22:58:03 +08:00
|
|
|
destroyButtons();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool onProcessMessage(Message* msg) override {
|
|
|
|
switch (msg->type()) {
|
|
|
|
|
|
|
|
case kMouseLeaveMessage: {
|
|
|
|
destroyButtons();
|
|
|
|
invalidate();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case kMouseMoveMessage: {
|
2018-07-20 10:05:14 +08:00
|
|
|
if (!isEnabled())
|
|
|
|
break;
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
gfx::Rect bounds = this->bounds();
|
2014-10-29 22:58:03 +08:00
|
|
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
|
|
|
|
|
|
|
const Accelerators* accels = (m_key ? &m_key->accels() : NULL);
|
|
|
|
int y = bounds.y;
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
int dh = textSize().h + 4*guiscale();
|
2014-10-29 22:58:03 +08:00
|
|
|
int maxi = (accels && accels->size() > 1 ? accels->size(): 1);
|
|
|
|
|
2022-02-19 06:01:46 +08:00
|
|
|
auto theme = SkinTheme::get(this);
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
for (int i=0; i<maxi; ++i, y += dh) {
|
2017-02-07 04:58:55 +08:00
|
|
|
int w = Graphics::measureUITextLength(
|
2018-07-20 10:05:14 +08:00
|
|
|
(accels && i < (int)accels->size() ? getAccelText((*accels)[i]).c_str(): ""),
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
font());
|
2017-10-24 21:20:21 +08:00
|
|
|
gfx::Rect itemBounds(bounds.x + m_headerItem->keyXPos(), y, w, dh);
|
2015-05-06 23:13:35 +08:00
|
|
|
itemBounds = itemBounds.enlarge(
|
|
|
|
gfx::Border(
|
|
|
|
4*guiscale(), 0,
|
|
|
|
6*guiscale(), 1*guiscale()));
|
|
|
|
|
|
|
|
if (accels &&
|
|
|
|
i < (int)accels->size() &&
|
|
|
|
mouseMsg->position().y >= itemBounds.y &&
|
|
|
|
mouseMsg->position().y < itemBounds.y+itemBounds.h) {
|
|
|
|
if (m_hotAccel != i) {
|
|
|
|
m_hotAccel = i;
|
|
|
|
|
2016-09-14 02:02:00 +08:00
|
|
|
m_changeConn = obs::connection();
|
2015-05-06 23:13:35 +08:00
|
|
|
m_changeButton.reset(new Button(""));
|
2020-07-04 08:51:46 +08:00
|
|
|
m_changeConn = m_changeButton->Click.connect([this, i]{ onChangeAccel(i); });
|
2022-02-19 06:01:46 +08:00
|
|
|
m_changeButton->setStyle(theme->styles.miniButton());
|
2015-05-06 23:13:35 +08:00
|
|
|
addChild(m_changeButton.get());
|
|
|
|
|
2016-09-14 02:02:00 +08:00
|
|
|
m_deleteConn = obs::connection();
|
2015-05-06 23:13:35 +08:00
|
|
|
m_deleteButton.reset(new Button(""));
|
2020-07-04 08:51:46 +08:00
|
|
|
m_deleteConn = m_deleteButton->Click.connect([this, i]{ onDeleteAccel(i); });
|
2022-02-19 06:01:46 +08:00
|
|
|
m_deleteButton->setStyle(theme->styles.miniButton());
|
2015-05-06 23:13:35 +08:00
|
|
|
addChild(m_deleteButton.get());
|
2014-10-30 09:06:25 +08:00
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
m_changeButton->setBgColor(gfx::ColorNone);
|
|
|
|
m_changeButton->setBounds(itemBounds);
|
2018-07-20 10:05:14 +08:00
|
|
|
m_changeButton->setText(getAccelText((*accels)[i]));
|
2014-10-30 09:06:25 +08:00
|
|
|
|
|
|
|
const char* label = "x";
|
|
|
|
m_deleteButton->setBgColor(gfx::ColorNone);
|
2017-02-15 01:16:37 +08:00
|
|
|
m_deleteButton->setBounds(
|
|
|
|
gfx::Rect(
|
|
|
|
itemBounds.x + itemBounds.w + 2*guiscale(),
|
|
|
|
itemBounds.y,
|
|
|
|
Graphics::measureUITextLength(
|
|
|
|
label, font()) + 4*guiscale(),
|
|
|
|
itemBounds.h));
|
2014-10-30 09:06:25 +08:00
|
|
|
m_deleteButton->setText(label);
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
invalidate();
|
|
|
|
}
|
2015-05-06 23:13:35 +08:00
|
|
|
}
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2015-05-06 23:13:35 +08:00
|
|
|
if (i == 0 && !m_addButton &&
|
|
|
|
(!m_menuitem || m_menuitem->getCommand())) {
|
2016-09-14 02:02:00 +08:00
|
|
|
m_addConn = obs::connection();
|
2015-05-06 23:13:35 +08:00
|
|
|
m_addButton.reset(new Button(""));
|
2020-07-04 08:51:46 +08:00
|
|
|
m_addConn = m_addButton->Click.connect([this]{ onAddAccel(); });
|
2022-02-19 06:01:46 +08:00
|
|
|
m_addButton->setStyle(theme->styles.miniButton());
|
2015-05-06 23:13:35 +08:00
|
|
|
addChild(m_addButton.get());
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2017-02-07 04:58:55 +08:00
|
|
|
itemBounds.w = 8*guiscale() + Graphics::measureUITextLength("Add", font());
|
2015-05-06 23:13:35 +08:00
|
|
|
itemBounds.x -= itemBounds.w + 2*guiscale();
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2015-05-06 23:13:35 +08:00
|
|
|
m_addButton->setBgColor(gfx::ColorNone);
|
|
|
|
m_addButton->setBounds(itemBounds);
|
2022-01-10 16:37:54 +08:00
|
|
|
m_addButton->setText(Strings::keyboard_shortcuts_add());
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2015-05-06 23:13:35 +08:00
|
|
|
invalidate();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-05-21 04:08:04 +08:00
|
|
|
return KeyItemBase::onProcessMessage(msg);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void destroyButtons() {
|
2016-09-14 02:02:00 +08:00
|
|
|
m_changeConn = obs::connection();
|
|
|
|
m_deleteConn = obs::connection();
|
|
|
|
m_addConn = obs::connection();
|
2016-07-01 23:50:47 +08:00
|
|
|
|
|
|
|
if (!m_lockButtons) {
|
|
|
|
m_changeButton.reset();
|
|
|
|
m_deleteButton.reset();
|
|
|
|
m_addButton.reset();
|
|
|
|
}
|
|
|
|
// Just hide the buttons
|
|
|
|
else {
|
|
|
|
if (m_changeButton) m_changeButton->setVisible(false);
|
|
|
|
if (m_deleteButton) m_deleteButton->setVisible(false);
|
|
|
|
if (m_addButton) m_addButton->setVisible(false);
|
|
|
|
}
|
|
|
|
|
2015-05-06 23:13:35 +08:00
|
|
|
m_hotAccel = -1;
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
std::string getAccelText(const Accelerator& accel) const {
|
|
|
|
if (m_key && m_key->type() == KeyType::WheelAction &&
|
|
|
|
accel.isEmpty()) {
|
2022-01-10 16:37:54 +08:00
|
|
|
return Strings::keyboard_shortcuts_default_action();
|
2018-07-20 10:05:14 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return accel.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
KeyboardShortcuts& m_keys;
|
|
|
|
MenuKeys& m_menuKeys;
|
2018-07-18 10:53:08 +08:00
|
|
|
KeyPtr m_key;
|
|
|
|
KeyPtr m_keyOrig;
|
2014-10-29 22:58:03 +08:00
|
|
|
AppMenuItem* m_menuitem;
|
|
|
|
int m_level;
|
|
|
|
ui::Accelerators m_newAccels;
|
2019-08-02 06:14:46 +08:00
|
|
|
std::shared_ptr<ui::Button> m_changeButton;
|
|
|
|
std::shared_ptr<ui::Button> m_deleteButton;
|
|
|
|
std::shared_ptr<ui::Button> m_addButton;
|
2016-09-14 02:02:00 +08:00
|
|
|
obs::scoped_connection m_changeConn;
|
|
|
|
obs::scoped_connection m_deleteConn;
|
|
|
|
obs::scoped_connection m_addConn;
|
2014-10-29 22:58:03 +08:00
|
|
|
int m_hotAccel;
|
2016-07-01 23:50:47 +08:00
|
|
|
bool m_lockButtons;
|
2017-10-24 21:20:21 +08:00
|
|
|
HeaderItem* m_headerItem;
|
2014-10-29 22:58:03 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts {
|
2022-03-09 07:40:11 +08:00
|
|
|
// TODO Merge with CanvasSizeWindow::Dir
|
|
|
|
enum class Dir { NW, N, NE, W, C, E, SW, S, SE };
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
public:
|
2018-07-24 02:15:04 +08:00
|
|
|
KeyboardShortcutsWindow(app::KeyboardShortcuts& keys,
|
|
|
|
MenuKeys& menuKeys,
|
2022-03-09 07:40:11 +08:00
|
|
|
const std::string& searchText,
|
|
|
|
int& curSection)
|
2018-07-24 02:15:04 +08:00
|
|
|
: m_keys(keys)
|
|
|
|
, m_menuKeys(menuKeys)
|
|
|
|
, m_searchChange(false)
|
2022-03-09 07:40:11 +08:00
|
|
|
, m_wasDefault(false)
|
|
|
|
, m_curSection(curSection) {
|
2014-10-29 22:58:03 +08:00
|
|
|
setAutoRemap(false);
|
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
m_listBoxes.push_back(menus());
|
|
|
|
m_listBoxes.push_back(commands());
|
|
|
|
m_listBoxes.push_back(tools());
|
|
|
|
m_listBoxes.push_back(actions());
|
2018-07-20 10:05:14 +08:00
|
|
|
m_listBoxes.push_back(wheelActions());
|
2022-03-09 07:40:11 +08:00
|
|
|
m_listBoxes.push_back(dragActions());
|
2018-07-20 10:05:14 +08:00
|
|
|
|
|
|
|
#ifdef __APPLE__ // Zoom sliding two fingers option only on macOS
|
|
|
|
slideZoom()->setVisible(true);
|
|
|
|
#else
|
|
|
|
slideZoom()->setVisible(false);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
wheelBehavior()->setSelectedItem(
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.hasMouseWheelCustomization() ? 1: 0);
|
2018-07-20 10:05:14 +08:00
|
|
|
if (isDefaultWheelBehavior()) {
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.setDefaultMouseWheelKeys(wheelZoom()->isSelected());
|
2018-07-20 10:05:14 +08:00
|
|
|
m_wasDefault = true;
|
|
|
|
}
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.addMissingMouseWheelKeys();
|
2018-07-20 10:05:14 +08:00
|
|
|
updateSlideZoomText();
|
|
|
|
|
|
|
|
onWheelBehaviorChange();
|
|
|
|
|
2020-07-04 08:51:46 +08:00
|
|
|
wheelBehavior()->ItemChange.connect([this]{ onWheelBehaviorChange(); });
|
|
|
|
wheelZoom()->Click.connect([this]{ onWheelZoomChange(); });
|
2017-10-24 21:20:21 +08:00
|
|
|
|
2020-07-04 08:51:46 +08:00
|
|
|
search()->Change.connect([this]{ onSearchChange(); });
|
|
|
|
section()->Change.connect([this]{ onSectionChange(); });
|
2022-03-09 07:40:11 +08:00
|
|
|
dragActions()->Change.connect([this]{ onDragActionsChange(); });
|
|
|
|
dragAngle()->ItemChange.connect([this]{ onDragVectorChange(); });
|
|
|
|
dragDistance()->Change.connect([this]{ onDragVectorChange(); });
|
2020-07-04 08:51:46 +08:00
|
|
|
importButton()->Click.connect([this]{ onImport(); });
|
|
|
|
exportButton()->Click.connect([this]{ onExport(); });
|
|
|
|
resetButton()->Click.connect([this]{ onReset(); });
|
2014-10-29 22:58:03 +08:00
|
|
|
|
|
|
|
fillAllLists();
|
2015-12-16 04:12:11 +08:00
|
|
|
|
|
|
|
if (!searchText.empty()) {
|
|
|
|
search()->setText(searchText);
|
|
|
|
onSearchChange();
|
|
|
|
}
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2016-11-18 05:18:50 +08:00
|
|
|
~KeyboardShortcutsWindow() {
|
|
|
|
deleteAllKeyItems();
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
bool isDefaultWheelBehavior() {
|
|
|
|
return (wheelBehavior()->selectedItem() == 0);
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
private:
|
2018-11-27 05:46:13 +08:00
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
void deleteAllKeyItems() {
|
2016-11-18 05:18:50 +08:00
|
|
|
deleteList(searchList());
|
|
|
|
deleteList(menus());
|
|
|
|
deleteList(commands());
|
|
|
|
deleteList(tools());
|
|
|
|
deleteList(actions());
|
2018-07-20 10:05:14 +08:00
|
|
|
deleteList(wheelActions());
|
2022-03-09 07:40:11 +08:00
|
|
|
deleteList(dragActions());
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void fillAllLists() {
|
|
|
|
deleteAllKeyItems();
|
|
|
|
|
2021-05-21 04:08:04 +08:00
|
|
|
// Fill each list box with the keyboard shortcuts...
|
|
|
|
|
2017-12-01 01:51:13 +08:00
|
|
|
fillMenusList(menus(), AppMenus::instance()->getRootMenu(), 0);
|
2021-05-21 04:08:04 +08:00
|
|
|
|
|
|
|
{
|
|
|
|
// Create a pseudo-item for the palette menu
|
|
|
|
KeyItemBase* listItem = new KeyItemBase(
|
|
|
|
Strings::palette_popup_menu_title());
|
|
|
|
menus()->addChild(listItem);
|
|
|
|
fillMenusList(menus(), AppMenus::instance()->getPalettePopupMenu(), 1);
|
|
|
|
}
|
|
|
|
|
2017-12-01 01:51:13 +08:00
|
|
|
fillToolsList(tools(), App::instance()->toolBox());
|
2018-07-24 02:15:04 +08:00
|
|
|
fillWheelActionsList();
|
2022-03-09 07:40:11 +08:00
|
|
|
fillDragActionsList();
|
2017-12-01 01:51:13 +08:00
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
for (const KeyPtr& key : m_keys) {
|
2016-12-02 04:36:10 +08:00
|
|
|
if (key->type() == KeyType::Tool ||
|
2018-07-20 10:05:14 +08:00
|
|
|
key->type() == KeyType::Quicktool ||
|
2022-03-09 07:40:11 +08:00
|
|
|
key->type() == KeyType::WheelAction ||
|
|
|
|
key->type() == KeyType::DragAction) {
|
2016-12-02 04:36:10 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
std::string text = key->triggerString();
|
2014-11-16 05:31:12 +08:00
|
|
|
switch (key->keycontext()) {
|
2015-09-12 07:04:02 +08:00
|
|
|
case KeyContext::SelectionTool:
|
|
|
|
case KeyContext::TranslatingSelection:
|
|
|
|
case KeyContext::ScalingSelection:
|
|
|
|
case KeyContext::RotatingSelection:
|
2014-11-17 05:33:31 +08:00
|
|
|
case KeyContext::MoveTool:
|
2015-09-16 23:19:10 +08:00
|
|
|
case KeyContext::FreehandTool:
|
2016-04-05 05:46:48 +08:00
|
|
|
case KeyContext::ShapeTool:
|
2017-10-24 00:03:18 +08:00
|
|
|
text =
|
|
|
|
convertKeyContextToUserFriendlyString(key->keycontext())
|
|
|
|
+ ": " + text;
|
2016-04-05 05:46:48 +08:00
|
|
|
break;
|
2014-11-16 05:31:12 +08:00
|
|
|
}
|
2018-07-24 02:15:04 +08:00
|
|
|
KeyItem* keyItem = new KeyItem(m_keys, m_menuKeys, text, key,
|
|
|
|
nullptr, 0, &m_headerItem);
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2016-12-02 04:36:10 +08:00
|
|
|
ListBox* listBox = nullptr;
|
2014-10-29 22:58:03 +08:00
|
|
|
switch (key->type()) {
|
|
|
|
case KeyType::Command:
|
|
|
|
listBox = this->commands();
|
|
|
|
break;
|
|
|
|
case KeyType::Action:
|
|
|
|
listBox = this->actions();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(listBox);
|
2018-07-24 02:15:04 +08:00
|
|
|
if (listBox)
|
2014-10-29 22:58:03 +08:00
|
|
|
listBox->addChild(keyItem);
|
|
|
|
}
|
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
commands()->sortItems();
|
|
|
|
tools()->sortItems();
|
|
|
|
actions()->sortItems();
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2022-03-09 07:40:11 +08:00
|
|
|
section()->selectIndex(m_curSection);
|
2015-12-01 02:08:18 +08:00
|
|
|
updateViews();
|
|
|
|
}
|
|
|
|
|
2016-11-18 05:18:50 +08:00
|
|
|
void deleteList(Widget* listbox) {
|
2017-10-24 21:20:21 +08:00
|
|
|
if (m_headerItem.parent() == listbox)
|
|
|
|
listbox->removeChild(&m_headerItem);
|
|
|
|
|
2022-05-20 21:57:50 +08:00
|
|
|
while (auto item = listbox->lastChild()) {
|
2016-11-18 05:18:50 +08:00
|
|
|
listbox->removeChild(item);
|
|
|
|
delete item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-01 02:08:18 +08:00
|
|
|
void fillSearchList(const std::string& search) {
|
2016-11-18 05:18:50 +08:00
|
|
|
deleteList(searchList());
|
2015-12-01 02:08:18 +08:00
|
|
|
|
2017-03-16 01:24:42 +08:00
|
|
|
MatchWords match(search);
|
2015-12-01 02:08:18 +08:00
|
|
|
|
2016-08-20 01:27:45 +08:00
|
|
|
int sectionIdx = 0; // index 0 is menus
|
2017-10-24 21:20:21 +08:00
|
|
|
for (auto listBox : m_listBoxes) {
|
2015-12-01 02:08:18 +08:00
|
|
|
Separator* group = nullptr;
|
|
|
|
|
2015-12-04 06:46:13 +08:00
|
|
|
for (auto item : listBox->children()) {
|
2015-12-01 02:08:18 +08:00
|
|
|
if (KeyItem* keyItem = dynamic_cast<KeyItem*>(item)) {
|
2016-08-20 01:27:45 +08:00
|
|
|
std::string itemText = keyItem->searchableText();
|
2017-03-16 01:24:42 +08:00
|
|
|
if (!match(itemText))
|
|
|
|
continue;
|
2015-12-01 02:08:18 +08:00
|
|
|
|
2017-03-16 01:24:42 +08:00
|
|
|
if (!group) {
|
2017-07-25 02:13:32 +08:00
|
|
|
group = new SeparatorInView(
|
2017-03-16 01:24:42 +08:00
|
|
|
section()->children()[sectionIdx]->text(), HORIZONTAL);
|
|
|
|
searchList()->addChild(group);
|
|
|
|
}
|
2015-12-01 02:08:18 +08:00
|
|
|
|
2017-03-16 01:24:42 +08:00
|
|
|
KeyItem* copyItem =
|
2018-07-24 02:15:04 +08:00
|
|
|
new KeyItem(m_keys, m_menuKeys, itemText, keyItem->key(),
|
|
|
|
keyItem->menuitem(), 0, &m_headerItem);
|
2016-11-18 05:18:50 +08:00
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
if (!item->isEnabled())
|
|
|
|
copyItem->setEnabled(false);
|
|
|
|
|
2017-03-16 01:24:42 +08:00
|
|
|
searchList()->addChild(copyItem);
|
2015-12-01 02:08:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++sectionIdx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
void onWheelBehaviorChange() {
|
|
|
|
const bool isDefault = isDefaultWheelBehavior();
|
|
|
|
wheelActions()->setEnabled(!isDefault);
|
|
|
|
wheelZoom()->setVisible(isDefault);
|
|
|
|
|
|
|
|
if (isDefault) {
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.setDefaultMouseWheelKeys(wheelZoom()->isSelected());
|
2018-07-20 10:05:14 +08:00
|
|
|
m_wasDefault = true;
|
|
|
|
}
|
|
|
|
else if (m_wasDefault) {
|
|
|
|
m_wasDefault = false;
|
2018-07-24 02:15:04 +08:00
|
|
|
for (KeyPtr& key : m_keys) {
|
|
|
|
if (key->type() == KeyType::WheelAction)
|
|
|
|
key->copyOriginalToUser();
|
|
|
|
}
|
2018-07-20 10:05:14 +08:00
|
|
|
}
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.addMissingMouseWheelKeys();
|
2018-07-20 10:05:14 +08:00
|
|
|
updateSlideZoomText();
|
|
|
|
|
|
|
|
fillWheelActionsList();
|
|
|
|
updateViews();
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateSlideZoomText() {
|
|
|
|
slideZoom()->setText(
|
|
|
|
isDefaultWheelBehavior() ?
|
|
|
|
Strings::options_slide_zoom():
|
|
|
|
Strings::keyboard_shortcuts_slide_as_wheel());
|
|
|
|
}
|
|
|
|
|
|
|
|
void fillWheelActionsList() {
|
|
|
|
deleteList(wheelActions());
|
2018-07-24 02:15:04 +08:00
|
|
|
for (const KeyPtr& key : m_keys) {
|
|
|
|
if (key->type() == KeyType::WheelAction) {
|
|
|
|
KeyItem* keyItem = new KeyItem(
|
|
|
|
m_keys, m_menuKeys, key->triggerString(), key,
|
|
|
|
nullptr, 0, &m_headerItem);
|
|
|
|
wheelActions()->addChild(keyItem);
|
|
|
|
}
|
2018-07-20 10:05:14 +08:00
|
|
|
}
|
|
|
|
wheelActions()->sortItems();
|
|
|
|
}
|
|
|
|
|
2022-03-09 07:40:11 +08:00
|
|
|
void fillDragActionsList() {
|
|
|
|
deleteList(dragActions());
|
|
|
|
for (const KeyPtr& key : m_keys) {
|
|
|
|
if (key->type() == KeyType::DragAction) {
|
|
|
|
KeyItem* keyItem = new KeyItem(
|
|
|
|
m_keys, m_menuKeys, key->triggerString(), key,
|
|
|
|
nullptr, 0, &m_headerItem);
|
|
|
|
dragActions()->addChild(keyItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dragActions()->sortItems();
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
void onWheelZoomChange() {
|
|
|
|
const bool isDefault = isDefaultWheelBehavior();
|
|
|
|
if (isDefault)
|
|
|
|
onWheelBehaviorChange();
|
|
|
|
}
|
|
|
|
|
2015-12-01 02:08:18 +08:00
|
|
|
void onSearchChange() {
|
2023-04-15 00:43:36 +08:00
|
|
|
base::ScopedValue flag(m_searchChange, true);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string searchText = search()->text();
|
2015-12-01 02:08:18 +08:00
|
|
|
|
|
|
|
if (searchText.empty())
|
2022-03-09 07:40:11 +08:00
|
|
|
section()->selectIndex(m_curSection);
|
2015-12-01 02:08:18 +08:00
|
|
|
else {
|
|
|
|
fillSearchList(searchText);
|
|
|
|
section()->selectChild(nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateViews();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void onSectionChange() {
|
2015-12-01 02:08:18 +08:00
|
|
|
if (m_searchChange)
|
|
|
|
return;
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2015-12-01 02:08:18 +08:00
|
|
|
search()->setText("");
|
|
|
|
updateViews();
|
|
|
|
}
|
|
|
|
|
2022-03-09 07:40:11 +08:00
|
|
|
void onDragActionsChange() {
|
|
|
|
auto key = selectedDragActionKey();
|
|
|
|
if (!key)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int angle = 180 * key->dragVector().angle() / PI;
|
|
|
|
|
|
|
|
ui::Widget* oldFocus = manager()->getFocus();
|
|
|
|
dragAngle()->setSelectedItem((int)angleToDir(angle));
|
|
|
|
if (oldFocus)
|
|
|
|
oldFocus->requestFocus();
|
|
|
|
|
|
|
|
dragDistance()->setValue(key->dragVector().magnitude());
|
|
|
|
}
|
|
|
|
|
|
|
|
void onDragVectorChange() {
|
|
|
|
auto key = selectedDragActionKey();
|
|
|
|
if (!key)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto v = key->dragVector();
|
|
|
|
double a = dirToAngle((Dir)dragAngle()->selectedItem()).angle();
|
|
|
|
double m = dragDistance()->getValue();
|
|
|
|
v.x = m * std::cos(a);
|
|
|
|
v.y = m * std::sin(a);
|
|
|
|
if (std::fabs(v.x) < 0.00001) v.x = 0.0;
|
|
|
|
if (std::fabs(v.y) < 0.00001) v.y = 0.0;
|
|
|
|
key->setDragVector(v);
|
|
|
|
}
|
|
|
|
|
2015-12-01 02:08:18 +08:00
|
|
|
void updateViews() {
|
2017-10-24 21:20:21 +08:00
|
|
|
int s = section()->getSelectedIndex();
|
2022-03-09 07:40:11 +08:00
|
|
|
if (s >= 0)
|
|
|
|
m_curSection = s;
|
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
searchView()->setVisible(s < 0);
|
|
|
|
menusView()->setVisible(s == 0);
|
|
|
|
commandsView()->setVisible(s == 1);
|
|
|
|
toolsView()->setVisible(s == 2);
|
|
|
|
actionsView()->setVisible(s == 3);
|
2018-07-20 10:05:14 +08:00
|
|
|
wheelSection()->setVisible(s == 4);
|
2022-03-09 07:40:11 +08:00
|
|
|
dragSection()->setVisible(s == 5);
|
2017-10-24 21:20:21 +08:00
|
|
|
|
|
|
|
if (m_headerItem.parent())
|
|
|
|
m_headerItem.parent()->removeChild(&m_headerItem);
|
|
|
|
if (s < 0)
|
|
|
|
searchList()->insertChild(0, &m_headerItem);
|
|
|
|
else
|
|
|
|
m_listBoxes[s]->insertChild(0, &m_headerItem);
|
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
listsPlaceholder()->layout();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void onImport() {
|
2018-02-21 21:39:30 +08:00
|
|
|
base::paths exts = { KEYBOARD_FILENAME_EXTENSION };
|
|
|
|
base::paths filename;
|
2017-04-08 11:06:25 +08:00
|
|
|
if (!app::show_file_selector(
|
2022-01-03 16:31:46 +08:00
|
|
|
Strings::keyboard_shortcuts_import_keyboard_sc(), "", exts,
|
2017-04-08 11:06:25 +08:00
|
|
|
FileSelectorType::Open, filename))
|
2014-10-29 22:58:03 +08:00
|
|
|
return;
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
ASSERT(!filename.empty());
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.importFile(filename.front(), KeySource::UserDefined);
|
2017-04-08 11:06:25 +08:00
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
fillAllLists();
|
|
|
|
}
|
|
|
|
|
|
|
|
void onExport() {
|
2018-02-21 21:39:30 +08:00
|
|
|
base::paths exts = { KEYBOARD_FILENAME_EXTENSION };
|
|
|
|
base::paths filename;
|
2017-04-08 11:06:25 +08:00
|
|
|
|
|
|
|
if (!app::show_file_selector(
|
2022-01-03 16:31:46 +08:00
|
|
|
Strings::keyboard_shortcuts_export_keyboard_sc(), "", exts,
|
2017-04-08 11:06:25 +08:00
|
|
|
FileSelectorType::Save, filename))
|
2015-05-29 03:11:06 +08:00
|
|
|
return;
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
ASSERT(!filename.empty());
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.exportFile(filename.front());
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void onReset() {
|
2017-10-18 05:00:45 +08:00
|
|
|
if (ui::Alert::show(Strings::alerts_restore_all_shortcuts()) == 1) {
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys.reset();
|
2018-07-25 00:42:59 +08:00
|
|
|
if (!isDefaultWheelBehavior()) {
|
|
|
|
wheelBehavior()->setSelectedItem(0);
|
|
|
|
onWheelBehaviorChange();
|
|
|
|
}
|
2018-07-20 10:05:14 +08:00
|
|
|
listsPlaceholder()->layout();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-01 01:51:13 +08:00
|
|
|
void fillMenusList(ListBox* listbox, Menu* menu, int level) {
|
2015-12-04 06:46:13 +08:00
|
|
|
for (auto child : menu->children()) {
|
2014-10-29 22:58:03 +08:00
|
|
|
if (AppMenuItem* menuItem = dynamic_cast<AppMenuItem*>(child)) {
|
2019-05-28 03:29:53 +08:00
|
|
|
if (menuItem->isRecentFileItem())
|
2014-10-29 22:58:03 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
KeyItem* keyItem = new KeyItem(
|
2018-07-24 02:15:04 +08:00
|
|
|
m_keys, m_menuKeys,
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
menuItem->text().c_str(),
|
2018-07-24 02:15:04 +08:00
|
|
|
m_menuKeys[menuItem],
|
|
|
|
menuItem, level,
|
2017-10-24 21:20:21 +08:00
|
|
|
&m_headerItem);
|
2014-10-29 22:58:03 +08:00
|
|
|
|
|
|
|
listbox->addChild(keyItem);
|
|
|
|
|
|
|
|
if (menuItem->hasSubmenu())
|
2017-12-01 01:51:13 +08:00
|
|
|
fillMenusList(listbox, menuItem->getSubmenu(), level+1);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-01 01:51:13 +08:00
|
|
|
void fillToolsList(ListBox* listbox, ToolBox* toolbox) {
|
2016-12-02 04:36:10 +08:00
|
|
|
for (Tool* tool : *toolbox) {
|
|
|
|
std::string text = tool->getText();
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
KeyPtr key = m_keys.tool(tool);
|
|
|
|
KeyItem* keyItem = new KeyItem(m_keys, m_menuKeys, text, key,
|
|
|
|
nullptr, 0, &m_headerItem);
|
2016-12-02 04:36:10 +08:00
|
|
|
listbox->addChild(keyItem);
|
|
|
|
|
|
|
|
text += " (quick)";
|
2018-07-24 02:15:04 +08:00
|
|
|
key = m_keys.quicktool(tool);
|
|
|
|
keyItem = new KeyItem(m_keys, m_menuKeys, text, key,
|
|
|
|
nullptr, 0, &m_headerItem);
|
2016-12-02 04:36:10 +08:00
|
|
|
listbox->addChild(keyItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-27 05:46:13 +08:00
|
|
|
bool onProcessMessage(ui::Message* msg) override {
|
|
|
|
switch (msg->type()) {
|
|
|
|
case kOpenMessage:
|
|
|
|
load_window_pos(this, "KeyboardShortcuts");
|
|
|
|
invalidate();
|
|
|
|
break;
|
|
|
|
case kCloseMessage:
|
|
|
|
save_window_pos(this, "KeyboardShortcuts");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return app::gen::KeyboardShortcuts::onProcessMessage(msg);
|
|
|
|
}
|
|
|
|
|
2022-03-09 07:40:11 +08:00
|
|
|
KeyPtr selectedDragActionKey() {
|
|
|
|
auto item = dragActions()->getSelectedChild();
|
|
|
|
if (KeyItem* keyItem = dynamic_cast<KeyItem*>(item)) {
|
|
|
|
KeyPtr key = keyItem->key();
|
|
|
|
if (key && key->type() == KeyType::DragAction)
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dir angleToDir(int angle) {
|
|
|
|
if (angle >= -1*45/2 && angle < 1*45/2) return Dir::E;
|
|
|
|
if (angle >= 1*45/2 && angle < 3*45/2) return Dir::NE;
|
|
|
|
if (angle >= 3*45/2 && angle < 5*45/2) return Dir::N;
|
|
|
|
if (angle >= 5*45/2 && angle < 7*45/2) return Dir::NW;
|
|
|
|
if ((angle >= 7*45/2 && angle <= 180) ||
|
|
|
|
(angle >= -180 && angle <= -7*45/2)) return Dir::W;
|
|
|
|
if (angle > -7*45/2 && angle <= -5*45/2) return Dir::SW;
|
|
|
|
if (angle > -5*45/2 && angle <= -3*45/2) return Dir::S;
|
|
|
|
if (angle > -3*45/2 && angle <= -1*45/2) return Dir::SE;
|
|
|
|
return Dir::C;
|
|
|
|
}
|
|
|
|
|
|
|
|
DragVector dirToAngle(Dir dir) {
|
|
|
|
switch (dir) {
|
|
|
|
case Dir::NW: return DragVector(-1, +1);
|
|
|
|
case Dir::N: return DragVector( 0, +1);
|
|
|
|
case Dir::NE: return DragVector(+1, +1);
|
|
|
|
case Dir::W: return DragVector(-1, 0);
|
|
|
|
case Dir::C: return DragVector( 0, 0);
|
|
|
|
case Dir::E: return DragVector(+1, 0);
|
|
|
|
case Dir::SW: return DragVector(-1, -1);
|
|
|
|
case Dir::S: return DragVector( 0, -1);
|
|
|
|
case Dir::SE: return DragVector(+1, -1);
|
|
|
|
}
|
|
|
|
return DragVector();
|
|
|
|
}
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
app::KeyboardShortcuts& m_keys;
|
|
|
|
MenuKeys& m_menuKeys;
|
2017-10-24 21:20:21 +08:00
|
|
|
std::vector<ListBox*> m_listBoxes;
|
2015-12-01 02:08:18 +08:00
|
|
|
bool m_searchChange;
|
2018-07-20 10:05:14 +08:00
|
|
|
bool m_wasDefault;
|
2017-10-24 21:20:21 +08:00
|
|
|
HeaderItem m_headerItem;
|
2022-03-09 07:40:11 +08:00
|
|
|
int& m_curSection;
|
2014-10-29 22:58:03 +08:00
|
|
|
};
|
|
|
|
|
2017-10-24 21:20:21 +08:00
|
|
|
} // anonymous namespace
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
class KeyboardShortcutsCommand : public Command {
|
|
|
|
public:
|
|
|
|
KeyboardShortcutsCommand();
|
|
|
|
|
|
|
|
protected:
|
2015-12-16 04:12:11 +08:00
|
|
|
void onLoadParams(const Params& params) override;
|
2015-10-01 03:34:43 +08:00
|
|
|
void onExecute(Context* context) override;
|
2015-12-16 04:12:11 +08:00
|
|
|
|
|
|
|
private:
|
2018-07-24 02:15:04 +08:00
|
|
|
void fillMenusKeys(app::KeyboardShortcuts& keys,
|
|
|
|
MenuKeys& menuKeys, Menu* menu);
|
2017-12-01 01:51:13 +08:00
|
|
|
|
2015-12-16 04:12:11 +08:00
|
|
|
std::string m_search;
|
2014-10-29 22:58:03 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
KeyboardShortcutsCommand::KeyboardShortcutsCommand()
|
2017-12-02 02:10:21 +08:00
|
|
|
: Command(CommandId::KeyboardShortcuts(), CmdUIOnlyFlag)
|
2014-10-29 22:58:03 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-12-16 04:12:11 +08:00
|
|
|
void KeyboardShortcutsCommand::onLoadParams(const Params& params)
|
|
|
|
{
|
|
|
|
m_search = params.get("search");
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
void KeyboardShortcutsCommand::onExecute(Context* context)
|
|
|
|
{
|
2022-03-09 07:40:11 +08:00
|
|
|
static int curSection = 0;
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
app::KeyboardShortcuts* globalKeys = app::KeyboardShortcuts::instance();
|
|
|
|
app::KeyboardShortcuts keys;
|
|
|
|
keys.setKeys(*globalKeys, true);
|
|
|
|
keys.addMissingKeysForCommands();
|
|
|
|
|
|
|
|
MenuKeys menuKeys;
|
|
|
|
fillMenusKeys(keys, menuKeys, AppMenus::instance()->getRootMenu());
|
2021-05-21 04:08:04 +08:00
|
|
|
fillMenusKeys(keys, menuKeys, AppMenus::instance()->getPalettePopupMenu());
|
2017-12-01 01:51:13 +08:00
|
|
|
|
2015-12-16 04:12:11 +08:00
|
|
|
// Here we copy the m_search field because
|
|
|
|
// KeyboardShortcutsWindow::fillAllLists() modifies this same
|
|
|
|
// KeyboardShortcutsCommand instance (so m_search will be "")
|
|
|
|
// TODO Seeing this, we need a complete new way to handle UI commands execution
|
|
|
|
std::string neededSearchCopy = m_search;
|
2022-03-09 07:40:11 +08:00
|
|
|
KeyboardShortcutsWindow window(keys, menuKeys, neededSearchCopy, curSection);
|
2014-10-29 22:58:03 +08:00
|
|
|
|
2021-03-20 05:57:56 +08:00
|
|
|
ui::Display* mainDisplay = Manager::getDefault()->display();
|
|
|
|
ui::fit_bounds(mainDisplay, &window,
|
|
|
|
gfx::Rect(mainDisplay->size()),
|
|
|
|
[](const gfx::Rect& workarea,
|
|
|
|
gfx::Rect& bounds,
|
|
|
|
std::function<gfx::Rect(Widget*)> getWidgetBounds) {
|
|
|
|
gfx::Point center = bounds.center();
|
|
|
|
bounds.setSize(workarea.size()*3/4);
|
|
|
|
bounds.setOrigin(center - gfx::Point(bounds.size()/2));
|
|
|
|
});
|
|
|
|
|
2018-11-27 05:46:13 +08:00
|
|
|
window.loadLayout();
|
2014-10-29 22:58:03 +08:00
|
|
|
|
|
|
|
window.setVisible(true);
|
|
|
|
window.openWindowInForeground();
|
|
|
|
|
2015-12-05 01:54:15 +08:00
|
|
|
if (window.closer() == window.ok()) {
|
2018-07-24 02:15:04 +08:00
|
|
|
globalKeys->setKeys(keys, false);
|
|
|
|
for (const auto& p : menuKeys)
|
|
|
|
p.first->setKey(p.second);
|
2017-06-23 21:16:37 +08:00
|
|
|
|
2018-07-20 10:05:14 +08:00
|
|
|
// Save preferences in widgets that are bound to options automatically
|
|
|
|
{
|
2021-06-08 02:20:45 +08:00
|
|
|
Message msg(kSavePreferencesMessage);
|
2021-06-08 04:17:18 +08:00
|
|
|
msg.setPropagateToChildren(true);
|
2021-06-08 02:20:45 +08:00
|
|
|
window.sendMessage(&msg);
|
2018-07-20 10:05:14 +08:00
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
// Save keyboard shortcuts in configuration file
|
|
|
|
{
|
|
|
|
ResourceFinder rf;
|
|
|
|
rf.includeUserDir("user." KEYBOARD_FILENAME_EXTENSION);
|
|
|
|
std::string fn = rf.getFirstOrCreateDefault();
|
2018-07-24 02:15:04 +08:00
|
|
|
globalKeys->exportFile(fn);
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
}
|
2017-09-02 00:32:23 +08:00
|
|
|
|
|
|
|
AppMenus::instance()->syncNativeMenuItemKeyShortcuts();
|
2014-10-29 22:58:03 +08:00
|
|
|
}
|
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
void KeyboardShortcutsCommand::fillMenusKeys(app::KeyboardShortcuts& keys,
|
|
|
|
MenuKeys& menuKeys,
|
|
|
|
Menu* menu)
|
2017-12-01 01:51:13 +08:00
|
|
|
{
|
2018-07-24 02:15:04 +08:00
|
|
|
for (auto child : menu->children()) {
|
|
|
|
if (AppMenuItem* menuItem = dynamic_cast<AppMenuItem*>(child)) {
|
2019-05-28 03:29:53 +08:00
|
|
|
if (menuItem->isRecentFileItem())
|
2018-07-24 02:15:04 +08:00
|
|
|
continue;
|
2017-12-01 01:51:13 +08:00
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
if (menuItem->getCommand()) {
|
|
|
|
menuKeys[menuItem] =
|
2022-06-08 01:47:40 +08:00
|
|
|
keys.command(menuItem->getCommandId().c_str(),
|
2018-07-24 02:15:04 +08:00
|
|
|
menuItem->getParams());
|
|
|
|
}
|
2017-12-01 01:51:13 +08:00
|
|
|
|
2018-07-24 02:15:04 +08:00
|
|
|
if (menuItem->hasSubmenu())
|
|
|
|
fillMenusKeys(keys, menuKeys, menuItem->getSubmenu());
|
|
|
|
}
|
2017-12-01 01:51:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:58:03 +08:00
|
|
|
Command* CommandFactory::createKeyboardShortcutsCommand()
|
|
|
|
{
|
|
|
|
return new KeyboardShortcutsCommand;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace app
|