]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/DomainBrowser/macOS/CNDomainBrowserView.m
mDNSResponder-878.250.4.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / DomainBrowser / macOS / CNDomainBrowserView.m
1 /*
2 *
3 * Copyright (c) 2016 Apple Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #import "CNDomainBrowserView.h"
19 #import "_CNDomainBrowser.h"
20 #import "CNDomainBrowserPathUtils.h"
21
22 #define DEBUG_POPUP_CELLS 0
23 #define SHOW_SERVICETYPE_IF_SEARCH_COUNT 0
24 #define TEST_LEGACYBROWSE 0
25
26 #define BROWSER_CELL_SPACING 4
27
28 @protocol CNServiceTypeLocalizerDelegate <NSObject>
29 @property (strong) NSDictionary * localizedServiceTypesDictionary;
30 @end
31
32 @interface CNServiceTypeLocalizer : NSValueTransformer
33 {
34 id<CNServiceTypeLocalizerDelegate> _delegate;
35 }
36 - (instancetype)initWithDelegate:(id<CNServiceTypeLocalizerDelegate>)delegate;
37
38 @end
39
40 @implementation CNServiceTypeLocalizer
41
42 - (instancetype)initWithDelegate:(id<CNServiceTypeLocalizerDelegate>)delegate
43 {
44 if (self = [super init])
45 {
46 _delegate = delegate;
47 }
48 return(self);
49 }
50
51 + (Class)transformedValueClass
52 {
53 return [NSString class];
54 }
55
56 + (BOOL)allowsReverseTransformation
57 {
58 return NO;
59 }
60
61 - (nullable id)transformedValue:(nullable id)value
62 {
63 id result = value;
64
65 if (value && _delegate && [_delegate respondsToSelector: @selector(localizedServiceTypesDictionary)])
66 {
67 NSString * localizedValue = [_delegate.localizedServiceTypesDictionary objectForKey: value];
68 if (localizedValue) result = localizedValue;
69 }
70
71 return(result);
72 }
73
74 @end
75
76 @implementation NSBrowser(PathArray)
77
78 - (NSArray *)pathArrayToColumn:(NSInteger)column includeSelectedRow:(BOOL)includeSelection
79 {
80 NSMutableArray * pathArray = [NSMutableArray array];
81 if (!includeSelection) column--;
82 for (NSInteger c = 0 ; c <= column ; c++)
83 {
84 NSBrowserCell *cell = [self selectedCellInColumn: c];
85 if (cell) [pathArray addObject: [cell stringValue]];
86 }
87
88 return(pathArray);
89 }
90
91 @end
92
93 @interface CNDomainBrowserView ()
94
95 @property (strong) _CNDomainBrowser * bonjour;
96 @property (strong) NSSplitView * mainSplitView;
97
98 @property (strong) NSTableView * instanceTable;
99 @property (strong) NSArrayController * instanceC;
100 @property (strong) NSTableColumn * instanceNameColumn;
101 @property (strong) NSTableColumn * instanceServiceTypeColumn;
102 @property (strong) NSTableColumn * instancePathPopupColumn;
103
104 @property (strong) NSBrowser * browser;
105
106 @property (strong) CNServiceTypeLocalizer * serviceTypeLocalizer;
107
108 @end
109
110 @implementation CNDomainBrowserView
111
112 - (instancetype)initWithFrame:(NSRect)frameRect
113 {
114 if (self = [super initWithFrame: frameRect])
115 {
116 [self commonInit];
117 }
118 return(self);
119 }
120
121 - (nullable instancetype)initWithCoder:(NSCoder *)coder
122 {
123 if (self = [super initWithCoder: coder])
124 {
125 [self commonInit];
126 }
127 return(self);
128 }
129
130 - (void)awakeFromNib
131 {
132 [super awakeFromNib];
133
134 self.bonjour = [[_CNDomainBrowser alloc] initWithDelegate:(id<_CNDomainBrowserDelegate>)self];
135 _bonjour.browseRegistration = _browseRegistration;
136 _bonjour.ignoreLocal = _ignoreLocal;
137 _bonjour.ignoreBTMM = _ignoreBTMM;
138 }
139
140
141 - (void)contentViewsInit
142 {
143 NSRect frame = self.frame;
144 self.instanceC = [[NSArrayController alloc] init];
145 self.serviceTypeLocalizer = [[CNServiceTypeLocalizer alloc] initWithDelegate: (id<CNServiceTypeLocalizerDelegate>)self];
146
147 // Bottom browser
148 frame.origin.x = frame.origin.y = 0;
149 NSBrowser * browserView = [[NSBrowser alloc] initWithFrame: frame];
150 browserView.delegate = (id<NSBrowserDelegate>)self;
151 browserView.action = @selector(clickAction:);
152 browserView.titled = NO;
153 browserView.separatesColumns = NO;
154 browserView.allowsEmptySelection = YES;
155 browserView.allowsMultipleSelection = NO;
156 browserView.takesTitleFromPreviousColumn = NO;
157 browserView.hasHorizontalScroller = YES;
158 browserView.columnResizingType = NSBrowserNoColumnResizing;
159 browserView.minColumnWidth = 50;
160 self.browser = browserView;
161
162 [self addSubview: browserView];
163 }
164
165 - (void)commonInit
166 {
167 [self contentViewsInit];
168 }
169
170 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
171 {
172 [super viewWillMoveToWindow: newWindow];
173 if (newWindow)
174 {
175 [self.mainSplitView adjustSubviews];
176 }
177 }
178
179 - (void)setDomainSelectionToPathArray:(NSArray *)pathArray
180 {
181 NSInteger column = 0;
182 for (NSString * nextPathComponent in pathArray)
183 {
184 NSArray * subPath = [self.browser pathArrayToColumn: column includeSelectedRow: NO];
185 NSArray * rowArray = [[self.bonjour subDomainsAtDomainPath: subPath] sortedArrayUsingComparator: ^(id obj1, id obj2) {
186 return (NSComparisonResult)[ obj1[_CNSubDomainKey_subPath] compare: obj2[_CNSubDomainKey_subPath]];
187 }];
188 NSInteger nextRow = [rowArray indexOfObjectPassingTest: ^BOOL(id obj, NSUInteger index, BOOL *stop) {
189 (void)index;
190 (void)stop;
191 return [obj[_CNSubDomainKey_subPath] isEqualToString: nextPathComponent];
192 }];
193 [self.browser selectRow: nextRow inColumn: column++];
194 }
195 }
196
197 - (NSInteger)maxNumberOfVisibleSubDomainRows
198 {
199 NSInteger result = 0;
200
201 for (NSInteger i = self.browser.firstVisibleColumn ; i <= self.browser.lastVisibleColumn ; i++)
202 {
203 NSInteger rows = [self browser: self.browser numberOfRowsInColumn: i];
204 result = MAX(rows, result);
205 }
206
207 return(result);
208 }
209
210 #pragma mark - Public Methods
211
212 - (void)setIgnoreLocal:(BOOL)ignoreLocal
213 {
214 _ignoreLocal = ignoreLocal;
215 self.bonjour.ignoreLocal = _ignoreLocal;
216 }
217
218 - (void)setBrowseRegistration:(BOOL)browseRegistration
219 {
220 _browseRegistration = browseRegistration;
221 self.bonjour.browseRegistration = _browseRegistration;
222 }
223
224 - (NSString *)selectedDNSDomain
225 {
226 NSArray * pathArray = [self.browser pathArrayToColumn: self.browser.selectedColumn includeSelectedRow: YES];
227 return(DomainPathToDNSDomain(pathArray));
228 }
229
230 - (NSString *)defaultDNSDomain
231 {
232 return(DomainPathToDNSDomain(self.bonjour.defaultDomainPath));
233 }
234
235 - (NSArray *)flattenedDNSDomains
236 {
237 return(self.bonjour.flattenedDNSDomains);
238 }
239
240
241 - (void)startBrowse
242 {
243 [self.bonjour startBrowser];
244 }
245
246 - (void)stopBrowse
247 {
248 [self.bonjour stopBrowser];
249 }
250
251 - (BOOL)isBrowsing
252 {
253 return(self.bonjour.isBrowsing);
254 }
255
256 #pragma mark - Notifications
257
258 - (void)browser:(NSBrowser *)sender selectionDidChange:(NSArray *)pathArray
259 {
260 if (_delegate && [_delegate respondsToSelector: @selector(bonjourBrowserDomainSelected:)] &&
261 sender == self.browser)
262 {
263 [_delegate bonjourBrowserDomainSelected: pathArray ? DomainPathToDNSDomain(pathArray) : nil];
264 }
265 }
266
267 #pragma mark - NSBrowserDelegate
268
269 - (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
270 {
271 return ([self.bonjour subDomainsAtDomainPath: [sender pathArrayToColumn: column includeSelectedRow: NO]].count);
272 }
273
274 - (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(NSUInteger)row column:(NSInteger)column
275 {
276 // Get the name
277 NSMutableArray * pathArray = [NSMutableArray arrayWithArray: [sender pathArrayToColumn: column includeSelectedRow: NO]];
278 NSArray * rowArray = [[self.bonjour subDomainsAtDomainPath: pathArray] sortedArrayUsingComparator: ^(id obj1, id obj2) {
279 return (NSComparisonResult)[ obj1[_CNSubDomainKey_subPath] compare: obj2[_CNSubDomainKey_subPath]];
280 }];
281 if (row < rowArray.count)
282 {
283 NSDictionary * item = [rowArray objectAtIndex: row];
284 NSString *val = item[_CNSubDomainKey_subPath];
285 [cell setStringValue: val];
286
287 // See if it's a leaf
288 [pathArray addObject: val];
289 ((NSBrowserCell*)cell).leaf = (![self.bonjour subDomainsAtDomainPath: pathArray].count);
290
291 // Make Default domain bold
292 if ([item[_CNSubDomainKey_defaultFlag] boolValue]) ((NSBrowserCell*)cell).font = [NSFont boldSystemFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]];
293 else ((NSBrowserCell*)cell).font = [NSFont controlContentFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]];
294 }
295 }
296
297 - (CGFloat)browser:(NSBrowser *)sender shouldSizeColumn:(NSInteger)column forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth
298 {
299 (void)forUserResize;
300 CGFloat newSize = 0;
301
302 NSArray * pathArray = [NSArray arrayWithArray: [sender pathArrayToColumn: column includeSelectedRow: NO]];
303 NSArray * rowArray = [[self.bonjour subDomainsAtDomainPath: pathArray] sortedArrayUsingComparator: ^(id obj1, id obj2) {
304 return (NSComparisonResult)[ obj1[_CNSubDomainKey_subPath] compare: obj2[_CNSubDomainKey_subPath]];
305 }];
306
307 for (NSDictionary * next in rowArray)
308 {
309 NSFont * font = [next[_CNSubDomainKey_defaultFlag] boolValue] ?
310 [NSFont boldSystemFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]]:
311 [NSFont controlContentFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]];
312 NSArray * itemArray = [pathArray arrayByAddingObjectsFromArray: [NSArray arrayWithObject: next[_CNSubDomainKey_subPath]]];
313 NSBrowserCell * cell = [[NSBrowserCell alloc] initTextCell: next[_CNSubDomainKey_subPath]];
314 cell.font = font;
315 cell.leaf = ([self.bonjour subDomainsAtDomainPath: itemArray].count == 0);
316 newSize = MAX(newSize, cell.cellSize.width + BROWSER_CELL_SPACING);
317 }
318
319 if (!newSize) newSize = suggestedWidth;
320 newSize = (NSInteger)(newSize + 0.5);
321
322 return(newSize);
323 }
324
325 #pragma mark - _CNDomainBrowser Delegates
326
327 - (void)bonjourBrowserDomainUpdate:(NSArray *)defaultDomainPath
328 {
329 (void)defaultDomainPath;
330 [self.browser loadColumnZero];
331 [self setDomainSelectionToPathArray: self.bonjour.defaultDomainPath];
332 }
333
334 #pragma mark - Commands
335
336 - (IBAction)clickAction:(id)sender
337 {
338 (void)sender;
339 NSArray * pathArray = [self.browser pathArrayToColumn: self.browser.selectedColumn includeSelectedRow: YES];
340 if (!pathArray.count) pathArray = self.bonjour.defaultDomainPath;
341 [self setDomainSelectionToPathArray: pathArray];
342 [self browser: self.browser selectionDidChange: pathArray];
343 }
344
345 @end
346
347 @interface CNBonjourDomainCell()
348
349 @property(strong) NSMutableArray * browserCells;
350
351 @end
352
353 @implementation CNBonjourDomainCell
354
355 - (instancetype)init
356 {
357 self = [super init];
358 if (self)
359 {
360 self.browserCells = [NSMutableArray array];
361 }
362 return self;
363 }
364
365 - (instancetype)initWithCoder:(NSCoder *)coder
366 {
367 self = [super initWithCoder:coder];
368 if (self)
369 {
370 self.browserCells = [NSMutableArray array];
371 }
372 return self;
373 }
374
375 - (id)copyWithZone:(NSZone *)zone
376 {
377 CNBonjourDomainCell *cell = [super copyWithZone: zone];
378 if (cell)
379 {
380 cell.browserCells = [NSMutableArray arrayWithArray: self.browserCells];
381 }
382 return cell;
383 }
384
385 - (void) setObjectValue:(id)objectValue
386 {
387 [super setObjectValue: objectValue];
388
389 [self.browserCells removeAllObjects];
390 if ([objectValue isKindOfClass: [NSString class]])
391 {
392 NSUInteger count = 0;
393 NSArray * subPaths = DNSDomainToDomainPath(objectValue);
394 for (NSString * nextPath in subPaths)
395 {
396 NSBrowserCell * nextCell = [[NSBrowserCell alloc] initTextCell: nextPath];
397 nextCell.leaf = (++count == subPaths.count);
398 [self.browserCells addObject: nextCell];
399 }
400 }
401 }
402
403 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
404 {
405 CGFloat usedWidth = BROWSER_CELL_SPACING / 2;
406 for (NSBrowserCell * nextCell in self.browserCells)
407 {
408 NSRect nextRect = cellFrame;
409 nextRect.size.width = cellFrame.size.width - usedWidth;
410 nextRect.origin.x += usedWidth;
411
412 NSSize cellSize = [nextCell cellSizeForBounds: nextRect];
413 CGFloat yOffset = (nextRect.size.height - cellSize.height) / 2;
414 nextRect.size.width = cellSize.width;
415 nextRect.size.height = cellSize.height;
416 nextRect.origin.y += yOffset;
417
418 [nextCell drawInteriorWithFrame: nextRect
419 inView: controlView];
420 usedWidth += nextRect.size.width + BROWSER_CELL_SPACING;
421 }
422 }
423
424 @end
425
426 @interface CNBonjourDomainView()
427
428 @property(strong) CNBonjourDomainCell * cell;
429
430 @end
431
432 @implementation CNBonjourDomainView
433
434 - (instancetype)initWithFrame:(NSRect)frameRect
435 {
436 self = [super initWithFrame:frameRect];
437 if (self)
438 {
439 self.cell = [[CNBonjourDomainCell alloc] init];
440 }
441 return self;
442 }
443
444 - (instancetype)initWithCoder:(NSCoder *)coder
445 {
446 self = [super initWithCoder:coder];
447 if (self)
448 {
449 self.cell = [[CNBonjourDomainCell alloc] init];
450 }
451 return self;
452 }
453
454 - (void) setDomain:(NSString *)domain
455 {
456 if (![domain isEqualToString: self.cell.stringValue])
457 {
458 self.cell.stringValue = domain;
459 self.needsDisplay = YES;
460 }
461 }
462
463 - (NSString *)domain
464 {
465 return self.cell.stringValue;
466 }
467
468 - (void) drawRect:(NSRect)dirtyRect
469 {
470 (void)dirtyRect; // Unused
471 [self.cell drawInteriorWithFrame: self.bounds inView: self];
472 }
473
474 @end