• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 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 "ui/message_center/cocoa/settings_entry_view.h"
6
7#include <algorithm>
8
9#include "base/strings/sys_string_conversions.h"
10#include "skia/ext/skia_utils_mac.h"
11#include "ui/base/resource/resource_bundle.h"
12#import "ui/message_center/cocoa/settings_controller.h"
13#include "ui/message_center/message_center_style.h"
14#include "ui/resources/grit/ui_resources.h"
15
16using message_center::settings::kEntryIconSize;
17using message_center::settings::kInternalHorizontalSpacing;
18
19// Size of the widget rendered for us by Cocoa.
20const int kCocoaCheckboxSize = 14;
21
22// Intrinsic padding pixels out of our control.
23// Cocoa gives the checkmark some blank space on either side.
24const int kIntrinsicCheckmarkLeftPadding = 2;
25const int kIntrinsicCheckmarkRightPadding = 4;
26// Labels have a bit of whitespace to the left, which can throw
27// off measurements.
28const int kIntrinsicTextLeftPadding = 1;
29
30// The learn more image is bigger than the actual size of the learn more
31// pixels, this represents the difference.
32const int kIntrinsicLearnMorePadding = 2;
33
34// Corrected padding values used in layout.
35// This computes the amout of padding based on the area reserved for the
36// checkbox and the actual checkbox size in pixels.
37const int kCheckmarkPaddingNecessary =
38    (message_center::settings::kCheckboxSizeWithPadding - kCocoaCheckboxSize) /
39    2;
40
41// These represent the additional padding that we must give the checkmark
42// control based on the required padding and the intrinsic padding.
43const int kCorrectedCheckmarkLeftPadding =
44    kCheckmarkPaddingNecessary - kIntrinsicCheckmarkLeftPadding;
45const int kCorrectedCheckmarkRightPadding =
46    kCheckmarkPaddingNecessary + kInternalHorizontalSpacing -
47    kIntrinsicCheckmarkRightPadding;
48
49// The amount of space we want, based on the spec and the intrinsic text space
50// included by Cocoa.
51const int kCorrectedIconTextPadding =
52    kInternalHorizontalSpacing - kIntrinsicTextLeftPadding;
53
54// We want a certain amount of space to the right of the learn more button,
55// this metric incorporates the intrinsic learn more blank space to compute it.
56const int kCorrectedEntryRightPadding =
57    kInternalHorizontalSpacing - kIntrinsicLearnMorePadding;
58
59////////////////////////////////////////////////////////////////////////////////
60
61@interface MCSettingsButton : NSButton
62@end
63
64@implementation MCSettingsButton
65// drawRect: needs to fill the button with a background, otherwise we don't get
66// subpixel antialiasing.
67- (void)drawRect:(NSRect)dirtyRect {
68  NSColor* color = gfx::SkColorToCalibratedNSColor(
69      message_center::kMessageCenterBackgroundColor);
70  [color set];
71  NSRectFill(dirtyRect);
72  [super drawRect:dirtyRect];
73}
74@end
75
76@interface MCSettingsButtonCell : NSButtonCell {
77  // A checkbox's regular image is the checkmark image. This additional image
78  // is used for the favicon or app icon shown next to the checkmark.
79  base::scoped_nsobject<NSImage> extraImage_;
80}
81- (void)setExtraImage:(NSImage*)extraImage;
82@end
83
84@implementation MCSettingsButtonCell
85- (BOOL)isOpaque {
86  return YES;
87}
88
89- (void)setExtraImage:(NSImage*)extraImage {
90  extraImage_.reset([extraImage retain]);
91}
92
93- (NSRect)drawTitle:(NSAttributedString*)title
94          withFrame:(NSRect)frame
95             inView:(NSView*)controlView {
96  CGFloat inset = kCorrectedCheckmarkRightPadding;
97  // drawTitle:withFrame:inView: draws the checkmark image. Draw the extra
98  // image as part of the checkbox's text.
99  if (extraImage_) {
100    NSRect imageRect = frame;
101    imageRect.origin.x += inset;
102    // Center the image vertically.
103    if (NSHeight(frame) > kEntryIconSize)
104      imageRect.origin.y += (NSHeight(frame) - kEntryIconSize) / 2;
105    imageRect.size = NSMakeSize(kEntryIconSize, kEntryIconSize);
106    [extraImage_ drawInRect:imageRect
107                   fromRect:NSZeroRect
108                  operation:NSCompositeSourceOver
109                   fraction:1.0
110             respectFlipped:YES
111                      hints:nil];
112
113    inset += kEntryIconSize + kCorrectedIconTextPadding;
114  }
115  frame.origin.x += inset;
116  frame.size.width -= inset;
117  return [super drawTitle:title withFrame:frame inView:controlView];
118}
119
120- (NSSize)cellSizeForBounds:(NSRect)aRect {
121  NSSize size = [super cellSizeForBounds:aRect];
122  size.width += kCorrectedCheckmarkRightPadding;
123  if (extraImage_) {
124    size.width += kEntryIconSize + kCorrectedIconTextPadding;
125    size.height = std::max(static_cast<CGFloat>(kEntryIconSize), size.height);
126  }
127  return size;
128}
129
130- (NSUInteger)hitTestForEvent:(NSEvent*)event
131                       inRect:(NSRect)cellFrame
132                       ofView:(NSView*)controlView {
133  NSUInteger result =
134      [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
135  if (result == NSCellHitNone) {
136    // The default button cell does hit testing on the attributed string. Since
137    // this cell draws additional spacing and an icon in front of the string,
138    // tweak the hit testing result.
139    NSPoint point =
140        [controlView convertPoint:[event locationInWindow] fromView:nil];
141
142    NSRect rect = [self titleRectForBounds:[controlView bounds]];
143    rect.size = [[self attributedTitle] size];
144    rect.size.width += kCorrectedCheckmarkRightPadding;
145
146    if (extraImage_)
147      rect.size.width += kEntryIconSize + kCorrectedIconTextPadding;
148
149    if (NSPointInRect(point, rect))
150      result = NSCellHitContentArea | NSCellHitTrackableArea;
151  }
152  return result;
153}
154@end
155
156@implementation MCSettingsEntryView
157- (id)initWithController:(MCSettingsController*)controller
158                notifier:(message_center::Notifier*)notifier
159                   frame:(NSRect)frame
160            hasSeparator:(BOOL)hasSeparator {
161  if ((self = [super initWithFrame:frame])) {
162    [self setBoxType:NSBoxCustom];
163    [self setBorderType:NSNoBorder];
164    [self setTitlePosition:NSNoTitle];
165    [self setContentViewMargins:NSZeroSize];
166
167    hasSeparator_ = hasSeparator;
168    controller_ = controller;
169    notifier_ = notifier;
170    if (!notifier->icon.IsEmpty())
171      notifierIcon_.reset(notifier->icon.CopyNSImage());
172    [self layout];
173  }
174  return self;
175}
176
177- (void)setNotifierIcon:(NSImage*)notifierIcon {
178  notifierIcon_.reset([notifierIcon retain]);
179  [self layout];
180}
181
182- (NSButton*)checkbox {
183  return checkbox_;
184}
185
186- (void)layout {
187  BOOL hasLearnMore =
188      [controller_ notifierHasAdvancedSettings:notifier_->notifier_id];
189
190  // Now calculate the space available for the checkbox button.
191  NSRect checkboxFrame = [self bounds];
192  checkboxFrame.origin.x += kCorrectedCheckmarkLeftPadding;
193  checkboxFrame.size.width -=
194      kCorrectedCheckmarkLeftPadding + kCorrectedEntryRightPadding;
195
196  NSRect learnMoreFrame =
197      NSMakeRect(checkboxFrame.origin.x + checkboxFrame.size.width,
198                 checkboxFrame.origin.y,
199                 0,
200                 checkboxFrame.size.height);
201
202  // Initially place the learn more button right-aligned.
203  if (hasLearnMore) {
204    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
205    NSImage* defaultImage =
206        rb.GetNativeImageNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS).ToNSImage();
207    NSSize defaultImageSize = [defaultImage size];
208    learnMoreFrame.size.width = defaultImageSize.width;
209    learnMoreFrame.origin.x -= defaultImageSize.width;
210
211    // May need to center the image if it's shorter than the entry.
212    if (defaultImageSize.height < learnMoreFrame.size.height) {
213      learnMoreFrame.origin.y +=
214          (learnMoreFrame.size.height - defaultImageSize.height) / 2;
215      learnMoreFrame.size.height = defaultImageSize.height;
216    }
217
218    // Since we have an image then we need to ensure that the text from the
219    // checkbox doesn't overlap with the learn more image.
220    checkboxFrame.size.width -=
221        kCorrectedIconTextPadding + learnMoreFrame.size.width;
222
223    if (!learnMoreButton_.get()) {
224      learnMoreButton_.reset(
225          [[HoverImageButton alloc] initWithFrame:learnMoreFrame]);
226      [self addSubview:learnMoreButton_];
227    } else {
228      [learnMoreButton_ setFrame:learnMoreFrame];
229    }
230    [learnMoreButton_ setDefaultImage:defaultImage];
231    [learnMoreButton_ setHoverImage:rb.GetNativeImageNamed(
232        IDR_NOTIFICATION_ADVANCED_SETTINGS_HOVER).ToNSImage()];
233    [learnMoreButton_ setPressedImage:rb.GetNativeImageNamed(
234        IDR_NOTIFICATION_ADVANCED_SETTINGS_PRESSED).ToNSImage()];
235    [learnMoreButton_ setBordered:NO];
236    [learnMoreButton_ setTarget:self];
237    [learnMoreButton_ setAction:@selector(learnMoreClicked:)];
238  }
239
240  if (!checkbox_.get()) {
241    checkbox_.reset([[MCSettingsButton alloc] initWithFrame:checkboxFrame]);
242    [self addSubview:checkbox_];
243  } else {
244    [checkbox_ setFrame:checkboxFrame];
245  }
246
247  base::scoped_nsobject<MCSettingsButtonCell> cell([[MCSettingsButtonCell alloc]
248      initTextCell:base::SysUTF16ToNSString(notifier_->name)]);
249  if ([notifierIcon_ isValid])
250    [cell setExtraImage:notifierIcon_];
251
252  [checkbox_ setCell:cell];
253  [checkbox_ setButtonType:NSSwitchButton];
254  [checkbox_ setState:notifier_->enabled ? NSOnState : NSOffState];
255  [checkbox_ setTarget:self];
256  [checkbox_ setAction:@selector(checkboxClicked:)];
257
258  if (hasSeparator_) {
259    NSRect separatorRect = [self bounds];
260    separatorRect.size.height = 1;
261    if (!separator_.get()) {
262      separator_.reset([[NSBox alloc] initWithFrame:separatorRect]);
263      [separator_ setBoxType:NSBoxCustom];
264      [separator_ setBorderType:NSLineBorder];
265      [separator_ setBorderColor:gfx::SkColorToCalibratedNSColor(
266          message_center::settings::kEntrySeparatorColor)];
267      [separator_ setTitlePosition:NSNoTitle];
268      [separator_ setContentViewMargins:NSZeroSize];
269      [self addSubview:separator_];
270    } else {
271      [separator_ setFrame:separatorRect];
272    }
273  }
274}
275
276- (void)checkboxClicked:(id)sender {
277  BOOL enabled = [sender state] == NSOnState;
278  [controller_ setSettingsNotifier:notifier_ enabled:enabled];
279}
280
281- (void)learnMoreClicked:(id)sender {
282  [controller_ learnMoreClicked:notifier_];
283}
284
285// Testing API /////////////////////////////////////////////////////////////////
286
287- (void)clickLearnMore {
288  [learnMoreButton_ performClick:nil];
289}
290
291@end
292
293///////////////////////////////////////////////////////////////////////////////
294