2015-02-12 12:16:25 -03:00
|
|
|
// Aseprite
|
2022-04-13 22:46:48 -03:00
|
|
|
// Copyright (C) 2019-2022 Igara Studio S.A.
|
2018-06-27 11:32:24 -03:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 12:16:25 -03:00
|
|
|
//
|
2016-08-26 17:02:58 -03:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2007-09-18 23:57:02 +00:00
|
|
|
#include "config.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "app/app_menus.h"
|
|
|
|
|
|
|
|
#include "app/app.h"
|
|
|
|
#include "app/commands/command.h"
|
|
|
|
#include "app/commands/commands.h"
|
|
|
|
#include "app/commands/params.h"
|
|
|
|
#include "app/console.h"
|
2022-04-13 22:46:48 -03:00
|
|
|
#include "app/extensions.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/gui_xml.h"
|
2017-10-11 18:02:38 -03:00
|
|
|
#include "app/i18n/strings.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/recent_files.h"
|
2014-10-29 11:58:03 -03:00
|
|
|
#include "app/resource_finder.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/tools/tool_box.h"
|
|
|
|
#include "app/ui/app_menuitem.h"
|
2014-10-29 11:58:03 -03:00
|
|
|
#include "app/ui/keyboard_shortcuts.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/main_window.h"
|
2017-09-01 13:32:23 -03:00
|
|
|
#include "app/ui_context.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/util/filetoks.h"
|
2014-10-29 11:58:03 -03:00
|
|
|
#include "base/fs.h"
|
2017-09-01 13:32:23 -03:00
|
|
|
#include "base/string.h"
|
2020-03-16 10:29:58 -03:00
|
|
|
#include "fmt/format.h"
|
2018-08-09 12:58:43 -03:00
|
|
|
#include "os/menus.h"
|
|
|
|
#include "os/system.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "ui/ui.h"
|
2020-03-16 10:29:58 -03:00
|
|
|
#include "ver/info.h"
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2009-12-16 23:24:57 +00:00
|
|
|
#include "tinyxml.h"
|
2014-09-21 11:59:39 -03:00
|
|
|
|
2017-09-26 16:39:33 -03:00
|
|
|
#include <cctype>
|
2014-09-21 11:59:39 -03:00
|
|
|
#include <cstring>
|
2020-04-08 17:50:17 -03:00
|
|
|
#include <string>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
#define MENUS_TRACE(...) // TRACEARGS
|
2009-12-16 23:24:57 +00:00
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
namespace app {
|
|
|
|
|
2012-06-17 22:02:54 -03:00
|
|
|
using namespace ui;
|
|
|
|
|
2017-10-03 11:50:57 -03:00
|
|
|
namespace {
|
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
// TODO Move this to "os" layer
|
2017-10-03 11:50:57 -03:00
|
|
|
const int kUnicodeEsc = 27;
|
|
|
|
const int kUnicodeEnter = '\r'; // 10
|
|
|
|
const int kUnicodeInsert = 0xF727; // NSInsertFunctionKey
|
|
|
|
const int kUnicodeDel = 0xF728; // NSDeleteFunctionKey
|
|
|
|
const int kUnicodeHome = 0xF729; // NSHomeFunctionKey
|
|
|
|
const int kUnicodeEnd = 0xF72B; // NSEndFunctionKey
|
|
|
|
const int kUnicodePageUp = 0xF72C; // NSPageUpFunctionKey
|
|
|
|
const int kUnicodePageDown = 0xF72D; // NSPageDownFunctionKey
|
|
|
|
const int kUnicodeLeft = 0xF702; // NSLeftArrowFunctionKey
|
|
|
|
const int kUnicodeRight = 0xF703; // NSRightArrowFunctionKey
|
|
|
|
const int kUnicodeUp = 0xF700; // NSUpArrowFunctionKey
|
|
|
|
const int kUnicodeDown = 0xF701; // NSDownArrowFunctionKey
|
|
|
|
|
2020-04-08 17:50:17 -03:00
|
|
|
const char* kFileRecentListGroup = "file_recent_list";
|
|
|
|
|
2017-10-03 11:50:57 -03:00
|
|
|
void destroy_instance(AppMenus* instance)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2012-07-09 13:20:58 -03:00
|
|
|
delete instance;
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
bool is_text_entry_shortcut(const os::Shortcut& shortcut)
|
2017-09-26 16:39:33 -03:00
|
|
|
{
|
2018-08-09 12:58:43 -03:00
|
|
|
const os::KeyModifiers mod = shortcut.modifiers();
|
2017-09-26 16:39:33 -03:00
|
|
|
const int chr = shortcut.unicode();
|
|
|
|
const int lchr = std::tolower(chr);
|
|
|
|
|
|
|
|
bool result =
|
2018-08-09 12:58:43 -03:00
|
|
|
((mod == os::KeyModifiers::kKeyNoneModifier ||
|
|
|
|
mod == os::KeyModifiers::kKeyShiftModifier) &&
|
2017-09-26 16:39:33 -03:00
|
|
|
chr >= 32 && chr < 0xF000)
|
|
|
|
||
|
2018-08-09 12:58:43 -03:00
|
|
|
((mod == os::KeyModifiers::kKeyCmdModifier ||
|
|
|
|
mod == os::KeyModifiers::kKeyCtrlModifier) &&
|
2017-09-26 16:39:33 -03:00
|
|
|
(lchr == 'a' || lchr == 'c' || lchr == 'v' || lchr == 'x'))
|
|
|
|
||
|
|
|
|
(chr == kUnicodeInsert ||
|
|
|
|
chr == kUnicodeDel ||
|
|
|
|
chr == kUnicodeHome ||
|
|
|
|
chr == kUnicodeEnd ||
|
|
|
|
chr == kUnicodeLeft ||
|
|
|
|
chr == kUnicodeRight ||
|
|
|
|
chr == kUnicodeEsc ||
|
|
|
|
chr == kUnicodeEnter);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-03 11:50:57 -03:00
|
|
|
bool can_call_global_shortcut(const AppMenuItem::Native* native)
|
2017-09-26 09:30:01 -03:00
|
|
|
{
|
2017-10-03 11:50:57 -03:00
|
|
|
ASSERT(native);
|
|
|
|
|
2017-09-26 09:30:01 -03:00
|
|
|
ui::Manager* manager = ui::Manager::getDefault();
|
|
|
|
ASSERT(manager);
|
|
|
|
ui::Widget* focus = manager->getFocus();
|
|
|
|
return
|
2017-09-27 12:29:12 -03:00
|
|
|
// The mouse is not capture
|
|
|
|
(manager->getCapture() == nullptr) &&
|
2017-09-26 09:30:01 -03:00
|
|
|
// The foreground window must be the main window to avoid calling
|
|
|
|
// a global command inside a modal dialog.
|
|
|
|
(manager->getForegroundWindow() == App::instance()->mainWindow()) &&
|
2018-06-29 12:14:54 -03:00
|
|
|
// If we are in a menubox window (e.g. we've pressed
|
|
|
|
// Alt+mnemonic), we should disable the native shortcuts
|
|
|
|
// temporarily so we can use mnemonics without modifiers
|
|
|
|
// (e.g. Alt+S opens the Sprite menu, then 'S' key should execute
|
|
|
|
// "Sprite Size" command in that menu, instead of Stroke command
|
|
|
|
// which is in 'Edit > Stroke'). This is necessary in macOS, when
|
|
|
|
// the native menu + Aseprite pixel-art menus are enabled.
|
|
|
|
(dynamic_cast<MenuBoxWindow*>(manager->getTopWindow()) == nullptr) &&
|
2017-09-26 09:30:01 -03:00
|
|
|
// The focused widget cannot be an entry, because entry fields
|
|
|
|
// prefer text input, so we cannot call shortcuts without
|
|
|
|
// modifiers (e.g. F or T keystrokes) to trigger a global command
|
|
|
|
// in a text field.
|
|
|
|
(focus == nullptr ||
|
|
|
|
focus->type() != ui::kEntryWidget ||
|
2017-10-03 11:50:57 -03:00
|
|
|
!is_text_entry_shortcut(native->shortcut)) &&
|
|
|
|
(native->keyContext == KeyContext::Any ||
|
|
|
|
native->keyContext == KeyboardShortcuts::instance()->getCurrentKeyContext());
|
2017-09-26 09:30:01 -03:00
|
|
|
}
|
|
|
|
|
2017-09-01 13:32:23 -03:00
|
|
|
// TODO this should be on "she" library (or we should use
|
2018-08-09 12:58:43 -03:00
|
|
|
// os::Shortcut instead of ui::Accelerators)
|
2017-10-03 11:50:57 -03:00
|
|
|
int from_scancode_to_unicode(KeyScancode scancode)
|
2017-09-01 13:32:23 -03:00
|
|
|
{
|
|
|
|
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
|
2017-09-26 16:39:33 -03:00
|
|
|
kUnicodeEsc, // kKeyEsc
|
2017-09-01 13:32:23 -03:00
|
|
|
'~', // kKeyTilde
|
|
|
|
'-', // kKeyMinus
|
|
|
|
'=', // kKeyEquals
|
|
|
|
8, // kKeyBackspace
|
|
|
|
9, // kKeyTab
|
|
|
|
'[', // kKeyOpenbrace
|
|
|
|
']', // kKeyClosebrace
|
2017-09-26 16:39:33 -03:00
|
|
|
kUnicodeEnter, // kKeyEnter
|
2017-09-01 13:32:23 -03:00
|
|
|
':', // kKeyColon
|
|
|
|
'\'', // kKeyQuote
|
|
|
|
'\\', // kKeyBackslash
|
|
|
|
0, // kKeyBackslash2
|
|
|
|
',', // kKeyComma
|
|
|
|
'.', // kKeyStop
|
|
|
|
'/', // kKeySlash
|
|
|
|
' ', // kKeySpace
|
2017-09-26 16:39:33 -03:00
|
|
|
kUnicodeInsert, // kKeyInsert (NSInsertFunctionKey)
|
|
|
|
kUnicodeDel, // kKeyDel (NSDeleteFunctionKey)
|
|
|
|
kUnicodeHome, // kKeyHome (NSHomeFunctionKey)
|
|
|
|
kUnicodeEnd, // kKeyEnd (NSEndFunctionKey)
|
|
|
|
kUnicodePageUp, // kKeyPageUp (NSPageUpFunctionKey)
|
|
|
|
kUnicodePageDown, // kKeyPageDown (NSPageDownFunctionKey)
|
|
|
|
kUnicodeLeft, // kKeyLeft (NSLeftArrowFunctionKey)
|
|
|
|
kUnicodeRight, // kKeyRight (NSRightArrowFunctionKey)
|
|
|
|
kUnicodeUp, // kKeyUp (NSUpArrowFunctionKey)
|
|
|
|
kUnicodeDown, // kKeyDown (NSDownArrowFunctionKey)
|
2017-09-01 13:32:23 -03:00
|
|
|
'/', // 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;
|
|
|
|
}
|
|
|
|
|
2017-10-03 11:50:57 -03:00
|
|
|
AppMenuItem::Native get_native_shortcut_for_command(
|
2017-09-26 09:30:01 -03:00
|
|
|
const char* commandId,
|
|
|
|
const Params& params = Params())
|
2017-09-01 13:32:23 -03:00
|
|
|
{
|
2017-10-03 11:50:57 -03:00
|
|
|
AppMenuItem::Native native;
|
2018-07-17 23:53:08 -03:00
|
|
|
KeyPtr key = KeyboardShortcuts::instance()->command(commandId, params);
|
2017-10-03 11:50:57 -03:00
|
|
|
if (key) {
|
2018-07-17 23:53:08 -03:00
|
|
|
native.shortcut = get_os_shortcut_from_key(key.get());
|
2017-10-03 11:50:57 -03:00
|
|
|
native.keyContext = key->keycontext();
|
|
|
|
}
|
|
|
|
return native;
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
|
2017-10-03 11:50:57 -03:00
|
|
|
} // anonymous namespace
|
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
os::Shortcut get_os_shortcut_from_key(const Key* key)
|
2017-09-01 13:32:23 -03:00
|
|
|
{
|
|
|
|
if (key && !key->accels().empty()) {
|
|
|
|
const ui::Accelerator& accel = key->accels().front();
|
2020-02-17 16:36:18 -03:00
|
|
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
// Shortcuts with spacebar as modifier do not work well in macOS
|
|
|
|
// (they will be called when the space bar is unpressed too).
|
|
|
|
if ((accel.modifiers() & ui::kKeySpaceModifier) == ui::kKeySpaceModifier)
|
|
|
|
return os::Shortcut();
|
|
|
|
#endif
|
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
return os::Shortcut(
|
2017-09-01 13:32:23 -03:00
|
|
|
(accel.unicodeChar() ? accel.unicodeChar():
|
|
|
|
from_scancode_to_unicode(accel.scancode())),
|
|
|
|
accel.modifiers());
|
|
|
|
}
|
|
|
|
else
|
2018-08-09 12:58:43 -03:00
|
|
|
return os::Shortcut();
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
// static
|
|
|
|
AppMenus* AppMenus::instance()
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2012-07-09 13:20:58 -03:00
|
|
|
static AppMenus* instance = NULL;
|
|
|
|
if (!instance) {
|
|
|
|
instance = new AppMenus;
|
2020-07-03 21:51:46 -03:00
|
|
|
App::instance()->Exit.connect([]{ destroy_instance(instance); });
|
2012-07-09 13:20:58 -03:00
|
|
|
}
|
|
|
|
return instance;
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
AppMenus::AppMenus()
|
2019-05-27 16:29:53 -03:00
|
|
|
: m_recentFilesPlaceholder(nullptr)
|
2017-09-01 13:32:23 -03:00
|
|
|
, m_osMenu(nullptr)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2015-03-02 11:18:33 -03:00
|
|
|
m_recentFilesConn =
|
2016-04-22 13:19:06 -03:00
|
|
|
App::instance()->recentFiles()->Changed.connect(
|
2020-07-03 21:51:46 -03:00
|
|
|
[this]{ rebuildRecentList(); });
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
void AppMenus::reload()
|
|
|
|
{
|
2020-04-08 17:50:17 -03:00
|
|
|
MENUS_TRACE("MENUS: AppMenus::reload()");
|
|
|
|
|
2013-10-14 19:58:11 -03:00
|
|
|
XmlDocumentRef doc(GuiXml::instance()->doc());
|
2015-04-02 20:42:43 -03:00
|
|
|
TiXmlHandle handle(doc.get());
|
2010-10-27 17:21:12 -03:00
|
|
|
const char* path = GuiXml::instance()->filename();
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2020-04-02 23:18:08 -03:00
|
|
|
////////////////////////////////////////
|
2020-04-08 17:50:17 -03:00
|
|
|
// Remove all menu items added to groups from recent files and
|
|
|
|
// scripts so we can re-add them later in the new menus.
|
2020-04-02 23:18:08 -03:00
|
|
|
|
|
|
|
for (auto& it : m_groups) {
|
|
|
|
GroupInfo& group = it.second;
|
2020-04-08 17:50:17 -03:00
|
|
|
MENUS_TRACE("MENUS: - groups", it.first, "with", group.items.size(), "item(s)");
|
|
|
|
group.end = nullptr; // This value will be restored later
|
|
|
|
for (auto& item : group.items)
|
2020-04-02 23:18:08 -03:00
|
|
|
item->parent()->removeChild(item);
|
|
|
|
}
|
|
|
|
|
2014-10-29 11:58:03 -03:00
|
|
|
////////////////////////////////////////
|
|
|
|
// Load menus
|
2009-12-16 23:24:57 +00:00
|
|
|
|
2016-10-27 12:25:33 -03:00
|
|
|
LOG("MENU: Loading menus from %s\n", path);
|
2009-12-16 23:24:57 +00:00
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
m_rootMenu.reset(loadMenuById(handle, "main_menu"));
|
2009-12-16 23:24:57 +00:00
|
|
|
|
2016-10-27 12:25:33 -03:00
|
|
|
LOG("MENU: Main menu loaded.\n");
|
2010-10-27 21:02:48 -03:00
|
|
|
|
2017-10-11 18:02:38 -03:00
|
|
|
m_tabPopupMenu.reset(loadMenuById(handle, "tab_popup_menu"));
|
|
|
|
m_documentTabPopupMenu.reset(loadMenuById(handle, "document_tab_popup_menu"));
|
|
|
|
m_layerPopupMenu.reset(loadMenuById(handle, "layer_popup_menu"));
|
|
|
|
m_framePopupMenu.reset(loadMenuById(handle, "frame_popup_menu"));
|
|
|
|
m_celPopupMenu.reset(loadMenuById(handle, "cel_popup_menu"));
|
|
|
|
m_celMovementPopupMenu.reset(loadMenuById(handle, "cel_movement_popup_menu"));
|
2019-10-01 14:55:08 -03:00
|
|
|
m_tagPopupMenu.reset(loadMenuById(handle, "tag_popup_menu"));
|
2017-10-11 18:02:38 -03:00
|
|
|
m_slicePopupMenu.reset(loadMenuById(handle, "slice_popup_menu"));
|
|
|
|
m_palettePopupMenu.reset(loadMenuById(handle, "palette_popup_menu"));
|
|
|
|
m_inkPopupMenu.reset(loadMenuById(handle, "ink_popup_menu"));
|
2007-09-23 20:13:58 +00:00
|
|
|
|
2018-09-05 13:35:13 -03:00
|
|
|
// Add one menu item to run each script from the user scripts/ folder
|
|
|
|
{
|
|
|
|
MenuItem* scriptsMenu = dynamic_cast<MenuItem*>(
|
|
|
|
m_rootMenu->findItemById("scripts_menu"));
|
|
|
|
#ifdef ENABLE_SCRIPTING
|
|
|
|
// Load scripts
|
|
|
|
ResourceFinder rf;
|
|
|
|
rf.includeUserDir("scripts/.");
|
|
|
|
std::string scriptsDir = rf.getFirstOrCreateDefault();
|
|
|
|
scriptsDir = base::get_file_path(scriptsDir);
|
|
|
|
if (base::is_directory(scriptsDir)) {
|
2018-11-16 17:46:54 -03:00
|
|
|
loadScriptsSubmenu(scriptsMenu->getSubmenu(), scriptsDir, true);
|
2018-09-05 13:35:13 -03:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
// Scripting is not available
|
|
|
|
if (scriptsMenu) {
|
|
|
|
delete scriptsMenu;
|
|
|
|
delete m_rootMenu->findItemById("scripts_menu_separator");
|
2020-04-25 09:38:19 -03:00
|
|
|
|
|
|
|
// Remove scripts group
|
|
|
|
auto it = m_groups.find("file_scripts");
|
|
|
|
if (it != m_groups.end())
|
|
|
|
m_groups.erase(it);
|
2018-09-05 13:35:13 -03:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-07-19 15:27:48 -03:00
|
|
|
// Remove the "Enter license" menu item when DRM is not enabled.
|
|
|
|
#ifndef ENABLE_DRM
|
|
|
|
if (auto helpMenuItem = m_rootMenu->findItemById("help_menu")) {
|
|
|
|
if (Menu* helpMenu = dynamic_cast<MenuItem*>(helpMenuItem)->getSubmenu()) {
|
|
|
|
delete helpMenu->findChild("enter_license_separator");
|
|
|
|
delete helpMenu->findChild("enter_license");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = m_groups.find("help_enter_license");
|
|
|
|
if (it != m_groups.end())
|
|
|
|
m_groups.erase(it);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-04-02 23:18:08 -03:00
|
|
|
////////////////////////////////////////
|
2020-04-08 17:50:17 -03:00
|
|
|
// Re-add menu items in groups (recent files & scripts)
|
2020-04-02 23:18:08 -03:00
|
|
|
|
|
|
|
for (auto& it : m_groups) {
|
|
|
|
GroupInfo& group = it.second;
|
|
|
|
if (group.end) {
|
2020-04-08 17:50:17 -03:00
|
|
|
MENUS_TRACE("MENUS: - re-adding group ", it.first, "with", group.items.size(), "item(s)");
|
|
|
|
|
2020-04-02 23:18:08 -03:00
|
|
|
auto menu = group.end->parent();
|
|
|
|
int insertIndex = menu->getChildIndex(group.end);
|
|
|
|
for (auto& item : group.items) {
|
|
|
|
menu->insertChild(++insertIndex, item);
|
|
|
|
group.end = item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Delete items that don't have a group now
|
|
|
|
else {
|
2020-04-08 17:50:17 -03:00
|
|
|
MENUS_TRACE("MENUS: - deleting group ", it.first, "with", group.items.size(), "item(s)");
|
2020-04-02 23:18:08 -03:00
|
|
|
for (auto& item : group.items)
|
2020-04-08 17:50:17 -03:00
|
|
|
item->deferDelete();
|
2020-04-02 23:18:08 -03:00
|
|
|
group.items.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-13 11:56:39 -03:00
|
|
|
////////////////////////////////////////
|
|
|
|
// Load keyboard shortcuts for commands
|
|
|
|
|
|
|
|
LOG("MENU: Loading commands keyboard shortcuts from %s\n", path);
|
|
|
|
|
|
|
|
TiXmlElement* xmlKey = handle
|
|
|
|
.FirstChild("gui")
|
|
|
|
.FirstChild("keyboard").ToElement();
|
|
|
|
|
2022-04-13 22:46:48 -03:00
|
|
|
// From a fresh start, load the default keys
|
2018-09-13 11:56:39 -03:00
|
|
|
KeyboardShortcuts::instance()->clear();
|
|
|
|
KeyboardShortcuts::instance()->importFile(xmlKey, KeySource::Original);
|
|
|
|
|
2022-04-13 22:46:48 -03:00
|
|
|
// Load extension-defined keys
|
|
|
|
for (const Extension* ext : App::instance()->extensions()) {
|
|
|
|
if (ext->isEnabled() &&
|
|
|
|
ext->hasKeys()) {
|
|
|
|
for (const auto& kv : ext->keys()) {
|
|
|
|
KeyboardShortcuts::instance()->importFile(
|
|
|
|
kv.second, KeySource::ExtensionDefined);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load user-defined keys
|
2018-09-13 11:56:39 -03:00
|
|
|
{
|
|
|
|
ResourceFinder rf;
|
|
|
|
rf.includeUserDir("user.aseprite-keys");
|
|
|
|
std::string fn = rf.getFirstOrCreateDefault();
|
|
|
|
if (base::is_file(fn))
|
|
|
|
KeyboardShortcuts::instance()->importFile(fn, KeySource::UserDefined);
|
|
|
|
}
|
|
|
|
|
2018-06-27 11:32:24 -03:00
|
|
|
// Create native menus after the default + user defined keyboard
|
|
|
|
// shortcuts are loaded correctly.
|
|
|
|
createNativeMenus();
|
2012-07-09 13:20:58 -03:00
|
|
|
}
|
|
|
|
|
2018-09-05 14:21:11 -03:00
|
|
|
#ifdef ENABLE_SCRIPTING
|
2018-11-16 17:46:54 -03:00
|
|
|
void AppMenus::loadScriptsSubmenu(ui::Menu* menu,
|
|
|
|
const std::string& dir,
|
|
|
|
const bool rootLevel)
|
2018-09-05 14:21:11 -03:00
|
|
|
{
|
|
|
|
auto files = base::list_files(dir);
|
2018-11-16 17:46:54 -03:00
|
|
|
std::sort(files.begin(), files.end(),
|
|
|
|
[](const std::string& a, const std::string& b) {
|
|
|
|
return base::compare_filenames(a, b) < 0;
|
|
|
|
});
|
|
|
|
int insertPos = 0;
|
2018-09-05 14:21:11 -03:00
|
|
|
for (auto fn : files) {
|
|
|
|
std::string fullFn = base::join_path(dir, fn);
|
|
|
|
AppMenuItem* menuitem = nullptr;
|
|
|
|
|
2018-11-16 15:18:19 -03:00
|
|
|
if (fn[0] == '.') // Ignore all files and directories that start with a dot
|
|
|
|
continue;
|
|
|
|
|
2018-09-05 14:21:11 -03:00
|
|
|
if (base::is_file(fullFn)) {
|
|
|
|
if (base::string_to_lower(base::get_file_extension(fn)) == "lua") {
|
|
|
|
Params params;
|
|
|
|
params.set("filename", fullFn.c_str());
|
|
|
|
menuitem = new AppMenuItem(
|
|
|
|
base::get_file_title(fn).c_str(),
|
2022-06-07 14:47:40 -03:00
|
|
|
CommandId::RunScript(),
|
2018-09-05 14:21:11 -03:00
|
|
|
params);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (base::is_directory(fullFn)) {
|
|
|
|
Menu* submenu = new Menu();
|
2018-11-16 17:46:54 -03:00
|
|
|
loadScriptsSubmenu(submenu, fullFn, false);
|
2018-09-05 14:21:11 -03:00
|
|
|
|
|
|
|
menuitem = new AppMenuItem(
|
|
|
|
base::get_file_title(fn).c_str());
|
|
|
|
menuitem->setSubmenu(submenu);
|
|
|
|
}
|
2018-11-16 17:46:54 -03:00
|
|
|
if (menuitem) {
|
|
|
|
menu->insertChild(insertPos++, menuitem);
|
|
|
|
}
|
2018-09-05 14:21:11 -03:00
|
|
|
}
|
2018-11-16 17:46:54 -03:00
|
|
|
if (rootLevel && insertPos > 0)
|
|
|
|
menu->insertChild(insertPos, new MenuSeparator());
|
2018-09-05 14:21:11 -03:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-08-15 10:39:06 -03:00
|
|
|
void AppMenus::initTheme()
|
|
|
|
{
|
|
|
|
updateMenusList();
|
|
|
|
for (Menu* menu : m_menus)
|
|
|
|
if (menu)
|
|
|
|
menu->initTheme();
|
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
bool AppMenus::rebuildRecentList()
|
|
|
|
{
|
2020-04-08 17:50:17 -03:00
|
|
|
MENUS_TRACE("MENUS: AppMenus::rebuildRecentList m_recentFilesPlaceholder=", m_recentFilesPlaceholder);
|
|
|
|
|
2019-05-27 16:29:53 -03:00
|
|
|
if (!m_recentFilesPlaceholder)
|
|
|
|
return true;
|
2012-07-09 13:20:58 -03:00
|
|
|
|
2019-05-27 16:29:53 -03:00
|
|
|
Menu* menu = dynamic_cast<Menu*>(m_recentFilesPlaceholder->parent());
|
|
|
|
if (!menu)
|
|
|
|
return false;
|
2012-07-09 13:20:58 -03:00
|
|
|
|
2019-05-27 16:29:53 -03:00
|
|
|
AppMenuItem* owner = dynamic_cast<AppMenuItem*>(menu->getOwnerMenuItem());
|
|
|
|
if (!owner || owner->hasSubmenuOpened())
|
|
|
|
return false;
|
2012-07-09 13:20:58 -03:00
|
|
|
|
2019-05-27 16:29:53 -03:00
|
|
|
// Remove active items
|
2020-04-08 17:50:17 -03:00
|
|
|
for (auto item : m_recentMenuItems)
|
|
|
|
removeMenuItemFromGroup(item);
|
|
|
|
m_recentMenuItems.clear();
|
2019-05-27 16:29:53 -03:00
|
|
|
|
|
|
|
auto recent = App::instance()->recentFiles();
|
|
|
|
base::paths files;
|
|
|
|
files.insert(files.end(),
|
|
|
|
recent->pinnedFiles().begin(),
|
|
|
|
recent->pinnedFiles().end());
|
|
|
|
files.insert(files.end(),
|
|
|
|
recent->recentFiles().begin(),
|
|
|
|
recent->recentFiles().end());
|
|
|
|
if (!files.empty()) {
|
|
|
|
Params params;
|
|
|
|
for (const auto& fn : files) {
|
|
|
|
params.set("filename", fn.c_str());
|
|
|
|
|
2020-04-08 17:50:17 -03:00
|
|
|
std::unique_ptr<AppMenuItem> menuitem(
|
|
|
|
new AppMenuItem(base::get_file_name(fn).c_str(),
|
2022-06-07 14:47:40 -03:00
|
|
|
CommandId::OpenFile(),
|
|
|
|
params));
|
2019-05-27 16:29:53 -03:00
|
|
|
menuitem->setIsRecentFileItem(true);
|
2020-04-08 17:50:17 -03:00
|
|
|
|
|
|
|
m_recentMenuItems.push_back(menuitem.get());
|
|
|
|
addMenuItemIntoGroup(kFileRecentListGroup, std::move(menuitem));
|
2012-07-09 13:20:58 -03:00
|
|
|
}
|
2019-05-27 16:29:53 -03:00
|
|
|
}
|
|
|
|
else {
|
2020-04-08 17:50:17 -03:00
|
|
|
std::unique_ptr<AppMenuItem> menuitem(
|
|
|
|
new AppMenuItem(
|
2022-06-14 14:00:19 -03:00
|
|
|
Strings::main_menu_file_no_recent_file()));
|
2019-05-27 16:29:53 -03:00
|
|
|
menuitem->setIsRecentFileItem(true);
|
|
|
|
menuitem->setEnabled(false);
|
2020-04-08 17:50:17 -03:00
|
|
|
|
|
|
|
m_recentMenuItems.push_back(menuitem.get());
|
|
|
|
addMenuItemIntoGroup(kFileRecentListGroup, std::move(menuitem));
|
2019-05-27 16:29:53 -03:00
|
|
|
}
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2019-05-27 16:29:53 -03:00
|
|
|
// Sync native menus
|
|
|
|
if (owner->native() &&
|
|
|
|
owner->native()->menuItem) {
|
2020-07-07 19:06:48 -03:00
|
|
|
auto menus = os::instance()->menus();
|
|
|
|
os::MenuRef osMenu = (menus ? menus->makeMenu(): nullptr);
|
2019-05-27 16:29:53 -03:00
|
|
|
if (osMenu) {
|
2020-07-07 19:06:48 -03:00
|
|
|
createNativeSubmenus(osMenu.get(), menu);
|
2019-05-27 16:29:53 -03:00
|
|
|
owner->native()->menuItem->setSubmenu(osMenu);
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
return true;
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2020-04-02 23:18:08 -03:00
|
|
|
void AppMenus::addMenuItemIntoGroup(const std::string& groupId,
|
|
|
|
std::unique_ptr<MenuItem>&& menuItem)
|
|
|
|
{
|
2020-04-08 17:50:17 -03:00
|
|
|
GroupInfo& group = m_groups[groupId];
|
2020-04-02 23:18:08 -03:00
|
|
|
Widget* menu = group.end->parent();
|
|
|
|
ASSERT(menu);
|
|
|
|
int insertIndex = menu->getChildIndex(group.end);
|
|
|
|
menu->insertChild(insertIndex+1, menuItem.get());
|
|
|
|
|
|
|
|
group.end = menuItem.get();
|
|
|
|
group.items.push_back(menuItem.get());
|
|
|
|
|
|
|
|
menuItem.release();
|
|
|
|
}
|
|
|
|
|
2020-04-08 17:50:17 -03:00
|
|
|
template<typename Pred>
|
|
|
|
void AppMenus::removeMenuItemFromGroup(Pred pred)
|
2020-04-02 23:18:08 -03:00
|
|
|
{
|
|
|
|
for (auto& it : m_groups) {
|
|
|
|
GroupInfo& group = it.second;
|
|
|
|
for (auto it=group.items.begin(); it != group.items.end(); ) {
|
|
|
|
auto& item = *it;
|
2020-04-08 17:50:17 -03:00
|
|
|
if (pred(item)) {
|
|
|
|
if (item == group.end)
|
|
|
|
group.end = group.end->previousSibling();
|
|
|
|
|
|
|
|
item->parent()->removeChild(item);
|
2020-04-24 09:24:42 -03:00
|
|
|
if (auto appItem = dynamic_cast<AppMenuItem*>(item)) {
|
|
|
|
if (appItem)
|
|
|
|
appItem->disposeNative();
|
|
|
|
}
|
2020-04-08 17:50:17 -03:00
|
|
|
item->deferDelete();
|
|
|
|
|
2020-04-02 23:18:08 -03:00
|
|
|
it = group.items.erase(it);
|
|
|
|
}
|
2020-04-08 17:50:17 -03:00
|
|
|
else {
|
2020-04-02 23:18:08 -03:00
|
|
|
++it;
|
2020-04-08 17:50:17 -03:00
|
|
|
}
|
2020-04-02 23:18:08 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 17:50:17 -03:00
|
|
|
void AppMenus::removeMenuItemFromGroup(Command* cmd)
|
|
|
|
{
|
|
|
|
removeMenuItemFromGroup(
|
|
|
|
[cmd](Widget* item){
|
|
|
|
auto appMenuItem = dynamic_cast<AppMenuItem*>(item);
|
|
|
|
return (appMenuItem && appMenuItem->getCommand() == cmd);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppMenus::removeMenuItemFromGroup(Widget* menuItem)
|
|
|
|
{
|
|
|
|
removeMenuItemFromGroup(
|
|
|
|
[menuItem](Widget* item){
|
|
|
|
return (item == menuItem);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
Menu* AppMenus::loadMenuById(TiXmlHandle& handle, const char* id)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2010-08-03 23:33:44 -03:00
|
|
|
ASSERT(id != NULL);
|
2009-10-09 01:34:06 +00:00
|
|
|
|
2009-12-16 23:24:57 +00:00
|
|
|
// <gui><menus><menu>
|
|
|
|
TiXmlElement* xmlMenu = handle
|
|
|
|
.FirstChild("gui")
|
|
|
|
.FirstChild("menus")
|
|
|
|
.FirstChild("menu").ToElement();
|
|
|
|
while (xmlMenu) {
|
2017-10-11 18:02:38 -03:00
|
|
|
const char* menuId = xmlMenu->Attribute("id");
|
2009-12-16 23:24:57 +00:00
|
|
|
|
2017-10-11 18:02:38 -03:00
|
|
|
if (menuId && strcmp(menuId, id) == 0) {
|
|
|
|
m_xmlTranslator.setStringIdPrefix(menuId);
|
2012-07-09 13:20:58 -03:00
|
|
|
return convertXmlelemToMenu(xmlMenu);
|
2017-10-11 18:02:38 -03:00
|
|
|
}
|
2009-12-16 23:24:57 +00:00
|
|
|
|
|
|
|
xmlMenu = xmlMenu->NextSiblingElement();
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
2008-01-06 19:30:17 +00:00
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
throw base::Exception("Error loading menu '%s'\nReinstall the application.", id);
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2011-04-22 23:00:35 -03:00
|
|
|
Menu* menu = new Menu();
|
2021-05-20 17:08:04 -03:00
|
|
|
menu->setText(m_xmlTranslator(elem, "text"));
|
2007-09-23 20:13:58 +00:00
|
|
|
|
2009-12-16 23:24:57 +00:00
|
|
|
TiXmlElement* child = elem->FirstChildElement();
|
|
|
|
while (child) {
|
2012-07-09 13:20:58 -03:00
|
|
|
Widget* menuitem = convertXmlelemToMenuitem(child);
|
2009-12-16 23:24:57 +00:00
|
|
|
if (menuitem)
|
2011-03-29 21:35:17 -03:00
|
|
|
menu->addChild(menuitem);
|
2009-12-16 23:24:57 +00:00
|
|
|
else
|
2011-01-20 23:33:57 -03:00
|
|
|
throw base::Exception("Error converting the element \"%s\" to a menu-item.\n",
|
2012-01-05 19:45:03 -03:00
|
|
|
static_cast<const char*>(child->Value()));
|
2007-09-23 20:13:58 +00:00
|
|
|
|
2009-12-16 23:24:57 +00:00
|
|
|
child = child->NextSiblingElement();
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2007-09-23 20:13:58 +00:00
|
|
|
return menu;
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2018-09-05 13:35:13 -03:00
|
|
|
const char* id = elem->Attribute("id");
|
2020-04-02 23:18:08 -03:00
|
|
|
const char* group = elem->Attribute("group");
|
2018-09-05 13:35:13 -03:00
|
|
|
|
2009-12-16 23:24:57 +00:00
|
|
|
// is it a <separator>?
|
2018-09-05 13:35:13 -03:00
|
|
|
if (strcmp(elem->Value(), "separator") == 0) {
|
|
|
|
auto item = new MenuSeparator;
|
2019-05-27 16:29:53 -03:00
|
|
|
if (id) {
|
|
|
|
item->setId(id);
|
|
|
|
|
|
|
|
// Recent list menu
|
|
|
|
if (std::strcmp(id, "recent_files_placeholder") == 0) {
|
|
|
|
m_recentFilesPlaceholder = item;
|
|
|
|
}
|
|
|
|
}
|
2020-04-08 17:50:17 -03:00
|
|
|
if (group)
|
|
|
|
m_groups[group].end = item;
|
2018-09-05 13:35:13 -03:00
|
|
|
return item;
|
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2017-11-30 11:57:18 -03:00
|
|
|
const char* command_id = elem->Attribute("command");
|
2009-12-16 23:24:57 +00:00
|
|
|
Command* command =
|
2017-11-30 11:57:18 -03:00
|
|
|
command_id ? Commands::instance()->byId(command_id):
|
|
|
|
nullptr;
|
2009-10-09 01:34:06 +00:00
|
|
|
|
|
|
|
// load params
|
|
|
|
Params params;
|
|
|
|
if (command) {
|
2009-12-16 23:24:57 +00:00
|
|
|
TiXmlElement* xmlParam = elem->FirstChildElement("param");
|
|
|
|
while (xmlParam) {
|
|
|
|
const char* param_name = xmlParam->Attribute("name");
|
|
|
|
const char* param_value = xmlParam->Attribute("value");
|
|
|
|
|
|
|
|
if (param_name && param_value)
|
2012-01-05 19:45:03 -03:00
|
|
|
params.set(param_name, param_value);
|
2009-12-16 23:24:57 +00:00
|
|
|
|
|
|
|
xmlParam = xmlParam->NextSiblingElement();
|
2009-10-09 01:34:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
// Create the item
|
2017-10-11 18:02:38 -03:00
|
|
|
AppMenuItem* menuitem = new AppMenuItem(m_xmlTranslator(elem, "text"),
|
2022-06-07 14:47:40 -03:00
|
|
|
(command ? command->id(): ""),
|
|
|
|
params);
|
2007-09-18 23:57:02 +00:00
|
|
|
if (!menuitem)
|
2017-02-14 14:16:37 -03:00
|
|
|
return nullptr;
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2018-09-05 13:35:13 -03:00
|
|
|
if (id) menuitem->setId(id);
|
2017-02-14 14:16:37 -03:00
|
|
|
menuitem->processMnemonicFromText();
|
2020-04-08 17:50:17 -03:00
|
|
|
if (group)
|
|
|
|
m_groups[group].end = menuitem;
|
2017-02-14 14:16:37 -03:00
|
|
|
|
|
|
|
// Has it a ID?
|
2009-12-16 23:24:57 +00:00
|
|
|
if (id) {
|
2019-05-27 16:29:53 -03:00
|
|
|
if (std::strcmp(id, "help_menu") == 0) {
|
2017-09-01 13:32:23 -03:00
|
|
|
m_helpMenuitem = menuitem;
|
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2011-04-22 23:00:35 -03:00
|
|
|
// Has it a sub-menu (<menu>)?
|
2009-12-16 23:24:57 +00:00
|
|
|
if (strcmp(elem->Value(), "menu") == 0) {
|
2011-04-22 23:00:35 -03:00
|
|
|
// Create the sub-menu
|
2012-07-09 13:20:58 -03:00
|
|
|
Menu* subMenu = convertXmlelemToMenu(elem);
|
2011-04-22 23:00:35 -03:00
|
|
|
if (!subMenu)
|
2011-01-20 23:33:57 -03:00
|
|
|
throw base::Exception("Error reading the sub-menu\n");
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2011-04-22 23:00:35 -03:00
|
|
|
menuitem->setSubmenu(subMenu);
|
2007-09-23 20:13:58 +00:00
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2007-09-23 20:13:58 +00:00
|
|
|
return menuitem;
|
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2018-07-17 23:53:08 -03:00
|
|
|
void AppMenus::applyShortcutToMenuitemsWithCommand(Command* command,
|
|
|
|
const Params& params,
|
|
|
|
const KeyPtr& key)
|
2014-10-29 11:58:03 -03:00
|
|
|
{
|
2017-08-15 10:39:06 -03:00
|
|
|
updateMenusList();
|
|
|
|
for (Menu* menu : m_menus)
|
2015-11-27 11:40:07 -03:00
|
|
|
if (menu)
|
|
|
|
applyShortcutToMenuitemsWithCommand(menu, command, params, key);
|
2014-10-29 11:58:03 -03:00
|
|
|
}
|
|
|
|
|
2018-07-17 23:53:08 -03:00
|
|
|
void AppMenus::applyShortcutToMenuitemsWithCommand(Menu* menu,
|
|
|
|
Command* command,
|
|
|
|
const Params& params,
|
|
|
|
const KeyPtr& key)
|
2007-09-23 20:13:58 +00:00
|
|
|
{
|
2015-12-03 19:46:13 -03:00
|
|
|
for (auto child : menu->children()) {
|
2015-06-23 14:37:22 -03:00
|
|
|
if (child->type() == kMenuItemWidget) {
|
2014-11-06 21:04:32 -03:00
|
|
|
AppMenuItem* menuitem = dynamic_cast<AppMenuItem*>(child);
|
|
|
|
if (!menuitem)
|
|
|
|
continue;
|
2011-04-22 23:00:35 -03:00
|
|
|
|
2022-06-07 14:47:40 -03:00
|
|
|
const std::string& mi_commandId = menuitem->getCommandId();
|
2015-03-11 15:40:22 -03:00
|
|
|
const Params& mi_params = menuitem->getParams();
|
2009-10-09 01:34:06 +00:00
|
|
|
|
2022-06-07 14:47:40 -03:00
|
|
|
if ((base::utf8_icmp(mi_commandId, command->id()) == 0) &&
|
2015-03-11 15:40:22 -03:00
|
|
|
(mi_params == params)) {
|
2014-10-29 11:58:03 -03:00
|
|
|
// Set the keyboard shortcut to be shown in this menu-item
|
|
|
|
menuitem->setKey(key);
|
2009-10-09 01:34:06 +00:00
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2011-04-22 23:00:35 -03:00
|
|
|
if (Menu* submenu = menuitem->getSubmenu())
|
2014-10-29 11:58:03 -03:00
|
|
|
applyShortcutToMenuitemsWithCommand(submenu, command, params, key);
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-05 21:20:19 -03:00
|
|
|
|
2017-09-01 13:32:23 -03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-15 10:39:06 -03:00
|
|
|
// TODO redesign the list of popup menus, it might be an
|
|
|
|
// autogenerated widget from 'gen'
|
|
|
|
void AppMenus::updateMenusList()
|
|
|
|
{
|
|
|
|
m_menus.clear();
|
2018-08-08 17:27:26 -03:00
|
|
|
m_menus.push_back(m_rootMenu.get());
|
|
|
|
m_menus.push_back(m_tabPopupMenu.get());
|
|
|
|
m_menus.push_back(m_documentTabPopupMenu.get());
|
|
|
|
m_menus.push_back(m_layerPopupMenu.get());
|
|
|
|
m_menus.push_back(m_framePopupMenu.get());
|
|
|
|
m_menus.push_back(m_celPopupMenu.get());
|
|
|
|
m_menus.push_back(m_celMovementPopupMenu.get());
|
2019-10-01 14:55:08 -03:00
|
|
|
m_menus.push_back(m_tagPopupMenu.get());
|
2018-08-08 17:27:26 -03:00
|
|
|
m_menus.push_back(m_slicePopupMenu.get());
|
|
|
|
m_menus.push_back(m_palettePopupMenu.get());
|
|
|
|
m_menus.push_back(m_inkPopupMenu.get());
|
2017-08-15 10:39:06 -03:00
|
|
|
}
|
|
|
|
|
2017-09-01 13:32:23 -03:00
|
|
|
void AppMenus::createNativeMenus()
|
|
|
|
{
|
2018-08-09 12:58:43 -03:00
|
|
|
os::Menus* menus = os::instance()->menus();
|
2017-09-01 13:32:23 -03:00
|
|
|
if (!menus) // This platform doesn't support native menu items
|
|
|
|
return;
|
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
// Save a reference to the old menu to avoid destroying it.
|
|
|
|
os::MenuRef oldOSMenu = m_osMenu;
|
|
|
|
m_osMenu = menus->makeMenu();
|
2017-09-01 13:32:23 -03:00
|
|
|
|
|
|
|
#ifdef __APPLE__ // Create default macOS app menus (App ... Window)
|
|
|
|
{
|
2020-03-16 10:29:58 -03:00
|
|
|
os::MenuItemInfo about(fmt::format("About {}", get_app_name()));
|
2017-12-01 15:10:21 -03:00
|
|
|
auto native = get_native_shortcut_for_command(CommandId::About());
|
2017-10-03 11:50:57 -03:00
|
|
|
about.shortcut = native.shortcut;
|
|
|
|
about.execute = [native]{
|
|
|
|
if (can_call_global_shortcut(&native)) {
|
2017-12-01 15:10:21 -03:00
|
|
|
Command* cmd = Commands::instance()->byId(CommandId::About());
|
2019-06-03 22:57:20 -03:00
|
|
|
UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
};
|
2019-03-22 13:40:31 +08:00
|
|
|
about.validate = [native](os::MenuItem* item){
|
|
|
|
item->setEnabled(can_call_global_shortcut(&native));
|
|
|
|
};
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
os::MenuItemInfo preferences("Preferences...");
|
2017-12-01 15:10:21 -03:00
|
|
|
native = get_native_shortcut_for_command(CommandId::Options());
|
2017-10-03 11:50:57 -03:00
|
|
|
preferences.shortcut = native.shortcut;
|
|
|
|
preferences.execute = [native]{
|
|
|
|
if (can_call_global_shortcut(&native)) {
|
2017-12-01 15:10:21 -03:00
|
|
|
Command* cmd = Commands::instance()->byId(CommandId::Options());
|
2019-06-03 22:57:20 -03:00
|
|
|
UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
};
|
2019-03-22 13:40:31 +08:00
|
|
|
preferences.validate = [native](os::MenuItem* item){
|
|
|
|
item->setEnabled(can_call_global_shortcut(&native));
|
|
|
|
};
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2020-03-16 10:29:58 -03:00
|
|
|
os::MenuItemInfo hide(fmt::format("Hide {}", get_app_name()), os::MenuItemInfo::Hide);
|
2018-08-09 12:58:43 -03:00
|
|
|
hide.shortcut = os::Shortcut('h', os::kKeyCmdModifier);
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2020-03-16 10:29:58 -03:00
|
|
|
os::MenuItemInfo quit(fmt::format("Quit {}", get_app_name()), os::MenuItemInfo::Quit);
|
2018-08-09 12:58:43 -03:00
|
|
|
quit.shortcut = os::Shortcut('q', os::kKeyCmdModifier);
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
os::MenuRef appMenu = menus->makeMenu();
|
|
|
|
appMenu->addItem(menus->makeMenuItem(about));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(os::MenuItemInfo(os::MenuItemInfo::Separator)));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(preferences));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(os::MenuItemInfo(os::MenuItemInfo::Separator)));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(hide));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(os::MenuItemInfo("Hide Others", os::MenuItemInfo::HideOthers)));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(os::MenuItemInfo("Show All", os::MenuItemInfo::ShowAll)));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(os::MenuItemInfo(os::MenuItemInfo::Separator)));
|
|
|
|
appMenu->addItem(menus->makeMenuItem(quit));
|
|
|
|
|
|
|
|
os::MenuItemRef appItem = menus->makeMenuItem(os::MenuItemInfo("App"));
|
2017-09-01 13:32:23 -03:00
|
|
|
appItem->setSubmenu(appMenu);
|
|
|
|
m_osMenu->addItem(appItem);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
createNativeSubmenus(m_osMenu.get(), m_rootMenu.get());
|
2017-09-01 13:32:23 -03:00
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
os::MenuItemInfo minimize("Minimize", os::MenuItemInfo::Minimize);
|
|
|
|
minimize.shortcut = os::Shortcut('m', os::kKeyCmdModifier);
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
os::MenuRef windowMenu = menus->makeMenu();
|
|
|
|
windowMenu->addItem(menus->makeMenuItem(minimize));
|
|
|
|
windowMenu->addItem(menus->makeMenuItem(os::MenuItemInfo("Zoom", os::MenuItemInfo::Zoom)));
|
2017-09-01 13:32:23 -03:00
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
os::MenuItemRef windowItem = menus->makeMenuItem(os::MenuItemInfo("Window"));
|
2017-09-01 13:32:23 -03:00
|
|
|
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);
|
2020-04-24 09:24:42 -03:00
|
|
|
if (oldOSMenu)
|
2020-07-07 19:06:48 -03:00
|
|
|
oldOSMenu.reset();
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
void AppMenus::createNativeSubmenus(os::Menu* osMenu,
|
|
|
|
const ui::Menu* uiMenu)
|
2017-09-01 13:32:23 -03:00
|
|
|
{
|
2018-08-09 12:58:43 -03:00
|
|
|
os::Menus* menus = os::instance()->menus();
|
2017-09-01 13:32:23 -03:00
|
|
|
|
|
|
|
for (const auto& child : uiMenu->children()) {
|
2018-08-09 12:58:43 -03:00
|
|
|
os::MenuItemInfo info;
|
2017-09-01 13:32:23 -03:00
|
|
|
AppMenuItem* appMenuItem = dynamic_cast<AppMenuItem*>(child);
|
2017-10-03 11:50:57 -03:00
|
|
|
AppMenuItem::Native native;
|
2017-09-01 13:32:23 -03:00
|
|
|
|
|
|
|
if (child->type() == kSeparatorWidget) {
|
2018-08-09 12:58:43 -03:00
|
|
|
info.type = os::MenuItemInfo::Separator;
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
else if (child->type() == kMenuItemWidget) {
|
2017-09-26 09:30:01 -03:00
|
|
|
if (appMenuItem &&
|
|
|
|
appMenuItem->getCommand()) {
|
2017-10-03 11:50:57 -03:00
|
|
|
native = get_native_shortcut_for_command(
|
2022-06-07 14:47:40 -03:00
|
|
|
appMenuItem->getCommandId().c_str(),
|
2017-09-26 09:30:01 -03:00
|
|
|
appMenuItem->getParams());
|
|
|
|
}
|
|
|
|
|
2018-08-09 12:58:43 -03:00
|
|
|
info.type = os::MenuItemInfo::Normal;
|
2017-09-01 13:32:23 -03:00
|
|
|
info.text = child->text();
|
2017-10-03 11:50:57 -03:00
|
|
|
info.shortcut = native.shortcut;
|
2017-09-26 16:39:33 -03:00
|
|
|
info.execute = [appMenuItem]{
|
2017-10-03 11:50:57 -03:00
|
|
|
if (can_call_global_shortcut(appMenuItem->native()))
|
2017-09-26 16:39:33 -03:00
|
|
|
appMenuItem->executeClick();
|
2017-09-01 13:32:23 -03:00
|
|
|
};
|
2018-08-09 12:58:43 -03:00
|
|
|
info.validate = [appMenuItem](os::MenuItem* osItem) {
|
2017-10-03 11:50:57 -03:00
|
|
|
if (can_call_global_shortcut(appMenuItem->native())) {
|
2017-09-26 16:39:33 -03:00
|
|
|
appMenuItem->validateItem();
|
|
|
|
osItem->setEnabled(appMenuItem->isEnabled());
|
|
|
|
osItem->setChecked(appMenuItem->isSelected());
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Disable item when there are a modal window
|
2017-09-26 16:39:33 -03:00
|
|
|
osItem->setEnabled(false);
|
2017-09-01 13:32:23 -03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ASSERT(false); // Unsupported menu item type
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-07-07 19:06:48 -03:00
|
|
|
os::MenuItemRef osItem = menus->makeMenuItem(info);
|
2017-09-01 13:32:23 -03:00
|
|
|
if (osItem) {
|
|
|
|
osMenu->addItem(osItem);
|
2017-10-03 11:50:57 -03:00
|
|
|
if (appMenuItem) {
|
|
|
|
native.menuItem = osItem;
|
|
|
|
appMenuItem->setNative(native);
|
|
|
|
}
|
2017-09-01 13:32:23 -03:00
|
|
|
|
|
|
|
if (child->type() == ui::kMenuItemWidget &&
|
|
|
|
((ui::MenuItem*)child)->hasSubmenu()) {
|
2020-07-07 19:06:48 -03:00
|
|
|
os::MenuRef osSubmenu = menus->makeMenu();
|
|
|
|
createNativeSubmenus(osSubmenu.get(), ((ui::MenuItem*)child)->getSubmenu());
|
2017-09-01 13:32:23 -03:00
|
|
|
osItem->setSubmenu(osSubmenu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
} // namespace app
|