Commit 172e7511 authored by groby@chromium.org's avatar groby@chromium.org

[rAC, OSX] Use bubble windows for error messages.

In the short term, this addresses the issue of messages that are too long
to fit in the dialog - the window will extend outside the dialog. In the
long run, this also prepares for further changes around the error bubble
that do require the BaseBubble machinery.

R=rsesek@chromium.org
BUG=308200

Review URL: https://codereview.chromium.org/49063012

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233458 0039d316-1c4b-4281-b951-d872f2087c98
parent 177133f6
......@@ -18,6 +18,7 @@ class AutofillDialogViewDelegate;
}
@class InfoBubbleView;
@class AutofillErrorBubbleController;
// UI controller for details for current payment instrument.
@interface AutofillDetailsContainer
......@@ -33,6 +34,8 @@ class AutofillDialogViewDelegate;
// An info bubble to display validation errors.
base::scoped_nsobject<InfoBubbleView> errorBubble_;
AutofillErrorBubbleController* errorBubbleController_;
autofill::AutofillDialogViewDelegate* delegate_; // Not owned.
}
......
......@@ -8,21 +8,8 @@
#include "base/mac/foundation_util.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_error_bubble_controller.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h"
#import "chrome/browser/ui/cocoa/info_bubble_view.h"
#include "skia/ext/skia_utils_mac.h"
namespace {
// Imported constant from Views version. TODO(groby): Share.
SkColor const kWarningColor = 0xffde4932; // SkColorSetRGB(0xde, 0x49, 0x32);
} // namespace
@interface AutofillDetailsContainer ()
// Compute infobubble origin based on anchor/view.
- (NSPoint)originFromAnchorView:(NSView*)view;
@end
@implementation AutofillDetailsContainer
......@@ -61,22 +48,6 @@ SkColor const kWarningColor = 0xffde4932; // SkColorSetRGB(0xde, 0x49, 0x32);
for (AutofillSectionContainer* container in details_.get())
[[scrollView_ documentView] addSubview:[container view]];
errorBubble_.reset([[InfoBubbleView alloc] initWithFrame:NSZeroRect]);
[errorBubble_ setBackgroundColor:
gfx::SkColorToCalibratedNSColor(kWarningColor)];
[errorBubble_ setArrowLocation:info_bubble::kTopCenter];
[errorBubble_ setAlignment:info_bubble::kAlignArrowToAnchor];
[errorBubble_ setHidden:YES];
base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]);
[label setEditable:NO];
[label setBordered:NO];
[label setDrawsBackground:NO];
[label setTextColor:[NSColor whiteColor]];
[errorBubble_ addSubview:label];
[[scrollView_ documentView] addSubview:errorBubble_];
[self performLayout];
}
......@@ -127,21 +98,52 @@ SkColor const kWarningColor = 0xffde4932; // SkColorSetRGB(0xde, 0x49, 0x32);
}
- (void)updateErrorBubble {
if (!delegate_->ShouldShowErrorBubble())
[errorBubble_ setHidden:YES];
if (!delegate_->ShouldShowErrorBubble()) {
[errorBubbleController_ close];
}
}
// TODO(groby): Unify with BaseBubbleController's originFromAnchor:view:.
- (NSPoint)originFromAnchorView:(NSView*)view {
- (NSPoint)anchorPointFromView:(NSView*)view {
// All math done in window coordinates, since views might be flipped.
NSRect viewRect = [view convertRect:[view bounds] toView:nil];
NSPoint anchorPoint =
NSMakePoint(NSMidX(viewRect), NSMinY(viewRect));
NSRect bubbleRect = [errorBubble_ convertRect:[errorBubble_ bounds]
toView:nil];
NSPoint bubbleOrigin = NSMakePoint(anchorPoint.x - NSWidth(bubbleRect) / 2.0,
anchorPoint.y - NSHeight(bubbleRect));
return [[errorBubble_ superview] convertPoint:bubbleOrigin fromView:nil];
return [[view window] convertBaseToScreen:anchorPoint];
}
- (void)errorBubbleWindowWillClose:(NSNotification*)notification {
DCHECK_EQ([notification object], [errorBubbleController_ window]);
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center removeObserver:self
name:NSWindowWillCloseNotification
object:[errorBubbleController_ window]];
errorBubbleController_ = nil;
}
- (void)showErrorBubbleForField:(NSControl<AutofillInputField>*)field {
DCHECK(!errorBubbleController_);
NSWindow* parentWindow = [field window];
DCHECK(parentWindow);
errorBubbleController_ =
[[AutofillErrorBubbleController alloc]
initWithParentWindow:parentWindow
message:[field validityMessage]];
// Handle bubble self-deleting.
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(errorBubbleWindowWillClose:)
name:NSWindowWillCloseNotification
object:[errorBubbleController_ window]];
// Compute anchor point (in window coords - views might be flipped).
NSRect viewRect = [field convertRect:[field bounds] toView:nil];
NSPoint anchorPoint = NSMakePoint(NSMidX(viewRect), NSMinY(viewRect));
[errorBubbleController_ setAnchorPoint:
[parentWindow convertBaseToScreen:anchorPoint]];
[errorBubbleController_ showWindow:self];
}
- (void)hideErrorBubble {
......@@ -156,27 +158,15 @@ SkColor const kWarningColor = 0xffde4932; // SkColorSetRGB(0xde, 0x49, 0x32);
base::mac::ObjCCast<NSView>([[field window] firstResponder]);
if (![firstResponderView isDescendantOf:field])
return;
if (!delegate_->ShouldShowErrorBubble()) {
DCHECK([errorBubble_ isHidden]);
DCHECK(!errorBubbleController_);
return;
}
if ([field invalid]) {
const CGFloat labelInset = 3.0;
NSTextField* label = [[errorBubble_ subviews] objectAtIndex:0];
[label setStringValue:[field validityMessage]];
[label sizeToFit];
NSSize bubbleSize = [label frame].size;
bubbleSize.width += 2 * labelInset;
bubbleSize.height += 2 * labelInset + info_bubble::kBubbleArrowHeight;
[errorBubble_ setFrameSize:bubbleSize];
[label setFrameOrigin:NSMakePoint(labelInset, labelInset)];
[errorBubble_ setFrameOrigin:[self originFromAnchorView:field]];
[errorBubble_ setHidden:NO];
[self showErrorBubbleForField:field];
} else {
[errorBubble_ setHidden:YES];
[errorBubbleController_ close];
}
}
......
// 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 CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_ERROR_BUBBLE_CONTROLLER_H_
#define CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_ERROR_BUBBLE_CONTROLLER_H_
#import <Cocoa/Cocoa.h>
#include "base/mac/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/base_bubble_controller.h"
// Bubble controller for field validation error bubbles.
@interface AutofillErrorBubbleController : BaseBubbleController {
base::scoped_nsobject<NSTextField> label_;
}
// Creates an error bubble with the given |message|. You need to call
// -showWindow: to make the bubble visible. It will autorelease itself when the
// user dismisses the bubble.
- (id)initWithParentWindow:(NSWindow*)parentWindow
message:(NSString*)message;
@end
#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_AUTOFILL_ERROR_BUBBLE_CONTROLLER_H_
// 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 "chrome/browser/ui/cocoa/autofill/autofill_error_bubble_controller.h"
#import "chrome/browser/ui/cocoa/info_bubble_view.h"
#import "chrome/browser/ui/cocoa/info_bubble_window.h"
#include "skia/ext/skia_utils_mac.h"
namespace {
// Border inset for error label.
const CGFloat kLabelInset = 3.0;
// Imported constant from Views version. TODO(groby): Share.
SkColor const kWarningColor = 0xffde4932; // SkColorSetRGB(0xde, 0x49, 0x32);
} // namespace
@implementation AutofillErrorBubbleController
- (id)initWithParentWindow:(NSWindow*)parentWindow
message:(NSString*)message {
base::scoped_nsobject<InfoBubbleWindow> window(
[[InfoBubbleWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 100)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]);
[window setAllowedAnimations:info_bubble::kAnimateNone];
if ((self = [super initWithWindow:window
parentWindow:parentWindow
anchoredAt:NSZeroPoint])) {
[self setShouldOpenAsKeyWindow:NO];
[[self bubble] setBackgroundColor:
gfx::SkColorToCalibratedNSColor(kWarningColor)];
[[self bubble] setArrowLocation:info_bubble::kTopCenter];
[[self bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
label_.reset([[NSTextField alloc] init]);
[label_ setEditable:NO];
[label_ setBordered:NO];
[label_ setDrawsBackground:NO];
[label_ setTextColor:[NSColor whiteColor]];
[label_ setStringValue:message];
[label_ sizeToFit];
[label_ setFrameOrigin:NSMakePoint(kLabelInset, kLabelInset)];
[[self bubble] addSubview:label_];
NSRect windowFrame = [[self window] frame];
windowFrame.size = NSMakeSize(
NSMaxX([label_ frame]),
NSHeight([label_ frame]) + info_bubble::kBubbleArrowHeight);
windowFrame = NSInsetRect(windowFrame, -kLabelInset, -kLabelInset);
[[self window] setFrame:windowFrame display:NO];
}
return self;
}
@end
// 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 "chrome/browser/ui/cocoa/autofill/autofill_error_bubble_controller.h"
#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
#include "chrome/browser/ui/cocoa/run_loop_testing.h"
class AutofillErrorBubbleControllerTest : public CocoaTest {
};
TEST_F(AutofillErrorBubbleControllerTest, ShowAndClose) {
AutofillErrorBubbleController* controller =
[[AutofillErrorBubbleController alloc] initWithParentWindow:test_window()
message:@"test msg"];
EXPECT_FALSE([[controller window] isVisible]);
[controller showWindow:nil];
EXPECT_TRUE([[controller window] isVisible]);
// Close will self-delete, but all pending messages must be processed so the
// deallocation happens.
[controller close];
chrome::testing::NSRunLoopRunAllPending();
}
......@@ -37,7 +37,9 @@ class TabStripModelObserverBridge;
id resignationObserver_;
// The controlled window should be the key window when it's opened. True by
// default.
bool shouldOpenAsKeyWindow_;
BOOL shouldOpenAsKeyWindow_;
// The bubble window should close if it (or its parent) resigns key status.
BOOL shouldCloseOnResignKey_;
}
@property(nonatomic, readonly) NSWindow* parentWindow;
......@@ -45,7 +47,9 @@ class TabStripModelObserverBridge;
// arrow tip points.
@property(nonatomic, assign) NSPoint anchorPoint;
@property(nonatomic, readonly) InfoBubbleView* bubble;
@property(nonatomic, assign) bool shouldOpenAsKeyWindow;
@property(nonatomic, assign) BOOL shouldOpenAsKeyWindow;
// Controls if the bubble auto-closes if the user clicks outside the bubble.
@property(nonatomic, assign) BOOL shouldCloseOnResignKey;
// Creates a bubble. |nibPath| is just the basename, e.g. @"FirstRunBubble".
// |anchoredAt| is in screen space. You need to call -showWindow: to make the
......
......@@ -29,6 +29,7 @@
@synthesize anchorPoint = anchor_;
@synthesize bubble = bubble_;
@synthesize shouldOpenAsKeyWindow = shouldOpenAsKeyWindow_;
@synthesize shouldCloseOnResignKey = shouldCloseOnResignKey_;
- (id)initWithWindowNibPath:(NSString*)nibPath
parentWindow:(NSWindow*)parentWindow
......@@ -72,6 +73,7 @@
parentWindow_ = parentWindow;
anchor_ = anchoredAt;
shouldOpenAsKeyWindow_ = YES;
shouldCloseOnResignKey_ = YES;
DCHECK(![[self window] delegate]);
[theWindow setDelegate:self];
......@@ -184,7 +186,7 @@
- (void)windowDidResignKey:(NSNotification*)notification {
NSWindow* window = [self window];
DCHECK_EQ([notification object], window);
if ([window isVisible]) {
if ([window isVisible] && [self shouldCloseOnResignKey]) {
// If the window isn't visible, it is already closed, and this notification
// has been sent as part of the closing operation, so no need to close.
[self close];
......
......@@ -156,7 +156,8 @@ TEST_F(BaseBubbleControllerTest, ResignKeyCloses) {
EXPECT_TRUE([other_window isVisible]);
}
// Test that clicking outside the window causes the bubble to close.
// Test that clicking outside the window causes the bubble to close if
// shouldCloseOnResignKey is YES.
TEST_F(BaseBubbleControllerTest, LionClickOutsideCloses) {
// The event tap is only installed on 10.7+.
if (!base::mac::IsOSLionOrLater())
......@@ -166,16 +167,26 @@ TEST_F(BaseBubbleControllerTest, LionClickOutsideCloses) {
base::scoped_nsobject<BaseBubbleController> keep_alive([controller_ retain]);
NSWindow* window = [controller_ window];
EXPECT_TRUE([controller_ shouldCloseOnResignKey]); // Verify default value.
EXPECT_FALSE([window isVisible]);
[controller_ showWindow:nil];
EXPECT_TRUE([window isVisible]);
[controller_ setShouldCloseOnResignKey:NO];
NSEvent* event = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
NSMakePoint(10, 10), test_window());
[NSApp sendEvent:event];
chrome::testing::NSRunLoopRunAllPending();
EXPECT_TRUE([window isVisible]);
[controller_ setShouldCloseOnResignKey:YES];
event = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
NSMakePoint(10, 10), test_window());
[NSApp sendEvent:event];
chrome::testing::NSRunLoopRunAllPending();
EXPECT_FALSE([window isVisible]);
}
......@@ -500,6 +500,8 @@
'browser/ui/cocoa/autofill/autofill_dialog_cocoa.h',
'browser/ui/cocoa/autofill/autofill_dialog_cocoa.mm',
'browser/ui/cocoa/autofill/autofill_dialog_constants.h',
'browser/ui/cocoa/autofill/autofill_error_bubble_controller.h',
'browser/ui/cocoa/autofill/autofill_error_bubble_controller.mm',
'browser/ui/cocoa/autofill/autofill_layout.h',
'browser/ui/cocoa/autofill/autofill_notification_container.h',
'browser/ui/cocoa/autofill/autofill_notification_container.mm',
......
......@@ -1466,6 +1466,7 @@
'browser/ui/cocoa/applescript/bookmark_item_applescript_unittest.mm',
'browser/ui/cocoa/autofill/autofill_account_chooser_unittest.mm',
'browser/ui/cocoa/autofill/autofill_details_container_unittest.mm',
'browser/ui/cocoa/autofill/autofill_error_bubble_controller_unittest.mm',
'browser/ui/cocoa/autofill/autofill_main_container_unittest.mm',
'browser/ui/cocoa/autofill/autofill_notification_container_unittest.mm',
'browser/ui/cocoa/autofill/autofill_notification_controller_unittest.mm',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment