-
Notifications
You must be signed in to change notification settings - Fork 2
/
XSWindowController.m
executable file
·337 lines (276 loc) · 11.2 KB
/
XSWindowController.m
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
//
// XSWindowController.m
// View Controllers
//
// Created by Jonathan Dann and Cathy Shive on 14/04/2008.
//
// Copyright (c) 2008 Jonathan Dann and Cathy Shive
// Copyright (c) 2013 Jonathan Mitchell
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// If you use it, acknowledgement in an About Page or other appropriate place would be nice.
// For example, "Contains "View Controllers" by Jonathan Dann and Cathy Shive" will do.
#import "XSWindowController.h"
#import "XSViewController.h"
static BOOL m_addControllersToResponderChainInAscendingOrder = YES;
@interface XSWindowController ()
@property(nonatomic,copy) NSMutableArray *respondingViewControllers;
- (void)configureViewController:(XSViewController *)viewController;
- (void)configureViewControllers:(NSArray *)viewControllers;
@end
@implementation XSWindowController
@synthesize responderChainPatchRoot = _responderChainPatchRoot;
+ (BOOL)addControllersToResponderChainInAscendingOrder
{
return m_addControllersToResponderChainInAscendingOrder;
}
+ (void)setAddControllersToResponderChainInAscendingOrder:(BOOL)value
{
m_addControllersToResponderChainInAscendingOrder = value;
}
#pragma mark -
#pragma mark Lifecycle
- (id)initWithWindow:(NSWindow *)window // designated initialiser
{
self = [super initWithWindow:window];
if (self) {
_respondingViewControllers = [NSMutableArray array];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder // designated initialiser
{
self = [super initWithCoder:coder];
if (self) {
_respondingViewControllers = [NSMutableArray array];
}
return self;
}
- (void)dealloc
{
NSInteger respondingViewControllers = [self countOfRespondingViewControllers];
if (respondingViewControllers != 0) {
NSLog(@"%@ - Warning: controllers are still present in the responder chain.", [self className]);
for (XSViewController *viewController in self.respondingViewControllers) {
NSLog(@"Found controller : %@ ", [viewController className]);
}
}
}
#pragma mark -
#pragma mark Window management
- (void)windowWillClose:(NSNotification *)notification
{
[self.respondingViewControllers makeObjectsPerformSelector:@selector(removeObservations)];
}
- (void)windowDidLoad
{
[super windowDidLoad];
[self patchResponderChain];
}
#pragma mark -
#pragma mark View controller management
- (void)setRespondingViewControllers:(NSMutableArray *)newViewControllers
{
if (_respondingViewControllers == newViewControllers) {
return;
}
NSMutableArray *newViewControllersCopy = [newViewControllers mutableCopy];
_respondingViewControllers = newViewControllersCopy;
}
- (NSUInteger)countOfRespondingViewControllers
{
return [self.respondingViewControllers count];
}
- (XSViewController *)objectInRespondingViewControllersAtIndex:(NSUInteger)index
{
return [self.respondingViewControllers objectAtIndex:index];
}
- (void)addRespondingViewController:(XSViewController *)viewController
{
[self configureViewController:viewController];
[self.respondingViewControllers insertObject:viewController atIndex:[self.respondingViewControllers count]];
[self patchResponderChain];
}
- (void)insertObject:(XSViewController *)viewController inRespondingViewControllersAtIndex:(NSUInteger)index
{
[self configureViewController:viewController];
[self.respondingViewControllers insertObject:viewController atIndex:index];
[self patchResponderChain];
}
- (void)insertObjects:(NSArray *)viewControllers inViewControllersAtIndexes:(NSIndexSet *)indexes
{
[self configureViewControllers:viewControllers];
[self.respondingViewControllers insertObjects:viewControllers atIndexes:indexes];
[self patchResponderChain];
}
- (void)insertObjects:(NSArray *)viewControllers inViewControllersAtIndex:(NSUInteger)index
{
[self configureViewControllers:viewControllers];
[self insertObjects:viewControllers inViewControllersAtIndexes:[NSIndexSet indexSetWithIndex:index]];
}
- (void)removeRespondingViewController:(XSViewController *)viewController
{
if ([self.respondingViewControllers containsObject:viewController]) {
[self.respondingViewControllers removeObject:viewController];
}
[self patchResponderChain];
}
- (void)removeAllRespondingViewControllers
{
if (self.respondingViewControllers.count == 0) {
return;
}
[self.respondingViewControllers removeAllObjects];
[self patchResponderChain];
}
- (void)removeObjectFromRespondingViewControllersAtIndex:(NSUInteger)index
{
[self.respondingViewControllers removeObjectAtIndex:index];
[self patchResponderChain];
}
#pragma mark -
#pragma mark View controller configuration
- (void)configureViewController:(XSViewController *)viewController
{
NSAssert([viewController isKindOfClass:[XSViewController class]], @"Invalid view controller class");
if (viewController.windowController != self) {
viewController.windowController = self;
}
}
- (void)configureViewControllers:(NSArray *)viewControllers
{
for (XSViewController *viewController in viewControllers) {
[self configureViewController:viewController];
}
}
#pragma mark -
#pragma mark Responder chain management
- (void)setResponderChainPatchRoot:(NSResponder *)rootResponder
{
_responderChainPatchRoot = rootResponder;
if (_responderChainPatchRoot != self && _responderChainPatchRoot != self.window && _responderChainPatchRoot != nil) {
NSLog(@"The responder chain patch root has been set to an unanticipated object: %@", rootResponder);
}
[self patchResponderChain];
}
- (NSResponder *)responderChainPatchRoot
{
// Default to patching from the window
if (!_responderChainPatchRoot) {
return [self window];
}
return _responderChainPatchRoot;
}
- (void)patchResponderChain
{
// note that the declaration for nextresponder is @property (nullable, assign) NSResponder *nextResponder;
// this is not the same as weak; it is the same as __unsafe_unretained.
// hence we need to ensure that controllers get removed from the chain before they deallocate.
// otherwise we start talking to zombies.
// We are being called by view controllers at the beginning of creating the tree,
// most likely load time and the root of the tree hasn't been added to our list of controllers.
if ([self.respondingViewControllers count] == 0) {
if (self.responderChainPatchRoot == self.window) {
if (self.responderChainPatchRoot.nextResponder != self) {
self.responderChainPatchRoot.nextResponder = self;
}
}
return;
}
// Get the responders
NSArray *flatViewControllers = [self respondingDescendants];
// Start building from the patch root
XSViewController *nextViewController = [flatViewControllers objectAtIndex:0];
[self.responderChainPatchRoot setNextResponder:nextViewController];
NSUInteger index = 0;
NSUInteger viewControllerCount = [flatViewControllers count] - 1;
// Set the next responder of each controller to the next, the last in the array has no default next responder.
for (index = 0; index < viewControllerCount ; index++) {
nextViewController = [flatViewControllers objectAtIndex:index + 1];
[[flatViewControllers objectAtIndex:index] setNextResponder:nextViewController];
}
// Append the window controller to the chain when building from the window
if (self.responderChainPatchRoot == self.window) {
if (self.responderChainPatchRoot.nextResponder != self) {
nextViewController.nextResponder = self;
}
}
}
+ (void)patchResponderChain:(XSViewController * )vc
{
/*
The responder chain is being constructed or modified either before being added to a window
or while the view hierachy has been removed from its window controller. This means that a controller
always has access to at least a partial responder chain.
*/
NSArray *flatViewControllers = [vc respondingDescendants];
if (flatViewControllers.count == 0) {
return;
}
// reverse the order to build from the children up
if (self.addControllersToResponderChainInAscendingOrder) {
flatViewControllers = [flatViewControllers xsv_reverse];
}
NSUInteger index = 0;
NSUInteger viewControllerCount = [flatViewControllers count] - 1;
XSViewController *nextViewController = [flatViewControllers objectAtIndex:0];
// Set the next responder of each controller to the next, the last in the array has no default next responder.
for (index = 0; index < viewControllerCount ; index++) {
nextViewController = [flatViewControllers objectAtIndex:index + 1];
[[flatViewControllers objectAtIndex:index] setNextResponder:nextViewController];
}
nextViewController.nextResponder = vc;
}
- (NSArray *)respondingDescendants
{
NSMutableArray *flatViewControllers = [NSMutableArray array];
// flatten the view controllers into an array
for (XSViewController *viewController in self.respondingViewControllers) {
[flatViewControllers addObject:viewController];
[flatViewControllers addObjectsFromArray:[viewController respondingDescendants]];
}
// reverse the order to build from the children up
if (self.class.addControllersToResponderChainInAscendingOrder) {
return [flatViewControllers xsv_reverse];
}
return flatViewControllers;
}
- (void)logResponderChain
{
// Note that for events the responder chain naturally terminates with the window controller:
// NSView -> .. -> NSView -> NSWindow -> NSWindowController
//
// For action messages the chain is more elaborate, eg: document based app:
// NSView -> .. -> NSView -> NSWindow -> NSWindowController -> NSWindow delegate -> NSDocument -> NSApp -> AppDelegate -> NSDocumentController
//
// Notes:
// 1. The actually responders called will of course on the view controller hierarchy and its configuration.
// 2. This method generally reports an event style responder chain.
//
NSResponder *responder = [self.window firstResponder];
NSLog(@"First responder: %@", responder);
while ((responder = [responder nextResponder])) {
NSLog(@"%@ responder: %@", ([responder nextResponder] ? @"Next" : @"Last"), responder);
}
}
@end