OLD | NEW |
---|---|
(Empty) | |
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 "chrome/browser/ui/views/message_center/message_center_tray_host_win.h" | |
6 | |
7 #include "base/memory/singleton.h" | |
8 #include "base/utf_string_conversions.h" | |
9 #include "chrome/browser/browser_process.h" | |
10 #include "chrome/browser/status_icons/status_icon.h" | |
11 #include "chrome/browser/status_icons/status_tray.h" | |
12 #include "chrome/browser/ui/message_center/message_center_util.h" | |
13 #include "grit/theme_resources.h" | |
14 #include "ui/base/resource/resource_bundle.h" | |
15 #include "ui/base/win/hwnd_util.h" | |
16 #include "ui/gfx/image/image_skia_operations.h" | |
17 #include "ui/gfx/screen.h" | |
18 #include "ui/message_center/message_bubble_base.h" | |
19 #include "ui/message_center/message_center_tray.h" | |
20 #include "ui/views/widget/widget.h" | |
21 | |
22 namespace { | |
23 | |
24 // Tray constants | |
25 const int kPaddingFromLeftEdgeOfSystemTrayBottomAlignment = 8; | |
26 | |
27 gfx::Rect GetCornerAnchorRect(gfx::Size preferred_size) { | |
28 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); | |
29 gfx::Point cursor = screen->GetCursorScreenPoint(); | |
30 gfx::Rect rect = screen->GetPrimaryDisplay().work_area(); | |
31 rect.Inset(10, 5); | |
32 gfx::Point bottom_right( | |
33 rect.bottom_right().x() - preferred_size.width() / 2, | |
34 rect.bottom_right().y()); | |
35 return gfx::Rect(bottom_right, gfx::Size()); | |
36 } | |
37 | |
38 // GetMouseAnchorRect returns a rectangle that is near the cursor point, but | |
39 // whose behavior depends on where the Windows taskbar is. If it is on the | |
40 // top or bottom of the screen, we want the arrow to touch the edge of the | |
41 // taskbar directly above or below the mouse pointer and within the work area. | |
42 // Otherwise, position the anchor on the mouse cursor directly. | |
43 gfx::Rect GetMouseAnchorRect() { | |
44 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); | |
45 gfx::Point cursor = screen->GetCursorScreenPoint(); | |
46 gfx::Rect rect = screen->GetPrimaryDisplay().bounds(); | |
47 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); | |
48 | |
49 // Inset the rectangle by the taskbar width if it is on top or bottom. | |
50 rect.set_y(work_area.y()); | |
51 rect.set_height(work_area.height()); | |
52 | |
53 rect.Inset(kPaddingFromLeftEdgeOfSystemTrayBottomAlignment, 0); | |
54 | |
55 // Want to find a mouse point that is on the mouse cursor, unless the mouse is | |
56 // over the start menu and the start menu is on the top or bottom. | |
57 gfx::Rect mouse_anchor_rect(gfx::BoundingRect(cursor, rect.bottom_right())); | |
58 mouse_anchor_rect.set_height(0); | |
59 if (!rect.Contains(cursor)) | |
60 mouse_anchor_rect.AdjustToFit(rect); | |
61 mouse_anchor_rect.set_width(0); | |
62 return mouse_anchor_rect; | |
63 } | |
64 | |
65 } // namespace | |
66 | |
67 #if defined(ENABLE_MESSAGE_CENTER) | |
68 | |
69 namespace chrome { | |
70 | |
71 // static | |
72 ui::MessageCenterTrayDelegate* GetMessageCenterTray() { | |
73 return ui::MessageCenterTrayHostWin::GetInstance(); | |
74 } | |
75 | |
76 } // namespace chrome | |
77 | |
78 #endif | |
79 | |
80 | |
81 namespace ui { | |
82 | |
83 namespace internal { | |
84 | |
85 class WebNotificationBubbleWrapper | |
86 : public views::WidgetObserver, | |
87 public views::TrayBubbleView::Delegate { | |
88 public: | |
89 // Takes ownership of |bubble| and creates |bubble_wrapper_|. | |
90 WebNotificationBubbleWrapper(MessageCenterTrayHostWin* tray, | |
Pete Williamson
2013/01/17 19:07:45
Make this a platform specific class in its own fil
dewittj
2013/01/18 00:57:46
Done.
| |
91 message_center::MessageBubbleBase* bubble, | |
92 AnchorType anchor_type) | |
93 : tray_(tray) { | |
94 bubble_.reset(bubble); | |
95 | |
96 // Windows-specific initialization. | |
97 views::TrayBubbleView::AnchorAlignment anchor_alignment = | |
98 tray->GetAnchorAlignment(); | |
99 views::TrayBubbleView::InitParams init_params = | |
100 bubble->GetInitParams(anchor_alignment); | |
101 init_params.anchor_type = anchor_type; | |
102 init_params.close_on_deactivate = false; | |
103 init_params.arrow_alignment = | |
104 views::BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR; | |
105 // TODO(dewittj): Show big shadow without blocking clicks. | |
106 init_params.shadow = views::BubbleBorder::NO_SHADOW; | |
107 | |
108 bubble_view_ = views::TrayBubbleView::Create( | |
109 tray->GetBubbleWindowContainer(), NULL, this, &init_params); | |
110 | |
111 bubble_widget_ = views::BubbleDelegateView::CreateBubble(bubble_view_); | |
112 bubble_widget_->AddObserver(this); | |
113 bubble_widget_->StackAtTop(); | |
114 bubble_widget_->SetAlwaysOnTop(true); | |
115 bubble_widget_->Activate(); | |
116 bubble_view_->InitializeAndShowBubble(); | |
117 | |
118 bubble_view_->set_close_on_deactivate(true); | |
119 bubble->InitializeContents(bubble_view_); | |
120 } | |
121 ~WebNotificationBubbleWrapper() { | |
122 bubble_.reset(); | |
123 if (bubble_widget_) { | |
124 bubble_widget_->RemoveObserver(this); | |
125 bubble_widget_->Close(); | |
126 bubble_widget_ = NULL; | |
127 } | |
128 } | |
129 | |
130 // Overridden from views::WidgetObserver. | |
131 void OnWidgetClosing(views::Widget* widget) { | |
132 bubble_widget_->RemoveObserver(this); | |
133 bubble_widget_ = NULL; | |
134 tray_->HideBubbleWithView(bubble_view_); | |
135 } | |
136 | |
137 // TrayBubbleView::Delegate implementation. | |
138 // Called when the view is destroyed. Any pointers to the view should be | |
139 // cleared when this gets called. | |
140 virtual void BubbleViewDestroyed() { | |
141 bubble_->BubbleViewDestroyed(); | |
142 } | |
143 | |
144 // Called when the mouse enters/exits the view. | |
145 virtual void OnMouseEnteredView() { | |
146 bubble_->OnMouseEnteredView(); | |
147 }; | |
148 virtual void OnMouseExitedView() { | |
149 bubble_->OnMouseExitedView(); | |
150 } | |
151 | |
152 // Called from GetAccessibleState(); should return the appropriate | |
153 // accessible name for the bubble. | |
154 virtual string16 GetAccessibleNameForBubble() { | |
155 // TODO(dewittj): get a string resource. | |
156 return ASCIIToUTF16("Windows Notification Center"); | |
157 } | |
158 | |
159 // Passes responsibility for BubbleDelegateView::GetAnchorRect to the | |
160 // delegate. | |
161 virtual gfx::Rect GetAnchorRect(views::Widget* anchor_widget, | |
162 AnchorType anchor_type, | |
163 AnchorAlignment anchor_alignment) { | |
164 gfx::Size size = bubble_view_->GetPreferredSize(); | |
165 return tray_->GetAnchorRect(size, | |
166 anchor_type, | |
167 anchor_alignment); | |
168 } | |
169 | |
170 // Called when a bubble wants to hide/destroy itself (e.g. last visible | |
171 // child view was closed). | |
172 virtual void HideBubble(const views::TrayBubbleView* bubble_view) { | |
173 tray_->HideBubbleWithView(bubble_view); | |
174 } | |
175 | |
176 // Convenience accessors. | |
177 views::TrayBubbleView* bubble_view() const { return bubble_view_; } | |
178 views::Widget* bubble_widget() const { | |
179 return bubble_widget_; | |
180 } | |
181 message_center::MessageBubbleBase* bubble() const { return bubble_.get(); } | |
182 | |
183 private: | |
184 scoped_ptr<message_center::MessageBubbleBase> bubble_; | |
185 // Unowned. | |
186 views::TrayBubbleView* bubble_view_; | |
187 views::Widget* bubble_widget_; | |
188 MessageCenterTrayHostWin* tray_; | |
189 }; | |
190 | |
191 } // namespace internal | |
192 | |
193 // TODO(dewittj): Un-singleton. | |
194 MessageCenterTrayHostWin* MessageCenterTrayHostWin::GetInstance() { | |
195 return Singleton<MessageCenterTrayHostWin>::get(); | |
196 } | |
197 | |
198 MessageCenterTrayHostWin::MessageCenterTrayHostWin() | |
199 : status_icon_(NULL), | |
200 message_center_visible_(false) { | |
201 message_center_tray_ = new MessageCenterTray(this); | |
202 message_center_tray_->AddObserver(this); | |
203 } | |
204 | |
205 MessageCenterTrayHostWin::~MessageCenterTrayHostWin() { | |
206 message_center_tray_->RemoveObserver(this); | |
207 if (status_icon_ != NULL) { | |
208 status_icon_->RemoveObserver(this); | |
209 StatusTray * status_tray = g_browser_process->status_tray(); | |
210 status_tray->RemoveStatusIcon(status_icon_); | |
211 status_icon_ = NULL; | |
212 } | |
213 } | |
214 | |
215 message_center::MessageCenter* MessageCenterTrayHostWin::message_center() { | |
216 return message_center_tray_->message_center(); | |
217 } | |
218 | |
219 bool MessageCenterTrayHostWin::ShowPopups( | |
220 message_center::MessageBubbleBase* bubble) { | |
221 if (!CanShowPopups()) | |
222 return false; | |
223 popup_bubble_.reset(new internal::WebNotificationBubbleWrapper( | |
224 this, | |
225 bubble, | |
226 views::TrayBubbleView::ANCHOR_TYPE_BUBBLE)); | |
227 return true; | |
228 } | |
229 void MessageCenterTrayHostWin::HidePopups() { | |
230 popup_bubble_.reset(); | |
231 } | |
232 bool MessageCenterTrayHostWin::ShowMessageCenter( | |
233 message_center::MessageBubbleBase* bubble) { | |
234 // TODO(dewittj): CanShowMessageCenter. | |
235 | |
236 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); | |
237 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); | |
238 gfx::Point work_area_center = work_area.CenterPoint(); | |
239 gfx::Point anchor_center = message_center_anchor_rect_.CenterPoint(); | |
240 int max_height = 0; | |
241 if (work_area_center < anchor_center) | |
242 max_height = anchor_center.y() - work_area.origin().y(); | |
243 else | |
244 max_height = work_area.bottom() - message_center_anchor_rect_.bottom(); | |
245 bubble->SetMaxHeight(max_height); | |
246 | |
247 message_center_bubble_.reset(new internal::WebNotificationBubbleWrapper( | |
248 this, | |
249 bubble, | |
250 views::TrayBubbleView::ANCHOR_TYPE_TRAY)); | |
251 // TODO(dewittj): Prevent auto-hide? | |
252 return true; | |
253 } | |
254 | |
255 void MessageCenterTrayHostWin::HideMessageCenter() { | |
256 message_center_bubble_.reset(); | |
257 } | |
258 | |
259 void MessageCenterTrayHostWin::UpdateMessageCenter() { | |
260 if (message_center_bubble_.get()) | |
261 message_center_bubble_->bubble()->ScheduleUpdate(); | |
262 } | |
263 | |
264 void MessageCenterTrayHostWin::UpdatePopups() { | |
265 if (popup_bubble_.get()) | |
266 popup_bubble_->bubble()->ScheduleUpdate(); | |
267 }; | |
268 | |
269 | |
270 | |
271 void MessageCenterTrayHostWin::OnMessageCenterTrayChanged() { | |
272 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
273 bool has_notifications = message_center()->NotificationCount() > 0; | |
274 StatusTray* status_tray = g_browser_process->status_tray(); | |
275 if (has_notifications) { | |
276 if (status_icon_ == NULL) { | |
277 status_icon_ = status_tray->CreateStatusIcon(); | |
278 status_icon_->AddObserver(this); | |
279 } | |
280 // TODO(dewittj): Get some icons. | |
281 gfx::ImageSkia* icon = | |
282 rb.GetImageSkiaNamed(IDR_ALLOWED_NOTIFICATION); | |
283 if (message_center()->UnreadNotificationCount() > 0) { | |
284 status_icon_->SetImage(*icon); | |
285 } else { | |
286 status_icon_->SetImage( | |
287 gfx::ImageSkiaOperations::CreateTransparentImage(*icon, .5)); | |
288 } | |
289 } else if (status_icon_ != NULL) { | |
290 status_tray->RemoveStatusIcon(status_icon_); | |
291 status_icon_ = NULL; | |
292 } | |
293 } | |
294 | |
295 gfx::Rect MessageCenterTrayHostWin::GetAnchorRect( | |
296 gfx::Size preferred_size, | |
297 views::TrayBubbleView::AnchorType anchor_type, | |
298 views::TrayBubbleView::AnchorAlignment anchor_alignment) { | |
299 // |message_center_visible_| is set before the bubble is actually rendered, | |
300 // so the flag can be used to determine which anchor to use. | |
301 if (anchor_type == views::TrayBubbleView::ANCHOR_TYPE_TRAY) { | |
302 return message_center_anchor_rect_; | |
303 } | |
304 return GetCornerAnchorRect(preferred_size); | |
305 } | |
306 | |
307 bool MessageCenterTrayHostWin::CanShowPopups() { | |
Pete Williamson
2013/01/17 19:07:45
Check to see if we still need this method.
| |
308 // TODO(dewittj): This will eventually depend on whether quiet mode is active. | |
309 return true; | |
310 } | |
311 | |
312 views::TrayBubbleView::AnchorAlignment | |
313 MessageCenterTrayHostWin::GetAnchorAlignment() { | |
314 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); | |
315 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong. | |
316 gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds(); | |
317 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); | |
318 | |
319 if (work_area.height() < screen_bounds.height()) | |
320 return views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM; | |
321 if (work_area.x() > screen_bounds.x()) | |
322 return views::TrayBubbleView::ANCHOR_ALIGNMENT_LEFT; | |
323 return views::TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT; | |
324 } | |
325 | |
326 gfx::NativeView MessageCenterTrayHostWin::GetBubbleWindowContainer() { | |
327 return NULL; | |
328 } | |
329 | |
330 | |
331 void MessageCenterTrayHostWin::OnStatusIconClicked() { | |
332 UpdateAnchorRect(); | |
333 message_center_tray_->ToggleMessageCenterBubble(); | |
334 } | |
335 | |
336 void MessageCenterTrayHostWin::HideBubbleWithView( | |
337 const views::TrayBubbleView* bubble_view) { | |
338 if (message_center_bubble_.get() && | |
339 bubble_view == message_center_bubble_->bubble_view()) { | |
340 message_center_tray_->HideMessageCenterBubble(); | |
341 } else if (popup_bubble_.get() && | |
342 bubble_view == popup_bubble_->bubble_view()) { | |
343 message_center_tray_->HidePopupBubble(); | |
344 } | |
345 } | |
346 | |
347 void MessageCenterTrayHostWin::UpdateAnchorRect() { | |
348 message_center_anchor_rect_ = GetMouseAnchorRect(); | |
349 } | |
350 | |
351 } // namespace ui | |
OLD | NEW |