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