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