81b7a07acaec67220f786261dc3e6cb8e66bb8e6
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / WindowManagerImpl.java
blob81b7a07acaec67220f786261dc3e6cb8e66bb8e6
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.wm.impl;
18 import com.intellij.Patches;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.IdeEventQueue;
21 import com.intellij.ide.impl.DataManagerImpl;
22 import com.intellij.ide.ui.UISettings;
23 import com.intellij.openapi.actionSystem.ActionManager;
24 import com.intellij.openapi.application.ApplicationInfo;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ex.ApplicationInfoEx;
27 import com.intellij.openapi.components.ApplicationComponent;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.keymap.KeymapManager;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.project.ProjectManager;
32 import com.intellij.openapi.util.NamedJDOMExternalizable;
33 import com.intellij.openapi.util.SystemInfo;
34 import com.intellij.openapi.wm.IdeFrame;
35 import com.intellij.openapi.wm.StatusBar;
36 import com.intellij.openapi.wm.WindowManagerListener;
37 import com.intellij.openapi.wm.ex.StatusBarEx;
38 import com.intellij.openapi.wm.ex.WindowManagerEx;
39 import com.intellij.util.Alarm;
40 import com.intellij.util.EventDispatcher;
41 import com.intellij.util.ui.UIUtil;
42 import com.sun.jna.examples.WindowUtils;
43 import org.jdom.Element;
44 import org.jetbrains.annotations.NonNls;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
48 import javax.swing.*;
49 import java.awt.*;
50 import java.awt.event.ComponentEvent;
51 import java.awt.event.WindowAdapter;
52 import java.awt.event.WindowEvent;
53 import java.beans.PropertyChangeEvent;
54 import java.beans.PropertyChangeListener;
55 import java.util.Collection;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.Set;
60 /**
61 * @author Anton Katilin
62 * @author Vladimir Kondratyev
65 public final class WindowManagerImpl extends WindowManagerEx implements ApplicationComponent, NamedJDOMExternalizable {
66 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.WindowManagerImpl");
67 private static boolean ourAlphaModeLibraryLoaded;
68 @NonNls private static final String FOCUSED_WINDOW_PROPERTY_NAME = "focusedWindow";
69 @NonNls private static final String X_ATTR = "x";
70 @NonNls private static final String FRAME_ELEMENT = "frame";
71 @NonNls private static final String Y_ATTR = "y";
72 @NonNls private static final String WIDTH_ATTR = "width";
73 @NonNls private static final String HEIGHT_ATTR = "height";
74 @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state";
75 private Boolean myAlphaModeSupported = null;
77 private final EventDispatcher<WindowManagerListener> myEventDispatcher = EventDispatcher.create(WindowManagerListener.class);
79 static {
80 initialize();
83 @SuppressWarnings({"HardCodedStringLiteral"})
84 private static void initialize() {
85 try {
86 System.loadLibrary("jawt");
87 ourAlphaModeLibraryLoaded = true;
89 catch (Throwable exc) {
90 ourAlphaModeLibraryLoaded = false;
94 /**
95 * Union of bounds of all available default screen devices.
97 private final Rectangle myScreenBounds;
99 private final CommandProcessor myCommandProcessor;
100 private final WindowWatcher myWindowWatcher;
102 * That is the default layout.
104 private final DesktopLayout myLayout;
106 private final HashMap<Project, IdeFrameImpl> myProject2Frame;
108 private final HashMap<Project, Set<JDialog>> myDialogsToDispose;
111 * This members is needed to read frame's bounds from XML.
112 * <code>myFrameBounds</code> can be <code>null</code>.
114 private Rectangle myFrameBounds;
115 private int myFrameExtendedState;
116 private final WindowAdapter myActivationListener;
117 private final ApplicationInfoEx myApplicationInfoEx;
118 private final DataManager myDataManager;
119 private final ActionManager myActionManager;
120 private final UISettings myUiSettings;
121 private final KeymapManager myKeymapManager;
124 * invoked by reflection
125 * @param dataManager
126 * @param applicationInfoEx
127 * @param actionManager
128 * @param uiSettings
129 * @param keymapManager
131 public WindowManagerImpl(DataManager dataManager,
132 ApplicationInfoEx applicationInfoEx,
133 ActionManager actionManager,
134 UISettings uiSettings,
135 KeymapManager keymapManager) {
136 myApplicationInfoEx = applicationInfoEx;
137 myDataManager = dataManager;
138 myActionManager = actionManager;
139 myUiSettings = uiSettings;
140 myKeymapManager = keymapManager;
141 if (myDataManager instanceof DataManagerImpl) {
142 ((DataManagerImpl)myDataManager).setWindowManager(this);
145 myCommandProcessor = new CommandProcessor();
146 myWindowWatcher = new WindowWatcher();
147 final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
148 keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, myWindowWatcher);
149 if (Patches.SUN_BUG_ID_4218084) {
150 keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, new SUN_BUG_ID_4218084_Patch());
152 myLayout = new DesktopLayout();
153 myProject2Frame = new HashMap<Project, IdeFrameImpl>();
154 myDialogsToDispose = new HashMap<Project, Set<JDialog>>();
155 myFrameExtendedState = Frame.NORMAL;
157 // Calculate screen bounds.
159 Rectangle screenBounds = new Rectangle();
160 if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
161 final GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
162 final GraphicsDevice[] devices = env.getScreenDevices();
163 for (final GraphicsDevice device : devices) {
164 screenBounds = screenBounds.union(device.getDefaultConfiguration().getBounds());
167 myScreenBounds = screenBounds;
169 myActivationListener = new WindowAdapter() {
170 public void windowActivated(WindowEvent e) {
171 Window activeWindow = e.getWindow();
172 if (activeWindow instanceof IdeFrameImpl) { // must be
173 proceedDialogDisposalQueue(((IdeFrameImpl)activeWindow).getProject());
179 public void showFrame(final String[] args) {
180 IdeEventQueue.getInstance().setWindowManager(this);
181 final IdeFrameImpl frame = new IdeFrameImpl(myApplicationInfoEx, myActionManager, myUiSettings, myDataManager, myKeymapManager,
182 ApplicationManager.getApplication(), args);
183 myProject2Frame.put(null, frame);
184 if (myFrameBounds != null) {
185 frame.setBounds(myFrameBounds);
187 frame.setVisible(true);
188 frame.setExtendedState(myFrameExtendedState);
191 public IdeFrameImpl[] getAllFrames() {
192 final Collection<IdeFrameImpl> ideFrames = myProject2Frame.values();
193 return ideFrames.toArray(new IdeFrameImpl[ideFrames.size()]);
196 @Override
197 public void addListener(final WindowManagerListener listener) {
198 myEventDispatcher.addListener(listener);
201 public void removeListener(final WindowManagerListener listener) {
202 myEventDispatcher.removeListener(listener);
205 public final Rectangle getScreenBounds() {
206 return myScreenBounds;
209 public final boolean isInsideScreenBounds(final int x, final int y, final int width) {
210 return
211 x >= myScreenBounds.x + 50 - width &&
212 y >= myScreenBounds.y - 50 &&
213 x <= myScreenBounds.x + myScreenBounds.width - 50 &&
214 y <= myScreenBounds.y + myScreenBounds.height - 50;
217 public final boolean isInsideScreenBounds(final int x, final int y) {
218 return myScreenBounds.contains(x, y);
221 public final boolean isAlphaModeSupported() {
222 if (myAlphaModeSupported == null) {
223 myAlphaModeSupported = calcAlphaModelSupported();
225 return myAlphaModeSupported.booleanValue();
228 private static boolean calcAlphaModelSupported() {
229 if (AWTUtilitiesWrapper.isTranslucencyAPISupported()) {
230 return AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT);
232 try {
233 return WindowUtils.isWindowAlphaSupported();
235 catch (Throwable e) {
236 return false;
240 public final void setAlphaModeRatio(final Window window, final float ratio) {
241 if (!window.isDisplayable() || !window.isShowing()) {
242 throw new IllegalArgumentException("window must be displayable and showing. window=" + window);
244 if (ratio < 0.0f || ratio > 1.0f) {
245 throw new IllegalArgumentException("ratio must be in [0..1] range. ratio=" + ratio);
247 if (!isAlphaModeSupported() || !isAlphaModeEnabled(window)) {
248 return;
252 setAlphaMode(window, ratio);
255 private void setAlphaMode(Window window, float ratio) {
256 try {
257 if (SystemInfo.isMacOSLeopard) {
258 if (window instanceof JWindow) {
259 ((JWindow)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
260 } else if (window instanceof JDialog) {
261 ((JDialog)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
264 else if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT)) {
265 AWTUtilitiesWrapper.setWindowOpacity(window, 1.0f - ratio);
267 else {
268 WindowUtils.setWindowAlpha(window, 1.0f - ratio);
271 catch (Throwable e) {
272 LOG.debug(e);
276 public void setWindowMask(final Window window, final Shape mask) {
277 try {
278 if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.PERPIXEL_TRANSPARENT)) {
279 AWTUtilitiesWrapper.setWindowShape(window, mask);
281 else {
282 WindowUtils.setWindowMask(window, mask);
285 catch (Throwable e) {
286 LOG.debug(e);
290 public void resetWindow(final Window window) {
291 try {
292 if (!isAlphaModeSupported()) return;
294 WindowUtils.setWindowMask(window, (Shape)null);
295 setAlphaMode(window, 0f);
297 catch (Throwable e) {
298 LOG.debug(e);
302 public final boolean isAlphaModeEnabled(final Window window) {
303 if (!window.isDisplayable() || !window.isShowing()) {
304 throw new IllegalArgumentException("window must be displayable and showing. window=" + window);
306 return isAlphaModeSupported();
309 public final void setAlphaModeEnabled(final Window window, final boolean state) {
310 if (!window.isDisplayable() || !window.isShowing()) {
311 throw new IllegalArgumentException("window must be displayable and showing. window=" + window);
315 public void hideDialog(JDialog dialog, Project project) {
316 if (project == null) {
317 dialog.dispose();
319 else {
320 IdeFrameImpl frame = getFrame(project);
321 if (frame.isActive()) {
322 dialog.dispose();
324 else {
325 queueForDisposal(dialog, project);
326 dialog.setVisible(false);
331 public final void disposeComponent() {}
333 public final void initComponent() {
336 public final void doNotSuggestAsParent(final Window window) {
337 myWindowWatcher.doNotSuggestAsParent(window);
340 public final void dispatchComponentEvent(final ComponentEvent e) {
341 myWindowWatcher.dispatchComponentEvent(e);
344 public final Window suggestParentWindow(final Project project) {
345 return myWindowWatcher.suggestParentWindow(project);
348 public final StatusBar getStatusBar(final Project project) {
349 if (!myProject2Frame.containsKey(project)) {
350 return null;
352 final IdeFrameImpl frame = getFrame(project);
353 LOG.assertTrue(frame != null);
354 return frame.getStatusBar();
357 public IdeFrame findFrameFor(@Nullable final Project project) {
358 IdeFrameImpl frame = null;
359 if (project != null) {
360 frame = getFrame(project);
361 } else {
362 Container eachParent = getMostRecentFocusedWindow();
363 while(eachParent != null) {
364 if (eachParent instanceof IdeFrameImpl) {
365 frame = (IdeFrameImpl)eachParent;
366 break;
368 eachParent = eachParent.getParent();
371 if (frame == null) {
372 frame = tryToFindTheOnlyFrame();
376 LOG.assertTrue(frame != null, "Project: " + project);
378 return frame;
381 private IdeFrameImpl tryToFindTheOnlyFrame() {
382 IdeFrameImpl candidate = null;
383 final Frame[] all = Frame.getFrames();
384 for (Frame each : all) {
385 if (each instanceof IdeFrameImpl) {
386 if (candidate == null) {
387 candidate = (IdeFrameImpl)each;
388 } else {
389 candidate = null;
390 break;
394 return candidate;
397 public final IdeFrameImpl getFrame(final Project project) {
398 // no assert! otherwise WindowWatcher.suggestParentWindow fails for default project
399 //LOG.assertTrue(myProject2Frame.containsKey(project));
400 return myProject2Frame.get(project);
403 public IdeFrame getIdeFrame(final Project project) {
404 if (project != null) {
405 return getFrame(project);
406 } else {
407 final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
408 final Component parent = UIUtil.findUltimateParent(window);
409 if (parent instanceof IdeFrame) return (IdeFrame)parent;
411 final Frame[] frames = Frame.getFrames();
412 for (Frame each : frames) {
413 if (each instanceof IdeFrame) {
414 return (IdeFrame)each;
419 return null;
422 public final IdeFrameImpl allocateFrame(final Project project) {
423 LOG.assertTrue(!myProject2Frame.containsKey(project));
425 final IdeFrameImpl frame;
426 if (myProject2Frame.containsKey(null)) {
427 frame = myProject2Frame.get(null);
428 myProject2Frame.remove(null);
429 myProject2Frame.put(project, frame);
430 frame.setProject(project);
432 else {
433 frame = new IdeFrameImpl((ApplicationInfoEx)ApplicationInfo.getInstance(), ActionManager.getInstance(), UISettings.getInstance(), DataManager.getInstance(),
434 KeymapManager.getInstance(), ApplicationManager.getApplication(), new String[0]);
435 if (myFrameBounds != null) {
436 frame.setBounds(myFrameBounds);
438 frame.setProject(project);
439 myProject2Frame.put(project, frame);
440 frame.setVisible(true);
443 frame.addWindowListener(myActivationListener);
445 myEventDispatcher.getMulticaster().frameCreated(frame);
447 return frame;
450 private void proceedDialogDisposalQueue(Project project) {
451 Set<JDialog> dialogs = myDialogsToDispose.get(project);
452 if (dialogs == null) return;
453 for (JDialog dialog : dialogs) {
454 dialog.dispose();
456 myDialogsToDispose.put(project, null);
459 private void queueForDisposal(JDialog dialog, Project project) {
460 Set<JDialog> dialogs = myDialogsToDispose.get(project);
461 if (dialogs == null) {
462 dialogs = new HashSet<JDialog>();
463 myDialogsToDispose.put(project, dialogs);
465 dialogs.add(dialog);
468 public final void releaseFrame(final IdeFrameImpl frame) {
470 myEventDispatcher.getMulticaster().beforeFrameReleased(frame);
472 final Project project = frame.getProject();
473 LOG.assertTrue(project != null);
475 frame.removeWindowListener(myActivationListener);
476 proceedDialogDisposalQueue(project);
478 frame.setProject(null);
479 frame.setTitle(null);
480 frame.setFileTitle(null, null);
482 final StatusBarEx statusBar = frame.getStatusBar();
483 statusBar.clear();
485 myProject2Frame.remove(project);
486 if (myProject2Frame.isEmpty()) {
487 myProject2Frame.put(null, frame);
489 else {
491 statusBar.dispose();
492 frame.dispose();
496 public final Window getMostRecentFocusedWindow() {
497 return myWindowWatcher.getFocusedWindow();
500 public final Component getFocusedComponent(@NotNull final Window window) {
501 return myWindowWatcher.getFocusedComponent(window);
504 public final Component getFocusedComponent(final Project project) {
505 return myWindowWatcher.getFocusedComponent(project);
509 * Private part
511 public final CommandProcessor getCommandProcessor() {
512 return myCommandProcessor;
515 public final String getExternalFileName() {
516 return "window.manager";
519 public final void readExternal(final Element element) {
520 final Element frameElement = element.getChild(FRAME_ELEMENT);
521 if (frameElement != null) {
522 myFrameBounds = loadFrameBounds(frameElement);
523 try {
524 myFrameExtendedState = Integer.parseInt(frameElement.getAttributeValue(EXTENDED_STATE_ATTR));
525 if ((myFrameExtendedState & Frame.ICONIFIED) > 0) {
526 myFrameExtendedState = Frame.NORMAL;
529 catch (NumberFormatException ignored) {
530 myFrameExtendedState = Frame.NORMAL;
534 final Element desktopElement = element.getChild(DesktopLayout.TAG);
535 if (desktopElement != null) {
536 myLayout.readExternal(desktopElement);
540 private static Rectangle loadFrameBounds(final Element frameElement) {
541 Rectangle bounds = new Rectangle();
542 try {
543 bounds.x = Integer.parseInt(frameElement.getAttributeValue(X_ATTR));
545 catch (NumberFormatException ignored) {
546 return null;
548 try {
549 bounds.y = Integer.parseInt(frameElement.getAttributeValue(Y_ATTR));
551 catch (NumberFormatException ignored) {
552 return null;
554 try {
555 bounds.width = Integer.parseInt(frameElement.getAttributeValue(WIDTH_ATTR));
557 catch (NumberFormatException ignored) {
558 return null;
560 try {
561 bounds.height = Integer.parseInt(frameElement.getAttributeValue(HEIGHT_ATTR));
563 catch (NumberFormatException ignored) {
564 return null;
566 return bounds;
569 public final void writeExternal(final Element element) {
570 // Save frame bounds
571 final Element frameElement = new Element(FRAME_ELEMENT);
572 element.addContent(frameElement);
573 final Project[] projects = ProjectManager.getInstance().getOpenProjects();
574 final Project project;
575 if (projects.length > 0) {
576 project = projects[projects.length - 1];
578 else {
579 project = null;
582 final IdeFrameImpl frame = getFrame(project);
583 if (frame != null) {
584 final Rectangle rectangle = frame.getBounds();
585 frameElement.setAttribute(X_ATTR, Integer.toString(rectangle.x));
586 frameElement.setAttribute(Y_ATTR, Integer.toString(rectangle.y));
587 frameElement.setAttribute(WIDTH_ATTR, Integer.toString(rectangle.width));
588 frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(rectangle.height));
589 frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(frame.getExtendedState()));
591 // Save default layout
592 final Element layoutElement = new Element(DesktopLayout.TAG);
593 element.addContent(layoutElement);
594 myLayout.writeExternal(layoutElement);
598 public final DesktopLayout getLayout() {
599 return myLayout;
602 public final void setLayout(final DesktopLayout layout) {
603 myLayout.copyFrom(layout);
606 @NotNull
607 public final String getComponentName() {
608 return "WindowManager";
612 * We cannot clear selected menu path just by changing of focused window. Under Windows LAF
613 * focused window changes sporadically when user clickes on menu item or submenu. The problem
614 * is that all popups under Windows LAF always has native window ancestor. This window isn't
615 * focusable but by mouse click focused window changes in this manner:
616 * InitialFocusedWindow->null
617 * null->InitialFocusedWindow
618 * To fix this problem we use alarm to accumulate such focus events.
620 private static final class SUN_BUG_ID_4218084_Patch implements PropertyChangeListener {
621 private final Alarm myAlarm;
622 private Window myInitialFocusedWindow;
623 private Window myLastFocusedWindow;
624 private final Runnable myClearSelectedPathRunnable;
626 public SUN_BUG_ID_4218084_Patch() {
627 myAlarm = new Alarm();
628 myClearSelectedPathRunnable = new Runnable() {
629 public void run() {
630 if (myInitialFocusedWindow != myLastFocusedWindow) {
631 MenuSelectionManager.defaultManager().clearSelectedPath();
637 public void propertyChange(final PropertyChangeEvent e) {
638 if (myAlarm.getActiveRequestCount() == 0) {
639 myInitialFocusedWindow = (Window)e.getOldValue();
640 final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
641 if (selectedPath.length == 0) { // there is no visible popup
642 return;
644 Component firstComponent = null;
645 for (final MenuElement menuElement : selectedPath) {
646 final Component component = menuElement.getComponent();
647 if (component instanceof JMenuBar) {
648 firstComponent = component;
649 break;
650 } else if (component instanceof JPopupMenu) {
651 firstComponent = ((JPopupMenu) component).getInvoker();
652 break;
655 if (firstComponent == null) {
656 return;
658 final Window window = SwingUtilities.getWindowAncestor(firstComponent);
659 if (window != myInitialFocusedWindow) { // focused window doesn't have popup
660 return;
663 myLastFocusedWindow = (Window)e.getNewValue();
664 myAlarm.cancelAllRequests();
665 myAlarm.addRequest(myClearSelectedPathRunnable, 150);
669 public WindowWatcher getWindowWatcher() {
670 return myWindowWatcher;