1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/input_window_dialog.h"
6
7#include "base/compiler_specific.h"
8#include "base/message_loop.h"
9#include "base/task.h"
10#include "grit/generated_resources.h"
11#include "views/controls/label.h"
12#include "views/controls/textfield/textfield.h"
13#include "views/controls/textfield/textfield_controller.h"
14#include "views/layout/grid_layout.h"
15#include "views/layout/layout_constants.h"
16#include "views/window/dialog_delegate.h"
17#include "views/window/window.h"
18
19// Width to make the text field, in pixels.
20static const int kTextfieldWidth = 200;
21
22class ContentView;
23
24// The Windows implementation of the cross platform input dialog interface.
25class WinInputWindowDialog : public InputWindowDialog {
26 public:
27  WinInputWindowDialog(HWND parent,
28                       const std::wstring& window_title,
29                       const std::wstring& label,
30                       const std::wstring& contents,
31                       Delegate* delegate);
32  virtual ~WinInputWindowDialog();
33
34  virtual void Show();
35  virtual void Close();
36
37  const std::wstring& window_title() const { return window_title_; }
38  const std::wstring& label() const { return label_; }
39  const std::wstring& contents() const { return contents_; }
40
41  InputWindowDialog::Delegate* delegate() { return delegate_.get(); }
42
43 private:
44  // Our chrome views window.
45  views::Window* window_;
46
47  // Strings to feed to the on screen window.
48  std::wstring window_title_;
49  std::wstring label_;
50  std::wstring contents_;
51
52  // Our delegate. Consumes the window's output.
53  scoped_ptr<InputWindowDialog::Delegate> delegate_;
54};
55
56// ContentView, as the name implies, is the content view for the InputWindow.
57// It registers accelerators that accept/cancel the input.
58class ContentView : public views::View,
59                    public views::DialogDelegate,
60                    public views::TextfieldController {
61 public:
62  explicit ContentView(WinInputWindowDialog* delegate)
63      : delegate_(delegate),
64        ALLOW_THIS_IN_INITIALIZER_LIST(focus_grabber_factory_(this)) {
65    DCHECK(delegate_);
66  }
67
68  // views::DialogDelegate:
69  virtual bool IsDialogButtonEnabled(
70      MessageBoxFlags::DialogButton button) const;
71  virtual bool Accept();
72  virtual bool Cancel();
73  virtual void DeleteDelegate();
74  virtual std::wstring GetWindowTitle() const;
75  virtual bool IsModal() const { return true; }
76  virtual views::View* GetContentsView();
77
78  // views::TextfieldController:
79  virtual void ContentsChanged(views::Textfield* sender,
80                               const std::wstring& new_contents);
81  virtual bool HandleKeyEvent(views::Textfield*,
82                              const views::KeyEvent&) {
83    return false;
84  }
85
86 protected:
87  // views::View:
88  virtual void ViewHierarchyChanged(bool is_add,
89                                    views::View* parent,
90                                    views::View* child);
91
92 private:
93  // Set up dialog controls and layout.
94  void InitControlLayout();
95
96  // Sets focus to the first focusable element within the dialog.
97  void FocusFirstFocusableControl();
98
99  // The Textfield that the user can type into.
100  views::Textfield* text_field_;
101
102  // The delegate that the ContentView uses to communicate changes to the
103  // caller.
104  WinInputWindowDialog* delegate_;
105
106  // Helps us set focus to the first Textfield in the window.
107  ScopedRunnableMethodFactory<ContentView> focus_grabber_factory_;
108
109  DISALLOW_COPY_AND_ASSIGN(ContentView);
110};
111
112///////////////////////////////////////////////////////////////////////////////
113// ContentView, views::DialogDelegate implementation:
114
115bool ContentView::IsDialogButtonEnabled(
116    MessageBoxFlags::DialogButton button) const {
117  if (button == MessageBoxFlags::DIALOGBUTTON_OK &&
118      !delegate_->delegate()->IsValid(text_field_->text())) {
119    return false;
120  }
121  return true;
122}
123
124bool ContentView::Accept() {
125  delegate_->delegate()->InputAccepted(text_field_->text());
126  return true;
127}
128
129bool ContentView::Cancel() {
130  delegate_->delegate()->InputCanceled();
131  return true;
132}
133
134void ContentView::DeleteDelegate() {
135  delete delegate_;
136}
137
138std::wstring ContentView::GetWindowTitle() const {
139  return delegate_->window_title();
140}
141
142views::View* ContentView::GetContentsView() {
143  return this;
144}
145
146///////////////////////////////////////////////////////////////////////////////
147// ContentView, views::TextfieldController implementation:
148
149void ContentView::ContentsChanged(views::Textfield* sender,
150                                  const std::wstring& new_contents) {
151  GetDialogClientView()->UpdateDialogButtons();
152}
153
154///////////////////////////////////////////////////////////////////////////////
155// ContentView, protected:
156
157void ContentView::ViewHierarchyChanged(bool is_add,
158                                       views::View* parent,
159                                       views::View* child) {
160  if (is_add && child == this)
161    InitControlLayout();
162}
163
164///////////////////////////////////////////////////////////////////////////////
165// ContentView, private:
166
167void ContentView::InitControlLayout() {
168  text_field_ = new views::Textfield;
169  text_field_->SetText(delegate_->contents());
170  text_field_->SetController(this);
171
172  using views::ColumnSet;
173  using views::GridLayout;
174
175  // TODO(sky): Vertical alignment should be baseline.
176  GridLayout* layout = GridLayout::CreatePanel(this);
177  SetLayoutManager(layout);
178
179  ColumnSet* c1 = layout->AddColumnSet(0);
180  c1->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
181                GridLayout::USE_PREF, 0, 0);
182  c1->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
183  c1->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1,
184                GridLayout::USE_PREF, kTextfieldWidth, kTextfieldWidth);
185
186  layout->StartRow(0, 0);
187  views::Label* label = new views::Label(delegate_->label());
188  layout->AddView(label);
189  layout->AddView(text_field_);
190
191  MessageLoop::current()->PostTask(FROM_HERE,
192      focus_grabber_factory_.NewRunnableMethod(
193          &ContentView::FocusFirstFocusableControl));
194}
195
196void ContentView::FocusFirstFocusableControl() {
197  text_field_->SelectAll();
198  text_field_->RequestFocus();
199}
200
201WinInputWindowDialog::WinInputWindowDialog(HWND parent,
202                                           const std::wstring& window_title,
203                                           const std::wstring& label,
204                                           const std::wstring& contents,
205                                           Delegate* delegate)
206    : window_title_(window_title),
207      label_(label),
208      contents_(contents),
209      delegate_(delegate) {
210  window_ = views::Window::CreateChromeWindow(parent, gfx::Rect(),
211                                              new ContentView(this));
212  window_->client_view()->AsDialogClientView()->UpdateDialogButtons();
213}
214
215WinInputWindowDialog::~WinInputWindowDialog() {
216}
217
218void WinInputWindowDialog::Show() {
219  window_->Show();
220}
221
222void WinInputWindowDialog::Close() {
223  window_->CloseWindow();
224}
225
226// static
227InputWindowDialog* InputWindowDialog::Create(HWND parent,
228                                             const std::wstring& window_title,
229                                             const std::wstring& label,
230                                             const std::wstring& contents,
231                                             Delegate* delegate) {
232  return new WinInputWindowDialog(parent,
233                                  window_title,
234                                  label,
235                                  contents,
236                                  delegate);
237}
238