blob: f34f9f017205729ae57e441d90e957818358dbd3 [file] [log] [blame]
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001// Copyright (c) 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_controller.h"
6
7#include "base/mac/foundation_util.h"
Ben Murdocheb525c52013-07-10 11:40:50 +01008#import "base/mac/scoped_nsobject.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01009#include "base/stl_util.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010010#include "base/strings/sys_string_conversions.h"
11#include "grit/ui_strings.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010012#include "skia/ext/skia_utils_mac.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010013#include "ui/base/l10n/l10n_util.h"
14#include "ui/base/resource/resource_bundle.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010015#import "ui/message_center/cocoa/tray_view_controller.h"
16#include "ui/message_center/message_center_style.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010017
18const int kMarginWidth = 16;
19const int kEntryHeight = 38;
20const int kIconSize = 16;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010021const int kIconTextPadding = 8;
22const int kCheckmarkIconPadding = 16;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010023
24const int kIntrinsicCheckmarkPadding = 4; // Padding already provided by Cocoa.
25const int kCorrectedCheckmarkPadding =
26 kCheckmarkIconPadding - kIntrinsicCheckmarkPadding;
27
28@interface MCSettingsButtonCell : NSButtonCell {
29 // A checkbox's regular image is the checkmark image. This additional image
30 // is used for the favicon or app icon shown next to the checkmark.
Ben Murdocheb525c52013-07-10 11:40:50 +010031 base::scoped_nsobject<NSImage> extraImage_;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010032}
33- (void)setExtraImage:(NSImage*)extraImage;
34@end
35
36@implementation MCSettingsButtonCell
37- (void)setExtraImage:(NSImage*)extraImage {
38 extraImage_.reset([extraImage retain]);
39}
40
41- (NSRect)drawTitle:(NSAttributedString*)title
42 withFrame:(NSRect)frame
43 inView:(NSView*)controlView {
44 CGFloat inset = kCorrectedCheckmarkPadding;
45 // drawTitle:withFrame:inView: draws the checkmark image. Draw the extra
46 // image as part of the checkbox's text.
47 if (extraImage_) {
48 NSRect imageRect = frame;
49 imageRect.origin.x += inset;
50 imageRect.size = NSMakeSize(kIconSize, kIconSize);
51 [extraImage_ drawInRect:imageRect
52 fromRect:NSZeroRect
53 operation:NSCompositeSourceOver
54 fraction:1.0
55 respectFlipped:YES
56 hints:nil];
57
58 inset += kIconSize + kIconTextPadding;
59 }
60 frame.origin.x += inset;
61 frame.size.width -= inset;
62 return [super drawTitle:title withFrame:frame inView:controlView];
63}
64
65- (NSUInteger)hitTestForEvent:(NSEvent*)event
66 inRect:(NSRect)cellFrame
67 ofView:(NSView*)controlView {
68 NSUInteger result =
69 [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
70 if (result == NSCellHitNone) {
71 // The default button cell does hit testing on the attributed string. Since
72 // this cell draws additional spacing and an icon in front of the string,
73 // tweak the hit testing result.
74 NSPoint point =
75 [controlView convertPoint:[event locationInWindow] fromView:nil];
76
77 NSRect rect = [self titleRectForBounds:[controlView bounds]];
78 rect.size = [[self attributedTitle] size];
79 rect.size.width += kCorrectedCheckmarkPadding;
80
81 if (extraImage_) {
82 rect.size.width +=
83 kIconSize + kIconTextPadding + kCorrectedCheckmarkPadding;
84 }
85
86 if (NSPointInRect(point, rect))
87 result = NSCellHitContentArea | NSCellHitTrackableArea;
88 }
89 return result;
90}
91@end
92
93@interface MCSettingsController (Private)
94// Sets the icon on the checkbox corresponding to |notifiers_[index]|.
95- (void)setIcon:(NSImage*)icon forNotifierIndex:(size_t)index;
96
Ben Murdocheb525c52013-07-10 11:40:50 +010097- (void)setIcon:(NSImage*)icon
98 forNotifierId:(const message_center::NotifierId&)id;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010099
100// Returns the NSButton corresponding to the checkbox for |notifiers_[index]|.
101- (NSButton*)buttonForNotifierAtIndex:(size_t)index;
102@end
103
104namespace message_center {
105
Ben Murdocheb525c52013-07-10 11:40:50 +0100106NotifierSettingsObserverMac::~NotifierSettingsObserverMac() {}
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100107
Ben Murdocheb525c52013-07-10 11:40:50 +0100108void NotifierSettingsObserverMac::UpdateIconImage(const NotifierId& notifier_id,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100109 const gfx::Image& icon) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100110 [settings_controller_ setIcon:icon.AsNSImage() forNotifierId:notifier_id];
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100111}
112
Ben Murdochbb1529c2013-08-08 10:24:53 +0100113void NotifierSettingsObserverMac::NotifierGroupChanged() {}
114
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100115} // namespace message_center
116
117@implementation MCSettingsController
118
119- (id)initWithProvider:(message_center::NotifierSettingsProvider*)provider {
120 if ((self = [super initWithNibName:nil bundle:nil])) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100121 observer_.reset(new message_center::NotifierSettingsObserverMac(self));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100122 provider_ = provider;
Ben Murdocheb525c52013-07-10 11:40:50 +0100123 provider_->AddObserver(observer_.get());
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100124 }
125 return self;
126}
127
128- (void)dealloc {
Ben Murdocheb525c52013-07-10 11:40:50 +0100129 provider_->RemoveObserver(observer_.get());
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100130 provider_->OnNotifierSettingsClosing();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100131 STLDeleteElements(&notifiers_);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100132 [super dealloc];
133}
134
135- (NSTextField*)newLabelWithFrame:(NSRect)frame {
136 NSTextField* label = [[NSTextField alloc] initWithFrame:frame];
137 [label setDrawsBackground:NO];
138 [label setBezeled:NO];
139 [label setEditable:NO];
140 [label setSelectable:NO];
141 [label setAutoresizingMask:NSViewMinYMargin];
142 return label;
143}
144
145- (void)loadView {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100146 DCHECK(notifiers_.empty());
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100147 provider_->GetNotifierList(&notifiers_);
148 CGFloat maxHeight = [MCTrayViewController maxTrayClientHeight];
149
150 // Container view.
151 NSRect fullFrame =
152 NSMakeRect(0, 0, [MCTrayViewController trayWidth], maxHeight);
Ben Murdocheb525c52013-07-10 11:40:50 +0100153 base::scoped_nsobject<NSBox> view([[NSBox alloc] initWithFrame:fullFrame]);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100154 [view setBorderType:NSNoBorder];
155 [view setBoxType:NSBoxCustom];
156 [view setContentViewMargins:NSZeroSize];
157 [view setFillColor:gfx::SkColorToCalibratedNSColor(
158 message_center::kMessageCenterBackgroundColor)];
159 [view setTitlePosition:NSNoTitle];
160 [self setView:view];
161
162 // "Settings" text.
163 NSRect headerFrame = NSMakeRect(
164 kMarginWidth, kMarginWidth, NSWidth(fullFrame), NSHeight(fullFrame));
165 settingsText_.reset([self newLabelWithFrame:headerFrame]);
166 [settingsText_ setAutoresizingMask:NSViewMinYMargin];
167 [settingsText_ setTextColor:gfx::SkColorToCalibratedNSColor(
168 message_center::kRegularTextColor)];
169 [settingsText_ setFont:
170 [NSFont messageFontOfSize:message_center::kTitleFontSize]];
171
172 [settingsText_ setStringValue:
173 l10n_util::GetNSString(IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL)];
174 [settingsText_ sizeToFit];
175 headerFrame = [settingsText_ frame];
176 headerFrame.origin.y =
177 NSMaxY(fullFrame) - kMarginWidth - NSHeight(headerFrame);
178 [[self view] addSubview:settingsText_];
179
180 // Subheader.
181 NSRect subheaderFrame = NSMakeRect(
182 kMarginWidth, kMarginWidth, NSWidth(fullFrame), NSHeight(fullFrame));
183 detailsText_.reset([self newLabelWithFrame:subheaderFrame]);
184 [detailsText_ setAutoresizingMask:NSViewMinYMargin];
185 [detailsText_ setTextColor:gfx::SkColorToCalibratedNSColor(
186 message_center::kDimTextColor)];
187 [detailsText_ setFont:
188 [NSFont messageFontOfSize:message_center::kMessageFontSize]];
189
190 [detailsText_ setStringValue:l10n_util::GetNSString(
191 IDS_MESSAGE_CENTER_SETTINGS_DIALOG_DESCRIPTION)];
192 [detailsText_ sizeToFit];
193 subheaderFrame = [detailsText_ frame];
194 subheaderFrame.origin.y =
195 NSMinY(headerFrame) - message_center::kTextTopPadding -
196 NSHeight(subheaderFrame);
197 [[self view] addSubview:detailsText_];
198
199 // Document view for the notifier settings.
200 CGFloat y = 0;
201 NSRect documentFrame = NSMakeRect(0, 0, NSWidth(fullFrame), 0);
Ben Murdocheb525c52013-07-10 11:40:50 +0100202 base::scoped_nsobject<NSView> documentView(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100203 [[NSView alloc] initWithFrame:documentFrame]);
204 for (int i = notifiers_.size() - 1; i >= 0; --i) {
205 message_center::Notifier* notifier = notifiers_[i];
206
207 // TODO(thakis): Use a custom button cell.
208 NSRect frame = NSMakeRect(
209 kMarginWidth, y, NSWidth(documentFrame) - kMarginWidth, kEntryHeight);
Ben Murdocheb525c52013-07-10 11:40:50 +0100210 base::scoped_nsobject<NSButton> button(
211 [[NSButton alloc] initWithFrame:frame]);
212 base::scoped_nsobject<MCSettingsButtonCell> cell(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100213 [[MCSettingsButtonCell alloc]
214 initTextCell:base::SysUTF16ToNSString(notifier->name)]);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100215 if (!notifier->icon.IsEmpty())
216 [cell setExtraImage:notifier->icon.AsNSImage()];
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100217 [button setCell:cell];
218 [button setButtonType:NSSwitchButton];
219
220 [button setState:notifier->enabled ? NSOnState : NSOffState];
221 [button setTag:i];
222 [button setTarget:self];
223 [button setAction:@selector(checkboxClicked:)];
224
225 [documentView addSubview:button.release()];
226
227 y += NSHeight(frame);
228 }
229 documentFrame.size.height = y;
230 [documentView setFrame:documentFrame];
231
232 // Scroll view for the notifier settings.
233 NSRect scrollFrame = documentFrame;
234 scrollFrame.origin.y = kMarginWidth;
235 CGFloat remainingHeight =
236 NSMinY(subheaderFrame) - message_center::kTextTopPadding -
237 NSMinY(scrollFrame);
238
239 if (NSHeight(documentFrame) < remainingHeight) {
240 // Everything fits without scrolling.
241 CGFloat delta = remainingHeight - NSHeight(documentFrame);
242 headerFrame.origin.y -= delta;
243 subheaderFrame.origin.y -= delta;
244 fullFrame.size.height -= delta;
245 } else {
246 scrollFrame.size.height = remainingHeight;
247 }
248
249 scrollView_.reset([[NSScrollView alloc] initWithFrame:scrollFrame]);
250 [scrollView_ setAutohidesScrollers:YES];
251 [scrollView_ setAutoresizingMask:NSViewMinYMargin];
252 [scrollView_ setDocumentView:documentView];
253 [scrollView_ setDrawsBackground:NO];
254 [scrollView_ setHasHorizontalScroller:NO];
255 [scrollView_ setHasVerticalScroller:YES];
256
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100257 // Scroll to top.
258 NSPoint newScrollOrigin =
259 NSMakePoint(0.0,
260 NSMaxY([[scrollView_ documentView] frame]) -
261 NSHeight([[scrollView_ contentView] bounds]));
262 [[scrollView_ documentView] scrollPoint:newScrollOrigin];
263
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100264 // Set final sizes.
265 [[self view] setFrame:fullFrame];
266 [[self view] addSubview:scrollView_];
267 [settingsText_ setFrame:headerFrame];
268 [detailsText_ setFrame:subheaderFrame];
269}
270
271- (void)checkboxClicked:(id)sender {
272 provider_->SetNotifierEnabled(*notifiers_[[sender tag]],
273 [sender state] == NSOnState);
274}
275
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100276// Testing API /////////////////////////////////////////////////////////////////
277
278- (NSScrollView*)scrollView {
279 return scrollView_;
280}
281
282// Private API /////////////////////////////////////////////////////////////////
283
284- (void)setIcon:(NSImage*)icon forNotifierIndex:(size_t)index {
285 NSButton* button = [self buttonForNotifierAtIndex:index];
286 [[button cell] setExtraImage:icon];
287 [button setNeedsDisplay:YES];
288}
289
Ben Murdocheb525c52013-07-10 11:40:50 +0100290- (void)setIcon:(NSImage*)icon
291 forNotifierId:(const message_center::NotifierId&)id {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100292 for (size_t i = 0; i < notifiers_.size(); ++i) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100293 if (notifiers_[i]->notifier_id == id) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100294 [self setIcon:icon forNotifierIndex:i];
295 return;
296 }
297 }
298}
299
300- (NSButton*)buttonForNotifierAtIndex:(size_t)index {
301 NSArray* subviews = [[scrollView_ documentView] subviews];
302 // The checkboxes are in bottom-top order, the checkbox for notifiers_[0] is
303 // last.
304 NSView* view = [subviews objectAtIndex:notifiers_.size() - 1 - index];
305 return base::mac::ObjCCastStrict<NSButton>(view);
306}
307
308@end