<commit_before>// Copyright (c) 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_section_container.h"

#include <algorithm>

#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
#include "chrome/browser/ui/chrome_style.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_section_view.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_suggestion_container.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
#import "chrome/browser/ui/cocoa/autofill/layout_view.h"
#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
#import "chrome/browser/ui/cocoa/image_button_cell.h"
#import "chrome/browser/ui/cocoa/menu_button.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "grit/theme_resources.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/resource/resource_bundle.h"

namespace {

// Constants used for layouting controls. These variables are copied from
// "ui/views/layout/layout_constants.h".

// Horizontal spacing between controls that are logically related.
const int kRelatedControlHorizontalSpacing = 8;

// Vertical spacing between controls that are logically related.
const int kRelatedControlVerticalSpacing = 8;

// TODO(estade): pull out these constants, and figure out better values
// for them. Note: These are duplicated from Views code.

// Fixed width for the details section.
const int kDetailsWidth = 440;

// Top/bottom inset for contents of a detail section.
const size_t kDetailSectionInset = 10;

// Vertical padding around the section header.
const CGFloat kVerticalHeaderPadding = 6;

// If the Autofill data comes from a credit card, make sure to overwrite the
// CC comboboxes (even if they already have something in them). If the
// Autofill data comes from an AutofillProfile, leave the comboboxes alone.
// TODO(groby): This kind of logic should _really_ live on the delegate.
bool ShouldOverwriteComboboxes(autofill::DialogSection section,
                               autofill::ServerFieldType type) {
  if (autofill::AutofillType(type).group() != autofill::CREDIT_CARD) {
    return false;
  }

  if (section == autofill::SECTION_CC) {
    return true;
  }

  return section == autofill::SECTION_CC_BILLING;
}

bool CompareInputRows(const autofill::DetailInput* input1,
                      const autofill::DetailInput* input2) {
  // Row ID -1 is sorted to the end of rows.
  if (input2->row_id == -1)
    return false;
  return input2->row_id < input1->row_id;
}

}

@interface AutofillSectionContainer ()

// A text field has been edited or activated - inform the delegate that it's
// time to show a suggestion popup & possibly reset the validity of the input.
- (void)textfieldEditedOrActivated:(NSControl<AutofillInputField>*)field
                            edited:(BOOL)edited;

// Convenience method to retrieve a field type via the control's tag.
- (autofill::ServerFieldType)fieldTypeForControl:(NSControl*)control;

// Find the DetailInput* associated with a field type.
- (const autofill::DetailInput*)detailInputForType:
    (autofill::ServerFieldType)type;

// Takes an NSArray of controls and builds a FieldValueMap from them.
// Translates between Cocoa code and delegate, essentially.
// All controls must inherit from NSControl and conform to AutofillInputView.
- (void)fillDetailOutputs:(autofill::FieldValueMap*)outputs
             fromControls:(NSArray*)controls;

// Updates input fields based on delegate status. If |shouldClobber| is YES,
// will clobber existing data and reset fields to the initial values.
- (void)updateAndClobber:(BOOL)shouldClobber;

// Return YES if this is a section that contains CC info. (And, more
// importantly, a potential CVV field)
- (BOOL)isCreditCardSection;

// Create properly styled label for section. Autoreleased.
- (NSTextField*)makeDetailSectionLabel:(NSString*)labelText;

// Create a button offering input suggestions.
- (MenuButton*)makeSuggestionButton;

// Create a view with all inputs requested by |delegate_|. Autoreleased.
- (LayoutView*)makeInputControls;

// Refresh all field icons based on |delegate_| status.
- (void)updateFieldIcons;

// Refresh the enabled/disabled state of all input fields.
- (void)updateEditability;

@end

@implementation AutofillSectionContainer

@synthesize section = section_;
@synthesize validationDelegate = validationDelegate_;

- (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate
            forSection:(autofill::DialogSection)section {
  if (self = [super init]) {
    section_ = section;
    delegate_ = delegate;
  }
  return self;
}

- (void)getInputs:(autofill::FieldValueMap*)output {
  [self fillDetailOutputs:output fromControls:[inputs_ subviews]];
}

// Note: This corresponds to Views' "UpdateDetailsGroupState".
- (void)modelChanged {
  ui::MenuModel* suggestionModel = delegate_->MenuModelForSection(section_);
  menuController_.reset([[MenuController alloc] initWithModel:suggestionModel
                                       useWithPopUpButtonCell:YES]);
  NSMenu* menu = [menuController_ menu];

  const BOOL hasSuggestions = [menu numberOfItems] > 0;
  [suggestButton_ setHidden:!hasSuggestions];

  [suggestButton_ setAttachedMenu:menu];

  [self updateSuggestionState];

  // TODO(groby): "Save in Chrome" handling. http://crbug.com/306203.

  if (![[self view] isHidden])
    [self validateFor:autofill::VALIDATE_EDIT];

  // Always request re-layout on state change.
  id delegate = [[view_ window] windowController];
  if ([delegate respondsToSelector:@selector(requestRelayout)])
    [delegate performSelector:@selector(requestRelayout)];
}

- (void)loadView {
  // Keep a list of weak pointers to DetailInputs.
  const autofill::DetailInputs& inputs =
      delegate_->RequestedFieldsForSection(section_);
  for (size_t i = 0; i < inputs.size(); ++i) {
    detailInputs_.push_back(&(inputs[i]));
  }

  inputs_.reset([[self makeInputControls] retain]);
  string16 labelText = delegate_->LabelForSection(section_);
  label_.reset(
      [[self makeDetailSectionLabel:base::SysUTF16ToNSString(labelText)]
          retain]);

  suggestButton_.reset([[self makeSuggestionButton] retain]);
  suggestContainer_.reset([[AutofillSuggestionContainer alloc] init]);

  view_.reset([[AutofillSectionView alloc] initWithFrame:NSZeroRect]);
  [self setView:view_];
  [[self view] setSubviews:
      @[label_, inputs_, [suggestContainer_ view], suggestButton_]];

  if ([self isCreditCardSection]) {
    // Credit card sections *MUST* have a CREDIT_CARD_VERIFICATION_CODE input.
    DCHECK([self detailInputForType:autofill::CREDIT_CARD_VERIFICATION_CODE]);
    [[suggestContainer_ inputField] setTag:
        autofill::CREDIT_CARD_VERIFICATION_CODE];
    [[suggestContainer_ inputField] setInputDelegate:self];
  }

  [self modelChanged];
}

- (NSSize)preferredSize {
  if ([view_ isHidden])
    return NSZeroSize;

  NSSize labelSize = [label_ frame].size;  // Assumes sizeToFit was called.
  CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth];
  if (showSuggestions_)
    controlHeight = [suggestContainer_ preferredSize].height;

  return NSMakeSize(kDetailsWidth + 2 * chrome_style::kHorizontalPadding,
                    labelSize.height + kVerticalHeaderPadding +
                        controlHeight + 2 * kDetailSectionInset);
}

- (void)performLayout {
  if ([view_ isHidden])
    return;

  NSSize buttonSize = [suggestButton_ frame].size;  // Assume sizeToFit.
  NSSize labelSize = [label_ frame].size;  // Assumes sizeToFit was called.
  CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth];
  if (showSuggestions_)
    controlHeight = [suggestContainer_ preferredSize].height;

  NSRect viewFrame = NSZeroRect;
  viewFrame.size = [self preferredSize];

  NSRect contentFrame = NSInsetRect(viewFrame,
                                    chrome_style::kHorizontalPadding,
                                    kDetailSectionInset);
  NSRect controlFrame, labelFrame, buttonFrame;

  // Label is top left, suggestion button is top right, controls are below that.
  NSDivideRect(contentFrame, &labelFrame, &controlFrame,
               kVerticalHeaderPadding + labelSize.height, NSMaxYEdge);
  NSDivideRect(labelFrame, &buttonFrame, &labelFrame,
               buttonSize.width, NSMaxXEdge);

  labelFrame = NSOffsetRect(labelFrame, 0, kVerticalHeaderPadding);
  labelFrame.size = labelSize;

  buttonFrame = NSOffsetRect(buttonFrame, 0, 5);
  buttonFrame.size = buttonSize;

  if (showSuggestions_) {
    [[suggestContainer_ view] setFrame:controlFrame];
    [suggestContainer_ performLayout];
  } else {
    [inputs_ setFrame:controlFrame];
  }
  [label_ setFrame:labelFrame];
  [suggestButton_ setFrame:buttonFrame];
  [inputs_ setHidden:showSuggestions_];
  [[suggestContainer_ view] setHidden:!showSuggestions_];
  [view_ setFrameSize:viewFrame.size];
}

- (KeyEventHandled)keyEvent:(NSEvent*)event forInput:(id)sender {
  content::NativeWebKeyboardEvent webEvent(event);

  // Only handle keyDown, to handle key repeats without duplicates.
  if (webEvent.type != content::NativeWebKeyboardEvent::KeyDown)
    return kKeyEventNotHandled;<commit_msg>[rAC, OSX] Fix key handling for autofill popup.<commit_after>// Copyright (c) 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_section_container.h"

#include <algorithm>

#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
#include "chrome/browser/ui/chrome_style.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_section_view.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_suggestion_container.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
#import "chrome/browser/ui/cocoa/autofill/layout_view.h"
#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
#import "chrome/browser/ui/cocoa/image_button_cell.h"
#import "chrome/browser/ui/cocoa/menu_button.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "grit/theme_resources.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/resource/resource_bundle.h"

namespace {

// Constants used for layouting controls. These variables are copied from
// "ui/views/layout/layout_constants.h".

// Horizontal spacing between controls that are logically related.
const int kRelatedControlHorizontalSpacing = 8;

// Vertical spacing between controls that are logically related.
const int kRelatedControlVerticalSpacing = 8;

// TODO(estade): pull out these constants, and figure out better values
// for them. Note: These are duplicated from Views code.

// Fixed width for the details section.
const int kDetailsWidth = 440;

// Top/bottom inset for contents of a detail section.
const size_t kDetailSectionInset = 10;

// Vertical padding around the section header.
const CGFloat kVerticalHeaderPadding = 6;

// If the Autofill data comes from a credit card, make sure to overwrite the
// CC comboboxes (even if they already have something in them). If the
// Autofill data comes from an AutofillProfile, leave the comboboxes alone.
// TODO(groby): This kind of logic should _really_ live on the delegate.
bool ShouldOverwriteComboboxes(autofill::DialogSection section,
                               autofill::ServerFieldType type) {
  if (autofill::AutofillType(type).group() != autofill::CREDIT_CARD) {
    return false;
  }

  if (section == autofill::SECTION_CC) {
    return true;
  }

  return section == autofill::SECTION_CC_BILLING;
}

bool CompareInputRows(const autofill::DetailInput* input1,
                      const autofill::DetailInput* input2) {
  // Row ID -1 is sorted to the end of rows.
  if (input2->row_id == -1)
    return false;
  return input2->row_id < input1->row_id;
}

}

@interface AutofillSectionContainer ()

// A text field has been edited or activated - inform the delegate that it's
// time to show a suggestion popup & possibly reset the validity of the input.
- (void)textfieldEditedOrActivated:(NSControl<AutofillInputField>*)field
                            edited:(BOOL)edited;

// Convenience method to retrieve a field type via the control's tag.
- (autofill::ServerFieldType)fieldTypeForControl:(NSControl*)control;

// Find the DetailInput* associated with a field type.
- (const autofill::DetailInput*)detailInputForType:
    (autofill::ServerFieldType)type;

// Takes an NSArray of controls and builds a FieldValueMap from them.
// Translates between Cocoa code and delegate, essentially.
// All controls must inherit from NSControl and conform to AutofillInputView.
- (void)fillDetailOutputs:(autofill::FieldValueMap*)outputs
             fromControls:(NSArray*)controls;

// Updates input fields based on delegate status. If |shouldClobber| is YES,
// will clobber existing data and reset fields to the initial values.
- (void)updateAndClobber:(BOOL)shouldClobber;

// Return YES if this is a section that contains CC info. (And, more
// importantly, a potential CVV field)
- (BOOL)isCreditCardSection;

// Create properly styled label for section. Autoreleased.
- (NSTextField*)makeDetailSectionLabel:(NSString*)labelText;

// Create a button offering input suggestions.
- (MenuButton*)makeSuggestionButton;

// Create a view with all inputs requested by |delegate_|. Autoreleased.
- (LayoutView*)makeInputControls;

// Refresh all field icons based on |delegate_| status.
- (void)updateFieldIcons;

// Refresh the enabled/disabled state of all input fields.
- (void)updateEditability;

@end

@implementation AutofillSectionContainer

@synthesize section = section_;
@synthesize validationDelegate = validationDelegate_;

- (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate
            forSection:(autofill::DialogSection)section {
  if (self = [super init]) {
    section_ = section;
    delegate_ = delegate;
  }
  return self;
}

- (void)getInputs:(autofill::FieldValueMap*)output {
  [self fillDetailOutputs:output fromControls:[inputs_ subviews]];
}

// Note: This corresponds to Views' "UpdateDetailsGroupState".
- (void)modelChanged {
  ui::MenuModel* suggestionModel = delegate_->MenuModelForSection(section_);
  menuController_.reset([[MenuController alloc] initWithModel:suggestionModel
                                       useWithPopUpButtonCell:YES]);
  NSMenu* menu = [menuController_ menu];

  const BOOL hasSuggestions = [menu numberOfItems] > 0;
  [suggestButton_ setHidden:!hasSuggestions];

  [suggestButton_ setAttachedMenu:menu];

  [self updateSuggestionState];

  // TODO(groby): "Save in Chrome" handling. http://crbug.com/306203.

  if (![[self view] isHidden])
    [self validateFor:autofill::VALIDATE_EDIT];

  // Always request re-layout on state change.
  id delegate = [[view_ window] windowController];
  if ([delegate respondsToSelector:@selector(requestRelayout)])
    [delegate performSelector:@selector(requestRelayout)];
}

- (void)loadView {
  // Keep a list of weak pointers to DetailInputs.
  const autofill::DetailInputs& inputs =
      delegate_->RequestedFieldsForSection(section_);
  for (size_t i = 0; i < inputs.size(); ++i) {
    detailInputs_.push_back(&(inputs[i]));
  }

  inputs_.reset([[self makeInputControls] retain]);
  string16 labelText = delegate_->LabelForSection(section_);
  label_.reset(
      [[self makeDetailSectionLabel:base::SysUTF16ToNSString(labelText)]
          retain]);

  suggestButton_.reset([[self makeSuggestionButton] retain]);
  suggestContainer_.reset([[AutofillSuggestionContainer alloc] init]);

  view_.reset([[AutofillSectionView alloc] initWithFrame:NSZeroRect]);
  [self setView:view_];
  [[self view] setSubviews:
      @[label_, inputs_, [suggestContainer_ view], suggestButton_]];

  if ([self isCreditCardSection]) {
    // Credit card sections *MUST* have a CREDIT_CARD_VERIFICATION_CODE input.
    DCHECK([self detailInputForType:autofill::CREDIT_CARD_VERIFICATION_CODE]);
    [[suggestContainer_ inputField] setTag:
        autofill::CREDIT_CARD_VERIFICATION_CODE];
    [[suggestContainer_ inputField] setInputDelegate:self];
  }

  [self modelChanged];
}

- (NSSize)preferredSize {
  if ([view_ isHidden])
    return NSZeroSize;

  NSSize labelSize = [label_ frame].size;  // Assumes sizeToFit was called.
  CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth];
  if (showSuggestions_)
    controlHeight = [suggestContainer_ preferredSize].height;

  return NSMakeSize(kDetailsWidth + 2 * chrome_style::kHorizontalPadding,
                    labelSize.height + kVerticalHeaderPadding +
                        controlHeight + 2 * kDetailSectionInset);
}

- (void)performLayout {
  if ([view_ isHidden])
    return;

  NSSize buttonSize = [suggestButton_ frame].size;  // Assume sizeToFit.
  NSSize labelSize = [label_ frame].size;  // Assumes sizeToFit was called.
  CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth];
  if (showSuggestions_)
    controlHeight = [suggestContainer_ preferredSize].height;

  NSRect viewFrame = NSZeroRect;
  viewFrame.size = [self preferredSize];

  NSRect contentFrame = NSInsetRect(viewFrame,
                                    chrome_style::kHorizontalPadding,
                                    kDetailSectionInset);
  NSRect controlFrame, labelFrame, buttonFrame;

  // Label is top left, suggestion button is top right, controls are below that.
  NSDivideRect(contentFrame, &labelFrame, &controlFrame,
               kVerticalHeaderPadding + labelSize.height, NSMaxYEdge);
  NSDivideRect(labelFrame, &buttonFrame, &labelFrame,
               buttonSize.width, NSMaxXEdge);

  labelFrame = NSOffsetRect(labelFrame, 0, kVerticalHeaderPadding);
  labelFrame.size = labelSize;

  buttonFrame = NSOffsetRect(buttonFrame, 0, 5);
  buttonFrame.size = buttonSize;

  if (showSuggestions_) {
    [[suggestContainer_ view] setFrame:controlFrame];
    [suggestContainer_ performLayout];
  } else {
    [inputs_ setFrame:controlFrame];
  }
  [label_ setFrame:labelFrame];
  [suggestButton_ setFrame:buttonFrame];
  [inputs_ setHidden:showSuggestions_];
  [[suggestContainer_ view] setHidden:!showSuggestions_];
  [view_ setFrameSize:viewFrame.size];
}

- (KeyEventHandled)keyEvent:(NSEvent*)event forInput:(id)sender {
  content::NativeWebKeyboardEvent webEvent(event);

  // Only handle keyDown, to handle key repeats without duplicates.
  if (webEvent.type != content::NativeWebKeyboardEvent::RawKeyDown)
    return kKeyEventNotHandled;
{'fraction_non_alphanumeric': 0.10246091936232782, 'fraction_numerical': 0.004127328070990042, 'mean_word_length': 4.467983074753174, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '136896', 'n_tokens_mistral': 6051, 'n_tokens_neox': 5729, 'n_words': 1456}