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
31using ash::internal::SnapSizer;
32
33namespace ash {
34
35namespace {
36
37// Delay before forcing an update of the snap location.
38const int kUpdateDelayMS = 400;
39
40// The delay of the bubble appearance.
41const int kBubbleAppearanceDelayMS = 500;
42
43// The minimum sanp size in percent of the screen width.
44const 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.
50class 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
64FrameMaximizeButton::EscapeEventFilter::EscapeEventFilter(
65    FrameMaximizeButton* button)
66    : button_(button) {
67  Shell::GetInstance()->AddPreTargetHandler(this);
68}
69
70FrameMaximizeButton::EscapeEventFilter::~EscapeEventFilter() {
71  Shell::GetInstance()->RemovePreTargetHandler(this);
72}
73
74void 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
84FrameMaximizeButton::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
101FrameMaximizeButton::~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
109void 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
149void 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
165void FrameMaximizeButton::DestroyMaximizeMenu() {
166  Cancel(false);
167}
168
169void FrameMaximizeButton::OnWindowBoundsChanged(
170    aura::Window* window,
171    const gfx::Rect& old_bounds,
172    const gfx::Rect& new_bounds) {
173  Cancel(false);
174}
175
176void 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
186void 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
196void 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
203bool 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
217void 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
233void 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
254bool FrameMaximizeButton::OnMouseDragged(const ui::MouseEvent& event) {
255  if (is_snap_enabled_)
256    ProcessUpdateEvent(event);
257  return ImageButton::OnMouseDragged(event);
258}
259
260void 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
268void FrameMaximizeButton::OnMouseCaptureLost() {
269  Cancel(false);
270  ImageButton::OnMouseCaptureLost();
271}
272
273void 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
321void 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
329void 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
354void 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
364bool 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
388void 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
401void FrameMaximizeButton::InstallEventFilter() {
402  if (escape_event_filter_)
403    return;
404
405  escape_event_filter_.reset(new EscapeEventFilter(this));
406}
407
408void FrameMaximizeButton::UninstallEventFilter() {
409  escape_event_filter_.reset(NULL);
410}
411
412void 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
420void 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
468SnapType 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
483gfx::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
517gfx::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
524void 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
593MaximizeBubbleFrameState
594FrameMaximizeButton::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