1// Copyright (c) 2011 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/styled_text_field_cell.h"
6
7#include "base/logging.h"
8#include "chrome/browser/themes/theme_properties.h"
9#include "chrome/browser/themes/theme_service.h"
10#import "chrome/browser/ui/cocoa/nsview_additions.h"
11#import "chrome/browser/ui/cocoa/themed_window.h"
12#include "grit/theme_resources.h"
13#import "ui/base/cocoa/nsgraphics_context_additions.h"
14#include "ui/base/resource/resource_bundle.h"
15#include "ui/gfx/font.h"
16#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
17
18@implementation StyledTextFieldCell
19
20- (CGFloat)topTextFrameOffset {
21  return 0.0;
22}
23
24- (CGFloat)bottomTextFrameOffset {
25  return 0.0;
26}
27
28- (CGFloat)cornerRadius {
29  return 0.0;
30}
31
32- (rect_path_utils::RoundedCornerFlags)roundedCornerFlags {
33  return rect_path_utils::RoundedCornerAll;
34}
35
36- (BOOL)shouldDrawBezel {
37  return NO;
38}
39
40- (NSRect)textFrameForFrameInternal:(NSRect)cellFrame {
41  CGFloat topOffset = [self topTextFrameOffset];
42  NSRect textFrame = cellFrame;
43  textFrame.origin.y += topOffset;
44  textFrame.size.height -= topOffset + [self bottomTextFrameOffset];
45  return textFrame;
46}
47
48// Returns the same value as textCursorFrameForFrame, but does not call it
49// directly to avoid potential infinite loops.
50- (NSRect)textFrameForFrame:(NSRect)cellFrame {
51  return [self textFrameForFrameInternal:cellFrame];
52}
53
54// Returns the same value as textFrameForFrame, but does not call it directly to
55// avoid potential infinite loops.
56- (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
57  return [self textFrameForFrameInternal:cellFrame];
58}
59
60// Override to show the I-beam cursor only in the area given by
61// |textCursorFrameForFrame:|.
62- (void)resetCursorRect:(NSRect)cellFrame inView:(NSView *)controlView {
63  [super resetCursorRect:[self textCursorFrameForFrame:cellFrame]
64                  inView:controlView];
65}
66
67// For NSTextFieldCell this is the area within the borders.  For our
68// purposes, we count the info decorations as being part of the
69// border.
70- (NSRect)drawingRectForBounds:(NSRect)theRect {
71  return [super drawingRectForBounds:[self textFrameForFrame:theRect]];
72}
73
74// TODO(shess): This code is manually drawing the cell's border area,
75// but otherwise the cell assumes -setBordered:YES for purposes of
76// calculating things like the editing area.  This is probably
77// incorrect.  I know that this affects -drawingRectForBounds:.
78- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
79  const CGFloat lineWidth = [controlView cr_lineWidth];
80  const CGFloat halfLineWidth = lineWidth / 2.0;
81
82  DCHECK([controlView isFlipped]);
83  rect_path_utils::RoundedCornerFlags roundedCornerFlags =
84      [self roundedCornerFlags];
85
86  // TODO(shess): This inset is also reflected by |kFieldVisualInset|
87  // in omnibox_popup_view_mac.mm.
88  const NSRect frame = NSInsetRect(cellFrame, 0, lineWidth);
89  const CGFloat radius = [self cornerRadius];
90
91  // Paint button background image if there is one (otherwise the border won't
92  // look right).
93  ThemeService* themeProvider =
94      static_cast<ThemeService*>([[controlView window] themeProvider]);
95  if (themeProvider) {
96    NSColor* backgroundImageColor = nil;
97    if (themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND)) {
98      backgroundImageColor =
99          themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND);
100    }
101    if (backgroundImageColor) {
102      // Set the phase to match window.
103      NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
104      NSPoint midPoint = NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect));
105      [[NSGraphicsContext currentContext] cr_setPatternPhase:midPoint
106                                                     forView:controlView];
107
108      // NOTE(shess): This seems like it should be using a 0.0 inset,
109      // but AFAICT using a halfLineWidth inset is important in mixing the
110      // toolbar background and the omnibox background.
111      rect_path_utils::FillRectWithInset(roundedCornerFlags, frame,
112                                         halfLineWidth, halfLineWidth, radius,
113                                         backgroundImageColor);
114    }
115
116    // Draw the outer stroke (over the background).
117    BOOL active = [[controlView window] isMainWindow];
118    NSColor* strokeColor = themeProvider->GetNSColor(
119        active ? ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE :
120                 ThemeProperties::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE);
121    rect_path_utils::FrameRectWithInset(roundedCornerFlags, frame, 0.0, 0.0,
122                                        radius, lineWidth, strokeColor);
123  }
124
125  // Fill interior with background color.
126  rect_path_utils::FillRectWithInset(roundedCornerFlags, frame, lineWidth,
127                                     lineWidth, radius,
128                                     [self backgroundColor]);
129
130  // Draw the shadow.  For the rounded-rect case, the shadow needs to
131  // slightly turn in at the corners.  |shadowFrame| is at the same
132  // midline as the inner border line on the top and left, but at the
133  // outer border line on the bottom and right.  The clipping change
134  // will clip the bottom and right edges (and corner).
135  {
136    gfx::ScopedNSGraphicsContextSaveGState state;
137    [rect_path_utils::RectPathWithInset(roundedCornerFlags, frame, lineWidth,
138                                        lineWidth, radius) addClip];
139    const NSRect shadowFrame =
140        NSOffsetRect(frame, halfLineWidth, halfLineWidth);
141    NSColor* shadowShade = [NSColor colorWithCalibratedWhite:0.0
142                                                       alpha:0.05 / lineWidth];
143    rect_path_utils::FrameRectWithInset(roundedCornerFlags, shadowFrame,
144                                        halfLineWidth, halfLineWidth,
145                                        radius - halfLineWidth, lineWidth,
146                                        shadowShade);
147  }
148
149  // Draw optional bezel below bottom stroke.
150  if ([self shouldDrawBezel] && themeProvider &&
151      themeProvider->UsingDefaultTheme()) {
152
153    NSColor* bezelColor = themeProvider->GetNSColor(
154        ThemeProperties::COLOR_TOOLBAR_BEZEL);
155    [[bezelColor colorWithAlphaComponent:0.5 / lineWidth] set];
156    NSRect bezelRect = NSMakeRect(cellFrame.origin.x,
157                                  NSMaxY(cellFrame) - lineWidth,
158                                  NSWidth(cellFrame),
159                                  lineWidth);
160    bezelRect = NSInsetRect(bezelRect, radius - halfLineWidth, 0.0);
161    NSRectFillUsingOperation(bezelRect, NSCompositeSourceOver);
162  }
163
164  // Draw the interior before the focus ring, to make sure nothing overlaps it.
165  [self drawInteriorWithFrame:cellFrame inView:controlView];
166
167  // Draw the focus ring if needed.
168  if ([self showsFirstResponder]) {
169    NSColor* color = [[NSColor keyboardFocusIndicatorColor]
170        colorWithAlphaComponent:0.5 / lineWidth];
171    rect_path_utils::FrameRectWithInset(roundedCornerFlags, frame, 0.0, 0.0,
172                                        radius, lineWidth * 2, color);
173  }
174}
175
176@end
177