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