sticky documentation popup [take 1]
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / WindowManagerImpl.java
blobc5f0c2ad74021200735a82d0f32bfb629454f98d
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 @Override
210 public Rectangle getScreenBounds(@NotNull Project project) {
211 final GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
212 final Point onScreen = getFrame(project).getLocationOnScreen();
213 final GraphicsDevice[] devices = environment.getScreenDevices();
214 for (final GraphicsDevice device : devices) {
215 final Rectangle bounds = device.getDefaultConfiguration().getBounds();
216 if (bounds.contains(onScreen)) {
217 return bounds;
221 return null;
225 public final boolean isInsideScreenBounds(final int x, final int y, final int width) {
226 return
227 x >= myScreenBounds.x + 50 - width &&
228 y >= myScreenBounds.y - 50 &&
229 x <= myScreenBounds.x + myScreenBounds.width - 50 &&
230 y <= myScreenBounds.y + myScreenBounds.height - 50;
233 public final boolean isInsideScreenBounds(final int x, final int y) {
234 return myScreenBounds.contains(x, y);
237 public final boolean isAlphaModeSupported() {
238 if (myAlphaModeSupported == null) {
239 myAlphaModeSupported = calcAlphaModelSupported();
241 return myAlphaModeSupported.booleanValue();
244 private static boolean calcAlphaModelSupported() {
245 if (AWTUtilitiesWrapper.isTranslucencyAPISupported()) {
246 return AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT);
248 try {
249 return WindowUtils.isWindowAlphaSupported();
251 catch (Throwable e) {
252 return false;
256 public final void setAlphaModeRatio(final Window window, final float ratio) {
257 if (!window.isDisplayable() || !window.isShowing()) {
258 throw new IllegalArgumentException("window must be displayable and showing. window=" + window);
260 if (ratio < 0.0f || ratio > 1.0f) {
261 throw new IllegalArgumentException("ratio must be in [0..1] range. ratio=" + ratio);
263 if (!isAlphaModeSupported() || !isAlphaModeEnabled(window)) {
264 return;
268 setAlphaMode(window, ratio);
271 private void setAlphaMode(Window window, float ratio) {
272 try {
273 if (SystemInfo.isMacOSLeopard) {
274 if (window instanceof JWindow) {
275 ((JWindow)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
276 } else if (window instanceof JDialog) {
277 ((JDialog)window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
280 else if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT)) {
281 AWTUtilitiesWrapper.setWindowOpacity(window, 1.0f - ratio);
283 else {
284 WindowUtils.setWindowAlpha(window, 1.0f - ratio);
287 catch (Throwable e) {
288 LOG.debug(e);
292 public void setWindowMask(final Window window, final Shape mask) {
293 try {
294 if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.PERPIXEL_TRANSPARENT)) {
295 AWTUtilitiesWrapper.setWindowShape(window, mask);
297 else {
298 WindowUtils.setWindowMask(window, mask);
301 catch (Throwable e) {
302 LOG.debug(e);
306 public void resetWindow(final Window window) {
307 try {
308 if (!isAlphaModeSupported()) return;
310 WindowUtils.setWindowMask(window, (Shape)null);
311 setAlphaMode(window, 0f);
313 catch (Throwable e) {
314 LOG.debug(e);
318 public final boolean isAlphaModeEnabled(final Window window) {
319 if (!window.isDisplayable() || !window.isShowing()) {
320 throw new IllegalArgumentException("window must be displayable and showing. window=" + window);
322 return isAlphaModeSupported();
325 public final void setAlphaModeEnabled(final Window window, final boolean state) {
326 if (!window.isDisplayable() || !window.isShowing()) {
327 throw new IllegalArgumentException("window must be displayable and showing. window=" + window);
331 public void hideDialog(JDialog dialog, Project project) {
332 if (project == null) {
333 dialog.dispose();
335 else {
336 IdeFrameImpl frame = getFrame(project);
337 if (frame.isActive()) {
338 dialog.dispose();
340 else {
341 queueForDisposal(dialog, project);
342 dialog.setVisible(false);
347 public final void disposeComponent() {}
349 public final void initComponent() {
352 public final void doNotSuggestAsParent(final Window window) {
353 myWindowWatcher.doNotSuggestAsParent(window);
356 public final void dispatchComponentEvent(final ComponentEvent e) {
357 myWindowWatcher.dispatchComponentEvent(e);
360 public final Window suggestParentWindow(final Project project) {
361 return myWindowWatcher.suggestParentWindow(project);
364 public final StatusBar getStatusBar(final Project project) {
365 if (!myProject2Frame.containsKey(project)) {
366 return null;
368 final IdeFrameImpl frame = getFrame(project);
369 LOG.assertTrue(frame != null);
370 return frame.getStatusBar();
373 public IdeFrame findFrameFor(@Nullable final Project project) {
374 IdeFrameImpl frame = null;
375 if (project != null) {
376 frame = getFrame(project);
377 } else {
378 Container eachParent = getMostRecentFocusedWindow();
379 while(eachParent != null) {
380 if (eachParent instanceof IdeFrameImpl) {
381 frame = (IdeFrameImpl)eachParent;
382 break;
384 eachParent = eachParent.getParent();
387 if (frame == null) {
388 frame = tryToFindTheOnlyFrame();
392 LOG.assertTrue(frame != null, "Project: " + project);
394 return frame;
397 private IdeFrameImpl tryToFindTheOnlyFrame() {
398 IdeFrameImpl candidate = null;
399 final Frame[] all = Frame.getFrames();
400 for (Frame each : all) {
401 if (each instanceof IdeFrameImpl) {
402 if (candidate == null) {
403 candidate = (IdeFrameImpl)each;
404 } else {
405 candidate = null;
406 break;
410 return candidate;
413 public final IdeFrameImpl getFrame(final Project project) {
414 // no assert! otherwise WindowWatcher.suggestParentWindow fails for default project
415 //LOG.assertTrue(myProject2Frame.containsKey(project));
416 return myProject2Frame.get(project);
419 public IdeFrame getIdeFrame(final Project project) {
420 if (project != null) {
421 return getFrame(project);
422 } else {
423 final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
424 final Component parent = UIUtil.findUltimateParent(window);
425 if (parent instanceof IdeFrame) return (IdeFrame)parent;
427 final Frame[] frames = Frame.getFrames();
428 for (Frame each : frames) {
429 if (each instanceof IdeFrame) {
430 return (IdeFrame)each;
435 return null;
438 public final IdeFrameImpl allocateFrame(final Project project) {
439 LOG.assertTrue(!myProject2Frame.containsKey(project));
441 final IdeFrameImpl frame;
442 if (myProject2Frame.containsKey(null)) {
443 frame = myProject2Frame.get(null);
444 myProject2Frame.remove(null);
445 myProject2Frame.put(project, frame);
446 frame.setProject(project);
448 else {
449 frame = new IdeFrameImpl((ApplicationInfoEx)ApplicationInfo.getInstance(), ActionManager.getInstance(), UISettings.getInstance(), DataManager.getInstance(),
450 KeymapManager.getInstance(), ApplicationManager.getApplication(), new String[0]);
451 if (myFrameBounds != null) {
452 frame.setBounds(myFrameBounds);
454 frame.setProject(project);
455 myProject2Frame.put(project, frame);
456 frame.setVisible(true);
459 frame.addWindowListener(myActivationListener);
461 myEventDispatcher.getMulticaster().frameCreated(frame);
463 return frame;
466 private void proceedDialogDisposalQueue(Project project) {
467 Set<JDialog> dialogs = myDialogsToDispose.get(project);
468 if (dialogs == null) return;
469 for (JDialog dialog : dialogs) {
470 dialog.dispose();
472 myDialogsToDispose.put(project, null);
475 private void queueForDisposal(JDialog dialog, Project project) {
476 Set<JDialog> dialogs = myDialogsToDispose.get(project);
477 if (dialogs == null) {
478 dialogs = new HashSet<JDialog>();
479 myDialogsToDispose.put(project, dialogs);
481 dialogs.add(dialog);
484 public final void releaseFrame(final IdeFrameImpl frame) {
486 myEventDispatcher.getMulticaster().beforeFrameReleased(frame);
488 final Project project = frame.getProject();
489 LOG.assertTrue(project != null);
491 frame.removeWindowListener(myActivationListener);
492 proceedDialogDisposalQueue(project);
494 frame.setProject(null);
495 frame.setTitle(null);
496 frame.setFileTitle(null, null);
498 final StatusBarEx statusBar = frame.getStatusBar();
499 statusBar.clear();
501 myProject2Frame.remove(project);
502 if (myProject2Frame.isEmpty()) {
503 myProject2Frame.put(null, frame);
505 else {
507 statusBar.dispose();
508 frame.dispose();
512 public final Window getMostRecentFocusedWindow() {
513 return myWindowWatcher.getFocusedWindow();
516 public final Component getFocusedComponent(@NotNull final Window window) {
517 return myWindowWatcher.getFocusedComponent(window);
520 public final Component getFocusedComponent(final Project project) {
521 return myWindowWatcher.getFocusedComponent(project);
525 * Private part
527 public final CommandProcessor getCommandProcessor() {
528 return myCommandProcessor;
531 public final String getExternalFileName() {
532 return "window.manager";
535 public final void readExternal(final Element element) {
536 final Element frameElement = element.getChild(FRAME_ELEMENT);
537 if (frameElement != null) {
538 myFrameBounds = loadFrameBounds(frameElement);
539 try {
540 myFrameExtendedState = Integer.parseInt(frameElement.getAttributeValue(EXTENDED_STATE_ATTR));
541 if ((myFrameExtendedState & Frame.ICONIFIED) > 0) {
542 myFrameExtendedState = Frame.NORMAL;
545 catch (NumberFormatException ignored) {
546 myFrameExtendedState = Frame.NORMAL;
550 final Element desktopElement = element.getChild(DesktopLayout.TAG);
551 if (desktopElement != null) {
552 myLayout.readExternal(desktopElement);
556 private static Rectangle loadFrameBounds(final Element frameElement) {
557 Rectangle bounds = new Rectangle();
558 try {
559 bounds.x = Integer.parseInt(frameElement.getAttributeValue(X_ATTR));
561 catch (NumberFormatException ignored) {
562 return null;
564 try {
565 bounds.y = Integer.parseInt(frameElement.getAttributeValue(Y_ATTR));
567 catch (NumberFormatException ignored) {
568 return null;
570 try {
571 bounds.width = Integer.parseInt(frameElement.getAttributeValue(WIDTH_ATTR));
573 catch (NumberFormatException ignored) {
574 return null;
576 try {
577 bounds.height = Integer.parseInt(frameElement.getAttributeValue(HEIGHT_ATTR));
579 catch (NumberFormatException ignored) {
580 return null;
582 return bounds;
585 public final void writeExternal(final Element element) {
586 // Save frame bounds
587 final Element frameElement = new Element(FRAME_ELEMENT);
588 element.addContent(frameElement);
589 final Project[] projects = ProjectManager.getInstance().getOpenProjects();
590 final Project project;
591 if (projects.length > 0) {
592 project = projects[projects.length - 1];
594 else {
595 project = null;
598 final IdeFrameImpl frame = getFrame(project);
599 if (frame != null) {
600 final Rectangle rectangle = frame.getBounds();
601 frameElement.setAttribute(X_ATTR, Integer.toString(rectangle.x));
602 frameElement.setAttribute(Y_ATTR, Integer.toString(rectangle.y));
603 frameElement.setAttribute(WIDTH_ATTR, Integer.toString(rectangle.width));
604 frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(rectangle.height));
605 frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(frame.getExtendedState()));
607 // Save default layout
608 final Element layoutElement = new Element(DesktopLayout.TAG);
609 element.addContent(layoutElement);
610 myLayout.writeExternal(layoutElement);
614 public final DesktopLayout getLayout() {
615 return myLayout;
618 public final void setLayout(final DesktopLayout layout) {
619 myLayout.copyFrom(layout);
622 @NotNull
623 public final String getComponentName() {
624 return "WindowManager";
628 * We cannot clear selected menu path just by changing of focused window. Under Windows LAF
629 * focused window changes sporadically when user clickes on menu item or submenu. The problem
630 * is that all popups under Windows LAF always has native window ancestor. This window isn't
631 * focusable but by mouse click focused window changes in this manner:
632 * InitialFocusedWindow->null
633 * null->InitialFocusedWindow
634 * To fix this problem we use alarm to accumulate such focus events.
636 private static final class SUN_BUG_ID_4218084_Patch implements PropertyChangeListener {
637 private final Alarm myAlarm;
638 private Window myInitialFocusedWindow;
639 private Window myLastFocusedWindow;
640 private final Runnable myClearSelectedPathRunnable;
642 public SUN_BUG_ID_4218084_Patch() {
643 myAlarm = new Alarm();
644 myClearSelectedPathRunnable = new Runnable() {
645 public void run() {
646 if (myInitialFocusedWindow != myLastFocusedWindow) {
647 MenuSelectionManager.defaultManager().clearSelectedPath();
653 public void propertyChange(final PropertyChangeEvent e) {
654 if (myAlarm.getActiveRequestCount() == 0) {
655 myInitialFocusedWindow = (Window)e.getOldValue();
656 final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
657 if (selectedPath.length == 0) { // there is no visible popup
658 return;
660 Component firstComponent = null;
661 for (final MenuElement menuElement : selectedPath) {
662 final Component component = menuElement.getComponent();
663 if (component instanceof JMenuBar) {
664 firstComponent = component;
665 break;
666 } else if (component instanceof JPopupMenu) {
667 firstComponent = ((JPopupMenu) component).getInvoker();
668 break;
671 if (firstComponent == null) {
672 return;
674 final Window window = SwingUtilities.getWindowAncestor(firstComponent);
675 if (window != myInitialFocusedWindow) { // focused window doesn't have popup
676 return;
679 myLastFocusedWindow = (Window)e.getNewValue();
680 myAlarm.cancelAllRequests();
681 myAlarm.addRequest(myClearSelectedPathRunnable, 150);
685 public WindowWatcher getWindowWatcher() {
686 return myWindowWatcher;