mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-11 09:40:42 +00:00
Add native macOS menus (fix #135)
* This implements the Cmd+H and Cmd+M keys too: https://community.aseprite.org/t/279 * Also Cmd+, has more priority on macOS than Cmd+K to open the preferences (so macOS menu shows Cmd+,)
This commit is contained in:
parent
607c4d139e
commit
0154a73d36
@ -50,11 +50,11 @@
|
||||
<key command="ReplaceColor" shortcut="Shift+R" />
|
||||
<key command="HueSaturation" shortcut="Ctrl+U" mac="Cmd+U" />
|
||||
<key command="ConvolutionMatrix" shortcut="F9" />
|
||||
<key command="ColorCurve" shortcut="Ctrl+M" mac="Cmd+M" />
|
||||
<key command="ColorCurve" shortcut="Ctrl+M" />
|
||||
<key command="ColorCurve" shortcut="F10" />
|
||||
<key command="PasteText" shortcut="T" />
|
||||
<key command="Options" shortcut="Ctrl+K" mac="Cmd+K" />
|
||||
<key command="Options" mac="Cmd+," />
|
||||
<key command="Options" shortcut="Ctrl+K" mac="Cmd+K" />
|
||||
<key command="KeyboardShortcuts" shortcut="Ctrl+Alt+Shift+K" mac="Cmd+Alt+Shift+K" />
|
||||
<!-- Sprite -->
|
||||
<key command="SpriteProperties" shortcut="Ctrl+P" mac="Cmd+P" />
|
||||
@ -129,7 +129,7 @@
|
||||
<key command="PaletteEditor">
|
||||
<param name="popup" value="background" />
|
||||
</key>
|
||||
<key command="ShowExtras" shortcut="Ctrl+H" mac="Cmd+H" />
|
||||
<key command="ShowExtras" shortcut="Ctrl+H" />
|
||||
<!-- Tabs -->
|
||||
<key command="GotoNextTab" shortcut="Ctrl+Tab" />
|
||||
<key command="GotoPreviousTab" shortcut="Ctrl+Shift+Tab" />
|
||||
@ -837,7 +837,7 @@
|
||||
<separator />
|
||||
<item command="Refresh" text="&Refresh && Reload Skin" />
|
||||
</menu>
|
||||
<menu text="&Help">
|
||||
<menu text="&Help" id="help_menu">
|
||||
<item command="OpenBrowser" text="Readme">
|
||||
<param name="filename" value="README.md" />
|
||||
</item>
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
#include "app/app_menus.h"
|
||||
|
||||
#include "base/string.h"
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
@ -23,9 +22,13 @@
|
||||
#include "app/ui/app_menuitem.h"
|
||||
#include "app/ui/keyboard_shortcuts.h"
|
||||
#include "app/ui/main_window.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/filetoks.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
#include "she/menus.h"
|
||||
#include "she/system.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
@ -42,6 +45,167 @@ static void destroy_instance(AppMenus* instance)
|
||||
delete instance;
|
||||
}
|
||||
|
||||
// TODO this should be on "she" library (or we should use
|
||||
// she::Shortcut instead of ui::Accelerators)
|
||||
static int from_scancode_to_unicode(KeyScancode scancode)
|
||||
{
|
||||
static int map[] = {
|
||||
0, // kKeyNil
|
||||
'a', // kKeyA
|
||||
'b', // kKeyB
|
||||
'c', // kKeyC
|
||||
'd', // kKeyD
|
||||
'e', // kKeyE
|
||||
'f', // kKeyF
|
||||
'g', // kKeyG
|
||||
'h', // kKeyH
|
||||
'i', // kKeyI
|
||||
'j', // kKeyJ
|
||||
'k', // kKeyK
|
||||
'l', // kKeyL
|
||||
'm', // kKeyM
|
||||
'n', // kKeyN
|
||||
'o', // kKeyO
|
||||
'p', // kKeyP
|
||||
'q', // kKeyQ
|
||||
'r', // kKeyR
|
||||
's', // kKeyS
|
||||
't', // kKeyT
|
||||
'u', // kKeyU
|
||||
'v', // kKeyV
|
||||
'w', // kKeyW
|
||||
'x', // kKeyX
|
||||
'y', // kKeyY
|
||||
'z', // kKeyZ
|
||||
'0', // kKey0
|
||||
'1', // kKey1
|
||||
'2', // kKey2
|
||||
'3', // kKey3
|
||||
'4', // kKey4
|
||||
'5', // kKey5
|
||||
'6', // kKey6
|
||||
'7', // kKey7
|
||||
'8', // kKey8
|
||||
'9', // kKey9
|
||||
0, // kKey0Pad
|
||||
0, // kKey1Pad
|
||||
0, // kKey2Pad
|
||||
0, // kKey3Pad
|
||||
0, // kKey4Pad
|
||||
0, // kKey5Pad
|
||||
0, // kKey6Pad
|
||||
0, // kKey7Pad
|
||||
0, // kKey8Pad
|
||||
0, // kKey9Pad
|
||||
0xF704, // kKeyF1 (NSF1FunctionKey)
|
||||
0xF705, // kKeyF2
|
||||
0xF706, // kKeyF3
|
||||
0xF707, // kKeyF4
|
||||
0xF708, // kKeyF5
|
||||
0xF709, // kKeyF6
|
||||
0xF70A, // kKeyF7
|
||||
0xF70B, // kKeyF8
|
||||
0xF70C, // kKeyF9
|
||||
0xF70D, // kKeyF10
|
||||
0xF70E, // kKeyF11
|
||||
0xF70F, // kKeyF12
|
||||
27, // kKeyEsc
|
||||
'~', // kKeyTilde
|
||||
'-', // kKeyMinus
|
||||
'=', // kKeyEquals
|
||||
8, // kKeyBackspace
|
||||
9, // kKeyTab
|
||||
'[', // kKeyOpenbrace
|
||||
']', // kKeyClosebrace
|
||||
'\r', // kKeyEnter // 10, kKeyEnter
|
||||
':', // kKeyColon
|
||||
'\'', // kKeyQuote
|
||||
'\\', // kKeyBackslash
|
||||
0, // kKeyBackslash2
|
||||
',', // kKeyComma
|
||||
'.', // kKeyStop
|
||||
'/', // kKeySlash
|
||||
' ', // kKeySpace
|
||||
0xF727, // kKeyInsert (NSInsertFunctionKey)
|
||||
0xF728, // kKeyDel (NSDeleteFunctionKey)
|
||||
0xF729, // kKeyHome (NSHomeFunctionKey)
|
||||
0xF72B, // kKeyEnd (NSEndFunctionKey)
|
||||
0xF72C, // kKeyPageUp (NSPageUpFunctionKey)
|
||||
0xF72D, // kKeyPageDown (NSPageDownFunctionKey)
|
||||
0xF702, // kKeyLeft (NSLeftArrowFunctionKey)
|
||||
0xF703, // kKeyRight (NSRightArrowFunctionKey)
|
||||
0xF700, // kKeyUp (NSUpArrowFunctionKey)
|
||||
0xF701, // kKeyDown (NSDownArrowFunctionKey)
|
||||
'/', // kKeySlashPad
|
||||
'*', // kKeyAsterisk
|
||||
0, // kKeyMinusPad
|
||||
0, // kKeyPlusPad
|
||||
0, // kKeyDelPad
|
||||
0, // kKeyEnterPad
|
||||
0, // kKeyPrtscr
|
||||
0, // kKeyPause
|
||||
0, // kKeyAbntC1
|
||||
0, // kKeyYen
|
||||
0, // kKeyKana
|
||||
0, // kKeyConvert
|
||||
0, // kKeyNoconvert
|
||||
0, // kKeyAt
|
||||
0, // kKeyCircumflex
|
||||
0, // kKeyColon2
|
||||
0, // kKeyKanji
|
||||
0, // kKeyEqualsPad
|
||||
'`', // kKeyBackquote
|
||||
0, // kKeySemicolon
|
||||
0, // kKeyUnknown1
|
||||
0, // kKeyUnknown2
|
||||
0, // kKeyUnknown3
|
||||
0, // kKeyUnknown4
|
||||
0, // kKeyUnknown5
|
||||
0, // kKeyUnknown6
|
||||
0, // kKeyUnknown7
|
||||
0, // kKeyUnknown8
|
||||
0, // kKeyLShift
|
||||
0, // kKeyRShift
|
||||
0, // kKeyLControl
|
||||
0, // kKeyRControl
|
||||
0, // kKeyAlt
|
||||
0, // kKeyAltGr
|
||||
0, // kKeyLWin
|
||||
0, // kKeyRWin
|
||||
0, // kKeyMenu
|
||||
0, // kKeyCommand
|
||||
0, // kKeyScrLock
|
||||
0, // kKeyNumLock
|
||||
0, // kKeyCapsLock
|
||||
};
|
||||
if (scancode >= 0 && scancode < sizeof(map) / sizeof(map[0]))
|
||||
return map[scancode];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fill_menu_item_info_with_key(she::MenuItemInfo& info,
|
||||
const char* commandId,
|
||||
const Params& params = Params())
|
||||
{
|
||||
Key* key = KeyboardShortcuts::instance()->command(commandId, params);
|
||||
if (key)
|
||||
info.shortcut = get_os_shortcut_from_key(key);
|
||||
}
|
||||
|
||||
she::Shortcut get_os_shortcut_from_key(Key* key)
|
||||
{
|
||||
if (key && !key->accels().empty()) {
|
||||
const ui::Accelerator& accel = key->accels().front();
|
||||
return she::Shortcut(
|
||||
(accel.unicodeChar() ? accel.unicodeChar():
|
||||
from_scancode_to_unicode(accel.scancode())),
|
||||
accel.modifiers());
|
||||
}
|
||||
else
|
||||
return she::Shortcut();
|
||||
}
|
||||
|
||||
// static
|
||||
AppMenus* AppMenus::instance()
|
||||
{
|
||||
@ -54,13 +218,20 @@ AppMenus* AppMenus::instance()
|
||||
}
|
||||
|
||||
AppMenus::AppMenus()
|
||||
: m_recentListMenuitem(NULL)
|
||||
: m_recentListMenuitem(nullptr)
|
||||
, m_osMenu(nullptr)
|
||||
{
|
||||
m_recentFilesConn =
|
||||
App::instance()->recentFiles()->Changed.connect(
|
||||
base::Bind(&AppMenus::rebuildRecentList, this));
|
||||
}
|
||||
|
||||
AppMenus::~AppMenus()
|
||||
{
|
||||
if (m_osMenu)
|
||||
m_osMenu->dispose();
|
||||
}
|
||||
|
||||
void AppMenus::reload()
|
||||
{
|
||||
XmlDocumentRef doc(GuiXml::instance()->doc());
|
||||
@ -93,6 +264,8 @@ void AppMenus::reload()
|
||||
m_palettePopupMenu.reset(loadMenuById(handle, "palette_popup"));
|
||||
m_inkPopupMenu.reset(loadMenuById(handle, "ink_popup"));
|
||||
|
||||
createNativeMenus();
|
||||
|
||||
////////////////////////////////////////
|
||||
// Load keyboard shortcuts for commands
|
||||
|
||||
@ -125,7 +298,7 @@ void AppMenus::initTheme()
|
||||
|
||||
bool AppMenus::rebuildRecentList()
|
||||
{
|
||||
MenuItem* list_menuitem = m_recentListMenuitem;
|
||||
AppMenuItem* list_menuitem = dynamic_cast<AppMenuItem*>(m_recentListMenuitem);
|
||||
MenuItem* menuitem;
|
||||
|
||||
// Update the recent file list menu item
|
||||
@ -166,6 +339,16 @@ bool AppMenus::rebuildRecentList()
|
||||
menuitem->setEnabled(false);
|
||||
submenu->addChild(menuitem);
|
||||
}
|
||||
|
||||
// Sync native menus
|
||||
if (list_menuitem->nativeMenuItem()) {
|
||||
she::Menus* menus = she::instance()->menus();
|
||||
she::Menu* osMenu = (menus ? menus->createMenu(): nullptr);
|
||||
if (osMenu) {
|
||||
createNativeSubmenus(osMenu, submenu);
|
||||
list_menuitem->nativeMenuItem()->setSubmenu(osMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -248,9 +431,12 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
|
||||
const char* id = elem->Attribute("id");
|
||||
if (id) {
|
||||
// Recent list menu
|
||||
if (strcmp(id, "recent_list") == 0) {
|
||||
if (std::strcmp(id, "recent_list") == 0) {
|
||||
m_recentListMenuitem = menuitem;
|
||||
}
|
||||
else if (std::strcmp(id, "help_menu") == 0) {
|
||||
m_helpMenuitem = menuitem;
|
||||
}
|
||||
}
|
||||
|
||||
// Has it a sub-menu (<menu>)?
|
||||
@ -312,6 +498,24 @@ void AppMenus::applyShortcutToMenuitemsWithCommand(Menu* menu, Command* command,
|
||||
}
|
||||
}
|
||||
|
||||
void AppMenus::syncNativeMenuItemKeyShortcuts()
|
||||
{
|
||||
syncNativeMenuItemKeyShortcuts(m_rootMenu.get());
|
||||
}
|
||||
|
||||
void AppMenus::syncNativeMenuItemKeyShortcuts(Menu* menu)
|
||||
{
|
||||
for (auto child : menu->children()) {
|
||||
if (child->type() == kMenuItemWidget) {
|
||||
if (AppMenuItem* menuitem = dynamic_cast<AppMenuItem*>(child))
|
||||
menuitem->syncNativeMenuItemKeyShortcut();
|
||||
|
||||
if (Menu* submenu = static_cast<MenuItem*>(child)->getSubmenu())
|
||||
syncNativeMenuItemKeyShortcuts(submenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO redesign the list of popup menus, it might be an
|
||||
// autogenerated widget from 'gen'
|
||||
void AppMenus::updateMenusList()
|
||||
@ -330,4 +534,149 @@ void AppMenus::updateMenusList()
|
||||
m_menus.push_back(m_inkPopupMenu);
|
||||
}
|
||||
|
||||
void AppMenus::createNativeMenus()
|
||||
{
|
||||
she::Menus* menus = she::instance()->menus();
|
||||
if (!menus) // This platform doesn't support native menu items
|
||||
return;
|
||||
|
||||
if (m_osMenu)
|
||||
m_osMenu->dispose();
|
||||
m_osMenu = menus->createMenu();
|
||||
|
||||
#ifdef __APPLE__ // Create default macOS app menus (App ... Window)
|
||||
{
|
||||
she::MenuItemInfo about("About " PACKAGE);
|
||||
fill_menu_item_info_with_key(about, CommandId::About);
|
||||
about.execute = []{
|
||||
if (Manager::getDefault()->getForegroundWindow() == App::instance()->mainWindow()) {
|
||||
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::About);
|
||||
UIContext::instance()->executeCommand(cmd);
|
||||
}
|
||||
};
|
||||
|
||||
she::MenuItemInfo preferences("Preferences...");
|
||||
fill_menu_item_info_with_key(preferences, CommandId::Options);
|
||||
preferences.execute = []{
|
||||
if (Manager::getDefault()->getForegroundWindow() == App::instance()->mainWindow()) {
|
||||
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::Options);
|
||||
UIContext::instance()->executeCommand(cmd);
|
||||
}
|
||||
};
|
||||
|
||||
she::MenuItemInfo hide("Hide " PACKAGE, she::MenuItemInfo::Hide);
|
||||
hide.shortcut = she::Shortcut('h', she::kKeyCmdModifier);
|
||||
|
||||
she::MenuItemInfo quit("Quit " PACKAGE, she::MenuItemInfo::Quit);
|
||||
quit.shortcut = she::Shortcut('q', she::kKeyCmdModifier);
|
||||
|
||||
she::Menu* appMenu = menus->createMenu();
|
||||
appMenu->addItem(menus->createMenuItem(about));
|
||||
appMenu->addItem(menus->createMenuItem(she::MenuItemInfo(she::MenuItemInfo::Separator)));
|
||||
appMenu->addItem(menus->createMenuItem(preferences));
|
||||
appMenu->addItem(menus->createMenuItem(she::MenuItemInfo(she::MenuItemInfo::Separator)));
|
||||
appMenu->addItem(menus->createMenuItem(hide));
|
||||
appMenu->addItem(menus->createMenuItem(she::MenuItemInfo("Hide Others", she::MenuItemInfo::HideOthers)));
|
||||
appMenu->addItem(menus->createMenuItem(she::MenuItemInfo("Show All", she::MenuItemInfo::ShowAll)));
|
||||
appMenu->addItem(menus->createMenuItem(she::MenuItemInfo(she::MenuItemInfo::Separator)));
|
||||
appMenu->addItem(menus->createMenuItem(quit));
|
||||
|
||||
she::MenuItem* appItem = menus->createMenuItem(she::MenuItemInfo("App"));
|
||||
appItem->setSubmenu(appMenu);
|
||||
m_osMenu->addItem(appItem);
|
||||
}
|
||||
#endif
|
||||
|
||||
createNativeSubmenus(m_osMenu, m_rootMenu);
|
||||
|
||||
#ifdef __APPLE__
|
||||
{
|
||||
// Search the index where help menu is located (so the Window menu
|
||||
// can take its place/index position)
|
||||
int i = 0, helpIndex = int(m_rootMenu->children().size());
|
||||
for (const auto child : m_rootMenu->children()) {
|
||||
if (child == m_helpMenuitem) {
|
||||
helpIndex = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
she::MenuItemInfo minimize("Minimize", she::MenuItemInfo::Minimize);
|
||||
minimize.shortcut = she::Shortcut('m', she::kKeyCmdModifier);
|
||||
|
||||
she::Menu* windowMenu = menus->createMenu();
|
||||
windowMenu->addItem(menus->createMenuItem(minimize));
|
||||
windowMenu->addItem(menus->createMenuItem(she::MenuItemInfo("Zoom", she::MenuItemInfo::Zoom)));
|
||||
|
||||
she::MenuItem* windowItem = menus->createMenuItem(she::MenuItemInfo("Window"));
|
||||
windowItem->setSubmenu(windowMenu);
|
||||
|
||||
// We use helpIndex+1 because the first index in m_osMenu is the
|
||||
// App menu.
|
||||
m_osMenu->insertItem(helpIndex+1, windowItem);
|
||||
}
|
||||
#endif
|
||||
|
||||
menus->setAppMenu(m_osMenu);
|
||||
}
|
||||
|
||||
void AppMenus::createNativeSubmenus(she::Menu* osMenu, const ui::Menu* uiMenu)
|
||||
{
|
||||
she::Menus* menus = she::instance()->menus();
|
||||
|
||||
for (const auto& child : uiMenu->children()) {
|
||||
she::MenuItemInfo info;
|
||||
AppMenuItem* appMenuItem = dynamic_cast<AppMenuItem*>(child);
|
||||
|
||||
if (child->type() == kSeparatorWidget) {
|
||||
info.type = she::MenuItemInfo::Separator;
|
||||
}
|
||||
else if (child->type() == kMenuItemWidget) {
|
||||
info.type = she::MenuItemInfo::Normal;
|
||||
info.text = child->text();
|
||||
info.execute = [child]{
|
||||
if (child->manager()->getForegroundWindow() == App::instance()->mainWindow())
|
||||
((ui::MenuItem*)child)->executeClick();
|
||||
};
|
||||
info.validate = [child](she::MenuItem* item) {
|
||||
if (child->manager()->getForegroundWindow() == App::instance()->mainWindow()) {
|
||||
((ui::MenuItem*)child)->validateItem();
|
||||
item->setEnabled(child->isEnabled());
|
||||
item->setChecked(child->isSelected());
|
||||
}
|
||||
else {
|
||||
// Disable item when there are a modal window
|
||||
item->setEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (appMenuItem && appMenuItem->getCommand()) {
|
||||
fill_menu_item_info_with_key(
|
||||
info,
|
||||
appMenuItem->getCommand()->id().c_str(),
|
||||
appMenuItem->getParams());
|
||||
}
|
||||
}
|
||||
else {
|
||||
ASSERT(false); // Unsupported menu item type
|
||||
continue;
|
||||
}
|
||||
|
||||
she::MenuItem* osItem = menus->createMenuItem(info);
|
||||
if (osItem) {
|
||||
osMenu->addItem(osItem);
|
||||
if (appMenuItem)
|
||||
appMenuItem->setNativeMenuItem(osItem);
|
||||
|
||||
if (child->type() == ui::kMenuItemWidget &&
|
||||
((ui::MenuItem*)child)->hasSubmenu()) {
|
||||
she::Menu* osSubmenu = menus->createMenu();
|
||||
createNativeSubmenus(osSubmenu, ((ui::MenuItem*)child)->getSubmenu());
|
||||
osItem->setSubmenu(osSubmenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -17,6 +17,11 @@
|
||||
class TiXmlElement;
|
||||
class TiXmlHandle;
|
||||
|
||||
namespace she {
|
||||
class Menu;
|
||||
class Shortcut;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class Key;
|
||||
class Command;
|
||||
@ -32,6 +37,8 @@ namespace app {
|
||||
public:
|
||||
static AppMenus* instance();
|
||||
|
||||
~AppMenus();
|
||||
|
||||
void reload();
|
||||
void initTheme();
|
||||
|
||||
@ -52,6 +59,7 @@ namespace app {
|
||||
Menu* getInkPopupMenu() { return m_inkPopupMenu; }
|
||||
|
||||
void applyShortcutToMenuitemsWithCommand(Command* command, const Params& params, Key* key);
|
||||
void syncNativeMenuItemKeyShortcuts();
|
||||
|
||||
private:
|
||||
Menu* loadMenuById(TiXmlHandle& handle, const char *id);
|
||||
@ -59,10 +67,14 @@ namespace app {
|
||||
Widget* convertXmlelemToMenuitem(TiXmlElement* elem);
|
||||
Widget* createInvalidVersionMenuitem();
|
||||
void applyShortcutToMenuitemsWithCommand(Menu* menu, Command* command, const Params& params, Key* key);
|
||||
void syncNativeMenuItemKeyShortcuts(Menu* menu);
|
||||
void updateMenusList();
|
||||
void createNativeMenus();
|
||||
void createNativeSubmenus(she::Menu* osMenu, const ui::Menu* uiMenu);
|
||||
|
||||
base::UniquePtr<Menu> m_rootMenu;
|
||||
MenuItem* m_recentListMenuitem;
|
||||
MenuItem* m_helpMenuitem;
|
||||
base::UniquePtr<Menu> m_tabPopupMenu;
|
||||
base::UniquePtr<Menu> m_documentTabPopupMenu;
|
||||
base::UniquePtr<Menu> m_layerPopupMenu;
|
||||
@ -75,8 +87,11 @@ namespace app {
|
||||
base::UniquePtr<Menu> m_inkPopupMenu;
|
||||
obs::scoped_connection m_recentFilesConn;
|
||||
std::vector<Menu*> m_menus;
|
||||
she::Menu* m_osMenu;
|
||||
};
|
||||
|
||||
she::Shortcut get_os_shortcut_from_key(Key* key);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
||||
|
@ -690,6 +690,8 @@ void KeyboardShortcutsCommand::onExecute(Context* context)
|
||||
else {
|
||||
window.restoreKeys();
|
||||
}
|
||||
|
||||
AppMenus::instance()->syncNativeMenuItemKeyShortcuts();
|
||||
}
|
||||
|
||||
Command* CommandFactory::createKeyboardShortcutsCommand()
|
||||
|
@ -429,23 +429,14 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
|
||||
Command* command = key->command();
|
||||
|
||||
// Commands are executed only when the main window is
|
||||
// the current window running at foreground.
|
||||
for (auto childWidget : children()) {
|
||||
Window* child = static_cast<Window*>(childWidget);
|
||||
|
||||
// There are a foreground window executing?
|
||||
if (child->isForeground()) {
|
||||
break;
|
||||
}
|
||||
// Is it the desktop and the top-window=
|
||||
else if (child->isDesktop() && child == App::instance()->mainWindow()) {
|
||||
// the current window running.
|
||||
if (getForegroundWindow() == App::instance()->mainWindow()) {
|
||||
// OK, so we can execute the command represented
|
||||
// by the pressed-key in the message...
|
||||
UIContext::instance()->executeCommand(
|
||||
command, key->params());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,13 @@
|
||||
|
||||
#include "app/ui/app_menuitem.h"
|
||||
|
||||
#include "app/app_menus.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/ui/keyboard_shortcuts.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "she/menus.h"
|
||||
#include "ui/accelerator.h"
|
||||
#include "ui/menu.h"
|
||||
#include "ui/message.h"
|
||||
@ -34,12 +36,30 @@ Params AppMenuItem::s_contextParams;
|
||||
|
||||
AppMenuItem::AppMenuItem(const char* text, Command* command, const Params& params)
|
||||
: MenuItem(text)
|
||||
, m_key(NULL)
|
||||
, m_key(nullptr)
|
||||
, m_command(command)
|
||||
, m_params(params)
|
||||
, m_nativeMenuItem(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void AppMenuItem::setKey(Key* key)
|
||||
{
|
||||
m_key = key;
|
||||
syncNativeMenuItemKeyShortcut();
|
||||
}
|
||||
|
||||
void AppMenuItem::setNativeMenuItem(she::MenuItem* nativeMenuItem)
|
||||
{
|
||||
m_nativeMenuItem = nativeMenuItem;
|
||||
}
|
||||
|
||||
void AppMenuItem::syncNativeMenuItemKeyShortcut()
|
||||
{
|
||||
if (m_nativeMenuItem)
|
||||
m_nativeMenuItem->setShortcut(get_os_shortcut_from_key(m_key));
|
||||
}
|
||||
|
||||
// static
|
||||
void AppMenuItem::setContextParams(const Params& params)
|
||||
{
|
||||
@ -54,28 +74,6 @@ bool AppMenuItem::onProcessMessage(Message* msg)
|
||||
// disable the menu (the keyboard shortcuts are processed by "manager_msg_proc")
|
||||
setEnabled(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (msg->type() == kOpenMessage ||
|
||||
msg->type() == kOpenMenuItemMessage) {
|
||||
// Update the context flags after opening the menuitem's
|
||||
// submenu to update the "enabled" flag of each command
|
||||
// correctly.
|
||||
Context* context = UIContext::instance();
|
||||
context->updateFlags();
|
||||
|
||||
if (m_command) {
|
||||
Params params = m_params;
|
||||
if (!s_contextParams.empty())
|
||||
params |= s_contextParams;
|
||||
|
||||
m_command->loadParams(params);
|
||||
|
||||
setEnabled(m_command->isEnabled(context));
|
||||
setSelected(m_command->isChecked(context));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return MenuItem::onProcessMessage(msg);
|
||||
@ -113,12 +111,35 @@ void AppMenuItem::onClick()
|
||||
if (!s_contextParams.empty())
|
||||
params |= s_contextParams;
|
||||
|
||||
// Load parameters to call Command::isEnabled, so we can check if
|
||||
// the command is enabled with this parameters.
|
||||
m_command->loadParams(params);
|
||||
|
||||
UIContext* context = UIContext::instance();
|
||||
if (m_command->isEnabled(context))
|
||||
if (m_command->isEnabled(context)) {
|
||||
context->executeCommand(m_command, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppMenuItem::onValidate()
|
||||
{
|
||||
// Update the context flags after opening the menuitem's
|
||||
// submenu to update the "enabled" flag of each command
|
||||
// correctly.
|
||||
Context* context = UIContext::instance();
|
||||
context->updateFlags();
|
||||
|
||||
if (m_command) {
|
||||
Params params = m_params;
|
||||
if (!s_contextParams.empty())
|
||||
params |= s_contextParams;
|
||||
|
||||
m_command->loadParams(params);
|
||||
|
||||
setEnabled(m_command->isEnabled(context));
|
||||
setSelected(m_command->isChecked(context));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -11,6 +11,10 @@
|
||||
#include "app/commands/params.h"
|
||||
#include "ui/menu.h"
|
||||
|
||||
namespace she {
|
||||
class MenuItem;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class Key;
|
||||
class Command;
|
||||
@ -25,22 +29,28 @@ namespace app {
|
||||
AppMenuItem(const char* text, Command* command = nullptr, const Params& params = Params());
|
||||
|
||||
Key* key() { return m_key; }
|
||||
void setKey(Key* key) { m_key = key; }
|
||||
void setKey(Key* key);
|
||||
|
||||
Command* getCommand() { return m_command; }
|
||||
const Params& getParams() const { return m_params; }
|
||||
|
||||
she::MenuItem* nativeMenuItem() { return m_nativeMenuItem; }
|
||||
void setNativeMenuItem(she::MenuItem* nativeMenuItem);
|
||||
void syncNativeMenuItemKeyShortcut();
|
||||
|
||||
static void setContextParams(const Params& params);
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(ui::Message* msg) override;
|
||||
void onSizeHint(ui::SizeHintEvent& ev) override;
|
||||
void onClick() override;
|
||||
void onValidate() override;
|
||||
|
||||
private:
|
||||
Key* m_key;
|
||||
Command* m_command;
|
||||
Params m_params;
|
||||
she::MenuItem* m_nativeMenuItem;
|
||||
|
||||
static Params s_contextParams;
|
||||
};
|
||||
|
@ -227,6 +227,7 @@ endif()
|
||||
if(APPLE)
|
||||
list(APPEND SHE_SOURCES
|
||||
osx/logger.mm
|
||||
osx/menus.mm
|
||||
osx/native_dialogs.mm)
|
||||
endif()
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
# SHE - Simplified Hardware Entry-point
|
||||
|
||||
SHE is an abstraction layer to access in different way to the
|
||||
hardware/operating system. It will use Allegro 4, Allegro 5 or SDL
|
||||
libraries, but will be easily portable to other back-ends.
|
||||
`she` is an abstraction layer to access in different way to the
|
||||
hardware/operating system. It can be implemented with different
|
||||
back-ends:
|
||||
|
||||
* Previous version were using Allegro 4 (it still uses Allegro 4 on Linux)
|
||||
* Now we use our own implementation on Windows and macOS to handle
|
||||
events, and [Skia](https://skia.org/) to render graphics.
|
||||
|
@ -11,6 +11,7 @@
|
||||
#ifdef _WIN32
|
||||
#include "she/win/native_dialogs.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "she/osx/menus.h"
|
||||
#include "she/osx/native_dialogs.h"
|
||||
#elif defined(ASEPRITE_WITH_GTK_FILE_DIALOG_SUPPORT) && defined(__linux__)
|
||||
#include "she/gtk/native_dialogs.h"
|
||||
@ -33,11 +34,13 @@ Logger* getOsxLogger();
|
||||
class CommonSystem : public System {
|
||||
public:
|
||||
CommonSystem()
|
||||
: m_nativeDialogs(nullptr) {
|
||||
: m_nativeDialogs(nullptr)
|
||||
, m_menus(nullptr) {
|
||||
}
|
||||
|
||||
~CommonSystem() {
|
||||
delete m_nativeDialogs;
|
||||
delete m_menus;
|
||||
}
|
||||
|
||||
void dispose() override {
|
||||
@ -52,6 +55,14 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
Menus* menus() override {
|
||||
#ifdef __APPLE__
|
||||
if (!m_menus)
|
||||
m_menus = new MenusOSX();
|
||||
#endif
|
||||
return m_menus;
|
||||
}
|
||||
|
||||
NativeDialogs* nativeDialogs() override {
|
||||
#ifdef _WIN32
|
||||
if (!m_nativeDialogs)
|
||||
@ -103,6 +114,7 @@ public:
|
||||
|
||||
private:
|
||||
NativeDialogs* m_nativeDialogs;
|
||||
Menus* m_menus;
|
||||
base::UniquePtr<ft::Lib> m_ft;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2012-2016 David Capello
|
||||
// Copyright (C) 2012-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -22,7 +22,9 @@ namespace she {
|
||||
};
|
||||
|
||||
// TODO These are virtual key code (not scancodes), we should rename
|
||||
// it to KeyCodes or something similar.
|
||||
// it to KeyCodes or use Unicode directly as on macOS (some
|
||||
// special keys like F1, arrow keys, etc. have a special
|
||||
// unicode value).
|
||||
enum KeyScancode {
|
||||
kKeyNil = 0,
|
||||
kKeyA = 1,
|
||||
@ -150,7 +152,7 @@ namespace she {
|
||||
kKeyLWin = 120,
|
||||
kKeyRWin = 121,
|
||||
kKeyMenu = 122,
|
||||
kKeyCommand = 123, // macOS - TODO This should be inside the modifiers range
|
||||
kKeyCommand = 123,
|
||||
kKeyScrLock = 124,
|
||||
kKeyNumLock = 125,
|
||||
kKeyCapsLock = 126,
|
||||
|
92
src/she/menus.h
Normal file
92
src/she/menus.h
Normal file
@ -0,0 +1,92 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef SHE_MENUS_H_INCLUDED
|
||||
#define SHE_MENUS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "she/keys.h"
|
||||
#include "she/shortcut.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace she {
|
||||
class MenuItem;
|
||||
|
||||
struct MenuItemInfo {
|
||||
enum Type {
|
||||
Normal,
|
||||
Separator
|
||||
};
|
||||
|
||||
enum Action {
|
||||
UserDefined,
|
||||
|
||||
// macOS standard commands
|
||||
Hide,
|
||||
HideOthers,
|
||||
ShowAll,
|
||||
Quit,
|
||||
Minimize,
|
||||
Zoom,
|
||||
};
|
||||
|
||||
Type type;
|
||||
Action action;
|
||||
std::string text;
|
||||
Shortcut shortcut;
|
||||
std::function<void()> execute;
|
||||
std::function<void(she::MenuItem*)> validate;
|
||||
|
||||
explicit MenuItemInfo(const Type type = Normal,
|
||||
const Action action = UserDefined)
|
||||
: type(type)
|
||||
, action(action) {
|
||||
}
|
||||
|
||||
explicit MenuItemInfo(const char* text,
|
||||
const Action action = UserDefined)
|
||||
: type(Normal)
|
||||
, action(action)
|
||||
, text(text) {
|
||||
}
|
||||
};
|
||||
|
||||
class Menu;
|
||||
class MenuItem;
|
||||
class Menus;
|
||||
|
||||
class MenuItem {
|
||||
public:
|
||||
virtual ~MenuItem() { }
|
||||
virtual void dispose() = 0;
|
||||
virtual void setText(const std::string& text) = 0;
|
||||
virtual void setSubmenu(Menu* submenu) = 0;
|
||||
virtual void setEnabled(bool state) = 0;
|
||||
virtual void setChecked(bool state) = 0;
|
||||
virtual void setShortcut(const Shortcut& shortcut) = 0;
|
||||
};
|
||||
|
||||
class Menu {
|
||||
public:
|
||||
virtual ~Menu() { }
|
||||
virtual void dispose() = 0;
|
||||
virtual void addItem(MenuItem* item) = 0;
|
||||
virtual void insertItem(const int index, MenuItem* item) = 0;
|
||||
};
|
||||
|
||||
class Menus {
|
||||
public:
|
||||
virtual ~Menus() { }
|
||||
virtual Menu* createMenu() = 0;
|
||||
virtual MenuItem* createMenuItem(const MenuItemInfo& info) = 0;
|
||||
virtual void setAppMenu(Menu* menu) = 0;
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2012-2016 David Capello
|
||||
// Copyright (C) 2012-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -17,6 +17,8 @@
|
||||
- (void)applicationWillResignActive:(NSNotification*)notification;
|
||||
- (void)applicationDidBecomeActive:(NSNotification*)notification;
|
||||
- (BOOL)application:(NSApplication*)app openFiles:(NSArray*)filenames;
|
||||
- (void)executeMenuItem:(id)sender;
|
||||
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem;
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2012-2016 David Capello
|
||||
// Copyright (C) 2012-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -20,11 +20,18 @@
|
||||
#include "she/osx/view.h"
|
||||
#include "she/system.h"
|
||||
|
||||
@protocol OSXValidateMenuItemProtocol
|
||||
- (void)validateMenuItem;
|
||||
@end
|
||||
|
||||
@implementation OSXAppDelegate
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
|
||||
{
|
||||
return NSTerminateNow;
|
||||
she::Event ev;
|
||||
ev.setType(she::Event::CloseDisplay);
|
||||
she::queue_event(ev);
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)app
|
||||
@ -34,9 +41,6 @@
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification*)notification
|
||||
{
|
||||
she::Event ev;
|
||||
ev.setType(she::Event::CloseDisplay);
|
||||
she::queue_event(ev);
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification*)notification
|
||||
@ -61,4 +65,19 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)executeMenuItem:(id)sender
|
||||
{
|
||||
[sender executeMenuItem:sender];
|
||||
}
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
|
||||
{
|
||||
if ([menuItem respondsToSelector:@selector(validateMenuItem)]) {
|
||||
[((id<OSXValidateMenuItemProtocol>)menuItem) validateMenuItem];
|
||||
return menuItem.enabled;
|
||||
}
|
||||
else
|
||||
return [super validateMenuItem:menuItem];
|
||||
}
|
||||
|
||||
@end
|
||||
|
25
src/she/osx/menus.h
Normal file
25
src/she/osx/menus.h
Normal file
@ -0,0 +1,25 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef SHE_OSX_MENUS_H_INCLUDED
|
||||
#define SHE_OSX_MENUS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "she/menus.h"
|
||||
|
||||
namespace she {
|
||||
|
||||
class MenusOSX : public Menus {
|
||||
public:
|
||||
MenusOSX();
|
||||
Menu* createMenu() override;
|
||||
MenuItem* createMenuItem(const MenuItemInfo& info) override;
|
||||
void setAppMenu(Menu* menu) override;
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
#endif
|
288
src/she/osx/menus.mm
Normal file
288
src/she/osx/menus.mm
Normal file
@ -0,0 +1,288 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/debug.h"
|
||||
#include "base/string.h"
|
||||
#include "she/osx/menus.h"
|
||||
#include "she/shortcut.h"
|
||||
|
||||
namespace she {
|
||||
class MenuItemOSX;
|
||||
}
|
||||
|
||||
@interface OSXNSMenu : NSMenu
|
||||
- (BOOL)performKeyEquivalent:(NSEvent*)event;
|
||||
@end
|
||||
|
||||
@interface OSXNSMenuItem : NSMenuItem {
|
||||
she::MenuItemOSX* original;
|
||||
}
|
||||
+ (OSXNSMenuItem*)alloc:(she::MenuItemOSX*)original;
|
||||
- (void)executeMenuItem:(id)sender;
|
||||
- (void)validateMenuItem;
|
||||
@end
|
||||
|
||||
namespace she {
|
||||
|
||||
extern bool g_keyEquivalentUsed;
|
||||
|
||||
class MenuItemOSX : public MenuItem {
|
||||
public:
|
||||
MenuItemOSX(const MenuItemInfo& info);
|
||||
void dispose() override;
|
||||
void setText(const std::string& text) override;
|
||||
void setSubmenu(Menu* submenu) override;
|
||||
void setEnabled(bool state) override;
|
||||
void setChecked(bool state) override;
|
||||
void setShortcut(const Shortcut& shortcut) override;
|
||||
NSMenuItem* handle() { return m_handle; }
|
||||
|
||||
// Called by OSXNSMenuItem.executeMenuItem
|
||||
void execute();
|
||||
void validate();
|
||||
|
||||
private:
|
||||
void syncTitle();
|
||||
NSMenuItem* m_handle;
|
||||
Menu* m_submenu;
|
||||
std::function<void()> m_execute;
|
||||
std::function<void(she::MenuItem*)> m_validate;
|
||||
};
|
||||
|
||||
class MenuOSX : public Menu {
|
||||
public:
|
||||
MenuOSX();
|
||||
void dispose() override;
|
||||
void addItem(MenuItem* item) override;
|
||||
void insertItem(const int index, MenuItem* item) override;
|
||||
NSMenu* handle() { return m_handle; }
|
||||
private:
|
||||
NSMenu* m_handle;
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
@implementation OSXNSMenu
|
||||
- (BOOL)performKeyEquivalent:(NSEvent*)event
|
||||
{
|
||||
BOOL result = [super performKeyEquivalent:event];
|
||||
if (result)
|
||||
she::g_keyEquivalentUsed = true;
|
||||
return result;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation OSXNSMenuItem
|
||||
+ (OSXNSMenuItem*)alloc:(she::MenuItemOSX*)original
|
||||
{
|
||||
OSXNSMenuItem* item = [super alloc];
|
||||
item->original = original;
|
||||
return item;
|
||||
}
|
||||
- (void)executeMenuItem:(id)sender
|
||||
{
|
||||
original->execute();
|
||||
}
|
||||
- (void)validateMenuItem
|
||||
{
|
||||
original->validate();
|
||||
}
|
||||
@end
|
||||
|
||||
namespace she {
|
||||
|
||||
MenuItemOSX::MenuItemOSX(const MenuItemInfo& info)
|
||||
: m_handle(nullptr)
|
||||
, m_submenu(nullptr)
|
||||
{
|
||||
switch (info.type) {
|
||||
|
||||
case MenuItemInfo::Normal: {
|
||||
SEL sel = nil;
|
||||
id target = nil;
|
||||
switch (info.action) {
|
||||
|
||||
case MenuItemInfo::UserDefined:
|
||||
sel = @selector(executeMenuItem:);
|
||||
|
||||
// TODO this is strange, it doesn't work, we receive the
|
||||
// message in OSXAppDelegate anyway. So
|
||||
// OSXAppDelegate.executeMenuItem: will redirect the message
|
||||
// to OSXNSMenuItem.executeMenuItem:
|
||||
target = m_handle;
|
||||
break;
|
||||
|
||||
case MenuItemInfo::Hide:
|
||||
sel = @selector(hide:);
|
||||
break;
|
||||
|
||||
case MenuItemInfo::HideOthers:
|
||||
sel = @selector(hideOtherApplications:);
|
||||
break;
|
||||
|
||||
case MenuItemInfo::ShowAll:
|
||||
sel = @selector(unhideAllApplications:);
|
||||
break;
|
||||
|
||||
case MenuItemInfo::Quit:
|
||||
sel = @selector(terminate:);
|
||||
break;
|
||||
|
||||
case MenuItemInfo::Minimize:
|
||||
sel = @selector(performMiniaturize:);
|
||||
break;
|
||||
|
||||
case MenuItemInfo::Zoom:
|
||||
sel = @selector(performZoom:);
|
||||
break;
|
||||
}
|
||||
|
||||
m_handle =
|
||||
[[OSXNSMenuItem alloc:this]
|
||||
initWithTitle:[NSString stringWithUTF8String:info.text.c_str()]
|
||||
action:sel
|
||||
keyEquivalent:@""];
|
||||
|
||||
m_handle.target = target;
|
||||
m_execute = info.execute;
|
||||
m_validate = info.validate;
|
||||
|
||||
if (!info.shortcut.isEmpty())
|
||||
setShortcut(info.shortcut);
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuItemInfo::Separator:
|
||||
m_handle = [NSMenuItem separatorItem];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuItemOSX::dispose()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void MenuItemOSX::setText(const std::string& text)
|
||||
{
|
||||
[m_handle setTitle:[NSString stringWithUTF8String:text.c_str()]];
|
||||
syncTitle();
|
||||
}
|
||||
|
||||
void MenuItemOSX::setSubmenu(Menu* submenu)
|
||||
{
|
||||
m_submenu = submenu;
|
||||
if (submenu) {
|
||||
[m_handle setSubmenu:((MenuOSX*)submenu)->handle()];
|
||||
syncTitle();
|
||||
}
|
||||
else
|
||||
[m_handle setSubmenu:nil];
|
||||
}
|
||||
|
||||
void MenuItemOSX::setEnabled(bool state)
|
||||
{
|
||||
m_handle.enabled = state;
|
||||
}
|
||||
|
||||
void MenuItemOSX::setChecked(bool state)
|
||||
{
|
||||
if (state)
|
||||
m_handle.state = NSOnState;
|
||||
else
|
||||
m_handle.state = NSOffState;
|
||||
}
|
||||
|
||||
void MenuItemOSX::setShortcut(const Shortcut& shortcut)
|
||||
{
|
||||
KeyModifiers mods = shortcut.modifiers();
|
||||
NSEventModifierFlags nsFlags = 0;
|
||||
if (mods & kKeyShiftModifier) nsFlags |= NSEventModifierFlagShift;
|
||||
if (mods & kKeyCtrlModifier) nsFlags |= NSEventModifierFlagControl;
|
||||
if (mods & kKeyAltModifier) nsFlags |= NSEventModifierFlagOption;
|
||||
if (mods & kKeyCmdModifier) nsFlags |= NSEventModifierFlagCommand;
|
||||
|
||||
NSString* keyStr;
|
||||
if (shortcut.unicode()) {
|
||||
unichar chr = shortcut.unicode();
|
||||
keyStr = [NSString stringWithCharacters:&chr length:1];
|
||||
}
|
||||
else
|
||||
keyStr = @"";
|
||||
|
||||
m_handle.keyEquivalent = keyStr;
|
||||
m_handle.keyEquivalentModifierMask = nsFlags;
|
||||
}
|
||||
|
||||
void MenuItemOSX::execute()
|
||||
{
|
||||
if (m_execute)
|
||||
m_execute();
|
||||
}
|
||||
|
||||
void MenuItemOSX::validate()
|
||||
{
|
||||
if (m_validate)
|
||||
m_validate(this);
|
||||
}
|
||||
|
||||
void MenuItemOSX::syncTitle()
|
||||
{
|
||||
// On macOS the submenu title is the one that is displayed in the
|
||||
// UI instead of the MenuItem title (so here we copy the menu item
|
||||
// title to the submenu title)
|
||||
if (m_submenu)
|
||||
[((MenuOSX*)m_submenu)->handle() setTitle:m_handle.title];
|
||||
}
|
||||
|
||||
MenuOSX::MenuOSX()
|
||||
{
|
||||
m_handle = [[OSXNSMenu alloc] init];
|
||||
}
|
||||
|
||||
void MenuOSX::dispose()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void MenuOSX::addItem(MenuItem* item)
|
||||
{
|
||||
ASSERT(item);
|
||||
[m_handle addItem:((MenuItemOSX*)item)->handle()];
|
||||
}
|
||||
|
||||
void MenuOSX::insertItem(const int index, MenuItem* item)
|
||||
{
|
||||
ASSERT(item);
|
||||
[m_handle insertItem:((MenuItemOSX*)item)->handle()
|
||||
atIndex:index];
|
||||
}
|
||||
|
||||
MenusOSX::MenusOSX()
|
||||
{
|
||||
}
|
||||
|
||||
Menu* MenusOSX::createMenu()
|
||||
{
|
||||
return new MenuOSX;
|
||||
}
|
||||
|
||||
MenuItem* MenusOSX::createMenuItem(const MenuItemInfo& info)
|
||||
{
|
||||
return new MenuItemOSX(info);
|
||||
}
|
||||
|
||||
void MenusOSX::setAppMenu(Menu* menu)
|
||||
{
|
||||
if (menu)
|
||||
[NSApp setMainMenu:((MenuOSX*)menu)->handle()];
|
||||
else
|
||||
[NSApp setMainMenu:nil];
|
||||
}
|
||||
|
||||
} // namespace she
|
@ -4,6 +4,8 @@
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#define KEY_TRACE(...)
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
@ -20,6 +22,12 @@
|
||||
|
||||
namespace she {
|
||||
|
||||
// Global variable used between View and OSXNSMenu to check if the
|
||||
// keyDown: event was used by a key equivalent in the menu.
|
||||
//
|
||||
// TODO I'm not proud of this, but it does the job
|
||||
bool g_keyEquivalentUsed = false;
|
||||
|
||||
bool osx_is_key_pressed(KeyScancode scancode);
|
||||
|
||||
namespace {
|
||||
@ -167,8 +175,14 @@ using namespace she;
|
||||
|
||||
- (void)keyDown:(NSEvent*)event
|
||||
{
|
||||
g_keyEquivalentUsed = false;
|
||||
[super keyDown:event];
|
||||
|
||||
// If a key equivalent used the keyDown event, we don't generate
|
||||
// this she::KeyDown event.
|
||||
if (g_keyEquivalentUsed)
|
||||
return;
|
||||
|
||||
KeyScancode scancode = scancode_from_nsevent(event);
|
||||
Event ev;
|
||||
ev.setType(Event::KeyDown);
|
||||
@ -212,6 +226,10 @@ using namespace she;
|
||||
}
|
||||
}
|
||||
|
||||
KEY_TRACE("View keyDown: unicode=%d (%c) scancode=%d modifiers=%d\n",
|
||||
ev.unicodeChar(), ev.unicodeChar(),
|
||||
ev.scancode(), ev.modifiers());
|
||||
|
||||
if (sendMsg)
|
||||
queue_event(ev);
|
||||
}
|
||||
|
@ -79,13 +79,13 @@ static KeyScancode from_char_to_scancode(int chr)
|
||||
kKey7, // 55 = 37 = 7
|
||||
kKey8, // 56 = 38 = 8
|
||||
kKey9, // 57 = 39 = 9
|
||||
kKeyNil, // 58 = 3A = :
|
||||
kKeyColon, // 58 = 3A = :
|
||||
kKeySemicolon, // 59 = 3B = ;
|
||||
kKeyNil, // 60 = 3C = <
|
||||
kKeyEquals, // 61 = 3D = =
|
||||
kKeyNil, // 62 = 3E = >
|
||||
kKeyNil, // 63 = 3F = ?
|
||||
kKeyNil, // 64 = 40 = @
|
||||
kKeyAt, // 64 = 40 = @
|
||||
kKeyA, // 65 = 41 = A
|
||||
kKeyB, // 66 = 42 = B
|
||||
kKeyC, // 67 = 43 = C
|
||||
@ -115,9 +115,9 @@ static KeyScancode from_char_to_scancode(int chr)
|
||||
kKeyOpenbrace, // 91 = 5B = [
|
||||
kKeyBackslash, // 92 = 5C = backslash
|
||||
kKeyClosebrace, // 93 = 5D = ]
|
||||
kKeyNil, // 94 = 5E = ^
|
||||
kKeyCircumflex, // 94 = 5E = ^
|
||||
kKeyNil, // 95 = 5F = _
|
||||
kKeyNil, // 96 = 60 = `
|
||||
kKeyBackquote, // 96 = 60 = `
|
||||
kKeyA, // 97 = 61 = a
|
||||
kKeyB, // 98 = 62 = b
|
||||
kKeyC, // 99 = 63 = c
|
||||
@ -147,7 +147,7 @@ static KeyScancode from_char_to_scancode(int chr)
|
||||
kKeyOpenbrace, // 123 = 7B = {
|
||||
kKeyBackslash, // 124 = 7C = |
|
||||
kKeyClosebrace, // 125 = 7D = }
|
||||
kKeyNil, // 126 = 7E = ~
|
||||
kKeyTilde, // 126 = 7E = ~
|
||||
kKeyNil, // 127 = 7F = DEL
|
||||
};
|
||||
|
||||
|
@ -53,6 +53,10 @@ using namespace she;
|
||||
[self center];
|
||||
[self makeKeyAndOrderFront:self];
|
||||
|
||||
// Hide the "View > Show Tab Bar" menu item
|
||||
if ([self respondsToSelector:@selector(setTabbingMode:)])
|
||||
[self setTabbingMode:NSWindowTabbingModeDisallowed];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2012-2016 David Capello
|
||||
// Copyright (C) 2012-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -8,13 +8,24 @@
|
||||
#define SHE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "she/capabilities.h"
|
||||
#include "she/display.h"
|
||||
#include "she/display_handle.h"
|
||||
#include "she/draw_text.h"
|
||||
#include "she/error.h"
|
||||
#include "she/event.h"
|
||||
#include "she/event_queue.h"
|
||||
#include "she/font.h"
|
||||
#include "she/keys.h"
|
||||
#include "she/logger.h"
|
||||
#include "she/menus.h"
|
||||
#include "she/native_cursor.h"
|
||||
#include "she/native_dialogs.h"
|
||||
#include "she/pointer_type.h"
|
||||
#include "she/scoped_handle.h"
|
||||
#include "she/shortcut.h"
|
||||
#include "she/surface.h"
|
||||
#include "she/surface_format.h"
|
||||
#include "she/system.h"
|
||||
|
||||
#endif
|
||||
|
35
src/she/shortcut.h
Normal file
35
src/she/shortcut.h
Normal file
@ -0,0 +1,35 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef SHE_SHORTCUT_H_INCLUDED
|
||||
#define SHE_SHORTCUT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "she/keys.h"
|
||||
|
||||
namespace she {
|
||||
|
||||
class Shortcut {
|
||||
public:
|
||||
Shortcut(int unicode = 0,
|
||||
KeyModifiers modifiers = kKeyNoneModifier)
|
||||
: m_unicode(unicode)
|
||||
, m_modifiers(modifiers) {
|
||||
}
|
||||
|
||||
int unicode() const { return m_unicode; }
|
||||
KeyModifiers modifiers() const { return m_modifiers; }
|
||||
|
||||
bool isEmpty() const { return m_unicode == 0; }
|
||||
|
||||
private:
|
||||
int m_unicode;
|
||||
KeyModifiers m_modifiers;
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2012-2016 David Capello
|
||||
// Copyright (C) 2012-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -20,6 +20,7 @@ namespace she {
|
||||
class EventQueue;
|
||||
class Font;
|
||||
class Logger;
|
||||
class Menus;
|
||||
class NativeDialogs;
|
||||
class Surface;
|
||||
|
||||
@ -37,6 +38,7 @@ namespace she {
|
||||
virtual void finishLaunching() = 0;
|
||||
virtual Capabilities capabilities() const = 0;
|
||||
virtual Logger* logger() = 0;
|
||||
virtual Menus* menus() = 0;
|
||||
virtual NativeDialogs* nativeDialogs() = 0;
|
||||
virtual EventQueue* eventQueue() = 0;
|
||||
virtual bool gpuAcceleration() const = 0;
|
||||
|
@ -721,7 +721,12 @@ bool MenuItem::onProcessMessage(Message* msg)
|
||||
break;
|
||||
|
||||
default:
|
||||
if (msg->type() == kOpenMenuItemMessage) {
|
||||
if (msg->type() == kOpenMessage) {
|
||||
validateItem();
|
||||
}
|
||||
else if (msg->type() == kOpenMenuItemMessage) {
|
||||
validateItem();
|
||||
|
||||
MenuBaseData* base = get_base(this);
|
||||
bool select_first = static_cast<OpenMenuItemMessage*>(msg)->select_first();
|
||||
|
||||
@ -895,6 +900,12 @@ void MenuItem::onClick()
|
||||
Click();
|
||||
}
|
||||
|
||||
void MenuItem::onValidate()
|
||||
{
|
||||
// Here the user can customize the automatic validation of the menu
|
||||
// item before it's shown.
|
||||
}
|
||||
|
||||
void MenuItem::onSizeHint(SizeHintEvent& ev)
|
||||
{
|
||||
Size size(0, 0);
|
||||
@ -1220,6 +1231,11 @@ void MenuItem::executeClick()
|
||||
Manager::getDefault()->enqueueMessage(msg);
|
||||
}
|
||||
|
||||
void MenuItem::validateItem()
|
||||
{
|
||||
onValidate();
|
||||
}
|
||||
|
||||
static MenuItem* check_for_letter(Menu* menu, const KeyMessage* keymsg)
|
||||
{
|
||||
for (auto child : menu->children()) {
|
||||
|
@ -123,6 +123,9 @@ namespace ui {
|
||||
return m_submenu_menubox;
|
||||
}
|
||||
|
||||
void executeClick();
|
||||
void validateItem();
|
||||
|
||||
// Fired when the menu item is clicked.
|
||||
obs::signal<void()> Click;
|
||||
|
||||
@ -132,6 +135,7 @@ namespace ui {
|
||||
void onPaint(PaintEvent& ev) override;
|
||||
void onSizeHint(SizeHintEvent& ev) override;
|
||||
virtual void onClick();
|
||||
virtual void onValidate();
|
||||
|
||||
bool inBar();
|
||||
|
||||
@ -140,7 +144,6 @@ namespace ui {
|
||||
void closeSubmenu(bool last_of_close_chain);
|
||||
void startTimer();
|
||||
void stopTimer();
|
||||
void executeClick();
|
||||
|
||||
bool m_highlighted; // Is it highlighted?
|
||||
Menu* m_submenu; // The sub-menu
|
||||
|
Loading…
x
Reference in New Issue
Block a user