1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ui/base/cocoa/command_dispatcher.h"
#include "base/logging.h"
#include "ui/base/cocoa/cocoa_base_utils.h"
#import "ui/base/cocoa/user_interface_item_command_handler.h"
// Expose -[NSWindow hasKeyAppearance], which determines whether the traffic
// lights on the window are "lit". CommandDispatcher uses this property on a
// parent window to decide whether keys and commands should bubble up.
@interface NSWindow (PrivateAPI)
- (BOOL)hasKeyAppearance;
@end
@interface CommandDispatcher ()
// The parent to bubble events to, or nil.
- (NSWindow<CommandDispatchingWindow>*)bubbleParent;
@end
namespace {
// Duplicate the given key event, but changing the associated window.
NSEvent* KeyEventForWindow(NSWindow* window, NSEvent* event) {
NSEventType event_type = [event type];
// Convert the event's location from the original window's coordinates into
// our own.
NSPoint location = [event locationInWindow];
location = ui::ConvertPointFromWindowToScreen([event window], location);
location = ui::ConvertPointFromScreenToWindow(window, location);
// Various things *only* apply to key down/up.
bool is_a_repeat = false;
NSString* characters = nil;
NSString* charactors_ignoring_modifiers = nil;
if (event_type == NSKeyDown || event_type == NSKeyUp) {
is_a_repeat = [event isARepeat];
characters = [event characters];
charactors_ignoring_modifiers = [event charactersIgnoringModifiers];
}
// This synthesis may be slightly imperfect: we provide nil for the context,
// since I (viettrungluu) am sceptical that putting in the original context
// (if one is given) is valid.
return [NSEvent keyEventWithType:event_type
location:location
modifierFlags:[event modifierFlags]
timestamp:[event timestamp]
windowNumber:[window windowNumber]
context:nil
characters:characters
charactersIgnoringModifiers:charactors_ignoring_modifiers
isARepeat:is_a_repeat
keyCode:[event keyCode]];
}
} // namespace
@implementation CommandDispatcher {
@private
BOOL redispatchingEvent_;
BOOL eventHandled_;
NSWindow<CommandDispatchingWindow>* owner_; // Weak, owns us.
}
@synthesize delegate = delegate_;
- (instancetype)initWithOwner:(NSWindow<CommandDispatchingWindow>*)owner {
if ((self = [super init])) {
owner_ = owner;
}
return self;
}
- (BOOL)performKeyEquivalent:(NSEvent*)event {
if ([delegate_ eventHandledByExtensionCommand:event
isRedispatch:redispatchingEvent_]) {
return YES;
}
if (redispatchingEvent_)
return NO;
// Give a CommandDispatcherTarget (e.g. a web site) a chance to handle the
// event. If it doesn't want to handle it, it will call us back with
// -redispatchKeyEvent:. Only allow this behavior when dispatching key events
// on the key window.
if ([owner_ isKeyWindow]) {
NSResponder* r = [owner_ firstResponder];
if ([r conformsToProtocol:@protocol(CommandDispatcherTarget)])
return [r performKeyEquivalent:event];
}
if ([delegate_ prePerformKeyEquivalent:event window:owner_])
return YES;
if ([owner_ defaultPerformKeyEquivalent:event])
return YES;
if ([delegate_ postPerformKeyEquivalent:event window:owner_])
return YES;
// Allow commands to "bubble up" to CommandDispatchers in parent windows, if
// they were not handled here.
return [[self bubbleParent] performKeyEquivalent:event];
}
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
forHandler:(id<UserInterfaceItemCommandHandler>)handler {
// Since this class implements these selectors, |super| will always say they
// are enabled. Only use [super] to validate other selectors. If there is no
// command handler, defer to AppController.
if ([item action] == @selector(commandDispatch:) ||
[item action] == @selector(commandDispatchUsingKeyModifiers:)) {
if (handler) {
// -dispatch:.. can't later decide to bubble events because
// -commandDispatch:.. is assumed to always succeed. So, if there is a
// |handler|, only validate against that for -commandDispatch:.
return [handler validateUserInterfaceItem:item window:owner_];
}
id appController = [NSApp delegate];
DCHECK([appController
conformsToProtocol:@protocol(NSUserInterfaceValidations)]);
if ([appController validateUserInterfaceItem:item])
return YES;
}
// Note this may validate an action bubbled up from a child window. However,
// if the child window also -respondsToSelector: (but validated it `NO`), the
// action will be dispatched to the child only, which may NSBeep().
// TODO(tapted): Fix this. E.g. bubble up validation via the bubbleParent's
// CommandDispatcher rather than the NSUserInterfaceValidations protocol, so
// that this step can be skipped.
if ([owner_ defaultValidateUserInterfaceItem:item])
return YES;
return [[self bubbleParent] validateUserInterfaceItem:item];
}
- (BOOL)redispatchKeyEvent:(NSEvent*)event {
DCHECK(event);
NSEventType eventType = [event type];
if (eventType != NSKeyDown && eventType != NSKeyUp &&
eventType != NSFlagsChanged) {
NOTREACHED();
return YES; // Pretend it's been handled in an effort to limit damage.
}
// Ordinarily, the event's window should be |owner_|. However, when switching
// between normal and fullscreen mode, we switch out the window, and the
// event's window might be the previous window (or even an earlier one if the
// renderer is running slowly and several mode switches occur). In this rare
// case, we synthesize a new key event so that its associate window (number)
// is our |owner_|'s.
if ([event window] != owner_)
event = KeyEventForWindow(owner_, event);
// Redispatch the event.
eventHandled_ = YES;
redispatchingEvent_ = YES;
[NSApp sendEvent:event];
redispatchingEvent_ = NO;
// If the event was not handled by [NSApp sendEvent:], the sendEvent:
// method below will be called, and because |redispatchingEvent_| is YES,
// |eventHandled_| will be set to NO.
return eventHandled_;
}
- (BOOL)preSendEvent:(NSEvent*)event {
if (redispatchingEvent_) {
// If we get here, then the event was not handled by NSApplication.
eventHandled_ = NO;
// Return YES to stop native -sendEvent handling.
return YES;
}
return NO;
}
- (void)dispatch:(id)sender
forHandler:(id<UserInterfaceItemCommandHandler>)handler {
if (handler)
[handler commandDispatch:sender window:owner_];
else
[[self bubbleParent] commandDispatch:sender];
}
- (void)dispatchUsingKeyModifiers:(id)sender
forHandler:(id<UserInterfaceItemCommandHandler>)handler {
if (handler)
[handler commandDispatchUsingKeyModifiers:sender window:owner_];
else
[[self bubbleParent] commandDispatchUsingKeyModifiers:sender];
}
- (NSWindow<CommandDispatchingWindow>*)bubbleParent {
NSWindow* parent = [owner_ parentWindow];
if (parent && [parent hasKeyAppearance] &&
[parent conformsToProtocol:@protocol(CommandDispatchingWindow)])
return static_cast<NSWindow<CommandDispatchingWindow>*>(parent);
return nil;
}
@end
|