tray_sms.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 "ash/system/chromeos/network/tray_sms.h"
6
7#include "ash/shell.h"
8#include "ash/system/tray/fixed_sized_scroll_view.h"
9#include "ash/system/tray/system_tray.h"
10#include "ash/system/tray/system_tray_bubble.h"
11#include "ash/system/tray/system_tray_notifier.h"
12#include "ash/system/tray/tray_constants.h"
13#include "ash/system/tray/tray_details_view.h"
14#include "ash/system/tray/tray_item_more.h"
15#include "ash/system/tray/tray_item_view.h"
16#include "ash/system/tray/tray_notification_view.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/strings/utf_string_conversions.h"
19#include "grit/ash_resources.h"
20#include "grit/ash_strings.h"
21#include "ui/base/l10n/l10n_util.h"
22#include "ui/base/resource/resource_bundle.h"
23#include "ui/views/bubble/tray_bubble_view.h"
24#include "ui/views/controls/image_view.h"
25#include "ui/views/controls/label.h"
26#include "ui/views/layout/box_layout.h"
27#include "ui/views/layout/fill_layout.h"
28#include "ui/views/layout/grid_layout.h"
29#include "ui/views/view.h"
30
31namespace {
32
33// Min height of the list of messages in the popup.
34const int kMessageListMinHeight = 200;
35// Top/bottom padding of the text items.
36const int kPaddingVertical = 10;
37
38bool GetMessageFromDictionary(const base::DictionaryValue* message,
39                              std::string* number,
40                              std::string* text) {
41  if (!message->GetStringWithoutPathExpansion(ash::kSmsNumberKey, number))
42    return false;
43  if (!message->GetStringWithoutPathExpansion(ash::kSmsTextKey, text))
44    return false;
45  return true;
46}
47
48}  // namespace
49
50namespace ash {
51namespace internal {
52
53class TraySms::SmsDefaultView : public TrayItemMore {
54 public:
55  explicit SmsDefaultView(TraySms* owner)
56      : TrayItemMore(owner, true) {
57    SetImage(ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
58        IDR_AURA_UBER_TRAY_SMS));
59    Update();
60  }
61
62  virtual ~SmsDefaultView() {}
63
64  void Update() {
65    int message_count = static_cast<TraySms*>(owner())->messages().GetSize();
66    base::string16 label = l10n_util::GetStringFUTF16(
67        IDS_ASH_STATUS_TRAY_SMS_MESSAGES, base::IntToString16(message_count));
68    SetLabel(label);
69    SetAccessibleName(label);
70  }
71
72 private:
73  DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
74};
75
76// An entry (row) in SmsDetailedView or NotificationView.
77class TraySms::SmsMessageView : public views::View,
78                                public views::ButtonListener {
79 public:
80  enum ViewType {
81    VIEW_DETAILED,
82    VIEW_NOTIFICATION
83  };
84
85  SmsMessageView(TraySms* owner,
86                 ViewType view_type,
87                 size_t index,
88                 const std::string& number,
89                 const std::string& message)
90      : owner_(owner),
91        index_(index) {
92    number_label_ = new views::Label(
93        l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_SMS_NUMBER,
94                                   UTF8ToUTF16(number)));
95    number_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
96    number_label_->SetFont(
97        number_label_->font().DeriveFont(0, gfx::Font::BOLD));
98
99    message_label_ = new views::Label(UTF8ToUTF16(message));
100    message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
101    message_label_->SetMultiLine(true);
102
103    if (view_type == VIEW_DETAILED)
104      LayoutDetailedView();
105    else
106      LayoutNotificationView();
107  }
108
109  virtual ~SmsMessageView() {
110  }
111
112  // Overridden from ButtonListener.
113  virtual void ButtonPressed(views::Button* sender,
114                             const ui::Event& event) OVERRIDE {
115    owner_->RemoveMessage(index_);
116    owner_->Update(false);
117  }
118
119 private:
120  void LayoutDetailedView() {
121    views::ImageButton* close_button = new views::ImageButton(this);
122    close_button->SetImage(views::CustomButton::STATE_NORMAL,
123        ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
124            IDR_AURA_WINDOW_CLOSE));
125    const int msg_width = owner_->system_tray()->GetSystemBubble()->
126        bubble_view()->GetPreferredSize().width() -
127            (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2);
128    message_label_->SizeToFit(msg_width);
129
130    views::GridLayout* layout = new views::GridLayout(this);
131    SetLayoutManager(layout);
132
133    views::ColumnSet* columns = layout->AddColumnSet(0);
134
135    // Message
136    columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal);
137    columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
138                       0 /* resize percent */,
139                       views::GridLayout::FIXED, msg_width, msg_width);
140
141    // Close button
142    columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
143                       0, /* resize percent */
144                       views::GridLayout::FIXED,
145                       kNotificationIconWidth, kNotificationIconWidth);
146
147
148    layout->AddPaddingRow(0, kPaddingVertical);
149    layout->StartRow(0, 0);
150    layout->AddView(number_label_);
151    layout->AddView(close_button, 1, 2);  // 2 rows for icon
152    layout->StartRow(0, 0);
153    layout->AddView(message_label_);
154
155    layout->AddPaddingRow(0, kPaddingVertical);
156  }
157
158  void LayoutNotificationView() {
159    SetLayoutManager(
160        new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
161    AddChildView(number_label_);
162    message_label_->SizeToFit(kTrayNotificationContentsWidth);
163    AddChildView(message_label_);
164  }
165
166  TraySms* owner_;
167  size_t index_;
168  views::Label* number_label_;
169  views::Label* message_label_;
170
171  DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
172};
173
174class TraySms::SmsDetailedView : public TrayDetailsView,
175                                 public ViewClickListener {
176 public:
177  explicit SmsDetailedView(TraySms* owner)
178      : TrayDetailsView(owner) {
179    Init();
180    Update();
181  }
182
183  virtual ~SmsDetailedView() {
184  }
185
186  void Init() {
187    CreateScrollableList();
188    CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
189  }
190
191  void Update() {
192    UpdateMessageList();
193    Layout();
194    SchedulePaint();
195  }
196
197  // Overridden from views::View.
198  virtual gfx::Size GetPreferredSize() OVERRIDE {
199    gfx::Size preferred_size = TrayDetailsView::GetPreferredSize();
200    if (preferred_size.height() < kMessageListMinHeight)
201      preferred_size.set_height(kMessageListMinHeight);
202    return preferred_size;
203  }
204
205 private:
206  void UpdateMessageList() {
207    const base::ListValue& messages =
208        static_cast<TraySms*>(owner())->messages();
209    scroll_content()->RemoveAllChildViews(true);
210    for (size_t index = 0; index < messages.GetSize(); ++index) {
211      const base::DictionaryValue* message = NULL;
212      if (!messages.GetDictionary(index, &message)) {
213        LOG(ERROR) << "SMS message not a dictionary at: " << index;
214        continue;
215      }
216      std::string number, text;
217      if (!GetMessageFromDictionary(message, &number, &text)) {
218        LOG(ERROR) << "Error parsing SMS message";
219        continue;
220      }
221      SmsMessageView* msgview = new SmsMessageView(
222          static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
223          number, text);
224      scroll_content()->AddChildView(msgview);
225    }
226    scroller()->Layout();
227  }
228
229  // Overridden from ViewClickListener.
230  virtual void OnViewClicked(views::View* sender) OVERRIDE {
231    if (sender == footer()->content())
232      owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
233  }
234
235  DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
236};
237
238class TraySms::SmsNotificationView : public TrayNotificationView {
239 public:
240  SmsNotificationView(TraySms* owner,
241                      size_t message_index,
242                      const std::string& number,
243                      const std::string& text)
244      : TrayNotificationView(owner, IDR_AURA_UBER_TRAY_SMS),
245        message_index_(message_index) {
246    SmsMessageView* message_view = new SmsMessageView(
247        owner, SmsMessageView::VIEW_NOTIFICATION, message_index_, number, text);
248    InitView(message_view);
249  }
250
251  void Update(size_t message_index,
252              const std::string& number,
253              const std::string& text) {
254    SmsMessageView* message_view = new SmsMessageView(
255        tray_sms(), SmsMessageView::VIEW_NOTIFICATION,
256        message_index_, number, text);
257    UpdateView(message_view);
258  }
259
260  // Overridden from TrayNotificationView:
261  virtual void OnClose() OVERRIDE {
262    tray_sms()->RemoveMessage(message_index_);
263  }
264
265  virtual void OnClickAction() OVERRIDE {
266    owner()->PopupDetailedView(0, true);
267  }
268
269 private:
270  TraySms* tray_sms() {
271    return static_cast<TraySms*>(owner());
272  }
273
274  size_t message_index_;
275
276  DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
277};
278
279TraySms::TraySms(SystemTray* system_tray)
280    : SystemTrayItem(system_tray),
281      default_(NULL),
282      detailed_(NULL),
283      notification_(NULL) {
284  Shell::GetInstance()->system_tray_notifier()->AddSmsObserver(this);
285}
286
287TraySms::~TraySms() {
288  Shell::GetInstance()->system_tray_notifier()->RemoveSmsObserver(this);
289}
290
291views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
292  CHECK(default_ == NULL);
293  default_ = new SmsDefaultView(this);
294  default_->SetVisible(!messages_.empty());
295  return default_;
296}
297
298views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
299  CHECK(detailed_ == NULL);
300  HideNotificationView();
301  if (messages_.empty())
302    return NULL;
303  detailed_ = new SmsDetailedView(this);
304  return detailed_;
305}
306
307views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
308  CHECK(notification_ == NULL);
309  if (detailed_)
310    return NULL;
311  size_t index;
312  std::string number, text;
313  if (GetLatestMessage(&index, &number, &text))
314    notification_ = new SmsNotificationView(this, index, number, text);
315  return notification_;
316}
317
318void TraySms::DestroyDefaultView() {
319  default_ = NULL;
320}
321
322void TraySms::DestroyDetailedView() {
323  detailed_ = NULL;
324}
325
326void TraySms::DestroyNotificationView() {
327  notification_ = NULL;
328}
329
330void TraySms::AddMessage(const base::DictionaryValue& message) {
331  messages_.Append(message.DeepCopy());
332  Update(true);
333}
334
335bool TraySms::GetLatestMessage(size_t* index,
336                               std::string* number,
337                               std::string* text) {
338  if (messages_.empty())
339    return false;
340  DictionaryValue* message;
341  size_t message_index = messages_.GetSize() - 1;
342  if (!messages_.GetDictionary(message_index, &message))
343    return false;
344  if (!GetMessageFromDictionary(message, number, text))
345    return false;
346  *index = message_index;
347  return true;
348}
349
350void TraySms::RemoveMessage(size_t index) {
351  if (index < messages_.GetSize())
352    messages_.Remove(index, NULL);
353}
354
355void TraySms::Update(bool notify) {
356  if (messages_.empty()) {
357    if (default_)
358      default_->SetVisible(false);
359    if (detailed_)
360      HideDetailedView();
361    HideNotificationView();
362  } else {
363    if (default_) {
364      default_->SetVisible(true);
365      default_->Update();
366    }
367    if (detailed_)
368      detailed_->Update();
369    if (notification_) {
370      size_t index;
371      std::string number, text;
372      if (GetLatestMessage(&index, &number, &text))
373        notification_->Update(index, number, text);
374    } else if (notify) {
375      ShowNotificationView();
376    }
377  }
378}
379
380}  // namespace internal
381}  // namespace ash
382