summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/notifications/message_center_settings_controller.cc48
-rw-r--r--chrome/browser/notifications/message_center_settings_controller.h7
-rw-r--r--chrome/common/extensions/api/notifications.idl3
-rw-r--r--ui/message_center/cocoa/settings_controller.h13
-rw-r--r--ui/message_center/cocoa/settings_controller.mm232
-rw-r--r--ui/message_center/cocoa/settings_controller_unittest.mm53
-rw-r--r--ui/message_center/cocoa/settings_entry_view.h58
-rw-r--r--ui/message_center/cocoa/settings_entry_view.mm271
-rw-r--r--ui/message_center/fake_notifier_settings_provider.cc29
-rw-r--r--ui/message_center/fake_notifier_settings_provider.h19
-rw-r--r--ui/message_center/message_center.gyp13
-rw-r--r--ui/message_center/message_center_style.cc17
-rw-r--r--ui/message_center/message_center_style.h16
-rw-r--r--ui/message_center/notifier_settings.h10
-rw-r--r--ui/message_center/views/message_center_button_bar.cc8
-rw-r--r--ui/message_center/views/message_center_focus_border.cc25
-rw-r--r--ui/message_center/views/message_center_focus_border.h30
-rw-r--r--ui/message_center/views/message_center_view.cc39
-rw-r--r--ui/message_center/views/notifier_settings_view.cc396
-rw-r--r--ui/message_center/views/notifier_settings_view.h43
-rw-r--r--ui/message_center/views/notifier_settings_view_unittest.cc124
-rw-r--r--ui/resources/default_100_percent/common/notification_advanced_settings.pngbin0 -> 179 bytes
-rw-r--r--ui/resources/default_100_percent/common/notification_advanced_settings_hover.pngbin0 -> 179 bytes
-rw-r--r--ui/resources/default_100_percent/common/notification_advanced_settings_pressed.pngbin0 -> 179 bytes
-rw-r--r--ui/resources/default_200_percent/common/notification_advanced_settings.pngbin0 -> 282 bytes
-rw-r--r--ui/resources/default_200_percent/common/notification_advanced_settings_hover.pngbin0 -> 282 bytes
-rw-r--r--ui/resources/default_200_percent/common/notification_advanced_settings_pressed.pngbin0 -> 277 bytes
-rw-r--r--ui/resources/ui_resources.grd3
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
new file mode 100644
index 0000000..f3c7a58
--- /dev/null
+++ b/ui/resources/default_100_percent/common/notification_advanced_settings.png
Binary files differ
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
new file mode 100644
index 0000000..b5496d4
--- /dev/null
+++ b/ui/resources/default_100_percent/common/notification_advanced_settings_hover.png
Binary files differ
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
new file mode 100644
index 0000000..929e91b
--- /dev/null
+++ b/ui/resources/default_100_percent/common/notification_advanced_settings_pressed.png
Binary files differ
diff --git a/ui/resources/default_200_percent/common/notification_advanced_settings.png b/ui/resources/default_200_percent/common/notification_advanced_settings.png
new file mode 100644
index 0000000..8db4cc4
--- /dev/null
+++ b/ui/resources/default_200_percent/common/notification_advanced_settings.png
Binary files differ
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
new file mode 100644
index 0000000..d3880f8
--- /dev/null
+++ b/ui/resources/default_200_percent/common/notification_advanced_settings_hover.png
Binary files differ
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
new file mode 100644
index 0000000..ed55d30
--- /dev/null
+++ b/ui/resources/default_200_percent/common/notification_advanced_settings_pressed.png
Binary files differ
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"/>