Wrap call PluginLogo.start/end/BatchMode() to try/finally.
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / plugins / newui / PluginsGroupComponent.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.ide.plugins.newui;
3
4 import com.intellij.ide.plugins.IdeaPluginDescriptor;
5 import com.intellij.ide.plugins.PluginManagerConfigurable;
6 import com.intellij.ui.JBColor;
7 import com.intellij.ui.components.JBPanelWithEmptyText;
8 import com.intellij.ui.components.panels.NonOpaquePanel;
9 import com.intellij.ui.components.panels.OpaquePanel;
10 import com.intellij.ui.scale.JBUIScale;
11 import com.intellij.util.Function;
12 import com.intellij.util.containers.ContainerUtil;
13 import com.intellij.util.ui.JBUI;
14 import org.jetbrains.annotations.NotNull;
15 import org.jetbrains.annotations.Nullable;
16
17 import javax.swing.*;
18 import java.awt.*;
19 import java.awt.event.AdjustmentEvent;
20 import java.awt.event.AdjustmentListener;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.function.Consumer;
24
25 /**
26  * @author Alexander Lobas
27  */
28 public class PluginsGroupComponent extends JBPanelWithEmptyText {
29   private final EventHandler myEventHandler;
30   private final Function<? super IdeaPluginDescriptor, ? extends ListPluginComponent> myFunction;
31   private final List<UIPluginGroup> myGroups = new ArrayList<>();
32
33   public PluginsGroupComponent(@NotNull LayoutManager layout,
34                                @NotNull EventHandler eventHandler,
35                                @NotNull Function<? super IdeaPluginDescriptor, ? extends ListPluginComponent> function) {
36     super(layout);
37     myEventHandler = eventHandler;
38     myFunction = function;
39
40     myEventHandler.connect(this);
41
42     setOpaque(true);
43     setBackground(PluginManagerConfigurable.MAIN_BG_COLOR);
44   }
45
46   @NotNull
47   public List<UIPluginGroup> getGroups() {
48     return myGroups;
49   }
50
51   public void setSelectionListener(@Nullable Consumer<? super PluginsGroupComponent> listener) {
52     myEventHandler.setSelectionListener(listener);
53   }
54
55   @NotNull
56   public List<ListPluginComponent> getSelection() {
57     return myEventHandler.getSelection();
58   }
59
60   public void setSelection(@NotNull ListPluginComponent component) {
61     myEventHandler.setSelection(component);
62   }
63
64   public void setSelection(@NotNull List<? extends ListPluginComponent> components) {
65     myEventHandler.setSelection(components);
66   }
67
68   public void addGroup(@NotNull PluginsGroup group) {
69     addGroup(group, -1);
70   }
71
72   public void addGroup(@NotNull PluginsGroup group, int groupIndex) {
73     addGroup(group, group.descriptors, groupIndex);
74   }
75
76   public void addLazyGroup(@NotNull PluginsGroup group, @NotNull JScrollBar scrollBar, int gapSize, @NotNull Runnable uiCallback) {
77     if (group.descriptors.size() <= gapSize) {
78       addGroup(group);
79     }
80     else {
81       addGroup(group, group.descriptors.subList(0, gapSize), -1);
82       AdjustmentListener listener = new AdjustmentListener() {
83         @Override
84         public void adjustmentValueChanged(AdjustmentEvent e) {
85           if ((scrollBar.getValue() + scrollBar.getVisibleAmount()) >= scrollBar.getMaximum()) {
86             int fromIndex = group.ui.plugins.size();
87             int toIndex = Math.min(fromIndex + gapSize, group.descriptors.size());
88             ListPluginComponent lastComponent = group.ui.plugins.get(fromIndex - 1);
89             int uiIndex = getComponentIndex(lastComponent);
90             int eventIndex = myEventHandler.getCellIndex(lastComponent);
91             try {
92               PluginLogo.startBatchMode();
93               addToGroup(group, group.descriptors.subList(fromIndex, toIndex), uiIndex, eventIndex);
94             }
95             finally {
96               PluginLogo.endBatchMode();
97             }
98
99             if (group.descriptors.size() == group.ui.plugins.size()) {
100               scrollBar.removeAdjustmentListener(this);
101               group.clearCallback = null;
102             }
103
104             uiCallback.run();
105           }
106         }
107       };
108       group.clearCallback = () -> scrollBar.removeAdjustmentListener(listener);
109       scrollBar.addAdjustmentListener(listener);
110     }
111   }
112
113   public static final Color SECTION_HEADER_FOREGROUND =
114     JBColor.namedColor("Plugins.SectionHeader.foreground", new JBColor(0x787878, 0x999999));
115   private static final Color SECTION_HEADER_BACKGROUND =
116     JBColor.namedColor("Plugins.SectionHeader.background", new JBColor(0xF7F7F7, 0x3C3F41));
117
118   private void addGroup(@NotNull PluginsGroup group, @NotNull List<? extends IdeaPluginDescriptor> descriptors, int groupIndex) {
119     UIPluginGroup uiGroup = new UIPluginGroup();
120     group.ui = uiGroup;
121     myGroups.add(groupIndex == -1 ? myGroups.size() : groupIndex, uiGroup);
122
123     OpaquePanel panel = new OpaquePanel(new BorderLayout(), SECTION_HEADER_BACKGROUND);
124     panel.setBorder(JBUI.Borders.empty(4, 10));
125
126     JLabel title = new JLabel(group.title) {
127       @Override
128       public Dimension getPreferredSize() {
129         Dimension size = super.getPreferredSize();
130         Container parent = getParent();
131         Insets insets = parent.getInsets();
132         size.width = Math.min(parent.getWidth() - insets.left - insets.right -
133                               (parent.getComponentCount() == 2 ? parent.getComponent(1).getWidth() + JBUIScale.scale(20) : 0), size.width);
134         return size;
135       }
136
137       @Override
138       public String getToolTipText() {
139         return super.getPreferredSize().width > getWidth() ? super.getToolTipText() : null;
140       }
141     };
142     title.setToolTipText(group.title);
143     title.setForeground(SECTION_HEADER_FOREGROUND);
144     panel.add(title, BorderLayout.WEST);
145     group.titleLabel = title;
146
147     if (group.rightAction != null) {
148       panel.add(group.rightAction, BorderLayout.EAST);
149     }
150     else if (!ContainerUtil.isEmpty(group.rightActions)) {
151       JPanel actions = new NonOpaquePanel(new HorizontalLayout(JBUIScale.scale(5)));
152       panel.add(actions, BorderLayout.EAST);
153
154       for (JComponent action : group.rightActions) {
155         actions.add(action);
156       }
157     }
158
159     int index;
160     int eventIndex;
161
162     if (groupIndex == 0) {
163       add(panel, 0);
164       index = 1;
165       eventIndex = 0;
166     }
167     else if (groupIndex == -1) {
168       add(panel);
169       index = eventIndex = -1;
170     }
171     else {
172       assert groupIndex < myGroups.size();
173       index = getComponentIndex(myGroups.get(groupIndex + 1).panel);
174       assert index != -1;
175       add(panel, index++);
176
177       eventIndex = getEventIndexForGroup(groupIndex + 1);
178     }
179
180     uiGroup.panel = panel;
181
182     addToGroup(group, descriptors, index, eventIndex);
183   }
184
185   private int getEventIndexForGroup(int groupIndex) {
186     for (int i = groupIndex; i >= 0; i--) {
187       List<ListPluginComponent> plugins = myGroups.get(i).plugins;
188       if (!plugins.isEmpty()) {
189         return myEventHandler.getCellIndex(plugins.get(0));
190       }
191     }
192     return -1;
193   }
194
195   private void addToGroup(@NotNull PluginsGroup group,
196                           @NotNull List<? extends IdeaPluginDescriptor> descriptors,
197                           int index,
198                           int eventIndex) {
199     for (IdeaPluginDescriptor descriptor : descriptors) {
200       ListPluginComponent pluginComponent = myFunction.fun(descriptor);
201       group.ui.plugins.add(pluginComponent);
202       add(pluginComponent, index);
203       myEventHandler.addCell(pluginComponent, eventIndex);
204       pluginComponent.setListeners(myEventHandler);
205       if (index != -1) {
206         index++;
207       }
208       if (eventIndex != -1) {
209         eventIndex++;
210       }
211     }
212   }
213
214   public void addToGroup(@NotNull PluginsGroup group, @NotNull IdeaPluginDescriptor descriptor) {
215     int index = group.addWithIndex(descriptor);
216     ListPluginComponent anchor = null;
217     int uiIndex = -1;
218
219     if (index == group.ui.plugins.size()) {
220       int groupIndex = myGroups.indexOf(group.ui);
221       if (groupIndex < myGroups.size() - 1) {
222         UIPluginGroup nextGroup = myGroups.get(groupIndex + 1);
223         anchor = nextGroup.plugins.get(0);
224         uiIndex = getComponentIndex(nextGroup.panel);
225       }
226     }
227     else {
228       anchor = group.ui.plugins.get(index);
229       uiIndex = getComponentIndex(anchor);
230     }
231
232     ListPluginComponent pluginComponent = myFunction.fun(descriptor);
233     group.ui.plugins.add(index, pluginComponent);
234     add(pluginComponent, uiIndex);
235     myEventHandler.addCell(pluginComponent, anchor);
236     pluginComponent.setListeners(myEventHandler);
237   }
238
239   public void removeGroup(@NotNull PluginsGroup group) {
240     myGroups.remove(group.ui);
241     remove(group.ui.panel);
242
243     for (ListPluginComponent plugin : group.ui.plugins) {
244       plugin.close();
245       remove(plugin);
246       myEventHandler.removeCell(plugin);
247     }
248
249     myEventHandler.updateSelection();
250     group.clear();
251   }
252
253   public void removeFromGroup(@NotNull PluginsGroup group, @NotNull IdeaPluginDescriptor descriptor) {
254     int index = ContainerUtil.indexOf(group.ui.plugins, component -> component.myPlugin == descriptor);
255     assert index != -1;
256     ListPluginComponent component = group.ui.plugins.remove(index);
257     component.close();
258     remove(component);
259     myEventHandler.removeCell(component);
260     if (component.getSelection() == EventHandler.SelectionType.SELECTION) {
261       myEventHandler.updateSelection();
262     }
263     group.descriptors.remove(descriptor);
264   }
265
266   private int getComponentIndex(@NotNull Component component) {
267     int components = getComponentCount();
268     for (int i = 0; i < components; i++) {
269       if (getComponent(i) == component) {
270         return i;
271       }
272     }
273     return -1;
274   }
275
276   public void clear() {
277     for (UIPluginGroup group : myGroups) {
278       for (ListPluginComponent plugin : group.plugins) {
279         plugin.close();
280       }
281     }
282
283     myGroups.clear();
284     myEventHandler.clear();
285     removeAll();
286   }
287
288   public void initialSelection() {
289     initialSelection(true);
290   }
291
292   public void initialSelection(boolean scrollAndFocus) {
293     //noinspection SSBasedInspection
294     SwingUtilities.invokeLater(() -> {
295       myEventHandler.initialSelection(scrollAndFocus);
296       if (!myGroups.isEmpty()) {
297         scrollRectToVisible(myGroups.get(0).panel.getBounds());
298       }
299     });
300   }
301 }