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
|
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/autofill/autofill_suggestion_container.h"
#include <algorithm>
#include <cmath>
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
#include "chrome/browser/ui/chrome_style.h"
#include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
#include "skia/ext/skia_utils_mac.h"
namespace {
// Horizontal padding between text and other elements (in pixels).
const int kAroundTextPadding = 4;
// Padding at the top of suggestions.
const CGFloat kTopPadding = 10;
// Indicates infinite size in either vertical or horizontal direction.
// Technically, CGFLOAT_MAX should do. Practically, it runs into several issues.
// #1) Many computations on Retina devices overflow with that value.
// #2) In this particular use case, it results in the message
// "CGAffineTransformInvert: singular matrix."
const CGFloat kInfiniteSize = 1.0e6;
// A line fragment padding that creates the same visual look as text layout in
// an NSTextField does. (Which UX feedback was based on)
const CGFloat kLineFragmentPadding = 2.0;
// Padding added on top of the label so its first line looks centered with
// respect to the input field. Only added when the input field is showing.
const CGFloat kLabelWithInputTopPadding = 5.0;
}
// An attachment cell for a single icon - takes care of proper alignment of
// text and icon.
@interface IconAttachmentCell : NSTextAttachmentCell {
CGFloat baseline_; // The cell's baseline adjustment.
}
// Adjust the cell's baseline so that the lower edge of the image aligns with
// the longest descender, not the font baseline
- (void)adjustBaselineForFont:(NSFont*)font;
@end
@interface AutofillSuggestionView : NSView {
@private
// The main input field - only view not ignoring mouse events.
NSView* inputField_;
}
@property (assign, nonatomic) NSView* inputField;
@end
// The suggestion container should ignore any mouse events unless they occur
// within the bounds of an editable field.
@implementation AutofillSuggestionView
@synthesize inputField = inputField_;
- (NSView*)hitTest:(NSPoint)point {
NSView* hitView = [super hitTest:point];
if ([hitView isDescendantOf:inputField_])
return hitView;
return nil;
}
@end
@implementation IconAttachmentCell
- (NSPoint)cellBaselineOffset {
return NSMakePoint(0.0, baseline_);
}
// Ensure proper padding between text and icon.
- (NSSize)cellSize {
NSSize size = [super cellSize];
size.width += kAroundTextPadding;
return size;
}
// drawWithFrame: needs to be overridden to left-align the image. Default
// rendering centers images in the cell's frame.
- (void)drawWithFrame:(NSRect)frame inView:(NSView*)view {
frame.size.width -= kAroundTextPadding;
[super drawWithFrame:frame inView:view];
}
- (void)adjustBaselineForFont:(NSFont*)font {
CGFloat lineHeight = [font ascender];
baseline_ = std::floor((lineHeight - [[self image] size].height) / 2.0);
}
@end
@interface AutofillSuggestionContainer (Private)
// Set the main suggestion text and the corresponding |icon|.
// Attempts to wrap the text if |wrapText| is set.
- (void)setSuggestionText:(NSString*)line
icon:(NSImage*)icon
wrapText:(BOOL)wrapText;
@end
@implementation AutofillSuggestionContainer
- (AutofillTextField*)inputField {
return inputField_.get();
}
- (NSTextField*)makeDetailSectionLabel:(NSString*)labelText {
base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]);
[label setFont:
[[NSFontManager sharedFontManager] convertFont:[label font]
toHaveTrait:NSBoldFontMask]];
[label setStringValue:labelText];
[label setEditable:NO];
[label setBordered:NO];
[label sizeToFit];
return label.autorelease();
}
- (void)loadView {
label_.reset([[NSTextView alloc] initWithFrame:NSZeroRect]);
[[label_ textContainer] setLineFragmentPadding:kLineFragmentPadding];
[label_ setEditable:NO];
[label_ setSelectable:NO];
[label_ setDrawsBackground:NO];
base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
[[NSMutableParagraphStyle alloc] init]);
[paragraphStyle setLineSpacing:0.5 * [[label_ font] pointSize]];
[label_ setDefaultParagraphStyle:paragraphStyle];
inputField_.reset([[AutofillTextField alloc] initWithFrame:NSZeroRect]);
[inputField_ setHidden:YES];
spacer_.reset([[NSBox alloc] initWithFrame:NSZeroRect]);
[spacer_ setBoxType:NSBoxSeparator];
[spacer_ setBorderType:NSLineBorder];
base::scoped_nsobject<AutofillSuggestionView> view(
[[AutofillSuggestionView alloc] initWithFrame:NSZeroRect]);
[view setSubviews:
@[ label_, inputField_, spacer_ ]];
[view setInputField:inputField_];
[self setView:view];
}
- (void)setSuggestionText:(NSString*)line
icon:(NSImage*)icon
wrapText:(BOOL)wrapText {
[label_ setString:@""];
if ([icon size].width) {
base::scoped_nsobject<IconAttachmentCell> cell(
[[IconAttachmentCell alloc] initImageCell:icon]);
base::scoped_nsobject<NSTextAttachment> attachment(
[[NSTextAttachment alloc] init]);
[cell adjustBaselineForFont:[NSFont controlContentFontOfSize:0]];
[cell setAlignment:NSLeftTextAlignment];
[attachment setAttachmentCell:cell];
[[label_ textStorage] setAttributedString:
[NSAttributedString attributedStringWithAttachment:attachment]];
}
NSDictionary* attributes = @{
NSParagraphStyleAttributeName : [label_ defaultParagraphStyle],
NSCursorAttributeName : [NSCursor arrowCursor],
NSFontAttributeName : [NSFont controlContentFontOfSize:0]
};
base::scoped_nsobject<NSAttributedString> str1(
[[NSAttributedString alloc] initWithString:line
attributes:attributes]);
[[label_ textStorage] appendAttributedString:str1];
[label_ setVerticallyResizable:YES];
[label_ setHorizontallyResizable:!wrapText];
if (wrapText) {
CGFloat availableWidth =
4 * autofill::kFieldWidth - [inputField_ frame].size.width;
[label_ setFrameSize:NSMakeSize(availableWidth, kInfiniteSize)];
} else {
[label_ setFrameSize:NSMakeSize(kInfiniteSize, kInfiniteSize)];
}
[[label_ layoutManager] ensureLayoutForTextContainer:[label_ textContainer]];
[label_ sizeToFit];
}
- (void)
setSuggestionWithVerticallyCompactText:(NSString*)verticallyCompactText
horizontallyCompactText:(NSString*)horizontallyCompactText
icon:(NSImage*)icon
maxWidth:(CGFloat)maxWidth {
// Prefer the vertically compact text when it fits. If it doesn't fit, fall
// back to the horizontally compact text.
[self setSuggestionText:verticallyCompactText icon:icon wrapText:NO];
if ([self preferredSize].width > maxWidth)
[self setSuggestionText:horizontallyCompactText icon:icon wrapText:YES];
}
- (void)showInputField:(NSString*)text withIcon:(NSImage*)icon {
[[inputField_ cell] setPlaceholderString:text];
[[inputField_ cell] setIcon:icon];
[inputField_ setHidden:NO];
[inputField_ sizeToFit];
// Enforce fixed width.
NSSize frameSize = NSMakeSize(autofill::kFieldWidth,
NSHeight([inputField_ frame]));
[inputField_ setFrameSize:frameSize];
}
- (NSSize)preferredSize {
NSSize size = [label_ bounds].size;
// Final inputField_ sizing/spacing depends on a TODO(estade) in Views code.
if (![inputField_ isHidden]) {
size.height = std::max(size.height + kLabelWithInputTopPadding,
NSHeight([inputField_ frame]));
size.width += NSWidth([inputField_ frame]) + kAroundTextPadding;
}
size.height += kTopPadding;
return size;
}
- (void)performLayout {
NSRect bounds = [[self view] bounds];
NSSize preferredContainerSize = [self preferredSize];
// width is externally determined.
preferredContainerSize.width = NSWidth(bounds);
NSRect spacerFrame = NSMakeRect(0, preferredContainerSize.height - 1,
preferredContainerSize.width, 1);
NSRect labelFrame = [label_ bounds];
labelFrame.origin.x = NSMinX(bounds);
labelFrame.origin.y = NSMaxY(bounds) - NSHeight(labelFrame) - kTopPadding;
// Position input field - top is aligned to top of label field.
if (![inputField_ isHidden]) {
NSRect inputFieldFrame = [inputField_ frame];
inputFieldFrame.origin.x = NSMaxX(bounds) - NSWidth(inputFieldFrame);
inputFieldFrame.origin.y = NSMaxY(labelFrame) - NSHeight(inputFieldFrame);
[inputField_ setFrameOrigin:inputFieldFrame.origin];
// Vertically center the first line of the label with respect to the input
// field.
labelFrame.origin.y -= kLabelWithInputTopPadding;
// Due to fixed width, fields are guaranteed to not overlap.
DCHECK_LE(NSMaxX(labelFrame), NSMinX(inputFieldFrame));
}
[spacer_ setFrame:spacerFrame];
[label_ setFrame:labelFrame];
[[self view] setFrameSize:preferredContainerSize];
}
@end
|