3df08c80dd09ad756b9abdaaeaa44565c499d9ea
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / DesktopLayout.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.wm.impl;
17
18 import com.intellij.ide.ui.UISettings;
19 import com.intellij.openapi.util.JDOMExternalizable;
20 import com.intellij.openapi.wm.ToolWindow;
21 import com.intellij.openapi.wm.ToolWindowAnchor;
22 import com.intellij.util.ArrayUtil;
23 import org.jdom.Element;
24 import org.jetbrains.annotations.NonNls;
25 import org.jetbrains.annotations.Nullable;
26
27 import java.util.*;
28
29 /**
30  * @author Vladimir Kondratyev
31  */
32 public final class DesktopLayout implements JDOMExternalizable {
33   @NonNls static final String TAG = "layout";
34   /**
35    * Map between <code>id</code>s and registered <code>WindowInfo</code>s.
36    */
37   private final com.intellij.util.containers.HashMap<String, WindowInfoImpl> myRegisteredId2Info;
38   /**
39    * Map between <code>id</code>s and unregistered <code>WindowInfo</code>s.
40    */
41   private final com.intellij.util.containers.HashMap<String, WindowInfoImpl> myUnregisteredId2Info;
42   /**
43    *
44    */
45   private static final MyWindowInfoComparator ourWindowInfoComparator = new MyWindowInfoComparator();
46   /**
47    * Don't use this member directly. Get it only by <code>getInfos</code> method.
48    * It exists here only for optimization purposes. This member can be <code>null</code>
49    * if the cached data is invalid.
50    */
51   private WindowInfoImpl[] myRegisteredInfos;
52   /**
53    * Don't use this member directly. Get it only by <code>getUnregisteredInfos</code> method.
54    * It exists here only for optimization purposes. This member can be <code>null</code>
55    * if the cached data is invalid.
56    */
57   private WindowInfoImpl[] myUnregisteredInfos;
58   /**
59    * Don't use this member directly. Get it only by <code>getAllInfos</code> method.
60    * It exists here only for optimization purposes. This member can be <code>null</code>
61    * if the cached data is invalid.
62    */
63   private WindowInfoImpl[] myAllInfos;
64   @NonNls public static final String ID_ATTR = "id";
65
66   public DesktopLayout() {
67     myRegisteredId2Info = new com.intellij.util.containers.HashMap<String, WindowInfoImpl>();
68     myUnregisteredId2Info = new com.intellij.util.containers.HashMap<String, WindowInfoImpl>();
69   }
70
71   /**
72    * Copies itself from the passed
73    *
74    * @param layout to be copied.
75    */
76   public final void copyFrom(final DesktopLayout layout) {
77     final WindowInfoImpl[] infos = layout.getAllInfos();
78     for (int i = 0; i < infos.length; i++) {
79       WindowInfoImpl info = myRegisteredId2Info.get(infos[i].getId());
80       if (info != null) {
81         info.copyFrom(infos[i]);
82         continue;
83       }
84       info = myUnregisteredId2Info.get(infos[i].getId());
85       if (info != null) {
86         info.copyFrom(infos[i]);
87       }
88       else {
89         myUnregisteredId2Info.put(infos[i].getId(), infos[i].copy());
90       }
91     }
92     // invalidate caches
93     myRegisteredInfos = null;
94     myUnregisteredInfos = null;
95     myAllInfos = null;
96     // normalize orders
97     normalizeOrder(getAllInfos(ToolWindowAnchor.TOP));
98     normalizeOrder(getAllInfos(ToolWindowAnchor.LEFT));
99     normalizeOrder(getAllInfos(ToolWindowAnchor.BOTTOM));
100     normalizeOrder(getAllInfos(ToolWindowAnchor.RIGHT));
101   }
102
103   /**
104    * Creates or gets <code>WindowInfo</code> for the specified <code>id</code>. If tool
105    * window is being registered first time the method uses <code>anchor</code>.
106    *
107    * @param id     <code>id</code> of tool window to be registered.
108    * @param anchor the default tool window anchor.
109    * @return
110    */
111   final WindowInfoImpl register(final String id, final ToolWindowAnchor anchor, final boolean splitMode) {
112     WindowInfoImpl info = myUnregisteredId2Info.get(id);
113     if (info != null) { // tool window has been already registered some time
114       myUnregisteredId2Info.remove(id);
115     }
116     else { // tool window is being registered first time
117       info = new WindowInfoImpl(id);
118       info.setAnchor(anchor);
119       info.setSplit(splitMode);
120     }
121     myRegisteredId2Info.put(id, info);
122     // invalidate caches
123     myRegisteredInfos = null;
124     myUnregisteredInfos = null;
125     myAllInfos = null;
126     //
127     return info;
128   }
129
130   final void unregister(final String id) {
131     final WindowInfoImpl info = myRegisteredId2Info.remove(id).copy();
132     myUnregisteredId2Info.put(id, info);
133     // invalidate caches
134     myRegisteredInfos = null;
135     myUnregisteredInfos = null;
136     myAllInfos = null;
137   }
138
139   /**
140    * @return <code>WindowInfo</code> for the window with specified <code>id</code>.
141    *         If <code>onlyRegistered</code> is <code>true</code> then returns not <code>null</code>
142    *         value if and only if window with <code>id</code> is registered one.
143    */
144   final WindowInfoImpl getInfo(final String id, final boolean onlyRegistered) {
145     final WindowInfoImpl info = myRegisteredId2Info.get(id);
146     if (onlyRegistered || info != null) {
147       return info;
148     }
149     else {
150       return myUnregisteredId2Info.get(id);
151     }
152   }
153
154   @Nullable
155   final String getActiveId() {
156     final WindowInfoImpl[] infos = getInfos();
157     for (WindowInfoImpl info : infos) {
158       if (info.isActive()) {
159         return info.getId();
160       }
161     }
162     return null;
163   }
164
165   /**
166    * @return <code>WindowInfo</code>s for all registered tool windows.
167    */
168   final WindowInfoImpl[] getInfos() {
169     if (myRegisteredInfos == null) {
170       myRegisteredInfos = myRegisteredId2Info.values().toArray(new WindowInfoImpl[myRegisteredId2Info.size()]);
171     }
172     return myRegisteredInfos;
173   }
174
175   /**
176    * @return <code>WindowInfos</code>s for all windows that are currently unregistered.
177    */
178   private WindowInfoImpl[] getUnregisteredInfos() {
179     if (myUnregisteredInfos == null) {
180       myUnregisteredInfos = myUnregisteredId2Info.values().toArray(new WindowInfoImpl[myUnregisteredId2Info.size()]);
181     }
182     return myUnregisteredInfos;
183   }
184
185   /**
186    * @return <code>WindowInfo</code>s of all (registered and unregistered) tool windows.
187    */
188   WindowInfoImpl[] getAllInfos() {
189     final WindowInfoImpl[] registeredInfos = getInfos();
190     final WindowInfoImpl[] unregisteredInfos = getUnregisteredInfos();
191     myAllInfos = ArrayUtil.mergeArrays(registeredInfos, unregisteredInfos, WindowInfoImpl.class);
192     return myAllInfos;
193   }
194
195   /**
196    * @return all (registered and not unregistered) <code>WindowInfos</code> for the specified <code>anchor</code>.
197    *         Returned infos are sorted by order.
198    */
199   private WindowInfoImpl[] getAllInfos(final ToolWindowAnchor anchor) {
200     WindowInfoImpl[] infos = getAllInfos();
201     final ArrayList<WindowInfoImpl> list = new ArrayList<WindowInfoImpl>(infos.length);
202     for (WindowInfoImpl info : infos) {
203       if (anchor == info.getAnchor()) {
204         list.add(info);
205       }
206     }
207     infos = list.toArray(new WindowInfoImpl[list.size()]);
208     Arrays.sort(infos, ourWindowInfoComparator);
209     return infos;
210   }
211
212   /**
213    * Normalizes order of windows in the passed array. Note, that array should be
214    * sorted by order (by ascending). Order of first window will be <code>0</code>.
215    */
216   private static void normalizeOrder(final WindowInfoImpl[] infos) {
217     for (int i = 0; i < infos.length; i++) {
218       infos[i].setOrder(i);
219     }
220   }
221
222   final boolean isToolWindowRegistered(final String id) {
223     return myRegisteredId2Info.containsKey(id);
224   }
225
226   /**
227    * @return comparator which compares <code>StripeButtons</code> in the stripe with
228    *         specified <code>anchor</code>.
229    */
230   final Comparator comparator(final ToolWindowAnchor anchor) {
231     return new MyStripeButtonComparator(anchor);
232   }
233
234   /**
235    * @param anchor anchor of the stripe.
236    * @return maximum ordinal number in the specified stripe. Returns <code>-1</code>
237    *         if there is no any tool window with the specified anchor.
238    */
239   private int getMaxOrder(final ToolWindowAnchor anchor) {
240     int res = -1;
241     final WindowInfoImpl[] infos = getAllInfos();
242     for (int i = 0; i < infos.length; i++) {
243       final WindowInfoImpl info = infos[i];
244       if (anchor == info.getAnchor() && res < info.getOrder()) {
245         res = info.getOrder();
246       }
247     }
248     return res;
249   }
250
251   /**
252    * Sets new <code>anchor</code> and <code>id</code> for the specified tool window.
253    * Also the method properly updates order of all other tool windows.
254    *
255    * @param newAnchor new anchor
256    * @param newOrder  new order
257    */
258   final void setAnchor(final String id, final ToolWindowAnchor newAnchor, int newOrder) {
259     if (newOrder == -1) { // if order isn't defined then the window will the last in the stripe
260       newOrder = getMaxOrder(newAnchor) + 1;
261     }
262     final WindowInfoImpl info = getInfo(id, true);
263     final ToolWindowAnchor oldAnchor = info.getAnchor();
264     // Shift order to the right in the target stripe.
265     final WindowInfoImpl[] infos = getAllInfos(newAnchor);
266     for (int i = infos.length - 1; i > -1; i--) {
267       final WindowInfoImpl info2 = infos[i];
268       if (newOrder <= info2.getOrder()) {
269         info2.setOrder(info2.getOrder() + 1);
270       }
271     }
272     // "move" window into the target position
273     info.setAnchor(newAnchor);
274     info.setOrder(newOrder);
275     // Normalize orders in the source and target stripes
276     normalizeOrder(getAllInfos(oldAnchor));
277     if (oldAnchor != newAnchor) {
278       normalizeOrder(getAllInfos(newAnchor));
279     }
280   }
281
282   final void setSplitMode(final String id, boolean split) {
283     final WindowInfoImpl info = getInfo(id, true);
284     info.setSplit(split);
285   }
286
287   public final void readExternal(final org.jdom.Element layoutElement) {
288     for (Iterator i = layoutElement.getChildren().iterator(); i.hasNext();) {
289       final Element e = (Element)i.next();
290       if (WindowInfoImpl.TAG.equals(e.getName())) {
291         final WindowInfoImpl info = new WindowInfoImpl(e.getAttributeValue(ID_ATTR));
292         info.readExternal(e);
293         if (info.getOrder() == -1) { // if order isn't defined then window's button will be the last one in the stripe
294           info.setOrder(getMaxOrder(info.getAnchor()) + 1);
295         }
296         myUnregisteredId2Info.put(info.getId(), info);
297       }
298     }
299   }
300
301   public final void writeExternal(final Element layoutElement) {
302     final WindowInfoImpl[] infos = getAllInfos();
303     for (int i = 0; i < infos.length; i++) {
304       final Element element = new Element(WindowInfoImpl.TAG);
305       infos[i].writeExternal(element);
306       layoutElement.addContent(element);
307     }
308   }
309
310   public List<String> getVisibleIdsOn(final ToolWindowAnchor anchor, ToolWindowManagerImpl manager) {
311     ArrayList<String> ids = new ArrayList<String>();
312     for (WindowInfoImpl each : getInfos()) {
313       if (manager == null) break;
314       if (each.getAnchor() == anchor) {
315         final ToolWindow window = manager.getToolWindow(each.getId());
316         if (window == null) continue;
317         if (window.isAvailable() || UISettings.getInstance().ALWAYS_SHOW_WINDOW_BUTTONS) {
318           ids.add(each.getId());
319         }
320       }
321     }
322     return ids;
323   }
324
325   private static final class MyWindowInfoComparator implements Comparator<WindowInfoImpl> {
326     public int compare(final WindowInfoImpl info1, final WindowInfoImpl info2) {
327       return info1.getOrder() - info2.getOrder();
328     }
329   }
330
331   private final class MyStripeButtonComparator implements Comparator {
332     private final com.intellij.util.containers.HashMap<String, WindowInfoImpl> myId2Info;
333
334     public MyStripeButtonComparator(final ToolWindowAnchor anchor) {
335       myId2Info = new com.intellij.util.containers.HashMap<String, WindowInfoImpl>();
336       final WindowInfoImpl[] infos = getInfos();
337       for (int i = 0; i < infos.length; i++) {
338         final WindowInfoImpl info = infos[i];
339         if (anchor == info.getAnchor()) {
340           myId2Info.put(info.getId(), info.copy());
341         }
342       }
343     }
344
345     public final int compare(final Object obj1, final Object obj2) {
346       final WindowInfoImpl info1 = myId2Info.get(((StripeButton)obj1).getWindowInfo().getId());
347       final int order1 = info1 != null ? info1.getOrder() : 0;
348
349       final WindowInfoImpl info2 = myId2Info.get(((StripeButton)obj2).getWindowInfo().getId());
350       final int order2 = info2 != null ? info2.getOrder() : 0;
351
352       return order1 - order2;
353     }
354   }
355 }