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
;
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
)) {
225 public final boolean isInsideScreenBounds(final int x
, final int y
, final int width
) {
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
);
249 return WindowUtils
.isWindowAlphaSupported();
251 catch (Throwable e
) {
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
)) {
268 setAlphaMode(window
, ratio
);
271 private void setAlphaMode(Window window
, float ratio
) {
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
);
284 WindowUtils
.setWindowAlpha(window
, 1.0f
- ratio
);
287 catch (Throwable e
) {
292 public void setWindowMask(final Window window
, final Shape mask
) {
294 if (AWTUtilitiesWrapper
.isTranslucencySupported(AWTUtilitiesWrapper
.PERPIXEL_TRANSPARENT
)) {
295 AWTUtilitiesWrapper
.setWindowShape(window
, mask
);
298 WindowUtils
.setWindowMask(window
, mask
);
301 catch (Throwable e
) {
306 public void resetWindow(final Window window
) {
308 if (!isAlphaModeSupported()) return;
310 WindowUtils
.setWindowMask(window
, (Shape
)null);
311 setAlphaMode(window
, 0f
);
313 catch (Throwable 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) {
336 IdeFrameImpl frame
= getFrame(project
);
337 if (frame
.isActive()) {
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
)) {
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
);
378 Container eachParent
= getMostRecentFocusedWindow();
379 while(eachParent
!= null) {
380 if (eachParent
instanceof IdeFrameImpl
) {
381 frame
= (IdeFrameImpl
)eachParent
;
384 eachParent
= eachParent
.getParent();
388 frame
= tryToFindTheOnlyFrame();
392 LOG
.assertTrue(frame
!= null, "Project: " + project
);
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
;
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
);
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
;
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
);
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
);
466 private void proceedDialogDisposalQueue(Project project
) {
467 Set
<JDialog
> dialogs
= myDialogsToDispose
.get(project
);
468 if (dialogs
== null) return;
469 for (JDialog dialog
: dialogs
) {
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
);
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();
501 myProject2Frame
.remove(project
);
502 if (myProject2Frame
.isEmpty()) {
503 myProject2Frame
.put(null, frame
);
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
);
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
);
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();
559 bounds
.x
= Integer
.parseInt(frameElement
.getAttributeValue(X_ATTR
));
561 catch (NumberFormatException ignored
) {
565 bounds
.y
= Integer
.parseInt(frameElement
.getAttributeValue(Y_ATTR
));
567 catch (NumberFormatException ignored
) {
571 bounds
.width
= Integer
.parseInt(frameElement
.getAttributeValue(WIDTH_ATTR
));
573 catch (NumberFormatException ignored
) {
577 bounds
.height
= Integer
.parseInt(frameElement
.getAttributeValue(HEIGHT_ATTR
));
579 catch (NumberFormatException ignored
) {
585 public final void writeExternal(final Element element
) {
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];
598 final IdeFrameImpl frame
= getFrame(project
);
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() {
618 public final void setLayout(final DesktopLayout layout
) {
619 myLayout
.copyFrom(layout
);
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() {
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
660 Component firstComponent
= null;
661 for (final MenuElement menuElement
: selectedPath
) {
662 final Component component
= menuElement
.getComponent();
663 if (component
instanceof JMenuBar
) {
664 firstComponent
= component
;
666 } else if (component
instanceof JPopupMenu
) {
667 firstComponent
= ((JPopupMenu
) component
).getInvoker();
671 if (firstComponent
== null) {
674 final Window window
= SwingUtilities
.getWindowAncestor(firstComponent
);
675 if (window
!= myInitialFocusedWindow
) { // focused window doesn't have popup
679 myLastFocusedWindow
= (Window
)e
.getNewValue();
680 myAlarm
.cancelAllRequests();
681 myAlarm
.addRequest(myClearSelectedPathRunnable
, 150);
685 public WindowWatcher
getWindowWatcher() {
686 return myWindowWatcher
;