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 DEBUG_POPUP_CELLS 0
23 #define SHOW_SERVICETYPE_IF_SEARCH_COUNT 0
24 #define TEST_LEGACYBROWSE 0
26 #define BROWSER_CELL_SPACING 4
28 @protocol CNServiceTypeLocalizerDelegate <NSObject>
29 @property (strong) NSDictionary * localizedServiceTypesDictionary;
32 @interface CNServiceTypeLocalizer : NSValueTransformer
34 id<CNServiceTypeLocalizerDelegate> _delegate;
36 - (instancetype)initWithDelegate:(id<CNServiceTypeLocalizerDelegate>)delegate;
40 @implementation CNServiceTypeLocalizer
42 - (instancetype)initWithDelegate:(id<CNServiceTypeLocalizerDelegate>)delegate
44 if (self = [super init])
51 + (Class)transformedValueClass
53 return [NSString class];
56 + (BOOL)allowsReverseTransformation
61 - (nullable id)transformedValue:(nullable id)value
65 if (value && _delegate && [_delegate respondsToSelector: @selector(localizedServiceTypesDictionary)])
67 NSString * localizedValue = [_delegate.localizedServiceTypesDictionary objectForKey: value];
68 if (localizedValue) result = localizedValue;
76 @implementation NSBrowser(PathArray)
78 - (NSArray *)pathArrayToColumn:(NSInteger)column includeSelectedRow:(BOOL)includeSelection
80 NSMutableArray * pathArray = [NSMutableArray array];
81 if (!includeSelection) column--;
82 for (NSInteger c = 0 ; c <= column ; c++)
84 NSBrowserCell *cell = [self selectedCellInColumn: c];
85 if (cell) [pathArray addObject: [cell stringValue]];
93 @interface CNDomainBrowserView ()
95 @property (strong) _CNDomainBrowser * bonjour;
96 @property (strong) NSSplitView * mainSplitView;
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;
104 @property (strong) NSBrowser * browser;
106 @property (strong) CNServiceTypeLocalizer * serviceTypeLocalizer;
110 @implementation CNDomainBrowserView
112 - (instancetype)initWithFrame:(NSRect)frameRect
114 if (self = [super initWithFrame: frameRect])
121 - (nullable instancetype)initWithCoder:(NSCoder *)coder
123 if (self = [super initWithCoder: coder])
132 [super awakeFromNib];
134 self.bonjour = [[_CNDomainBrowser alloc] initWithDelegate:(id<_CNDomainBrowserDelegate>)self];
135 _bonjour.browseRegistration = _browseRegistration;
136 _bonjour.ignoreLocal = _ignoreLocal;
137 _bonjour.ignoreBTMM = _ignoreBTMM;
141 - (void)contentViewsInit
143 NSRect frame = self.frame;
144 self.instanceC = [[NSArrayController alloc] init];
145 self.serviceTypeLocalizer = [[CNServiceTypeLocalizer alloc] initWithDelegate: (id<CNServiceTypeLocalizerDelegate>)self];
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;
162 [self addSubview: browserView];
167 [self contentViewsInit];
170 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
172 [super viewWillMoveToWindow: newWindow];
175 [self.mainSplitView adjustSubviews];
179 - (void)setDomainSelectionToPathArray:(NSArray *)pathArray
181 NSInteger column = 0;
182 for (NSString * nextPathComponent in pathArray)
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]];
188 NSInteger nextRow = [rowArray indexOfObjectPassingTest: ^BOOL(id obj, NSUInteger index, BOOL *stop) {
191 return [obj[_CNSubDomainKey_subPath] isEqualToString: nextPathComponent];
193 [self.browser selectRow: nextRow inColumn: column++];
197 - (NSInteger)maxNumberOfVisibleSubDomainRows
199 NSInteger result = 0;
201 for (NSInteger i = self.browser.firstVisibleColumn ; i <= self.browser.lastVisibleColumn ; i++)
203 NSInteger rows = [self browser: self.browser numberOfRowsInColumn: i];
204 result = MAX(rows, result);
210 #pragma mark - Public Methods
212 - (void)setIgnoreLocal:(BOOL)ignoreLocal
214 _ignoreLocal = ignoreLocal;
215 self.bonjour.ignoreLocal = _ignoreLocal;
218 - (void)setBrowseRegistration:(BOOL)browseRegistration
220 _browseRegistration = browseRegistration;
221 self.bonjour.browseRegistration = _browseRegistration;
224 - (NSString *)selectedDNSDomain
226 NSArray * pathArray = [self.browser pathArrayToColumn: self.browser.selectedColumn includeSelectedRow: YES];
227 return(DomainPathToDNSDomain(pathArray));
230 - (NSString *)defaultDNSDomain
232 return(DomainPathToDNSDomain(self.bonjour.defaultDomainPath));
235 - (NSArray *)flattenedDNSDomains
237 return(self.bonjour.flattenedDNSDomains);
243 [self.bonjour startBrowser];
248 [self.bonjour stopBrowser];
253 return(self.bonjour.isBrowsing);
256 #pragma mark - Notifications
258 - (void)browser:(NSBrowser *)sender selectionDidChange:(NSArray *)pathArray
260 if (_delegate && [_delegate respondsToSelector: @selector(bonjourBrowserDomainSelected:)] &&
261 sender == self.browser)
263 [_delegate bonjourBrowserDomainSelected: pathArray ? DomainPathToDNSDomain(pathArray) : nil];
267 #pragma mark - NSBrowserDelegate
269 - (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
271 return ([self.bonjour subDomainsAtDomainPath: [sender pathArrayToColumn: column includeSelectedRow: NO]].count);
274 - (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(NSUInteger)row column:(NSInteger)column
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]];
281 if (row < rowArray.count)
283 NSDictionary * item = [rowArray objectAtIndex: row];
284 NSString *val = item[_CNSubDomainKey_subPath];
285 [cell setStringValue: val];
287 // See if it's a leaf
288 [pathArray addObject: val];
289 ((NSBrowserCell*)cell).leaf = (![self.bonjour subDomainsAtDomainPath: pathArray].count);
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]];
297 - (CGFloat)browser:(NSBrowser *)sender shouldSizeColumn:(NSInteger)column forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth
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]];
307 for (NSDictionary * next in rowArray)
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]];
315 cell.leaf = ([self.bonjour subDomainsAtDomainPath: itemArray].count == 0);
316 newSize = MAX(newSize, cell.cellSize.width + BROWSER_CELL_SPACING);
319 if (!newSize) newSize = suggestedWidth;
320 newSize = (NSInteger)(newSize + 0.5);
325 #pragma mark - _CNDomainBrowser Delegates
327 - (void)bonjourBrowserDomainUpdate:(NSArray *)defaultDomainPath
329 (void)defaultDomainPath;
330 [self.browser loadColumnZero];
331 [self setDomainSelectionToPathArray: self.bonjour.defaultDomainPath];
334 #pragma mark - Commands
336 - (IBAction)clickAction:(id)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];
347 @interface CNBonjourDomainCell()
349 @property(strong) NSMutableArray * browserCells;
353 @implementation CNBonjourDomainCell
360 self.browserCells = [NSMutableArray array];
365 - (instancetype)initWithCoder:(NSCoder *)coder
367 self = [super initWithCoder:coder];
370 self.browserCells = [NSMutableArray array];
375 - (id)copyWithZone:(NSZone *)zone
377 CNBonjourDomainCell *cell = [super copyWithZone: zone];
380 cell.browserCells = [NSMutableArray arrayWithArray: self.browserCells];
385 - (void) setObjectValue:(id)objectValue
387 [super setObjectValue: objectValue];
389 [self.browserCells removeAllObjects];
390 if ([objectValue isKindOfClass: [NSString class]])
392 NSUInteger count = 0;
393 NSArray * subPaths = DNSDomainToDomainPath(objectValue);
394 for (NSString * nextPath in subPaths)
396 NSBrowserCell * nextCell = [[NSBrowserCell alloc] initTextCell: nextPath];
397 nextCell.leaf = (++count == subPaths.count);
398 [self.browserCells addObject: nextCell];
403 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
405 CGFloat usedWidth = BROWSER_CELL_SPACING / 2;
406 for (NSBrowserCell * nextCell in self.browserCells)
408 NSRect nextRect = cellFrame;
409 nextRect.size.width = cellFrame.size.width - usedWidth;
410 nextRect.origin.x += usedWidth;
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;
418 [nextCell drawInteriorWithFrame: nextRect
419 inView: controlView];
420 usedWidth += nextRect.size.width + BROWSER_CELL_SPACING;
426 @interface CNBonjourDomainView()
428 @property(strong) CNBonjourDomainCell * cell;
432 @implementation CNBonjourDomainView
434 - (instancetype)initWithFrame:(NSRect)frameRect
436 self = [super initWithFrame:frameRect];
439 self.cell = [[CNBonjourDomainCell alloc] init];
444 - (instancetype)initWithCoder:(NSCoder *)coder
446 self = [super initWithCoder:coder];
449 self.cell = [[CNBonjourDomainCell alloc] init];
454 - (void) setDomain:(NSString *)domain
456 if (![domain isEqualToString: self.cell.stringValue])
458 self.cell.stringValue = domain;
459 self.needsDisplay = YES;
465 return self.cell.stringValue;
468 - (void) drawRect:(NSRect)dirtyRect
470 (void)dirtyRect; // Unused
471 [self.cell drawInteriorWithFrame: self.bounds inView: self];