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