base: Change DCHECK_IS_ON to a macro DCHECK_IS_ON().
[chromium-blink-merge.git] / ui / message_center / cocoa / settings_entry_view.mm
blobc31d230e6121ad00779aed341662127a896892ba
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"
7 #include <algorithm>
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
27 // off measurements.
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) /
39     2;
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
50 // included by Cocoa.
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
62 @end
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];
74 @end
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;
82 @end
84 @implementation MCSettingsButtonCell
85 - (BOOL)isOpaque {
86   return YES;
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.
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];
113     inset += kEntryIconSize + kCorrectedIconTextPadding;
114   }
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;
123   if (extraImage_) {
124     size.width += kEntryIconSize + kCorrectedIconTextPadding;
125     size.height = std::max(static_cast<CGFloat>(kEntryIconSize), size.height);
126   }
127   return size;
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];
142     NSRect rect = [self titleRectForBounds:[controlView bounds]];
143     rect.size = [[self attributedTitle] size];
144     rect.size.width += kCorrectedCheckmarkRightPadding;
146     if (extraImage_)
147       rect.size.width += kEntryIconSize + kCorrectedIconTextPadding;
149     if (NSPointInRect(point, rect))
150       result = NSCellHitContentArea | NSCellHitTrackableArea;
151   }
152   return result;
154 @end
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];
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;
177 - (void)setNotifierIcon:(NSImage*)notifierIcon {
178   notifierIcon_.reset([notifierIcon retain]);
179   [self layout];
182 - (NSButton*)checkbox {
183   return checkbox_;
186 - (void)layout {
187   BOOL hasLearnMore =
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,
199                  0,
200                  checkboxFrame.size.height);
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;
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     }
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_];
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   }
240   if (!checkbox_.get()) {
241     checkbox_.reset([[MCSettingsButton alloc] initWithFrame:checkboxFrame]);
242     [self addSubview:checkbox_];
243   } else {
244     [checkbox_ setFrame:checkboxFrame];
245   }
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:)];
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   }
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];
291 @end
293 ///////////////////////////////////////////////////////////////////////////////