// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/views/controls/message_box_view.h" #include "base/i18n/rtl.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_view_state.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/views/controls/button/checkbox.h" #include "ui/views/controls/label.h" #include "ui/views/controls/link.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/grid_layout.h" #include "ui/views/layout/layout_constants.h" #include "ui/views/widget/widget.h" #include "ui/views/window/client_view.h" #include "ui/views/window/dialog_delegate.h" namespace { const int kDefaultMessageWidth = 320; // Paragraph separators are defined in // http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBidiClass.txt // // # Bidi_Class=Paragraph_Separator // // 000A ; B # Cc // 000D ; B # Cc // 001C..001E ; B # Cc [3] .. // 0085 ; B # Cc // 2029 ; B # Zp PARAGRAPH SEPARATOR bool IsParagraphSeparator(base::char16 c) { return ( c == 0x000A || c == 0x000D || c == 0x001C || c == 0x001D || c == 0x001E || c == 0x0085 || c == 0x2029); } // Splits |text| into a vector of paragraphs. // Given an example "\nabc\ndef\n\n\nhij\n", the split results should be: // "", "abc", "def", "", "", "hij", and "". void SplitStringIntoParagraphs(const base::string16& text, std::vector* paragraphs) { paragraphs->clear(); size_t start = 0; for (size_t i = 0; i < text.length(); ++i) { if (IsParagraphSeparator(text[i])) { paragraphs->push_back(text.substr(start, i - start)); start = i + 1; } } paragraphs->push_back(text.substr(start, text.length() - start)); } } // namespace namespace views { /////////////////////////////////////////////////////////////////////////////// // MessageBoxView, public: MessageBoxView::InitParams::InitParams(const base::string16& message) : options(NO_OPTIONS), message(message), message_width(kDefaultMessageWidth), inter_row_vertical_spacing(kRelatedControlVerticalSpacing) {} MessageBoxView::InitParams::~InitParams() { } MessageBoxView::MessageBoxView(const InitParams& params) : prompt_field_(NULL), checkbox_(NULL), link_(NULL), message_width_(params.message_width) { Init(params); } MessageBoxView::~MessageBoxView() {} base::string16 MessageBoxView::GetInputText() { return prompt_field_ ? prompt_field_->text() : base::string16(); } bool MessageBoxView::IsCheckBoxSelected() { return checkbox_ ? checkbox_->checked() : false; } void MessageBoxView::SetCheckBoxLabel(const base::string16& label) { if (!checkbox_) checkbox_ = new Checkbox(label); else checkbox_->SetText(label); ResetLayoutManager(); } void MessageBoxView::SetCheckBoxSelected(bool selected) { if (!checkbox_) return; checkbox_->SetChecked(selected); } void MessageBoxView::SetLink(const base::string16& text, LinkListener* listener) { if (text.empty()) { DCHECK(!listener); delete link_; link_ = NULL; } else { DCHECK(listener); if (!link_) { link_ = new Link(); link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); } link_->SetText(text); link_->set_listener(listener); } ResetLayoutManager(); } void MessageBoxView::GetAccessibleState(ui::AXViewState* state) { state->role = ui::AX_ROLE_ALERT; } /////////////////////////////////////////////////////////////////////////////// // MessageBoxView, View overrides: void MessageBoxView::ViewHierarchyChanged( const ViewHierarchyChangedDetails& details) { if (details.child == this && details.is_add) { if (prompt_field_) prompt_field_->SelectAll(true); NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); } } bool MessageBoxView::AcceleratorPressed(const ui::Accelerator& accelerator) { // We only accepts Ctrl-C. DCHECK(accelerator.key_code() == 'C' && accelerator.IsCtrlDown()); // We must not intercept Ctrl-C when we have a text box and it's focused. if (prompt_field_ && prompt_field_->HasFocus()) return false; ui::ScopedClipboardWriter scw(ui::CLIPBOARD_TYPE_COPY_PASTE); base::string16 text = message_labels_[0]->text(); for (size_t i = 1; i < message_labels_.size(); ++i) text += message_labels_[i]->text(); scw.WriteText(text); return true; } /////////////////////////////////////////////////////////////////////////////// // MessageBoxView, private: void MessageBoxView::Init(const InitParams& params) { if (params.options & DETECT_DIRECTIONALITY) { std::vector texts; SplitStringIntoParagraphs(params.message, &texts); for (size_t i = 0; i < texts.size(); ++i) { Label* message_label = new Label(texts[i]); // Avoid empty multi-line labels, which have a height of 0. message_label->SetMultiLine(!texts[i].empty()); message_label->SetAllowCharacterBreak(true); message_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); message_labels_.push_back(message_label); } } else { Label* message_label = new Label(params.message); message_label->SetMultiLine(true); message_label->SetAllowCharacterBreak(true); message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); message_labels_.push_back(message_label); } if (params.options & HAS_PROMPT_FIELD) { prompt_field_ = new Textfield; prompt_field_->SetText(params.default_prompt); } inter_row_vertical_spacing_ = params.inter_row_vertical_spacing; ResetLayoutManager(); } void MessageBoxView::ResetLayoutManager() { // Initialize the Grid Layout Manager used for this dialog box. GridLayout* layout = GridLayout::CreatePanel(this); SetLayoutManager(layout); // Add the column set for the message displayed at the top of the dialog box. const int message_column_view_set_id = 0; ColumnSet* column_set = layout->AddColumnSet(message_column_view_set_id); column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, GridLayout::FIXED, message_width_, 0); // Column set for extra elements, if any. const int extra_column_view_set_id = 1; if (prompt_field_ || checkbox_ || link_) { column_set = layout->AddColumnSet(extra_column_view_set_id); column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, GridLayout::USE_PREF, 0, 0); } const int kMaxScrollViewHeight = 600; views::View* message_contents = new views::View(); message_contents->SetLayoutManager( new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); for (size_t i = 0; i < message_labels_.size(); ++i) message_contents->AddChildView(message_labels_[i]); ScrollView* scroll_view = new views::ScrollView(); scroll_view->ClipHeightTo(0, kMaxScrollViewHeight); scroll_view->SetContents(message_contents); layout->StartRow(0, message_column_view_set_id); layout->AddView(scroll_view); if (prompt_field_) { layout->AddPaddingRow(0, inter_row_vertical_spacing_); layout->StartRow(0, extra_column_view_set_id); layout->AddView(prompt_field_); } if (checkbox_) { layout->AddPaddingRow(0, inter_row_vertical_spacing_); layout->StartRow(0, extra_column_view_set_id); layout->AddView(checkbox_); } if (link_) { layout->AddPaddingRow(0, inter_row_vertical_spacing_); layout->StartRow(0, extra_column_view_set_id); layout->AddView(link_); } } } // namespace views