system_tray.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/tray/system_tray.h"
6
7#include "ash/ash_switches.h"
8#include "ash/shelf/shelf_layout_manager.h"
9#include "ash/shell.h"
10#include "ash/shell/panel_window.h"
11#include "ash/shell_window_ids.h"
12#include "ash/system/bluetooth/tray_bluetooth.h"
13#include "ash/system/brightness/tray_brightness.h"
14#include "ash/system/date/tray_date.h"
15#include "ash/system/drive/tray_drive.h"
16#include "ash/system/ime/tray_ime.h"
17#include "ash/system/locale/tray_locale.h"
18#include "ash/system/logout_button/tray_logout_button.h"
19#include "ash/system/monitor/tray_monitor.h"
20#include "ash/system/session_length_limit/tray_session_length_limit.h"
21#include "ash/system/status_area_widget.h"
22#include "ash/system/tray/system_tray_delegate.h"
23#include "ash/system/tray/system_tray_item.h"
24#include "ash/system/tray/tray_bubble_wrapper.h"
25#include "ash/system/tray/tray_constants.h"
26#include "ash/system/tray_accessibility.h"
27#include "ash/system/tray_caps_lock.h"
28#include "ash/system/tray_update.h"
29#include "ash/system/user/login_status.h"
30#include "ash/system/user/tray_user.h"
31#include "base/command_line.h"
32#include "base/logging.h"
33#include "base/strings/utf_string_conversions.h"
34#include "base/timer/timer.h"
35#include "grit/ash_strings.h"
36#include "ui/aura/root_window.h"
37#include "ui/base/events/event_constants.h"
38#include "ui/base/l10n/l10n_util.h"
39#include "ui/compositor/layer.h"
40#include "ui/gfx/canvas.h"
41#include "ui/gfx/screen.h"
42#include "ui/gfx/skia_util.h"
43#include "ui/views/border.h"
44#include "ui/views/controls/label.h"
45#include "ui/views/layout/box_layout.h"
46#include "ui/views/layout/fill_layout.h"
47#include "ui/views/view.h"
48
49#if defined(OS_CHROMEOS)
50#include "ash/system/chromeos/audio/tray_audio.h"
51#include "ash/system/chromeos/enterprise/tray_enterprise.h"
52#include "ash/system/chromeos/managed/tray_locally_managed_user.h"
53#include "ash/system/chromeos/network/tray_network.h"
54#include "ash/system/chromeos/network/tray_sms.h"
55#include "ash/system/chromeos/network/tray_vpn.h"
56#include "ash/system/chromeos/power/tray_power.h"
57#include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
58#include "ash/system/chromeos/screen_security/screen_share_tray_item.h"
59#include "ash/system/chromeos/settings/tray_settings.h"
60#include "ash/system/chromeos/tray_display.h"
61#include "ui/message_center/message_center.h"
62#endif
63
64using views::TrayBubbleView;
65
66namespace ash {
67
68// The minimum width of the system tray menu width.
69const int kMinimumSystemTrayMenuWidth = 300;
70
71namespace internal {
72
73// Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
74// instances for a bubble.
75
76class SystemBubbleWrapper {
77 public:
78  // Takes ownership of |bubble|.
79  explicit SystemBubbleWrapper(internal::SystemTrayBubble* bubble)
80      : bubble_(bubble) {
81  }
82
83  // Initializes the bubble view and creates |bubble_wrapper_|.
84  void InitView(TrayBackgroundView* tray,
85                views::View* anchor,
86                TrayBubbleView::InitParams* init_params) {
87    DCHECK(anchor);
88    user::LoginStatus login_status =
89        Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
90    bubble_->InitView(anchor, login_status, init_params);
91    bubble_wrapper_.reset(
92        new internal::TrayBubbleWrapper(tray, bubble_->bubble_view()));
93    if (ash::switches::UseAlternateShelfLayout()) {
94      // The system bubble should not have an arrow.
95      bubble_->bubble_view()->SetArrowPaintType(
96          views::BubbleBorder::PAINT_NONE);
97    }
98  }
99
100  // Convenience accessors:
101  SystemTrayBubble* bubble() const { return bubble_.get(); }
102  SystemTrayBubble::BubbleType bubble_type() const {
103    return bubble_->bubble_type();
104  }
105  TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
106
107 private:
108  scoped_ptr<internal::SystemTrayBubble> bubble_;
109  scoped_ptr<internal::TrayBubbleWrapper> bubble_wrapper_;
110
111  DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper);
112};
113
114}  // namespace internal
115
116// SystemTray
117
118using internal::SystemTrayBubble;
119
120SystemTray::SystemTray(internal::StatusAreaWidget* status_area_widget)
121    : internal::TrayBackgroundView(status_area_widget),
122      items_(),
123      default_bubble_height_(0),
124      hide_notifications_(false),
125      tray_accessibility_(NULL) {
126  SetContentsBackground();
127}
128
129SystemTray::~SystemTray() {
130  // Destroy any child views that might have back pointers before ~View().
131  system_bubble_.reset();
132  notification_bubble_.reset();
133  for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
134       it != items_.end();
135       ++it) {
136    (*it)->DestroyTrayView();
137  }
138}
139
140void SystemTray::InitializeTrayItems(SystemTrayDelegate* delegate) {
141  internal::TrayBackgroundView::Initialize();
142  CreateItems(delegate);
143}
144
145void SystemTray::CreateItems(SystemTrayDelegate* delegate) {
146#if !defined(OS_WIN)
147  AddTrayItem(new internal::TraySessionLengthLimit(this));
148  AddTrayItem(new internal::TrayLogoutButton(this));
149  // In multi-profile user mode we can have multiple user tiles.
150  ash::Shell* shell = ash::Shell::GetInstance();
151  int maximum_user_profiles =
152      shell->delegate()->IsMultiProfilesEnabled() ?
153          shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() :
154          0;
155  // Note: We purposely use one more item then logged in users to account for
156  // the additional separator.
157  for (int i = 0; i <= maximum_user_profiles; i++)
158    AddTrayItem(new internal::TrayUser(this, i));
159
160#endif
161#if defined(OS_CHROMEOS)
162  AddTrayItem(new internal::TrayEnterprise(this));
163  AddTrayItem(new internal::TrayLocallyManagedUser(this));
164#endif
165  AddTrayItem(new internal::TrayIME(this));
166  tray_accessibility_ = new internal::TrayAccessibility(this);
167  AddTrayItem(tray_accessibility_);
168#if defined(OS_CHROMEOS)
169  AddTrayItem(
170      new internal::TrayPower(this, message_center::MessageCenter::Get()));
171#endif
172#if defined(OS_CHROMEOS)
173  AddTrayItem(new internal::TrayNetwork(this));
174  AddTrayItem(new internal::TrayVPN(this));
175  AddTrayItem(new internal::TraySms(this));
176#endif
177#if !defined(OS_WIN)
178  AddTrayItem(new internal::TrayBluetooth(this));
179#endif
180  AddTrayItem(new internal::TrayDrive(this));
181  AddTrayItem(new internal::TrayLocale(this));
182#if defined(OS_CHROMEOS)
183  AddTrayItem(new internal::TrayDisplay(this));
184  AddTrayItem(new internal::ScreenCaptureTrayItem(this));
185  AddTrayItem(new internal::ScreenShareTrayItem(this));
186  AddTrayItem(new internal::TrayAudio(this));
187#endif
188#if !defined(OS_WIN)
189  AddTrayItem(new internal::TrayBrightness(this));
190  AddTrayItem(new internal::TrayCapsLock(this));
191#endif
192#if defined(OS_CHROMEOS)
193  AddTrayItem(new internal::TraySettings(this));
194#endif
195  AddTrayItem(new internal::TrayUpdate(this));
196  AddTrayItem(new internal::TrayDate(this));
197
198#if defined(OS_LINUX)
199  // Add memory monitor if enabled.
200  CommandLine* cmd = CommandLine::ForCurrentProcess();
201  if (cmd->HasSwitch(ash::switches::kAshEnableMemoryMonitor))
202    AddTrayItem(new internal::TrayMonitor(this));
203#endif
204
205  SetVisible(ash::Shell::GetInstance()->system_tray_delegate()->
206      GetTrayVisibilityOnStartup());
207}
208
209void SystemTray::AddTrayItem(SystemTrayItem* item) {
210  items_.push_back(item);
211
212  SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
213  views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus());
214  item->UpdateAfterShelfAlignmentChange(shelf_alignment());
215
216  if (tray_item) {
217    tray_container()->AddChildViewAt(tray_item, 0);
218    PreferredSizeChanged();
219    tray_item_map_[item] = tray_item;
220  }
221}
222
223void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
224  NOTIMPLEMENTED();
225}
226
227const std::vector<SystemTrayItem*>& SystemTray::GetTrayItems() const {
228  return items_.get();
229}
230
231void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
232  ShowDefaultViewWithOffset(creation_type,
233                            TrayBubbleView::InitParams::kArrowDefaultOffset);
234}
235
236void SystemTray::ShowDetailedView(SystemTrayItem* item,
237                                  int close_delay,
238                                  bool activate,
239                                  BubbleCreationType creation_type) {
240  std::vector<SystemTrayItem*> items;
241  items.push_back(item);
242  ShowItems(items, true, activate, creation_type, GetTrayXOffset(item));
243  if (system_bubble_)
244    system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
245}
246
247void SystemTray::SetDetailedViewCloseDelay(int close_delay) {
248  if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED))
249    system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
250}
251
252void SystemTray::HideDetailedView(SystemTrayItem* item) {
253  if (item != detailed_item_)
254    return;
255  DestroySystemBubble();
256  UpdateNotificationBubble();
257}
258
259void SystemTray::ShowNotificationView(SystemTrayItem* item) {
260  if (std::find(notification_items_.begin(), notification_items_.end(), item)
261      != notification_items_.end())
262    return;
263  notification_items_.push_back(item);
264  UpdateNotificationBubble();
265}
266
267void SystemTray::HideNotificationView(SystemTrayItem* item) {
268  std::vector<SystemTrayItem*>::iterator found_iter =
269      std::find(notification_items_.begin(), notification_items_.end(), item);
270  if (found_iter == notification_items_.end())
271    return;
272  notification_items_.erase(found_iter);
273  // Only update the notification bubble if visible (i.e. don't create one).
274  if (notification_bubble_)
275    UpdateNotificationBubble();
276}
277
278void SystemTray::UpdateAfterLoginStatusChange(user::LoginStatus login_status) {
279  DestroySystemBubble();
280  UpdateNotificationBubble();
281
282  for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
283      it != items_.end();
284      ++it) {
285    (*it)->UpdateAfterLoginStatusChange(login_status);
286  }
287
288  // Items default to SHELF_ALIGNMENT_BOTTOM. Update them if the initial
289  // position of the shelf differs.
290  if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM)
291    UpdateAfterShelfAlignmentChange(shelf_alignment());
292
293  SetVisible(true);
294  PreferredSizeChanged();
295}
296
297void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
298  for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
299      it != items_.end();
300      ++it) {
301    (*it)->UpdateAfterShelfAlignmentChange(alignment);
302  }
303}
304
305void SystemTray::SetHideNotifications(bool hide_notifications) {
306  if (notification_bubble_)
307    notification_bubble_->bubble()->SetVisible(!hide_notifications);
308  hide_notifications_ = hide_notifications;
309}
310
311bool SystemTray::ShouldShowLauncher() const {
312  return system_bubble_.get() && system_bubble_->bubble()->ShouldShowLauncher();
313}
314
315bool SystemTray::HasSystemBubble() const {
316  return system_bubble_.get() != NULL;
317}
318
319bool SystemTray::HasNotificationBubble() const {
320  return notification_bubble_.get() != NULL;
321}
322
323bool SystemTray::IsPressed() {
324  return HasSystemBubble();
325}
326
327internal::SystemTrayBubble* SystemTray::GetSystemBubble() {
328  if (!system_bubble_)
329    return NULL;
330  return system_bubble_->bubble();
331}
332
333bool SystemTray::IsAnyBubbleVisible() const {
334  return ((system_bubble_.get() &&
335           system_bubble_->bubble()->IsVisible()) ||
336          (notification_bubble_.get() &&
337           notification_bubble_->bubble()->IsVisible()));
338}
339
340bool SystemTray::IsMouseInNotificationBubble() const {
341  if (!notification_bubble_)
342    return false;
343  return notification_bubble_->bubble_view()->GetBoundsInScreen().Contains(
344      Shell::GetScreen()->GetCursorScreenPoint());
345}
346
347bool SystemTray::CloseSystemBubble() const {
348  if (!system_bubble_)
349    return false;
350  system_bubble_->bubble()->Close();
351  return true;
352}
353
354bool SystemTray::CloseNotificationBubbleForTest() const {
355  if (!notification_bubble_)
356    return false;
357  notification_bubble_->bubble()->Close();
358  return true;
359}
360
361// Private methods.
362
363bool SystemTray::HasSystemBubbleType(SystemTrayBubble::BubbleType type) {
364  DCHECK(type != SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
365  return system_bubble_.get() && system_bubble_->bubble_type() == type;
366}
367
368void SystemTray::DestroySystemBubble() {
369  system_bubble_.reset();
370  detailed_item_ = NULL;
371}
372
373void SystemTray::DestroyNotificationBubble() {
374  notification_bubble_.reset();
375  status_area_widget()->SetHideWebNotifications(false);
376}
377
378int SystemTray::GetTrayXOffset(SystemTrayItem* item) const {
379  // Don't attempt to align the arrow if the shelf is on the left or right.
380  if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM &&
381      shelf_alignment() != SHELF_ALIGNMENT_TOP)
382    return TrayBubbleView::InitParams::kArrowDefaultOffset;
383
384  std::map<SystemTrayItem*, views::View*>::const_iterator it =
385      tray_item_map_.find(item);
386  if (it == tray_item_map_.end())
387    return TrayBubbleView::InitParams::kArrowDefaultOffset;
388
389  const views::View* item_view = it->second;
390  if (item_view->bounds().IsEmpty()) {
391    // The bounds of item could be still empty if it does not have a visible
392    // tray view. In that case, use the default (minimum) offset.
393    return TrayBubbleView::InitParams::kArrowDefaultOffset;
394  }
395
396  gfx::Point point(item_view->width() / 2, 0);
397  ConvertPointToWidget(item_view, &point);
398  return point.x();
399}
400
401void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type,
402                                           int arrow_offset) {
403  ShowItems(items_.get(), false, true, creation_type, arrow_offset);
404}
405
406void SystemTray::ShowItems(const std::vector<SystemTrayItem*>& items,
407                           bool detailed,
408                           bool can_activate,
409                           BubbleCreationType creation_type,
410                           int arrow_offset) {
411  // No system tray bubbles in kiosk mode.
412  if (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus() ==
413      ash::user::LOGGED_IN_KIOSK_APP) {
414    return;
415  }
416
417  // Destroy any existing bubble and create a new one.
418  SystemTrayBubble::BubbleType bubble_type = detailed ?
419      SystemTrayBubble::BUBBLE_TYPE_DETAILED :
420      SystemTrayBubble::BUBBLE_TYPE_DEFAULT;
421
422  // Destroy the notification bubble here so that it doesn't get rebuilt
423  // while we add items to the main bubble_ (e.g. in HideNotificationView).
424  notification_bubble_.reset();
425
426  if (system_bubble_.get() && creation_type == BUBBLE_USE_EXISTING) {
427    system_bubble_->bubble()->UpdateView(items, bubble_type);
428  } else {
429    // The menu width is fixed, and it is a per language setting.
430    int menu_width = std::max(kMinimumSystemTrayMenuWidth,
431        Shell::GetInstance()->system_tray_delegate()->GetSystemTrayMenuWidth());
432
433    TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY,
434                                           GetAnchorAlignment(),
435                                           menu_width,
436                                           kTrayPopupMaxWidth);
437    init_params.can_activate = can_activate;
438    if (detailed) {
439      // This is the case where a volume control or brightness control bubble
440      // is created.
441      init_params.max_height = default_bubble_height_;
442      init_params.arrow_color = kBackgroundColor;
443    } else {
444      init_params.arrow_color = kHeaderBackgroundColor;
445    }
446    init_params.arrow_offset = arrow_offset;
447    // For Volume and Brightness we don't want to show an arrow when
448    // they are shown in a bubble by themselves.
449    init_params.arrow_paint_type = views::BubbleBorder::PAINT_NORMAL;
450    if (items.size() == 1 && items[0]->ShouldHideArrow())
451      init_params.arrow_paint_type = views::BubbleBorder::PAINT_TRANSPARENT;
452    SystemTrayBubble* bubble = new SystemTrayBubble(this, items, bubble_type);
453    system_bubble_.reset(new internal::SystemBubbleWrapper(bubble));
454    system_bubble_->InitView(this, tray_container(), &init_params);
455  }
456  // Save height of default view for creating detailed views directly.
457  if (!detailed)
458    default_bubble_height_ = system_bubble_->bubble_view()->height();
459
460  if (detailed && items.size() > 0)
461    detailed_item_ = items[0];
462  else
463    detailed_item_ = NULL;
464
465  UpdateNotificationBubble();  // State changed, re-create notifications.
466  status_area_widget()->SetHideWebNotifications(true);
467  GetShelfLayoutManager()->UpdateAutoHideState();
468}
469
470void SystemTray::UpdateNotificationBubble() {
471  // Only show the notification buble if we have notifications.
472  if (notification_items_.empty()) {
473    DestroyNotificationBubble();
474    return;
475  }
476  // Destroy the existing bubble before constructing a new one.
477  notification_bubble_.reset();
478  SystemTrayBubble* notification_bubble;
479  notification_bubble = new SystemTrayBubble(
480      this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
481  views::View* anchor;
482  TrayBubbleView::AnchorType anchor_type;
483  // Tray items might want to show notifications while we are creating and
484  // initializing the |system_bubble_| - but it might not be fully initialized
485  // when coming here - this would produce a crashed like crbug.com/247416.
486  // As such we check the existence of the widget here.
487  if (system_bubble_.get() &&
488      system_bubble_->bubble_view() &&
489      system_bubble_->bubble_view()->GetWidget()) {
490    anchor = system_bubble_->bubble_view();
491    anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE;
492  } else {
493    anchor = tray_container();
494    anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY;
495  }
496  TrayBubbleView::InitParams init_params(anchor_type,
497                                         GetAnchorAlignment(),
498                                         kTrayPopupMinWidth,
499                                         kTrayPopupMaxWidth);
500  init_params.arrow_color = kBackgroundColor;
501  init_params.arrow_offset = GetTrayXOffset(notification_items_[0]);
502  notification_bubble_.reset(
503      new internal::SystemBubbleWrapper(notification_bubble));
504  notification_bubble_->InitView(this, anchor, &init_params);
505
506  if (notification_bubble->bubble_view()->child_count() == 0) {
507    // It is possible that none of the items generated actual notifications.
508    DestroyNotificationBubble();
509    return;
510  }
511  if (hide_notifications_)
512    notification_bubble->SetVisible(false);
513  else
514    status_area_widget()->SetHideWebNotifications(true);
515}
516
517void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
518  if (alignment == shelf_alignment())
519    return;
520  internal::TrayBackgroundView::SetShelfAlignment(alignment);
521  UpdateAfterShelfAlignmentChange(alignment);
522  // Destroy any existing bubble so that it is rebuilt correctly.
523  system_bubble_.reset();
524  // Rebuild any notification bubble.
525  if (notification_bubble_) {
526    notification_bubble_.reset();
527    UpdateNotificationBubble();
528  }
529}
530
531void SystemTray::AnchorUpdated() {
532  if (notification_bubble_) {
533    notification_bubble_->bubble_view()->UpdateBubble();
534    // Ensure that the notification buble is above the launcher/status area.
535    notification_bubble_->bubble_view()->GetWidget()->StackAtTop();
536    UpdateBubbleViewArrow(notification_bubble_->bubble_view());
537  }
538  if (system_bubble_) {
539    system_bubble_->bubble_view()->UpdateBubble();
540    if (!ash::switches::UseAlternateShelfLayout())
541      UpdateBubbleViewArrow(system_bubble_->bubble_view());
542  }
543}
544
545base::string16 SystemTray::GetAccessibleNameForTray() {
546  return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME);
547}
548
549void SystemTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {
550  if (system_bubble_.get() && bubble_view == system_bubble_->bubble_view()) {
551    DestroySystemBubble();
552    UpdateNotificationBubble();  // State changed, re-create notifications.
553    GetShelfLayoutManager()->UpdateAutoHideState();
554  } else if (notification_bubble_.get() &&
555             bubble_view == notification_bubble_->bubble_view()) {
556    DestroyNotificationBubble();
557  }
558}
559
560bool SystemTray::ClickedOutsideBubble() {
561  if (!system_bubble_)
562    return false;
563  HideBubbleWithView(system_bubble_->bubble_view());
564  return true;
565}
566
567void SystemTray::BubbleViewDestroyed() {
568  if (system_bubble_) {
569    system_bubble_->bubble()->DestroyItemViews();
570    system_bubble_->bubble()->BubbleViewDestroyed();
571  }
572}
573
574void SystemTray::OnMouseEnteredView() {
575  if (system_bubble_)
576    system_bubble_->bubble()->StopAutoCloseTimer();
577}
578
579void SystemTray::OnMouseExitedView() {
580  if (system_bubble_)
581    system_bubble_->bubble()->RestartAutoCloseTimer();
582}
583
584base::string16 SystemTray::GetAccessibleNameForBubble() {
585  return GetAccessibleNameForTray();
586}
587
588gfx::Rect SystemTray::GetAnchorRect(
589    views::Widget* anchor_widget,
590    TrayBubbleView::AnchorType anchor_type,
591    TrayBubbleView::AnchorAlignment anchor_alignment) {
592  return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
593}
594
595void SystemTray::HideBubble(const TrayBubbleView* bubble_view) {
596  HideBubbleWithView(bubble_view);
597}
598
599bool SystemTray::PerformAction(const ui::Event& event) {
600  // If we're already showing the default view, hide it; otherwise, show it
601  // (and hide any popup that's currently shown).
602  if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
603    system_bubble_->bubble()->Close();
604  } else {
605    int arrow_offset = TrayBubbleView::InitParams::kArrowDefaultOffset;
606    if (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_TAP) {
607      const ui::LocatedEvent& located_event =
608          static_cast<const ui::LocatedEvent&>(event);
609      if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM ||
610          shelf_alignment() == SHELF_ALIGNMENT_TOP) {
611        gfx::Point point(located_event.x(), 0);
612        ConvertPointToWidget(this, &point);
613        arrow_offset = point.x();
614      }
615    }
616    ShowDefaultViewWithOffset(BUBBLE_CREATE_NEW, arrow_offset);
617  }
618  return true;
619}
620
621}  // namespace ash
622