diff options
author | dhollowa@chromium.org <dhollowa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-09 17:19:57 +0000 |
---|---|---|
committer | dhollowa@chromium.org <dhollowa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-09 17:19:57 +0000 |
commit | cff83feff679cf3f593f1c591ac91fc3b47277bd (patch) | |
tree | 052429411724e60429b6b4c5905fd4e469e283d4 /chrome/browser/autofill | |
parent | ba4874ea3edd4142f1f56d533dad25274e484edb (diff) | |
download | chromium_src-cff83feff679cf3f593f1c591ac91fc3b47277bd.zip chromium_src-cff83feff679cf3f593f1c591ac91fc3b47277bd.tar.gz chromium_src-cff83feff679cf3f593f1c591ac91fc3b47277bd.tar.bz2 |
AutoFill Profiles dialog implemented according to new mocks on Mac
New mocks are attached to bug 44622. These changes replace the in-place editing of address and credit card records with a table of records and separate sheets for manipulating the record data. Changes to the layout of fields on the sheets has been done also.
AutoFillDialog.xib changes: Replaced disclosure based list of address and credit cards with an NSTableView of the same data. Added buttons for "Add", "Edit", and "Remove".
Replaced AutoFillAddressViewController.xib with sheet-based AutoFillAddressSheetController.xib.
Replaced AutoFillCreditCardViewController.xib with sheet-based AutoFillCreditCardSheetController.xib.
BUG=44621
TEST=AutoFillAddressModelTest,AutoFillAddressSheetControllerTest,AutoFillCreditCardModelTest,AutoFillCreditCardSheetControllerTest,AutoFillDialogControllerTest
Review URL: http://codereview.chromium.org/2673006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49274 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autofill')
18 files changed, 929 insertions, 625 deletions
diff --git a/chrome/browser/autofill/autofill_address_model_mac.h b/chrome/browser/autofill/autofill_address_model_mac.h index 9ea052f..611fc7f 100644 --- a/chrome/browser/autofill/autofill_address_model_mac.h +++ b/chrome/browser/autofill/autofill_address_model_mac.h @@ -12,7 +12,6 @@ class AutoFillProfile; // A "model" class used with bindings mechanism and the // |AutoFillAddressViewController| to achieve the form-like view // of autofill data in the Chrome options UI. -// Note that |summary| is a derived property. // Model objects are initialized from a given profile using the designated // initializer |initWithProfile:|. // Users of this class must be prepared to handle nil string return values. @@ -22,9 +21,7 @@ class AutoFillProfile; @private // These are not scoped_nsobjects because we use them via KVO/bindings. NSString* label_; - NSString* firstName_; - NSString* middleName_; - NSString* lastName_; + NSString* fullName_; NSString* email_; NSString* companyName_; NSString* addressLine1_; @@ -37,14 +34,8 @@ class AutoFillProfile; NSString* faxWholeNumber_; } -// |summary| is a derived property based on |firstName|, |lastName| and -// |addressLine1|. KVO observers receive change notifications for |summary| -// when any of these properties change. -@property (readonly) NSString* summary; @property (nonatomic, copy) NSString* label; -@property (nonatomic, copy) NSString* firstName; -@property (nonatomic, copy) NSString* middleName; -@property (nonatomic, copy) NSString* lastName; +@property (nonatomic, copy) NSString* fullName; @property (nonatomic, copy) NSString* email; @property (nonatomic, copy) NSString* companyName; @property (nonatomic, copy) NSString* addressLine1; diff --git a/chrome/browser/autofill/autofill_address_model_mac.mm b/chrome/browser/autofill/autofill_address_model_mac.mm index a3b2089..773f8fb 100644 --- a/chrome/browser/autofill/autofill_address_model_mac.mm +++ b/chrome/browser/autofill/autofill_address_model_mac.mm @@ -10,11 +10,8 @@ @implementation AutoFillAddressModel -@dynamic summary; @synthesize label = label_; -@synthesize firstName = firstName_; -@synthesize middleName = middleName_; -@synthesize lastName = lastName_; +@synthesize fullName = fullName_; @synthesize email = email_; @synthesize companyName = companyName_; @synthesize addressLine1 = addressLine1_; @@ -26,27 +23,11 @@ @synthesize phoneWholeNumber = phoneWholeNumber_; @synthesize faxWholeNumber = faxWholeNumber_; -// Sets up the KVO dependency between "summary" and dependent fields. -+ (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString*)key { - NSSet* keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; - - if ([key isEqualToString:@"summary"]) { - NSSet* affectingKeys = - [NSSet setWithObjects:@"firstName", @"lastName", @"addressLine1", nil]; - keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKeys]; - } - return keyPaths; -} - - (id)initWithProfile:(const AutoFillProfile&)profile { if ((self = [super init])) { [self setLabel:SysUTF16ToNSString(profile.Label())]; - [self setFirstName:SysUTF16ToNSString( - profile.GetFieldText(AutoFillType(NAME_FIRST)))]; - [self setMiddleName:SysUTF16ToNSString( - profile.GetFieldText(AutoFillType(NAME_MIDDLE)))]; - [self setLastName:SysUTF16ToNSString( - profile.GetFieldText(AutoFillType(NAME_LAST)))]; + [self setFullName:SysUTF16ToNSString( + profile.GetFieldText(AutoFillType(NAME_FULL)))]; [self setEmail:SysUTF16ToNSString( profile.GetFieldText(AutoFillType(EMAIL_ADDRESS)))]; [self setCompanyName:SysUTF16ToNSString( @@ -73,9 +54,7 @@ - (void)dealloc { [label_ release]; - [firstName_ release]; - [middleName_ release]; - [lastName_ release]; + [fullName_ release]; [email_ release]; [companyName_ release]; [addressLine1_ release]; @@ -89,22 +68,11 @@ [super dealloc]; } -- (NSString*)summary { - // Create a temporary |profile| to generate summary string. - AutoFillProfile profile(string16(), 0); - [self copyModelToProfile:&profile]; - return SysUTF16ToNSString(profile.PreviewSummary()); -} - - (void)copyModelToProfile:(AutoFillProfile*)profile { DCHECK(profile); profile->set_label(base::SysNSStringToUTF16([self label])); - profile->SetInfo(AutoFillType(NAME_FIRST), - base::SysNSStringToUTF16([self firstName])); - profile->SetInfo(AutoFillType(NAME_MIDDLE), - base::SysNSStringToUTF16([self middleName])); - profile->SetInfo(AutoFillType(NAME_LAST), - base::SysNSStringToUTF16([self lastName])); + profile->SetInfo(AutoFillType(NAME_FULL), + base::SysNSStringToUTF16([self fullName])); profile->SetInfo(AutoFillType(EMAIL_ADDRESS), base::SysNSStringToUTF16([self email])); profile->SetInfo(AutoFillType(COMPANY_NAME), diff --git a/chrome/browser/autofill/autofill_address_model_mac_unittest.mm b/chrome/browser/autofill/autofill_address_model_mac_unittest.mm index 9265e04..fcdd1cc 100644 --- a/chrome/browser/autofill/autofill_address_model_mac_unittest.mm +++ b/chrome/browser/autofill/autofill_address_model_mac_unittest.mm @@ -45,9 +45,7 @@ TEST(AutoFillAddressModelTest, InitializationFromProfile) { EXPECT_TRUE(model.get()); EXPECT_TRUE([[model label] isEqualToString:@"Billing"]); - EXPECT_TRUE([[model firstName] isEqualToString:@"Marion"]); - EXPECT_TRUE([[model middleName] isEqualToString:@"Mitchell"]); - EXPECT_TRUE([[model lastName] isEqualToString:@"Morrison"]); + EXPECT_TRUE([[model fullName] isEqualToString:@"Marion Mitchell Morrison"]); EXPECT_TRUE([[model email] isEqualToString:@"johnwayne@me.xyz"]); EXPECT_TRUE([[model companyName] isEqualToString:@"Fox"]); EXPECT_TRUE([[model addressLine1] isEqualToString:@"123 Zoo St."]); @@ -82,9 +80,7 @@ TEST(AutoFillAddressModelTest, CopyModelToProfile) { EXPECT_TRUE(model.get()); [model setLabel:@"BillingX"]; - [model setFirstName:@"MarionX"]; - [model setMiddleName:@"MitchellX"]; - [model setLastName:@"MorrisonX"]; + [model setFullName:@"MarionX MitchellX MorrisonX"]; [model setEmail:@"trigger@me.xyz"]; [model setCompanyName:@"FoxX"]; [model setAddressLine1:@"123 Xoo St."]; @@ -105,6 +101,8 @@ TEST(AutoFillAddressModelTest, CopyModelToProfile) { profile.GetFieldText(AutoFillType(NAME_MIDDLE))); EXPECT_EQ(ASCIIToUTF16("MorrisonX"), profile.GetFieldText(AutoFillType(NAME_LAST))); + EXPECT_EQ(ASCIIToUTF16("MarionX MitchellX MorrisonX"), + profile.GetFieldText(AutoFillType(NAME_FULL))); EXPECT_EQ(ASCIIToUTF16("trigger@me.xyz"), profile.GetFieldText(AutoFillType(EMAIL_ADDRESS))); EXPECT_EQ(ASCIIToUTF16("FoxX"), diff --git a/chrome/browser/autofill/autofill_address_view_controller_mac.h b/chrome/browser/autofill/autofill_address_sheet_controller_mac.h index 2367f8d..963d4ff 100644 --- a/chrome/browser/autofill/autofill_address_view_controller_mac.h +++ b/chrome/browser/autofill/autofill_address_sheet_controller_mac.h @@ -2,52 +2,57 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_AUTOFILL_AUTOFILL_ADDRESS_VIEW_CONTROLLER_MAC_ -#define CHROME_BROWSER_AUTOFILL_AUTOFILL_ADDRESS_VIEW_CONTROLLER_MAC_ +#ifndef CHROME_BROWSER_AUTOFILL_AUTOFILL_ADDRESS_SHEET_CONTROLLER_MAC_ +#define CHROME_BROWSER_AUTOFILL_AUTOFILL_ADDRESS_SHEET_CONTROLLER_MAC_ #import <Cocoa/Cocoa.h> -#import "chrome/browser/cocoa/disclosure_view_controller.h" @class AutoFillAddressModel; @class AutoFillDialogController; class AutoFillProfile; +// The sheet can be invoked in "Add" or "Edit" mode. This dictates the caption +// seen at the top of the sheet. +enum { + kAutoFillAddressAddMode = 0, + kAutoFillAddressEditMode = 1 +}; +typedef NSInteger AutoFillAddressMode; + // A class that coordinates the |addressModel| and the associated view -// held in AutoFillAddressFormView.xib. +// held in AutoFillAddressSheet.xib. // |initWithProfile:| is the designated initializer. It takes |profile| // and transcribes it to |addressModel| to which the view is bound. -@interface AutoFillAddressViewController : DisclosureViewController { +@interface AutoFillAddressSheetController : NSWindowController { @private + // The caption at top of dialog. Text changes according to usage. Either + // "New address" or "Edit address" depending on |mode_|. + IBOutlet NSTextField* caption_; + // The primary model for this controller. The model is instantiated // from within |initWithProfile:|. We do not hold it as a scoped_nsobject // because it is exposed as a KVO compliant property. // Strong reference. AutoFillAddressModel* addressModel_; - // A reference to our parent controller. Used for notifying parent if/when - // deletion occurs. Also used to notify parent when the label of the address - // changes. May be not be nil. - // Weak reference, owns us. - AutoFillDialogController* parentController_; + // Either "Add" or "Edit" mode of sheet. + AutoFillAddressMode mode_; } @property (nonatomic, retain) AutoFillAddressModel* addressModel; +// IBActions for save and cancel buttons. Both invoke |endSheet:|. +- (IBAction)save:(id)sender; +- (IBAction)cancel:(id)sender; + // Designated initializer. Takes a copy of the data in |profile|, // it is not held as a reference. - (id)initWithProfile:(const AutoFillProfile&)profile - disclosure:(NSCellStateValue)disclosureState - controller:(AutoFillDialogController*) parentController; - -// Action to remove this address from the dialog. Forwards the request to -// |parentController_| which does all the actual work. We have the action -// here so that the delete button in the AutoFillAddressViewFormView.xib has -// something to call. -- (IBAction)deleteAddress:(id)sender; + mode:(AutoFillAddressMode)mode; // Copy data from internal model to |profile|. - (void)copyModelToProfile:(AutoFillProfile*)profile; @end -#endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_ADDRESS_VIEW_CONTROLLER_MAC_ +#endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_ADDRESS_SHEET_CONTROLLER_MAC_ diff --git a/chrome/browser/autofill/autofill_address_sheet_controller_mac.mm b/chrome/browser/autofill/autofill_address_sheet_controller_mac.mm new file mode 100644 index 0000000..1a6305d --- /dev/null +++ b/chrome/browser/autofill/autofill_address_sheet_controller_mac.mm @@ -0,0 +1,66 @@ +// Copyright (c) 2010 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/autofill/autofill_address_sheet_controller_mac.h" + +#include "app/l10n_util.h" +#include "base/mac_util.h" +#include "base/sys_string_conversions.h" +#import "chrome/browser/autofill/autofill_address_model_mac.h" +#import "chrome/browser/autofill/autofill_dialog_controller_mac.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "grit/generated_resources.h" + +@implementation AutoFillAddressSheetController + +@synthesize addressModel = addressModel_; + +- (id)initWithProfile:(const AutoFillProfile&)profile + mode:(AutoFillAddressMode)mode { + NSString* nibPath = [mac_util::MainAppBundle() + pathForResource:@"AutoFillAddressSheet" + ofType:@"nib"]; + self = [super initWithWindowNibPath:nibPath owner:self]; + if (self) { + // Create the model. + [self setAddressModel:[[[AutoFillAddressModel alloc] + initWithProfile:profile] autorelease]]; + + mode_ = mode; + } + return self; +} + +- (void)dealloc { + [addressModel_ release]; + [super dealloc]; +} + +- (void)awakeFromNib { + NSString* caption; + if (mode_ == kAutoFillAddressAddMode) + caption = l10n_util::GetNSString(IDS_AUTOFILL_ADD_ADDRESS_CAPTION); + else if (mode_ == kAutoFillAddressEditMode) + caption = l10n_util::GetNSString(IDS_AUTOFILL_EDIT_ADDRESS_CAPTION); + else + NOTREACHED(); + [caption_ setStringValue:caption]; +} + +- (IBAction)save:(id)sender { + // Call |makeFirstResponder:| to commit pending text field edits. + [[self window] makeFirstResponder:[self window]]; + + [NSApp endSheet:[self window] returnCode:1]; +} + +- (IBAction)cancel:(id)sender { + [NSApp endSheet:[self window] returnCode:0]; +} + +- (void)copyModelToProfile:(AutoFillProfile*)profile { + [addressModel_ copyModelToProfile:profile]; +} + +@end diff --git a/chrome/browser/autofill/autofill_address_view_controller_mac_unittest.mm b/chrome/browser/autofill/autofill_address_sheet_controller_mac_unittest.mm index b5c857c..f46425d 100644 --- a/chrome/browser/autofill/autofill_address_view_controller_mac_unittest.mm +++ b/chrome/browser/autofill/autofill_address_sheet_controller_mac_unittest.mm @@ -3,7 +3,7 @@ // found in the LICENSE file. #include "base/scoped_nsobject.h" -#import "chrome/browser/autofill/autofill_address_view_controller_mac.h" +#import "chrome/browser/autofill/autofill_address_sheet_controller_mac.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/cocoa/browser_test_helper.h" #import "chrome/browser/cocoa/cocoa_test_helper.h" @@ -11,17 +11,16 @@ namespace { -typedef CocoaTest AutoFillAddressViewControllerTest; +typedef CocoaTest AutoFillAddressSheetControllerTest; -TEST(AutoFillAddressViewControllerTest, Basic) { +TEST(AutoFillAddressSheetControllerTest, Basic) { // A basic test that creates a new instance and releases. // Aids valgrind leak detection. AutoFillProfile profile(ASCIIToUTF16("Home"), 0); - scoped_nsobject<AutoFillAddressViewController> controller( - [[AutoFillAddressViewController alloc] + scoped_nsobject<AutoFillAddressSheetController> controller( + [[AutoFillAddressSheetController alloc] initWithProfile:profile - disclosure:NSOffState - controller:nil]); + mode:kAutoFillAddressAddMode]); EXPECT_TRUE(controller.get()); } diff --git a/chrome/browser/autofill/autofill_address_view_controller_mac.mm b/chrome/browser/autofill/autofill_address_view_controller_mac.mm deleted file mode 100644 index e3163f8..0000000 --- a/chrome/browser/autofill/autofill_address_view_controller_mac.mm +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2010 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/autofill/autofill_address_view_controller_mac.h" -#include "base/mac_util.h" -#include "base/sys_string_conversions.h" -#import "chrome/browser/autofill/autofill_address_model_mac.h" -#import "chrome/browser/autofill/autofill_dialog_controller_mac.h" -#include "chrome/browser/autofill/autofill_profile.h" -#import "third_party/GTM/Foundation/GTMNSObject+KeyValueObserving.h" - -@interface AutoFillAddressViewController (PrivateMethods) -- (void)labelChanged:(GTMKeyValueChangeNotification*)notification; -@end - -@implementation AutoFillAddressViewController - -@synthesize addressModel = addressModel_; - -- (id)initWithProfile:(const AutoFillProfile&)profile - disclosure:(NSCellStateValue)disclosureState - controller:(AutoFillDialogController*) parentController { - self = [super initWithNibName:@"AutoFillAddressFormView" - bundle:mac_util::MainAppBundle() - disclosure:disclosureState]; - if (self) { - // Pull in the view for initialization. - [self view]; - - // Create the model. - [self setAddressModel:[[[AutoFillAddressModel alloc] - initWithProfile:profile] autorelease]]; - - // We keep track of our parent controller for model-update purposes. - parentController_ = parentController; - - // Register |self| as observer so we can notify parent controller. See - // |labelChanged:| for details. - [addressModel_ gtm_addObserver:self - forKeyPath:@"label" - selector:@selector(labelChanged:) - userInfo:nil - options:0]; - } - return self; -} - -- (void)dealloc { - [addressModel_ gtm_removeObserver:self - forKeyPath:@"label" - selector:@selector(labelChanged:)]; - [addressModel_ release]; - [super dealloc]; -} - -// Override KVO method to notify parent controller when the address "label" -// changes. Credit card UI updates accordingly. -- (void)labelChanged:(GTMKeyValueChangeNotification*)notification { - [parentController_ notifyAddressChange:self]; -} - -- (IBAction)deleteAddress:(id)sender { - [parentController_ deleteAddress:self]; -} - -- (void)copyModelToProfile:(AutoFillProfile*)profile { - [addressModel_ copyModelToProfile:profile]; -} - -@end - - diff --git a/chrome/browser/autofill/autofill_credit_card_model_mac.h b/chrome/browser/autofill/autofill_credit_card_model_mac.h index 97dfb67..202202e 100644 --- a/chrome/browser/autofill/autofill_credit_card_model_mac.h +++ b/chrome/browser/autofill/autofill_credit_card_model_mac.h @@ -12,7 +12,6 @@ class CreditCard; // A "model" class used with bindings mechanism and the // |AutoFillCreditCardViewController| to achieve the form-like view // of autofill data in the Chrome options UI. -// Note that |summary| is a derived property. // Model objects are initialized from the given |creditCard| using the // designated initializer |initWithCreditCard:|. // Users of this class must be prepared to handle nil string return values. @@ -31,10 +30,6 @@ class CreditCard; NSString* shippingAddress_; } -// |summary| is a derived property based on |creditCardNumber|, -// |expirationMonth| and |expirationYear|. KVO observers receive change -// notifications for |summary| when any of these properties change. -@property (readonly) NSString* summary; @property (nonatomic, copy) NSString* label; @property (nonatomic, copy) NSString* nameOnCard; @property (nonatomic, copy) NSString* creditCardNumber; @@ -42,7 +37,6 @@ class CreditCard; @property (nonatomic, copy) NSString* expirationYear; @property (nonatomic, copy) NSString* cvcCode; @property (nonatomic, copy) NSString* billingAddress; -@property (nonatomic, copy) NSString* shippingAddress; // Designated initializer. Initializes the property strings to values retrieved // from the |creditCard| object. diff --git a/chrome/browser/autofill/autofill_credit_card_model_mac.mm b/chrome/browser/autofill/autofill_credit_card_model_mac.mm index c75e14f..f75bd05 100644 --- a/chrome/browser/autofill/autofill_credit_card_model_mac.mm +++ b/chrome/browser/autofill/autofill_credit_card_model_mac.mm @@ -11,7 +11,6 @@ @implementation AutoFillCreditCardModel -@dynamic summary; @synthesize label = label_; @synthesize nameOnCard = nameOnCard_; @synthesize creditCardNumber = creditCardNumber_; @@ -19,19 +18,6 @@ @synthesize expirationYear = expirationYear_; @synthesize cvcCode = cvcCode_; @synthesize billingAddress = billingAddress_; -@synthesize shippingAddress = shippingAddress_; - -// Sets up the KVO dependency between "summary" and dependent fields. -+ (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString*)key { - NSSet* keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; - - if ([key isEqualToString:@"summary"]) { - NSSet* affectingKeys = [NSSet setWithObjects:@"creditCardNumber", - @"expirationMonth", @"expirationYear", nil]; - keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKeys]; - } - return keyPaths; -} - (id)initWithCreditCard:(const CreditCard&)creditCard { if ((self = [super init])) { @@ -48,8 +34,6 @@ creditCard.GetFieldText(AutoFillType(CREDIT_CARD_VERIFICATION_CODE)))]; [self setBillingAddress:SysUTF16ToNSString( creditCard.billing_address())]; - [self setShippingAddress:SysUTF16ToNSString( - creditCard.shipping_address())]; } return self; } @@ -62,17 +46,9 @@ [expirationYear_ release]; [cvcCode_ release]; [billingAddress_ release]; - [shippingAddress_ release]; [super dealloc]; } -- (NSString*)summary { - // Create a temporary |creditCard| to generate summary string. - CreditCard creditCard(string16(), 0); - [self copyModelToCreditCard:&creditCard]; - return SysUTF16ToNSString(creditCard.PreviewSummary()); -} - - (void)copyModelToCreditCard:(CreditCard*)creditCard { DCHECK(creditCard); creditCard->set_label(base::SysNSStringToUTF16([self label])); @@ -88,8 +64,6 @@ base::SysNSStringToUTF16([self cvcCode])); creditCard->set_billing_address( base::SysNSStringToUTF16([self billingAddress])); - creditCard->set_shipping_address( - base::SysNSStringToUTF16([self shippingAddress])); } @end diff --git a/chrome/browser/autofill/autofill_credit_card_model_mac_unittest.mm b/chrome/browser/autofill/autofill_credit_card_model_mac_unittest.mm index 6fdb1142..6d77e20 100644 --- a/chrome/browser/autofill/autofill_credit_card_model_mac_unittest.mm +++ b/chrome/browser/autofill/autofill_credit_card_model_mac_unittest.mm @@ -47,7 +47,6 @@ TEST(AutoFillCreditCardModelTest, InitializationFromCreditCard) { EXPECT_TRUE([[model expirationYear] isEqualToString:@"2010"]); EXPECT_TRUE([[model cvcCode] isEqualToString:@"123"]); EXPECT_TRUE([[model billingAddress] isEqualToString:@"Chicago"]); - EXPECT_TRUE([[model shippingAddress] isEqualToString:@"Indianapolis"]); } TEST(AutoFillCreditCardModelTest, CopyModelToCreditCard) { @@ -74,7 +73,6 @@ TEST(AutoFillCreditCardModelTest, CopyModelToCreditCard) { [model setExpirationYear:@"2011"]; [model setCvcCode:@"223"]; [model setBillingAddress:@"New York"]; - [model setShippingAddress:@"Boston"]; [model copyModelToCreditCard:&credit_card]; @@ -92,7 +90,6 @@ TEST(AutoFillCreditCardModelTest, CopyModelToCreditCard) { credit_card.GetFieldText( AutoFillType(CREDIT_CARD_VERIFICATION_CODE))); EXPECT_EQ(ASCIIToUTF16("New York"), credit_card.billing_address()); - EXPECT_EQ(ASCIIToUTF16("Boston"), credit_card.shipping_address()); } } // namespace diff --git a/chrome/browser/autofill/autofill_credit_card_view_controller_mac.h b/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.h index 054252f..6fa281d 100644 --- a/chrome/browser/autofill/autofill_credit_card_view_controller_mac.h +++ b/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.h @@ -2,24 +2,36 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_AUTOFILL_AUTOFILL_CREDIT_CARD_VIEW_CONTROLLER_MAC_ -#define CHROME_BROWSER_AUTOFILL_AUTOFILL_CREDIT_CARD_VIEW_CONTROLLER_MAC_ +#ifndef CHROME_BROWSER_AUTOFILL_AUTOFILL_CREDIT_CARD_SHEET_CONTROLLER_MAC_ +#define CHROME_BROWSER_AUTOFILL_AUTOFILL_CREDIT_CARD_SHEET_CONTROLLER_MAC_ #import <Cocoa/Cocoa.h> -#import "chrome/browser/cocoa/disclosure_view_controller.h" @class AutoFillCreditCardModel; @class AutoFillDialogController; class CreditCard; +// The sheet can be invoked in "Add" or "Edit" mode. This dictates the caption +// seen at the top of the sheet. +enum { + kAutoFillCreditCardAddMode = 0, + kAutoFillCreditCardEditMode = 1 +}; +typedef NSInteger AutoFillCreditCardMode; + // A class that coordinates the |creditCardModel| and the associated view -// held in AutoFillCreditCardFormView.xib. +// held in AutoFillCreditCardSheet.xib. // |initWithCreditCard:| is the designated initializer. It takes |creditCard| // and transcribes it to |creditCardModel| to which the view is bound. -@interface AutoFillCreditCardViewController : DisclosureViewController { +@interface AutoFillCreditCardSheetController : NSWindowController { @private IBOutlet NSPopUpButton* billingAddressPopup_; - IBOutlet NSPopUpButton* shippingAddressPopup_; + IBOutlet NSPopUpButton* expirationMonthPopup_; + IBOutlet NSPopUpButton* expirationYearPopup_; + + // The caption at top of dialog. Text changes according to usage. Either + // "New credit card" or "Edit credit card" depending on context. + IBOutlet NSTextField* caption_; // The primary model for this controller. The model is instantiated // from within |initWithCreditCard:|. We do not hold it as a scoped_nsobject @@ -32,41 +44,39 @@ class CreditCard; // of addresses change in the |parentController_|. NSArray* billingAddressContents_; - // Array of strings that populate the |shippingAddressPopup_| control. We - // do not hold this as scoped_nsobject because it is exposed as a KVO - // compliant property. The values of this array may change as the list - // of addresses change in the |parentController_|. - NSArray* shippingAddressContents_; + // Contents of the expiration month and year popups. Strongly owned. We do + // not hold them as scoped_nsobjects because they are exposed as KVO compliant + // properties. + NSArray* expirationMonthContents_; + NSArray* expirationYearContents_; - // A reference to our parent controller. Used for notifying parent if/when - // deletion occurs. May be not be nil. + // A reference to our parent controller. Used for fetching billing address + // labels. May be not be nil. // Weak reference, owns us. AutoFillDialogController* parentController_; + + // Either "Add" or "Edit" mode of sheet. + AutoFillCreditCardMode mode_; } @property (nonatomic, retain) AutoFillCreditCardModel* creditCardModel; @property (nonatomic, retain) NSArray* billingAddressContents; -@property (nonatomic, retain) NSArray* shippingAddressContents; +@property (nonatomic, retain) NSArray* expirationMonthContents; +@property (nonatomic, retain) NSArray* expirationYearContents; // Designated initializer. Takes a copy of the data in |creditCard|, // it is not held as a reference. - (id)initWithCreditCard:(const CreditCard&)creditCard - disclosure:(NSCellStateValue)disclosureState + mode:(AutoFillCreditCardMode)mode controller:(AutoFillDialogController*)parentController; -// Action to remove this credit card from the dialog. Forwards the request to -// |parentController_| which does all the actual work. We have the action -// here so that the delete button in the AutoFillCreditCardViewFormView.xib has -// something to call. -- (IBAction)deleteCreditCard:(id)sender; - -// Action to notify observers of the address list when changes have occured. -// For the credit card controller this means rebuild the popup menus. -- (IBAction)onAddressesChanged:(id)sender; +// IBActions for save and cancel buttons. Both invoke |endSheet:|. +- (IBAction)save:(id)sender; +- (IBAction)cancel:(id)sender; // Copy data from internal model to |creditCard|. - (void)copyModelToCreditCard:(CreditCard*)creditCard; @end -#endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_CREDIT_CARD_VIEW_CONTROLLER_MAC_ +#endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_CREDIT_CARD_SHEET_CONTROLLER_MAC_ diff --git a/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.mm b/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.mm new file mode 100644 index 0000000..11a6302 --- /dev/null +++ b/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.mm @@ -0,0 +1,165 @@ +// Copyright (c) 2010 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/autofill/autofill_credit_card_sheet_controller_mac.h" + +#include "app/l10n_util.h" +#include "base/mac_util.h" +#include "base/sys_string_conversions.h" +#import "chrome/browser/autofill/autofill_credit_card_model_mac.h" +#import "chrome/browser/autofill/autofill_dialog_controller_mac.h" +#include "chrome/browser/autofill/credit_card.h" +#include "grit/generated_resources.h" + +// Private methods for the |AutoFillCreditCardSheetController| class. +@interface AutoFillCreditCardSheetController (PrivateMethods) +- (void)buildBillingAddressContents; +- (void)buildExpirationMonthContents; +- (void)buildExpirationYearContents; +@end + +@implementation AutoFillCreditCardSheetController + +@synthesize creditCardModel = creditCardModel_; +@synthesize billingAddressContents = billingAddressContents_; +@synthesize expirationMonthContents = expirationMonthContents_; +@synthesize expirationYearContents = expirationYearContents_; + +- (id)initWithCreditCard:(const CreditCard&)creditCard + mode:(AutoFillCreditCardMode)mode + controller:(AutoFillDialogController*)parentController { + NSString* nibPath = [mac_util::MainAppBundle() + pathForResource:@"AutoFillCreditCardSheet" + ofType:@"nib"]; + self = [super initWithWindowNibPath:nibPath owner:self]; + if (self) { + // Create the model. We use setter here for KVO. + [self setCreditCardModel:[[[AutoFillCreditCardModel alloc] + initWithCreditCard:creditCard] autorelease]]; + + // We keep track of our parent controller for model-update purposes. + parentController_ = parentController; + + mode_ = mode; + } + return self; +} + +- (void)dealloc { + [creditCardModel_ release]; + [billingAddressContents_ release]; + [expirationMonthContents_ release]; + [expirationYearContents_ release]; + [super dealloc]; +} + +- (void)awakeFromNib { + // Setup initial state of popups. + [self buildBillingAddressContents]; + [self buildExpirationMonthContents]; + [self buildExpirationYearContents]; + + // Turn menu autoenable off. We manually govern this. + [billingAddressPopup_ setAutoenablesItems:NO]; + [expirationMonthPopup_ setAutoenablesItems:NO]; + [expirationYearPopup_ setAutoenablesItems:NO]; + + // Set the caption based on the mode. + NSString* caption; + if (mode_ == kAutoFillCreditCardAddMode) + caption = l10n_util::GetNSString(IDS_AUTOFILL_ADD_CREDITCARD_CAPTION); + else if (mode_ == kAutoFillCreditCardEditMode) + caption = l10n_util::GetNSString(IDS_AUTOFILL_EDIT_CREDITCARD_CAPTION); + else + NOTREACHED(); + [caption_ setStringValue:caption]; +} + +- (IBAction)save:(id)sender { + // Call |makeFirstResponder:| to commit pending text field edits. + [[self window] makeFirstResponder:[self window]]; + + [NSApp endSheet:[self window] returnCode:1]; +} + +- (IBAction)cancel:(id)sender { + [NSApp endSheet:[self window] returnCode:0]; +} + +- (void)copyModelToCreditCard:(CreditCard*)creditCard { + // The model copies the popup values blindly. We need to clear the strings + // in the case that our special menus are in effect. + if ([billingAddressPopup_ indexOfSelectedItem] <= 0) + [creditCardModel_ setBillingAddress:@""]; + if ([expirationMonthPopup_ indexOfSelectedItem] <= 0) + [creditCardModel_ setExpirationMonth:@""]; + if ([expirationYearPopup_ indexOfSelectedItem] <= 0) + [creditCardModel_ setExpirationYear:@""]; + + [creditCardModel_ copyModelToCreditCard:creditCard]; +} + +// Builds the |billingAddressContents_| array of strings from the list of +// addresses returned by the |parentController_| and additional UI string. +// Ensures that current selection is valid. If not, reset it. +- (void)buildBillingAddressContents { + NSString* menuString = l10n_util::GetNSString( + IDS_AUTOFILL_DIALOG_CHOOSE_EXISTING_ADDRESS); + + // Build the menu array and set it. + NSArray* addressStrings = [parentController_ addressLabels]; + NSArray* newArray = [[NSArray arrayWithObject:menuString] + arrayByAddingObjectsFromArray:addressStrings]; + [self setBillingAddressContents:newArray]; + + // If the addresses no longer contain our selected item, reset the selection. + if ([addressStrings + indexOfObject:[creditCardModel_ billingAddress]] == NSNotFound) { + [creditCardModel_ setBillingAddress:menuString]; + } + + // Disable first item in menu. "Choose existing address" is a non-item. + [[billingAddressPopup_ itemAtIndex:0] setEnabled:NO]; +} + +// Builds array of valid months. Uses special @" " to indicate no selection. +- (void)buildExpirationMonthContents { + NSArray* newArray = [NSArray arrayWithObjects:@" ", + @"01", @"02", @"03", @"04", @"05", @"06", + @"07", @"08", @"09", @"10", @"11", @"12", nil ]; + + [self setExpirationMonthContents:newArray]; + + // If the value from the model is not found in the array then set to the empty + // item @" ". + if ([newArray + indexOfObject:[creditCardModel_ expirationMonth]] == NSNotFound) { + [creditCardModel_ setExpirationMonth:@" "]; + } + + // Disable first item in menu. @" " is a non-item. + [[expirationMonthPopup_ itemAtIndex:0] setEnabled:NO]; +} + +// Builds array of valid years. Uses special @" " to indicate no selection. +- (void)buildExpirationYearContents { + NSArray* newArray = [NSArray arrayWithObjects:@" ", + @"2010", @"2011", @"2012", @"2013", @"2014", @"2015", + @"2016", @"2017", @"2018", @"2019", @"2020", @"2021", nil ]; + + [self setExpirationYearContents:newArray]; + + // If the value from the model is not found in the array then set to the empty + // item @" ". + if ([newArray + indexOfObject:[creditCardModel_ expirationYear]] == NSNotFound) { + [creditCardModel_ setExpirationYear:@" "]; + } + + // Disable first item in menu. @" " is a non-item. + [[expirationYearPopup_ itemAtIndex:0] setEnabled:NO]; +} + +@end + diff --git a/chrome/browser/autofill/autofill_credit_card_view_controller_mac_unittest.mm b/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac_unittest.mm index 8724f4b..a15b421 100644 --- a/chrome/browser/autofill/autofill_credit_card_view_controller_mac_unittest.mm +++ b/chrome/browser/autofill/autofill_credit_card_sheet_controller_mac_unittest.mm @@ -3,7 +3,7 @@ // found in the LICENSE file. #include "base/scoped_nsobject.h" -#import "chrome/browser/autofill/autofill_credit_card_view_controller_mac.h" +#import "chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.h" #include "chrome/browser/autofill/credit_card.h" #include "chrome/browser/cocoa/browser_test_helper.h" #import "chrome/browser/cocoa/cocoa_test_helper.h" @@ -11,18 +11,19 @@ namespace { -typedef CocoaTest AutoFillCreditCardViewControllerTest; +typedef CocoaTest AutoFillCreditCardSheetControllerTest; -TEST(AutoFillCreditCardViewControllerTest, Basic) { +TEST(AutoFillCreditCardSheetControllerTest, Basic) { // A basic test that creates a new instance and releases. // Aids valgrind leak detection. CreditCard credit_card(ASCIIToUTF16("myCC"), 0); - scoped_nsobject<AutoFillCreditCardViewController> controller( - [[AutoFillCreditCardViewController alloc] + scoped_nsobject<AutoFillCreditCardSheetController> controller( + [[AutoFillCreditCardSheetController alloc] initWithCreditCard:credit_card - disclosure:NSOffState + mode:kAutoFillCreditCardAddMode controller:nil]); EXPECT_TRUE(controller.get()); } } // namespace + diff --git a/chrome/browser/autofill/autofill_credit_card_view_controller_mac.mm b/chrome/browser/autofill/autofill_credit_card_view_controller_mac.mm deleted file mode 100644 index 8e7b29c..0000000 --- a/chrome/browser/autofill/autofill_credit_card_view_controller_mac.mm +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2010 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/autofill/autofill_credit_card_view_controller_mac.h" -#include "app/l10n_util.h" -#include "base/mac_util.h" -#include "base/sys_string_conversions.h" -#import "chrome/browser/autofill/autofill_credit_card_model_mac.h" -#import "chrome/browser/autofill/autofill_dialog_controller_mac.h" -#include "chrome/browser/autofill/credit_card.h" -#include "grit/generated_resources.h" - -// Private methods for the |AutoFillCreditCardViewController| class. -@interface AutoFillCreditCardViewController (PrivateMethods) -- (void)rebuildBillingAddressContents; -- (void)rebuildShippingAddressContents; -@end - -@implementation AutoFillCreditCardViewController - -@synthesize creditCardModel = creditCardModel_; -@synthesize billingAddressContents = billingAddressContents_; -@synthesize shippingAddressContents = shippingAddressContents_; - -- (id)initWithCreditCard:(const CreditCard&)creditCard - disclosure:(NSCellStateValue)disclosureState - controller:(AutoFillDialogController*)parentController { - self = [super initWithNibName:@"AutoFillCreditCardFormView" - bundle:mac_util::MainAppBundle() - disclosure:disclosureState]; - if (self) { - // Pull in the view for initialization. - [self view]; - - // Create the model. We use setter here for KVO. - [self setCreditCardModel:[[[AutoFillCreditCardModel alloc] - initWithCreditCard:creditCard] autorelease]]; - - // We keep track of our parent controller for model-update purposes. - parentController_ = parentController; - - // Setup initial state of popups. - [self onAddressesChanged:self]; - } - return self; -} - -- (void)dealloc { - [creditCardModel_ release]; - [billingAddressContents_ release]; - [shippingAddressContents_ release]; - [super dealloc]; -} - -- (void)awakeFromNib { - [super awakeFromNib]; - - // Turn menu autoenable off. We manually govern this. - [billingAddressPopup_ setAutoenablesItems:NO]; - [shippingAddressPopup_ setAutoenablesItems:NO]; -} - -- (IBAction)deleteCreditCard:(id)sender { - [parentController_ deleteCreditCard:self]; -} - -- (IBAction)onAddressesChanged:(id)sender { - [self rebuildBillingAddressContents]; - [self rebuildShippingAddressContents]; -} - -- (void)copyModelToCreditCard:(CreditCard*)creditCard { - [creditCardModel_ copyModelToCreditCard:creditCard]; - - // The model copies the shipping and billing addresses blindly. We need - // to clear the strings in the case that our special menus are in effect. - if ([billingAddressPopup_ indexOfSelectedItem] <= 0) - creditCard->set_billing_address(string16()); - if ([shippingAddressPopup_ indexOfSelectedItem] <= 0) - creditCard->set_shipping_address(string16()); -} - -// Builds the |billingAddressContents_| array of strings from the list of -// addresses returned by the |parentController_| and additional UI string. -// Ensures that current selection is valid, if not reset it. -- (void)rebuildBillingAddressContents { - NSString* menuString = l10n_util::GetNSString( - IDS_AUTOFILL_DIALOG_CHOOSE_EXISTING_ADDRESS); - - // Build the menu array and set it. - NSArray* addressStrings = [parentController_ addressLabels]; - NSArray* newArray = [[NSArray arrayWithObject:menuString] - arrayByAddingObjectsFromArray:addressStrings]; - [self setBillingAddressContents:newArray]; - - // If the addresses no longer contain our selected item, reset the selection. - if ([addressStrings - indexOfObject:[creditCardModel_ billingAddress]] == NSNotFound) { - [creditCardModel_ setBillingAddress:menuString]; - } - - // Disable first item in menu. "Choose existing address" is a non-item. - [[billingAddressPopup_ itemAtIndex:0] setEnabled:NO]; -} - -// Builds the |shippingAddressContents_| array of strings from the list of -// addresses returned by the |parentController_| and additional UI string. -// Ensures that current selection is valid, if not reset it. -- (void)rebuildShippingAddressContents { - NSString* menuString = l10n_util::GetNSString( - IDS_AUTOFILL_DIALOG_SAME_AS_BILLING); - - // Build the menu array and set it. - NSArray* addressStrings = [parentController_ addressLabels]; - NSArray* newArray = [[NSArray arrayWithObject:menuString] - arrayByAddingObjectsFromArray:addressStrings]; - [self setShippingAddressContents:newArray]; - - // If the addresses no longer contain our selected item, reset the selection. - if ([addressStrings - indexOfObject:[creditCardModel_ shippingAddress]] == NSNotFound) { - [creditCardModel_ setShippingAddress:menuString]; - } -} - -@end - diff --git a/chrome/browser/autofill/autofill_dialog_controller_mac.h b/chrome/browser/autofill/autofill_dialog_controller_mac.h index 3ea6900..ba4c4d4 100644 --- a/chrome/browser/autofill/autofill_dialog_controller_mac.h +++ b/chrome/browser/autofill/autofill_dialog_controller_mac.h @@ -17,10 +17,10 @@ namespace AutoFillDialogControllerInternal { class PersonalDataManagerObserver; } // AutoFillDialogControllerInternal -@class AutoFillAddressViewController; -@class AutoFillCreditCardViewController; +@class AutoFillAddressSheetController; +@class AutoFillCreditCardSheetController; +@class AutoFillTableView; class Profile; -@class SectionSeparatorView; @class WindowSizeAutosaver; // A window controller for managing the autofill options dialog. @@ -28,30 +28,52 @@ class Profile; // personal address and credit card information. @interface AutoFillDialogController : NSWindowController { @private - IBOutlet NSView* childView_; - IBOutlet NSView* addressSection_; - IBOutlet SectionSeparatorView* addressSectionBox_; - IBOutlet NSView* creditCardSection_; + // Outlet to the main NSTableView object listing both addresses and credit + // cards with section headers for both. + IBOutlet AutoFillTableView* tableView_; - // Note on ownership: the controllers are strongly owned by the dialog - // controller. Their views are inserted into the dialog's view hierarchy - // but are retained by these controllers as well. + // This observer is passed in by the caller of the dialog. When the dialog + // is dismissed |observer_| is called with new values for the addresses and + // credit cards. + // Weak, not retained. + AutoFillDialogObserver* observer_; - // Array of |AutoFillAddressViewController|. - scoped_nsobject<NSMutableArray> addressFormViewControllers_; + // Reference to input parameter. + // Weak, not retained. + Profile* profile_; - // Array of |AutoFillCreditCardViewController|. - scoped_nsobject<NSMutableArray> creditCardFormViewControllers_; + // Reference to input parameter. + // Weak, not retained. + AutoFillProfile* importedProfile_; - AutoFillDialogObserver* observer_; // Weak, not retained. - Profile* profile_; // Weak, not retained. - AutoFillProfile* importedProfile_; // Weak, not retained. - CreditCard* importedCreditCard_; // Weak, not retained. + // Reference to input parameter. + // Weak, not retained. + CreditCard* importedCreditCard_; + + // Working list of input profiles. std::vector<AutoFillProfile> profiles_; + + // Working list of input credit cards. std::vector<CreditCard> creditCards_; + + // State of checkbox for enabling Mac Address Book integration. BOOL auxiliaryEnabled_; + + // State for |itemIsSelected| property used in bindings for "Edit..." and + // "Remove" buttons. + BOOL itemIsSelected_; + + // Utility object to save and restore dialog position. scoped_nsobject<WindowSizeAutosaver> sizeSaver_; + // Transient reference to address "Add" / "Edit" sheet for address + // information. + scoped_nsobject<AutoFillAddressSheetController> addressSheetController; + + // Transient reference to address "Add" / "Edit" sheet for credit card + // information. + scoped_nsobject<AutoFillCreditCardSheetController> creditCardSheetController; + // Manages PersonalDataManager loading. scoped_ptr<AutoFillDialogControllerInternal::PersonalDataManagerObserver> personalDataManagerObserver_; @@ -61,7 +83,11 @@ class Profile; // bound to this in nib. @property (nonatomic) BOOL auxiliaryEnabled; -// Main interface for displaying an application modal autofill dialog on screen. +// Property representing selection state in |tableView_|. Enabled state of +// edit and delete buttons are bound to this property. +@property (nonatomic) BOOL itemIsSelected; + +// Main interface for displaying an application modal AutoFill dialog on screen. // This class method creates a new |AutoFillDialogController| and runs it as a // modal dialog. The controller autoreleases itself when the dialog is closed. // |observer| can be NULL, but if it is, then no notification is sent during @@ -86,21 +112,25 @@ class Profile; - (IBAction)addNewAddress:(id)sender; - (IBAction)addNewCreditCard:(id)sender; -// IBActions for deleting items. |sender| is expected to be either a -// |AutoFillAddressViewController| or a |AutoFillCreditCardViewController|. -- (IBAction)deleteAddress:(id)sender; -- (IBAction)deleteCreditCard:(id)sender; +// IBAction for deleting an item. |sender| is expected to be the "Remove" +// button. The deletion acts on the selected item in either the address or +// credit card list. +- (IBAction)deleteSelection:(id)sender; -// IBAction for sender to alert dialog that an address label has changed. -- (IBAction)notifyAddressChange:(id)sender; +// IBActions for editing an item. |sender| is expected to be the "Edit..." +// button. The editing acts on the selected item in either the address or +// credit card list. +- (IBAction)editSelection:(id)sender; -// Returns an array of labels representing the addresses in the -// |addressFormViewControllers_|. -- (NSArray*)addressLabels; +// NSTableView data source methods. +- (id)tableView:(NSTableView *)tableView + objectValueForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)rowIndex; -// Returns an array of labels representing the credit cards in the -// |creditCardFormViewControllers_|. -- (NSArray*)creditCardLabels; +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView; + +// Returns an array of labels representing the addresses in the |profiles_|. +- (NSArray*)addressLabels; @end @@ -119,9 +149,11 @@ class Profile; profile:(Profile*)profile importedProfile:(AutoFillProfile*)importedProfile importedCreditCard:(CreditCard*)importedCreditCard; -- (NSMutableArray*)addressFormViewControllers; -- (NSMutableArray*)creditCardFormViewControllers; - (void)closeDialog; +- (AutoFillAddressSheetController*)addressSheetController; +- (AutoFillCreditCardSheetController*)creditCardSheetController; +- (void)selectAddressAtIndex:(size_t)i; +- (void)selectCreditCardAtIndex:(size_t)i; @end #endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_DIALOG_CONTROLLER_MAC_ diff --git a/chrome/browser/autofill/autofill_dialog_controller_mac.mm b/chrome/browser/autofill/autofill_dialog_controller_mac.mm index c867497..c39e3e8 100644 --- a/chrome/browser/autofill/autofill_dialog_controller_mac.mm +++ b/chrome/browser/autofill/autofill_dialog_controller_mac.mm @@ -4,29 +4,100 @@ #import "chrome/browser/autofill/autofill_dialog_controller_mac.h" #include "app/l10n_util.h" +#include "app/resource_bundle.h" #include "base/mac_util.h" #include "base/sys_string_conversions.h" #import "chrome/browser/autofill/autofill_address_model_mac.h" -#import "chrome/browser/autofill/autofill_address_view_controller_mac.h" +#import "chrome/browser/autofill/autofill_address_sheet_controller_mac.h" #import "chrome/browser/autofill/autofill_credit_card_model_mac.h" -#import "chrome/browser/autofill/autofill_credit_card_view_controller_mac.h" +#import "chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.h" #import "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/browser_process.h" -#import "chrome/browser/cocoa/disclosure_view_controller.h" -#import "chrome/browser/cocoa/section_separator_view.h" #import "chrome/browser/cocoa/window_size_autosaver.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/common/pref_names.h" #include "grit/generated_resources.h" +#include "grit/theme_resources.h" + +// Delegate protocol that needs to be in place for the AutoFillTableView's +// handling of delete and backspace keys. +@protocol DeleteKeyDelegate +- (IBAction)deleteSelection:(id)sender; +@end + +// A subclass of NSTableView that allows for deleting selected elements using +// the delete or backspace keys. +@interface AutoFillTableView : NSTableView { +} +@end + +@implementation AutoFillTableView + +// We override the keyDown method to dispatch the |deleteSelection:| action +// when the user presses the delete or backspace keys. Note a delegate must +// be present that conforms to the DeleteKeyDelegate protocol. +- (void)keyDown:(NSEvent *)event { + id object = [self delegate]; + unichar c = [[event characters] characterAtIndex: 0]; + + // If the user pressed delete and the delegate supports deleteSelection: + if ((c == NSDeleteFunctionKey || + c == NSDeleteCharFunctionKey || + c == NSDeleteCharacter) && + [object respondsToSelector:@selector(deleteSelection:)]) { + id <DeleteKeyDelegate> delegate = (id <DeleteKeyDelegate>) object; + + [delegate deleteSelection:self]; + } else { + [super keyDown:event]; + } +} + +@end // Private interface. -@interface AutoFillDialogController (PrivateAPI) +@interface AutoFillDialogController (PrivateMethods) // Asyncronous handler for when PersonalDataManager data loads. The // personal data manager notifies the dialog with this method when the // data loading is complete and ready to be used. - (void)onPersonalDataLoaded:(const std::vector<AutoFillProfile*>&)profiles creditCards:(const std::vector<CreditCard*>&)creditCards; + +// Returns true if |row| is an index to a valid profile in |tableView_|, and +// false otherwise. +- (BOOL)isProfileRow:(NSInteger)row; + +// Returns true if |row| is an index to the profile group row in |tableView_|, +// and false otherwise. +- (BOOL)isProfileGroupRow:(NSInteger)row; + +// Returns true if |row| is an index to a valid credit card in |tableView_|, and +// false otherwise. +- (BOOL)isCreditCardRow:(NSInteger)row; + +// Returns true if |row| is the index to the credit card group row in +// |tableView_|, and false otherwise. +- (BOOL)isCreditCardGroupRow:(NSInteger)row; + +// Returns the index to |profiles_| of the corresponding |row| in |tableView_|. +- (size_t)profileIndexFromRow:(NSInteger)row; + +// Returns the index to |creditCards_| of the corresponding |row| in +// |tableView_|. +- (size_t)creditCardIndexFromRow:(NSInteger)row; + +// Returns the |row| in |tableView_| that corresponds to the index |i| into +// |profiles_|. +- (NSInteger)rowFromProfileIndex:(size_t)i; + +// Returns the |row| in |tableView_| that corresponds to the index |i| into +// |creditCards_|. +- (NSInteger)rowFromCreditCardIndex:(size_t)row; + +// Invokes the modal dialog. +- (void)runModalDialog; + @end namespace AutoFillDialogControllerInternal { @@ -94,14 +165,10 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { } // namespace AutoFillDialogControllerInternal -@interface AutoFillDialogController (PrivateMethods) -- (void)runModalDialog; -- (void)installChildViews; -@end - @implementation AutoFillDialogController @synthesize auxiliaryEnabled = auxiliaryEnabled_; +@synthesize itemIsSelected = itemIsSelected_; + (void)showAutoFillDialogWithObserver:(AutoFillDialogObserver*)observer profile:(Profile*)profile @@ -120,8 +187,6 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { } - (void)awakeFromNib { - [addressSectionBox_ setShowTopLine:FALSE]; - PersonalDataManager* personal_data_manager = profile_->GetPersonalDataManager(); DCHECK(personal_data_manager); @@ -138,46 +203,27 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { self, personal_data_manager, profile_)); personal_data_manager->SetObserver(personalDataManagerObserver_.get()); } + + // Explicitly load the data in the table before window displays to avoid + // nasty flicker as tables update. + [tableView_ reloadData]; + + // Set up edit when double-clicking on a table row. + [tableView_ setDoubleAction:@selector(editSelection:)]; } // NSWindow Delegate callback. When the window closes the controller can // be released. - (void)windowWillClose:(NSNotification *)notification { - // Force views to go away so they properly remove their observations. - addressFormViewControllers_.reset(); - creditCardFormViewControllers_.reset(); + [tableView_ setDataSource:nil]; + [tableView_ setDelegate:nil]; [self autorelease]; } // Called when the user clicks the save button. - (IBAction)save:(id)sender { - // Call |makeFirstResponder:| to commit pending text field edits. - [[self window] makeFirstResponder:[self window]]; - // If we have an |observer_| then communicate the changes back. if (observer_) { - profiles_.clear(); - profiles_.resize([addressFormViewControllers_ count]); - int i = 0; - for (AutoFillAddressViewController* addressFormViewController in - addressFormViewControllers_.get()) { - // Initialize the profile here. The default initializer does not fully - // initialize. - profiles_[i] = AutoFillProfile(ASCIIToUTF16(""), 0); - [addressFormViewController copyModelToProfile:&profiles_[i]]; - i++; - } - creditCards_.clear(); - creditCards_.resize([creditCardFormViewControllers_ count]); - int j = 0; - for (AutoFillCreditCardViewController* creditCardFormViewController in - creditCardFormViewControllers_.get()) { - // Initialize the credit card here. The default initializer does not - // fully initialize. - creditCards_[j] = CreditCard(ASCIIToUTF16(""), 0); - [creditCardFormViewController copyModelToCreditCard:&creditCards_[j]]; - j++; - } profile_->GetPrefs()->SetBoolean(prefs::kAutoFillAuxiliaryProfilesEnabled, auxiliaryEnabled_); observer_->OnAutoFillDialogApply(&profiles_, &creditCards_); @@ -191,144 +237,306 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { [self closeDialog]; } -// Adds new address to bottom of list. A new address controller is created -// and its view is inserted into the view hierarchy. +// Invokes the "Add" sheet for address information. If user saves then the new +// information is added to |profiles_| in |addressAddDidEnd:| method. - (IBAction)addNewAddress:(id)sender { - // Insert relative to top of section, or below last address. - NSView* insertionPoint; - NSUInteger count = [addressFormViewControllers_.get() count]; - if (count == 0) { - insertionPoint = addressSection_; - } else { - insertionPoint = [[addressFormViewControllers_.get() - objectAtIndex:[addressFormViewControllers_.get() count] - 1] view]; + DCHECK(!addressSheetController.get()); + + // Create a new default address. + string16 newName = l10n_util::GetStringUTF16(IDS_AUTOFILL_NEW_ADDRESS); + AutoFillProfile newAddress(newName, 0); + + // Create a new address sheet controller in "Add" mode. + addressSheetController.reset( + [[AutoFillAddressSheetController alloc] + initWithProfile:newAddress + mode:kAutoFillAddressAddMode]); + + // Show the sheet. + [NSApp beginSheet:[addressSheetController window] + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(addressAddDidEnd:returnCode:contextInfo:) + contextInfo:NULL]; +} + +// Invokes the "Add" sheet for credit card information. If user saves then the +// new information is added to |creditCards_| in |creditCardAddDidEnd:| method. +- (IBAction)addNewCreditCard:(id)sender { + DCHECK(!creditCardSheetController.get()); + + // Create a new default credit card. + string16 newName = l10n_util::GetStringUTF16(IDS_AUTOFILL_NEW_CREDITCARD); + CreditCard newCreditCard(newName, 0); + + // Create a new address sheet controller in "Add" mode. + creditCardSheetController.reset( + [[AutoFillCreditCardSheetController alloc] + initWithCreditCard:newCreditCard + mode:kAutoFillCreditCardAddMode + controller:self]); + + // Show the sheet. + [NSApp beginSheet:[creditCardSheetController window] + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(creditCardAddDidEnd:returnCode:contextInfo:) + contextInfo:NULL]; +} + +// Add address sheet was dismissed. Non-zero |returnCode| indicates a save. +- (void)addressAddDidEnd:(NSWindow*)sheet + returnCode:(int)returnCode + contextInfo:(void*)contextInfo { + DCHECK(contextInfo == NULL); + + if (returnCode) { + // Create a new address and save it to the |profiles_| list. + AutoFillProfile newAddress(string16(), 0); + [addressSheetController copyModelToProfile:&newAddress]; + profiles_.push_back(newAddress); + + // Refresh the view based on new data. + [tableView_ reloadData]; + + // Update the selection to the newly added item. + NSInteger row = [self rowFromProfileIndex:profiles_.size() - 1]; + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:row] + byExtendingSelection:NO]; } + [sheet orderOut:self]; + addressSheetController.reset(nil); +} - // Create a new default address, and add it to our array of controllers. - string16 new_address_name = l10n_util::GetStringUTF16( - IDS_AUTOFILL_NEW_ADDRESS); - AutoFillProfile newProfile(new_address_name, 0); - scoped_nsobject<AutoFillAddressViewController> addressViewController( - [[AutoFillAddressViewController alloc] - initWithProfile:newProfile - disclosure:NSOnState - controller:self]); - [self willChangeValueForKey:@"addressLabels"]; - [addressFormViewControllers_.get() addObject:addressViewController]; - [self didChangeValueForKey:@"addressLabels"]; +// Add credit card sheet was dismissed. Non-zero |returnCode| indicates a save. +- (void)creditCardAddDidEnd:(NSWindow *)sheet + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + DCHECK(contextInfo == NULL); + + if (returnCode) { + // Create a new credit card and save it to the |creditCards_| list. + CreditCard newCreditCard(string16(), 0); + [creditCardSheetController copyModelToCreditCard:&newCreditCard]; + creditCards_.push_back(newCreditCard); + + // Refresh the view based on new data. + [tableView_ reloadData]; + + // Update the selection to the newly added item. + NSInteger row = [self rowFromCreditCardIndex:creditCards_.size() - 1]; + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:row] + byExtendingSelection:NO]; + } + [sheet orderOut:self]; + creditCardSheetController.reset(nil); +} - // Embed the new address into our target view. - [childView_ addSubview:[addressViewController view] - positioned:NSWindowBelow relativeTo:insertionPoint]; - [[addressViewController view] setFrameOrigin:NSMakePoint(0, 0)]; +// Deletes selected item, either address or credit card depending on the item +// selected. +- (IBAction)deleteSelection:(id)sender { + NSInteger selectedRow = [tableView_ selectedRow]; + if ([self isProfileRow:selectedRow]) { + profiles_.erase(profiles_.begin() + [self profileIndexFromRow:selectedRow]); + + // Select the previous row if possible, else current row, else deselect all. + if ([self tableView:tableView_ shouldSelectRow:selectedRow-1]) { + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow-1] + byExtendingSelection:NO]; + } else if ([self tableView:tableView_ shouldSelectRow:selectedRow]) { + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow] + byExtendingSelection:NO]; + } else { + [tableView_ selectRowIndexes:[NSIndexSet indexSet] + byExtendingSelection:NO]; + } + [tableView_ reloadData]; + } else if ([self isCreditCardRow:selectedRow]) { + creditCards_.erase( + creditCards_.begin() + [self creditCardIndexFromRow:selectedRow]); + + // Select the previous row if possible, else current row, else deselect all. + if ([self tableView:tableView_ shouldSelectRow:selectedRow-1]) { + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow-1] + byExtendingSelection:NO]; + } else if ([self tableView:tableView_ shouldSelectRow:selectedRow]) { + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow] + byExtendingSelection:NO]; + } else { + [tableView_ selectRowIndexes:[NSIndexSet indexSet] + byExtendingSelection:NO]; + } + [tableView_ reloadData]; + } +} - [self notifyAddressChange:self]; +// Edits the selected item, either address or credit card depending on the item +// selected. +- (IBAction)editSelection:(id)sender { + NSInteger selectedRow = [tableView_ selectedRow]; + if ([self isProfileRow:selectedRow]) { + if (!addressSheetController.get()) { + int i = [self profileIndexFromRow:selectedRow]; + + // Create a new address sheet controller in "Edit" mode. + addressSheetController.reset( + [[AutoFillAddressSheetController alloc] + initWithProfile:profiles_[i] + mode:kAutoFillAddressEditMode]); + + // Show the sheet. + [NSApp beginSheet:[addressSheetController window] + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(addressEditDidEnd:returnCode:contextInfo:) + contextInfo:&profiles_[i]]; + } + } else if ([self isCreditCardRow:selectedRow]) { + if (!creditCardSheetController.get()) { + int i = [self creditCardIndexFromRow:selectedRow]; + + // Create a new credit card sheet controller in "Edit" mode. + creditCardSheetController.reset( + [[AutoFillCreditCardSheetController alloc] + initWithCreditCard:creditCards_[i] + mode:kAutoFillCreditCardEditMode + controller:self]); + + // Show the sheet. + [NSApp beginSheet:[creditCardSheetController window] + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(creditCardEditDidEnd:returnCode:contextInfo:) + contextInfo:&creditCards_[i]]; + } + } +} - // Recalculate key view loop to account for change in view tree. - [[self window] recalculateKeyViewLoop]; +// Edit address sheet was dismissed. Non-zero |returnCode| indicates a save. +- (void)addressEditDidEnd:(NSWindow *)sheet + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + DCHECK(contextInfo != NULL); + if (returnCode) { + AutoFillProfile* profile = static_cast<AutoFillProfile*>(contextInfo); + [addressSheetController copyModelToProfile:profile]; + [tableView_ reloadData]; + } + [sheet orderOut:self]; + addressSheetController.reset(nil); } -// Adds new credit card to bottom of list. A new credit card controller is -// created and its view is inserted into the view hierarchy. -- (IBAction)addNewCreditCard:(id)sender { - // Insert relative to top of section, or below last address. - NSView* insertionPoint; - NSUInteger count = [creditCardFormViewControllers_.get() count]; - if (count == 0) { - insertionPoint = creditCardSection_; - } else { - insertionPoint = [[creditCardFormViewControllers_.get() - objectAtIndex:[creditCardFormViewControllers_.get() count] - 1] view]; +// Edit credit card sheet was dismissed. Non-zero |returnCode| indicates a +// save. +- (void)creditCardEditDidEnd:(NSWindow *)sheet + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + DCHECK(contextInfo != NULL); + if (returnCode) { + CreditCard* creditCard = static_cast<CreditCard*>(contextInfo); + [creditCardSheetController copyModelToCreditCard:creditCard]; + [tableView_ reloadData]; } + [sheet orderOut:self]; + creditCardSheetController.reset(nil); +} - // Create a new default credit card, and add it to our array of controllers. - string16 new_credit_card_name = l10n_util::GetStringUTF16( - IDS_AUTOFILL_NEW_CREDITCARD); - CreditCard newCreditCard(new_credit_card_name, 0); - scoped_nsobject<AutoFillCreditCardViewController> creditCardViewController( - [[AutoFillCreditCardViewController alloc] - initWithCreditCard:newCreditCard - disclosure:NSOnState - controller:self]); - [self willChangeValueForKey:@"creditCardLabels"]; - [creditCardFormViewControllers_.get() addObject:creditCardViewController]; - [self didChangeValueForKey:@"creditCardLabels"]; - - // Embed the new address into our target view. - [childView_ addSubview:[creditCardViewController view] - positioned:NSWindowBelow relativeTo:insertionPoint]; - [[creditCardViewController view] setFrameOrigin:NSMakePoint(0, 0)]; - - // Recalculate key view loop to account for change in view tree. - [[self window] recalculateKeyViewLoop]; -} - -- (IBAction)deleteAddress:(id)sender { - NSUInteger i = [addressFormViewControllers_.get() indexOfObject:sender]; - DCHECK(i != NSNotFound); - - // Remove controller's view from superview and remove from list of - // controllers. Note on lifetime: removing view from super view decrements - // refcount of view, removing controller from array decrements refcount of - // controller which in-turn decrement refcount of view. Both should dealloc - // at this point. - [[sender view] removeFromSuperview]; - [self willChangeValueForKey:@"addressLabels"]; - [addressFormViewControllers_.get() removeObjectAtIndex:i]; - [self didChangeValueForKey:@"addressLabels"]; - - [self notifyAddressChange:self]; - - // Recalculate key view loop to account for change in view tree. - [[self window] recalculateKeyViewLoop]; -} - -- (IBAction)deleteCreditCard:(id)sender { - NSUInteger i = [creditCardFormViewControllers_.get() indexOfObject:sender]; - DCHECK(i != NSNotFound); - - // Remove controller's view from superview and remove from list of - // controllers. Note on lifetime: removing view from super view decrements - // refcount of view, removing controller from array decrements refcount of - // controller which in-turn decrement refcount of view. Both should dealloc - // at this point. - [[sender view] removeFromSuperview]; - [self willChangeValueForKey:@"creditCardLabels"]; - [creditCardFormViewControllers_.get() removeObjectAtIndex:i]; - [self didChangeValueForKey:@"creditCardLabels"]; - - // Recalculate key view loop to account for change in view tree. - [[self window] recalculateKeyViewLoop]; -} - -// Credit card controllers are dependent upon the address labels. So we notify -// them here that something has changed. -- (IBAction)notifyAddressChange:(id)sender { - for (AutoFillCreditCardViewController* creditCardFormViewController in - creditCardFormViewControllers_.get()) { - [creditCardFormViewController onAddressesChanged:self]; +// NSTableView Delegate method. +- (BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row { + if ([self isProfileGroupRow:row] || [self isCreditCardGroupRow:row]) + return YES; + return NO; +} + +// NSTableView Delegate method. +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row { + return ![self tableView:tableView isGroupRow:row]; +} + +// NSTableView Delegate method. +- (id)tableView:(NSTableView *)tableView + objectValueForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row { + if ([[tableColumn identifier] isEqualToString:@"Spacer"]) + return @""; + + // Check that we're initialized before supplying data. + if (tableView == tableView_) { + + // Section label. + if ([self isProfileGroupRow:row]) + if ([[tableColumn identifier] isEqualToString:@"Label"]) + return @"Addresses"; + else + return @""; + + if (row < 0) + return @""; + + // Data row. + if ([self isProfileRow:row]) { + if ([[tableColumn identifier] isEqualToString:@"Label"]) + return SysUTF16ToNSString( + profiles_[[self profileIndexFromRow:row]].Label()); + + if ([[tableColumn identifier] isEqualToString:@"Summary"]) + return SysUTF16ToNSString( + profiles_[[self profileIndexFromRow:row]].PreviewSummary()); + + return @""; + } + + // Section label. + if ([self isCreditCardGroupRow:row]) + if ([[tableColumn identifier] isEqualToString:@"Label"]) + return @"Credit Cards"; + else + return @""; + + // Data row. + if ([self isCreditCardRow:row]) { + if ([[tableColumn identifier] isEqualToString:@"Label"]) + return SysUTF16ToNSString( + creditCards_[[self creditCardIndexFromRow:row]].Label()); + + if ([[tableColumn identifier] isEqualToString:@"Summary"]) + return SysUTF16ToNSString( + creditCards_[ + [self creditCardIndexFromRow:row]].PreviewSummary()); + + return @""; + } } + + return @""; } -- (NSArray*)addressLabels { - NSUInteger capacity = [addressFormViewControllers_ count]; - NSMutableArray* array = [NSMutableArray arrayWithCapacity:capacity]; +// We implement this delegate method to update our |itemIsSelected| property. +// The "Edit..." and "Remove" buttons' enabled state depends on having a +// valid selection in the table. +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { + if ([tableView_ selectedRow] >= 0) + [self setItemIsSelected:YES]; + else + [self setItemIsSelected:NO]; +} - for (AutoFillAddressViewController* addressFormViewController in - addressFormViewControllers_.get()) { - [array addObject:[[addressFormViewController addressModel] label]]; +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + if (tableView == tableView_) { + // 1 section header, the profiles, 1 section header, the credit cards. + return 1 + profiles_.size() + 1 + creditCards_.size(); } - return array; + return 0; } -- (NSArray*)creditCardLabels { - NSUInteger capacity = [creditCardFormViewControllers_ count]; +- (NSArray*)addressLabels { + NSUInteger capacity = profiles_.size(); NSMutableArray* array = [NSMutableArray arrayWithCapacity:capacity]; - for (AutoFillCreditCardViewController* creditCardFormViewController in - creditCardFormViewControllers_.get()) { - [array addObject:[[creditCardFormViewController creditCardModel] label]]; + std::vector<AutoFillProfile>::iterator i; + for (i = profiles_.begin(); i != profiles_.end(); ++i) { + [array addObject:SysUTF16ToNSString(i->Label())]; } return array; @@ -361,13 +569,14 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { profile:(Profile*)profile importedProfile:(AutoFillProfile*)importedProfile importedCreditCard:(CreditCard*)importedCreditCard { - CHECK(profile); + DCHECK(profile); // Use initWithWindowNibPath: instead of initWithWindowNibName: so we // can override it in a unit test. NSString* nibpath = [mac_util::MainAppBundle() pathForResource:@"AutoFillDialog" ofType:@"nib"]; if ((self = [super initWithWindowNibPath:nibpath owner:self])) { + // Initialize member variables based on input. observer_ = observer; profile_ = profile; importedProfile_ = importedProfile; @@ -380,14 +589,6 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { // Do not use [NSMutableArray array] here; we need predictable destruction // which will be prevented by having a reference held by an autorelease // pool. - - // Initialize array of sub-controllers. - addressFormViewControllers_.reset( - [[NSMutableArray alloc] initWithCapacity:0]); - - // Initialize array of sub-controllers. - creditCardFormViewControllers_.reset( - [[NSMutableArray alloc] initWithCapacity:0]); } return self; } @@ -398,12 +599,24 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { [NSApp stopModal]; } -- (NSMutableArray*)addressFormViewControllers { - return addressFormViewControllers_.get(); +- (AutoFillAddressSheetController*)addressSheetController { + return addressSheetController.get(); +} + +- (AutoFillCreditCardSheetController*)creditCardSheetController { + return creditCardSheetController.get(); +} + +- (void)selectAddressAtIndex:(size_t)i { + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex: + [self rowFromProfileIndex:i]] + byExtendingSelection:NO]; } -- (NSMutableArray*)creditCardFormViewControllers { - return creditCardFormViewControllers_.get(); +- (void)selectCreditCardAtIndex:(size_t)i { + [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex: + [self rowFromCreditCardIndex:i]] + byExtendingSelection:NO]; } @end @@ -424,52 +637,6 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { [NSApp runModalForWindow:[self window]]; } -// Install controller and views for the address form and the credit card form. -// They are installed into the appropriate sibling order so that they can be -// arranged vertically by the VerticalLayoutView class. We insert the views -// into the |childView_| but we hold onto the controllers and release them in -// our dealloc once the dialog closes. -- (void)installChildViews { - NSView* insertionPoint; - insertionPoint = addressSection_; - for (size_t i = 0; i < profiles_.size(); i++) { - // Special case for first address, we want to show full contents. - NSCellStateValue disclosureState = (i == 0) ? NSOnState : NSOffState; - scoped_nsobject<AutoFillAddressViewController> addressViewController( - [[AutoFillAddressViewController alloc] - initWithProfile:profiles_[i] - disclosure:disclosureState - controller:self]); - [self willChangeValueForKey:@"addressLabels"]; - [addressFormViewControllers_.get() addObject:addressViewController]; - [self didChangeValueForKey:@"addressLabels"]; - - // Embed the child view into our (owned by us) target view. - [childView_ addSubview:[addressViewController view] - positioned:NSWindowBelow relativeTo:insertionPoint]; - insertionPoint = [addressViewController view]; - [[addressViewController view] setFrameOrigin:NSMakePoint(0, 0)]; - } - - insertionPoint = creditCardSection_; - for (size_t i = 0; i < creditCards_.size(); i++) { - scoped_nsobject<AutoFillCreditCardViewController> creditCardViewController( - [[AutoFillCreditCardViewController alloc] - initWithCreditCard:creditCards_[i] - disclosure:NSOffState - controller:self]); - [self willChangeValueForKey:@"creditCardLabels"]; - [creditCardFormViewControllers_.get() addObject:creditCardViewController]; - [self didChangeValueForKey:@"creditCardLabels"]; - - // Embed the child view into our (owned by us) target view. - [childView_ addSubview:[creditCardViewController view] - positioned:NSWindowBelow relativeTo:insertionPoint]; - insertionPoint = [creditCardViewController view]; - [[creditCardViewController view] setFrameOrigin:NSMakePoint(0, 0)]; - } -} - - (void)onPersonalDataLoaded:(const std::vector<AutoFillProfile*>&)profiles creditCards:(const std::vector<CreditCard*>&)creditCards { if (importedProfile_) { @@ -492,8 +659,116 @@ void PersonalDataManagerObserver::OnPersonalDataLoaded() { iter != creditCards.end(); ++iter) creditCards_.push_back(**iter); } +} + +- (BOOL)isProfileRow:(NSInteger)row { + if (row > 0 && static_cast<size_t>(row) <= profiles_.size()) + return YES; + return NO; +} + +- (BOOL)isProfileGroupRow:(NSInteger)row { + if (row == 0) + return YES; + return NO; +} + +- (BOOL)isCreditCardRow:(NSInteger)row { + if (row > 0 && + static_cast<size_t>(row) >= profiles_.size() + 2 && + static_cast<size_t>(row) <= profiles_.size() + creditCards_.size() + 1) + return YES; + return NO; +} + +- (BOOL)isCreditCardGroupRow:(NSInteger)row { + if (row > 0 && static_cast<size_t>(row) == profiles_.size() + 1) + return YES; + return NO; +} + +- (size_t)profileIndexFromRow:(NSInteger)row { + DCHECK([self isProfileRow:row]); + return static_cast<size_t>(row) - 1; +} + +- (size_t)creditCardIndexFromRow:(NSInteger)row { + DCHECK([self isCreditCardRow:row]); + return static_cast<size_t>(row) - (profiles_.size() + 2); +} + +- (NSInteger)rowFromProfileIndex:(size_t)i { + return 1 + i; +} + +- (NSInteger)rowFromCreditCardIndex:(size_t)i { + return 1 + profiles_.size() + 1 + i; +} + +@end + +// An NSValueTransformer subclass for use in validation of empty data entry +// fields. Transforms a nil or empty string into a warning image. This data +// transformer is used in the address and credit card sheets for empty label +// strings. +@interface MissingAlertTransformer : NSValueTransformer { +} +@end + +@implementation MissingAlertTransformer ++ (Class)transformedValueClass { + return [NSImage class]; +} + ++ (BOOL)allowsReverseTransformation { + return NO; +} + +- (id)transformedValue:(id)string { + if (string == nil || [string length] == 0) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + NSImage* image = rb.GetNSImageNamed(IDR_WARNING); + DCHECK(image); + return image; + } + return nil; +} + +@end + +// An NSValueTransformer subclass for use in validation of phone number +// fields. Transforms an invalid phone number string into a warning image. +// This data transformer is used in the credit card sheet for invalid phone and +// fax numbers. +@interface InvalidPhoneTransformer : NSValueTransformer { +} +@end + +@implementation InvalidPhoneTransformer ++ (Class)transformedValueClass { + return [NSImage class]; +} - [self installChildViews]; ++ (BOOL)allowsReverseTransformation { + return NO; +} + +- (id)transformedValue:(id)string { + if (string != nil && [string length] != 0) { + // TODO(dhollowa): Using SetInfo() call to validate phone number. Should + // have explicit validation method. More robust validation is needed as + // well eventually. + AutoFillProfile profile(string16(), 0); + profile.SetInfo(AutoFillType(PHONE_HOME_WHOLE_NUMBER), + base::SysNSStringToUTF16(string)); + if (profile.GetFieldText(AutoFillType(PHONE_HOME_WHOLE_NUMBER)).empty()) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + NSImage* image = rb.GetNSImageNamed(IDR_WARNING); + DCHECK(image); + return image; + } + } + return nil; } @end diff --git a/chrome/browser/autofill/autofill_dialog_controller_mac_unittest.mm b/chrome/browser/autofill/autofill_dialog_controller_mac_unittest.mm index b82c377..07c7af0 100644 --- a/chrome/browser/autofill/autofill_dialog_controller_mac_unittest.mm +++ b/chrome/browser/autofill/autofill_dialog_controller_mac_unittest.mm @@ -4,9 +4,9 @@ #include "base/ref_counted.h" #import "chrome/browser/autofill/autofill_address_model_mac.h" -#import "chrome/browser/autofill/autofill_address_view_controller_mac.h" +#import "chrome/browser/autofill/autofill_address_sheet_controller_mac.h" #import "chrome/browser/autofill/autofill_credit_card_model_mac.h" -#import "chrome/browser/autofill/autofill_credit_card_view_controller_mac.h" +#import "chrome/browser/autofill/autofill_credit_card_sheet_controller_mac.h" #import "chrome/browser/autofill/autofill_dialog_controller_mac.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/autofill/personal_data_manager.h" @@ -240,11 +240,11 @@ TEST_F(AutoFillDialogControllerTest, NoEditsGiveBackOriginalCreditCard) { TEST_F(AutoFillDialogControllerTest, AutoFillDataMutation) { AutoFillProfile profile(ASCIIToUTF16("Home"), 17); - profile.SetInfo(AutoFillType(NAME_FIRST), ASCIIToUTF16("David")); + profile.SetInfo(AutoFillType(NAME_FIRST), ASCIIToUTF16("John")); profile.SetInfo(AutoFillType(NAME_MIDDLE), ASCIIToUTF16("C")); - profile.SetInfo(AutoFillType(NAME_LAST), ASCIIToUTF16("Holloway")); + profile.SetInfo(AutoFillType(NAME_LAST), ASCIIToUTF16("Smith")); profile.SetInfo(AutoFillType(EMAIL_ADDRESS), - ASCIIToUTF16("dhollowa@chromium.org")); + ASCIIToUTF16("john@chromium.org")); profile.SetInfo(AutoFillType(COMPANY_NAME), ASCIIToUTF16("Google Inc.")); profile.SetInfo(AutoFillType(ADDRESS_HOME_LINE1), ASCIIToUTF16("1122 Mountain View Road")); @@ -261,14 +261,15 @@ TEST_F(AutoFillDialogControllerTest, AutoFillDataMutation) { profiles().push_back(&profile); LoadDialog(); + [controller_ selectAddressAtIndex:0]; + [controller_ editSelection:nil]; - AutoFillAddressModel* am = [[[controller_ addressFormViewControllers] - objectAtIndex:0] addressModel]; + AutoFillAddressSheetController* sheet = [controller_ addressSheetController]; + ASSERT_TRUE(sheet != nil); + AutoFillAddressModel* am = [sheet addressModel]; EXPECT_TRUE([[am label] isEqualToString:@"Home"]); - EXPECT_TRUE([[am firstName] isEqualToString:@"David"]); - EXPECT_TRUE([[am middleName] isEqualToString:@"C"]); - EXPECT_TRUE([[am lastName] isEqualToString:@"Holloway"]); - EXPECT_TRUE([[am email] isEqualToString:@"dhollowa@chromium.org"]); + EXPECT_TRUE([[am fullName] isEqualToString:@"John C Smith"]); + EXPECT_TRUE([[am email] isEqualToString:@"john@chromium.org"]); EXPECT_TRUE([[am companyName] isEqualToString:@"Google Inc."]); EXPECT_TRUE([[am addressLine1] isEqualToString:@"1122 Mountain View Road"]); EXPECT_TRUE([[am addressLine2] isEqualToString:@"Suite #1"]); @@ -278,6 +279,7 @@ TEST_F(AutoFillDialogControllerTest, AutoFillDataMutation) { EXPECT_TRUE([[am phoneWholeNumber] isEqualToString:@"014155552258"]); EXPECT_TRUE([[am faxWholeNumber] isEqualToString:@"024087172258"]); + [sheet save:nil]; [controller_ save:nil]; ASSERT_TRUE(observer_.hit_); @@ -300,9 +302,13 @@ TEST_F(AutoFillDialogControllerTest, CreditCardDataMutation) { credit_cards().push_back(&credit_card); LoadDialog(); + [controller_ selectCreditCardAtIndex:0]; + [controller_ editSelection:nil]; - AutoFillCreditCardModel* cm = [[[controller_ creditCardFormViewControllers] - objectAtIndex:0] creditCardModel]; + AutoFillCreditCardSheetController* sheet = + [controller_ creditCardSheetController]; + ASSERT_TRUE(sheet != nil); + AutoFillCreditCardModel* cm = [sheet creditCardModel]; EXPECT_TRUE([[cm label] isEqualToString:@"myCC"]); EXPECT_TRUE([[cm nameOnCard] isEqualToString:@"DCH"]); EXPECT_TRUE([[cm creditCardNumber] isEqualToString:@"1234 5678 9101 1121"]); @@ -310,6 +316,7 @@ TEST_F(AutoFillDialogControllerTest, CreditCardDataMutation) { EXPECT_TRUE([[cm expirationYear] isEqualToString:@"2012"]); EXPECT_TRUE([[cm cvcCode] isEqualToString:@"222"]); + [sheet save:nil]; [controller_ save:nil]; ASSERT_TRUE(observer_.hit_); @@ -373,6 +380,9 @@ TEST_F(AutoFillDialogControllerTest, AddNewProfile) { profiles().push_back(&profile); LoadDialog(); [controller_ addNewAddress:nil]; + AutoFillAddressSheetController* sheet = [controller_ addressSheetController]; + ASSERT_TRUE(sheet != nil); + [sheet save:nil]; [controller_ save:nil]; // Should hit our observer. @@ -393,6 +403,10 @@ TEST_F(AutoFillDialogControllerTest, AddNewCreditCard) { credit_cards().push_back(&credit_card); LoadDialog(); [controller_ addNewCreditCard:nil]; + AutoFillCreditCardSheetController* sheet = + [controller_ creditCardSheetController]; + ASSERT_TRUE(sheet != nil); + [sheet save:nil]; [controller_ save:nil]; // Should hit our observer. @@ -412,10 +426,8 @@ TEST_F(AutoFillDialogControllerTest, DeleteProfile) { profile.SetInfo(AutoFillType(NAME_FIRST), ASCIIToUTF16("Joe")); profiles().push_back(&profile); LoadDialog(); - EXPECT_EQ([[[controller_ addressFormViewControllers] lastObject] - retainCount], 1UL); - [controller_ deleteAddress:[[controller_ addressFormViewControllers] - lastObject]]; + [controller_ selectAddressAtIndex:0]; + [controller_ deleteSelection:nil]; [controller_ save:nil]; // Should hit our observer. @@ -431,10 +443,8 @@ TEST_F(AutoFillDialogControllerTest, DeleteCreditCard) { credit_card.SetInfo(AutoFillType(CREDIT_CARD_NAME), ASCIIToUTF16("Joe")); credit_cards().push_back(&credit_card); LoadDialog(); - EXPECT_EQ([[[controller_ creditCardFormViewControllers] lastObject] - retainCount], 1UL); - [controller_ deleteCreditCard:[[controller_ creditCardFormViewControllers] - lastObject]]; + [controller_ selectCreditCardAtIndex:0]; + [controller_ deleteSelection:nil]; [controller_ save:nil]; // Should hit our observer. @@ -453,8 +463,8 @@ TEST_F(AutoFillDialogControllerTest, TwoProfilesDeleteOne) { profile2.SetInfo(AutoFillType(NAME_FIRST), ASCIIToUTF16("Bob")); profiles().push_back(&profile2); LoadDialog(); - [controller_ deleteAddress:[[controller_ addressFormViewControllers] - lastObject]]; + [controller_ selectAddressAtIndex:1]; + [controller_ deleteSelection:nil]; [controller_ save:nil]; // Should hit our observer. @@ -477,8 +487,8 @@ TEST_F(AutoFillDialogControllerTest, TwoCreditCardsDeleteOne) { credit_card2.SetInfo(AutoFillType(CREDIT_CARD_NAME), ASCIIToUTF16("Bob")); credit_cards().push_back(&credit_card2); LoadDialog(); - [controller_ deleteCreditCard:[[controller_ creditCardFormViewControllers] - lastObject]]; + [controller_ selectCreditCardAtIndex:1]; + [controller_ deleteSelection:nil]; [controller_ save:nil]; // Should hit our observer. diff --git a/chrome/browser/autofill/contact_info.cc b/chrome/browser/autofill/contact_info.cc index 0355fd0..3c77cda 100644 --- a/chrome/browser/autofill/contact_info.cc +++ b/chrome/browser/autofill/contact_info.cc @@ -112,7 +112,27 @@ void ContactInfo::SetInfo(const AutoFillType& type, const string16& value) { SetLast(value); else if (field_type == NAME_SUFFIX) set_suffix(value); - else if (field_type == EMAIL_ADDRESS) + else if (field_type == NAME_FULL) { + // TODO(dhollowa): This needs formal spec on how names are split from + // unstructured string to structured fields. + std::vector<string16> values; + SplitStringAlongWhitespace(value, &values); + if (values.size() == 1) { + SetInfo(AutoFillType(NAME_FIRST), values[0]); + } else if (values.size() == 2) { + SetInfo(AutoFillType(NAME_FIRST), values[0]); + SetInfo(AutoFillType(NAME_LAST), values[1]); + } else if (values.size() == 3) { + SetInfo(AutoFillType(NAME_FIRST), values[0]); + SetInfo(AutoFillType(NAME_MIDDLE), values[1]); + SetInfo(AutoFillType(NAME_LAST), values[2]); + } else if (values.size() >= 4) { + SetInfo(AutoFillType(NAME_FIRST), values[0]); + SetInfo(AutoFillType(NAME_MIDDLE), values[1]); + SetInfo(AutoFillType(NAME_LAST), values[2]); + SetInfo(AutoFillType(NAME_SUFFIX), values[3]); + } + } else if (field_type == EMAIL_ADDRESS) email_ = value; else if (field_type == COMPANY_NAME) company_name_ = value; |