blob: 7006ab3749a27cf772a1ec779b8042b317d225b5 [file] [log] [blame]
// 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 <algorithm>
#include "base/strings/sys_string_conversions.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"
#include "ui/resources/grit/ui_resources.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 MCSettingsButton : NSButton
@end
@implementation MCSettingsButton
// drawRect: needs to fill the button with a background, otherwise we don't get
// subpixel antialiasing.
- (void)drawRect:(NSRect)dirtyRect {
NSColor* color = gfx::SkColorToCalibratedNSColor(
message_center::kMessageCenterBackgroundColor);
[color set];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
@end
@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
- (BOOL)isOpaque {
return YES;
}
- (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<CGFloat>(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 layoutEntryView];
}
return self;
}
- (void)setNotifierIcon:(NSImage*)notifierIcon {
notifierIcon_.reset([notifierIcon retain]);
[self layoutEntryView];
}
- (NSButton*)checkbox {
return checkbox_;
}
- (void)layoutEntryView {
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([[MCSettingsButton 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
///////////////////////////////////////////////////////////////////////////////