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
25 namespace {
26
27 const 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
IsParagraphSeparator(base::char16 c)39 bool 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 "".
SplitStringIntoParagraphs(const base::string16 & text,std::vector<base::string16> * paragraphs)47 void 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
63 namespace views {
64
65 ///////////////////////////////////////////////////////////////////////////////
66 // MessageBoxView, public:
67
InitParams(const base::string16 & message)68 MessageBoxView::InitParams::InitParams(const base::string16& message)
69 : options(NO_OPTIONS),
70 message(message),
71 message_width(kDefaultMessageWidth),
72 inter_row_vertical_spacing(kRelatedControlVerticalSpacing) {}
73
~InitParams()74 MessageBoxView::InitParams::~InitParams() {
75 }
76
MessageBoxView(const InitParams & params)77 MessageBoxView::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
~MessageBoxView()86 MessageBoxView::~MessageBoxView() {}
87
GetInputText()88 base::string16 MessageBoxView::GetInputText() {
89 return prompt_field_ ? prompt_field_->text() : base::string16();
90 }
91
IsCheckBoxSelected()92 bool MessageBoxView::IsCheckBoxSelected() {
93 return checkbox_ ? checkbox_->checked() : false;
94 }
95
SetIcon(const gfx::ImageSkia & icon)96 void 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
SetCheckBoxLabel(const base::string16 & label)104 void MessageBoxView::SetCheckBoxLabel(const base::string16& label) {
105 if (!checkbox_)
106 checkbox_ = new Checkbox(label);
107 else
108 checkbox_->SetText(label);
109 ResetLayoutManager();
110 }
111
SetCheckBoxSelected(bool selected)112 void MessageBoxView::SetCheckBoxSelected(bool selected) {
113 if (!checkbox_)
114 return;
115 checkbox_->SetChecked(selected);
116 }
117
SetLink(const base::string16 & text,LinkListener * listener)118 void 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
GetAccessibleState(ui::AXViewState * state)136 void MessageBoxView::GetAccessibleState(ui::AXViewState* state) {
137 state->role = ui::AX_ROLE_ALERT;
138 }
139
140 ///////////////////////////////////////////////////////////////////////////////
141 // MessageBoxView, View overrides:
142
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)143 void 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
AcceleratorPressed(const ui::Accelerator & accelerator)153 bool 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
Init(const InitParams & params)176 void 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
ResetLayoutManager()207 void 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