3 * Copyright (c) 2016 Apple Inc. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #import "CNDomainBrowserView.h"
19 #import "_CNDomainBrowser.h"
20 #import "CNDomainBrowserPathUtils.h"
22 #define BROWSER_CELL_SPACING 4
23 #define INITIAL_LEGACYBROWSE 1
25 @implementation NSBrowser(PathArray)
27 - (NSArray *)pathArrayToColumn:(NSInteger)column includeSelectedRow:(BOOL)includeSelection
29 NSMutableArray * pathArray = [NSMutableArray array];
30 if (!includeSelection) column--;
31 for (NSInteger c = 0 ; c <= column ; c++)
33 NSBrowserCell *cell = [self selectedCellInColumn: c];
34 if (cell) [pathArray addObject: [cell stringValue]];
42 @interface CNDomainBrowserView ()
44 @property (strong) _CNDomainBrowser * bonjour;
46 @property (strong) NSTableView * instanceTable;
47 @property (strong) NSArrayController * instanceC;
48 @property (strong) NSTableColumn * instanceNameColumn;
49 @property (strong) NSTableColumn * instanceServiceTypeColumn;
50 @property (strong) NSTableColumn * instancePathPopupColumn;
52 @property (strong) NSBrowser * browser;
53 #if INITIAL_LEGACYBROWSE
54 @property (assign) BOOL initialPathSet;
59 @implementation CNDomainBrowserView
61 - (instancetype)initWithFrame:(NSRect)frameRect
63 if (self = [super initWithFrame: frameRect])
70 - (nullable instancetype)initWithCoder:(NSCoder *)coder
72 if (self = [super initWithCoder: coder])
83 self.bonjour = [[_CNDomainBrowser alloc] initWithDelegate:(id<_CNDomainBrowserDelegate>)self];
84 _bonjour.browseRegistration = _browseRegistration;
85 _bonjour.ignoreLocal = _ignoreLocal;
86 _bonjour.ignoreBTMM = _ignoreBTMM;
90 - (void)contentViewsInit
92 NSRect frame = self.frame;
93 self.instanceC = [[NSArrayController alloc] init];
96 frame.origin.x = frame.origin.y = 0;
97 NSBrowser * browserView = [[NSBrowser alloc] initWithFrame: frame];
98 browserView.delegate = (id<NSBrowserDelegate>)self;
99 browserView.action = @selector(clickAction:);
100 browserView.titled = NO;
101 browserView.separatesColumns = NO;
102 browserView.allowsEmptySelection = YES;
103 browserView.allowsMultipleSelection = NO;
104 browserView.takesTitleFromPreviousColumn = NO;
105 browserView.hasHorizontalScroller = YES;
106 browserView.columnResizingType = NSBrowserNoColumnResizing;
107 browserView.minColumnWidth = 50;
108 browserView.translatesAutoresizingMaskIntoConstraints = NO;
109 self.browser = browserView;
111 [self addSubview: browserView];
114 [NSLayoutConstraint constraintWithItem:_browser
115 attribute:NSLayoutAttributeLeft
116 relatedBy:NSLayoutRelationEqual
118 attribute:NSLayoutAttributeLeft
122 [NSLayoutConstraint constraintWithItem:_browser
123 attribute:NSLayoutAttributeRight
124 relatedBy:NSLayoutRelationEqual
126 attribute:NSLayoutAttributeRight
130 [NSLayoutConstraint constraintWithItem:_browser
131 attribute:NSLayoutAttributeBottom
132 relatedBy:NSLayoutRelationEqual
134 attribute:NSLayoutAttributeBottom
138 [NSLayoutConstraint constraintWithItem:_browser
139 attribute:NSLayoutAttributeTop
140 relatedBy:NSLayoutRelationEqual
142 attribute:NSLayoutAttributeTop
149 [self contentViewsInit];
152 - (void)viewWillMoveToSuperview:(NSView *)newSuperview
154 [super viewWillMoveToSuperview: newSuperview];
155 if (newSuperview && !_bonjour)
161 - (void)setDomainSelectionToPathArray:(NSArray *)pathArray
163 NSInteger column = 0;
164 for (NSString * nextPathComponent in pathArray)
166 NSArray * subPath = [self.browser pathArrayToColumn: column includeSelectedRow: NO];
167 NSArray * rowArray = [[self.bonjour subDomainsAtDomainPath: subPath] sortedArrayUsingComparator: ^(id obj1, id obj2) {
168 return (NSComparisonResult)[ obj1[_CNSubDomainKey_subPath] compare: obj2[_CNSubDomainKey_subPath]];
170 NSInteger nextRow = [rowArray indexOfObjectPassingTest: ^BOOL(id obj, NSUInteger index, BOOL *stop) {
173 return [obj[_CNSubDomainKey_subPath] isEqualToString: nextPathComponent];
175 [self.browser selectRow: nextRow inColumn: column++];
179 - (NSInteger)maxNumberOfVisibleSubDomainRows
181 NSInteger result = 0;
183 for (NSInteger i = self.browser.firstVisibleColumn ; i <= self.browser.lastVisibleColumn ; i++)
185 NSInteger rows = [self browser: self.browser numberOfRowsInColumn: i];
186 result = MAX(rows, result);
192 #pragma mark - Public Methods
194 - (void)setIgnoreLocal:(BOOL)ignoreLocal
196 _ignoreLocal = ignoreLocal;
197 self.bonjour.ignoreLocal = _ignoreLocal;
200 - (void)setBrowseRegistration:(BOOL)browseRegistration
202 _browseRegistration = browseRegistration;
203 self.bonjour.browseRegistration = _browseRegistration;
206 - (NSString *)selectedDNSDomain
208 NSArray * pathArray = [self.browser pathArrayToColumn: self.browser.selectedColumn includeSelectedRow: YES];
209 return(DomainPathToDNSDomain(pathArray));
212 - (NSString *)defaultDNSDomain
214 return(DomainPathToDNSDomain(self.bonjour.defaultDomainPath));
217 - (NSArray *)flattenedDNSDomains
219 return(self.bonjour.flattenedDNSDomains);
225 [self.bonjour startBrowser];
230 [self.bonjour stopBrowser];
231 _initialPathSet = NO;
236 return(self.bonjour.isBrowsing);
239 - (CGFloat)minimumHeight
241 return self.selectedDNSDomain.length ? [self.browser frameOfRow: [self.browser selectedRowInColumn: self.browser.lastVisibleColumn] inColumn: self.browser.lastVisibleColumn].size.height : 0.0;
244 - (void)showSelectedRow
246 for( NSInteger i = self.browser.firstVisibleColumn ; i <= self.browser.lastVisibleColumn ; i++ )
248 NSInteger selRow = [self.browser selectedRowInColumn: i];
249 if( selRow != NSNotFound ) [self.browser scrollRowToVisible: selRow inColumn: i];
253 - (BOOL)foundInstanceInMoreThanLocalDomain
255 return( [_bonjour foundInstanceInMoreThanLocalDomain] );
259 #pragma mark - Notifications
261 - (void)browser:(NSBrowser *)sender selectionDidChange:(NSArray *)pathArray
263 if (_delegate && [_delegate respondsToSelector: @selector(domainBrowserDomainSelected:)] &&
264 sender == self.browser)
266 [_delegate domainBrowserDomainSelected: pathArray ? DomainPathToDNSDomain(pathArray) : nil];
270 #pragma mark - NSBrowserDelegate
272 - (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
274 return ([self.bonjour subDomainsAtDomainPath: [sender pathArrayToColumn: column includeSelectedRow: NO]].count);
277 - (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(NSUInteger)row column:(NSInteger)column
280 NSMutableArray * pathArray = [NSMutableArray arrayWithArray: [sender pathArrayToColumn: column includeSelectedRow: NO]];
281 NSArray * rowArray = [[self.bonjour subDomainsAtDomainPath: pathArray] sortedArrayUsingComparator: ^(id obj1, id obj2) {
282 return (NSComparisonResult)[ obj1[_CNSubDomainKey_subPath] compare: obj2[_CNSubDomainKey_subPath]];
284 if (row < rowArray.count)
286 NSDictionary * item = [rowArray objectAtIndex: row];
287 NSString *val = item[_CNSubDomainKey_subPath];
288 [cell setStringValue: val];
290 // See if it's a leaf
291 [pathArray addObject: val];
292 ((NSBrowserCell*)cell).leaf = (![self.bonjour subDomainsAtDomainPath: pathArray].count);
294 // Make Default domain bold
295 if ([item[_CNSubDomainKey_defaultFlag] boolValue]) ((NSBrowserCell*)cell).font = [NSFont boldSystemFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]];
296 else ((NSBrowserCell*)cell).font = [NSFont controlContentFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]];
300 - (CGFloat)browser:(NSBrowser *)sender shouldSizeColumn:(NSInteger)column forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth
305 NSArray * pathArray = [NSArray arrayWithArray: [sender pathArrayToColumn: column includeSelectedRow: NO]];
306 NSArray * rowArray = [[self.bonjour subDomainsAtDomainPath: pathArray] sortedArrayUsingComparator: ^(id obj1, id obj2) {
307 return (NSComparisonResult)[ obj1[_CNSubDomainKey_subPath] compare: obj2[_CNSubDomainKey_subPath]];
310 for (NSDictionary * next in rowArray)
312 NSFont * font = [next[_CNSubDomainKey_defaultFlag] boolValue] ?
313 [NSFont boldSystemFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]]:
314 [NSFont controlContentFontOfSize: [NSFont systemFontSizeForControlSize: sender.controlSize]];
315 NSArray * itemArray = [pathArray arrayByAddingObjectsFromArray: [NSArray arrayWithObject: next[_CNSubDomainKey_subPath]]];
316 NSBrowserCell * cell = [[NSBrowserCell alloc] initTextCell: next[_CNSubDomainKey_subPath]];
318 cell.leaf = ([self.bonjour subDomainsAtDomainPath: itemArray].count == 0);
319 newSize = MAX(newSize, cell.cellSize.width + BROWSER_CELL_SPACING);
322 if (!newSize) newSize = suggestedWidth;
323 newSize = (NSInteger)(newSize + 0.5);
328 #pragma mark - _CNDomainBrowser Delegates
330 - (void)bonjourBrowserDomainUpdate:(NSArray *)defaultDomainPath
332 (void)defaultDomainPath;
333 [self.browser loadColumnZero];
334 #if INITIAL_LEGACYBROWSE
335 if( !_initialPathSet )
337 _initialPathSet = YES;
338 [_delegate domainBrowserDomainUpdate: [NSString string]];
343 [self setDomainSelectionToPathArray: self.bonjour.defaultDomainPath];
344 if (_delegate && [_delegate respondsToSelector: @selector(domainBrowserDomainUpdate:)])
346 [_delegate domainBrowserDomainUpdate: defaultDomainPath ? DomainPathToDNSDomain(defaultDomainPath) : [NSString string]];
351 #pragma mark - Commands
353 - (IBAction)clickAction:(id)sender
356 NSArray * pathArray = [self.browser pathArrayToColumn: self.browser.selectedColumn includeSelectedRow: YES];
357 if (!pathArray.count && (([NSEvent modifierFlags] & NSEventModifierFlagOption ) != NSEventModifierFlagOption)) pathArray = self.bonjour.defaultDomainPath;
358 [self setDomainSelectionToPathArray: pathArray];
359 [self browser: self.browser selectionDidChange: pathArray];
364 @interface CNBonjourDomainCell()
366 @property(strong) NSMutableArray * browserCells;
370 @implementation CNBonjourDomainCell
377 self.browserCells = [NSMutableArray array];
382 - (instancetype)initWithCoder:(NSCoder *)coder
384 self = [super initWithCoder:coder];
387 self.browserCells = [NSMutableArray array];
392 - (id)copyWithZone:(NSZone *)zone
394 CNBonjourDomainCell *cell = [super copyWithZone: zone];
397 cell.browserCells = [NSMutableArray arrayWithArray: self.browserCells];
402 - (void) setObjectValue:(id)objectValue
404 [super setObjectValue: objectValue];
406 [self.browserCells removeAllObjects];
407 if ([objectValue isKindOfClass: [NSString class]])
409 NSUInteger count = 0;
410 NSArray * subPaths = DNSDomainToDomainPath(objectValue);
411 for (NSString * nextPath in subPaths)
413 NSBrowserCell * nextCell = [[NSBrowserCell alloc] initTextCell: nextPath];
414 nextCell.leaf = (++count == subPaths.count);
415 [self.browserCells addObject: nextCell];
420 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
422 CGFloat usedWidth = BROWSER_CELL_SPACING / 2;
423 for (NSBrowserCell * nextCell in self.browserCells)
425 NSRect nextRect = cellFrame;
426 nextRect.size.width = cellFrame.size.width - usedWidth;
427 nextRect.origin.x += usedWidth;
429 NSSize cellSize = [nextCell cellSizeForBounds: nextRect];
430 CGFloat yOffset = (nextRect.size.height - cellSize.height) / 2;
431 nextRect.size.width = cellSize.width;
432 nextRect.size.height = cellSize.height;
433 nextRect.origin.y += yOffset;
435 [nextCell drawInteriorWithFrame: nextRect
436 inView: controlView];
437 usedWidth += nextRect.size.width + BROWSER_CELL_SPACING;
443 @interface CNBonjourDomainView()
445 @property(strong) CNBonjourDomainCell * cell;
449 @implementation CNBonjourDomainView
451 - (instancetype)initWithFrame:(NSRect)frameRect
453 self = [super initWithFrame:frameRect];
456 self.cell = [[CNBonjourDomainCell alloc] init];
461 - (instancetype)initWithCoder:(NSCoder *)coder
463 self = [super initWithCoder:coder];
466 self.cell = [[CNBonjourDomainCell alloc] init];
471 - (void) setDomain:(NSString *)domain
473 if (![domain isEqualToString: self.cell.stringValue])
475 self.cell.stringValue = domain;
476 self.needsDisplay = YES;
482 return self.cell.stringValue;
485 - (void) drawRect:(NSRect)dirtyRect
487 (void)dirtyRect; // Unused
488 [self.cell drawInteriorWithFrame: self.bounds inView: self];