Home | History | Annotate | Download | only in cocoa
      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(&notifiers_);
    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(&notifiers_);
    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