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