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/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