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 "ash/wm/maximize_bubble_controller.h" | |
6 | |
7 #include "ash/shell.h" | |
8 #include "ash/shell_delegate.h" | |
9 #include "ash/shell_window_ids.h" | |
10 #include "ash/wm/window_animations.h" | |
11 #include "ash/wm/workspace/frame_maximize_button.h" | |
12 #include "base/timer/timer.h" | |
13 #include "grit/ash_resources.h" | |
14 #include "grit/ash_strings.h" | |
15 #include "third_party/skia/include/core/SkPath.h" | |
16 #include "ui/aura/window.h" | |
17 #include "ui/base/animation/animation.h" | |
18 #include "ui/base/l10n/l10n_util.h" | |
19 #include "ui/base/resource/resource_bundle.h" | |
20 #include "ui/gfx/canvas.h" | |
21 #include "ui/gfx/path.h" | |
22 #include "ui/gfx/screen.h" | |
23 #include "ui/views/bubble/bubble_delegate.h" | |
24 #include "ui/views/bubble/bubble_frame_view.h" | |
25 #include "ui/views/controls/button/button.h" | |
26 #include "ui/views/controls/button/image_button.h" | |
27 #include "ui/views/controls/label.h" | |
28 #include "ui/views/layout/box_layout.h" | |
29 #include "ui/views/mouse_watcher.h" | |
30 #include "ui/views/widget/widget.h" | |
31 | |
32 namespace { | |
33 | |
34 // The spacing between two buttons. | |
35 const int kLayoutSpacing = -1; | |
36 | |
37 // The background color. | |
38 const SkColor kBubbleBackgroundColor = 0xFF141414; | |
39 | |
40 // The text color within the bubble. | |
41 const SkColor kBubbleTextColor = SK_ColorWHITE; | |
42 | |
43 // The line width of the bubble. | |
44 const int kLineWidth = 1; | |
45 | |
46 // The spacing for the top and bottom of the info label. | |
47 const int kLabelSpacing = 4; | |
48 | |
49 // The pixel dimensions of the arrow. | |
50 const int kArrowHeight = 10; | |
51 const int kArrowWidth = 20; | |
52 | |
53 // The animation offset in y for the bubble when appearing. | |
54 const int kBubbleAnimationOffsetY = 5; | |
55 | |
56 class MaximizeBubbleBorder : public views::BubbleBorder { | |
57 public: | |
58 MaximizeBubbleBorder(views::View* content_view, views::View* anchor); | |
59 | |
60 virtual ~MaximizeBubbleBorder() {} | |
61 | |
62 // Get the mouse active area of the window. | |
63 void GetMask(gfx::Path* mask); | |
64 | |
65 // Overridden from views::BubbleBorder to match the design specs. | |
66 virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to, | |
67 const gfx::Size& contents_size) const OVERRIDE; | |
68 | |
69 // Overridden from views::Border. | |
70 virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE; | |
71 | |
72 private: | |
73 // Note: Animations can continue after then main window frame was destroyed. | |
74 // To avoid this problem, the owning screen metrics get extracted upon | |
75 // creation. | |
76 gfx::Size anchor_size_; | |
77 gfx::Point anchor_screen_origin_; | |
78 views::View* content_view_; | |
79 | |
80 DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder); | |
81 }; | |
82 | |
83 MaximizeBubbleBorder::MaximizeBubbleBorder(views::View* content_view, | |
84 views::View* anchor) | |
85 : views::BubbleBorder(views::BubbleBorder::TOP_RIGHT, | |
86 views::BubbleBorder::NO_SHADOW, | |
87 kBubbleBackgroundColor), | |
88 anchor_size_(anchor->size()), | |
89 anchor_screen_origin_(0, 0), | |
90 content_view_(content_view) { | |
91 views::View::ConvertPointToScreen(anchor, &anchor_screen_origin_); | |
92 set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); | |
93 } | |
94 | |
95 void MaximizeBubbleBorder::GetMask(gfx::Path* mask) { | |
96 gfx::Insets inset = GetInsets(); | |
97 // Note: Even though the tip could be added as activatable, it is left out | |
98 // since it would not change the action behavior in any way plus it makes | |
99 // more sense to keep the focus on the underlying button for clicks. | |
100 int left = inset.left() - kLineWidth; | |
101 int right = inset.left() + content_view_->width() + kLineWidth; | |
102 int top = inset.top() - kLineWidth; | |
103 int bottom = inset.top() + content_view_->height() + kLineWidth; | |
104 mask->moveTo(left, top); | |
105 mask->lineTo(right, top); | |
106 mask->lineTo(right, bottom); | |
107 mask->lineTo(left, bottom); | |
108 mask->lineTo(left, top); | |
109 mask->close(); | |
110 } | |
111 | |
112 gfx::Rect MaximizeBubbleBorder::GetBounds( | |
113 const gfx::Rect& position_relative_to, | |
114 const gfx::Size& contents_size) const { | |
115 gfx::Size border_size(contents_size); | |
116 gfx::Insets insets = GetInsets(); | |
117 border_size.Enlarge(insets.width(), insets.height()); | |
118 | |
119 // Position the bubble to center the box on the anchor. | |
120 int x = (-border_size.width() + anchor_size_.width()) / 2; | |
121 // Position the bubble under the anchor, overlapping the arrow with it. | |
122 int y = anchor_size_.height() - insets.top(); | |
123 | |
124 gfx::Point view_origin(x + anchor_screen_origin_.x(), | |
125 y + anchor_screen_origin_.y()); | |
126 | |
127 return gfx::Rect(view_origin, border_size); | |
128 } | |
129 | |
130 void MaximizeBubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) { | |
131 gfx::Insets inset = GetInsets(); | |
132 | |
133 // Draw the border line around everything. | |
134 int y = inset.top(); | |
135 // Top | |
136 canvas->FillRect(gfx::Rect(inset.left(), | |
137 y - kLineWidth, | |
138 content_view_->width(), | |
139 kLineWidth), | |
140 kBubbleBackgroundColor); | |
141 // Bottom | |
142 canvas->FillRect(gfx::Rect(inset.left(), | |
143 y + content_view_->height(), | |
144 content_view_->width(), | |
145 kLineWidth), | |
146 kBubbleBackgroundColor); | |
147 // Left | |
148 canvas->FillRect(gfx::Rect(inset.left() - kLineWidth, | |
149 y - kLineWidth, | |
150 kLineWidth, | |
151 content_view_->height() + 2 * kLineWidth), | |
152 kBubbleBackgroundColor); | |
153 // Right | |
154 canvas->FillRect(gfx::Rect(inset.left() + content_view_->width(), | |
155 y - kLineWidth, | |
156 kLineWidth, | |
157 content_view_->height() + 2 * kLineWidth), | |
158 kBubbleBackgroundColor); | |
159 | |
160 // Draw the arrow afterwards covering the border. | |
161 SkPath path; | |
162 path.incReserve(4); | |
163 // The center of the tip should be in the middle of the button. | |
164 int tip_x = inset.left() + content_view_->width() / 2; | |
165 int left_base_x = tip_x - kArrowWidth / 2; | |
166 int left_base_y = y; | |
167 int tip_y = left_base_y - kArrowHeight; | |
168 path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y)); | |
169 path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y)); | |
170 path.lineTo(SkIntToScalar(left_base_x + kArrowWidth), | |
171 SkIntToScalar(left_base_y)); | |
172 | |
173 SkPaint paint; | |
174 paint.setStyle(SkPaint::kFill_Style); | |
175 paint.setColor(kBubbleBackgroundColor); | |
176 canvas->DrawPath(path, paint); | |
177 } | |
178 | |
179 } // namespace | |
180 | |
181 namespace ash { | |
182 | |
183 class BubbleContentsButtonRow; | |
184 class BubbleContentsView; | |
185 class BubbleDialogButton; | |
186 | |
187 // The mouse watcher host which makes sure that the bubble does not get closed | |
188 // while the mouse cursor is over the maximize button or the balloon content. | |
189 // Note: This object gets destroyed when the MouseWatcher gets destroyed. | |
190 class BubbleMouseWatcherHost: public views::MouseWatcherHost { | |
191 public: | |
192 explicit BubbleMouseWatcherHost(MaximizeBubbleController::Bubble* bubble) | |
193 : bubble_(bubble) {} | |
194 virtual ~BubbleMouseWatcherHost() {} | |
195 | |
196 // Implementation of MouseWatcherHost. | |
197 virtual bool Contains(const gfx::Point& screen_point, | |
198 views::MouseWatcherHost::MouseEventType type) OVERRIDE; | |
199 private: | |
200 MaximizeBubbleController::Bubble* bubble_; | |
201 | |
202 DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost); | |
203 }; | |
204 | |
205 // The class which creates and manages the bubble menu element. | |
206 // It creates a 'bubble border' and the content accordingly. | |
207 // Note: Since the SnapSizer will show animations on top of the maximize button | |
208 // this menu gets created as a separate window and the SnapSizer will be | |
209 // created underneath this window. | |
210 class MaximizeBubbleController::Bubble : public views::BubbleDelegateView, | |
211 public views::MouseWatcherListener { | |
212 public: | |
213 explicit Bubble(MaximizeBubbleController* owner, int appearance_delay_ms_); | |
214 virtual ~Bubble() {} | |
215 | |
216 // The window of the menu under which the SnapSizer will get created. | |
217 aura::Window* GetBubbleWindow(); | |
218 | |
219 // Overridden from views::BubbleDelegateView. | |
220 virtual gfx::Rect GetAnchorRect() OVERRIDE; | |
221 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; | |
222 virtual bool CanActivate() const OVERRIDE { return false; } | |
223 | |
224 // Overridden from views::WidgetDelegateView. | |
225 virtual bool WidgetHasHitTestMask() const OVERRIDE; | |
226 virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE; | |
227 | |
228 // Implementation of MouseWatcherListener. | |
229 virtual void MouseMovedOutOfHost() OVERRIDE; | |
230 | |
231 // Implementation of MouseWatcherHost. | |
232 virtual bool Contains(const gfx::Point& screen_point, | |
233 views::MouseWatcherHost::MouseEventType type); | |
234 | |
235 // Overridden from views::View. | |
236 virtual gfx::Size GetPreferredSize() OVERRIDE; | |
237 | |
238 // Overridden from views::Widget::Observer. | |
239 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE; | |
240 | |
241 // Called from the controller class to indicate that the menu should get | |
242 // destroyed. | |
243 virtual void ControllerRequestsCloseAndDelete(); | |
244 | |
245 // Called from the owning class to change the menu content to the given | |
246 // |snap_type| so that the user knows what is selected. | |
247 void SetSnapType(SnapType snap_type); | |
248 | |
249 // Get the owning MaximizeBubbleController. This might return NULL in case | |
250 // of an asynchronous shutdown. | |
251 MaximizeBubbleController* controller() const { return owner_; } | |
252 | |
253 // Added for unit test: Retrieve the button for an action. | |
254 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE. | |
255 views::CustomButton* GetButtonForUnitTest(SnapType state); | |
256 | |
257 private: | |
258 // True if the shut down has been initiated. | |
259 bool shutting_down_; | |
260 | |
261 // Our owning class. | |
262 MaximizeBubbleController* owner_; | |
263 | |
264 // The widget which contains our menu and the bubble border. | |
265 views::Widget* bubble_widget_; | |
266 | |
267 // The content accessor of the menu. | |
268 BubbleContentsView* contents_view_; | |
269 | |
270 // The bubble border. | |
271 MaximizeBubbleBorder* bubble_border_; | |
272 | |
273 // The rectangle before the animation starts. | |
274 gfx::Rect initial_position_; | |
275 | |
276 // The mouse watcher which takes care of out of window hover events. | |
277 scoped_ptr<views::MouseWatcher> mouse_watcher_; | |
278 | |
279 // The fade delay - if 0 it will show / hide immediately. | |
280 const int appearance_delay_ms_; | |
281 | |
282 DISALLOW_COPY_AND_ASSIGN(Bubble); | |
283 }; | |
284 | |
285 // A class that creates all buttons and put them into a view. | |
286 class BubbleContentsButtonRow : public views::View, | |
287 public views::ButtonListener { | |
288 public: | |
289 explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble* bubble); | |
290 | |
291 virtual ~BubbleContentsButtonRow() {} | |
292 | |
293 // Overridden from ButtonListener. | |
294 virtual void ButtonPressed(views::Button* sender, | |
295 const ui::Event& event) OVERRIDE; | |
296 // Called from BubbleDialogButton. | |
297 void ButtonHovered(BubbleDialogButton* sender); | |
298 | |
299 // Added for unit test: Retrieve the button for an action. | |
300 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE. | |
301 views::CustomButton* GetButtonForUnitTest(SnapType state); | |
302 | |
303 MaximizeBubbleController::Bubble* bubble() { return bubble_; } | |
304 | |
305 private: | |
306 // Functions to add the left and right maximize / restore buttons. | |
307 void AddMaximizeLeftButton(); | |
308 void AddMaximizeRightButton(); | |
309 void AddMinimizeButton(); | |
310 | |
311 // The owning object which gets notifications. | |
312 MaximizeBubbleController::Bubble* bubble_; | |
313 | |
314 // The created buttons for our menu. | |
315 BubbleDialogButton* left_button_; | |
316 BubbleDialogButton* minimize_button_; | |
317 BubbleDialogButton* right_button_; | |
318 | |
319 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow); | |
320 }; | |
321 | |
322 // A class which creates the content of the bubble: The buttons, and the label. | |
323 class BubbleContentsView : public views::View { | |
324 public: | |
325 explicit BubbleContentsView(MaximizeBubbleController::Bubble* bubble); | |
326 | |
327 virtual ~BubbleContentsView() {} | |
328 | |
329 // Set the label content to reflect the currently selected |snap_type|. | |
330 // This function can be executed through the frame maximize button as well as | |
331 // through hover operations. | |
332 void SetSnapType(SnapType snap_type); | |
333 | |
334 // Added for unit test: Retrieve the button for an action. | |
335 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE. | |
336 views::CustomButton* GetButtonForUnitTest(SnapType state) { | |
337 return buttons_view_->GetButtonForUnitTest(state); | |
338 } | |
339 | |
340 private: | |
341 // The owning class. | |
342 MaximizeBubbleController::Bubble* bubble_; | |
343 | |
344 // The object which owns all the buttons. | |
345 BubbleContentsButtonRow* buttons_view_; | |
346 | |
347 // The label object which shows the user the selected action. | |
348 views::Label* label_view_; | |
349 | |
350 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView); | |
351 }; | |
352 | |
353 // The image button gets overridden to be able to capture mouse hover events. | |
354 // The constructor also assigns all button states and | |
355 class BubbleDialogButton : public views::ImageButton { | |
356 public: | |
357 explicit BubbleDialogButton( | |
358 BubbleContentsButtonRow* button_row_listener, | |
359 int normal_image, | |
360 int hovered_image, | |
361 int pressed_image); | |
362 virtual ~BubbleDialogButton() {} | |
363 | |
364 // CustomButton overrides: | |
365 virtual void OnMouseCaptureLost() OVERRIDE; | |
366 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; | |
367 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; | |
368 virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE; | |
369 | |
370 private: | |
371 // The creating class which needs to get notified in case of a hover event. | |
372 BubbleContentsButtonRow* button_row_; | |
373 | |
374 DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton); | |
375 }; | |
376 | |
377 MaximizeBubbleController::Bubble::Bubble( | |
378 MaximizeBubbleController* owner, | |
379 int appearance_delay_ms) | |
380 : views::BubbleDelegateView(owner->frame_maximize_button(), | |
381 views::BubbleBorder::TOP_RIGHT), | |
382 shutting_down_(false), | |
383 owner_(owner), | |
384 bubble_widget_(NULL), | |
385 contents_view_(NULL), | |
386 bubble_border_(NULL), | |
387 appearance_delay_ms_(appearance_delay_ms) { | |
388 set_margins(gfx::Insets()); | |
389 | |
390 // The window needs to be owned by the root so that the SnapSizer does not | |
391 // cover it upon animation. | |
392 aura::Window* parent = Shell::GetContainer( | |
393 Shell::GetTargetRootWindow(), | |
394 internal::kShellWindowId_ShelfContainer); | |
395 set_parent_window(parent); | |
396 | |
397 set_notify_enter_exit_on_child(true); | |
398 set_adjust_if_offscreen(false); | |
399 SetPaintToLayer(true); | |
400 set_color(kBubbleBackgroundColor); | |
401 set_close_on_deactivate(false); | |
402 set_background( | |
403 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); | |
404 | |
405 SetLayoutManager(new views::BoxLayout( | |
406 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing)); | |
407 | |
408 contents_view_ = new BubbleContentsView(this); | |
409 AddChildView(contents_view_); | |
410 | |
411 // Note that the returned widget has an observer which points to our | |
412 // functions. | |
413 bubble_widget_ = views::BubbleDelegateView::CreateBubble(this); | |
414 bubble_widget_->set_focus_on_creation(false); | |
415 | |
416 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); | |
417 bubble_widget_->non_client_view()->frame_view()->set_background(NULL); | |
418 | |
419 bubble_border_ = new MaximizeBubbleBorder(this, anchor_view()); | |
420 GetBubbleFrameView()->SetBubbleBorder(bubble_border_); | |
421 GetBubbleFrameView()->set_background(NULL); | |
422 | |
423 // Recalculate size with new border. | |
424 SizeToContents(); | |
425 | |
426 if (!appearance_delay_ms_) | |
427 GetWidget()->Show(); | |
428 else | |
429 StartFade(true); | |
430 | |
431 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction( | |
432 ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE); | |
433 | |
434 mouse_watcher_.reset(new views::MouseWatcher( | |
435 new BubbleMouseWatcherHost(this), | |
436 this)); | |
437 mouse_watcher_->Start(); | |
438 } | |
439 | |
440 bool BubbleMouseWatcherHost::Contains( | |
441 const gfx::Point& screen_point, | |
442 views::MouseWatcherHost::MouseEventType type) { | |
443 return bubble_->Contains(screen_point, type); | |
444 } | |
445 | |
446 aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() { | |
447 return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL; | |
448 } | |
449 | |
450 gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() { | |
451 if (!owner_) | |
452 return gfx::Rect(); | |
453 | |
454 gfx::Rect anchor_rect = | |
455 owner_->frame_maximize_button()->GetBoundsInScreen(); | |
456 return anchor_rect; | |
457 } | |
458 | |
459 void MaximizeBubbleController::Bubble::AnimationProgressed( | |
460 const ui::Animation* animation) { | |
461 // First do everything needed for the fade by calling the base function. | |
462 BubbleDelegateView::AnimationProgressed(animation); | |
463 // When fading in we are done. | |
464 if (!shutting_down_) | |
465 return; | |
466 // Upon fade out an additional shift is required. | |
467 int shift = animation->CurrentValueBetween(kBubbleAnimationOffsetY, 0); | |
468 gfx::Rect rect = initial_position_; | |
469 | |
470 rect.set_y(rect.y() + shift); | |
471 bubble_widget_->GetNativeWindow()->SetBounds(rect); | |
472 } | |
473 | |
474 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const { | |
475 return bubble_border_ != NULL; | |
476 } | |
477 | |
478 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask( | |
479 gfx::Path* mask) const { | |
480 DCHECK(mask); | |
481 DCHECK(bubble_border_); | |
482 bubble_border_->GetMask(mask); | |
483 } | |
484 | |
485 void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() { | |
486 if (!owner_ || shutting_down_) | |
487 return; | |
488 // When we leave the bubble, we might be still be in gesture mode or over | |
489 // the maximize button. So only close if none of the other cases apply. | |
490 if (!owner_->frame_maximize_button()->is_snap_enabled()) { | |
491 gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint(); | |
492 if (!owner_->frame_maximize_button()->GetBoundsInScreen().Contains( | |
493 screen_location)) { | |
494 owner_->RequestDestructionThroughOwner(); | |
495 } | |
496 } | |
497 } | |
498 | |
499 bool MaximizeBubbleController::Bubble::Contains( | |
500 const gfx::Point& screen_point, | |
501 views::MouseWatcherHost::MouseEventType type) { | |
502 if (!owner_ || shutting_down_) | |
503 return false; | |
504 bool inside_button = | |
505 owner_->frame_maximize_button()->GetBoundsInScreen().Contains( | |
506 screen_point); | |
507 if (!owner_->frame_maximize_button()->is_snap_enabled() && inside_button) { | |
508 SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL ? | |
509 SNAP_RESTORE : SNAP_MAXIMIZE); | |
510 return true; | |
511 } | |
512 // Check if either a gesture is taking place (=> bubble stays no matter what | |
513 // the mouse does) or the mouse is over the maximize button or the bubble | |
514 // content. | |
515 return (owner_->frame_maximize_button()->is_snap_enabled() || | |
516 inside_button || | |
517 contents_view_->GetBoundsInScreen().Contains(screen_point)); | |
518 } | |
519 | |
520 gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() { | |
521 return contents_view_->GetPreferredSize(); | |
522 } | |
523 | |
524 void MaximizeBubbleController::Bubble::OnWidgetDestroying( | |
525 views::Widget* widget) { | |
526 if (bubble_widget_ == widget) { | |
527 mouse_watcher_->Stop(); | |
528 | |
529 if (owner_) { | |
530 // If the bubble destruction was triggered by some other external | |
531 // influence then ourselves, the owner needs to be informed that the menu | |
532 // is gone. | |
533 shutting_down_ = true; | |
534 owner_->RequestDestructionThroughOwner(); | |
535 owner_ = NULL; | |
536 } | |
537 } | |
538 BubbleDelegateView::OnWidgetDestroying(widget); | |
539 } | |
540 | |
541 void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() { | |
542 // This only gets called from the owning base class once it is deleted. | |
543 if (shutting_down_) | |
544 return; | |
545 shutting_down_ = true; | |
546 owner_ = NULL; | |
547 | |
548 // Close the widget asynchronously after the hide animation is finished. | |
549 initial_position_ = bubble_widget_->GetNativeWindow()->bounds(); | |
550 if (!appearance_delay_ms_) | |
551 bubble_widget_->CloseNow(); | |
552 else | |
553 StartFade(false); | |
554 } | |
555 | |
556 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) { | |
557 if (contents_view_) | |
558 contents_view_->SetSnapType(snap_type); | |
559 } | |
560 | |
561 views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest( | |
562 SnapType state) { | |
563 return contents_view_->GetButtonForUnitTest(state); | |
564 } | |
565 | |
566 BubbleContentsButtonRow::BubbleContentsButtonRow( | |
567 MaximizeBubbleController::Bubble* bubble) | |
568 : bubble_(bubble), | |
569 left_button_(NULL), | |
570 minimize_button_(NULL), | |
571 right_button_(NULL) { | |
572 SetLayoutManager(new views::BoxLayout( | |
573 views::BoxLayout::kHorizontal, 0, 0, kLayoutSpacing)); | |
574 set_background( | |
575 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); | |
576 | |
577 if (base::i18n::IsRTL()) { | |
578 AddMaximizeRightButton(); | |
579 AddMinimizeButton(); | |
580 AddMaximizeLeftButton(); | |
581 } else { | |
582 AddMaximizeLeftButton(); | |
583 AddMinimizeButton(); | |
584 AddMaximizeRightButton(); | |
585 } | |
586 } | |
587 | |
588 // Overridden from ButtonListener. | |
589 void BubbleContentsButtonRow::ButtonPressed(views::Button* sender, | |
590 const ui::Event& event) { | |
591 // While shutting down, the connection to the owner might already be broken. | |
592 if (!bubble_->controller()) | |
593 return; | |
594 if (sender == left_button_) | |
595 bubble_->controller()->OnButtonClicked( | |
596 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ? | |
597 SNAP_RESTORE : SNAP_LEFT); | |
598 else if (sender == minimize_button_) | |
599 bubble_->controller()->OnButtonClicked(SNAP_MINIMIZE); | |
600 else if (sender == right_button_) | |
601 bubble_->controller()->OnButtonClicked( | |
602 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ? | |
603 SNAP_RESTORE : SNAP_RIGHT); | |
604 else | |
605 NOTREACHED() << "Unknown button pressed."; | |
606 } | |
607 | |
608 // Called from BubbleDialogButton. | |
609 void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton* sender) { | |
610 // While shutting down, the connection to the owner might already be broken. | |
611 if (!bubble_->controller()) | |
612 return; | |
613 if (sender == left_button_) | |
614 bubble_->controller()->OnButtonHover( | |
615 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ? | |
616 SNAP_RESTORE : SNAP_LEFT); | |
617 else if (sender == minimize_button_) | |
618 bubble_->controller()->OnButtonHover(SNAP_MINIMIZE); | |
619 else if (sender == right_button_) | |
620 bubble_->controller()->OnButtonHover( | |
621 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ? | |
622 SNAP_RESTORE : SNAP_RIGHT); | |
623 else | |
624 bubble_->controller()->OnButtonHover(SNAP_NONE); | |
625 } | |
626 | |
627 views::CustomButton* BubbleContentsButtonRow::GetButtonForUnitTest( | |
628 SnapType state) { | |
629 switch (state) { | |
630 case SNAP_LEFT: | |
631 return left_button_; | |
632 case SNAP_MINIMIZE: | |
633 return minimize_button_; | |
634 case SNAP_RIGHT: | |
635 return right_button_; | |
636 default: | |
637 NOTREACHED(); | |
638 return NULL; | |
639 } | |
640 } | |
641 | |
642 void BubbleContentsButtonRow::AddMaximizeLeftButton() { | |
643 if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT) { | |
644 left_button_ = new BubbleDialogButton( | |
645 this, | |
646 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE, | |
647 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H, | |
648 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P); | |
649 } else { | |
650 left_button_ = new BubbleDialogButton( | |
651 this, | |
652 IDR_AURA_WINDOW_POSITION_LEFT, | |
653 IDR_AURA_WINDOW_POSITION_LEFT_H, | |
654 IDR_AURA_WINDOW_POSITION_LEFT_P); | |
655 } | |
656 } | |
657 | |
658 void BubbleContentsButtonRow::AddMaximizeRightButton() { | |
659 if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT) { | |
660 right_button_ = new BubbleDialogButton( | |
661 this, | |
662 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE, | |
663 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H, | |
664 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P); | |
665 } else { | |
666 right_button_ = new BubbleDialogButton( | |
667 this, | |
668 IDR_AURA_WINDOW_POSITION_RIGHT, | |
669 IDR_AURA_WINDOW_POSITION_RIGHT_H, | |
670 IDR_AURA_WINDOW_POSITION_RIGHT_P); | |
671 } | |
672 } | |
673 | |
674 void BubbleContentsButtonRow::AddMinimizeButton() { | |
675 minimize_button_ = new BubbleDialogButton( | |
676 this, | |
677 IDR_AURA_WINDOW_POSITION_MIDDLE, | |
678 IDR_AURA_WINDOW_POSITION_MIDDLE_H, | |
679 IDR_AURA_WINDOW_POSITION_MIDDLE_P); | |
680 } | |
681 | |
682 BubbleContentsView::BubbleContentsView( | |
683 MaximizeBubbleController::Bubble* bubble) | |
684 : bubble_(bubble), | |
685 buttons_view_(NULL), | |
686 label_view_(NULL) { | |
687 SetLayoutManager(new views::BoxLayout( | |
688 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing)); | |
689 set_background( | |
690 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); | |
691 | |
692 buttons_view_ = new BubbleContentsButtonRow(bubble); | |
693 AddChildView(buttons_view_); | |
694 | |
695 label_view_ = new views::Label(); | |
696 SetSnapType(SNAP_NONE); | |
697 label_view_->SetBackgroundColor(kBubbleBackgroundColor); | |
698 label_view_->SetEnabledColor(kBubbleTextColor); | |
699 label_view_->set_border(views::Border::CreateEmptyBorder( | |
700 kLabelSpacing, 0, kLabelSpacing, 0)); | |
701 AddChildView(label_view_); | |
702 } | |
703 | |
704 // Set the label content to reflect the currently selected |snap_type|. | |
705 // This function can be executed through the frame maximize button as well as | |
706 // through hover operations. | |
707 void BubbleContentsView::SetSnapType(SnapType snap_type) { | |
708 if (!bubble_->controller()) | |
709 return; | |
710 | |
711 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
712 int id = 0; | |
713 switch (snap_type) { | |
714 case SNAP_LEFT: | |
715 id = IDS_ASH_SNAP_WINDOW_LEFT; | |
716 break; | |
717 case SNAP_RIGHT: | |
718 id = IDS_ASH_SNAP_WINDOW_RIGHT; | |
719 break; | |
720 case SNAP_MAXIMIZE: | |
721 DCHECK_NE(FRAME_STATE_FULL, bubble_->controller()->maximize_type()); | |
722 id = IDS_ASH_MAXIMIZE_WINDOW; | |
723 break; | |
724 case SNAP_MINIMIZE: | |
725 id = IDS_ASH_MINIMIZE_WINDOW; | |
726 break; | |
727 case SNAP_RESTORE: | |
728 DCHECK_NE(FRAME_STATE_NONE, bubble_->controller()->maximize_type()); | |
729 id = IDS_ASH_RESTORE_WINDOW; | |
730 break; | |
731 default: | |
732 // If nothing is selected, we automatically select the click operation. | |
733 id = bubble_->controller()->maximize_type() == FRAME_STATE_FULL ? | |
734 IDS_ASH_RESTORE_WINDOW : IDS_ASH_MAXIMIZE_WINDOW; | |
735 break; | |
736 } | |
737 label_view_->SetText(rb.GetLocalizedString(id)); | |
738 } | |
739 | |
740 MaximizeBubbleController::MaximizeBubbleController( | |
741 FrameMaximizeButton* frame_maximize_button, | |
742 MaximizeBubbleFrameState maximize_type, | |
743 int appearance_delay_ms) | |
744 : frame_maximize_button_(frame_maximize_button), | |
745 bubble_(NULL), | |
746 maximize_type_(maximize_type), | |
747 appearance_delay_ms_(appearance_delay_ms) { | |
748 // Create the task which will create the bubble delayed. | |
749 base::OneShotTimer<MaximizeBubbleController>* new_timer = | |
750 new base::OneShotTimer<MaximizeBubbleController>(); | |
751 // Note: Even if there was no delay time given, we need to have a timer. | |
752 new_timer->Start( | |
753 FROM_HERE, | |
754 base::TimeDelta::FromMilliseconds( | |
755 appearance_delay_ms_ ? appearance_delay_ms_ : 10), | |
756 this, | |
757 &MaximizeBubbleController::CreateBubble); | |
758 timer_.reset(new_timer); | |
759 if (!appearance_delay_ms_) | |
760 CreateBubble(); | |
761 } | |
762 | |
763 MaximizeBubbleController::~MaximizeBubbleController() { | |
764 // Note: The destructor only gets initiated through the owner. | |
765 timer_.reset(); | |
766 if (bubble_) { | |
767 bubble_->ControllerRequestsCloseAndDelete(); | |
768 bubble_ = NULL; | |
769 } | |
770 } | |
771 | |
772 void MaximizeBubbleController::SetSnapType(SnapType snap_type) { | |
773 if (bubble_) | |
774 bubble_->SetSnapType(snap_type); | |
775 } | |
776 | |
777 aura::Window* MaximizeBubbleController::GetBubbleWindow() { | |
778 return bubble_ ? bubble_->GetBubbleWindow() : NULL; | |
779 } | |
780 | |
781 void MaximizeBubbleController::DelayCreation() { | |
782 if (timer_.get() && timer_->IsRunning()) | |
783 timer_->Reset(); | |
784 } | |
785 | |
786 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) { | |
787 frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type); | |
788 } | |
789 | |
790 void MaximizeBubbleController::OnButtonHover(SnapType snap_type) { | |
791 frame_maximize_button_->SnapButtonHovered(snap_type); | |
792 } | |
793 | |
794 views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest( | |
795 SnapType state) { | |
796 return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL; | |
797 } | |
798 | |
799 void MaximizeBubbleController::RequestDestructionThroughOwner() { | |
800 // Tell the parent to destroy us (if this didn't happen yet). | |
801 if (timer_) { | |
802 timer_.reset(NULL); | |
803 // Informs the owner that the menu is gone and requests |this| destruction. | |
804 frame_maximize_button_->DestroyMaximizeMenu(); | |
805 // Note: After this call |this| is destroyed. | |
806 } | |
807 } | |
808 | |
809 void MaximizeBubbleController::CreateBubble() { | |
810 if (!bubble_) | |
811 bubble_ = new Bubble(this, appearance_delay_ms_); | |
812 | |
813 timer_->Stop(); | |
814 } | |
815 | |
816 BubbleDialogButton::BubbleDialogButton( | |
817 BubbleContentsButtonRow* button_row, | |
818 int normal_image, | |
819 int hovered_image, | |
820 int pressed_image) | |
821 : views::ImageButton(button_row), | |
822 button_row_(button_row) { | |
823 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
824 SetImage(views::CustomButton::STATE_NORMAL, | |
825 rb.GetImageSkiaNamed(normal_image)); | |
826 SetImage(views::CustomButton::STATE_HOVERED, | |
827 rb.GetImageSkiaNamed(hovered_image)); | |
828 SetImage(views::CustomButton::STATE_PRESSED, | |
829 rb.GetImageSkiaNamed(pressed_image)); | |
830 button_row->AddChildView(this); | |
831 } | |
832 | |
833 void BubbleDialogButton::OnMouseCaptureLost() { | |
834 button_row_->ButtonHovered(NULL); | |
835 views::ImageButton::OnMouseCaptureLost(); | |
836 } | |
837 | |
838 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) { | |
839 button_row_->ButtonHovered(this); | |
840 views::ImageButton::OnMouseEntered(event); | |
841 } | |
842 | |
843 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) { | |
844 button_row_->ButtonHovered(NULL); | |
845 views::ImageButton::OnMouseExited(event); | |
846 } | |
847 | |
848 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) { | |
849 if (!button_row_->bubble()->controller()) | |
850 return false; | |
851 | |
852 // Remove the phantom window when we leave the button. | |
853 gfx::Point screen_location(event.location()); | |
854 View::ConvertPointToScreen(this, &screen_location); | |
855 if (!GetBoundsInScreen().Contains(screen_location)) | |
856 button_row_->ButtonHovered(NULL); | |
857 else | |
858 button_row_->ButtonHovered(this); | |
859 | |
860 // Pass the event on to the normal handler. | |
861 return views::ImageButton::OnMouseDragged(event); | |
862 } | |
863 | |
864 } // namespace ash | |
OLD | NEW |