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.
5 #import "ui/message_center/cocoa/settings_entry_view.h"
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"
16 using message_center::settings::kEntryIconSize;
17 using message_center::settings::kInternalHorizontalSpacing;
19 // Size of the widget rendered for us by Cocoa.
20 const int kCocoaCheckboxSize = 14;
22 // Intrinsic padding pixels out of our control.
23 // Cocoa gives the checkmark some blank space on either side.
24 const int kIntrinsicCheckmarkLeftPadding = 2;
25 const int kIntrinsicCheckmarkRightPadding = 4;
26 // Labels have a bit of whitespace to the left, which can throw
28 const int kIntrinsicTextLeftPadding = 1;
30 // The learn more image is bigger than the actual size of the learn more
31 // pixels, this represents the difference.
32 const int kIntrinsicLearnMorePadding = 2;
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.
37 const int kCheckmarkPaddingNecessary =
38 (message_center::settings::kCheckboxSizeWithPadding - kCocoaCheckboxSize) /
41 // These represent the additional padding that we must give the checkmark
42 // control based on the required padding and the intrinsic padding.
43 const int kCorrectedCheckmarkLeftPadding =
44 kCheckmarkPaddingNecessary - kIntrinsicCheckmarkLeftPadding;
45 const int kCorrectedCheckmarkRightPadding =
46 kCheckmarkPaddingNecessary + kInternalHorizontalSpacing -
47 kIntrinsicCheckmarkRightPadding;
49 // The amount of space we want, based on the spec and the intrinsic text space
51 const int kCorrectedIconTextPadding =
52 kInternalHorizontalSpacing - kIntrinsicTextLeftPadding;
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.
56 const int kCorrectedEntryRightPadding =
57 kInternalHorizontalSpacing - kIntrinsicLearnMorePadding;
59 ////////////////////////////////////////////////////////////////////////////////
61 @interface MCSettingsButton : NSButton
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);
71 NSRectFill(dirtyRect);
72 [super drawRect:dirtyRect];
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_;
81 - (void)setExtraImage:(NSImage*)extraImage;
84 @implementation MCSettingsButtonCell
89 - (void)setExtraImage:(NSImage*)extraImage {
90 extraImage_.reset([extraImage retain]);
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.
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
108 operation:NSCompositeSourceOver
113 inset += kEntryIconSize + kCorrectedIconTextPadding;
115 frame.origin.x += inset;
116 frame.size.width -= inset;
117 return [super drawTitle:title withFrame:frame inView:controlView];
120 - (NSSize)cellSizeForBounds:(NSRect)aRect {
121 NSSize size = [super cellSizeForBounds:aRect];
122 size.width += kCorrectedCheckmarkRightPadding;
124 size.width += kEntryIconSize + kCorrectedIconTextPadding;
125 size.height = std::max(static_cast<CGFloat>(kEntryIconSize), size.height);
130 - (NSUInteger)hitTestForEvent:(NSEvent*)event
131 inRect:(NSRect)cellFrame
132 ofView:(NSView*)controlView {
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.
140 [controlView convertPoint:[event locationInWindow] fromView:nil];
142 NSRect rect = [self titleRectForBounds:[controlView bounds]];
143 rect.size = [[self attributedTitle] size];
144 rect.size.width += kCorrectedCheckmarkRightPadding;
147 rect.size.width += kEntryIconSize + kCorrectedIconTextPadding;
149 if (NSPointInRect(point, rect))
150 result = NSCellHitContentArea | NSCellHitTrackableArea;
156 @implementation MCSettingsEntryView
157 - (id)initWithController:(MCSettingsController*)controller
158 notifier:(message_center::Notifier*)notifier
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];
167 hasSeparator_ = hasSeparator;
168 controller_ = controller;
169 notifier_ = notifier;
170 if (!notifier->icon.IsEmpty())
171 notifierIcon_.reset(notifier->icon.CopyNSImage());
172 [self layoutEntryView];
177 - (void)setNotifierIcon:(NSImage*)notifierIcon {
178 notifierIcon_.reset([notifierIcon retain]);
179 [self layoutEntryView];
182 - (NSButton*)checkbox {
186 - (void)layoutEntryView {
188 [controller_ notifierHasAdvancedSettings:notifier_->notifier_id];
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;
196 NSRect learnMoreFrame =
197 NSMakeRect(checkboxFrame.origin.x + checkboxFrame.size.width,
198 checkboxFrame.origin.y,
200 checkboxFrame.size.height);
202 // Initially place the learn more button right-aligned.
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;
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;
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;
223 if (!learnMoreButton_.get()) {
224 learnMoreButton_.reset(
225 [[HoverImageButton alloc] initWithFrame:learnMoreFrame]);
226 [self addSubview:learnMoreButton_];
228 [learnMoreButton_ setFrame:learnMoreFrame];
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:)];
240 if (!checkbox_.get()) {
241 checkbox_.reset([[MCSettingsButton alloc] initWithFrame:checkboxFrame]);
242 [self addSubview:checkbox_];
244 [checkbox_ setFrame:checkboxFrame];
247 base::scoped_nsobject<MCSettingsButtonCell> cell([[MCSettingsButtonCell alloc]
248 initTextCell:base::SysUTF16ToNSString(notifier_->name)]);
249 if ([notifierIcon_ isValid])
250 [cell setExtraImage:notifierIcon_];
252 [checkbox_ setCell:cell];
253 [checkbox_ setButtonType:NSSwitchButton];
254 [checkbox_ setState:notifier_->enabled ? NSOnState : NSOffState];
255 [checkbox_ setTarget:self];
256 [checkbox_ setAction:@selector(checkboxClicked:)];
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_];
271 [separator_ setFrame:separatorRect];
276 - (void)checkboxClicked:(id)sender {
277 BOOL enabled = [sender state] == NSOnState;
278 [controller_ setSettingsNotifier:notifier_ enabled:enabled];
281 - (void)learnMoreClicked:(id)sender {
282 [controller_ learnMoreClicked:notifier_];
285 // Testing API /////////////////////////////////////////////////////////////////
287 - (void)clickLearnMore {
288 [learnMoreButton_ performClick:nil];
293 ///////////////////////////////////////////////////////////////////////////////