1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h" 6 7 #include "base/logging.h" 8 #import "chrome/browser/ui/cocoa/image_utils.h" 9 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h" 10 #import "chrome/browser/ui/cocoa/location_bar/location_bar_decoration.h" 11 12 namespace { 13 14 const CGFloat kBaselineAdjust = 3.0; 15 16 // Matches the clipping radius of |GradientButtonCell|. 17 const CGFloat kCornerRadius = 4.0; 18 19 // How far to inset the left-hand decorations from the field's bounds. 20 const CGFloat kLeftDecorationXOffset = 5.0; 21 22 // How far to inset the right-hand decorations from the field's bounds. 23 // TODO(shess): Why is this different from |kLeftDecorationXOffset|? 24 // |kDecorationOuterXOffset|? 25 const CGFloat kRightDecorationXOffset = 5.0; 26 27 // The amount of padding on either side reserved for drawing 28 // decorations. [Views has |kItemPadding| == 3.] 29 const CGFloat kDecorationHorizontalPad = 3.0; 30 31 // How long to wait for mouse-up on the location icon before assuming 32 // that the user wants to drag. 33 const NSTimeInterval kLocationIconDragTimeout = 0.25; 34 35 // Calculate the positions for a set of decorations. |frame| is the 36 // overall frame to do layout in, |remaining_frame| will get the 37 // left-over space. |all_decorations| is the set of decorations to 38 // lay out, |decorations| will be set to the decorations which are 39 // visible and which fit, in the same order as |all_decorations|, 40 // while |decoration_frames| will be the corresponding frames. 41 // |x_edge| describes the edge to layout the decorations against 42 // (|NSMinXEdge| or |NSMaxXEdge|). |initial_padding| is the padding 43 // from the edge of |cell_frame| (|kDecorationHorizontalPad| is used 44 // between decorations). 45 void CalculatePositionsHelper( 46 NSRect frame, 47 const std::vector<LocationBarDecoration*>& all_decorations, 48 NSRectEdge x_edge, 49 CGFloat initial_padding, 50 std::vector<LocationBarDecoration*>* decorations, 51 std::vector<NSRect>* decoration_frames, 52 NSRect* remaining_frame) { 53 DCHECK(x_edge == NSMinXEdge || x_edge == NSMaxXEdge); 54 DCHECK_EQ(decorations->size(), decoration_frames->size()); 55 56 // The outer-most decoration will be inset a bit further from the 57 // edge. 58 CGFloat padding = initial_padding; 59 60 for (size_t i = 0; i < all_decorations.size(); ++i) { 61 if (all_decorations[i]->IsVisible()) { 62 NSRect padding_rect, available; 63 64 // Peel off the outside padding. 65 NSDivideRect(frame, &padding_rect, &available, padding, x_edge); 66 67 // Find out how large the decoration will be in the remaining 68 // space. 69 const CGFloat used_width = 70 all_decorations[i]->GetWidthForSpace(NSWidth(available)); 71 72 if (used_width != LocationBarDecoration::kOmittedWidth) { 73 DCHECK_GT(used_width, 0.0); 74 NSRect decoration_frame; 75 76 // Peel off the desired width, leaving the remainder in 77 // |frame|. 78 NSDivideRect(available, &decoration_frame, &frame, 79 used_width, x_edge); 80 81 decorations->push_back(all_decorations[i]); 82 decoration_frames->push_back(decoration_frame); 83 DCHECK_EQ(decorations->size(), decoration_frames->size()); 84 85 // Adjust padding for between decorations. 86 padding = kDecorationHorizontalPad; 87 } 88 } 89 } 90 91 DCHECK_EQ(decorations->size(), decoration_frames->size()); 92 *remaining_frame = frame; 93 } 94 95 // Helper function for calculating placement of decorations w/in the 96 // cell. |frame| is the cell's boundary rectangle, |remaining_frame| 97 // will get any space left after decorations are laid out (for text). 98 // |left_decorations| is a set of decorations for the left-hand side 99 // of the cell, |right_decorations| for the right-hand side. 100 // |decorations| will contain the resulting visible decorations, and 101 // |decoration_frames| will contain their frames in the same 102 // coordinates as |frame|. Decorations will be ordered left to right. 103 // As a convenience returns the index of the first right-hand 104 // decoration. 105 size_t CalculatePositionsInFrame( 106 NSRect frame, 107 const std::vector<LocationBarDecoration*>& left_decorations, 108 const std::vector<LocationBarDecoration*>& right_decorations, 109 std::vector<LocationBarDecoration*>* decorations, 110 std::vector<NSRect>* decoration_frames, 111 NSRect* remaining_frame) { 112 decorations->clear(); 113 decoration_frames->clear(); 114 115 // Layout |left_decorations| against the LHS. 116 CalculatePositionsHelper(frame, left_decorations, 117 NSMinXEdge, kLeftDecorationXOffset, 118 decorations, decoration_frames, &frame); 119 DCHECK_EQ(decorations->size(), decoration_frames->size()); 120 121 // Capture the number of visible left-hand decorations. 122 const size_t left_count = decorations->size(); 123 124 // Layout |right_decorations| against the RHS. 125 CalculatePositionsHelper(frame, right_decorations, 126 NSMaxXEdge, kRightDecorationXOffset, 127 decorations, decoration_frames, &frame); 128 DCHECK_EQ(decorations->size(), decoration_frames->size()); 129 130 // Reverse the right-hand decorations so that overall everything is 131 // sorted left to right. 132 std::reverse(decorations->begin() + left_count, decorations->end()); 133 std::reverse(decoration_frames->begin() + left_count, 134 decoration_frames->end()); 135 136 *remaining_frame = frame; 137 return left_count; 138 } 139 140 } // namespace 141 142 @implementation AutocompleteTextFieldCell 143 144 - (CGFloat)baselineAdjust { 145 return kBaselineAdjust; 146 } 147 148 - (CGFloat)cornerRadius { 149 return kCornerRadius; 150 } 151 152 - (BOOL)shouldDrawBezel { 153 return YES; 154 } 155 156 - (void)clearDecorations { 157 leftDecorations_.clear(); 158 rightDecorations_.clear(); 159 } 160 161 - (void)addLeftDecoration:(LocationBarDecoration*)decoration { 162 leftDecorations_.push_back(decoration); 163 } 164 165 - (void)addRightDecoration:(LocationBarDecoration*)decoration { 166 rightDecorations_.push_back(decoration); 167 } 168 169 - (CGFloat)availableWidthInFrame:(const NSRect)frame { 170 std::vector<LocationBarDecoration*> decorations; 171 std::vector<NSRect> decorationFrames; 172 NSRect textFrame; 173 CalculatePositionsInFrame(frame, leftDecorations_, rightDecorations_, 174 &decorations, &decorationFrames, &textFrame); 175 176 return NSWidth(textFrame); 177 } 178 179 - (NSRect)frameForDecoration:(const LocationBarDecoration*)aDecoration 180 inFrame:(NSRect)cellFrame { 181 // Short-circuit if the decoration is known to be not visible. 182 if (aDecoration && !aDecoration->IsVisible()) 183 return NSZeroRect; 184 185 // Layout the decorations. 186 std::vector<LocationBarDecoration*> decorations; 187 std::vector<NSRect> decorationFrames; 188 NSRect textFrame; 189 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_, 190 &decorations, &decorationFrames, &textFrame); 191 192 // Find our decoration and return the corresponding frame. 193 std::vector<LocationBarDecoration*>::const_iterator iter = 194 std::find(decorations.begin(), decorations.end(), aDecoration); 195 if (iter != decorations.end()) { 196 const size_t index = iter - decorations.begin(); 197 return decorationFrames[index]; 198 } 199 200 // Decorations which are not visible should have been filtered out 201 // at the top, but return |NSZeroRect| rather than a 0-width rect 202 // for consistency. 203 NOTREACHED(); 204 return NSZeroRect; 205 } 206 207 // Overriden to account for the decorations. 208 - (NSRect)textFrameForFrame:(NSRect)cellFrame { 209 // Get the frame adjusted for decorations. 210 std::vector<LocationBarDecoration*> decorations; 211 std::vector<NSRect> decorationFrames; 212 NSRect textFrame = [super textFrameForFrame:cellFrame]; 213 CalculatePositionsInFrame(textFrame, leftDecorations_, rightDecorations_, 214 &decorations, &decorationFrames, &textFrame); 215 216 // NOTE: This function must closely match the logic in 217 // |-drawInteriorWithFrame:inView:|. 218 219 return textFrame; 220 } 221 222 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame { 223 std::vector<LocationBarDecoration*> decorations; 224 std::vector<NSRect> decorationFrames; 225 NSRect textFrame; 226 size_t left_count = 227 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_, 228 &decorations, &decorationFrames, &textFrame); 229 230 // Determine the left-most extent for the i-beam cursor. 231 CGFloat minX = NSMinX(textFrame); 232 for (size_t index = left_count; index--; ) { 233 if (decorations[index]->AcceptsMousePress()) 234 break; 235 236 // If at leftmost decoration, expand to edge of cell. 237 if (!index) { 238 minX = NSMinX(cellFrame); 239 } else { 240 minX = NSMinX(decorationFrames[index]) - kDecorationHorizontalPad; 241 } 242 } 243 244 // Determine the right-most extent for the i-beam cursor. 245 CGFloat maxX = NSMaxX(textFrame); 246 for (size_t index = left_count; index < decorations.size(); ++index) { 247 if (decorations[index]->AcceptsMousePress()) 248 break; 249 250 // If at rightmost decoration, expand to edge of cell. 251 if (index == decorations.size() - 1) { 252 maxX = NSMaxX(cellFrame); 253 } else { 254 maxX = NSMaxX(decorationFrames[index]) + kDecorationHorizontalPad; 255 } 256 } 257 258 // I-beam cursor covers left-most to right-most. 259 return NSMakeRect(minX, NSMinY(textFrame), maxX - minX, NSHeight(textFrame)); 260 } 261 262 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 263 std::vector<LocationBarDecoration*> decorations; 264 std::vector<NSRect> decorationFrames; 265 NSRect workingFrame; 266 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_, 267 &decorations, &decorationFrames, &workingFrame); 268 269 // Draw the decorations. 270 for (size_t i = 0; i < decorations.size(); ++i) { 271 if (decorations[i]) 272 decorations[i]->DrawInFrame(decorationFrames[i], controlView); 273 } 274 275 // NOTE: This function must closely match the logic in 276 // |-textFrameForFrame:|. 277 278 // Superclass draws text portion WRT original |cellFrame|. 279 [super drawInteriorWithFrame:cellFrame inView:controlView]; 280 } 281 282 - (LocationBarDecoration*)decorationForEvent:(NSEvent*)theEvent 283 inRect:(NSRect)cellFrame 284 ofView:(AutocompleteTextField*)controlView 285 { 286 const BOOL flipped = [controlView isFlipped]; 287 const NSPoint location = 288 [controlView convertPoint:[theEvent locationInWindow] fromView:nil]; 289 290 std::vector<LocationBarDecoration*> decorations; 291 std::vector<NSRect> decorationFrames; 292 NSRect textFrame; 293 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_, 294 &decorations, &decorationFrames, &textFrame); 295 296 for (size_t i = 0; i < decorations.size(); ++i) { 297 if (NSMouseInRect(location, decorationFrames[i], flipped)) 298 return decorations[i]; 299 } 300 301 return NULL; 302 } 303 304 - (NSMenu*)decorationMenuForEvent:(NSEvent*)theEvent 305 inRect:(NSRect)cellFrame 306 ofView:(AutocompleteTextField*)controlView { 307 LocationBarDecoration* decoration = 308 [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView]; 309 if (decoration) 310 return decoration->GetMenu(); 311 return nil; 312 } 313 314 - (BOOL)mouseDown:(NSEvent*)theEvent 315 inRect:(NSRect)cellFrame 316 ofView:(AutocompleteTextField*)controlView { 317 LocationBarDecoration* decoration = 318 [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView]; 319 if (!decoration || !decoration->AcceptsMousePress()) 320 return NO; 321 322 NSRect decorationRect = 323 [self frameForDecoration:decoration inFrame:cellFrame]; 324 325 // If the decoration is draggable, then initiate a drag if the user 326 // drags or holds the mouse down for awhile. 327 if (decoration->IsDraggable()) { 328 NSDate* timeout = 329 [NSDate dateWithTimeIntervalSinceNow:kLocationIconDragTimeout]; 330 NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | 331 NSLeftMouseUpMask) 332 untilDate:timeout 333 inMode:NSEventTrackingRunLoopMode 334 dequeue:YES]; 335 if (!event || [event type] == NSLeftMouseDragged) { 336 NSPasteboard* pboard = decoration->GetDragPasteboard(); 337 DCHECK(pboard); 338 339 NSImage* image = decoration->GetDragImage(); 340 DCHECK(image); 341 342 NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect); 343 344 // If the original click is not within |dragImageRect|, then 345 // center the image under the mouse. Otherwise, will drag from 346 // where the click was on the image. 347 const NSPoint mousePoint = 348 [controlView convertPoint:[theEvent locationInWindow] fromView:nil]; 349 if (!NSMouseInRect(mousePoint, dragImageRect, [controlView isFlipped])) { 350 dragImageRect.origin = 351 NSMakePoint(mousePoint.x - NSWidth(dragImageRect) / 2.0, 352 mousePoint.y - NSHeight(dragImageRect) / 2.0); 353 } 354 355 // -[NSView dragImage:at:*] wants the images lower-left point, 356 // regardless of -isFlipped. Converting the rect to window base 357 // coordinates doesn't require any special-casing. Note that 358 // -[NSView dragFile:fromRect:*] takes a rect rather than a 359 // point, likely for this exact reason. 360 const NSPoint dragPoint = 361 [controlView convertRect:dragImageRect toView:nil].origin; 362 [[controlView window] dragImage:image 363 at:dragPoint 364 offset:NSZeroSize 365 event:theEvent 366 pasteboard:pboard 367 source:self 368 slideBack:YES]; 369 370 return YES; 371 } 372 373 // On mouse-up fall through to mouse-pressed case. 374 DCHECK_EQ([event type], NSLeftMouseUp); 375 } 376 377 if (!decoration->OnMousePressed(decorationRect)) 378 return NO; 379 380 return YES; 381 } 382 383 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { 384 return NSDragOperationCopy; 385 } 386 387 - (void)updateToolTipsInRect:(NSRect)cellFrame 388 ofView:(AutocompleteTextField*)controlView { 389 std::vector<LocationBarDecoration*> decorations; 390 std::vector<NSRect> decorationFrames; 391 NSRect textFrame; 392 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_, 393 &decorations, &decorationFrames, &textFrame); 394 395 for (size_t i = 0; i < decorations.size(); ++i) { 396 NSString* tooltip = decorations[i]->GetToolTip(); 397 if ([tooltip length] > 0) 398 [controlView addToolTip:tooltip forRect:decorationFrames[i]]; 399 } 400 } 401 402 @end 403