OLD | NEW |
| (Empty) |
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 | |
16 using message_center::settings::kEntryIconSize; | |
17 using message_center::settings::kInternalHorizontalSpacing; | |
18 | |
19 // Size of the widget rendered for us by Cocoa. | |
20 const int kCocoaCheckboxSize = 14; | |
21 | |
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; | |
29 | |
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; | |
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. | |
37 const 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. | |
43 const int kCorrectedCheckmarkLeftPadding = | |
44 kCheckmarkPaddingNecessary - kIntrinsicCheckmarkLeftPadding; | |
45 const 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. | |
51 const 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. | |
56 const 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 layoutEntryView]; | |
173 } | |
174 return self; | |
175 } | |
176 | |
177 - (void)setNotifierIcon:(NSImage*)notifierIcon { | |
178 notifierIcon_.reset([notifierIcon retain]); | |
179 [self layoutEntryView]; | |
180 } | |
181 | |
182 - (NSButton*)checkbox { | |
183 return checkbox_; | |
184 } | |
185 | |
186 - (void)layoutEntryView { | |
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 /////////////////////////////////////////////////////////////////////////////// | |
OLD | NEW |