普通文本  |  429行  |  13.75 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/window/dialog_client_view.h"

#include <algorithm>

#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/blue_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"

namespace views {

namespace {

// The group used by the buttons.  This name is chosen voluntarily big not to
// conflict with other groups that could be in the dialog content.
const int kButtonGroup = 6666;

#if defined(OS_WIN) || defined(OS_CHROMEOS)
const bool kIsOkButtonOnLeftSide = true;
#else
const bool kIsOkButtonOnLeftSide = false;
#endif

// Returns true if the given view should be shown (i.e. exists and is
// visible).
bool ShouldShow(View* view) {
  return view && view->visible();
}

// Do the layout for a button.
void LayoutButton(LabelButton* button, gfx::Rect* row_bounds) {
  if (!button)
    return;

  const gfx::Size size = button->GetPreferredSize();
  row_bounds->set_width(row_bounds->width() - size.width());
  button->SetBounds(row_bounds->right(), row_bounds->y(),
                    size.width(), row_bounds->height());
  row_bounds->set_width(row_bounds->width() - kRelatedButtonHSpacing);
}

}  // namespace

///////////////////////////////////////////////////////////////////////////////
// DialogClientView, public:

DialogClientView::DialogClientView(Widget* owner, View* contents_view)
    : ClientView(owner, contents_view),
      ok_button_(NULL),
      cancel_button_(NULL),
      default_button_(NULL),
      focus_manager_(NULL),
      extra_view_(NULL),
      footnote_view_(NULL),
      notified_delegate_(false) {
}

DialogClientView::~DialogClientView() {
}

void DialogClientView::AcceptWindow() {
  // Only notify the delegate once. See |notified_delegate_|'s comment.
  if (!notified_delegate_ && GetDialogDelegate()->Accept(false)) {
    notified_delegate_ = true;
    Close();
  }
}

void DialogClientView::CancelWindow() {
  // Only notify the delegate once. See |notified_delegate_|'s comment.
  if (!notified_delegate_ && GetDialogDelegate()->Cancel()) {
    notified_delegate_ = true;
    Close();
  }
}

void DialogClientView::UpdateDialogButtons() {
  const int buttons = GetDialogDelegate()->GetDialogButtons();
  ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE);
  if (default_button_)
    default_button_->SetIsDefault(false);
  default_button_ = NULL;

  if (buttons & ui::DIALOG_BUTTON_OK) {
    if (!ok_button_) {
      ok_button_ = CreateDialogButton(ui::DIALOG_BUTTON_OK);
      if (!(buttons & ui::DIALOG_BUTTON_CANCEL))
        ok_button_->AddAccelerator(escape);
      AddChildView(ok_button_);
    }

    UpdateButton(ok_button_, ui::DIALOG_BUTTON_OK);
  } else if (ok_button_) {
    delete ok_button_;
    ok_button_ = NULL;
  }

  if (buttons & ui::DIALOG_BUTTON_CANCEL) {
    if (!cancel_button_) {
      cancel_button_ = CreateDialogButton(ui::DIALOG_BUTTON_CANCEL);
      cancel_button_->AddAccelerator(escape);
      AddChildView(cancel_button_);
    }

    UpdateButton(cancel_button_, ui::DIALOG_BUTTON_CANCEL);
  } else if (cancel_button_) {
    delete cancel_button_;
    cancel_button_ = NULL;
  }

  // Use the escape key to close the window if there are no dialog buttons.
  if (!has_dialog_buttons())
    AddAccelerator(escape);
  else
    ResetAccelerators();
}

///////////////////////////////////////////////////////////////////////////////
// DialogClientView, ClientView overrides:

bool DialogClientView::CanClose() {
  if (notified_delegate_)
    return true;

  // The dialog is closing but no Accept or Cancel action has been performed
  // before: it's a Close action.
  if (GetDialogDelegate()->Close()) {
    notified_delegate_ = true;
    GetDialogDelegate()->OnClosed();
    return true;
  }
  return false;
}

DialogClientView* DialogClientView::AsDialogClientView() {
  return this;
}

const DialogClientView* DialogClientView::AsDialogClientView() const {
  return this;
}

void DialogClientView::OnWillChangeFocus(View* focused_before,
                                         View* focused_now) {
  // Make the newly focused button default or restore the dialog's default.
  const int default_button = GetDialogDelegate()->GetDefaultDialogButton();
  LabelButton* new_default_button = NULL;
  if (focused_now &&
      !strcmp(focused_now->GetClassName(), LabelButton::kViewClassName)) {
    new_default_button = static_cast<LabelButton*>(focused_now);
  } else if (default_button == ui::DIALOG_BUTTON_OK && ok_button_) {
    new_default_button = ok_button_;
  } else if (default_button == ui::DIALOG_BUTTON_CANCEL && cancel_button_) {
    new_default_button = cancel_button_;
  }

  if (default_button_ && default_button_ != new_default_button)
    default_button_->SetIsDefault(false);
  default_button_ = new_default_button;
  if (default_button_ && !default_button_->is_default())
    default_button_->SetIsDefault(true);
}

void DialogClientView::OnDidChangeFocus(View* focused_before,
                                        View* focused_now) {
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, View overrides:

gfx::Size DialogClientView::GetPreferredSize() const {
  // Initialize the size to fit the buttons and extra view row.
  gfx::Size size(
      (ok_button_ ? ok_button_->GetPreferredSize().width() : 0) +
      (cancel_button_ ? cancel_button_->GetPreferredSize().width() : 0) +
      (cancel_button_ && ok_button_ ? kRelatedButtonHSpacing : 0) +
      (ShouldShow(extra_view_) ? extra_view_->GetPreferredSize().width() : 0) +
      (ShouldShow(extra_view_) && has_dialog_buttons() ?
           kRelatedButtonHSpacing : 0),
      0);

  int buttons_height = GetButtonsAndExtraViewRowHeight();
  if (buttons_height != 0) {
    size.Enlarge(0, buttons_height + kRelatedControlVerticalSpacing);
    // Inset the buttons and extra view.
    const gfx::Insets insets = GetButtonRowInsets();
    size.Enlarge(insets.width(), insets.height());
  }

  // Increase the size as needed to fit the contents view.
  // NOTE: The contents view is not inset on the top or side client view edges.
  gfx::Size contents_size = contents_view()->GetPreferredSize();
  size.Enlarge(0, contents_size.height());
  size.set_width(std::max(size.width(), contents_size.width()));

  // Increase the size as needed to fit the footnote view.
  if (ShouldShow(footnote_view_)) {
    gfx::Size footnote_size = footnote_view_->GetPreferredSize();
    if (!footnote_size.IsEmpty())
      size.set_width(std::max(size.width(), footnote_size.width()));

    int footnote_height = footnote_view_->GetHeightForWidth(size.width());
    size.Enlarge(0, footnote_height);
  }

  return size;
}

void DialogClientView::Layout() {
  gfx::Rect bounds = GetContentsBounds();

  // Layout the footnote view.
  if (ShouldShow(footnote_view_)) {
    const int height = footnote_view_->GetHeightForWidth(bounds.width());
    footnote_view_->SetBounds(bounds.x(), bounds.bottom() - height,
                              bounds.width(), height);
    if (height != 0)
      bounds.Inset(0, 0, 0, height);
  }

  // Layout the row containing the buttons and the extra view.
  if (has_dialog_buttons() || ShouldShow(extra_view_)) {
    bounds.Inset(GetButtonRowInsets());
    const int height = GetButtonsAndExtraViewRowHeight();
    gfx::Rect row_bounds(bounds.x(), bounds.bottom() - height,
                         bounds.width(), height);
    if (kIsOkButtonOnLeftSide) {
      LayoutButton(cancel_button_, &row_bounds);
      LayoutButton(ok_button_, &row_bounds);
    } else {
      LayoutButton(ok_button_, &row_bounds);
      LayoutButton(cancel_button_, &row_bounds);
    }
    if (extra_view_) {
      row_bounds.set_width(std::min(row_bounds.width(),
          extra_view_->GetPreferredSize().width()));
      extra_view_->SetBoundsRect(row_bounds);
    }

    if (height > 0)
      bounds.Inset(0, 0, 0, height + kRelatedControlVerticalSpacing);
  }

  // Layout the contents view to the top and side edges of the contents bounds.
  // NOTE: The local insets do not apply to the contents view sides or top.
  const gfx::Rect contents_bounds = GetContentsBounds();
  contents_view()->SetBounds(contents_bounds.x(), contents_bounds.y(),
      contents_bounds.width(), bounds.bottom() - contents_bounds.y());
}

bool DialogClientView::AcceleratorPressed(const ui::Accelerator& accelerator) {
  DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
  Close();
  return true;
}

void DialogClientView::ViewHierarchyChanged(
    const ViewHierarchyChangedDetails& details) {
  ClientView::ViewHierarchyChanged(details);
  if (details.is_add && details.child == this) {
    focus_manager_ = GetFocusManager();
    if (focus_manager_)
      GetFocusManager()->AddFocusChangeListener(this);

    UpdateDialogButtons();
    CreateExtraView();
    CreateFootnoteView();
  } else if (!details.is_add && details.child == this) {
    if (focus_manager_)
      focus_manager_->RemoveFocusChangeListener(this);
    focus_manager_ = NULL;
  } else if (!details.is_add) {
    if (details.child == default_button_)
      default_button_ = NULL;
    if (details.child == ok_button_)
      ok_button_ = NULL;
    if (details.child == cancel_button_)
      cancel_button_ = NULL;
  }
}

void DialogClientView::NativeViewHierarchyChanged() {
  FocusManager* focus_manager = GetFocusManager();
  if (focus_manager_ != focus_manager) {
    if (focus_manager_)
      focus_manager_->RemoveFocusChangeListener(this);
    focus_manager_ = focus_manager;
    if (focus_manager_)
      focus_manager_->AddFocusChangeListener(this);
  }
}

void DialogClientView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
  // The old dialog style needs an explicit background color, while the new
  // dialog style simply inherits the bubble's frame view color.
  const DialogDelegate* dialog = GetDialogDelegate();

  if (dialog && !dialog->UseNewStyleForThisDialog()) {
    set_background(views::Background::CreateSolidBackground(GetNativeTheme()->
        GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
  }
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, ButtonListener implementation:

void DialogClientView::ButtonPressed(Button* sender, const ui::Event& event) {
  // Check for a valid delegate to avoid handling events after destruction.
  if (!GetDialogDelegate())
    return;

  if (sender == ok_button_)
    AcceptWindow();
  else if (sender == cancel_button_)
    CancelWindow();
  else
    NOTREACHED();
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, protected:

DialogClientView::DialogClientView(View* contents_view)
    : ClientView(NULL, contents_view),
      ok_button_(NULL),
      cancel_button_(NULL),
      default_button_(NULL),
      focus_manager_(NULL),
      extra_view_(NULL),
      footnote_view_(NULL),
      notified_delegate_(false) {}

DialogDelegate* DialogClientView::GetDialogDelegate() const {
  return GetWidget()->widget_delegate()->AsDialogDelegate();
}

void DialogClientView::CreateExtraView() {
  if (extra_view_)
    return;

  extra_view_ = GetDialogDelegate()->CreateExtraView();
  if (extra_view_) {
    extra_view_->SetGroup(kButtonGroup);
    AddChildView(extra_view_);
  }
}

void DialogClientView::CreateFootnoteView() {
  if (footnote_view_)
    return;

  footnote_view_ = GetDialogDelegate()->CreateFootnoteView();
  if (footnote_view_)
    AddChildView(footnote_view_);
}

void DialogClientView::ChildPreferredSizeChanged(View* child) {
  if (child == footnote_view_ || child == extra_view_)
    Layout();
}

void DialogClientView::ChildVisibilityChanged(View* child) {
  ChildPreferredSizeChanged(child);
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, private:

LabelButton* DialogClientView::CreateDialogButton(ui::DialogButton type) {
  const base::string16 title = GetDialogDelegate()->GetDialogButtonLabel(type);
  LabelButton* button = NULL;
  if (GetDialogDelegate()->UseNewStyleForThisDialog() &&
      GetDialogDelegate()->GetDefaultDialogButton() == type &&
      GetDialogDelegate()->ShouldDefaultButtonBeBlue()) {
    button = new BlueButton(this, title);
  } else {
    button = new LabelButton(this, title);
    button->SetStyle(Button::STYLE_BUTTON);
  }
  button->SetFocusable(true);

  const int kDialogMinButtonWidth = 75;
  button->SetMinSize(gfx::Size(kDialogMinButtonWidth, 0));
  button->SetGroup(kButtonGroup);
  return button;
}

void DialogClientView::UpdateButton(LabelButton* button,
                                    ui::DialogButton type) {
  DialogDelegate* dialog = GetDialogDelegate();
  button->SetText(dialog->GetDialogButtonLabel(type));
  button->SetEnabled(dialog->IsDialogButtonEnabled(type));

  if (type == dialog->GetDefaultDialogButton()) {
    default_button_ = button;
    button->SetIsDefault(true);
  }
}

int DialogClientView::GetButtonsAndExtraViewRowHeight() const {
  int extra_view_height = ShouldShow(extra_view_) ?
      extra_view_->GetPreferredSize().height() : 0;
  int buttons_height = std::max(
      ok_button_ ? ok_button_->GetPreferredSize().height() : 0,
      cancel_button_ ? cancel_button_->GetPreferredSize().height() : 0);
  return std::max(extra_view_height, buttons_height);
}

gfx::Insets DialogClientView::GetButtonRowInsets() const {
  // NOTE: The insets only apply to the buttons, extra view, and footnote view.
  return GetButtonsAndExtraViewRowHeight() == 0 ? gfx::Insets() :
      gfx::Insets(0, kButtonHEdgeMarginNew,
                  kButtonVEdgeMarginNew, kButtonHEdgeMarginNew);
}

void DialogClientView::Close() {
  GetWidget()->Close();
  GetDialogDelegate()->OnClosed();
}

}  // namespace views