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
;
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
;
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);
83 @SuppressWarnings({"HardCodedStringLiteral"})
84 private static void initialize() {
86 System
.loadLibrary("jawt");
87 ourAlphaModeLibraryLoaded
= true;
89 catch (Throwable exc
) {
90 ourAlphaModeLibraryLoaded
= false;
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
126 * @param applicationInfoEx
127 * @param actionManager
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()]);
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
) {
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
);
233 return WindowUtils
.isWindowAlphaSupported();
235 catch (Throwable e
) {
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
)) {
252 setAlphaMode(window
, ratio
);
255 private void setAlphaMode(Window window
, float ratio
) {
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
);
268 WindowUtils
.setWindowAlpha(window
, 1.0f
- ratio
);
271 catch (Throwable e
) {
276 public void setWindowMask(final Window window
, final Shape mask
) {
278 if (AWTUtilitiesWrapper
.isTranslucencySupported(AWTUtilitiesWrapper
.PERPIXEL_TRANSPARENT
)) {
279 AWTUtilitiesWrapper
.setWindowShape(window
, mask
);
282 WindowUtils
.setWindowMask(window
, mask
);
285 catch (Throwable e
) {
290 public void resetWindow(final Window window
) {
292 if (!isAlphaModeSupported()) return;
294 WindowUtils
.setWindowMask(window
, (Shape
)null);
295 setAlphaMode(window
, 0f
);
297 catch (Throwable 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) {
320 IdeFrameImpl frame
= getFrame(project
);
321 if (frame
.isActive()) {
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
)) {
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
);
362 Container eachParent
= getMostRecentFocusedWindow();
363 while(eachParent
!= null) {
364 if (eachParent
instanceof IdeFrameImpl
) {
365 frame
= (IdeFrameImpl
)eachParent
;
368 eachParent
= eachParent
.getParent();
372 frame
= tryToFindTheOnlyFrame();
376 LOG
.assertTrue(frame
!= null, "Project: " + project
);
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
;
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
);
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
;
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
);
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
);
450 private void proceedDialogDisposalQueue(Project project
) {
451 Set
<JDialog
> dialogs
= myDialogsToDispose
.get(project
);
452 if (dialogs
== null) return;
453 for (JDialog dialog
: dialogs
) {
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
);
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();
485 myProject2Frame
.remove(project
);
486 if (myProject2Frame
.isEmpty()) {
487 myProject2Frame
.put(null, frame
);
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
);
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
);
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();
543 bounds
.x
= Integer
.parseInt(frameElement
.getAttributeValue(X_ATTR
));
545 catch (NumberFormatException ignored
) {
549 bounds
.y
= Integer
.parseInt(frameElement
.getAttributeValue(Y_ATTR
));
551 catch (NumberFormatException ignored
) {
555 bounds
.width
= Integer
.parseInt(frameElement
.getAttributeValue(WIDTH_ATTR
));
557 catch (NumberFormatException ignored
) {
561 bounds
.height
= Integer
.parseInt(frameElement
.getAttributeValue(HEIGHT_ATTR
));
563 catch (NumberFormatException ignored
) {
569 public final void writeExternal(final Element element
) {
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];
582 final IdeFrameImpl frame
= getFrame(project
);
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() {
602 public final void setLayout(final DesktopLayout layout
) {
603 myLayout
.copyFrom(layout
);
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() {
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
644 Component firstComponent
= null;
645 for (final MenuElement menuElement
: selectedPath
) {
646 final Component component
= menuElement
.getComponent();
647 if (component
instanceof JMenuBar
) {
648 firstComponent
= component
;
650 } else if (component
instanceof JPopupMenu
) {
651 firstComponent
= ((JPopupMenu
) component
).getInvoker();
655 if (firstComponent
== null) {
658 final Window window
= SwingUtilities
.getWindowAncestor(firstComponent
);
659 if (window
!= myInitialFocusedWindow
) { // focused window doesn't have popup
663 myLastFocusedWindow
= (Window
)e
.getNewValue();
664 myAlarm
.cancelAllRequests();
665 myAlarm
.addRequest(myClearSelectedPathRunnable
, 150);
669 public WindowWatcher
getWindowWatcher() {
670 return myWindowWatcher
;