Home | History | Annotate | Download | only in message_center
      1 // Copyright 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 "chrome/browser/ui/views/message_center/web_notification_tray.h"
      6 
      7 #include "base/i18n/number_formatting.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "base/strings/string16.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/browser_process.h"
     12 #include "chrome/browser/status_icons/status_icon.h"
     13 #include "chrome/browser/status_icons/status_icon_menu_model.h"
     14 #include "chrome/browser/status_icons/status_tray.h"
     15 #include "chrome/common/pref_names.h"
     16 #include "chrome/grit/chromium_strings.h"
     17 #include "chrome/grit/generated_resources.h"
     18 #include "content/public/browser/notification_service.h"
     19 #include "grit/theme_resources.h"
     20 #include "ui/base/l10n/l10n_util.h"
     21 #include "ui/base/resource/resource_bundle.h"
     22 #include "ui/gfx/canvas.h"
     23 #include "ui/gfx/image/image_skia_operations.h"
     24 #include "ui/gfx/rect.h"
     25 #include "ui/gfx/screen.h"
     26 #include "ui/gfx/size.h"
     27 #include "ui/message_center/message_center_tray.h"
     28 #include "ui/message_center/message_center_tray_delegate.h"
     29 #include "ui/message_center/views/desktop_popup_alignment_delegate.h"
     30 #include "ui/message_center/views/message_popup_collection.h"
     31 #include "ui/strings/grit/ui_strings.h"
     32 #include "ui/views/widget/widget.h"
     33 
     34 namespace {
     35 
     36 // Tray constants
     37 const int kScreenEdgePadding = 2;
     38 
     39 // Number of pixels the message center is offset from the mouse.
     40 const int kMouseOffset = 5;
     41 
     42 // Menu commands
     43 const int kToggleQuietMode = 0;
     44 const int kEnableQuietModeHour = 1;
     45 const int kEnableQuietModeDay = 2;
     46 
     47 gfx::ImageSkia* GetIcon(int unread_count, bool is_quiet_mode) {
     48   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     49   int resource_id = IDR_NOTIFICATION_TRAY_EMPTY;
     50 
     51   if (unread_count) {
     52     if (is_quiet_mode)
     53       resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION;
     54     else
     55       resource_id = IDR_NOTIFICATION_TRAY_ATTENTION;
     56   } else if (is_quiet_mode) {
     57     resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY;
     58   }
     59 
     60   return rb.GetImageSkiaNamed(resource_id);
     61 }
     62 
     63 }  // namespace
     64 
     65 namespace message_center {
     66 
     67 namespace internal {
     68 
     69 // Gets the position of the taskbar from the work area bounds. Returns
     70 // ALIGNMENT_NONE if position cannot be found.
     71 Alignment GetTaskbarAlignment() {
     72   gfx::Screen* screen = gfx::Screen::GetNativeScreen();
     73   // TODO(dewittj): It's possible GetPrimaryDisplay is wrong.
     74   gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds();
     75   gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
     76   work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
     77 
     78   // Comparing the work area to the screen bounds gives us the location of the
     79   // taskbar.  If the work area is exactly the same as the screen bounds,
     80   // we are unable to locate the taskbar so we say we don't know it's alignment.
     81   if (work_area.height() < screen_bounds.height()) {
     82     if (work_area.y() > screen_bounds.y())
     83       return ALIGNMENT_TOP;
     84     return ALIGNMENT_BOTTOM;
     85   }
     86   if (work_area.width() < screen_bounds.width()) {
     87     if (work_area.x() > screen_bounds.x())
     88       return ALIGNMENT_LEFT;
     89     return ALIGNMENT_RIGHT;
     90   }
     91 
     92   return ALIGNMENT_NONE;
     93 }
     94 
     95 gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) {
     96   gfx::Point center_point = rect.CenterPoint();
     97   gfx::Point rv;
     98 
     99   if (query.x() > center_point.x())
    100     rv.set_x(rect.right());
    101   else
    102     rv.set_x(rect.x());
    103 
    104   if (query.y() > center_point.y())
    105     rv.set_y(rect.bottom());
    106   else
    107     rv.set_y(rect.y());
    108 
    109   return rv;
    110 }
    111 
    112 // Gets the corner of the screen where the message center should pop up.
    113 Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) {
    114   gfx::Point center = work_area.CenterPoint();
    115 
    116   Alignment anchor_alignment =
    117       center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM;
    118   anchor_alignment =
    119       (Alignment)(anchor_alignment |
    120                   (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT));
    121 
    122   return anchor_alignment;
    123 }
    124 
    125 }  // namespace internal
    126 
    127 MessageCenterTrayDelegate* CreateMessageCenterTray() {
    128   return new WebNotificationTray(g_browser_process->local_state());
    129 }
    130 
    131 WebNotificationTray::WebNotificationTray(PrefService* local_state)
    132     : message_center_delegate_(NULL),
    133       status_icon_(NULL),
    134       status_icon_menu_(NULL),
    135       should_update_tray_content_(true) {
    136   message_center_tray_.reset(
    137       new MessageCenterTray(this, g_browser_process->message_center()));
    138   last_quiet_mode_state_ = message_center()->IsQuietMode();
    139   alignment_delegate_.reset(new message_center::DesktopPopupAlignmentDelegate);
    140   popup_collection_.reset(new message_center::MessagePopupCollection(
    141       NULL, message_center(), message_center_tray_.get(),
    142       alignment_delegate_.get()));
    143 
    144 #if defined(OS_WIN)
    145   // |local_state| can be NULL in tests.
    146   if (local_state) {
    147     did_force_tray_visible_.reset(new BooleanPrefMember());
    148     did_force_tray_visible_->Init(prefs::kMessageCenterForcedOnTaskbar,
    149                                   local_state);
    150   }
    151 #endif
    152   title_ = l10n_util::GetStringFUTF16(
    153       IDS_MESSAGE_CENTER_FOOTER_WITH_PRODUCT_TITLE,
    154       l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
    155 }
    156 
    157 WebNotificationTray::~WebNotificationTray() {
    158   // Reset this early so that delegated events during destruction don't cause
    159   // problems.
    160   popup_collection_.reset();
    161   message_center_tray_.reset();
    162   DestroyStatusIcon();
    163 }
    164 
    165 message_center::MessageCenter* WebNotificationTray::message_center() {
    166   return message_center_tray_->message_center();
    167 }
    168 
    169 bool WebNotificationTray::ShowPopups() {
    170   alignment_delegate_->StartObserving(gfx::Screen::GetNativeScreen());
    171   popup_collection_->DoUpdateIfPossible();
    172   return true;
    173 }
    174 
    175 void WebNotificationTray::HidePopups() {
    176   DCHECK(popup_collection_.get());
    177   popup_collection_->MarkAllPopupsShown();
    178 }
    179 
    180 bool WebNotificationTray::ShowMessageCenter() {
    181   message_center_delegate_ =
    182       new MessageCenterWidgetDelegate(this,
    183                                       message_center_tray_.get(),
    184                                       false,  // settings initally invisible
    185                                       GetPositionInfo(),
    186                                       title_);
    187 
    188   return true;
    189 }
    190 
    191 void WebNotificationTray::HideMessageCenter() {
    192   if (message_center_delegate_) {
    193     views::Widget* widget = message_center_delegate_->GetWidget();
    194     if (widget)
    195       widget->Close();
    196   }
    197 }
    198 
    199 bool WebNotificationTray::ShowNotifierSettings() {
    200   if (message_center_delegate_) {
    201     message_center_delegate_->SetSettingsVisible(true);
    202     return true;
    203   }
    204   message_center_delegate_ =
    205       new MessageCenterWidgetDelegate(this,
    206                                       message_center_tray_.get(),
    207                                       true,  // settings initally visible
    208                                       GetPositionInfo(),
    209                                       title_);
    210 
    211   return true;
    212 }
    213 
    214 bool WebNotificationTray::IsContextMenuEnabled() const {
    215   // It can always return true because the notifications are invisible if
    216   // the context menu shouldn't be enabled, such as in the lock screen.
    217   return true;
    218 }
    219 
    220 void WebNotificationTray::OnMessageCenterTrayChanged() {
    221   if (status_icon_) {
    222     bool quiet_mode_state = message_center()->IsQuietMode();
    223     if (last_quiet_mode_state_ != quiet_mode_state) {
    224       last_quiet_mode_state_ = quiet_mode_state;
    225 
    226       // Quiet mode has changed, update the quiet mode menu.
    227       status_icon_menu_->SetCommandIdChecked(kToggleQuietMode,
    228                                              quiet_mode_state);
    229     }
    230   } else if (message_center()->NotificationCount() == 0) {
    231     // If there's no existing status icon and we still don't have any
    232     // notifications to display, nothing needs to be done.
    233     return;
    234   }
    235 
    236   // See the comments in ash/system/web_notification/web_notification_tray.cc
    237   // for why PostTask.
    238   should_update_tray_content_ = true;
    239   base::MessageLoop::current()->PostTask(
    240       FROM_HERE,
    241       base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr()));
    242 }
    243 
    244 void WebNotificationTray::OnStatusIconClicked() {
    245   // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
    246   gfx::Screen* screen = gfx::Screen::GetNativeScreen();
    247   mouse_click_point_ = screen->GetCursorScreenPoint();
    248   message_center_tray_->ToggleMessageCenterBubble();
    249 }
    250 
    251 void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
    252   if (command_id == kToggleQuietMode) {
    253     bool in_quiet_mode = message_center()->IsQuietMode();
    254     message_center()->SetQuietMode(!in_quiet_mode);
    255     return;
    256   }
    257   base::TimeDelta expires_in = command_id == kEnableQuietModeDay
    258                                    ? base::TimeDelta::FromDays(1)
    259                                    : base::TimeDelta::FromHours(1);
    260   message_center()->EnterQuietModeWithExpire(expires_in);
    261 }
    262 
    263 void WebNotificationTray::UpdateStatusIcon() {
    264   if (!should_update_tray_content_)
    265     return;
    266   should_update_tray_content_ = false;
    267 
    268   int unread_notifications = message_center()->UnreadNotificationCount();
    269 
    270   base::string16 tool_tip;
    271   if (unread_notifications > 0) {
    272     base::string16 str_unread_count = base::FormatNumber(unread_notifications);
    273     tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD,
    274                                           str_unread_count);
    275   } else {
    276     tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP);
    277   }
    278 
    279   if (message_center()->GetVisibleNotifications().empty()) {
    280     DestroyStatusIcon();
    281     return;
    282   }
    283 
    284   gfx::ImageSkia* icon_image = GetIcon(
    285       unread_notifications,
    286       message_center()->IsQuietMode());
    287 
    288   if (status_icon_) {
    289     status_icon_->SetImage(*icon_image);
    290     status_icon_->SetToolTip(tool_tip);
    291     return;
    292   }
    293 
    294   CreateStatusIcon(*icon_image, tool_tip);
    295 }
    296 
    297 void WebNotificationTray::SendHideMessageCenter() {
    298   message_center_tray_->HideMessageCenterBubble();
    299 }
    300 
    301 void WebNotificationTray::MarkMessageCenterHidden() {
    302   if (message_center_delegate_) {
    303     message_center_tray_->MarkMessageCenterHidden();
    304     message_center_delegate_ = NULL;
    305   }
    306 }
    307 
    308 PositionInfo WebNotificationTray::GetPositionInfo() {
    309   PositionInfo pos_info;
    310 
    311   gfx::Screen* screen = gfx::Screen::GetNativeScreen();
    312   gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
    313   work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
    314 
    315   gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_);
    316 
    317   pos_info.taskbar_alignment = internal::GetTaskbarAlignment();
    318 
    319   // We assume the taskbar is either at the top or at the bottom if we are not
    320   // able to find it.
    321   if (pos_info.taskbar_alignment == ALIGNMENT_NONE) {
    322     if (mouse_click_point_.y() > corner.y())
    323       pos_info.taskbar_alignment = ALIGNMENT_TOP;
    324     else
    325       pos_info.taskbar_alignment = ALIGNMENT_BOTTOM;
    326   }
    327 
    328   pos_info.message_center_alignment =
    329       internal::GetAnchorAlignment(work_area, corner);
    330 
    331   pos_info.inital_anchor_point = corner;
    332   pos_info.max_height = work_area.height();
    333 
    334   if (work_area.Contains(mouse_click_point_)) {
    335     // Message center is in the work area. So position it few pixels above the
    336     // mouse click point if alignemnt is towards bottom and few pixels below if
    337     // alignment is towards top.
    338     pos_info.inital_anchor_point.set_y(
    339         mouse_click_point_.y() +
    340         (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset
    341                                                               : kMouseOffset));
    342 
    343     // Subtract the distance between mouse click point and the closest
    344     // (insetted) edge from the max height to show the message center within the
    345     // (insetted) work area bounds. Also subtract the offset from the mouse
    346     // click point we added earlier.
    347     pos_info.max_height -=
    348         std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset;
    349   }
    350   return pos_info;
    351 }
    352 
    353 MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
    354   return message_center_tray_.get();
    355 }
    356 
    357 void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image,
    358                                            const base::string16& tool_tip) {
    359   if (status_icon_)
    360     return;
    361 
    362   StatusTray* status_tray = g_browser_process->status_tray();
    363   if (!status_tray)
    364     return;
    365 
    366   status_icon_ = status_tray->CreateStatusIcon(
    367       StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip);
    368   if (!status_icon_)
    369     return;
    370 
    371   status_icon_->AddObserver(this);
    372   AddQuietModeMenu(status_icon_);
    373 }
    374 
    375 void WebNotificationTray::DestroyStatusIcon() {
    376   if (!status_icon_)
    377     return;
    378 
    379   status_icon_->RemoveObserver(this);
    380   StatusTray* status_tray = g_browser_process->status_tray();
    381   if (status_tray)
    382     status_tray->RemoveStatusIcon(status_icon_);
    383   status_icon_menu_ = NULL;
    384   status_icon_ = NULL;
    385 }
    386 
    387 void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) {
    388   DCHECK(status_icon);
    389 
    390   scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
    391   menu->AddCheckItem(kToggleQuietMode,
    392                      l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE));
    393   menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode());
    394   menu->AddItem(kEnableQuietModeHour,
    395                 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR));
    396   menu->AddItem(kEnableQuietModeDay,
    397                 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY));
    398 
    399   status_icon_menu_ = menu.get();
    400   status_icon->SetContextMenu(menu.Pass());
    401 }
    402 
    403 MessageCenterWidgetDelegate*
    404 WebNotificationTray::GetMessageCenterWidgetDelegateForTest() {
    405   return message_center_delegate_;
    406 }
    407 
    408 }  // namespace message_center
    409