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