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 31 namespace { 32 33 // Min height of the list of messages in the popup. 34 const int kMessageListMinHeight = 200; 35 // Top/bottom padding of the text items. 36 const int kPaddingVertical = 10; 37 38 bool 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 50 namespace ash { 51 namespace internal { 52 53 class 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. 77 class 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 174 class 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 238 class 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 279 TraySms::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 287 TraySms::~TraySms() { 288 Shell::GetInstance()->system_tray_notifier()->RemoveSmsObserver(this); 289 } 290 291 views::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 298 views::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 307 views::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 318 void TraySms::DestroyDefaultView() { 319 default_ = NULL; 320 } 321 322 void TraySms::DestroyDetailedView() { 323 detailed_ = NULL; 324 } 325 326 void TraySms::DestroyNotificationView() { 327 notification_ = NULL; 328 } 329 330 void TraySms::AddMessage(const base::DictionaryValue& message) { 331 messages_.Append(message.DeepCopy()); 332 Update(true); 333 } 334 335 bool 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 350 void TraySms::RemoveMessage(size_t index) { 351 if (index < messages_.GetSize()) 352 messages_.Remove(index, NULL); 353 } 354 355 void 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