• 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 "grit/ui_resources.h"
11#include "skia/ext/skia_utils_mac.h"
12#include "ui/base/resource/resource_bundle.h"
13#import "ui/message_center/cocoa/settings_controller.h"
14#include "ui/message_center/message_center_style.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@interface MCSettingsButtonCell : NSButtonCell {
60  // A checkbox's regular image is the checkmark image. This additional image
61  // is used for the favicon or app icon shown next to the checkmark.
62  base::scoped_nsobject<NSImage> extraImage_;
63}
64- (void)setExtraImage:(NSImage*)extraImage;
65@end
66
67@implementation MCSettingsButtonCell
68- (void)setExtraImage:(NSImage*)extraImage {
69  extraImage_.reset([extraImage retain]);
70}
71
72- (NSRect)drawTitle:(NSAttributedString*)title
73          withFrame:(NSRect)frame
74             inView:(NSView*)controlView {
75  CGFloat inset = kCorrectedCheckmarkRightPadding;
76  // drawTitle:withFrame:inView: draws the checkmark image. Draw the extra
77  // image as part of the checkbox's text.
78  if (extraImage_) {
79    NSRect imageRect = frame;
80    imageRect.origin.x += inset;
81    // Center the image vertically.
82    if (NSHeight(frame) > kEntryIconSize)
83      imageRect.origin.y += (NSHeight(frame) - kEntryIconSize) / 2;
84    imageRect.size = NSMakeSize(kEntryIconSize, kEntryIconSize);
85    [extraImage_ drawInRect:imageRect
86                   fromRect:NSZeroRect
87                  operation:NSCompositeSourceOver
88                   fraction:1.0
89             respectFlipped:YES
90                      hints:nil];
91
92    inset += kEntryIconSize + kCorrectedIconTextPadding;
93  }
94  frame.origin.x += inset;
95  frame.size.width -= inset;
96  return [super drawTitle:title withFrame:frame inView:controlView];
97}
98
99- (NSSize)cellSizeForBounds:(NSRect)aRect {
100  NSSize size = [super cellSizeForBounds:aRect];
101  size.width += kCorrectedCheckmarkRightPadding;
102  if (extraImage_) {
103    size.width += kEntryIconSize + kCorrectedIconTextPadding;
104    size.height = std::max(static_cast<CGFloat>(kEntryIconSize), size.height);
105  }
106  return size;
107}
108
109- (NSUInteger)hitTestForEvent:(NSEvent*)event
110                       inRect:(NSRect)cellFrame
111                       ofView:(NSView*)controlView {
112  NSUInteger result =
113      [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
114  if (result == NSCellHitNone) {
115    // The default button cell does hit testing on the attributed string. Since
116    // this cell draws additional spacing and an icon in front of the string,
117    // tweak the hit testing result.
118    NSPoint point =
119        [controlView convertPoint:[event locationInWindow] fromView:nil];
120
121    NSRect rect = [self titleRectForBounds:[controlView bounds]];
122    rect.size = [[self attributedTitle] size];
123    rect.size.width += kCorrectedCheckmarkRightPadding;
124
125    if (extraImage_)
126      rect.size.width += kEntryIconSize + kCorrectedIconTextPadding;
127
128    if (NSPointInRect(point, rect))
129      result = NSCellHitContentArea | NSCellHitTrackableArea;
130  }
131  return result;
132}
133@end
134
135@implementation MCSettingsEntryView
136
137- (id)initWithController:(MCSettingsController*)controller
138                notifier:(message_center::Notifier*)notifier
139                   frame:(NSRect)frame
140            hasSeparator:(BOOL)hasSeparator {
141  if ((self = [super initWithFrame:frame])) {
142    [self setBoxType:NSBoxCustom];
143    [self setBorderType:NSNoBorder];
144    [self setTitlePosition:NSNoTitle];
145    [self setContentViewMargins:NSZeroSize];
146
147    hasSeparator_ = hasSeparator;
148    controller_ = controller;
149    notifier_ = notifier;
150    if (!notifier->icon.IsEmpty())
151      notifierIcon_.reset(notifier->icon.CopyNSImage());
152    [self layout];
153  }
154  return self;
155}
156
157- (void)setNotifierIcon:(NSImage*)notifierIcon {
158  notifierIcon_.reset([notifierIcon retain]);
159  [self layout];
160}
161
162- (NSButton*)checkbox {
163  return checkbox_;
164}
165
166- (void)layout {
167  BOOL hasLearnMore =
168      [controller_ notifierHasAdvancedSettings:notifier_->notifier_id];
169
170  // Now calculate the space available for the checkbox button.
171  NSRect checkboxFrame = [self bounds];
172  checkboxFrame.origin.x += kCorrectedCheckmarkLeftPadding;
173  checkboxFrame.size.width -=
174      kCorrectedCheckmarkLeftPadding + kCorrectedEntryRightPadding;
175
176  NSRect learnMoreFrame =
177      NSMakeRect(checkboxFrame.origin.x + checkboxFrame.size.width,
178                 checkboxFrame.origin.y,
179                 0,
180                 checkboxFrame.size.height);
181
182  // Initially place the learn more button right-aligned.
183  if (hasLearnMore) {
184    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
185    NSImage* defaultImage =
186        rb.GetNativeImageNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS).ToNSImage();
187    NSSize defaultImageSize = [defaultImage size];
188    learnMoreFrame.size.width = defaultImageSize.width;
189    learnMoreFrame.origin.x -= defaultImageSize.width;
190
191    // May need to center the image if it's shorter than the entry.
192    if (defaultImageSize.height < learnMoreFrame.size.height) {
193      learnMoreFrame.origin.y +=
194          (learnMoreFrame.size.height - defaultImageSize.height) / 2;
195      learnMoreFrame.size.height = defaultImageSize.height;
196    }
197
198    // Since we have an image then we need to ensure that the text from the
199    // checkbox doesn't overlap with the learn more image.
200    checkboxFrame.size.width -=
201        kCorrectedIconTextPadding + learnMoreFrame.size.width;
202
203    if (!learnMoreButton_.get()) {
204      learnMoreButton_.reset(
205          [[HoverImageButton alloc] initWithFrame:learnMoreFrame]);
206      [self addSubview:learnMoreButton_];
207    } else {
208      [learnMoreButton_ setFrame:learnMoreFrame];
209    }
210    [learnMoreButton_ setDefaultImage:defaultImage];
211    [learnMoreButton_ setHoverImage:rb.GetNativeImageNamed(
212        IDR_NOTIFICATION_ADVANCED_SETTINGS_HOVER).ToNSImage()];
213    [learnMoreButton_ setPressedImage:rb.GetNativeImageNamed(
214        IDR_NOTIFICATION_ADVANCED_SETTINGS_PRESSED).ToNSImage()];
215    [learnMoreButton_ setBordered:NO];
216    [learnMoreButton_ setTarget:self];
217    [learnMoreButton_ setAction:@selector(learnMoreClicked:)];
218  }
219
220  if (!checkbox_.get()) {
221    checkbox_.reset([[NSButton alloc] initWithFrame:checkboxFrame]);
222    [self addSubview:checkbox_];
223  } else {
224    [checkbox_ setFrame:checkboxFrame];
225  }
226
227  base::scoped_nsobject<MCSettingsButtonCell> cell([[MCSettingsButtonCell alloc]
228      initTextCell:base::SysUTF16ToNSString(notifier_->name)]);
229  if ([notifierIcon_ isValid])
230    [cell setExtraImage:notifierIcon_];
231
232  [checkbox_ setCell:cell];
233  [checkbox_ setButtonType:NSSwitchButton];
234  [checkbox_ setState:notifier_->enabled ? NSOnState : NSOffState];
235  [checkbox_ setTarget:self];
236  [checkbox_ setAction:@selector(checkboxClicked:)];
237
238  if (hasSeparator_) {
239    NSRect separatorRect = [self bounds];
240    separatorRect.size.height = 1;
241    if (!separator_.get()) {
242      separator_.reset([[NSBox alloc] initWithFrame:separatorRect]);
243      [separator_ setBoxType:NSBoxCustom];
244      [separator_ setBorderType:NSLineBorder];
245      [separator_ setBorderColor:gfx::SkColorToCalibratedNSColor(
246          message_center::settings::kEntrySeparatorColor)];
247      [separator_ setTitlePosition:NSNoTitle];
248      [separator_ setContentViewMargins:NSZeroSize];
249      [self addSubview:separator_];
250    } else {
251      [separator_ setFrame:separatorRect];
252    }
253  }
254}
255
256- (void)checkboxClicked:(id)sender {
257  BOOL enabled = [sender state] == NSOnState;
258  [controller_ setSettingsNotifier:notifier_ enabled:enabled];
259}
260
261- (void)learnMoreClicked:(id)sender {
262  [controller_ learnMoreClicked:notifier_];
263}
264
265// Testing API /////////////////////////////////////////////////////////////////
266
267- (void)clickLearnMore {
268  [learnMoreButton_ performClick:nil];
269}
270
271@end
272
273///////////////////////////////////////////////////////////////////////////////
274