1// Copyright (c) 2013 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 "ash/popup_message.h"
6
7#include "ash/wm/window_animations.h"
8#include "grit/ash_resources.h"
9#include "ui/base/resource/resource_bundle.h"
10#include "ui/gfx/insets.h"
11#include "ui/views/bubble/bubble_delegate.h"
12#include "ui/views/bubble/bubble_frame_view.h"
13#include "ui/views/controls/image_view.h"
14#include "ui/views/controls/label.h"
15#include "ui/views/layout/box_layout.h"
16#include "ui/views/widget/widget.h"
17
18namespace ash {
19namespace {
20const int kMessageTopBottomMargin = 10;
21const int kMessageLeftRightMargin = 10;
22const int kMessageMinHeight = 29 - 2 * kMessageTopBottomMargin;
23const SkColor kMessageTextColor = SkColorSetRGB(0x22, 0x22, 0x22);
24
25// The maximum width of the Message bubble.  Borrowed the value from
26// ash/message/message_controller.cc
27const int kMessageMaxWidth = 250;
28
29// The offset for the Message bubble - making sure that the bubble is flush
30// with the shelf. The offset includes the arrow size in pixels as well as
31// the activation bar and other spacing elements.
32const int kArrowOffsetLeftRight = 11;
33const int kArrowOffsetTopBottom = 7;
34
35// The number of pixels between the icon and the text.
36const int kHorizontalPopupPaddingBetweenItems = 10;
37
38// The number of pixels between the text items.
39const int kVerticalPopupPaddingBetweenItems = 10;
40}  // namespace
41
42// The implementation of Message of the launcher.
43class PopupMessage::MessageBubble : public views::BubbleDelegateView {
44 public:
45  MessageBubble(const base::string16& caption,
46                const base::string16& message,
47                IconType message_type,
48                views::View* anchor,
49                views::BubbleBorder::Arrow arrow_orientation,
50                const gfx::Size& size_override,
51                int arrow_offset);
52
53  void Close();
54
55 private:
56  // views::View overrides:
57  virtual gfx::Size GetPreferredSize() const OVERRIDE;
58
59  // Each component (width/height) can force a size override for that component
60  // if not 0.
61  gfx::Size size_override_;
62
63  DISALLOW_COPY_AND_ASSIGN(MessageBubble);
64};
65
66PopupMessage::MessageBubble::MessageBubble(const base::string16& caption,
67                                           const base::string16& message,
68                                           IconType message_type,
69                                           views::View* anchor,
70                                           views::BubbleBorder::Arrow arrow,
71                                           const gfx::Size& size_override,
72                                           int arrow_offset)
73    : views::BubbleDelegateView(anchor, arrow),
74      size_override_(size_override) {
75  gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom,
76                                   kArrowOffsetLeftRight,
77                                   kArrowOffsetTopBottom,
78                                   kArrowOffsetLeftRight);
79  // An anchor can have an asymmetrical border for spacing reasons. Adjust the
80  // anchor location for this.
81  if (anchor->border())
82    insets += anchor->border()->GetInsets();
83
84  set_anchor_view_insets(insets);
85  set_close_on_esc(false);
86  set_close_on_deactivate(false);
87  set_can_activate(false);
88  set_accept_events(false);
89
90  set_margins(gfx::Insets(kMessageTopBottomMargin, kMessageLeftRightMargin,
91                          kMessageTopBottomMargin, kMessageLeftRightMargin));
92  set_shadow(views::BubbleBorder::SMALL_SHADOW);
93
94  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
95  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
96                                        kHorizontalPopupPaddingBetweenItems));
97
98  // Here is the layout:
99  //         arrow_offset (if not 0)
100  //       |-------------|
101  //       |             ^
102  //       +-------------------------------------------------+
103  //      -|                                                 |-
104  //  icon |  [!]  Caption in bold which can be multi line   | caption_label
105  //      -|                                                 |-
106  //       |       Message text which can be multi line      | message_label
107  //       |       as well.                                  |
108  //       |                                                 |-
109  //       +-------------------------------------------------+
110  //             |------------details container--------------|
111  // Note that the icon, caption and massage are optional.
112
113  // Add the icon to the first column (if there is one).
114  if (message_type != ICON_NONE) {
115    views::ImageView* icon = new views::ImageView();
116    icon->SetImage(
117        bundle.GetImageNamed(IDR_AURA_WARNING_ICON).ToImageSkia());
118    icon->SetVerticalAlignment(views::ImageView::LEADING);
119    AddChildView(icon);
120  }
121
122  // Create a container for the text items and use it as second column.
123  views::View* details = new views::View();
124  AddChildView(details);
125  details->SetLayoutManager(new views::BoxLayout(
126      views::BoxLayout::kVertical, 0, 0, kVerticalPopupPaddingBetweenItems));
127
128  // The caption label.
129  if (!caption.empty()) {
130    views::Label* caption_label = new views::Label(caption);
131    caption_label->SetMultiLine(true);
132    caption_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
133    caption_label->SetFontList(
134        bundle.GetFontList(ui::ResourceBundle::BoldFont));
135    caption_label->SetEnabledColor(kMessageTextColor);
136    details->AddChildView(caption_label);
137  }
138
139  // The message label.
140  if (!message.empty()) {
141    views::Label* message_label = new views::Label(message);
142    message_label->SetMultiLine(true);
143    message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
144    message_label->SetEnabledColor(kMessageTextColor);
145    details->AddChildView(message_label);
146  }
147  views::BubbleDelegateView::CreateBubble(this);
148
149  // Change the arrow offset if needed.
150  if (arrow_offset) {
151    // With the creation of the bubble, the bubble got already placed (and
152    // possibly re-oriented to fit on the screen). Since it is not possible to
153    // set the arrow offset before the creation, we need to set the offset,
154    // and the orientation variables again and force a re-placement.
155    GetBubbleFrameView()->bubble_border()->set_arrow_offset(arrow_offset);
156    GetBubbleFrameView()->bubble_border()->set_arrow(arrow);
157    SetAlignment(views::BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR);
158  }
159}
160
161void PopupMessage::MessageBubble::Close() {
162  if (GetWidget())
163    GetWidget()->Close();
164}
165
166gfx::Size PopupMessage::MessageBubble::GetPreferredSize() const {
167  gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize();
168  // Override the size with either the provided size or adjust it to not
169  // violate our minimum / maximum sizes.
170  if (size_override_.height())
171    pref_size.set_height(size_override_.height());
172  else if (pref_size.height() < kMessageMinHeight)
173    pref_size.set_height(kMessageMinHeight);
174
175  if (size_override_.width())
176    pref_size.set_width(size_override_.width());
177  else if (pref_size.width() > kMessageMaxWidth)
178    pref_size.set_width(kMessageMaxWidth);
179
180  return pref_size;
181}
182
183PopupMessage::PopupMessage(const base::string16& caption,
184                           const base::string16& message,
185                           IconType message_type,
186                           views::View* anchor,
187                           views::BubbleBorder::Arrow arrow,
188                           const gfx::Size& size_override,
189                           int arrow_offset)
190    : view_(NULL) {
191  view_ = new MessageBubble(
192      caption, message, message_type, anchor, arrow, size_override,
193      arrow_offset);
194  widget_ = view_->GetWidget();
195
196  gfx::NativeView native_view = widget_->GetNativeView();
197  wm::SetWindowVisibilityAnimationType(
198      native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
199  wm::SetWindowVisibilityAnimationTransition(
200      native_view, wm::ANIMATE_HIDE);
201  view_->GetWidget()->Show();
202}
203
204PopupMessage::~PopupMessage() {
205  CancelHidingAnimation();
206  Close();
207}
208
209void PopupMessage::Close() {
210  if (view_) {
211    view_->Close();
212    view_ = NULL;
213    widget_ = NULL;
214  }
215}
216
217void PopupMessage::CancelHidingAnimation() {
218  if (!widget_ || !widget_->GetNativeView())
219    return;
220
221  gfx::NativeView native_view = widget_->GetNativeView();
222  wm::SetWindowVisibilityAnimationTransition(
223      native_view, wm::ANIMATE_NONE);
224}
225
226}  // namespace ash
227