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