1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2017 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/ui/app_menuitem.h"
13
14#include "app/app_menus.h"
15#include "app/commands/command.h"
16#include "app/commands/commands.h"
17#include "app/commands/params.h"
18#include "app/modules/gui.h"
19#include "app/ui/keyboard_shortcuts.h"
20#include "app/ui_context.h"
21#include "os/menus.h"
22#include "ui/accelerator.h"
23#include "ui/menu.h"
24#include "ui/message.h"
25#include "ui/size_hint_event.h"
26#include "ui/widget.h"
27
28#include <cstdarg>
29#include <string>
30
31namespace app {
32
33using namespace ui;
34
35// static
36Params AppMenuItem::s_contextParams;
37
38AppMenuItem::AppMenuItem(const std::string& text,
39 const std::string& commandId,
40 const Params& params)
41 : MenuItem(text)
42 , m_key(nullptr)
43 , m_commandId(commandId)
44 , m_params(params)
45 , m_isRecentFileItem(false)
46 , m_native(nullptr)
47{
48}
49
50Command* AppMenuItem::getCommand() const
51{
52 if (!m_commandId.empty())
53 return Commands::instance()->byId(m_commandId.c_str());
54 else
55 return nullptr;
56}
57
58void AppMenuItem::setKey(const KeyPtr& key)
59{
60 m_key = key;
61 syncNativeMenuItemKeyShortcut();
62}
63
64void AppMenuItem::setNative(const Native& native)
65{
66 if (!m_native)
67 m_native.reset(new Native(native));
68 else
69 *m_native = native;
70}
71
72void AppMenuItem::disposeNative()
73{
74 m_native.reset();
75}
76
77void AppMenuItem::syncNativeMenuItemKeyShortcut()
78{
79 if (m_native) {
80 os::Shortcut shortcut = get_os_shortcut_from_key(m_key.get());
81
82 m_native->shortcut = shortcut;
83 m_native->menuItem->setShortcut(shortcut);
84 m_native->keyContext = (m_key ? m_key->keycontext(): KeyContext::Any);
85 }
86}
87
88// static
89void AppMenuItem::setContextParams(const Params& params)
90{
91 s_contextParams = params;
92}
93
94bool AppMenuItem::onProcessMessage(Message* msg)
95{
96 switch (msg->type()) {
97
98 case kCloseMessage:
99 // Don't disable items with submenus
100 if (!hasSubmenu()) {
101 // Disable the menu item (the keyboard shortcuts are processed
102 // by "manager_msg_proc")
103 setEnabled(false);
104 }
105 break;
106 }
107
108 return MenuItem::onProcessMessage(msg);
109}
110
111void AppMenuItem::onSizeHint(SizeHintEvent& ev)
112{
113 gfx::Size size(0, 0);
114
115 if (hasText()) {
116 size.w =
117 + textWidth()
118 + (inBar() ? childSpacing()/4: childSpacing())
119 + border().width();
120
121 size.h =
122 + textHeight()
123 + border().height();
124
125 if (m_key && !m_key->accels().empty()) {
126 size.w += Graphics::measureUITextLength(
127 m_key->accels().front().toString().c_str(), font());
128 }
129 }
130
131 ev.setSizeHint(size);
132}
133
134void AppMenuItem::onClick()
135{
136 MenuItem::onClick();
137
138 if (!m_commandId.empty()) {
139 Params params = m_params;
140 if (!s_contextParams.empty())
141 params |= s_contextParams;
142
143 // Load parameters to call Command::isEnabled, so we can check if
144 // the command is enabled with this parameters.
145 if (auto command = getCommand()) {
146 command->loadParams(params);
147
148 UIContext* context = UIContext::instance();
149 if (command->isEnabled(context))
150 context->executeCommandFromMenuOrShortcut(command, params);
151
152 // TODO At this point, the "this" pointer might be deleted if
153 // the command reloaded the App main menu
154 }
155 }
156}
157
158void AppMenuItem::onValidate()
159{
160 // Update the context flags after opening the menuitem's
161 // submenu to update the "enabled" flag of each command
162 // correctly.
163 Context* context = UIContext::instance();
164 context->updateFlags();
165
166 if (auto command = getCommand()) {
167 Params params = m_params;
168 if (!s_contextParams.empty())
169 params |= s_contextParams;
170
171 command->loadParams(params);
172
173 setEnabled(command->isEnabled(context));
174 setSelected(command->isChecked(context));
175 }
176}
177
178} // namespace app
179