cleanup
[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 com.intellij.util.containers.HashMap;
24 import org.jdom.Element;
25 import org.jetbrains.annotations.NonNls;
26 import org.jetbrains.annotations.Nullable;
27
28 import java.util.*;
29
30 /**
31  * @author Vladimir Kondratyev
32  */
33 public final class DesktopLayout implements JDOMExternalizable {
34   @NonNls static final String TAG = "layout";
35   /**
36    * Map between <code>id</code>s and registered <code>WindowInfo</code>s.
37    */
38   private final HashMap<String, WindowInfoImpl> myRegisteredId2Info;
39   /**
40    * Map between <code>id</code>s and unregistered <code>WindowInfo</code>s.
41    */
42   private final HashMap<String, WindowInfoImpl> myUnregisteredId2Info;
43   /**
44    *
45    */
46   private static final MyWindowInfoComparator ourWindowInfoComparator = new MyWindowInfoComparator();
47   /**
48    * Don't use this member directly. Get it only by <code>getInfos</code> method.
49    * It exists here only for optimization purposes. This member can be <code>null</code>
50    * if the cached data is invalid.
51    */
52   private WindowInfoImpl[] myRegisteredInfos;
53   /**
54    * Don't use this member directly. Get it only by <code>getUnregisteredInfos</code> method.
55    * It exists here only for optimization purposes. This member can be <code>null</code>
56    * if the cached data is invalid.
57    */
58   private WindowInfoImpl[] myUnregisteredInfos;
59   /**
60    * Don't use this member directly. Get it only by <code>getAllInfos</code> method.
61    * It exists here only for optimization purposes. This member can be <code>null</code>
62    * if the cached data is invalid.
63    */
64   private WindowInfoImpl[] myAllInfos;
65   @NonNls public static final String ID_ATTR = "id";
66
67   public DesktopLayout() {
68     myRegisteredId2Info = new HashMap<String, WindowInfoImpl>();
69     myUnregisteredId2Info = new HashMap<String, WindowInfoImpl>();
70   }
71
72   /**
73    * Copies itself from the passed
74    *
75    * @param layout to be copied.
76    */
77   public final void copyFrom(final DesktopLayout layout) {
78     final WindowInfoImpl[] infos = layout.getAllInfos();
79     for (WindowInfoImpl info1 : infos) {
80       WindowInfoImpl info = myRegisteredId2Info.get(info1.getId());
81       if (info != null) {
82         info.copyFrom(info1);
83         continue;
84       }
85       info = myUnregisteredId2Info.get(info1.getId());
86       if (info != null) {
87         info.copyFrom(info1);
88       }
89       else {
90         myUnregisteredId2Info.put(info1.getId(), info1.copy());
91       }
92     }
93     // invalidate caches
94     myRegisteredInfos = null;
95     myUnregisteredInfos = null;
96     myAllInfos = null;
97     // normalize orders
98     normalizeOrder(getAllInfos(ToolWindowAnchor.TOP));
99     normalizeOrder(getAllInfos(ToolWindowAnchor.LEFT));
100     normalizeOrder(getAllInfos(ToolWindowAnchor.BOTTOM));
101     normalizeOrder(getAllInfos(ToolWindowAnchor.RIGHT));
102   }
103
104   /**
105    * Creates or gets <code>WindowInfo</code> for the specified <code>id</code>. If tool
106    * window is being registered first time the method uses <code>anchor</code>.
107    *
108    * @param id     <code>id</code> of tool window to be registered.
109    * @param anchor the default tool window anchor.
110    * @return
111    */
112   final WindowInfoImpl register(final String id, final ToolWindowAnchor anchor, final boolean splitMode) {
113     WindowInfoImpl info = myUnregisteredId2Info.get(id);
114     if (info != null) { // tool window has been already registered some time
115       myUnregisteredId2Info.remove(id);
116     }
117     else { // tool window is being registered first time
118       info = new WindowInfoImpl(id);
119       info.setAnchor(anchor);
120       info.setSplit(splitMode);
121     }
122     myRegisteredId2Info.put(id, info);
123     // invalidate caches
124     myRegisteredInfos = null;
125     myUnregisteredInfos = null;
126     myAllInfos = null;
127     //
128     return info;
129   }
130
131   final void unregister(final String id) {
132     final WindowInfoImpl info = myRegisteredId2Info.remove(id).copy();
133     myUnregisteredId2Info.put(id, info);
134     // invalidate caches
135     myRegisteredInfos = null;
136     myUnregisteredInfos = null;
137     myAllInfos = null;
138   }
139
140   /**
141    * @return <code>WindowInfo</code> for the window with specified <code>id</code>.
142    *         If <code>onlyRegistered</code> is <code>true</code> then returns not <code>null</code>
143    *         value if and only if window with <code>id</code> is registered one.
144    */
145   final WindowInfoImpl getInfo(final String id, final boolean onlyRegistered) {
146     final WindowInfoImpl info = myRegisteredId2Info.get(id);
147     if (onlyRegistered || info != null) {
148       return info;
149     }
150     else {
151       return myUnregisteredId2Info.get(id);
152     }
153   }
154
155   @Nullable
156   final String getActiveId() {
157     final WindowInfoImpl[] infos = getInfos();
158     for (WindowInfoImpl info : infos) {
159       if (info.isActive()) {
160         return info.getId();
161       }
162     }
163     return null;
164   }
165
166   /**
167    * @return <code>WindowInfo</code>s for all registered tool windows.
168    */
169   final WindowInfoImpl[] getInfos() {
170     if (myRegisteredInfos == null) {
171       myRegisteredInfos = myRegisteredId2Info.values().toArray(new WindowInfoImpl[myRegisteredId2Info.size()]);
172     }
173     return myRegisteredInfos;
174   }
175
176   /**
177    * @return <code>WindowInfos</code>s for all windows that are currently unregistered.
178    */
179   private WindowInfoImpl[] getUnregisteredInfos() {
180     if (myUnregisteredInfos == null) {
181       myUnregisteredInfos = myUnregisteredId2Info.values().toArray(new WindowInfoImpl[myUnregisteredId2Info.size()]);
182     }
183     return myUnregisteredInfos;
184   }
185
186   /**
187    * @return <code>WindowInfo</code>s of all (registered and unregistered) tool windows.
188    */
189   WindowInfoImpl[] getAllInfos() {
190     final WindowInfoImpl[] registeredInfos = getInfos();
191     final WindowInfoImpl[] unregisteredInfos = getUnregisteredInfos();
192     myAllInfos = ArrayUtil.mergeArrays(registeredInfos, unregisteredInfos, WindowInfoImpl.class);
193     return myAllInfos;
194   }
195
196   /**
197    * @return all (registered and not unregistered) <code>WindowInfos</code> for the specified <code>anchor</code>.
198    *         Returned infos are sorted by order.
199    */
200   private WindowInfoImpl[] getAllInfos(final ToolWindowAnchor anchor) {
201     WindowInfoImpl[] infos = getAllInfos();
202     final ArrayList<WindowInfoImpl> list = new ArrayList<WindowInfoImpl>(infos.length);
203     for (WindowInfoImpl info : infos) {
204       if (anchor == info.getAnchor()) {
205         list.add(info);
206       }
207     }
208     infos = list.toArray(new WindowInfoImpl[list.size()]);
209     Arrays.sort(infos, ourWindowInfoComparator);
210     return infos;
211   }
212
213   /**
214    * Normalizes order of windows in the passed array. Note, that array should be
215    * sorted by order (by ascending). Order of first window will be <code>0</code>.
216    */
217   private static void normalizeOrder(final WindowInfoImpl[] infos) {
218     for (int i = 0; i < infos.length; i++) {
219       infos[i].setOrder(i);
220     }
221   }
222
223   final boolean isToolWindowRegistered(final String id) {
224     return myRegisteredId2Info.containsKey(id);
225   }
226
227   /**
228    * @return comparator which compares <code>StripeButtons</code> in the stripe with
229    *         specified <code>anchor</code>.
230    */
231   final Comparator<StripeButton> comparator(final ToolWindowAnchor anchor) {
232     return new MyStripeButtonComparator(anchor);
233   }
234
235   /**
236    * @param anchor anchor of the stripe.
237    * @return maximum ordinal number in the specified stripe. Returns <code>-1</code>
238    *         if there is no any tool window with the specified anchor.
239    */
240   private int getMaxOrder(final ToolWindowAnchor anchor) {
241     int res = -1;
242     final WindowInfoImpl[] infos = getAllInfos();
243     for (final WindowInfoImpl info : infos) {
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 Element layoutElement) {
288     for (Object o : layoutElement.getChildren()) {
289       final Element e = (Element)o;
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 (WindowInfoImpl info : infos) {
304       final Element element = new Element(WindowInfoImpl.TAG);
305       info.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<StripeButton> {
332     private final HashMap<String, WindowInfoImpl> myId2Info;
333
334     public MyStripeButtonComparator(final ToolWindowAnchor anchor) {
335       myId2Info = new HashMap<String, WindowInfoImpl>();
336       final WindowInfoImpl[] infos = getInfos();
337       for (final WindowInfoImpl info : infos) {
338         if (anchor == info.getAnchor()) {
339           myId2Info.put(info.getId(), info.copy());
340         }
341       }
342     }
343
344     public final int compare(final StripeButton obj1, final StripeButton obj2) {
345       final WindowInfoImpl info1 = myId2Info.get(obj1.getWindowInfo().getId());
346       final int order1 = info1 != null ? info1.getOrder() : 0;
347
348       final WindowInfoImpl info2 = myId2Info.get(obj2.getWindowInfo().getId());
349       final int order2 = info2 != null ? info2.getOrder() : 0;
350
351       return order1 - order2;
352     }
353   }
354 }