Home | History | Annotate | Download | only in controls
      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