Only fsync leveldb's directory when the manifest is being updated.
[chromium-blink-merge.git] / ash / wm / workspace / frame_maximize_button.cc
blob0329e3f6d4668e5342a2586f4d061dd017991c69
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.
5 #include "ash/wm/workspace/frame_maximize_button.h"
7 #include "ash/launcher/launcher.h"
8 #include "ash/screen_ash.h"
9 #include "ash/shelf/shelf_widget.h"
10 #include "ash/shell.h"
11 #include "ash/shell_delegate.h"
12 #include "ash/wm/maximize_bubble_controller.h"
13 #include "ash/wm/property_util.h"
14 #include "ash/wm/window_properties.h"
15 #include "ash/wm/window_util.h"
16 #include "ash/wm/workspace/phantom_window_controller.h"
17 #include "ash/wm/workspace/snap_sizer.h"
18 #include "grit/ash_strings.h"
19 #include "ui/aura/window.h"
20 #include "ui/base/events/event.h"
21 #include "ui/base/events/event_handler.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/gfx/screen.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/window/non_client_view.h"
29 using ash::internal::SnapSizer;
31 namespace ash {
33 namespace {
35 // Delay before forcing an update of the snap location.
36 const int kUpdateDelayMS = 400;
38 // The delay of the bubble appearance.
39 const int kBubbleAppearanceDelayMS = 500;
41 // The minimum sanp size in percent of the screen width.
42 const int kMinSnapSizePercent = 50;
45 // EscapeEventFilter is installed on the RootWindow to track when the escape key
46 // is pressed. We use an EventFilter for this as the FrameMaximizeButton
47 // normally does not get focus.
48 class FrameMaximizeButton::EscapeEventFilter : public ui::EventHandler {
49 public:
50 explicit EscapeEventFilter(FrameMaximizeButton* button);
51 virtual ~EscapeEventFilter();
53 // EventFilter overrides:
54 virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
56 private:
57 FrameMaximizeButton* button_;
59 DISALLOW_COPY_AND_ASSIGN(EscapeEventFilter);
62 FrameMaximizeButton::EscapeEventFilter::EscapeEventFilter(
63 FrameMaximizeButton* button)
64 : button_(button) {
65 Shell::GetInstance()->AddPreTargetHandler(this);
68 FrameMaximizeButton::EscapeEventFilter::~EscapeEventFilter() {
69 Shell::GetInstance()->RemovePreTargetHandler(this);
72 void FrameMaximizeButton::EscapeEventFilter::OnKeyEvent(
73 ui::KeyEvent* event) {
74 if (event->type() == ui::ET_KEY_PRESSED &&
75 event->key_code() == ui::VKEY_ESCAPE) {
76 button_->Cancel(false);
80 // FrameMaximizeButton ---------------------------------------------------------
82 FrameMaximizeButton::FrameMaximizeButton(views::ButtonListener* listener,
83 views::NonClientFrameView* frame)
84 : ImageButton(listener),
85 frame_(frame),
86 is_snap_enabled_(false),
87 exceeded_drag_threshold_(false),
88 widget_(NULL),
89 press_is_gesture_(false),
90 snap_type_(SNAP_NONE),
91 bubble_appearance_delay_ms_(kBubbleAppearanceDelayMS) {
92 // TODO(sky): nuke this. It's temporary while we don't have good images.
93 SetImageAlignment(ALIGN_LEFT, ALIGN_BOTTOM);
95 if (ash::Shell::IsForcedMaximizeMode())
96 views::View::SetVisible(false);
99 FrameMaximizeButton::~FrameMaximizeButton() {
100 // Before the window gets destroyed, the maximizer dialog needs to be shut
101 // down since it would otherwise call into a deleted object.
102 maximizer_.reset();
103 if (widget_)
104 OnWindowDestroying(widget_->GetNativeWindow());
107 void FrameMaximizeButton::SnapButtonHovered(SnapType type) {
108 // Make sure to only show hover operations when no button is pressed and
109 // a similar snap operation in progress does not get re-applied.
110 if (is_snap_enabled_ || (type == snap_type_ && snap_sizer_))
111 return;
112 // Prime the mouse location with the center of the (local) button.
113 press_location_ = gfx::Point(width() / 2, height() / 2);
114 // Then get an adjusted mouse position to initiate the effect.
115 gfx::Point location = press_location_;
116 switch (type) {
117 case SNAP_LEFT:
118 location.set_x(location.x() - width());
119 break;
120 case SNAP_RIGHT:
121 location.set_x(location.x() + width());
122 break;
123 case SNAP_MINIMIZE:
124 location.set_y(location.y() + height());
125 break;
126 case SNAP_RESTORE:
127 // Simulate a mouse button move over the according button.
128 if (GetMaximizeBubbleFrameState() == FRAME_STATE_SNAP_LEFT)
129 location.set_x(location.x() - width());
130 else if (GetMaximizeBubbleFrameState() == FRAME_STATE_SNAP_RIGHT)
131 location.set_x(location.x() + width());
132 break;
133 case SNAP_MAXIMIZE:
134 break;
135 case SNAP_NONE:
136 Cancel(true);
137 return;
138 default:
139 // We should not come here.
140 NOTREACHED();
142 // Note: There is no hover with touch - we can therefore pass false for touch
143 // operations.
144 UpdateSnap(location, true, false);
147 void FrameMaximizeButton::ExecuteSnapAndCloseMenu(SnapType snap_type) {
148 DCHECK_NE(snap_type_, SNAP_NONE);
149 Cancel(true);
150 // Tell our menu to close.
151 maximizer_.reset();
152 snap_type_ = snap_type;
153 // Since Snap might destroy |this|, but the snap_sizer needs to be destroyed,
154 // The ownership of the snap_sizer is taken now.
155 scoped_ptr<SnapSizer> snap_sizer(snap_sizer_.release());
156 Snap(*snap_sizer.get());
159 void FrameMaximizeButton::DestroyMaximizeMenu() {
160 Cancel(false);
163 void FrameMaximizeButton::OnWindowBoundsChanged(
164 aura::Window* window,
165 const gfx::Rect& old_bounds,
166 const gfx::Rect& new_bounds) {
167 Cancel(false);
170 void FrameMaximizeButton::OnWindowPropertyChanged(aura::Window* window,
171 const void* key,
172 intptr_t old) {
173 // Changing the window position is managed status should not Cancel.
174 // Note that this case might happen when a non user managed window
175 // transitions from maximized to L/R maximized.
176 if (key != ash::internal::kWindowPositionManagedKey)
177 Cancel(false);
180 void FrameMaximizeButton::OnWindowDestroying(aura::Window* window) {
181 maximizer_.reset();
182 if (widget_) {
183 CHECK_EQ(widget_->GetNativeWindow(), window);
184 widget_->GetNativeWindow()->RemoveObserver(this);
185 widget_->RemoveObserver(this);
186 widget_ = NULL;
190 void FrameMaximizeButton::OnWidgetActivationChanged(views::Widget* widget,
191 bool active) {
192 // Upon losing focus, the control bubble should hide.
193 if (!active && maximizer_)
194 maximizer_.reset();
197 bool FrameMaximizeButton::OnMousePressed(const ui::MouseEvent& event) {
198 // If we are already in a mouse click / drag operation, a second button down
199 // call will cancel (this addresses crbug.com/143755).
200 if (is_snap_enabled_) {
201 Cancel(false);
202 } else {
203 is_snap_enabled_ = event.IsOnlyLeftMouseButton();
204 if (is_snap_enabled_)
205 ProcessStartEvent(event);
207 ImageButton::OnMousePressed(event);
208 return true;
211 void FrameMaximizeButton::OnMouseEntered(const ui::MouseEvent& event) {
212 ImageButton::OnMouseEntered(event);
213 if (!maximizer_) {
214 DCHECK(GetWidget());
215 if (!widget_) {
216 widget_ = frame_->GetWidget();
217 widget_->GetNativeWindow()->AddObserver(this);
218 widget_->AddObserver(this);
220 maximizer_.reset(new MaximizeBubbleController(
221 this,
222 GetMaximizeBubbleFrameState(),
223 bubble_appearance_delay_ms_));
227 void FrameMaximizeButton::OnMouseExited(const ui::MouseEvent& event) {
228 ImageButton::OnMouseExited(event);
229 // Remove the bubble menu when the button is not pressed and the mouse is not
230 // within the bubble.
231 if (!is_snap_enabled_ && maximizer_) {
232 if (maximizer_->GetBubbleWindow()) {
233 gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint();
234 if (!maximizer_->GetBubbleWindow()->GetBoundsInScreen().Contains(
235 screen_location)) {
236 maximizer_.reset();
237 // Make sure that all remaining snap hover states get removed.
238 SnapButtonHovered(SNAP_NONE);
240 } else {
241 // The maximize dialog does not show up immediately after creating the
242 // |mazimizer_|. Destroy the dialog therefore before it shows up.
243 maximizer_.reset();
248 bool FrameMaximizeButton::OnMouseDragged(const ui::MouseEvent& event) {
249 if (is_snap_enabled_)
250 ProcessUpdateEvent(event);
251 return ImageButton::OnMouseDragged(event);
254 void FrameMaximizeButton::OnMouseReleased(const ui::MouseEvent& event) {
255 maximizer_.reset();
256 bool snap_was_enabled = is_snap_enabled_;
257 if (!ProcessEndEvent(event) && snap_was_enabled)
258 ImageButton::OnMouseReleased(event);
259 // At this point |this| might be already destroyed.
262 void FrameMaximizeButton::OnMouseCaptureLost() {
263 Cancel(false);
264 ImageButton::OnMouseCaptureLost();
267 void FrameMaximizeButton::OnGestureEvent(ui::GestureEvent* event) {
268 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
269 is_snap_enabled_ = true;
270 ProcessStartEvent(*event);
271 event->SetHandled();
272 return;
275 if (event->type() == ui::ET_GESTURE_TAP ||
276 (event->type() == ui::ET_GESTURE_SCROLL_END && is_snap_enabled_) ||
277 event->type() == ui::ET_SCROLL_FLING_START) {
278 // The position of the event may have changed from the previous event (both
279 // for TAP and SCROLL_END). So it is necessary to update the snap-state for
280 // the current event.
281 ProcessUpdateEvent(*event);
282 if (event->type() == ui::ET_GESTURE_TAP)
283 snap_type_ = SnapTypeForLocation(event->location());
284 ProcessEndEvent(*event);
285 event->SetHandled();
286 return;
289 if (is_snap_enabled_) {
290 if (event->type() == ui::ET_GESTURE_END &&
291 event->details().touch_points() == 1) {
292 // The position of the event may have changed from the previous event. So
293 // it is necessary to update the snap-state for the current event.
294 ProcessUpdateEvent(*event);
295 snap_type_ = SnapTypeForLocation(event->location());
296 ProcessEndEvent(*event);
297 event->SetHandled();
298 return;
301 if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
302 event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
303 ProcessUpdateEvent(*event);
304 event->SetHandled();
305 return;
309 ImageButton::OnGestureEvent(event);
312 void FrameMaximizeButton::SetVisible(bool visible) {
313 // In the enforced maximized mode we do not allow to be made visible.
314 if (ash::Shell::IsForcedMaximizeMode())
315 return;
317 views::View::SetVisible(visible);
320 void FrameMaximizeButton::ProcessStartEvent(const ui::LocatedEvent& event) {
321 DCHECK(is_snap_enabled_);
322 // Prepare the help menu.
323 if (!maximizer_) {
324 maximizer_.reset(new MaximizeBubbleController(
325 this,
326 GetMaximizeBubbleFrameState(),
327 bubble_appearance_delay_ms_));
328 } else {
329 // If the menu did not show up yet, we delay it even a bit more.
330 maximizer_->DelayCreation();
332 snap_sizer_.reset(NULL);
333 InstallEventFilter();
334 snap_type_ = SNAP_NONE;
335 press_location_ = event.location();
336 press_is_gesture_ = event.IsGestureEvent();
337 exceeded_drag_threshold_ = false;
338 update_timer_.Start(
339 FROM_HERE,
340 base::TimeDelta::FromMilliseconds(kUpdateDelayMS),
341 this,
342 &FrameMaximizeButton::UpdateSnapFromEventLocation);
345 void FrameMaximizeButton::ProcessUpdateEvent(const ui::LocatedEvent& event) {
346 DCHECK(is_snap_enabled_);
347 if (!exceeded_drag_threshold_) {
348 exceeded_drag_threshold_ = views::View::ExceededDragThreshold(
349 event.location() - press_location_);
351 if (exceeded_drag_threshold_)
352 UpdateSnap(event.location(), false, event.IsGestureEvent());
355 bool FrameMaximizeButton::ProcessEndEvent(const ui::LocatedEvent& event) {
356 update_timer_.Stop();
357 UninstallEventFilter();
358 bool should_snap = is_snap_enabled_;
359 is_snap_enabled_ = false;
361 // Remove our help bubble.
362 maximizer_.reset();
364 if (!should_snap || snap_type_ == SNAP_NONE)
365 return false;
367 SetState(views::CustomButton::STATE_NORMAL);
368 // SetState will not call SchedulePaint() if state was already set to
369 // STATE_NORMAL during a drag.
370 SchedulePaint();
371 phantom_window_.reset();
372 // Since Snap might destroy |this|, but the snap_sizer needs to be destroyed,
373 // The ownership of the snap_sizer is taken now.
374 scoped_ptr<SnapSizer> snap_sizer(snap_sizer_.release());
375 Snap(*snap_sizer.get());
376 return true;
379 void FrameMaximizeButton::Cancel(bool keep_menu_open) {
380 if (!keep_menu_open) {
381 maximizer_.reset();
382 UninstallEventFilter();
383 is_snap_enabled_ = false;
384 snap_sizer_.reset();
386 phantom_window_.reset();
387 snap_type_ = SNAP_NONE;
388 update_timer_.Stop();
389 SchedulePaint();
392 void FrameMaximizeButton::InstallEventFilter() {
393 if (escape_event_filter_)
394 return;
396 escape_event_filter_.reset(new EscapeEventFilter(this));
399 void FrameMaximizeButton::UninstallEventFilter() {
400 escape_event_filter_.reset(NULL);
403 void FrameMaximizeButton::UpdateSnapFromEventLocation() {
404 // If the drag threshold has been exceeded the snap location is up to date.
405 if (exceeded_drag_threshold_)
406 return;
407 exceeded_drag_threshold_ = true;
408 UpdateSnap(press_location_, false, press_is_gesture_);
411 void FrameMaximizeButton::UpdateSnap(const gfx::Point& location,
412 bool select_default,
413 bool is_touch) {
414 SnapType type = SnapTypeForLocation(location);
415 if (type == snap_type_) {
416 if (snap_sizer_) {
417 snap_sizer_->Update(LocationForSnapSizer(location));
418 phantom_window_->Show(ScreenAsh::ConvertRectToScreen(
419 frame_->GetWidget()->GetNativeView()->parent(),
420 snap_sizer_->target_bounds()));
422 return;
425 snap_type_ = type;
426 snap_sizer_.reset();
427 SchedulePaint();
429 if (snap_type_ == SNAP_NONE) {
430 phantom_window_.reset();
431 return;
434 if (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT) {
435 SnapSizer::Edge snap_edge = snap_type_ == SNAP_LEFT ?
436 SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE;
437 SnapSizer::InputType input_type =
438 is_touch ? SnapSizer::TOUCH_MAXIMIZE_BUTTON_INPUT :
439 SnapSizer::OTHER_INPUT;
440 snap_sizer_.reset(new SnapSizer(frame_->GetWidget()->GetNativeWindow(),
441 LocationForSnapSizer(location),
442 snap_edge,
443 input_type));
444 if (select_default)
445 snap_sizer_->SelectDefaultSizeAndDisableResize();
447 if (!phantom_window_) {
448 phantom_window_.reset(new internal::PhantomWindowController(
449 frame_->GetWidget()->GetNativeWindow()));
451 if (maximizer_) {
452 phantom_window_->set_phantom_below_window(maximizer_->GetBubbleWindow());
453 maximizer_->SetSnapType(snap_type_);
455 phantom_window_->Show(
456 ScreenBoundsForType(snap_type_, *snap_sizer_.get()));
459 SnapType FrameMaximizeButton::SnapTypeForLocation(
460 const gfx::Point& location) const {
461 MaximizeBubbleFrameState maximize_type = GetMaximizeBubbleFrameState();
462 gfx::Vector2d delta(location - press_location_);
463 if (!views::View::ExceededDragThreshold(delta))
464 return maximize_type != FRAME_STATE_FULL ? SNAP_MAXIMIZE : SNAP_RESTORE;
465 if (delta.x() < 0 && delta.y() > delta.x() && delta.y() < -delta.x())
466 return maximize_type == FRAME_STATE_SNAP_LEFT ? SNAP_RESTORE : SNAP_LEFT;
467 if (delta.x() > 0 && delta.y() > -delta.x() && delta.y() < delta.x())
468 return maximize_type == FRAME_STATE_SNAP_RIGHT ? SNAP_RESTORE : SNAP_RIGHT;
469 if (delta.y() > 0)
470 return SNAP_MINIMIZE;
471 return maximize_type != FRAME_STATE_FULL ? SNAP_MAXIMIZE : SNAP_RESTORE;
474 gfx::Rect FrameMaximizeButton::ScreenBoundsForType(
475 SnapType type,
476 const SnapSizer& snap_sizer) const {
477 aura::Window* window = frame_->GetWidget()->GetNativeWindow();
478 switch (type) {
479 case SNAP_LEFT:
480 case SNAP_RIGHT:
481 return ScreenAsh::ConvertRectToScreen(
482 frame_->GetWidget()->GetNativeView()->parent(),
483 snap_sizer.target_bounds());
484 case SNAP_MAXIMIZE:
485 return ScreenAsh::ConvertRectToScreen(
486 window->parent(),
487 ScreenAsh::GetMaximizedWindowBoundsInParent(window));
488 case SNAP_MINIMIZE: {
489 Launcher* launcher = Launcher::ForWindow(window);
490 // Launcher is created lazily and can be NULL.
491 if (!launcher)
492 return gfx::Rect();
493 gfx::Rect item_rect(launcher->GetScreenBoundsOfItemIconForWindow(
494 window));
495 if (!item_rect.IsEmpty()) {
496 // PhantomWindowController insets slightly, outset it so the phantom
497 // doesn't appear inset.
498 item_rect.Inset(-8, -8);
499 return item_rect;
501 return launcher->shelf_widget()->GetWindowBoundsInScreen();
503 case SNAP_RESTORE: {
504 const gfx::Rect* restore = GetRestoreBoundsInScreen(window);
505 return restore ?
506 *restore : frame_->GetWidget()->GetWindowBoundsInScreen();
508 case SNAP_NONE:
509 NOTREACHED();
511 return gfx::Rect();
514 gfx::Point FrameMaximizeButton::LocationForSnapSizer(
515 const gfx::Point& location) const {
516 gfx::Point result(location);
517 views::View::ConvertPointToScreen(this, &result);
518 return result;
521 void FrameMaximizeButton::Snap(const SnapSizer& snap_sizer) {
522 ash::Shell* shell = ash::Shell::GetInstance();
523 views::Widget* widget = frame_->GetWidget();
524 switch (snap_type_) {
525 case SNAP_LEFT:
526 case SNAP_RIGHT: {
527 shell->delegate()->RecordUserMetricsAction(
528 snap_type_ == SNAP_LEFT ?
529 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT :
530 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT);
531 // Get the bounds in screen coordinates for restore purposes.
532 gfx::Rect restore = widget->GetWindowBoundsInScreen();
533 if (widget->IsMaximized() || widget->IsFullscreen()) {
534 aura::Window* window = widget->GetNativeWindow();
535 // In case of maximized we have a restore boundary.
536 DCHECK(ash::GetRestoreBoundsInScreen(window));
537 // If it was maximized we need to recover the old restore set.
538 restore = *ash::GetRestoreBoundsInScreen(window);
540 // The auto position manager will kick in when this is the only window.
541 // To avoid interference with it we tell it temporarily to not change
542 // the coordinates of this window.
543 bool is_managed = ash::wm::IsWindowPositionManaged(window);
544 if (is_managed)
545 ash::wm::SetWindowPositionManaged(window, false);
547 // Set the restore size we want to restore to.
548 ash::SetRestoreBoundsInScreen(window,
549 ScreenBoundsForType(snap_type_,
550 snap_sizer));
551 widget->Restore();
553 // After the window is where we want it to be we allow the window to be
554 // auto managed again.
555 if (is_managed)
556 ash::wm::SetWindowPositionManaged(window, true);
557 } else {
558 // Others might also have set up a restore rectangle already. If so,
559 // we should not overwrite the restore rectangle.
560 bool restore_set =
561 GetRestoreBoundsInScreen(widget->GetNativeWindow()) != NULL;
562 widget->SetBounds(ScreenBoundsForType(snap_type_, snap_sizer));
563 if (restore_set)
564 break;
566 // Remember the widow's bounds for restoration.
567 ash::SetRestoreBoundsInScreen(widget->GetNativeWindow(), restore);
568 break;
570 case SNAP_MAXIMIZE:
571 widget->Maximize();
572 shell->delegate()->RecordUserMetricsAction(
573 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE);
574 break;
575 case SNAP_MINIMIZE:
576 widget->Minimize();
577 shell->delegate()->RecordUserMetricsAction(
578 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MINIMIZE);
579 break;
580 case SNAP_RESTORE:
581 widget->Restore();
582 shell->delegate()->RecordUserMetricsAction(
583 ash::UMA_WINDOW_MAXIMIZE_BUTTON_RESTORE);
584 break;
585 case SNAP_NONE:
586 NOTREACHED();
590 MaximizeBubbleFrameState
591 FrameMaximizeButton::GetMaximizeBubbleFrameState() const {
592 // When there are no restore bounds, we are in normal mode.
593 if (!ash::GetRestoreBoundsInScreen(
594 frame_->GetWidget()->GetNativeWindow()))
595 return FRAME_STATE_NONE;
596 // The normal maximized test can be used.
597 if (frame_->GetWidget()->IsMaximized())
598 return FRAME_STATE_FULL;
599 // For Left/right maximize we need to check the dimensions.
600 gfx::Rect bounds = frame_->GetWidget()->GetWindowBoundsInScreen();
601 gfx::Rect screen = Shell::GetScreen()->GetDisplayMatching(bounds).work_area();
602 if (bounds.width() < (screen.width() * kMinSnapSizePercent) / 100)
603 return FRAME_STATE_NONE;
604 // We might still have a horizontally filled window at this point which we
605 // treat as no special state.
606 if (bounds.y() != screen.y() || bounds.height() != screen.height())
607 return FRAME_STATE_NONE;
609 // We have to be in a maximize mode at this point.
610 if (bounds.x() == screen.x())
611 return FRAME_STATE_SNAP_LEFT;
612 if (bounds.right() == screen.right())
613 return FRAME_STATE_SNAP_RIGHT;
614 // If we come here, it is likely caused by the fact that the
615 // "VerticalResizeDoubleClick" stored a restore rectangle. In that case
616 // we allow all maximize operations (and keep the restore rectangle).
617 return FRAME_STATE_NONE;
620 } // namespace ash