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