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