// Copyright (c) 2011 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 "chrome/browser/ui/input_window_dialog.h"

#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "grit/generated_resources.h"
#include "views/controls/label.h"
#include "views/controls/textfield/textfield.h"
#include "views/controls/textfield/textfield_controller.h"
#include "views/layout/grid_layout.h"
#include "views/layout/layout_constants.h"
#include "views/window/dialog_delegate.h"
#include "views/window/window.h"

// Width to make the text field, in pixels.
static const int kTextfieldWidth = 200;

class ContentView;

// The Windows implementation of the cross platform input dialog interface.
class WinInputWindowDialog : public InputWindowDialog {
 public:
  WinInputWindowDialog(HWND parent,
                       const std::wstring& window_title,
                       const std::wstring& label,
                       const std::wstring& contents,
                       Delegate* delegate);
  virtual ~WinInputWindowDialog();

  virtual void Show();
  virtual void Close();

  const std::wstring& window_title() const { return window_title_; }
  const std::wstring& label() const { return label_; }
  const std::wstring& contents() const { return contents_; }

  InputWindowDialog::Delegate* delegate() { return delegate_.get(); }

 private:
  // Our chrome views window.
  views::Window* window_;

  // Strings to feed to the on screen window.
  std::wstring window_title_;
  std::wstring label_;
  std::wstring contents_;

  // Our delegate. Consumes the window's output.
  scoped_ptr<InputWindowDialog::Delegate> delegate_;
};

// ContentView, as the name implies, is the content view for the InputWindow.
// It registers accelerators that accept/cancel the input.
class ContentView : public views::View,
                    public views::DialogDelegate,
                    public views::TextfieldController {
 public:
  explicit ContentView(WinInputWindowDialog* delegate)
      : delegate_(delegate),
        ALLOW_THIS_IN_INITIALIZER_LIST(focus_grabber_factory_(this)) {
    DCHECK(delegate_);
  }

  // views::DialogDelegate:
  virtual bool IsDialogButtonEnabled(
      MessageBoxFlags::DialogButton button) const;
  virtual bool Accept();
  virtual bool Cancel();
  virtual void DeleteDelegate();
  virtual std::wstring GetWindowTitle() const;
  virtual bool IsModal() const { return true; }
  virtual views::View* GetContentsView();

  // views::TextfieldController:
  virtual void ContentsChanged(views::Textfield* sender,
                               const std::wstring& new_contents);
  virtual bool HandleKeyEvent(views::Textfield*,
                              const views::KeyEvent&) {
    return false;
  }

 protected:
  // views::View:
  virtual void ViewHierarchyChanged(bool is_add,
                                    views::View* parent,
                                    views::View* child);

 private:
  // Set up dialog controls and layout.
  void InitControlLayout();

  // Sets focus to the first focusable element within the dialog.
  void FocusFirstFocusableControl();

  // The Textfield that the user can type into.
  views::Textfield* text_field_;

  // The delegate that the ContentView uses to communicate changes to the
  // caller.
  WinInputWindowDialog* delegate_;

  // Helps us set focus to the first Textfield in the window.
  ScopedRunnableMethodFactory<ContentView> focus_grabber_factory_;

  DISALLOW_COPY_AND_ASSIGN(ContentView);
};

///////////////////////////////////////////////////////////////////////////////
// ContentView, views::DialogDelegate implementation:

bool ContentView::IsDialogButtonEnabled(
    MessageBoxFlags::DialogButton button) const {
  if (button == MessageBoxFlags::DIALOGBUTTON_OK &&
      !delegate_->delegate()->IsValid(text_field_->text())) {
    return false;
  }
  return true;
}

bool ContentView::Accept() {
  delegate_->delegate()->InputAccepted(text_field_->text());
  return true;
}

bool ContentView::Cancel() {
  delegate_->delegate()->InputCanceled();
  return true;
}

void ContentView::DeleteDelegate() {
  delete delegate_;
}

std::wstring ContentView::GetWindowTitle() const {
  return delegate_->window_title();
}

views::View* ContentView::GetContentsView() {
  return this;
}

///////////////////////////////////////////////////////////////////////////////
// ContentView, views::TextfieldController implementation:

void ContentView::ContentsChanged(views::Textfield* sender,
                                  const std::wstring& new_contents) {
  GetDialogClientView()->UpdateDialogButtons();
}

///////////////////////////////////////////////////////////////////////////////
// ContentView, protected:

void ContentView::ViewHierarchyChanged(bool is_add,
                                       views::View* parent,
                                       views::View* child) {
  if (is_add && child == this)
    InitControlLayout();
}

///////////////////////////////////////////////////////////////////////////////
// ContentView, private:

void ContentView::InitControlLayout() {
  text_field_ = new views::Textfield;
  text_field_->SetText(delegate_->contents());
  text_field_->SetController(this);

  using views::ColumnSet;
  using views::GridLayout;

  // TODO(sky): Vertical alignment should be baseline.
  GridLayout* layout = GridLayout::CreatePanel(this);
  SetLayoutManager(layout);

  ColumnSet* c1 = layout->AddColumnSet(0);
  c1->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
                GridLayout::USE_PREF, 0, 0);
  c1->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
  c1->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
                GridLayout::USE_PREF, kTextfieldWidth, kTextfieldWidth);

  layout->StartRow(0, 0);
  views::Label* label = new views::Label(delegate_->label());
  layout->AddView(label);
  layout->AddView(text_field_);

  MessageLoop::current()->PostTask(FROM_HERE,
      focus_grabber_factory_.NewRunnableMethod(
          &ContentView::FocusFirstFocusableControl));
}

void ContentView::FocusFirstFocusableControl() {
  text_field_->SelectAll();
  text_field_->RequestFocus();
}

WinInputWindowDialog::WinInputWindowDialog(HWND parent,
                                           const std::wstring& window_title,
                                           const std::wstring& label,
                                           const std::wstring& contents,
                                           Delegate* delegate)
    : window_title_(window_title),
      label_(label),
      contents_(contents),
      delegate_(delegate) {
  window_ = views::Window::CreateChromeWindow(parent, gfx::Rect(),
                                              new ContentView(this));
  window_->client_view()->AsDialogClientView()->UpdateDialogButtons();
}

WinInputWindowDialog::~WinInputWindowDialog() {
}

void WinInputWindowDialog::Show() {
  window_->Show();
}

void WinInputWindowDialog::Close() {
  window_->CloseWindow();
}

// static
InputWindowDialog* InputWindowDialog::Create(HWND parent,
                                             const std::wstring& window_title,
                                             const std::wstring& label,
                                             const std::wstring& contents,
                                             Delegate* delegate) {
  return new WinInputWindowDialog(parent,
                                  window_title,
                                  label,
                                  contents,
                                  delegate);
}