1// 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 <algorithm> 8 9#include "base/mac/foundation_util.h" 10#import "base/mac/scoped_nsobject.h" 11#include "base/stl_util.h" 12#include "base/strings/sys_string_conversions.h" 13#include "skia/ext/skia_utils_mac.h" 14#include "ui/base/l10n/l10n_util.h" 15#import "ui/message_center/cocoa/opaque_views.h" 16#import "ui/message_center/cocoa/settings_entry_view.h" 17#import "ui/message_center/cocoa/tray_view_controller.h" 18#include "ui/message_center/message_center_style.h" 19#include "ui/strings/grit/ui_strings.h" 20 21using message_center::settings::kHorizontalMargin; 22using message_center::settings::kEntryHeight; 23 24// Intrinsic padding pixels out of our control. 25const int kIntrinsicHeaderTextTopPadding = 3; 26const int kIntrinsicSubheaderTextTopPadding = 5; 27const int kIntrinsicSubheaderTextBottomPadding = 3; 28const int kIntrinsicDropDownVerticalPadding = 2; 29const int kIntrinsicDropDownHorizontalPadding = 3; 30 31// Corrected padding values used in layout. 32// Calculated additional blank space above the header text, including 33// the intrinsic blank space above the header label. 34const int kCorrectedHeaderTextTopPadding = 35 message_center::settings::kTopMargin - kIntrinsicHeaderTextTopPadding; 36 37// Calculated additional blank space above the subheader text, including 38// the intrinsic blank space above the subheader label. 39const int kCorrectedSubheaderTextTopPadding = 40 message_center::settings::kTitleToDescriptionSpace - 41 kIntrinsicSubheaderTextTopPadding; 42 43// Calcoulated additional vertical padding for the drop-down, including the 44// blank space included with the drop-down control. 45const int kCorrectedDropDownTopPadding = 46 message_center::settings::kDescriptionToSwitcherSpace - 47 kIntrinsicDropDownVerticalPadding - kIntrinsicSubheaderTextBottomPadding; 48 49// Calculated additional horizontal blank space for the drop down, including 50// the blank space included with the drop-down control. 51const int kCorrectedDropDownMargin = 52 kHorizontalMargin - kIntrinsicDropDownHorizontalPadding; 53 54@interface MCSettingsController (Private) 55// Sets the icon on the checkbox corresponding to |notifiers_[index]|. 56- (void)setIcon:(NSImage*)icon forNotifierIndex:(size_t)index; 57 58- (void)setIcon:(NSImage*)icon 59 forNotifierId:(const message_center::NotifierId&)id; 60 61// Returns the NSButton corresponding to the checkbox for |notifiers_[index]|. 62- (MCSettingsEntryView*)entryForNotifierAtIndex:(size_t)index; 63 64// Update the contents view. 65- (void)updateView; 66 67// Handler for the notifier group dropdown menu. 68- (void)notifierGroupSelectionChanged:(id)sender; 69 70@end 71 72namespace message_center { 73 74NotifierSettingsObserverMac::~NotifierSettingsObserverMac() {} 75 76void NotifierSettingsObserverMac::UpdateIconImage(const NotifierId& notifier_id, 77 const gfx::Image& icon) { 78 [settings_controller_ setIcon:icon.AsNSImage() forNotifierId:notifier_id]; 79} 80 81void NotifierSettingsObserverMac::NotifierGroupChanged() { 82 [settings_controller_ updateView]; 83} 84 85void NotifierSettingsObserverMac::NotifierEnabledChanged( 86 const NotifierId& notifier_id, bool enabled) {} 87 88} // namespace message_center 89 90@implementation MCSettingsController 91 92- (id)initWithProvider:(message_center::NotifierSettingsProvider*)provider 93 trayViewController:(MCTrayViewController*)trayViewController { 94 if ((self = [super initWithNibName:nil bundle:nil])) { 95 observer_.reset(new message_center::NotifierSettingsObserverMac(self)); 96 provider_ = provider; 97 trayViewController_ = trayViewController; 98 provider_->AddObserver(observer_.get()); 99 } 100 return self; 101} 102 103- (void)dealloc { 104 provider_->RemoveObserver(observer_.get()); 105 provider_->OnNotifierSettingsClosing(); 106 STLDeleteElements(¬ifiers_); 107 [super dealloc]; 108} 109 110- (NSTextField*)newLabelWithFrame:(NSRect)frame { 111 NSColor* color = gfx::SkColorToCalibratedNSColor( 112 message_center::kMessageCenterBackgroundColor); 113 MCTextField* label = 114 [[MCTextField alloc] initWithFrame:frame backgroundColor:color]; 115 116 return label; 117} 118 119- (void)updateView { 120 notifiers_.clear(); 121 [trayViewController_ updateSettings]; 122} 123 124- (void)loadView { 125 DCHECK(notifiers_.empty()); 126 provider_->GetNotifierList(¬ifiers_); 127 CGFloat maxHeight = [MCTrayViewController maxTrayClientHeight]; 128 129 // Container view. 130 NSRect fullFrame = 131 NSMakeRect(0, 0, [MCTrayViewController trayWidth], maxHeight); 132 base::scoped_nsobject<NSBox> view([[NSBox alloc] initWithFrame:fullFrame]); 133 [view setBorderType:NSNoBorder]; 134 [view setBoxType:NSBoxCustom]; 135 [view setContentViewMargins:NSZeroSize]; 136 [view setFillColor:gfx::SkColorToCalibratedNSColor( 137 message_center::kMessageCenterBackgroundColor)]; 138 [view setTitlePosition:NSNoTitle]; 139 [self setView:view]; 140 141 // "Settings" text. 142 NSRect headerFrame = NSMakeRect(kHorizontalMargin, 143 kHorizontalMargin, 144 NSWidth(fullFrame), 145 NSHeight(fullFrame)); 146 settingsText_.reset([self newLabelWithFrame:headerFrame]); 147 [settingsText_ setTextColor: 148 gfx::SkColorToCalibratedNSColor(message_center::kRegularTextColor)]; 149 [settingsText_ 150 setFont:[NSFont messageFontOfSize:message_center::kTitleFontSize]]; 151 152 [settingsText_ setStringValue: 153 l10n_util::GetNSString(IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL)]; 154 [settingsText_ sizeToFit]; 155 headerFrame = [settingsText_ frame]; 156 headerFrame.origin.y = NSMaxY(fullFrame) - kCorrectedHeaderTextTopPadding - 157 NSHeight(headerFrame); 158 [[self view] addSubview:settingsText_]; 159 160 // Subheader. 161 NSRect subheaderFrame = NSMakeRect(kHorizontalMargin, 162 kHorizontalMargin, 163 NSWidth(fullFrame), 164 NSHeight(fullFrame)); 165 detailsText_.reset([self newLabelWithFrame:subheaderFrame]); 166 [detailsText_ setTextColor: 167 gfx::SkColorToCalibratedNSColor(message_center::kDimTextColor)]; 168 [detailsText_ 169 setFont:[NSFont messageFontOfSize:message_center::kMessageFontSize]]; 170 171 size_t groupCount = provider_->GetNotifierGroupCount(); 172 [detailsText_ setStringValue:l10n_util::GetNSString( 173 groupCount > 1 ? IDS_MESSAGE_CENTER_SETTINGS_DESCRIPTION_MULTIUSER 174 : IDS_MESSAGE_CENTER_SETTINGS_DIALOG_DESCRIPTION)]; 175 [detailsText_ sizeToFit]; 176 subheaderFrame = [detailsText_ frame]; 177 subheaderFrame.origin.y = 178 NSMinY(headerFrame) - kCorrectedSubheaderTextTopPadding - 179 NSHeight(subheaderFrame); 180 [[self view] addSubview:detailsText_]; 181 182 // Profile switcher is only needed for more than one profile. 183 NSRect dropDownButtonFrame = subheaderFrame; 184 if (groupCount > 1) { 185 dropDownButtonFrame = NSMakeRect(kCorrectedDropDownMargin, 186 kHorizontalMargin, 187 NSWidth(fullFrame), 188 NSHeight(fullFrame)); 189 groupDropDownButton_.reset( 190 [[MCDropDown alloc] initWithFrame:dropDownButtonFrame pullsDown:YES]); 191 [groupDropDownButton_ 192 setBackgroundColor:gfx::SkColorToCalibratedNSColor( 193 message_center::kMessageCenterBackgroundColor)]; 194 [groupDropDownButton_ setAction:@selector(notifierGroupSelectionChanged:)]; 195 [groupDropDownButton_ setTarget:self]; 196 // Add a dummy item for pull-down. 197 [groupDropDownButton_ addItemWithTitle:@""]; 198 base::string16 title; 199 for (size_t i = 0; i < groupCount; ++i) { 200 const message_center::NotifierGroup& group = 201 provider_->GetNotifierGroupAt(i); 202 base::string16 item = 203 group.login_info.empty() ? group.name : group.login_info; 204 [groupDropDownButton_ addItemWithTitle:base::SysUTF16ToNSString(item)]; 205 if (provider_->IsNotifierGroupActiveAt(i)) { 206 title = item; 207 [[groupDropDownButton_ lastItem] setState:NSOnState]; 208 } 209 } 210 [groupDropDownButton_ setTitle:base::SysUTF16ToNSString(title)]; 211 [groupDropDownButton_ sizeToFit]; 212 dropDownButtonFrame = [groupDropDownButton_ frame]; 213 dropDownButtonFrame.origin.y = 214 NSMinY(subheaderFrame) - kCorrectedDropDownTopPadding - 215 NSHeight(dropDownButtonFrame); 216 dropDownButtonFrame.size.width = 217 NSWidth(fullFrame) - 2 * kCorrectedDropDownMargin; 218 [[self view] addSubview:groupDropDownButton_]; 219 } 220 221 // Document view for the notifier settings. 222 CGFloat y = 0; 223 NSRect documentFrame = NSMakeRect(0, 0, NSWidth(fullFrame), 0); 224 base::scoped_nsobject<NSView> documentView( 225 [[NSView alloc] initWithFrame:documentFrame]); 226 int notifierCount = notifiers_.size(); 227 for (int i = notifierCount - 1; i >= 0; --i) { 228 message_center::Notifier* notifier = notifiers_[i]; 229 // TODO(thakis): Use a custom button cell. 230 NSRect frame = NSMakeRect(kHorizontalMargin, 231 y, 232 NSWidth(documentFrame) - kHorizontalMargin * 2, 233 kEntryHeight); 234 235 base::scoped_nsobject<MCSettingsEntryView> entryView( 236 [[MCSettingsEntryView alloc] 237 initWithController:self 238 notifier:notifier 239 frame:frame 240 hasSeparator:(i != notifierCount - 1)]); 241 [documentView addSubview:entryView]; 242 y += NSHeight(frame); 243 } 244 245 documentFrame.size.height = y - kIntrinsicDropDownVerticalPadding; 246 [documentView setFrame:documentFrame]; 247 248 // Scroll view for the notifier settings. 249 NSRect scrollFrame = documentFrame; 250 scrollFrame.origin.y = 0; 251 CGFloat remainingHeight = NSMinY(dropDownButtonFrame) - NSMinY(scrollFrame); 252 253 if (NSHeight(documentFrame) < remainingHeight) { 254 // Everything fits without scrolling. 255 CGFloat delta = remainingHeight - NSHeight(documentFrame); 256 headerFrame.origin.y -= delta; 257 subheaderFrame.origin.y -= delta; 258 dropDownButtonFrame.origin.y -= delta; 259 fullFrame.size.height -= delta; 260 } else { 261 scrollFrame.size.height = remainingHeight; 262 } 263 264 scrollView_.reset([[NSScrollView alloc] initWithFrame:scrollFrame]); 265 [scrollView_ setAutohidesScrollers:YES]; 266 [scrollView_ setAutoresizingMask:NSViewMinYMargin]; 267 [scrollView_ setDocumentView:documentView]; 268 [scrollView_ setDrawsBackground:NO]; 269 [scrollView_ setHasHorizontalScroller:NO]; 270 [scrollView_ setHasVerticalScroller:YES]; 271 272 // Scroll to top. 273 NSPoint newScrollOrigin = 274 NSMakePoint(0.0, 275 NSMaxY([[scrollView_ documentView] frame]) - 276 NSHeight([[scrollView_ contentView] bounds])); 277 [[scrollView_ documentView] scrollPoint:newScrollOrigin]; 278 279 // Set final sizes. 280 [[self view] setFrame:fullFrame]; 281 [[self view] addSubview:scrollView_]; 282 [settingsText_ setFrame:headerFrame]; 283 [detailsText_ setFrame:subheaderFrame]; 284 [groupDropDownButton_ setFrame:dropDownButtonFrame]; 285} 286 287- (void)setSettingsNotifier:(message_center::Notifier*)notifier 288 enabled:(BOOL)enabled { 289 provider_->SetNotifierEnabled(*notifier, enabled); 290} 291 292- (void)learnMoreClicked:(message_center::Notifier*)notifier { 293 provider_->OnNotifierAdvancedSettingsRequested(notifier->notifier_id, NULL); 294} 295 296// Testing API ///////////////////////////////////////////////////////////////// 297 298- (NSPopUpButton*)groupDropDownButton { 299 return groupDropDownButton_; 300} 301 302- (NSScrollView*)scrollView { 303 return scrollView_; 304} 305 306// Private API ///////////////////////////////////////////////////////////////// 307 308- (void)setIcon:(NSImage*)icon forNotifierIndex:(size_t)index { 309 MCSettingsEntryView* entry = [self entryForNotifierAtIndex:index]; 310 [entry setNotifierIcon:icon]; 311} 312 313- (void)setIcon:(NSImage*)icon 314 forNotifierId:(const message_center::NotifierId&)id { 315 for (size_t i = 0; i < notifiers_.size(); ++i) { 316 if (notifiers_[i]->notifier_id == id) { 317 [self setIcon:icon forNotifierIndex:i]; 318 return; 319 } 320 } 321} 322 323- (MCSettingsEntryView*)entryForNotifierAtIndex:(size_t)index { 324 NSArray* subviews = [[scrollView_ documentView] subviews]; 325 // The checkboxes are in bottom-top order, the checkbox for notifiers_[0] is 326 // last. 327 DCHECK_LT(notifiers_.size() - 1 - index, [subviews count]); 328 NSView* view = [subviews objectAtIndex:notifiers_.size() - 1 - index]; 329 return base::mac::ObjCCastStrict<MCSettingsEntryView>(view); 330} 331 332- (void)notifierGroupSelectionChanged:(id)sender { 333 DCHECK_EQ(groupDropDownButton_.get(), sender); 334 NSPopUpButton* button = static_cast<NSPopUpButton*>(sender); 335 // The first item is a dummy item. 336 provider_->SwitchToNotifierGroup([button indexOfSelectedItem] - 1); 337} 338 339- (BOOL)notifierHasAdvancedSettings:(const message_center::NotifierId&)id { 340 return provider_->NotifierHasAdvancedSettings(id); 341} 342 343@end 344