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