- add sources.
[platform/framework/web/crosswalk.git] / src / ui / message_center / cocoa / settings_entry_view.mm
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
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 @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 ///////////////////////////////////////////////////////////////////////////////