diff options
28 files changed, 1166 insertions, 291 deletions
diff --git a/chrome/browser/notifications/message_center_settings_controller.cc b/chrome/browser/notifications/message_center_settings_controller.cc index b59967c..580b20b 100644 --- a/chrome/browser/notifications/message_center_settings_controller.cc +++ b/chrome/browser/notifications/message_center_settings_controller.cc @@ -12,7 +12,9 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/app_icon_loader_impl.h" +#include "chrome/browser/extensions/event_router.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/favicon/favicon_service.h" #include "chrome/browser/favicon/favicon_service_factory.h" #include "chrome/browser/history/history_types.h" @@ -24,6 +26,7 @@ #include "chrome/browser/profiles/profile_info_cache.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/cancelable_task_tracker.h" +#include "chrome/common/extensions/api/notifications.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/favicon/favicon_types.h" #include "content/public/browser/notification_service.h" @@ -338,6 +341,49 @@ void MessageCenterSettingsController::OnNotifierSettingsClosing() { patterns_.clear(); } +bool MessageCenterSettingsController::NotifierHasAdvancedSettings( + const NotifierId& notifier_id) const { + // TODO(dewittj): Refactor this so that notifiers have a delegate that can + // handle this in a more appropriate location. + if (notifier_id.type != NotifierId::APPLICATION) + return false; + + const std::string& extension_id = notifier_id.id; + + Profile* profile = GetCurrentProfile(); + if (!profile) + return false; + + extensions::EventRouter* event_router = + extensions::ExtensionSystem::Get(profile)->event_router(); + + return event_router->ExtensionHasEventListener( + extension_id, extensions::api::notifications::OnShowSettings::kEventName); +} + +void MessageCenterSettingsController::OnNotifierAdvancedSettingsRequested( + const NotifierId& notifier_id, + const std::string* notification_id) { + // TODO(dewittj): Refactor this so that notifiers have a delegate that can + // handle this in a more appropriate location. + if (notifier_id.type != NotifierId::APPLICATION) + return; + + const std::string& extension_id = notifier_id.id; + + Profile* profile = GetCurrentProfile(); + if (!profile) + return; + + extensions::EventRouter* event_router = + extensions::ExtensionSystem::Get(profile)->event_router(); + scoped_ptr<base::ListValue> args(new base::ListValue()); + + scoped_ptr<extensions::Event> event(new extensions::Event( + extensions::api::notifications::OnShowSettings::kEventName, args.Pass())); + event_router->DispatchEventToExtension(extension_id, event.Pass()); +} + void MessageCenterSettingsController::OnFaviconLoaded( const GURL& url, const chrome::FaviconImageResult& favicon_result) { @@ -365,7 +411,7 @@ void MessageCenterSettingsController::Observe( NotifierGroupChanged()); } -Profile* MessageCenterSettingsController::GetCurrentProfile() { +Profile* MessageCenterSettingsController::GetCurrentProfile() const { if (notifier_groups_.size() > current_notifier_group_) return notifier_groups_[current_notifier_group_]->profile(); diff --git a/chrome/browser/notifications/message_center_settings_controller.h b/chrome/browser/notifications/message_center_settings_controller.h index d4fecc6..6001815 100644 --- a/chrome/browser/notifications/message_center_settings_controller.h +++ b/chrome/browser/notifications/message_center_settings_controller.h @@ -60,6 +60,11 @@ class MessageCenterSettingsController virtual void SetNotifierEnabled(const message_center::Notifier& notifier, bool enabled) OVERRIDE; virtual void OnNotifierSettingsClosing() OVERRIDE; + virtual bool NotifierHasAdvancedSettings( + const message_center::NotifierId& notifier_id) const OVERRIDE; + virtual void OnNotifierAdvancedSettingsRequested( + const message_center::NotifierId& notifier_id, + const std::string* notification_id) OVERRIDE; // Overridden from extensions::AppIconLoader::Delegate. virtual void SetAppImage(const std::string& id, @@ -74,7 +79,7 @@ class MessageCenterSettingsController void OnFaviconLoaded(const GURL& url, const chrome::FaviconImageResult& favicon_result); - Profile* GetCurrentProfile(); + Profile* GetCurrentProfile() const; void RebuildNotifierGroups(); diff --git a/chrome/common/extensions/api/notifications.idl b/chrome/common/extensions/api/notifications.idl index f916f01..fcec0a8 100644 --- a/chrome/common/extensions/api/notifications.idl +++ b/chrome/common/extensions/api/notifications.idl @@ -158,6 +158,9 @@ namespace notifications { // The user changes the permission level. static void onPermissionLevelChanged(PermissionLevel level); + + // The user clicked on a link for the app's notification settings. + static void onShowSettings(); }; }; diff --git a/ui/message_center/cocoa/settings_controller.h b/ui/message_center/cocoa/settings_controller.h index 7bce5ab..522e7e7 100644 --- a/ui/message_center/cocoa/settings_controller.h +++ b/ui/message_center/cocoa/settings_controller.h @@ -9,6 +9,7 @@ #import "base/mac/scoped_nsobject.h" #include "base/memory/scoped_ptr.h" +#import "ui/message_center/cocoa/settings_entry_view.h" #include "ui/message_center/message_center_export.h" #include "ui/message_center/notifier_settings.h" @@ -37,7 +38,6 @@ class NotifierSettingsObserverMac : public NotifierSettingsObserver { } // namespace message_center - // The view controller responsible for the settings sheet in the center. MESSAGE_CENTER_EXPORT @interface MCSettingsController : NSViewController { @@ -65,6 +65,17 @@ MESSAGE_CENTER_EXPORT - (id)initWithProvider:(message_center::NotifierSettingsProvider*)provider trayViewController:(MCTrayViewController*)trayViewController; +// Returns whether |provider_| has an advanced settings handler for the given +// notifier; i.e. we should show the "Learn More" button. +- (BOOL)notifierHasAdvancedSettings:(const message_center::NotifierId&)id; + +// Handler when a checkbox is enabled/disabled. +- (void)setSettingsNotifier:(message_center::Notifier*)notifier + enabled:(BOOL)enabled; + +// Handler when the learn more link is clicked. +- (void)learnMoreClicked:(message_center::Notifier*)notifier; + @end // Testing API ///////////////////////////////////////////////////////////////// diff --git a/ui/message_center/cocoa/settings_controller.mm b/ui/message_center/cocoa/settings_controller.mm index 8865dd9..82f2c46 100644 --- a/ui/message_center/cocoa/settings_controller.mm +++ b/ui/message_center/cocoa/settings_controller.mm @@ -4,6 +4,8 @@ #import "ui/message_center/cocoa/settings_controller.h" +#include <algorithm> + #include "base/mac/foundation_util.h" #import "base/mac/scoped_nsobject.h" #include "base/stl_util.h" @@ -11,100 +13,59 @@ #include "grit/ui_strings.h" #include "skia/ext/skia_utils_mac.h" #include "ui/base/l10n/l10n_util.h" -#include "ui/base/resource/resource_bundle.h" +#import "ui/message_center/cocoa/settings_entry_view.h" #import "ui/message_center/cocoa/tray_view_controller.h" #include "ui/message_center/message_center_style.h" -const int kMarginWidth = 16; -const int kEntryHeight = 38; -const int kIconSize = 16; -const int kIconTextPadding = 8; -const int kCheckmarkIconPadding = 16; - -const int kIntrinsicCheckmarkPadding = 4; // Padding already provided by Cocoa. -const int kCorrectedCheckmarkPadding = - kCheckmarkIconPadding - kIntrinsicCheckmarkPadding; - -@interface MCSettingsButtonCell : NSButtonCell { - // A checkbox's regular image is the checkmark image. This additional image - // is used for the favicon or app icon shown next to the checkmark. - base::scoped_nsobject<NSImage> extraImage_; -} -- (void)setExtraImage:(NSImage*)extraImage; -@end - -@implementation MCSettingsButtonCell -- (void)setExtraImage:(NSImage*)extraImage { - extraImage_.reset([extraImage retain]); -} - -- (NSRect)drawTitle:(NSAttributedString*)title - withFrame:(NSRect)frame - inView:(NSView*)controlView { - CGFloat inset = kCorrectedCheckmarkPadding; - // drawTitle:withFrame:inView: draws the checkmark image. Draw the extra - // image as part of the checkbox's text. - if (extraImage_) { - NSRect imageRect = frame; - imageRect.origin.x += inset; - imageRect.size = NSMakeSize(kIconSize, kIconSize); - [extraImage_ drawInRect:imageRect - fromRect:NSZeroRect - operation:NSCompositeSourceOver - fraction:1.0 - respectFlipped:YES - hints:nil]; - - inset += kIconSize + kIconTextPadding; - } - frame.origin.x += inset; - frame.size.width -= inset; - return [super drawTitle:title withFrame:frame inView:controlView]; -} - -- (NSUInteger)hitTestForEvent:(NSEvent*)event - inRect:(NSRect)cellFrame - ofView:(NSView*)controlView { - NSUInteger result = - [super hitTestForEvent:event inRect:cellFrame ofView:controlView]; - if (result == NSCellHitNone) { - // The default button cell does hit testing on the attributed string. Since - // this cell draws additional spacing and an icon in front of the string, - // tweak the hit testing result. - NSPoint point = - [controlView convertPoint:[event locationInWindow] fromView:nil]; - - NSRect rect = [self titleRectForBounds:[controlView bounds]]; - rect.size = [[self attributedTitle] size]; - rect.size.width += kCorrectedCheckmarkPadding; - - if (extraImage_) { - rect.size.width += - kIconSize + kIconTextPadding + kCorrectedCheckmarkPadding; - } - - if (NSPointInRect(point, rect)) - result = NSCellHitContentArea | NSCellHitTrackableArea; - } - return result; -} -@end +using message_center::settings::kHorizontalMargin; +using message_center::settings::kEntryHeight; + +// Intrinsic padding pixels out of our control. +const int kIntrinsicHeaderTextTopPadding = 3; +const int kIntrinsicSubheaderTextTopPadding = 5; +const int kIntrinsicSubheaderTextBottomPadding = 3; +const int kIntrinsicDropDownVerticalPadding = 2; +const int kIntrinsicDropDownHorizontalPadding = 3; + +// Corrected padding values used in layout. +// Calculated additional blank space above the header text, including +// the intrinsic blank space above the header label. +const int kCorrectedHeaderTextTopPadding = + message_center::settings::kTopMargin - kIntrinsicHeaderTextTopPadding; + +// Calculated additional blank space above the subheader text, including +// the intrinsic blank space above the subheader label. +const int kCorrectedSubheaderTextTopPadding = + message_center::settings::kTitleToDescriptionSpace - + kIntrinsicSubheaderTextTopPadding; + +// Calcoulated additional vertical padding for the drop-down, including the +// blank space included with the drop-down control. +const int kCorrectedDropDownTopPadding = + message_center::settings::kDescriptionToSwitcherSpace - + kIntrinsicDropDownVerticalPadding - kIntrinsicSubheaderTextBottomPadding; + +// Calculated additional horizontal blank space for the drop down, including +// the blank space included with the drop-down control. +const int kCorrectedDropDownMargin = + kHorizontalMargin - kIntrinsicDropDownHorizontalPadding; @interface MCSettingsController (Private) // Sets the icon on the checkbox corresponding to |notifiers_[index]|. - (void)setIcon:(NSImage*)icon forNotifierIndex:(size_t)index; - (void)setIcon:(NSImage*)icon - forNotifierId:(const message_center::NotifierId&)id; + forNotifierId:(const message_center::NotifierId&)id; // Returns the NSButton corresponding to the checkbox for |notifiers_[index]|. -- (NSButton*)buttonForNotifierAtIndex:(size_t)index; +- (MCSettingsEntryView*)entryForNotifierAtIndex:(size_t)index; // Update the contents view. - (void)updateView; // Handler for the notifier group dropdown menu. - (void)notifierGroupSelectionChanged:(id)sender; + @end namespace message_center { @@ -175,32 +136,36 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { [self setView:view]; // "Settings" text. - NSRect headerFrame = NSMakeRect( - kMarginWidth, kMarginWidth, NSWidth(fullFrame), NSHeight(fullFrame)); + NSRect headerFrame = NSMakeRect(kHorizontalMargin, + kHorizontalMargin, + NSWidth(fullFrame), + NSHeight(fullFrame)); settingsText_.reset([self newLabelWithFrame:headerFrame]); [settingsText_ setAutoresizingMask:NSViewMinYMargin]; - [settingsText_ setTextColor:gfx::SkColorToCalibratedNSColor( - message_center::kRegularTextColor)]; - [settingsText_ setFont: - [NSFont messageFontOfSize:message_center::kTitleFontSize]]; + [settingsText_ setTextColor: + gfx::SkColorToCalibratedNSColor(message_center::kRegularTextColor)]; + [settingsText_ + setFont:[NSFont messageFontOfSize:message_center::kTitleFontSize]]; [settingsText_ setStringValue: - l10n_util::GetNSString(IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL)]; + l10n_util::GetNSString(IDS_MESSAGE_CENTER_SETTINGS_BUTTON_LABEL)]; [settingsText_ sizeToFit]; headerFrame = [settingsText_ frame]; - headerFrame.origin.y = - NSMaxY(fullFrame) - kMarginWidth - NSHeight(headerFrame); + headerFrame.origin.y = NSMaxY(fullFrame) - kCorrectedHeaderTextTopPadding - + NSHeight(headerFrame); [[self view] addSubview:settingsText_]; // Subheader. - NSRect subheaderFrame = NSMakeRect( - kMarginWidth, kMarginWidth, NSWidth(fullFrame), NSHeight(fullFrame)); + NSRect subheaderFrame = NSMakeRect(kHorizontalMargin, + kHorizontalMargin, + NSWidth(fullFrame), + NSHeight(fullFrame)); detailsText_.reset([self newLabelWithFrame:subheaderFrame]); [detailsText_ setAutoresizingMask:NSViewMinYMargin]; - [detailsText_ setTextColor:gfx::SkColorToCalibratedNSColor( - message_center::kDimTextColor)]; - [detailsText_ setFont: - [NSFont messageFontOfSize:message_center::kMessageFontSize]]; + [detailsText_ setTextColor: + gfx::SkColorToCalibratedNSColor(message_center::kDimTextColor)]; + [detailsText_ + setFont:[NSFont messageFontOfSize:message_center::kMessageFontSize]]; size_t groupCount = provider_->GetNotifierGroupCount(); [detailsText_ setStringValue:l10n_util::GetNSString( @@ -209,15 +174,17 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { [detailsText_ sizeToFit]; subheaderFrame = [detailsText_ frame]; subheaderFrame.origin.y = - NSMinY(headerFrame) - message_center::kTextTopPadding - + NSMinY(headerFrame) - kCorrectedSubheaderTextTopPadding - NSHeight(subheaderFrame); [[self view] addSubview:detailsText_]; // Profile switcher is only needed for more than one profile. NSRect dropDownButtonFrame = subheaderFrame; if (groupCount > 1) { - dropDownButtonFrame = NSMakeRect( - kMarginWidth, kMarginWidth, NSWidth(fullFrame), NSHeight(fullFrame)); + dropDownButtonFrame = NSMakeRect(kCorrectedDropDownMargin, + kHorizontalMargin, + NSWidth(fullFrame), + NSHeight(fullFrame)); groupDropDownButton_.reset( [[NSPopUpButton alloc] initWithFrame:dropDownButtonFrame pullsDown:YES]); @@ -240,9 +207,10 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { [groupDropDownButton_ sizeToFit]; dropDownButtonFrame = [groupDropDownButton_ frame]; dropDownButtonFrame.origin.y = - NSMinY(subheaderFrame) - message_center::kTextTopPadding - + NSMinY(subheaderFrame) - kCorrectedDropDownTopPadding - NSHeight(dropDownButtonFrame); - dropDownButtonFrame.size.width = NSWidth(fullFrame) - 2 * kMarginWidth; + dropDownButtonFrame.size.width = + NSWidth(fullFrame) - 2 * kCorrectedDropDownMargin; [[self view] addSubview:groupDropDownButton_]; } @@ -251,40 +219,32 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { NSRect documentFrame = NSMakeRect(0, 0, NSWidth(fullFrame), 0); base::scoped_nsobject<NSView> documentView( [[NSView alloc] initWithFrame:documentFrame]); - for (int i = notifiers_.size() - 1; i >= 0; --i) { + int notifierCount = notifiers_.size(); + for (int i = notifierCount - 1; i >= 0; --i) { message_center::Notifier* notifier = notifiers_[i]; - // TODO(thakis): Use a custom button cell. - NSRect frame = NSMakeRect( - kMarginWidth, y, NSWidth(documentFrame) - kMarginWidth, kEntryHeight); - base::scoped_nsobject<NSButton> button( - [[NSButton alloc] initWithFrame:frame]); - base::scoped_nsobject<MCSettingsButtonCell> cell( - [[MCSettingsButtonCell alloc] - initTextCell:base::SysUTF16ToNSString(notifier->name)]); - if (!notifier->icon.IsEmpty()) - [cell setExtraImage:notifier->icon.AsNSImage()]; - [button setCell:cell]; - [button setButtonType:NSSwitchButton]; - - [button setState:notifier->enabled ? NSOnState : NSOffState]; - [button setTag:i]; - [button setTarget:self]; - [button setAction:@selector(checkboxClicked:)]; - - [documentView addSubview:button.release()]; - + NSRect frame = NSMakeRect(kHorizontalMargin, + y, + NSWidth(documentFrame) - kHorizontalMargin * 2, + kEntryHeight); + + base::scoped_nsobject<MCSettingsEntryView> entryView( + [[MCSettingsEntryView alloc] + initWithController:self + notifier:notifier + frame:frame + hasSeparator:(i != notifierCount - 1)]); + [documentView addSubview:entryView]; y += NSHeight(frame); } - documentFrame.size.height = y; + + documentFrame.size.height = y - kIntrinsicDropDownVerticalPadding; [documentView setFrame:documentFrame]; // Scroll view for the notifier settings. NSRect scrollFrame = documentFrame; - scrollFrame.origin.y = kMarginWidth; - CGFloat remainingHeight = - NSMinY(dropDownButtonFrame) - message_center::kTextTopPadding - - NSMinY(scrollFrame); + scrollFrame.origin.y = 0; + CGFloat remainingHeight = NSMinY(dropDownButtonFrame) - NSMinY(scrollFrame); if (NSHeight(documentFrame) < remainingHeight) { // Everything fits without scrolling. @@ -320,9 +280,13 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { [groupDropDownButton_ setFrame:dropDownButtonFrame]; } -- (void)checkboxClicked:(id)sender { - provider_->SetNotifierEnabled(*notifiers_[[sender tag]], - [sender state] == NSOnState); +- (void)setSettingsNotifier:(message_center::Notifier*)notifier + enabled:(BOOL)enabled { + provider_->SetNotifierEnabled(*notifier, enabled); +} + +- (void)learnMoreClicked:(message_center::Notifier*)notifier { + provider_->OnNotifierAdvancedSettingsRequested(notifier->notifier_id, NULL); } // Testing API ///////////////////////////////////////////////////////////////// @@ -338,13 +302,12 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { // Private API ///////////////////////////////////////////////////////////////// - (void)setIcon:(NSImage*)icon forNotifierIndex:(size_t)index { - NSButton* button = [self buttonForNotifierAtIndex:index]; - [[button cell] setExtraImage:icon]; - [button setNeedsDisplay:YES]; + MCSettingsEntryView* entry = [self entryForNotifierAtIndex:index]; + [entry setNotifierIcon:icon]; } - (void)setIcon:(NSImage*)icon - forNotifierId:(const message_center::NotifierId&)id { + forNotifierId:(const message_center::NotifierId&)id { for (size_t i = 0; i < notifiers_.size(); ++i) { if (notifiers_[i]->notifier_id == id) { [self setIcon:icon forNotifierIndex:i]; @@ -353,12 +316,13 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { } } -- (NSButton*)buttonForNotifierAtIndex:(size_t)index { +- (MCSettingsEntryView*)entryForNotifierAtIndex:(size_t)index { NSArray* subviews = [[scrollView_ documentView] subviews]; // The checkboxes are in bottom-top order, the checkbox for notifiers_[0] is // last. + DCHECK_LT(notifiers_.size() - 1 - index, [subviews count]); NSView* view = [subviews objectAtIndex:notifiers_.size() - 1 - index]; - return base::mac::ObjCCastStrict<NSButton>(view); + return base::mac::ObjCCastStrict<MCSettingsEntryView>(view); } - (void)notifierGroupSelectionChanged:(id)sender { @@ -368,4 +332,8 @@ void NotifierSettingsObserverMac::NotifierGroupChanged() { provider_->SwitchToNotifierGroup([button indexOfSelectedItem] - 1); } +- (BOOL)notifierHasAdvancedSettings:(const message_center::NotifierId&)id { + return provider_->NotifierHasAdvancedSettings(id); +} + @end diff --git a/ui/message_center/cocoa/settings_controller_unittest.mm b/ui/message_center/cocoa/settings_controller_unittest.mm index 2e4a491..74f9f07 100644 --- a/ui/message_center/cocoa/settings_controller_unittest.mm +++ b/ui/message_center/cocoa/settings_controller_unittest.mm @@ -11,15 +11,16 @@ @implementation MCSettingsController (TestingInterface) - (NSInteger)profileSwitcherListCount { // Subtract the dummy item. - return [self groupDropDownButton] ? - [[self groupDropDownButton] numberOfItems] - 1 : 0; + return [self groupDropDownButton] + ? [[self groupDropDownButton] numberOfItems] - 1 + : 0; } - (NSUInteger)scrollViewItemCount { return [[[[self scrollView] documentView] subviews] count]; } -- (NSButton*)bottomMostButton { +- (MCSettingsEntryView*)bottomMostButton { // The checkboxes are created bottom-to-top, so the first object is the // bottom-most. return [[[[self scrollView] documentView] subviews] objectAtIndex:0]; @@ -52,8 +53,8 @@ Notifier* NewNotifier(const std::string& id, TEST_F(CocoaTest, Basic) { // Notifiers are owned by settings controller. std::vector<Notifier*> notifiers; - notifiers.push_back(NewNotifier("id", "title", /*enabled=*/true)); - notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/false)); + notifiers.push_back(NewNotifier("id", "title", /*enabled=*/ true)); + notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/ false)); FakeNotifierSettingsProvider provider(notifiers); @@ -68,8 +69,8 @@ TEST_F(CocoaTest, Basic) { TEST_F(CocoaTest, Toggle) { // Notifiers are owned by settings controller. std::vector<Notifier*> notifiers; - notifiers.push_back(NewNotifier("id", "title", /*enabled=*/true)); - notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/false)); + notifiers.push_back(NewNotifier("id", "title", /*enabled=*/ true)); + notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/ false)); FakeNotifierSettingsProvider provider(notifiers); @@ -78,7 +79,8 @@ TEST_F(CocoaTest, Toggle) { trayViewController:nil]); [controller view]; - NSButton* toggleSecond = [controller bottomMostButton]; + MCSettingsEntryView* toggleView = [controller bottomMostButton]; + NSButton* toggleSecond = [toggleView checkbox]; [toggleSecond performClick:nil]; EXPECT_TRUE(provider.WasEnabled(*notifiers.back())); @@ -94,8 +96,8 @@ TEST_F(CocoaTest, Toggle) { TEST_F(CocoaTest, SingleProfile) { // Notifiers are owned by settings controller. std::vector<Notifier*> notifiers; - notifiers.push_back(NewNotifier("id", "title", /*enabled=*/true)); - notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/false)); + notifiers.push_back(NewNotifier("id", "title", /*enabled=*/ true)); + notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/ false)); FakeNotifierSettingsProvider provider(notifiers); @@ -110,13 +112,13 @@ TEST_F(CocoaTest, SingleProfile) { TEST_F(CocoaTest, MultiProfile) { FakeNotifierSettingsProvider provider; std::vector<Notifier*> group1_notifiers; - group1_notifiers.push_back(NewNotifier("id", "title", /*enabled=*/true)); - group1_notifiers.push_back(NewNotifier("id2", "title2", /*enabled=*/false)); + group1_notifiers.push_back(NewNotifier("id", "title", /*enabled=*/ true)); + group1_notifiers.push_back(NewNotifier("id2", "title2", /*enabled=*/ false)); provider.AddGroup(NewGroup("Group1", "GroupId1"), group1_notifiers); std::vector<Notifier*> group2_notifiers; - group2_notifiers.push_back(NewNotifier("id3", "title3", /*enabled=*/true)); - group2_notifiers.push_back(NewNotifier("id4", "title4", /*enabled=*/false)); - group2_notifiers.push_back(NewNotifier("id5", "title5", /*enabled=*/false)); + group2_notifiers.push_back(NewNotifier("id3", "title3", /*enabled=*/ true)); + group2_notifiers.push_back(NewNotifier("id4", "title4", /*enabled=*/ false)); + group2_notifiers.push_back(NewNotifier("id5", "title5", /*enabled=*/ false)); provider.AddGroup(NewGroup("Group2", "GroupId2"), group2_notifiers); base::scoped_nsobject<MCSettingsController> controller( @@ -127,4 +129,25 @@ TEST_F(CocoaTest, MultiProfile) { EXPECT_EQ(2, [controller profileSwitcherListCount]); } +TEST_F(CocoaTest, LearnMoreButton) { + std::vector<Notifier*> notifiers; + notifiers.push_back(NewNotifier("id", "title", /*enabled=*/ true)); + notifiers.push_back(NewNotifier("id2", "title2", /*enabled=*/ false)); + + FakeNotifierSettingsProvider provider(notifiers); + EXPECT_EQ(0u, provider.settings_requested_count()); + NotifierId has_settings_handler_notifier = + NotifierId(NotifierId::APPLICATION, "id2"); + provider.SetNotifierHasAdvancedSettings(has_settings_handler_notifier); + + base::scoped_nsobject<MCSettingsController> controller( + [[MCSettingsController alloc] initWithProvider:&provider + trayViewController:nil]); + [controller view]; + + [[controller bottomMostButton] clickLearnMore]; + + EXPECT_EQ(1u, provider.settings_requested_count()); +} + } // namespace message_center diff --git a/ui/message_center/cocoa/settings_entry_view.h b/ui/message_center/cocoa/settings_entry_view.h new file mode 100644 index 0000000..e8902c6 --- /dev/null +++ b/ui/message_center/cocoa/settings_entry_view.h @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_MESSAGE_CENTER_COCOA_SETTINGS_ENTRY_VIEW_H_ +#define UI_MESSAGE_CENTER_COCOA_SETTINGS_ENTRY_VIEW_H_ + +#import <Cocoa/Cocoa.h> + +#import "base/mac/scoped_nsobject.h" +#import "ui/base/cocoa/hover_image_button.h" +#include "ui/message_center/notifier_settings.h" + +@class MCSettingsController; + +// The view that renders individual notifiers in the settings sheet. This +// includes an enable/disable checkbox, icon, name, and learn more button, +// and an optional horizontal separator line. +@interface MCSettingsEntryView : NSBox { + @private + // Weak. Owns us. + MCSettingsController* controller_; + + // Weak, owned by MCSettingsController. + message_center::Notifier* notifier_; + + // The image that will be displayed next to the notifier name, loaded + // asynchronously. + base::scoped_nsobject<NSImage> notifierIcon_; + + // The button that can be displayed after the notifier name, that when + // clicked on causes an event to be fired for more information. + base::scoped_nsobject<HoverImageButton> learnMoreButton_; + + // The button that contains the label, notifier icon and checkbox. + base::scoped_nsobject<NSButton> checkbox_; + + // The border on the bottom of the view. + BOOL hasSeparator_; + base::scoped_nsobject<NSBox> separator_; +} + +- (id)initWithController:(MCSettingsController*)controller + notifier:(message_center::Notifier*)notifier + frame:(NSRect)frame + hasSeparator:(BOOL)hasSeparator; + +- (void)setNotifierIcon:(NSImage*)notifierIcon; + +- (NSButton*)checkbox; + +@end + +@interface MCSettingsEntryView (TestingAPI) +- (void)clickLearnMore; +@end + +#endif // UI_MESSAGE_CENTER_COCOA_SETTINGS_ENTRY_VIEW_H_ diff --git a/ui/message_center/cocoa/settings_entry_view.mm b/ui/message_center/cocoa/settings_entry_view.mm new file mode 100644 index 0000000..ea7e938 --- /dev/null +++ b/ui/message_center/cocoa/settings_entry_view.mm @@ -0,0 +1,271 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/message_center/cocoa/settings_entry_view.h" + +#include "base/strings/sys_string_conversions.h" +#include "grit/ui_resources.h" +#include "skia/ext/skia_utils_mac.h" +#include "ui/base/resource/resource_bundle.h" +#import "ui/message_center/cocoa/settings_controller.h" +#include "ui/message_center/message_center_style.h" + +using message_center::settings::kEntryIconSize; +using message_center::settings::kInternalHorizontalSpacing; + +// Size of the widget rendered for us by Cocoa. +const int kCocoaCheckboxSize = 14; + +// Intrinsic padding pixels out of our control. +// Cocoa gives the checkmark some blank space on either side. +const int kIntrinsicCheckmarkLeftPadding = 2; +const int kIntrinsicCheckmarkRightPadding = 4; +// Labels have a bit of whitespace to the left, which can throw +// off measurements. +const int kIntrinsicTextLeftPadding = 1; + +// The learn more image is bigger than the actual size of the learn more +// pixels, this represents the difference. +const int kIntrinsicLearnMorePadding = 2; + +// Corrected padding values used in layout. +// This computes the amout of padding based on the area reserved for the +// checkbox and the actual checkbox size in pixels. +const int kCheckmarkPaddingNecessary = + (message_center::settings::kCheckboxSizeWithPadding - kCocoaCheckboxSize) / + 2; + +// These represent the additional padding that we must give the checkmark +// control based on the required padding and the intrinsic padding. +const int kCorrectedCheckmarkLeftPadding = + kCheckmarkPaddingNecessary - kIntrinsicCheckmarkLeftPadding; +const int kCorrectedCheckmarkRightPadding = + kCheckmarkPaddingNecessary + kInternalHorizontalSpacing - + kIntrinsicCheckmarkRightPadding; + +// The amount of space we want, based on the spec and the intrinsic text space +// included by Cocoa. +const int kCorrectedIconTextPadding = + kInternalHorizontalSpacing - kIntrinsicTextLeftPadding; + +// We want a certain amount of space to the right of the learn more button, +// this metric incorporates the intrinsic learn more blank space to compute it. +const int kCorrectedEntryRightPadding = + kInternalHorizontalSpacing - kIntrinsicLearnMorePadding; + +@interface MCSettingsButtonCell : NSButtonCell { + // A checkbox's regular image is the checkmark image. This additional image + // is used for the favicon or app icon shown next to the checkmark. + base::scoped_nsobject<NSImage> extraImage_; +} +- (void)setExtraImage:(NSImage*)extraImage; +@end + +@implementation MCSettingsButtonCell +- (void)setExtraImage:(NSImage*)extraImage { + extraImage_.reset([extraImage retain]); +} + +- (NSRect)drawTitle:(NSAttributedString*)title + withFrame:(NSRect)frame + inView:(NSView*)controlView { + CGFloat inset = kCorrectedCheckmarkRightPadding; + // drawTitle:withFrame:inView: draws the checkmark image. Draw the extra + // image as part of the checkbox's text. + if (extraImage_) { + NSRect imageRect = frame; + imageRect.origin.x += inset; + // Center the image vertically. + if (NSHeight(frame) > kEntryIconSize) + imageRect.origin.y += (NSHeight(frame) - kEntryIconSize) / 2; + imageRect.size = NSMakeSize(kEntryIconSize, kEntryIconSize); + [extraImage_ drawInRect:imageRect + fromRect:NSZeroRect + operation:NSCompositeSourceOver + fraction:1.0 + respectFlipped:YES + hints:nil]; + + inset += kEntryIconSize + kCorrectedIconTextPadding; + } + frame.origin.x += inset; + frame.size.width -= inset; + return [super drawTitle:title withFrame:frame inView:controlView]; +} + +- (NSSize)cellSizeForBounds:(NSRect)aRect { + NSSize size = [super cellSizeForBounds:aRect]; + size.width += kCorrectedCheckmarkRightPadding; + if (extraImage_) { + size.width += kEntryIconSize + kCorrectedIconTextPadding; + size.height = std::max(static_cast<float>(kEntryIconSize), size.height); + } + return size; +} + +- (NSUInteger)hitTestForEvent:(NSEvent*)event + inRect:(NSRect)cellFrame + ofView:(NSView*)controlView { + NSUInteger result = + [super hitTestForEvent:event inRect:cellFrame ofView:controlView]; + if (result == NSCellHitNone) { + // The default button cell does hit testing on the attributed string. Since + // this cell draws additional spacing and an icon in front of the string, + // tweak the hit testing result. + NSPoint point = + [controlView convertPoint:[event locationInWindow] fromView:nil]; + + NSRect rect = [self titleRectForBounds:[controlView bounds]]; + rect.size = [[self attributedTitle] size]; + rect.size.width += kCorrectedCheckmarkRightPadding; + + if (extraImage_) + rect.size.width += kEntryIconSize + kCorrectedIconTextPadding; + + if (NSPointInRect(point, rect)) + result = NSCellHitContentArea | NSCellHitTrackableArea; + } + return result; +} +@end + +@implementation MCSettingsEntryView + +- (id)initWithController:(MCSettingsController*)controller + notifier:(message_center::Notifier*)notifier + frame:(NSRect)frame + hasSeparator:(BOOL)hasSeparator { + if ((self = [super initWithFrame:frame])) { + [self setBoxType:NSBoxCustom]; + [self setBorderType:NSNoBorder]; + [self setTitlePosition:NSNoTitle]; + [self setContentViewMargins:NSZeroSize]; + + hasSeparator_ = hasSeparator; + controller_ = controller; + notifier_ = notifier; + if (!notifier->icon.IsEmpty()) + notifierIcon_.reset(notifier->icon.CopyNSImage()); + [self layout]; + } + return self; +} + +- (void)setNotifierIcon:(NSImage*)notifierIcon { + notifierIcon_.reset([notifierIcon retain]); + [self layout]; +} + +- (NSButton*)checkbox { + return checkbox_; +} + +- (void)layout { + BOOL hasLearnMore = + [controller_ notifierHasAdvancedSettings:notifier_->notifier_id]; + + // Now calculate the space available for the checkbox button. + NSRect checkboxFrame = [self bounds]; + checkboxFrame.origin.x += kCorrectedCheckmarkLeftPadding; + checkboxFrame.size.width -= + kCorrectedCheckmarkLeftPadding + kCorrectedEntryRightPadding; + + NSRect learnMoreFrame = + NSMakeRect(checkboxFrame.origin.x + checkboxFrame.size.width, + checkboxFrame.origin.y, + 0, + checkboxFrame.size.height); + + // Initially place the learn more button right-aligned. + if (hasLearnMore) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + NSImage* defaultImage = + rb.GetNativeImageNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS).ToNSImage(); + NSSize defaultImageSize = [defaultImage size]; + learnMoreFrame.size.width = defaultImageSize.width; + learnMoreFrame.origin.x -= defaultImageSize.width; + + // May need to center the image if it's shorter than the entry. + if (defaultImageSize.height < learnMoreFrame.size.height) { + learnMoreFrame.origin.y += + (learnMoreFrame.size.height - defaultImageSize.height) / 2; + learnMoreFrame.size.height = defaultImageSize.height; + } + + // Since we have an image then we need to ensure that the text from the + // checkbox doesn't overlap with the learn more image. + checkboxFrame.size.width -= + kCorrectedIconTextPadding + learnMoreFrame.size.width; + + if (!learnMoreButton_.get()) { + learnMoreButton_.reset( + [[HoverImageButton alloc] initWithFrame:learnMoreFrame]); + [self addSubview:learnMoreButton_]; + } else { + [learnMoreButton_ setFrame:learnMoreFrame]; + } + [learnMoreButton_ setDefaultImage:defaultImage]; + [learnMoreButton_ setHoverImage:rb.GetNativeImageNamed( + IDR_NOTIFICATION_ADVANCED_SETTINGS_HOVER).ToNSImage()]; + [learnMoreButton_ setPressedImage:rb.GetNativeImageNamed( + IDR_NOTIFICATION_ADVANCED_SETTINGS_PRESSED).ToNSImage()]; + [learnMoreButton_ setBordered:NO]; + [learnMoreButton_ setTarget:self]; + [learnMoreButton_ setAction:@selector(learnMoreClicked:)]; + } + + if (!checkbox_.get()) { + checkbox_.reset([[NSButton alloc] initWithFrame:checkboxFrame]); + [self addSubview:checkbox_]; + } else { + [checkbox_ setFrame:checkboxFrame]; + } + + base::scoped_nsobject<MCSettingsButtonCell> cell([[MCSettingsButtonCell alloc] + initTextCell:base::SysUTF16ToNSString(notifier_->name)]); + if ([notifierIcon_ isValid]) + [cell setExtraImage:notifierIcon_]; + + [checkbox_ setCell:cell]; + [checkbox_ setButtonType:NSSwitchButton]; + [checkbox_ setState:notifier_->enabled ? NSOnState : NSOffState]; + [checkbox_ setTarget:self]; + [checkbox_ setAction:@selector(checkboxClicked:)]; + + if (hasSeparator_) { + NSRect separatorRect = [self bounds]; + separatorRect.size.height = 1; + if (!separator_.get()) { + separator_.reset([[NSBox alloc] initWithFrame:separatorRect]); + [separator_ setBoxType:NSBoxCustom]; + [separator_ setBorderType:NSLineBorder]; + [separator_ setBorderColor:gfx::SkColorToCalibratedNSColor( + message_center::settings::kEntrySeparatorColor)]; + [separator_ setTitlePosition:NSNoTitle]; + [separator_ setContentViewMargins:NSZeroSize]; + [self addSubview:separator_]; + } else { + [separator_ setFrame:separatorRect]; + } + } +} + +- (void)checkboxClicked:(id)sender { + BOOL enabled = [sender state] == NSOnState; + [controller_ setSettingsNotifier:notifier_ enabled:enabled]; +} + +- (void)learnMoreClicked:(id)sender { + [controller_ learnMoreClicked:notifier_]; +} + +// Testing API ///////////////////////////////////////////////////////////////// + +- (void)clickLearnMore { + [learnMoreButton_ performClick:nil]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////// diff --git a/ui/message_center/fake_notifier_settings_provider.cc b/ui/message_center/fake_notifier_settings_provider.cc index f593ed9..c25f7a2 100644 --- a/ui/message_center/fake_notifier_settings_provider.cc +++ b/ui/message_center/fake_notifier_settings_provider.cc @@ -17,13 +17,14 @@ FakeNotifierSettingsProvider::NotifierGroupItem::~NotifierGroupItem() { FakeNotifierSettingsProvider::FakeNotifierSettingsProvider() : closed_called_count_(0), - active_item_index_(0) { -} + active_item_index_(0), + notifier_settings_requested_count_(0u) { } FakeNotifierSettingsProvider::FakeNotifierSettingsProvider( const std::vector<Notifier*>& notifiers) : closed_called_count_(0), - active_item_index_(0) { + active_item_index_(0), + notifier_settings_requested_count_(0u) { NotifierGroupItem item; item.group = new NotifierGroup(gfx::Image(), UTF8ToUTF16("Fake name"), @@ -78,6 +79,19 @@ void FakeNotifierSettingsProvider::OnNotifierSettingsClosing() { closed_called_count_++; } +bool FakeNotifierSettingsProvider::NotifierHasAdvancedSettings( + const message_center::NotifierId& notifier_id) const { + if (!notifier_id_with_settings_handler_) + return false; + return *notifier_id_with_settings_handler_ == notifier_id; +} + +void FakeNotifierSettingsProvider::OnNotifierAdvancedSettingsRequested( + const NotifierId& notifier_id, + const std::string* notification_id) { + notifier_settings_requested_count_++; +} + void FakeNotifierSettingsProvider::AddObserver( NotifierSettingsObserver* observer) { } @@ -98,8 +112,17 @@ void FakeNotifierSettingsProvider::AddGroup( items_.push_back(item); } +void FakeNotifierSettingsProvider::SetNotifierHasAdvancedSettings( + const NotifierId& notifier_id) { + notifier_id_with_settings_handler_.reset(new NotifierId(notifier_id)); +} + int FakeNotifierSettingsProvider::closed_called_count() { return closed_called_count_; } +size_t FakeNotifierSettingsProvider::settings_requested_count() const { + return notifier_settings_requested_count_; +} + } // namespace message_center diff --git a/ui/message_center/fake_notifier_settings_provider.h b/ui/message_center/fake_notifier_settings_provider.h index b71a7e9..08f4d34 100644 --- a/ui/message_center/fake_notifier_settings_provider.h +++ b/ui/message_center/fake_notifier_settings_provider.h @@ -5,6 +5,7 @@ #ifndef UI_MESSAGE_CENTER_FAKE_NOTIFIER_SETTINGS_PROVIDER_H_ #define UI_MESSAGE_CENTER_FAKE_NOTIFIER_SETTINGS_PROVIDER_H_ +#include "base/memory/scoped_ptr.h" #include "ui/message_center/notifier_settings.h" namespace message_center { @@ -19,12 +20,10 @@ class FakeNotifierSettingsProvider : public NotifierSettingsProvider { virtual ~FakeNotifierSettingsProvider(); virtual size_t GetNotifierGroupCount() const OVERRIDE; - virtual const message_center::NotifierGroup& GetNotifierGroupAt( - size_t index) const OVERRIDE; + virtual const NotifierGroup& GetNotifierGroupAt(size_t index) const OVERRIDE; virtual bool IsNotifierGroupActiveAt(size_t index) const OVERRIDE; virtual void SwitchToNotifierGroup(size_t index) OVERRIDE; - virtual const message_center::NotifierGroup& GetActiveNotifierGroup() const - OVERRIDE; + virtual const NotifierGroup& GetActiveNotifierGroup() const OVERRIDE; virtual void GetNotifierList(std::vector<Notifier*>* notifiers) OVERRIDE; @@ -32,6 +31,11 @@ class FakeNotifierSettingsProvider : public NotifierSettingsProvider { bool enabled) OVERRIDE; virtual void OnNotifierSettingsClosing() OVERRIDE; + virtual bool NotifierHasAdvancedSettings(const NotifierId& notifier_id) const + OVERRIDE; + virtual void OnNotifierAdvancedSettingsRequested( + const NotifierId& notifier_id, + const std::string* notification_id) OVERRIDE; virtual void AddObserver(NotifierSettingsObserver* observer) OVERRIDE; virtual void RemoveObserver(NotifierSettingsObserver* observer) OVERRIDE; @@ -40,6 +44,11 @@ class FakeNotifierSettingsProvider : public NotifierSettingsProvider { void AddGroup(NotifierGroup* group, const std::vector<Notifier*>& notifiers); + // For testing, this method can be used to specify a notifier that has a learn + // more button. This class only saves the last one that was set. + void SetNotifierHasAdvancedSettings(const NotifierId& notifier_id); + size_t settings_requested_count() const; + private: struct NotifierGroupItem { NotifierGroup* group; @@ -53,6 +62,8 @@ class FakeNotifierSettingsProvider : public NotifierSettingsProvider { std::vector<NotifierGroupItem> items_; int closed_called_count_; size_t active_item_index_; + scoped_ptr<NotifierId> notifier_id_with_settings_handler_; + size_t notifier_settings_requested_count_; }; } // namespace message_center diff --git a/ui/message_center/message_center.gyp b/ui/message_center/message_center.gyp index e8ceb78..39e9813 100644 --- a/ui/message_center/message_center.gyp +++ b/ui/message_center/message_center.gyp @@ -34,6 +34,8 @@ 'cocoa/popup_controller.mm', 'cocoa/settings_controller.h', 'cocoa/settings_controller.mm', + 'cocoa/settings_entry_view.h', + 'cocoa/settings_entry_view.mm', 'cocoa/status_item_view.h', 'cocoa/status_item_view.mm', 'cocoa/tray_controller.h', @@ -77,6 +79,8 @@ 'views/message_center_bubble.h', 'views/message_center_button_bar.cc', 'views/message_center_button_bar.h', + 'views/message_center_focus_border.h', + 'views/message_center_focus_border.cc', 'views/message_center_view.cc', 'views/message_center_view.h', 'views/message_popup_collection.cc', @@ -162,6 +166,7 @@ 'dependencies': [ '../../base/base.gyp:base', '../../base/base.gyp:test_support_base', + '../../chrome/chrome_resources.gyp:packed_resources', '../../skia/skia.gyp:skia', '../../testing/gtest.gyp:gtest', '../../url/url.gyp:url_lib', @@ -169,6 +174,8 @@ '../gfx/gfx.gyp:gfx', '../ui.gyp:ui', '../ui_unittests.gyp:run_ui_unittests', + '../ui.gyp:ui_resources', + '../../url/url.gyp:url_lib', 'message_center', 'message_center_test_support', ], @@ -186,6 +193,11 @@ 'test/run_all_unittests.cc', ], 'conditions': [ + ['use_glib == 1 or OS == "ios"', { + 'dependencies': [ + '../base/strings/ui_strings.gyp:ui_unittest_strings', + ], + }], ['OS=="mac"', { 'dependencies': [ '../ui_unittests.gyp:ui_test_support', @@ -203,6 +215,7 @@ 'views/bounded_label_unittest.cc', 'views/message_center_view_unittest.cc', 'views/message_popup_collection_unittest.cc', + 'views/notifier_settings_view_unittest.cc', ], }], ['notifications==0', { # Android and iOS. diff --git a/ui/message_center/message_center_style.cc b/ui/message_center/message_center_style.cc index 2812576..1e53b0e 100644 --- a/ui/message_center/message_center_style.cc +++ b/ui/message_center/message_center_style.cc @@ -26,6 +26,23 @@ const size_t kMaxVisiblePopupNotifications = 3; const SkColor kMessageCenterBorderColor = SkColorSetRGB(0xC7, 0xCA, 0xCE); const SkColor kMessageCenterShadowColor = SkColorSetARGB(0.5 * 255, 0, 0, 0); +// Within the settings page //////////////////////////////////////////////////// + +namespace settings { + +const SkColor kEntrySeparatorColor = SkColorSetARGB(0.1 * 255, 0, 0, 0); +const int kEntryHeight = 45; +const int kEntrySeparatorHeight = 1; +const int kHorizontalMargin = 10; +const int kTopMargin = 20; +const int kTitleToDescriptionSpace = 20; +const int kEntryIconSize = 16; +const int kDescriptionToSwitcherSpace = 15; +const int kInternalHorizontalSpacing = 10; +const int kCheckboxSizeWithPadding = 24; + +} // namespace settings + // Within a notification /////////////////////////////////////////////////////// // Pixel dimensions. diff --git a/ui/message_center/message_center_style.h b/ui/message_center/message_center_style.h index 18dd340..172f7a1 100644 --- a/ui/message_center/message_center_style.h +++ b/ui/message_center/message_center_style.h @@ -33,6 +33,22 @@ MESSAGE_CENTER_EXPORT extern const int kNotificationWidth; // H size of the MESSAGE_CENTER_EXPORT extern const SkColor kMessageCenterBorderColor; MESSAGE_CENTER_EXPORT extern const SkColor kMessageCenterShadowColor; +// Settings dialog constants. +namespace settings { + +extern const SkColor kEntrySeparatorColor; +extern const int kEntryHeight; +extern const int kEntrySeparatorHeight; +extern const int kHorizontalMargin; +extern const int kTopMargin; +extern const int kTitleToDescriptionSpace; +extern const int kEntryIconSize; +extern const int kDescriptionToSwitcherSpace; +extern const int kInternalHorizontalSpacing; +extern const int kCheckboxSizeWithPadding; + +} // namespace + // Within a notification /////////////////////////////////////////////////////// // DIP dimensions (H = horizontal, V = vertical). diff --git a/ui/message_center/notifier_settings.h b/ui/message_center/notifier_settings.h index 6188599..9111427 100644 --- a/ui/message_center/notifier_settings.h +++ b/ui/message_center/notifier_settings.h @@ -157,6 +157,16 @@ class MESSAGE_CENTER_EXPORT NotifierSettingsProvider { // Called when the settings window is closed. virtual void OnNotifierSettingsClosing() = 0; + + // Called to determine if a particular notifier can respond to a request for + // more information. + virtual bool NotifierHasAdvancedSettings(const NotifierId& notifier_id) + const = 0; + + // Called upon request for more information about a particular notifier. + virtual void OnNotifierAdvancedSettingsRequested( + const NotifierId& notifier_id, + const std::string* notification_id) = 0; }; } // namespace message_center diff --git a/ui/message_center/views/message_center_button_bar.cc b/ui/message_center/views/message_center_button_bar.cc index 149c3ed..dc440d5 100644 --- a/ui/message_center/views/message_center_button_bar.cc +++ b/ui/message_center/views/message_center_button_bar.cc @@ -30,6 +30,8 @@ namespace message_center { namespace { const int kButtonSize = 40; const int kChevronWidth = 8; +const int kFooterTopMargin = 6; +const int kFooterBottomMargin = 3; const int kFooterLeftMargin = 20; const int kFooterRightMargin = 14; } // namespace @@ -183,8 +185,10 @@ void MessageCenterButtonBar::ViewVisibilityChanged() { int image_margin = std::max(0, (kButtonSize - settings_image->width()) / 2); views::GridLayout* layout = new views::GridLayout(this); SetLayoutManager(layout); - layout->SetInsets( - 0, kFooterLeftMargin, 0, std::max(0, kFooterRightMargin - image_margin)); + layout->SetInsets(kFooterTopMargin, + kFooterLeftMargin, + kFooterBottomMargin, + std::max(0, kFooterRightMargin - image_margin)); views::ColumnSet* column = layout->AddColumnSet(0); if (title_arrow_->visible()) { // Column for the left-arrow used to back out of settings. diff --git a/ui/message_center/views/message_center_focus_border.cc b/ui/message_center/views/message_center_focus_border.cc new file mode 100644 index 0000000..90a4630 --- /dev/null +++ b/ui/message_center/views/message_center_focus_border.cc @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/message_center/views/message_center_focus_border.h" + +#include "ui/gfx/canvas.h" +#include "ui/gfx/rect.h" +#include "ui/message_center/message_center_style.h" +#include "ui/views/view.h" + +namespace message_center { + +MessageCenterFocusBorder::MessageCenterFocusBorder() {} + +MessageCenterFocusBorder::~MessageCenterFocusBorder() {} + +void MessageCenterFocusBorder::Paint(const views::View& view, + gfx::Canvas* canvas) const { + gfx::Rect rect(view.GetLocalBounds()); + rect.Inset(2, 1, 2, 3); + canvas->DrawRect(rect, kFocusBorderColor); +} + +} // namespace message_center diff --git a/ui/message_center/views/message_center_focus_border.h b/ui/message_center/views/message_center_focus_border.h new file mode 100644 index 0000000..de49bae --- /dev/null +++ b/ui/message_center/views/message_center_focus_border.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_MESSAGE_CENTER_VIEWS_MESSAGE_CENTER_FOCUS_BORDER_H_ +#define UI_MESSAGE_CENTER_VIEWS_MESSAGE_CENTER_FOCUS_BORDER_H_ + +#include "ui/views/focus_border.h" +#include "ui/views/view.h" + +namespace message_center { + +// Focus border class that draws a 1px blue border inset more on the bottom than +// on the sides. +class MessageCenterFocusBorder : public views::FocusBorder { + public: + MessageCenterFocusBorder(); + virtual ~MessageCenterFocusBorder(); + + private: + // views::FocusBorder overrides: + virtual void Paint(const views::View& view, gfx::Canvas* canvas) const + OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(MessageCenterFocusBorder); +}; + +} // namespace message_center + +#endif // UI_MESSAGE_CENTER_VIEWS_MESSAGE_CENTER_FOCUS_BORDER_H_ diff --git a/ui/message_center/views/message_center_view.cc b/ui/message_center/views/message_center_view.cc index 4be6574..7225ea4 100644 --- a/ui/message_center/views/message_center_view.cc +++ b/ui/message_center/views/message_center_view.cc @@ -253,10 +253,10 @@ MessageListView::MessageListView(MessageCenterView* message_center_view, set_background(views::Background::CreateSolidBackground( kMessageCenterBackgroundColor)); set_border(views::Border::CreateEmptyBorder( - kMarginBetweenItems - shadow_insets.top(), /* top */ - kMarginBetweenItems - shadow_insets.left(), /* left */ - kMarginBetweenItems - shadow_insets.bottom(), /* bottom */ - kMarginBetweenItems - shadow_insets.right() /* right */ )); + top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */ + kMarginBetweenItems - shadow_insets.left(), /* left */ + top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */ + kMarginBetweenItems - shadow_insets.right() /* right */)); } MessageListView::~MessageListView() { @@ -753,12 +753,13 @@ void MessageCenterView::Layout() { int button_height = button_bar_->GetHeightForWidth(width()) + button_bar_->GetInsets().height(); // Skip unnecessary re-layout of contents during the resize animation. - if (settings_transition_animation_ && - settings_transition_animation_->is_animating() && - settings_transition_animation_->current_part_index() == 0) { - if (!top_down_) + bool animating = settings_transition_animation_ && + settings_transition_animation_->is_animating(); + if (animating && settings_transition_animation_->current_part_index() == 0) { + if (!top_down_) { button_bar_->SetBounds( 0, height() - button_height, width(), button_height); + } return; } @@ -777,18 +778,16 @@ void MessageCenterView::Layout() { else is_scrollable = settings_view_->IsScrollable(); - if (is_scrollable && !button_bar_->border()) { - // Draw separator line on the top of the button bar if it is on the bottom - // or draw it at the bottom if the bar is on the top. - button_bar_->set_border(views::Border::CreateSolidSidedBorder( - top_down_ ? 0 : 1, - 0, - top_down_ ? 1 : 0, - 0, - kFooterDelimiterColor)); - button_bar_->SchedulePaint(); - } else if (!is_scrollable && button_bar_->border()) { - button_bar_->set_border(NULL); + if (!animating) { + if (is_scrollable) { + // Draw separator line on the top of the button bar if it is on the bottom + // or draw it at the bottom if the bar is on the top. + button_bar_->set_border(views::Border::CreateSolidSidedBorder( + top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0, kFooterDelimiterColor)); + } else { + button_bar_->set_border(views::Border::CreateEmptyBorder( + top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0)); + } button_bar_->SchedulePaint(); } button_bar_->SetBounds(0, diff --git a/ui/message_center/views/notifier_settings_view.cc b/ui/message_center/views/notifier_settings_view.cc index 63e72a4..b43552a 100644 --- a/ui/message_center/views/notifier_settings_view.cc +++ b/ui/message_center/views/notifier_settings_view.cc @@ -21,6 +21,7 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/size.h" #include "ui/message_center/message_center_style.h" +#include "ui/message_center/views/message_center_focus_border.h" #include "ui/message_center/views/message_center_view.h" #include "ui/views/background.h" #include "ui/views/border.h" @@ -30,6 +31,8 @@ #include "ui/views/controls/button/menu_button.h" #include "ui/views/controls/image_view.h" #include "ui/views/controls/label.h" +#include "ui/views/controls/link.h" +#include "ui/views/controls/link_listener.h" #include "ui/views/controls/menu/menu_runner.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/controls/scrollbar/overlay_scroll_bar.h" @@ -43,44 +46,94 @@ #endif namespace message_center { +namespace settings { + +// Additional views-specific parameters. + +// The width of the settings pane in pixels. +const int kWidth = 360; + +// The width of the learn more icon in pixels. +const int kLearnMoreSize = 12; + +// The width of the click target that contains the learn more button in pixels. +const int kLearnMoreTargetWidth = 28; + +// The height of the click target that contains the learn more button in pixels. +const int kLearnMoreTargetHeight = 40; + +// The minimum height of the settings pane in pixels. +const int kMinimumHeight = 480; + +// The horizontal margin of the title area of the settings pane in addition to +// the standard margin from settings::kHorizontalMargin. +const int kTitleMargin = 10; + +} // namespace settings + namespace { + +// The amount of built-in padding for the notifier group switcher. const int kButtonPainterInsets = 5; -// We really want the margin to be 20px, but various views are padded by -// whitespace. -const int kDesiredMargin = 20; -// The MenuButton has 2px whitespace built-in. + +// Menu button metrics to make the text line up. const int kMenuButtonInnateMargin = 2; -const int kMinimumHorizontalMargin = kDesiredMargin - kMenuButtonInnateMargin; -// The EntryViews' leftmost view is a checkbox with 1px whitespace built in, so -// the margin for entry views should be one less than the target margin. -const int kCheckboxInnateMargin = 1; -const int kEntryMargin = kDesiredMargin - kCheckboxInnateMargin; const int kMenuButtonLeftPadding = 12; const int kMenuButtonRightPadding = 13; const int kMenuButtonVerticalPadding = 9; + +// Used to place the context menu correctly. const int kMenuWhitespaceOffset = 2; -const int kMinimumWindowHeight = 480; -const int kMinimumWindowWidth = 320; -const int kSettingsTitleBottomMargin = 12; -const int kSettingsTitleTopMargin = 15; -const int kSpaceInButtonComponents = 16; -const int kTitleVerticalMargin = 1; -const int kTitleElementSpacing = 10; -const int kEntryHeight = kMinimumWindowHeight / 10; + +// The innate vertical blank space in the label for the title of the settings +// pane. +const int kInnateTitleBottomMargin = 1; +const int kInnateTitleTopMargin = 7; + +// The innate top blank space in the label for the description of the settings +// pane. +const int kInnateDescriptionTopMargin = 2; + +// Checkboxes have some built-in right padding blank space. +const int kInnateCheckboxRightPadding = 2; + +// Spec defines the checkbox size; the innate padding throws this measurement +// off so we need to compute a slightly different area for the checkbox to +// inhabit. +const int kComputedCheckboxSize = + settings::kCheckboxSizeWithPadding - kInnateCheckboxRightPadding; + +// The menubutton has innate margin, so we need to compensate for that when +// figuring the margin of the title area. +const int kComputedContentsTitleMargin = 0 - kMenuButtonInnateMargin; + +// The spec doesn't include the bottom blank area of the title bar or the innate +// blank area in the description label, so we'll use this as the space between +// the title and description. +const int kComputedTitleBottomMargin = settings::kDescriptionToSwitcherSpace - + kInnateTitleBottomMargin - + kInnateDescriptionTopMargin; + +// The blank space above the title needs to be adjusted by the amount of blank +// space included in the title label. +const int kComputedTitleTopMargin = + settings::kTopMargin - kInnateTitleTopMargin; + +// The switcher has a lot of blank space built in so we should include that when +// spacing the title area vertically. +const int kComputedTitleElementSpacing = + settings::kDescriptionToSwitcherSpace - kButtonPainterInsets - 1; // The view to guarantee the 48px height and place the contents at the // middle. It also guarantee the left margin. class EntryView : public views::View { public: EntryView(views::View* contents); - virtual ~EntryView(); - - // Overridden from views::View: + virtual ~EntryView(); // Overridden from views::View: virtual void Layout() OVERRIDE; virtual gfx::Size GetPreferredSize() OVERRIDE; virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; virtual void OnFocus() OVERRIDE; - virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE; virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE; virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE; @@ -89,25 +142,25 @@ class EntryView : public views::View { }; EntryView::EntryView(views::View* contents) { + set_focus_border(new MessageCenterFocusBorder()); AddChildView(contents); } -EntryView::~EntryView() { -} +EntryView::~EntryView() {} void EntryView::Layout() { DCHECK_EQ(1, child_count()); views::View* content = child_at(0); - int content_width = width() - kEntryMargin * 2; + int content_width = width(); int content_height = content->GetHeightForWidth(content_width); int y = std::max((height() - content_height) / 2, 0); - content->SetBounds(kEntryMargin, y, content_width, content_height); + content->SetBounds(0, y, content_width, content_height); } gfx::Size EntryView::GetPreferredSize() { DCHECK_EQ(1, child_count()); gfx::Size size = child_at(0)->GetPreferredSize(); - size.SetToMax(gfx::Size(kMinimumWindowWidth, kEntryHeight)); + size.SetToMax(gfx::Size(settings::kWidth, settings::kEntryHeight)); return size; } @@ -121,13 +174,6 @@ void EntryView::OnFocus() { ScrollRectToVisible(GetLocalBounds()); } -void EntryView::OnPaintFocusBorder(gfx::Canvas* canvas) { - if (HasFocus() && (focusable() || IsAccessibilityFocusable())) { - canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3), - kFocusBorderColor); - } -} - bool EntryView::OnKeyPressed(const ui::KeyEvent& event) { return child_at(0)->OnKeyPressed(event); } @@ -183,9 +229,9 @@ class NotifierGroupMenuModel : public ui::SimpleMenuModel, // Overridden from ui::SimpleMenuModel::Delegate: virtual bool IsCommandIdChecked(int command_id) const OVERRIDE; virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE; - virtual bool GetAcceleratorForCommandId( - int command_id, - ui::Accelerator* accelerator) OVERRIDE; + virtual bool GetAcceleratorForCommandId(int command_id, + ui::Accelerator* accelerator) + OVERRIDE; virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE; private: @@ -243,78 +289,182 @@ void NotifierGroupMenuModel::ExecuteCommand(int command_id, int event_flags) { // We do not use views::Checkbox class directly because it doesn't support // showing 'icon'. -class NotifierSettingsView::NotifierButton : public views::CustomButton, - public views::ButtonListener { - public: - NotifierButton(Notifier* notifier, views::ButtonListener* listener) - : views::CustomButton(listener), - notifier_(notifier), - icon_view_(NULL), - checkbox_(new views::Checkbox(string16())) { - DCHECK(notifier); - SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kHorizontal, 0, 0, kSpaceInButtonComponents)); - checkbox_->SetChecked(notifier_->enabled); - checkbox_->set_listener(this); - checkbox_->set_focusable(false); - checkbox_->SetAccessibleName(notifier_->name); - AddChildView(checkbox_); - UpdateIconImage(notifier_->icon); - AddChildView(new views::Label(notifier_->name)); +NotifierSettingsView::NotifierButton::NotifierButton( + NotifierSettingsProvider* provider, + Notifier* notifier, + views::ButtonListener* listener) + : views::CustomButton(listener), + provider_(provider), + notifier_(notifier), + icon_view_(new views::ImageView()), + name_view_(new views::Label(notifier_->name)), + checkbox_(new views::Checkbox(string16())), + learn_more_(NULL) { + DCHECK(provider); + DCHECK(notifier); + + checkbox_->SetChecked(notifier_->enabled); + checkbox_->set_listener(this); + checkbox_->set_focusable(false); + checkbox_->SetAccessibleName(notifier_->name); + + if (ShouldHaveLearnMoreButton()) { + // Create a more-info button that will be right-aligned. + learn_more_ = new views::ImageButton(this); + learn_more_->set_focus_border(new MessageCenterFocusBorder()); + learn_more_->set_request_focus_on_press(false); + learn_more_->set_focusable(true); + + ui::ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + learn_more_->SetImage( + views::Button::STATE_NORMAL, + rb.GetImageSkiaNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS)); + learn_more_->SetImage( + views::Button::STATE_HOVERED, + rb.GetImageSkiaNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS_HOVER)); + learn_more_->SetImage( + views::Button::STATE_PRESSED, + rb.GetImageSkiaNamed(IDR_NOTIFICATION_ADVANCED_SETTINGS_PRESSED)); + learn_more_->SetState(views::Button::STATE_NORMAL); + int learn_more_border_width = + (settings::kLearnMoreTargetWidth - settings::kLearnMoreSize) / 2; + int learn_more_border_height = + (settings::kLearnMoreTargetHeight - settings::kLearnMoreSize) / 2; + // The image itself is quite small, this large invisible border creates a + // much bigger click target. + learn_more_->set_border( + views::Border::CreateEmptyBorder(learn_more_border_height, + learn_more_border_width, + learn_more_border_height, + learn_more_border_width)); + learn_more_->SetImageAlignment(views::ImageButton::ALIGN_CENTER, + views::ImageButton::ALIGN_MIDDLE); } - void UpdateIconImage(const gfx::Image& icon) { - notifier_->icon = icon; - if (icon.IsEmpty()) { - delete icon_view_; - icon_view_ = NULL; - } else { - if (!icon_view_) { - icon_view_ = new views::ImageView(); - AddChildViewAt(icon_view_, 1); - } - icon_view_->SetImage(icon.ToImageSkia()); - icon_view_->SetImageSize(gfx::Size(kSettingsIconSize, kSettingsIconSize)); - } - Layout(); - SchedulePaint(); - } + UpdateIconImage(notifier_->icon); +} - void SetChecked(bool checked) { - checkbox_->SetChecked(checked); - notifier_->enabled = checked; - } +NotifierSettingsView::NotifierButton::~NotifierButton() {} - bool checked() const { - return checkbox_->checked(); - } +void NotifierSettingsView::NotifierButton::UpdateIconImage( + const gfx::Image& icon) { + bool has_icon_view = false; - const Notifier& notifier() const { - return *notifier_.get(); + notifier_->icon = icon; + if (!icon.IsEmpty()) { + icon_view_->SetImage(icon.ToImageSkia()); + icon_view_->SetImageSize( + gfx::Size(settings::kEntryIconSize, settings::kEntryIconSize)); + has_icon_view = true; } + GridChanged(ShouldHaveLearnMoreButton(), has_icon_view); +} - private: - // Overridden from views::ButtonListener: - virtual void ButtonPressed(views::Button* button, - const ui::Event& event) OVERRIDE { - DCHECK(button == checkbox_); +void NotifierSettingsView::NotifierButton::SetChecked(bool checked) { + checkbox_->SetChecked(checked); + notifier_->enabled = checked; +} + +bool NotifierSettingsView::NotifierButton::checked() const { + return checkbox_->checked(); +} + +bool NotifierSettingsView::NotifierButton::has_learn_more() const { + return learn_more_ != NULL; +} + +const Notifier& NotifierSettingsView::NotifierButton::notifier() const { + return *notifier_.get(); +} + +void NotifierSettingsView::NotifierButton::SendLearnMorePressedForTest() { + if (learn_more_ == NULL) + return; + gfx::Point point(110, 120); + ui::MouseEvent pressed( + ui::ET_MOUSE_PRESSED, point, point, ui::EF_LEFT_MOUSE_BUTTON); + ButtonPressed(learn_more_, pressed); +} + +void NotifierSettingsView::NotifierButton::ButtonPressed( + views::Button* button, + const ui::Event& event) { + if (button == checkbox_) { // The checkbox state has already changed at this point, but we'll update // the state on NotifierSettingsView::ButtonPressed() too, so here change // back to the previous state. checkbox_->SetChecked(!checkbox_->checked()); CustomButton::NotifyClick(event); + } else if (button == learn_more_) { + DCHECK(provider_); + provider_->OnNotifierAdvancedSettingsRequested(notifier_->notifier_id, + NULL); } +} - virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE { - static_cast<views::View*>(checkbox_)->GetAccessibleState(state); +void NotifierSettingsView::NotifierButton::GetAccessibleState( + ui::AccessibleViewState* state) { + static_cast<views::View*>(checkbox_)->GetAccessibleState(state); +} + +bool NotifierSettingsView::NotifierButton::ShouldHaveLearnMoreButton() const { + if (!provider_) + return false; + + return provider_->NotifierHasAdvancedSettings(notifier_->notifier_id); +} + +void NotifierSettingsView::NotifierButton::GridChanged(bool has_learn_more, + bool has_icon_view) { + using views::ColumnSet; + using views::GridLayout; + + GridLayout* layout = new GridLayout(this); + SetLayoutManager(layout); + ColumnSet* cs = layout->AddColumnSet(0); + // Add a column for the checkbox. + cs->AddPaddingColumn(0, kInnateCheckboxRightPadding); + cs->AddColumn(GridLayout::CENTER, + GridLayout::CENTER, + 0, + GridLayout::FIXED, + kComputedCheckboxSize, + 0); + cs->AddPaddingColumn(0, settings::kInternalHorizontalSpacing); + + if (has_icon_view) { + // Add a column for the icon. + cs->AddColumn(GridLayout::CENTER, + GridLayout::CENTER, + 0, + GridLayout::FIXED, + settings::kEntryIconSize, + 0); + cs->AddPaddingColumn(0, settings::kInternalHorizontalSpacing); } - scoped_ptr<Notifier> notifier_; - views::ImageView* icon_view_; - views::Checkbox* checkbox_; + // Add a column for the name. + cs->AddColumn( + GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0); - DISALLOW_COPY_AND_ASSIGN(NotifierButton); -}; + // Add a padding column which contains expandable blank space. + cs->AddPaddingColumn(1, 0); + + // Add a column for the learn more button if necessary. + if (has_learn_more) { + cs->AddPaddingColumn(0, settings::kInternalHorizontalSpacing); + cs->AddColumn( + GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0); + } + + layout->StartRow(0, 0); + layout->AddView(checkbox_); + if (has_icon_view) + layout->AddView(icon_view_); + layout->AddView(name_view_); + if (has_learn_more) + layout->AddView(learn_more_); +} NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider) : title_arrow_(NULL), @@ -328,8 +478,8 @@ NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider) set_focusable(true); set_focus_border(NULL); - set_background(views::Background::CreateSolidBackground( - kMessageCenterBackgroundColor)); + set_background( + views::Background::CreateSolidBackground(kMessageCenterBackgroundColor)); if (get_use_acceleration_when_possible()) SetPaintToLayer(true); @@ -341,10 +491,10 @@ NotifierSettingsView::NotifierSettingsView(NotifierSettingsProvider* provider) title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); title_label_->SetMultiLine(true); title_label_->set_border( - views::Border::CreateEmptyBorder(kSettingsTitleTopMargin, - kDesiredMargin, - kSettingsTitleBottomMargin, - kDesiredMargin)); + views::Border::CreateEmptyBorder(kComputedTitleTopMargin, + settings::kTitleMargin, + kComputedTitleBottomMargin, + settings::kTitleMargin)); AddChildView(title_label_); @@ -372,7 +522,8 @@ bool NotifierSettingsView::IsScrollable() { void NotifierSettingsView::UpdateIconImage(const NotifierId& notifier_id, const gfx::Image& icon) { for (std::set<NotifierButton*>::iterator iter = buttons_.begin(); - iter != buttons_.end(); ++iter) { + iter != buttons_.end(); + ++iter) { if ((*iter)->notifier().notifier_id == notifier_id) { (*iter)->UpdateIconImage(icon); return; @@ -393,15 +544,15 @@ void NotifierSettingsView::UpdateContentsView( buttons_.clear(); views::View* contents_view = new views::View(); - contents_view->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + contents_view->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kVertical, settings::kHorizontalMargin, 0, 0)); views::View* contents_title_view = new views::View(); contents_title_view->SetLayoutManager( new views::BoxLayout(views::BoxLayout::kVertical, - kMinimumHorizontalMargin, - kTitleVerticalMargin, - kTitleElementSpacing)); + kComputedContentsTitleMargin, + 0, + kComputedTitleElementSpacing)); bool need_account_switcher = provider_ && provider_->GetNotifierGroupCount() > 1; @@ -415,7 +566,10 @@ void NotifierSettingsView::UpdateContentsView( top_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); top_label->SetMultiLine(true); top_label->set_border(views::Border::CreateEmptyBorder( - 0, kMenuButtonInnateMargin, 0, kMenuButtonInnateMargin)); + 0, + settings::kTitleMargin + kMenuButtonInnateMargin, + 0, + settings::kTitleMargin + kMenuButtonInnateMargin)); contents_title_view->AddChildView(top_label); if (need_account_switcher) { @@ -433,9 +587,26 @@ void NotifierSettingsView::UpdateContentsView( contents_view->AddChildView(contents_title_view); - for (size_t i = 0; i < notifiers.size(); ++i) { - NotifierButton* button = new NotifierButton(notifiers[i], this); + size_t notifier_count = notifiers.size(); + for (size_t i = 0; i < notifier_count; ++i) { + NotifierButton* button = new NotifierButton(provider_, notifiers[i], this); EntryView* entry = new EntryView(button); + + // This code emulates separators using borders. We will create an invisible + // border on the last notifier, as the spec leaves a space for it. + scoped_ptr<views::Border> entry_border; + if (i == notifier_count - 1) { + entry_border.reset(views::Border::CreateEmptyBorder( + 0, 0, settings::kEntrySeparatorHeight, 0)); + } else { + entry_border.reset(views::Border::CreateSolidSidedBorder( + 0, + 0, + settings::kEntrySeparatorHeight, + 0, + settings::kEntrySeparatorColor)); + } + entry->set_border(entry_border.release()); entry->set_focusable(true); contents_view->AddChildView(entry); buttons_.insert(button); @@ -449,7 +620,10 @@ void NotifierSettingsView::UpdateContentsView( void NotifierSettingsView::Layout() { int title_height = title_label_->GetHeightForWidth(width()); - title_label_->SetBounds(0, 0, width(), title_height); + title_label_->SetBounds(settings::kTitleMargin, + 0, + width() - settings::kTitleMargin * 2, + title_height); views::View* contents_view = scroller_->contents(); int content_width = width(); @@ -463,10 +637,10 @@ void NotifierSettingsView::Layout() { } gfx::Size NotifierSettingsView::GetMinimumSize() { - gfx::Size size(kMinimumWindowWidth, kMinimumWindowHeight); + gfx::Size size(settings::kWidth, settings::kMinimumHeight); int total_height = title_label_->GetPreferredSize().height() + scroller_->contents()->GetPreferredSize().height(); - if (total_height > kMinimumWindowHeight) + if (total_height > settings::kMinimumHeight) size.Enlarge(scroller_->GetScrollBarWidth(), 0); return size; } @@ -500,8 +674,8 @@ void NotifierSettingsView::ButtonPressed(views::Button* sender, return; } - std::set<NotifierButton*>::iterator iter = buttons_.find( - static_cast<NotifierButton*>(sender)); + std::set<NotifierButton*>::iterator iter = + buttons_.find(static_cast<NotifierButton*>(sender)); if (iter == buttons_.end()) return; diff --git a/ui/message_center/views/notifier_settings_view.h b/ui/message_center/views/notifier_settings_view.h index 5e64ab9..bbf77e3 100644 --- a/ui/message_center/views/notifier_settings_view.h +++ b/ui/message_center/views/notifier_settings_view.h @@ -11,8 +11,10 @@ #include "ui/message_center/message_center_export.h" #include "ui/message_center/notifier_settings.h" #include "ui/message_center/views/message_bubble_base.h" +#include "ui/views/controls/button/checkbox.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/menu_button_listener.h" +#include "ui/views/controls/image_view.h" #include "ui/views/view.h" namespace views { @@ -47,7 +49,44 @@ class MESSAGE_CENTER_EXPORT NotifierSettingsView } private: - class NotifierButton; + FRIEND_TEST_ALL_PREFIXES(NotifierSettingsViewTest, TestLearnMoreButton); + + class NotifierButton : public views::CustomButton, + public views::ButtonListener { + public: + NotifierButton(NotifierSettingsProvider* provider, + Notifier* notifier, + views::ButtonListener* listener); + virtual ~NotifierButton(); + + void UpdateIconImage(const gfx::Image& icon); + void SetChecked(bool checked); + bool checked() const; + bool has_learn_more() const; + const Notifier& notifier() const; + + void SendLearnMorePressedForTest(); + + private: + // Overridden from views::ButtonListener: + virtual void ButtonPressed(views::Button* button, + const ui::Event& event) OVERRIDE; + virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; + + bool ShouldHaveLearnMoreButton() const; + // Helper function to reset the layout when the view has substantially + // changed. + void GridChanged(bool has_learn_more, bool has_icon_view); + + NotifierSettingsProvider* provider_; // Weak. + const scoped_ptr<Notifier> notifier_; + views::ImageView* icon_view_; + views::Label* name_view_; + views::Checkbox* checkbox_; + views::ImageButton* learn_more_; + + DISALLOW_COPY_AND_ASSIGN(NotifierButton); + }; // Given a new list of notifiers, updates the view to reflect it. void UpdateContentsView(const std::vector<Notifier*>& notifiers); @@ -62,6 +101,8 @@ class MESSAGE_CENTER_EXPORT NotifierSettingsView // Overridden from views::ButtonListener: virtual void ButtonPressed(views::Button* sender, const ui::Event& event) OVERRIDE; + + // Overridden from views::MenuButtonListener: virtual void OnMenuButtonClicked(views::View* source, const gfx::Point& point) OVERRIDE; diff --git a/ui/message_center/views/notifier_settings_view_unittest.cc b/ui/message_center/views/notifier_settings_view_unittest.cc new file mode 100644 index 0000000..b241ac3 --- /dev/null +++ b/ui/message_center/views/notifier_settings_view_unittest.cc @@ -0,0 +1,124 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/message_center/fake_notifier_settings_provider.h" +#include "ui/message_center/views/notifier_settings_view.h" + +namespace message_center { + +namespace { + +Notifier* NewNotifier(const std::string& id, + const std::string& title, + bool enabled) { + NotifierId notifier_id(NotifierId::APPLICATION, id); + return new Notifier(notifier_id, base::UTF8ToUTF16(title), enabled); +} + +// A class used by NotifierSettingsView to integrate with a setting system +// for the clients of this module. +class MESSAGE_CENTER_EXPORT TestingNotifierSettingsProvider + : public FakeNotifierSettingsProvider { + public: + TestingNotifierSettingsProvider(const std::vector<Notifier*>& notifiers, + const NotifierId& settings_handler_id) + : FakeNotifierSettingsProvider(notifiers), + settings_handler_id_(settings_handler_id), + request_count_(0u) {} + virtual ~TestingNotifierSettingsProvider() {} + + virtual bool NotifierHasAdvancedSettings(const NotifierId& notifier_id) const + OVERRIDE { + return notifier_id == settings_handler_id_; + } + + virtual void OnNotifierAdvancedSettingsRequested( + const NotifierId& notifier_id, + const std::string* notification_id) OVERRIDE { + request_count_++; + last_notifier_id_settings_requested_.reset(new NotifierId(notifier_id)); + } + + size_t request_count() const { return request_count_; } + const NotifierId* last_requested_notifier_id() const { + return last_notifier_id_settings_requested_.get(); + } + + private: + NotifierId settings_handler_id_; + size_t request_count_; + scoped_ptr<NotifierId> last_notifier_id_settings_requested_; +}; + +} // namespace + +class NotifierSettingsViewTest : public testing::Test { + public: + NotifierSettingsViewTest(); + virtual ~NotifierSettingsViewTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + NotifierSettingsView* GetView() const; + TestingNotifierSettingsProvider* settings_provider() const { + return settings_provider_.get(); + } + + private: + scoped_ptr<TestingNotifierSettingsProvider> settings_provider_; + scoped_ptr<NotifierSettingsView> notifier_settings_view_; + + DISALLOW_COPY_AND_ASSIGN(NotifierSettingsViewTest); +}; + +NotifierSettingsViewTest::NotifierSettingsViewTest() {} + +NotifierSettingsViewTest::~NotifierSettingsViewTest() {} + +void NotifierSettingsViewTest::SetUp() { + std::vector<Notifier*> notifiers; + notifiers.push_back(NewNotifier("id", "title", /*enabled=*/true)); + notifiers.push_back(NewNotifier("id2", "other title", /*enabled=*/false)); + settings_provider_.reset(new TestingNotifierSettingsProvider( + notifiers, NotifierId(NotifierId::APPLICATION, "id"))); + notifier_settings_view_.reset( + new NotifierSettingsView(settings_provider_.get())); +} + +void NotifierSettingsViewTest::TearDown() { + notifier_settings_view_.reset(); + settings_provider_.reset(); +} + +NotifierSettingsView* NotifierSettingsViewTest::GetView() const { + return notifier_settings_view_.get(); +} + +TEST_F(NotifierSettingsViewTest, TestLearnMoreButton) { + const std::set<NotifierSettingsView::NotifierButton*> buttons = + GetView()->buttons_; + EXPECT_EQ(2u, buttons.size()); + size_t number_of_settings_buttons = 0; + std::set<NotifierSettingsView::NotifierButton*>::iterator iter = + buttons.begin(); + for (; iter != buttons.end(); ++iter) { + if ((*iter)->has_learn_more()) { + ++number_of_settings_buttons; + (*iter)->SendLearnMorePressedForTest(); + } + } + + EXPECT_EQ(1u, number_of_settings_buttons); + EXPECT_EQ(1u, settings_provider()->request_count()); + const NotifierId* last_settings_button_id = + settings_provider()->last_requested_notifier_id(); + ASSERT_FALSE(last_settings_button_id == NULL); + EXPECT_EQ(NotifierId(NotifierId::APPLICATION, "id"), + *last_settings_button_id); +} + +} // namespace message_center diff --git a/ui/resources/default_100_percent/common/notification_advanced_settings.png b/ui/resources/default_100_percent/common/notification_advanced_settings.png Binary files differnew file mode 100644 index 0000000..f3c7a58 --- /dev/null +++ b/ui/resources/default_100_percent/common/notification_advanced_settings.png diff --git a/ui/resources/default_100_percent/common/notification_advanced_settings_hover.png b/ui/resources/default_100_percent/common/notification_advanced_settings_hover.png Binary files differnew file mode 100644 index 0000000..b5496d4 --- /dev/null +++ b/ui/resources/default_100_percent/common/notification_advanced_settings_hover.png diff --git a/ui/resources/default_100_percent/common/notification_advanced_settings_pressed.png b/ui/resources/default_100_percent/common/notification_advanced_settings_pressed.png Binary files differnew file mode 100644 index 0000000..929e91b --- /dev/null +++ b/ui/resources/default_100_percent/common/notification_advanced_settings_pressed.png diff --git a/ui/resources/default_200_percent/common/notification_advanced_settings.png b/ui/resources/default_200_percent/common/notification_advanced_settings.png Binary files differnew file mode 100644 index 0000000..8db4cc4 --- /dev/null +++ b/ui/resources/default_200_percent/common/notification_advanced_settings.png diff --git a/ui/resources/default_200_percent/common/notification_advanced_settings_hover.png b/ui/resources/default_200_percent/common/notification_advanced_settings_hover.png Binary files differnew file mode 100644 index 0000000..d3880f8 --- /dev/null +++ b/ui/resources/default_200_percent/common/notification_advanced_settings_hover.png diff --git a/ui/resources/default_200_percent/common/notification_advanced_settings_pressed.png b/ui/resources/default_200_percent/common/notification_advanced_settings_pressed.png Binary files differnew file mode 100644 index 0000000..ed55d30 --- /dev/null +++ b/ui/resources/default_200_percent/common/notification_advanced_settings_pressed.png diff --git a/ui/resources/ui_resources.grd b/ui/resources/ui_resources.grd index 2e17560f..0a323cf 100644 --- a/ui/resources/ui_resources.grd +++ b/ui/resources/ui_resources.grd @@ -272,6 +272,9 @@ <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_ARROW" file="common/notification_arrow.png"/> <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_ARROW_HOVER" file="common/notification_arrow_hover.png"/> <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_ARROW_PRESSED" file="common/notification_arrow_pressed.png"/> + <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_ADVANCED_SETTINGS" file="common/notification_advanced_settings.png"/> + <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_ADVANCED_SETTINGS_HOVER" file="common/notification_advanced_settings_hover.png"/> + <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_ADVANCED_SETTINGS_PRESSED" file="common/notification_advanced_settings_pressed.png"/> <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_CLEAR_ALL" file="common/notification_clear_all.png"/> <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_CLEAR_ALL_DISABLED" file="common/notification_clear_all_disabled.png"/> <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_CLEAR_ALL_HOVER" file="common/notification_clear_all_hover.png"/> |