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<gfx::NativeWindow>& 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[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        // Raise the old |frontmost_window| (if any). The topmost |win| will be
35        // raised with makeKeyAndOrderFront: below.
36        [frontmost_window orderFront:nil];
37        frontmost_window = win;
38      }
39    }
40  }
41  if (all_miniaturized && frontmost_miniaturized_window) {
42    DCHECK(!frontmost_window);
43    // Note the call to makeKeyAndOrderFront: will deminiaturize the window.
44    frontmost_window = frontmost_miniaturized_window;
45  }
46  // If we couldn't find one on the active space, consider all spaces.
47  if (allow_workspace_switch && !frontmost_window)
48    frontmost_window = frontmost_window_all_spaces;
49
50  if (frontmost_window) {
51    [frontmost_window makeKeyAndOrderFront:nil];
52    [NSApp activateIgnoringOtherApps:YES];
53  }
54}
55
56}  // namespace
57
58void FocusWindowSet(const std::set<gfx::NativeWindow>& windows) {
59  FocusWindowSetHelper(windows, true, true);
60}
61
62void FocusWindowSetOnCurrentSpace(const std::set<gfx::NativeWindow>& 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