/* * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #import #import static NSString* const AUTOCOMPLETE_IDENTIFIER = @"Autocomplete"; static NSString* const AUTOCOMPLETE_SECTION_HEADER_IDENTIFIER = @"AutocompleteSectionHeader"; static constexpr CGFloat const POPOVER_PADDING = 8; static constexpr CGFloat const MINIMUM_WIDTH = 100; static constexpr CGFloat const CELL_HORIZONTAL_PADDING = 8; static constexpr CGFloat const CELL_VERTICAL_PADDING = 10; static constexpr CGFloat const CELL_ICON_SIZE = 16; static constexpr CGFloat const CELL_ICON_TEXT_SPACING = 6; static constexpr CGFloat const CELL_LABEL_VERTICAL_SPACING = 5; static constexpr CGFloat const SECTION_HEADER_HORIZONTAL_PADDING = 10; static constexpr CGFloat const SECTION_HEADER_VERTICAL_PADDING = 4; static NSFont* autocomplete_primary_font(); static NSFont* autocomplete_secondary_font(); static NSFont* autocomplete_section_header_font(); enum class AutocompleteRowKind { SectionHeader, Suggestion, }; struct AutocompleteRowModel { AutocompleteRowKind kind; String text; size_t suggestion_index { 0 }; }; static CGFloat autocomplete_text_field_height(NSFont* font) { static CGFloat primary_text_field_height = 0; static CGFloat secondary_text_field_height = 0; static CGFloat section_header_text_field_height = 0; static dispatch_once_t token; dispatch_once(&token, ^{ auto* text_field = [[NSTextField alloc] initWithFrame:NSZeroRect]; [text_field setBezeled:NO]; [text_field setDrawsBackground:NO]; [text_field setEditable:NO]; [text_field setStringValue:@"Ladybird"]; [text_field setFont:autocomplete_primary_font()]; primary_text_field_height = ceil([text_field fittingSize].height); [text_field setFont:autocomplete_secondary_font()]; secondary_text_field_height = ceil([text_field fittingSize].height); [text_field setFont:autocomplete_section_header_font()]; section_header_text_field_height = ceil([text_field fittingSize].height); }); if (font == autocomplete_secondary_font()) return secondary_text_field_height; if (font == autocomplete_section_header_font()) return section_header_text_field_height; return primary_text_field_height; } static CGFloat autocomplete_row_height() { static CGFloat row_height = 0; static dispatch_once_t token; dispatch_once(&token, ^{ auto content_height = max(CELL_ICON_SIZE, autocomplete_text_field_height(autocomplete_primary_font()) + CELL_LABEL_VERTICAL_SPACING + autocomplete_text_field_height(autocomplete_secondary_font())); row_height = ceil(content_height + (CELL_VERTICAL_PADDING * 2)); }); return row_height; } static CGFloat autocomplete_section_header_height() { static CGFloat row_height = 0; static dispatch_once_t token; dispatch_once(&token, ^{ row_height = ceil(autocomplete_text_field_height(autocomplete_section_header_font()) + (SECTION_HEADER_VERTICAL_PADDING * 2)); }); return row_height; } static NSFont* autocomplete_primary_font() { static NSFont* font; static dispatch_once_t token; dispatch_once(&token, ^{ font = [NSFont systemFontOfSize:[NSFont systemFontSize] weight:NSFontWeightSemibold]; }); return font; } static NSFont* autocomplete_secondary_font() { static NSFont* font; static dispatch_once_t token; dispatch_once(&token, ^{ font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; }); return font; } static NSFont* autocomplete_section_header_font() { static NSFont* font; static dispatch_once_t token; dispatch_once(&token, ^{ font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize] weight:NSFontWeightSemibold]; }); return font; } static NSImage* search_suggestion_icon() { static NSImage* image; static dispatch_once_t token; dispatch_once(&token, ^{ image = [NSImage imageWithSystemSymbolName:@"magnifyingglass" accessibilityDescription:@""]; [image setSize:NSMakeSize(CELL_ICON_SIZE, CELL_ICON_SIZE)]; }); return image; } static NSImage* literal_url_suggestion_icon() { static NSImage* image; static dispatch_once_t token; dispatch_once(&token, ^{ image = [NSImage imageWithSystemSymbolName:@"globe" accessibilityDescription:@""]; [image setSize:NSMakeSize(CELL_ICON_SIZE, CELL_ICON_SIZE)]; }); return image; } static CGFloat autocomplete_visible_width(NSView* view) { auto* scroll_view = view.enclosingScrollView; return scroll_view ? scroll_view.contentSize.width : NSWidth(view.bounds); } @protocol AutocompleteTableViewHoverObserver - (void)autocompleteTableViewHoveredRowChanged:(NSInteger)row; @end @interface AutocompleteRowView : NSTableRowView @end @implementation AutocompleteRowView - (void)drawSelectionInRect:(NSRect)dirtyRect { auto visible_bounds = NSMakeRect(0, 0, autocomplete_visible_width(self), NSHeight(self.bounds)); auto selection_rect = NSInsetRect(visible_bounds, 2, 3); auto* selection_path = [NSBezierPath bezierPathWithRoundedRect:selection_rect xRadius:6 yRadius:6]; [[[NSColor controlAccentColor] colorWithAlphaComponent:0.25] setFill]; [selection_path fill]; } @end @interface AutocompleteSuggestionView : NSTableCellView @property (nonatomic, strong) NSImageView* icon_view; @property (nonatomic, strong) NSTextField* title_text_field; @property (nonatomic, strong) NSTextField* url_text_field; @end @implementation AutocompleteSuggestionView @end @interface AutocompleteSectionHeaderView : NSTableCellView @property (nonatomic, strong) NSTextField* text_field; @end @implementation AutocompleteSectionHeaderView @end @interface AutocompleteScrollView : NSScrollView @end @implementation AutocompleteScrollView - (void)scrollWheel:(NSEvent*)event { } @end @interface AutocompleteTableView : NSTableView @property (nonatomic, weak) id hover_observer; @property (nonatomic, strong) NSTrackingArea* tracking_area; @end @implementation AutocompleteTableView - (void)updateTrackingAreas { if (self.tracking_area != nil) [self removeTrackingArea:self.tracking_area]; self.tracking_area = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited owner:self userInfo:nil]; [self addTrackingArea:self.tracking_area]; [super updateTrackingAreas]; } - (void)mouseMoved:(NSEvent*)event { [super mouseMoved:event]; auto point = [self convertPoint:event.locationInWindow fromView:nil]; [self.hover_observer autocompleteTableViewHoveredRowChanged:[self rowAtPoint:point]]; } - (void)mouseExited:(NSEvent*)event { [super mouseExited:event]; [self.hover_observer autocompleteTableViewHoveredRowChanged:-1]; } @end @interface AutocompleteWindow : NSWindow @end @implementation AutocompleteWindow - (BOOL)canBecomeKeyWindow { return NO; } - (BOOL)canBecomeMainWindow { return NO; } @end @interface Autocomplete () { Vector m_suggestions; Vector m_rows; } @property (nonatomic, weak) id observer; @property (nonatomic, weak) NSToolbarItem* toolbar_item; @property (nonatomic, strong) AutocompleteWindow* popup_window; @property (nonatomic, strong) NSView* content_view; @property (nonatomic, strong) NSScrollView* scroll_view; @property (nonatomic, strong) NSTableView* table_view; @property (nonatomic, strong) NSMutableDictionary* suggestion_icons; - (NSInteger)tableRowForSuggestionIndex:(NSInteger)suggestion_index; - (BOOL)isSelectableRow:(NSInteger)row; - (CGFloat)heightOfRowAtIndex:(size_t)row; - (CGFloat)tableHeightForVisibleSuggestionCount:(size_t)visible_suggestion_count; - (void)rebuildRows; - (void)selectRow:(NSInteger)row notifyObserver:(BOOL)notify_observer; - (NSInteger)stepToSelectableRowFrom:(NSInteger)row direction:(NSInteger)direction; @end @implementation Autocomplete - (instancetype)init:(id)observer withToolbarItem:(NSToolbarItem*)toolbar_item { if (self = [super init]) { self.observer = observer; self.toolbar_item = toolbar_item; auto* column = [[NSTableColumn alloc] init]; [column setEditable:NO]; self.table_view = [[AutocompleteTableView alloc] initWithFrame:NSZeroRect]; [self.table_view setAction:@selector(selectSuggestion:)]; [self.table_view setBackgroundColor:[NSColor clearColor]]; [self.table_view setHeaderView:nil]; [self.table_view setIntercellSpacing:NSMakeSize(0, 0)]; [self.table_view setRefusesFirstResponder:YES]; [self.table_view setStyle:NSTableViewStyleFullWidth]; [self.table_view setRowSizeStyle:NSTableViewRowSizeStyleCustom]; [self.table_view setRowHeight:autocomplete_row_height()]; [self.table_view setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular]; [self.table_view addTableColumn:column]; [self.table_view setDataSource:self]; [self.table_view setDelegate:self]; [self.table_view setTarget:self]; [(AutocompleteTableView*)self.table_view setHover_observer:self]; self.scroll_view = [[AutocompleteScrollView alloc] initWithFrame:NSZeroRect]; [self.scroll_view setAutohidesScrollers:YES]; [self.scroll_view setBorderType:NSNoBorder]; [self.scroll_view setDrawsBackground:NO]; [self.scroll_view setHasHorizontalScroller:NO]; [self.scroll_view setHasVerticalScroller:NO]; [self.scroll_view setHorizontalScrollElasticity:NSScrollElasticityNone]; [self.scroll_view setVerticalScrollElasticity:NSScrollElasticityNone]; [self.scroll_view setDocumentView:self.table_view]; self.content_view = [[NSView alloc] initWithFrame:NSZeroRect]; [self.content_view setWantsLayer:YES]; [self.content_view.layer setBackgroundColor:[NSColor windowBackgroundColor].CGColor]; [self.content_view.layer setCornerRadius:8]; [self.content_view addSubview:self.scroll_view]; self.suggestion_icons = [NSMutableDictionary dictionary]; self.popup_window = [[AutocompleteWindow alloc] initWithContentRect:NSZeroRect styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]; [self.popup_window setAcceptsMouseMovedEvents:YES]; [self.popup_window setBackgroundColor:[NSColor clearColor]]; [self.popup_window setContentView:self.content_view]; [self.popup_window setHasShadow:YES]; [self.popup_window setLevel:NSPopUpMenuWindowLevel]; [self.popup_window setOpaque:NO]; [self.popup_window setReleasedWhenClosed:NO]; } return self; } #pragma mark - Public methods - (void)showWithSuggestions:(Vector)suggestions selectedRow:(NSInteger)selected_row { m_suggestions = move(suggestions); [self rebuildRows]; [self.suggestion_icons removeAllObjects]; for (auto const& suggestion : m_suggestions) { if (suggestion.favicon_base64_png.has_value()) { auto* suggestion_text = Ladybird::string_to_ns_string(suggestion.text); if (auto* favicon = Ladybird::image_from_base64_png(*suggestion.favicon_base64_png, NSMakeSize(CELL_ICON_SIZE, CELL_ICON_SIZE)); favicon != nil) [self.suggestion_icons setObject:favicon forKey:suggestion_text]; } } [self.table_view reloadData]; if (m_rows.is_empty()) [self close]; else [self show]; auto table_row = [self tableRowForSuggestionIndex:selected_row]; if (table_row == NSNotFound) [self clearSelection]; else if (table_row != self.table_view.selectedRow) { // Refreshing the default row should not behave like an explicit // highlight, or the location field will re-preview the suggestion. [self selectRow:table_row notifyObserver:NO]; } } - (BOOL)close { if (!self.popup_window.isVisible) return NO; if (auto* parent_window = [self.toolbar_item.view window]) [parent_window removeChildWindow:self.popup_window]; [self.popup_window orderOut:nil]; [self.observer onAutocompleteDidClose]; return YES; } - (BOOL)isVisible { return self.popup_window.isVisible; } - (void)clearSelection { [self.table_view deselectAll:nil]; } - (Optional)selectedSuggestion { if (!self.popup_window.isVisible || self.table_view.numberOfRows == 0) return {}; auto row = [self.table_view selectedRow]; if (![self isSelectableRow:row]) return {}; return m_suggestions[m_rows[row].suggestion_index].text; } - (BOOL)selectNextSuggestion { if (self.table_view.numberOfRows == 0) return NO; if (!self.popup_window.isVisible) { [self show]; if (auto row = [self stepToSelectableRowFrom:-1 direction:1]; row != NSNotFound) [self selectRow:row notifyObserver:YES]; return YES; } if (auto row = [self stepToSelectableRowFrom:[self.table_view selectedRow] direction:1]; row != NSNotFound) [self selectRow:row notifyObserver:YES]; return YES; } - (BOOL)selectPreviousSuggestion { if (self.table_view.numberOfRows == 0) return NO; if (!self.popup_window.isVisible) { [self show]; if (auto row = [self stepToSelectableRowFrom:0 direction:-1]; row != NSNotFound) [self selectRow:row notifyObserver:YES]; return YES; } if (auto row = [self stepToSelectableRowFrom:[self.table_view selectedRow] direction:-1]; row != NSNotFound) [self selectRow:row notifyObserver:YES]; return YES; } - (void)selectSuggestion:(id)sender { if (auto suggestion = [self selectedSuggestion]; suggestion.has_value()) [self.observer onSelectedSuggestion:suggestion.release_value()]; } #pragma mark - Private methods - (void)rebuildRows { m_rows.clear(); m_rows.ensure_capacity(m_suggestions.size() * 2); auto current_section = WebView::AutocompleteSuggestionSection::None; for (size_t suggestion_index = 0; suggestion_index < m_suggestions.size(); ++suggestion_index) { auto const& suggestion = m_suggestions[suggestion_index]; if (suggestion.section != WebView::AutocompleteSuggestionSection::None && suggestion.section != current_section) { current_section = suggestion.section; m_rows.append({ .kind = AutocompleteRowKind::SectionHeader, .text = MUST(String::from_utf8(WebView::autocomplete_section_title(current_section))), }); } m_rows.append({ .kind = AutocompleteRowKind::Suggestion, .text = {}, .suggestion_index = suggestion_index }); } } - (BOOL)isSelectableRow:(NSInteger)row { if (row < 0 || row >= static_cast(m_rows.size())) return NO; return m_rows[row].kind == AutocompleteRowKind::Suggestion; } - (NSInteger)tableRowForSuggestionIndex:(NSInteger)suggestion_index { if (suggestion_index < 0) return NSNotFound; for (size_t row = 0; row < m_rows.size(); ++row) { auto const& row_model = m_rows[row]; if (row_model.kind == AutocompleteRowKind::Suggestion && row_model.suggestion_index == static_cast(suggestion_index)) return static_cast(row); } return NSNotFound; } - (CGFloat)heightOfRowAtIndex:(size_t)row { VERIFY(row < m_rows.size()); return m_rows[row].kind == AutocompleteRowKind::SectionHeader ? autocomplete_section_header_height() : autocomplete_row_height(); } - (CGFloat)tableHeightForVisibleSuggestionCount:(size_t)visible_suggestion_count { if (visible_suggestion_count == 0) return 0; CGFloat total_height = 0; size_t seen_suggestion_count = 0; for (size_t row = 0; row < m_rows.size(); ++row) { total_height += [self heightOfRowAtIndex:row]; if (m_rows[row].kind == AutocompleteRowKind::Suggestion) { ++seen_suggestion_count; if (seen_suggestion_count >= visible_suggestion_count) break; } } return ceil(total_height); } - (NSInteger)stepToSelectableRowFrom:(NSInteger)row direction:(NSInteger)direction { if (self.table_view.numberOfRows == 0) return NSNotFound; auto candidate = row; for (NSInteger attempt = 0; attempt < self.table_view.numberOfRows; ++attempt) { candidate += direction; if (candidate < 0) candidate = self.table_view.numberOfRows - 1; else if (candidate >= self.table_view.numberOfRows) candidate = 0; if ([self isSelectableRow:candidate]) return candidate; } return NSNotFound; } - (void)autocompleteTableViewHoveredRowChanged:(NSInteger)row { if (![self isSelectableRow:row]) return; if (row == self.table_view.selectedRow) return; [self selectRow:row notifyObserver:YES]; } - (void)show { auto* toolbar_view = self.toolbar_item.view; auto* parent_window = [toolbar_view window]; if (parent_window == nil) return; auto was_visible = self.popup_window.isVisible; size_t visible_suggestion_count = 0; for (auto const& row_model : m_rows) { if (row_model.kind == AutocompleteRowKind::Suggestion) ++visible_suggestion_count; } auto visible_table_height = [self tableHeightForVisibleSuggestionCount:visible_suggestion_count]; auto width = max(toolbar_view.frame.size.width, MINIMUM_WIDTH); auto content_size = NSMakeSize(width, visible_table_height + (POPOVER_PADDING * 2)); [self.content_view setFrame:NSMakeRect(0, 0, content_size.width, content_size.height)]; [self.scroll_view setFrame:NSInsetRect(self.content_view.bounds, 0, POPOVER_PADDING)]; CGFloat document_width = self.scroll_view.contentSize.width; [self.table_view setFrame:NSMakeRect(0, 0, document_width, visible_table_height)]; if (auto* column = self.table_view.tableColumns.firstObject) [column setWidth:document_width]; if (!was_visible) [self.table_view deselectAll:nil]; [self.table_view scrollRowToVisible:self.table_view.selectedRow >= 0 ? self.table_view.selectedRow : 0]; auto anchor_rect = [toolbar_view convertRect:toolbar_view.bounds toView:nil]; auto popup_rect = [parent_window convertRectToScreen:anchor_rect]; popup_rect.origin.y -= content_size.height; popup_rect.size = content_size; [self.popup_window setFrame:popup_rect display:NO]; if (!was_visible) [parent_window addChildWindow:self.popup_window ordered:NSWindowAbove]; [self.popup_window orderFront:nil]; } - (void)selectRow:(NSInteger)row notifyObserver:(BOOL)notify_observer { if (![self isSelectableRow:row]) return; [self.table_view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; [self.table_view scrollRowToVisible:[self.table_view selectedRow]]; if (notify_observer) { if (auto suggestion = [self selectedSuggestion]; suggestion.has_value()) [self.observer onHighlightedSuggestion:suggestion.release_value()]; } } #pragma mark - NSTableViewDataSource - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { return static_cast(m_rows.size()); } #pragma mark - NSTableViewDelegate - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row { return [self heightOfRowAtIndex:static_cast(row)]; } - (NSTableRowView*)tableView:(NSTableView*)tableView rowViewForRow:(NSInteger)row { auto visible_width = autocomplete_visible_width(tableView); return [[AutocompleteRowView alloc] initWithFrame:NSMakeRect(0, 0, visible_width, [self tableView:tableView heightOfRow:row])]; } - (NSView*)tableView:(NSTableView*)table_view viewForTableColumn:(NSTableColumn*)table_column row:(NSInteger)row { auto visible_width = autocomplete_visible_width(table_view); auto const& row_model = m_rows[row]; if (row_model.kind == AutocompleteRowKind::SectionHeader) { AutocompleteSectionHeaderView* view = (AutocompleteSectionHeaderView*)[table_view makeViewWithIdentifier:AUTOCOMPLETE_SECTION_HEADER_IDENTIFIER owner:self]; if (view == nil) { view = [[AutocompleteSectionHeaderView alloc] initWithFrame:NSZeroRect]; NSTextField* text_field = [[NSTextField alloc] initWithFrame:NSZeroRect]; [text_field setBezeled:NO]; [text_field setDrawsBackground:NO]; [text_field setEditable:NO]; [text_field setFont:autocomplete_section_header_font()]; [text_field setSelectable:NO]; [view addSubview:text_field]; [view setText_field:text_field]; [view setIdentifier:AUTOCOMPLETE_SECTION_HEADER_IDENTIFIER]; } auto* header_text = Ladybird::string_to_ns_string(row_model.text); auto header_height = autocomplete_text_field_height(autocomplete_section_header_font()); [view setFrame:NSMakeRect(0, 0, visible_width, [self tableView:table_view heightOfRow:row])]; [view.text_field setStringValue:header_text]; [view.text_field setTextColor:[NSColor tertiaryLabelColor]]; [view.text_field setFrame:NSMakeRect( SECTION_HEADER_HORIZONTAL_PADDING, floor((NSHeight(view.bounds) - header_height) / 2.f), NSWidth(view.bounds) - (SECTION_HEADER_HORIZONTAL_PADDING * 2), header_height)]; return view; } AutocompleteSuggestionView* view = (AutocompleteSuggestionView*)[table_view makeViewWithIdentifier:AUTOCOMPLETE_IDENTIFIER owner:self]; if (view == nil) { view = [[AutocompleteSuggestionView alloc] initWithFrame:NSZeroRect]; NSImageView* icon_view = [[NSImageView alloc] initWithFrame:NSZeroRect]; [icon_view setImageScaling:NSImageScaleProportionallyDown]; [view addSubview:icon_view]; [view setIcon_view:icon_view]; NSTextField* title_text_field = [[NSTextField alloc] initWithFrame:NSZeroRect]; [title_text_field setBezeled:NO]; [title_text_field setDrawsBackground:NO]; [title_text_field setEditable:NO]; [title_text_field setFont:autocomplete_primary_font()]; [title_text_field setLineBreakMode:NSLineBreakByTruncatingTail]; [title_text_field setSelectable:NO]; [view addSubview:title_text_field]; [view setTitle_text_field:title_text_field]; NSTextField* url_text_field = [[NSTextField alloc] initWithFrame:NSZeroRect]; [url_text_field setBezeled:NO]; [url_text_field setDrawsBackground:NO]; [url_text_field setEditable:NO]; [url_text_field setLineBreakMode:NSLineBreakByTruncatingTail]; [url_text_field setSelectable:NO]; [view addSubview:url_text_field]; [view setUrl_text_field:url_text_field]; [view setIdentifier:AUTOCOMPLETE_IDENTIFIER]; } auto const& suggestion = m_suggestions[row_model.suggestion_index]; auto* suggestion_text = Ladybird::string_to_ns_string(suggestion.text); auto* title_text = suggestion.title.has_value() ? Ladybird::string_to_ns_string(*suggestion.title) : nil; auto* secondary_text = suggestion.subtitle.has_value() ? Ladybird::string_to_ns_string(*suggestion.subtitle) : suggestion_text; auto* favicon = [self.suggestion_icons objectForKey:suggestion_text]; auto* icon = suggestion.source == WebView::AutocompleteSuggestionSource::LiteralURL ? literal_url_suggestion_icon() : suggestion.source == WebView::AutocompleteSuggestionSource::Search ? search_suggestion_icon() : favicon; [view setFrame:NSMakeRect(0, 0, visible_width, [self tableView:table_view heightOfRow:row])]; auto primary_text_height = autocomplete_text_field_height(autocomplete_primary_font()); auto secondary_text_height = autocomplete_text_field_height(autocomplete_secondary_font()); CGFloat text_origin_x = CELL_HORIZONTAL_PADDING + CELL_ICON_SIZE + CELL_ICON_TEXT_SPACING; CGFloat text_width = NSWidth(view.bounds) - text_origin_x - CELL_HORIZONTAL_PADDING; [view.icon_view setFrame:NSMakeRect( CELL_HORIZONTAL_PADDING, floor((NSHeight(view.bounds) - CELL_ICON_SIZE) / 2.f), CELL_ICON_SIZE, CELL_ICON_SIZE)]; [view.icon_view setImage:icon]; [view.icon_view setContentTintColor:suggestion.source != WebView::AutocompleteSuggestionSource::History ? [NSColor secondaryLabelColor] : nil]; [view.icon_view setHidden:(icon == nil)]; if (title_text != nil) { CGFloat text_block_height = primary_text_height + CELL_LABEL_VERTICAL_SPACING + secondary_text_height; CGFloat text_block_origin_y = floor((NSHeight(view.bounds) - text_block_height) / 2.f); [view.title_text_field setHidden:NO]; [view.title_text_field setStringValue:title_text]; [view.title_text_field setTextColor:[NSColor textColor]]; [view.title_text_field setFrame:NSMakeRect( text_origin_x, text_block_origin_y + secondary_text_height + CELL_LABEL_VERTICAL_SPACING, text_width, primary_text_height)]; [view.url_text_field setFont:autocomplete_secondary_font()]; [view.url_text_field setTextColor:[NSColor secondaryLabelColor]]; [view.url_text_field setFrame:NSMakeRect( text_origin_x, text_block_origin_y, text_width, secondary_text_height)]; } else { [view.title_text_field setHidden:YES]; [view.url_text_field setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; [view.url_text_field setTextColor:[NSColor textColor]]; [view.url_text_field setFrame:NSMakeRect( text_origin_x, floor((NSHeight(view.bounds) - primary_text_height) / 2.f), text_width, primary_text_height)]; } [view.url_text_field setStringValue:(title_text != nil ? secondary_text : suggestion_text)]; return view; } @end