Home | History | Annotate | Download | only in location_bar
      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