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#import <Cocoa/Cocoa.h>
6
7#include "ui/base/cocoa/focus_window_set.h"
8
9namespace ui {
10
11namespace {
12
13// This attempts to match OS X's native behavior, namely that a window
14// is only ever deminiaturized if ALL windows on ALL workspaces are
15// miniaturized.
16void FocusWindowSetHelper(const std::set<NSWindow*>& windows,
17                          bool allow_workspace_switch,
18                          bool visible_windows_only) {
19  NSArray* ordered_windows = [NSApp orderedWindows];
20  NSWindow* frontmost_window = nil;
21  NSWindow* frontmost_window_all_spaces = nil;
22  NSWindow* frontmost_miniaturized_window = nil;
23  bool all_miniaturized = true;
24  for (int i = [ordered_windows count] - 1; i >= 0; i--) {
25    NSWindow* win = [ordered_windows objectAtIndex:i];
26    if (windows.find(win) == windows.end())
27      continue;
28    if ([win isMiniaturized]) {
29      frontmost_miniaturized_window = win;
30    } else if (!visible_windows_only || [win isVisible]) {
31      all_miniaturized = false;
32      frontmost_window_all_spaces = win;
33      if ([win isOnActiveSpace]) {
34        [win orderFront:nil];
35        frontmost_window = win;
36      }
37    }
38  }
39  if (all_miniaturized && frontmost_miniaturized_window) {
40    [frontmost_miniaturized_window deminiaturize:nil];
41    frontmost_window = frontmost_miniaturized_window;
42  }
43  // If we couldn't find one on this window, consider all spaces.
44  if (allow_workspace_switch &&
45      !frontmost_window && frontmost_window_all_spaces) {
46    frontmost_window = frontmost_window_all_spaces;
47    [frontmost_window orderFront:nil];
48  }
49  if (frontmost_window) {
50    [NSApp activateIgnoringOtherApps:YES];
51    [frontmost_window makeMainWindow];
52    [frontmost_window makeKeyWindow];
53  }
54}
55
56}  // namespace
57
58void FocusWindowSet(const std::set<NSWindow*>& windows) {
59  FocusWindowSetHelper(windows, true, true);
60}
61
62void FocusWindowSetOnCurrentSpace(const std::set<NSWindow*>& windows) {
63  // This callback runs before AppKit picks its own window to
64  // deminiaturize, so we get to pick one from the right set. Limit to
65  // the windows on the current workspace. Otherwise we jump spaces
66  // haphazardly.
67  //
68  // Also consider both visible and hidden windows; this call races
69  // with the system unhiding the application. http://crbug.com/368238
70  //
71  // NOTE: If this is called in the
72  // applicationShouldHandleReopen:hasVisibleWindows: hook when
73  // clicking the dock icon, and that caused OS X to begin switch
74  // spaces, isOnActiveSpace gives the answer for the PREVIOUS
75  // space. This means that we actually raise and focus the wrong
76  // space's windows, leaving the new key window off-screen. To detect
77  // this, check if the key window is on the active space prior to
78  // calling.
79  //
80  // Also, if we decide to deminiaturize a window during a space switch,
81  // that can switch spaces and then switch back. Fortunately, this only
82  // happens if, say, space 1 contains an app, space 2 contains a
83  // miniaturized browser. We click the icon, OS X switches to space 1,
84  // we deminiaturize the browser, and that triggers switching back.
85  //
86  // TODO(davidben): To limit those cases, consider preferentially
87  // deminiaturizing a window on the current space.
88  FocusWindowSetHelper(windows, false, false);
89}
90
91}  // namespace ui
92