1// Copyright (c) 2012 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 "ui/views/controls/message_box_view.h"
6
7#include "base/i18n/rtl.h"
8#include "base/message_loop/message_loop.h"
9#include "base/strings/string_split.h"
10#include "base/strings/utf_string_conversions.h"
11#include "ui/accessibility/ax_view_state.h"
12#include "ui/base/clipboard/clipboard.h"
13#include "ui/base/clipboard/scoped_clipboard_writer.h"
14#include "ui/views/controls/button/checkbox.h"
15#include "ui/views/controls/image_view.h"
16#include "ui/views/controls/label.h"
17#include "ui/views/controls/link.h"
18#include "ui/views/controls/textfield/textfield.h"
19#include "ui/views/layout/grid_layout.h"
20#include "ui/views/layout/layout_constants.h"
21#include "ui/views/widget/widget.h"
22#include "ui/views/window/client_view.h"
23#include "ui/views/window/dialog_delegate.h"
24
25namespace {
26
27const int kDefaultMessageWidth = 320;
28
29// Paragraph separators are defined in
30// http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBidiClass.txt
31//
32// # Bidi_Class=Paragraph_Separator
33//
34// 000A          ; B # Cc       <control-000A>
35// 000D          ; B # Cc       <control-000D>
36// 001C..001E    ; B # Cc   [3] <control-001C>..<control-001E>
37// 0085          ; B # Cc       <control-0085>
38// 2029          ; B # Zp       PARAGRAPH SEPARATOR
39bool IsParagraphSeparator(base::char16 c) {
40  return ( c == 0x000A || c == 0x000D || c == 0x001C || c == 0x001D ||
41           c == 0x001E || c == 0x0085 || c == 0x2029);
42}
43
44// Splits |text| into a vector of paragraphs.
45// Given an example "\nabc\ndef\n\n\nhij\n", the split results should be:
46// "", "abc", "def", "", "", "hij", and "".
47void SplitStringIntoParagraphs(const base::string16& text,
48                               std::vector<base::string16>* paragraphs) {
49  paragraphs->clear();
50
51  size_t start = 0;
52  for (size_t i = 0; i < text.length(); ++i) {
53    if (IsParagraphSeparator(text[i])) {
54      paragraphs->push_back(text.substr(start, i - start));
55      start = i + 1;
56    }
57  }
58  paragraphs->push_back(text.substr(start, text.length() - start));
59}
60
61}  // namespace
62
63namespace views {
64
65///////////////////////////////////////////////////////////////////////////////
66// MessageBoxView, public:
67
68MessageBoxView::InitParams::InitParams(const base::string16& message)
69    : options(NO_OPTIONS),
70      message(message),
71      message_width(kDefaultMessageWidth),
72      inter_row_vertical_spacing(kRelatedControlVerticalSpacing) {}
73
74MessageBoxView::InitParams::~InitParams() {
75}
76
77MessageBoxView::MessageBoxView(const InitParams& params)
78    : prompt_field_(NULL),
79      icon_(NULL),
80      checkbox_(NULL),
81      link_(NULL),
82      message_width_(params.message_width) {
83  Init(params);
84}
85
86MessageBoxView::~MessageBoxView() {}
87
88base::string16 MessageBoxView::GetInputText() {
89  return prompt_field_ ? prompt_field_->text() : base::string16();
90}
91
92bool MessageBoxView::IsCheckBoxSelected() {
93  return checkbox_ ? checkbox_->checked() : false;
94}
95
96void MessageBoxView::SetIcon(const gfx::ImageSkia& icon) {
97  if (!icon_)
98    icon_ = new ImageView();
99  icon_->SetImage(icon);
100  icon_->SetBounds(0, 0, icon.width(), icon.height());
101  ResetLayoutManager();
102}
103
104void MessageBoxView::SetCheckBoxLabel(const base::string16& label) {
105  if (!checkbox_)
106    checkbox_ = new Checkbox(label);
107  else
108    checkbox_->SetText(label);
109  ResetLayoutManager();
110}
111
112void MessageBoxView::SetCheckBoxSelected(bool selected) {
113  if (!checkbox_)
114    return;
115  checkbox_->SetChecked(selected);
116}
117
118void MessageBoxView::SetLink(const base::string16& text,
119                             LinkListener* listener) {
120  if (text.empty()) {
121    DCHECK(!listener);
122    delete link_;
123    link_ = NULL;
124  } else {
125    DCHECK(listener);
126    if (!link_) {
127      link_ = new Link();
128      link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
129    }
130    link_->SetText(text);
131    link_->set_listener(listener);
132  }
133  ResetLayoutManager();
134}
135
136void MessageBoxView::GetAccessibleState(ui::AXViewState* state) {
137  state->role = ui::AX_ROLE_ALERT;
138}
139
140///////////////////////////////////////////////////////////////////////////////
141// MessageBoxView, View overrides:
142
143void MessageBoxView::ViewHierarchyChanged(
144    const ViewHierarchyChangedDetails& details) {
145  if (details.child == this && details.is_add) {
146    if (prompt_field_)
147      prompt_field_->SelectAll(true);
148
149    NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
150  }
151}
152
153bool MessageBoxView::AcceleratorPressed(const ui::Accelerator& accelerator) {
154  // We only accepts Ctrl-C.
155  DCHECK(accelerator.key_code() == 'C' && accelerator.IsCtrlDown());
156
157  // We must not intercept Ctrl-C when we have a text box and it's focused.
158  if (prompt_field_ && prompt_field_->HasFocus())
159    return false;
160
161  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
162  if (!clipboard)
163    return false;
164
165  ui::ScopedClipboardWriter scw(clipboard, ui::CLIPBOARD_TYPE_COPY_PASTE);
166  base::string16 text = message_labels_[0]->text();
167  for (size_t i = 1; i < message_labels_.size(); ++i)
168    text += message_labels_[i]->text();
169  scw.WriteText(text);
170  return true;
171}
172
173///////////////////////////////////////////////////////////////////////////////
174// MessageBoxView, private:
175
176void MessageBoxView::Init(const InitParams& params) {
177  if (params.options & DETECT_DIRECTIONALITY) {
178    std::vector<base::string16> texts;
179    SplitStringIntoParagraphs(params.message, &texts);
180    for (size_t i = 0; i < texts.size(); ++i) {
181      Label* message_label = new Label(texts[i]);
182      // Avoid empty multi-line labels, which have a height of 0.
183      message_label->SetMultiLine(!texts[i].empty());
184      message_label->SetAllowCharacterBreak(true);
185      message_label->set_directionality_mode(gfx::DIRECTIONALITY_FROM_TEXT);
186      message_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
187      message_labels_.push_back(message_label);
188    }
189  } else {
190    Label* message_label = new Label(params.message);
191    message_label->SetMultiLine(true);
192    message_label->SetAllowCharacterBreak(true);
193    message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
194    message_labels_.push_back(message_label);
195  }
196
197  if (params.options & HAS_PROMPT_FIELD) {
198    prompt_field_ = new Textfield;
199    prompt_field_->SetText(params.default_prompt);
200  }
201
202  inter_row_vertical_spacing_ = params.inter_row_vertical_spacing;
203
204  ResetLayoutManager();
205}
206
207void MessageBoxView::ResetLayoutManager() {
208  // Initialize the Grid Layout Manager used for this dialog box.
209  GridLayout* layout = GridLayout::CreatePanel(this);
210  SetLayoutManager(layout);
211
212  gfx::Size icon_size;
213  if (icon_)
214    icon_size = icon_->GetPreferredSize();
215
216  // Add the column set for the message displayed at the top of the dialog box.
217  // And an icon, if one has been set.
218  const int message_column_view_set_id = 0;
219  ColumnSet* column_set = layout->AddColumnSet(message_column_view_set_id);
220  if (icon_) {
221    column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0,
222                          GridLayout::FIXED, icon_size.width(),
223                          icon_size.height());
224    column_set->AddPaddingColumn(0, kUnrelatedControlHorizontalSpacing);
225  }
226  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
227                        GridLayout::FIXED, message_width_, 0);
228
229  // Column set for extra elements, if any.
230  const int extra_column_view_set_id = 1;
231  if (prompt_field_ || checkbox_ || link_) {
232    column_set = layout->AddColumnSet(extra_column_view_set_id);
233    if (icon_) {
234      column_set->AddPaddingColumn(
235          0, icon_size.width() + kUnrelatedControlHorizontalSpacing);
236    }
237    column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
238                          GridLayout::USE_PREF, 0, 0);
239  }
240
241  for (size_t i = 0; i < message_labels_.size(); ++i) {
242    layout->StartRow(i, message_column_view_set_id);
243    if (icon_) {
244      if (i == 0)
245        layout->AddView(icon_);
246      else
247        layout->SkipColumns(1);
248    }
249    layout->AddView(message_labels_[i]);
250  }
251
252  if (prompt_field_) {
253    layout->AddPaddingRow(0, inter_row_vertical_spacing_);
254    layout->StartRow(0, extra_column_view_set_id);
255    layout->AddView(prompt_field_);
256  }
257
258  if (checkbox_) {
259    layout->AddPaddingRow(0, inter_row_vertical_spacing_);
260    layout->StartRow(0, extra_column_view_set_id);
261    layout->AddView(checkbox_);
262  }
263
264  if (link_) {
265    layout->AddPaddingRow(0, inter_row_vertical_spacing_);
266    layout->StartRow(0, extra_column_view_set_id);
267    layout->AddView(link_);
268  }
269}
270
271}  // namespace views
272