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/base/accessibility/accessible_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 39 bool IsParagraphSeparator(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 "". 47 void SplitStringIntoParagraphs(const string16& text, 48 std::vector<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 68 MessageBoxView::InitParams::InitParams(const string16& message) 69 : options(NO_OPTIONS), 70 message(message), 71 message_width(kDefaultMessageWidth), 72 inter_row_vertical_spacing(kRelatedControlVerticalSpacing) {} 73 74 MessageBoxView::InitParams::~InitParams() { 75 } 76 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 86 MessageBoxView::~MessageBoxView() {} 87 88 string16 MessageBoxView::GetInputText() { 89 return prompt_field_ ? prompt_field_->text() : string16(); 90 } 91 92 bool MessageBoxView::IsCheckBoxSelected() { 93 return checkbox_ ? checkbox_->checked() : false; 94 } 95 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 104 void MessageBoxView::SetCheckBoxLabel(const string16& label) { 105 if (!checkbox_) 106 checkbox_ = new Checkbox(label); 107 else 108 checkbox_->SetText(label); 109 ResetLayoutManager(); 110 } 111 112 void MessageBoxView::SetCheckBoxSelected(bool selected) { 113 if (!checkbox_) 114 return; 115 checkbox_->SetChecked(selected); 116 } 117 118 void MessageBoxView::SetLink(const string16& text, LinkListener* listener) { 119 if (text.empty()) { 120 DCHECK(!listener); 121 delete link_; 122 link_ = NULL; 123 } else { 124 DCHECK(listener); 125 if (!link_) { 126 link_ = new Link(); 127 link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 128 } 129 link_->SetText(text); 130 link_->set_listener(listener); 131 } 132 ResetLayoutManager(); 133 } 134 135 void MessageBoxView::GetAccessibleState(ui::AccessibleViewState* state) { 136 state->role = ui::AccessibilityTypes::ROLE_ALERT; 137 } 138 139 /////////////////////////////////////////////////////////////////////////////// 140 // MessageBoxView, View overrides: 141 142 void MessageBoxView::ViewHierarchyChanged( 143 const ViewHierarchyChangedDetails& details) { 144 if (details.child == this && details.is_add) { 145 if (prompt_field_) 146 prompt_field_->SelectAll(true); 147 148 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); 149 } 150 } 151 152 bool MessageBoxView::AcceleratorPressed(const ui::Accelerator& accelerator) { 153 // We only accepts Ctrl-C. 154 DCHECK(accelerator.key_code() == 'C' && accelerator.IsCtrlDown()); 155 156 // We must not intercept Ctrl-C when we have a text box and it's focused. 157 if (prompt_field_ && prompt_field_->HasFocus()) 158 return false; 159 160 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); 161 if (!clipboard) 162 return false; 163 164 ui::ScopedClipboardWriter scw(clipboard, ui::Clipboard::BUFFER_STANDARD); 165 string16 text = message_labels_[0]->text(); 166 for (size_t i = 1; i < message_labels_.size(); ++i) 167 text += message_labels_[i]->text(); 168 scw.WriteText(text); 169 return true; 170 } 171 172 /////////////////////////////////////////////////////////////////////////////// 173 // MessageBoxView, private: 174 175 void MessageBoxView::Init(const InitParams& params) { 176 if (params.options & DETECT_DIRECTIONALITY) { 177 std::vector<string16> texts; 178 SplitStringIntoParagraphs(params.message, &texts); 179 // If the text originates from a web page, its alignment is based on its 180 // first character with strong directionality. 181 base::i18n::TextDirection message_direction = 182 base::i18n::GetFirstStrongCharacterDirection(params.message); 183 gfx::HorizontalAlignment alignment = 184 (message_direction == base::i18n::RIGHT_TO_LEFT) ? 185 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; 186 for (size_t i = 0; i < texts.size(); ++i) { 187 Label* message_label = new Label(texts[i]); 188 // Don't set multi-line to true if the text is empty, else the label will 189 // have a height of 0. 190 message_label->SetMultiLine(!texts[i].empty()); 191 message_label->SetAllowCharacterBreak(true); 192 message_label->set_directionality_mode(Label::AUTO_DETECT_DIRECTIONALITY); 193 message_label->SetHorizontalAlignment(alignment); 194 message_labels_.push_back(message_label); 195 } 196 } else { 197 Label* message_label = new Label(params.message); 198 message_label->SetMultiLine(true); 199 message_label->SetAllowCharacterBreak(true); 200 message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 201 message_labels_.push_back(message_label); 202 } 203 204 if (params.options & HAS_PROMPT_FIELD) { 205 prompt_field_ = new Textfield; 206 prompt_field_->SetText(params.default_prompt); 207 } 208 209 inter_row_vertical_spacing_ = params.inter_row_vertical_spacing; 210 211 ResetLayoutManager(); 212 } 213 214 void MessageBoxView::ResetLayoutManager() { 215 // Initialize the Grid Layout Manager used for this dialog box. 216 GridLayout* layout = GridLayout::CreatePanel(this); 217 SetLayoutManager(layout); 218 219 gfx::Size icon_size; 220 if (icon_) 221 icon_size = icon_->GetPreferredSize(); 222 223 // Add the column set for the message displayed at the top of the dialog box. 224 // And an icon, if one has been set. 225 const int message_column_view_set_id = 0; 226 ColumnSet* column_set = layout->AddColumnSet(message_column_view_set_id); 227 if (icon_) { 228 column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, 229 GridLayout::FIXED, icon_size.width(), 230 icon_size.height()); 231 column_set->AddPaddingColumn(0, kUnrelatedControlHorizontalSpacing); 232 } 233 column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, 234 GridLayout::FIXED, message_width_, 0); 235 236 // Column set for extra elements, if any. 237 const int extra_column_view_set_id = 1; 238 if (prompt_field_ || checkbox_ || link_) { 239 column_set = layout->AddColumnSet(extra_column_view_set_id); 240 if (icon_) { 241 column_set->AddPaddingColumn( 242 0, icon_size.width() + kUnrelatedControlHorizontalSpacing); 243 } 244 column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, 245 GridLayout::USE_PREF, 0, 0); 246 } 247 248 for (size_t i = 0; i < message_labels_.size(); ++i) { 249 layout->StartRow(i, message_column_view_set_id); 250 if (icon_) { 251 if (i == 0) 252 layout->AddView(icon_); 253 else 254 layout->SkipColumns(1); 255 } 256 layout->AddView(message_labels_[i]); 257 } 258 259 if (prompt_field_) { 260 layout->AddPaddingRow(0, inter_row_vertical_spacing_); 261 layout->StartRow(0, extra_column_view_set_id); 262 layout->AddView(prompt_field_); 263 } 264 265 if (checkbox_) { 266 layout->AddPaddingRow(0, inter_row_vertical_spacing_); 267 layout->StartRow(0, extra_column_view_set_id); 268 layout->AddView(checkbox_); 269 } 270 271 if (link_) { 272 layout->AddPaddingRow(0, inter_row_vertical_spacing_); 273 layout->StartRow(0, extra_column_view_set_id); 274 layout->AddView(link_); 275 } 276 } 277 278 } // namespace views 279