Kill plugin logos
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / welcomeScreen / DefaultWelcomeScreen.java
1 /*
2  * Copyright 2000-2012 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.welcomeScreen;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.BrowserUtil;
20 import com.intellij.ide.DataManager;
21 import com.intellij.ide.RecentProjectsManagerBase;
22 import com.intellij.ide.ReopenProjectAction;
23 import com.intellij.ide.dnd.FileCopyPasteUtil;
24 import com.intellij.ide.highlighter.ProjectFileType;
25 import com.intellij.ide.plugins.*;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.actionSystem.ex.ActionButtonLook;
28 import com.intellij.openapi.actionSystem.impl.PresentationFactory;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.application.ex.ApplicationInfoEx;
31 import com.intellij.openapi.options.ShowSettingsUtil;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.ui.Messages;
34 import com.intellij.openapi.util.IconLoader;
35 import com.intellij.openapi.util.io.FileUtil;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.openapi.wm.WelcomeScreen;
38 import com.intellij.openapi.wm.impl.IdeRootPane;
39 import com.intellij.ui.*;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.ui.EmptyIcon;
42 import com.intellij.util.ui.UIUtil;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.Nullable;
45
46 import javax.swing.*;
47 import java.awt.*;
48 import java.awt.event.*;
49 import java.awt.font.FontRenderContext;
50 import java.awt.geom.AffineTransform;
51 import java.awt.geom.Rectangle2D;
52 import java.io.File;
53 import java.util.*;
54 import java.util.List;
55
56 import static java.awt.GridBagConstraints.*;
57
58 /**
59  * @author pti
60  */
61 public class DefaultWelcomeScreen implements WelcomeScreen {
62   private static final Insets ACTION_GROUP_CAPTION_INSETS = new Insets(20, 30, 5, 0);
63   private static final Insets PLUGINS_CAPTION_INSETS = new Insets(20, 25, 0, 0);
64   private static final Insets ACTION_ICON_INSETS = new Insets(5, 20, 15, 0);
65   private static final Insets ACTION_NAME_INSETS = new Insets(15, 5, 0, 0);
66   private static final Insets ACTION_DESCRIPTION_INSETS = new Insets(7, 5, 0, 30);
67   private static final Insets NO_INSETS = new Insets(0, 0, 0, 0);
68
69   private static final int MAIN_GROUP = 0;
70   private static final int PLUGIN_DSC_MAX_WIDTH = 260;
71   private static final int PLUGIN_DSC_MAX_ROWS = 2;
72   private static final int PLUGIN_NAME_MAX_WIDTH = 180;
73   private static final int PLUGIN_NAME_MAX_ROWS = 2;
74   private static final int MAX_TOOLTIP_WIDTH = 400;
75   private static final int ACTION_BUTTON_PADDING = 5;
76
77   private static final Dimension ACTION_BUTTON_SIZE = new Dimension(66, 66);
78   private static final Dimension PLUGIN_LOGO_SIZE = new Dimension(16, 16);
79
80   @NonNls private static final String CAPTION_FONT_NAME = "Tahoma";
81   private static final Font TEXT_FONT = new Font(CAPTION_FONT_NAME, Font.PLAIN, 11);
82   private static final Font LINK_FONT = new Font(CAPTION_FONT_NAME, Font.BOLD, 12);
83   private static final Font GROUP_CAPTION_FONT = new Font(CAPTION_FONT_NAME, Font.BOLD, 18);
84
85   private static final Color WELCOME_PANEL_BACKGROUND = Color.WHITE;
86   private static final Color MAIN_PANEL_BACKGROUND = WELCOME_PANEL_BACKGROUND;
87   private static final Color PLUGINS_PANEL_BACKGROUND = Gray._248;
88   private static final Color PLUGINS_PANEL_BORDER = Gray._234;
89   private static final Color CAPTION_COLOR = new Color(47, 67, 96);
90   public static final SimpleTextAttributes CAPTION_BOLD_UNDERLINE_ATTRIBUTES =
91     new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE | SimpleTextAttributes.STYLE_BOLD, CAPTION_COLOR);
92   public static final SimpleTextAttributes CAPTION_UNDERLINE_ATTRIBUTES =
93     new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE, CAPTION_COLOR);
94   private static final Color DISABLED_CAPTION_COLOR = UIUtil.getInactiveTextColor();
95   private static final Color ACTION_BUTTON_COLOR = WELCOME_PANEL_BACKGROUND;
96   private static final Color BUTTON_POPPED_COLOR = Gray._241;
97   private static final Color BUTTON_PUSHED_COLOR = Gray._228;
98
99   @NonNls private static final String HTML_PREFIX = "<html>";
100   @NonNls private static final String HTML_SUFFIX = "</html>";
101   @NonNls private static final String ___HTML_SUFFIX = "...</html>";
102   @NonNls private static final String ESC_NEW_LINE = "\\n";
103
104   private final JPanel myWelcomePanel;
105   private final JPanel myMainPanel;
106   private final JPanel myPluginsPanel;
107
108   private Icon myCaptionImage;
109   private Icon myDeveloperSlogan;
110   private Color myCaptionBackground = new Color(23, 52, 150);
111
112   private MyActionButton myPressedButton = null;
113   private int mySelectedRow = -1;
114   private int mySelectedColumn = -1;
115   private int mySelectedGroup = -1;
116   private int myPluginsIdx = -1;
117   
118   private JComponent myRecentProjectsPanel;
119
120   public JPanel getWelcomePanel() {
121     return myWelcomePanel;
122   }
123
124   public DefaultWelcomeScreen(IdeRootPane rootPane) {
125     initApplicationSpecificImages();
126
127     myWelcomePanel = new JPanel(new GridBagLayout());
128
129     // Create caption pane
130     JPanel topPanel = createCaptionPane();
131
132     // Create Main Panel for Quick Start and Documentation
133     myMainPanel = new WelcomeScrollablePanel(new GridLayout(1, 2));
134     myMainPanel.setBackground(MAIN_PANEL_BACKGROUND);
135     setUpMainPanel(rootPane);
136     JScrollPane mainScrollPane = scrollPane(myMainPanel, null);
137
138     // Create Plugins Panel
139     myPluginsPanel = new WelcomeScrollablePanel(new GridBagLayout());
140     myPluginsPanel.setBackground(PLUGINS_PANEL_BACKGROUND);
141     setUpPluginsPanel();
142     JScrollPane pluginsScrollPane = scrollPane(myPluginsPanel, PLUGINS_PANEL_BORDER);
143
144     // Create Welcome panel
145     GridBagConstraints gBC;
146     myWelcomePanel.setBackground(WELCOME_PANEL_BACKGROUND);
147     gBC = new GridBagConstraints(0, 0, 2, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(7, 7, 7, 7), 0, 0);
148     myWelcomePanel.add(topPanel, gBC);
149     gBC = new GridBagConstraints(0, 1, 1, 1, 0.7, 1, NORTHWEST, BOTH, new Insets(0, 7, 7, 7), 0, 0);
150     myWelcomePanel.add(mainScrollPane, gBC);
151     gBC = new GridBagConstraints(1, 1, 1, 1, 0.3, 1, NORTHWEST, BOTH, new Insets(0, 0, 7, 7), 0, 0);
152     myWelcomePanel.add(pluginsScrollPane, gBC);
153   }
154
155   @Override
156   public void dispose() {
157   }
158
159   private void initApplicationSpecificImages() {
160     if (myCaptionImage == null) {
161       ApplicationInfoEx applicationInfoEx = ApplicationInfoEx.getInstanceEx();
162       myCaptionImage = IconLoader.getIcon(applicationInfoEx.getWelcomeScreenCaptionUrl());
163       myDeveloperSlogan = IconLoader.getIcon(applicationInfoEx.getWelcomeScreenDeveloperSloganUrl());
164
165       myCaptionBackground = UIUtil.getColorAt(myCaptionImage, myCaptionImage.getIconWidth() - 1, myCaptionImage.getIconHeight() - 2);
166     }
167   }
168
169   private JPanel createCaptionPane() {
170     JPanel topPanel = new JPanel(new GridBagLayout()) {
171       public void paint(Graphics g) {
172         Icon welcome = myCaptionImage;
173         welcome.paintIcon(null, g, 0, 0);
174         g.setColor(myCaptionBackground);
175         g.fillRect(welcome.getIconWidth(), 0, getWidth() - welcome.getIconWidth(), welcome.getIconHeight());
176         super.paint(g);
177       }
178     };
179     topPanel.setOpaque(false);
180     topPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, myCaptionBackground));
181
182     JPanel transparentTopPanel = new JPanel();
183     transparentTopPanel.setOpaque(false);
184
185     topPanel.add(transparentTopPanel, new GridBagConstraints(0, 0, 1, 1, 1, 0, CENTER, HORIZONTAL, NO_INSETS, 0, 0));
186     topPanel.add(new JLabel(myDeveloperSlogan), new GridBagConstraints(1, 0, 1, 1, 0, 0, SOUTHWEST, NONE, new Insets(0, 0, 0, 10), 0, 0));
187
188     return topPanel;
189   }
190
191   private void setUpMainPanel(IdeRootPane rootPane) {
192     final ActionManager actionManager = ActionManager.getInstance();
193
194     // Create QuickStarts group of actions
195     ActionGroupDescriptor quickStarts = new ActionGroupDescriptor(UIBundle.message("welcome.screen.quick.start.action.group.name"), 0);
196     // Append plug-in actions to the end of the QuickStart list
197     quickStarts.appendActionsFromGroup((DefaultActionGroup)actionManager.getAction(IdeActions.GROUP_WELCOME_SCREEN_QUICKSTART));
198     final JPanel quickStartPanel = quickStarts.getPanel();
199
200     AnAction[] recentProjectsActions = RecentProjectsManagerBase.getInstance().getRecentProjectsActions(false);
201     if (recentProjectsActions.length > 0) {
202       myRecentProjectsPanel = new JPanel(new GridBagLayout());
203       setUpRecentProjectsPanel(rootPane, recentProjectsActions);
204       quickStartPanel.add(myRecentProjectsPanel, new GridBagConstraints(0, quickStarts.getIdx() + 2, 2, 1, 1, 1, NORTHWEST, HORIZONTAL,
205                                                                       new Insets(14, 30, 5, 0), 0, 0));
206     }
207
208     // Add empty panel at the end of the QuickStarts panel
209     JPanel emptyPanel_2 = new JPanel();
210     emptyPanel_2.setBackground(MAIN_PANEL_BACKGROUND);
211     quickStartPanel.add(emptyPanel_2, new GridBagConstraints(0, quickStarts.getIdx() + 3, 2, 1, 1, 1, NORTHWEST, BOTH, NO_INSETS, 0, 0));
212
213     // Create Documentation group of actions
214     ActionGroupDescriptor docsGroup = new ActionGroupDescriptor(UIBundle.message("welcome.screen.documentation.action.group.name"), 1);
215     // Append plug-in actions to the end of the QuickStart list
216     docsGroup.appendActionsFromGroup((DefaultActionGroup)actionManager.getAction(IdeActions.GROUP_WELCOME_SCREEN_DOC));
217     final JPanel docsPanel = docsGroup.getPanel();
218     // Add empty panel at the end of the Documentation list
219     JPanel emptyPanel_3 = new JPanel();
220     emptyPanel_3.setBackground(MAIN_PANEL_BACKGROUND);
221     docsPanel.add(emptyPanel_3, new GridBagConstraints(0, docsGroup.getIdx() + 2, 2, 1, 1, 1, NORTHWEST, BOTH, NO_INSETS, 0, 0));
222
223     // Add QuickStarts and Docs to main panel
224     myMainPanel.add(quickStartPanel);
225     myMainPanel.add(docsPanel);
226
227     // Accept dropping of project file/dir from file manager and open that project
228     myWelcomePanel.setTransferHandler(new OpenProjectTransferHandler(myWelcomePanel));
229   }
230
231   private void setUpRecentProjectsPanel(final IdeRootPane rootPane, final AnAction[] recentProjectsActions) {
232     myRecentProjectsPanel.setBackground(MAIN_PANEL_BACKGROUND);
233
234     JLabel caption = new JLabel("Recent Projects");
235     caption.setFont(GROUP_CAPTION_FONT);
236     caption.setForeground(CAPTION_COLOR);
237     myRecentProjectsPanel.add(caption, new GridBagConstraints(0, 0, 2, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 20, 0), 0, 0));
238
239     JLabel iconLabel = new JLabel();
240     iconLabel.setIcon(AllIcons.General.ReopenRecentProject);
241     myRecentProjectsPanel.add(iconLabel, new GridBagConstraints(0, 1, 1, 5, 0, 0, NORTHWEST, NONE, new Insets(5, 0, 15, 20), 0, 0));
242
243     int row = 1;
244     for (final AnAction action : recentProjectsActions) {
245       if (!(action instanceof ReopenProjectAction)) continue;
246
247       final SimpleColoredComponent actionLabel = new SimpleColoredComponent() {
248         @Override
249         public Dimension getPreferredSize() {
250           boolean hasIcon = getIcon() != null;
251           Dimension preferredSize = super.getPreferredSize();
252           return new Dimension(preferredSize.width + (hasIcon ? 0 : AllIcons.Actions.CloseNew.getIconWidth() + myIconTextGap), preferredSize.height);
253         }
254
255         @Override
256         public Dimension getMinimumSize() {
257           return getPreferredSize();
258         }
259       };
260
261       actionLabel.append(String.valueOf(row), CAPTION_UNDERLINE_ATTRIBUTES);
262       actionLabel.append(". ", new SimpleTextAttributes(0, CAPTION_COLOR));
263
264       actionLabel.append(((ReopenProjectAction)action).getProjectName(), CAPTION_BOLD_UNDERLINE_ATTRIBUTES);
265       actionLabel.setIconOnTheRight(true);
266
267       String path = ((ReopenProjectAction)action).getProjectPath();
268       File pathFile = new File(path);
269       if (pathFile.isDirectory() && pathFile.getName().equals(((ReopenProjectAction)action).getProjectName())) {
270         path = pathFile.getParent();
271       }
272       path = FileUtil.getLocationRelativeToUserHome(path);
273       actionLabel.append("   " + path, new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, Color.GRAY));
274
275       actionLabel.setFont(new Font(CAPTION_FONT_NAME, Font.PLAIN, 12));
276       actionLabel.setForeground(CAPTION_COLOR);
277       actionLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
278
279       new ClickListener() {
280         @Override
281         public boolean onClick(MouseEvent e, int clickCount) {
282           if (e.getButton() == MouseEvent.BUTTON1) {
283             DataContext dataContext = DataManager.getInstance().getDataContext(myWelcomePanel);
284             int fragment = actionLabel.findFragmentAt(e.getX());
285             if (fragment == SimpleColoredComponent.FRAGMENT_ICON) {
286               final int rc = Messages.showOkCancelDialog(PlatformDataKeys.PROJECT.getData(dataContext),
287                                                          "Remove '" + action.getTemplatePresentation().getText() +
288                                                          "' from recent projects list?",
289                                                          "Remove Recent Project",
290                                                          Messages.getQuestionIcon());
291               if (rc == 0) {
292                 final RecentProjectsManagerBase manager = RecentProjectsManagerBase.getInstance();
293                 assert action instanceof ReopenProjectAction : action;
294                 manager.removePath(((ReopenProjectAction)action).getProjectPath());
295                 final AnAction[] actions = manager.getRecentProjectsActions(false);
296                 if (actions.length == 0) {
297                   hideRecentProjectsPanel();
298                 }
299                 else {
300                   for (int i = myRecentProjectsPanel.getComponentCount() - 1; i >= 0; i--) {
301                     myRecentProjectsPanel.remove(i);
302                   }
303                   setUpRecentProjectsPanel(rootPane, actions);
304                   myRecentProjectsPanel.revalidate();
305                 }
306               }
307             }
308             else if (fragment != -1) {
309               AnActionEvent event = new AnActionEvent(e, dataContext, "", action.getTemplatePresentation(), ActionManager.getInstance(), 0);
310               action.actionPerformed(event);
311             }
312           }
313           return true;
314         }
315       }.installOn(actionLabel);
316
317       actionLabel.addMouseListener(new MouseAdapter() {
318         @Override
319         public void mouseEntered(MouseEvent e) {
320           actionLabel.setIcon(AllIcons.Actions.CloseNew);
321         }
322
323         @Override
324         public void mouseExited(MouseEvent e) {
325           actionLabel.setIcon(null);
326         }
327       });
328       
329       action.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_0 + row, InputEvent.ALT_DOWN_MASK)),
330                                        rootPane, this);
331       myRecentProjectsPanel.add(actionLabel, new GridBagConstraints(1, row++, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(5, 0, 5, 0), 0, 0));
332       if (row == 9) break;
333     }
334   }
335
336   public void hideRecentProjectsPanel() {
337     myRecentProjectsPanel.setVisible(false);
338   }
339
340   private void setUpPluginsPanel() {
341     GridBagConstraints gBC;
342
343     JLabel pluginsCaption = new JLabel(UIBundle.message("welcome.screen.plugins.panel.plugins.label"));
344     pluginsCaption.setFont(GROUP_CAPTION_FONT);
345     pluginsCaption.setForeground(CAPTION_COLOR);
346
347     JLabel openPluginManager = new JLabel(UIBundle.message("welcome.screen.plugins.panel.manager.link"));
348     new ClickListener() {
349       @Override
350       public boolean onClick(MouseEvent e, int clickCount) {
351         final PluginManagerConfigurable configurable = new PluginManagerConfigurable(PluginManagerUISettings.getInstance());
352         ShowSettingsUtil.getInstance().editConfigurable(myPluginsPanel, configurable);
353         return true;
354       }
355     }.installOn(openPluginManager);
356
357     openPluginManager.setForeground(CAPTION_COLOR);
358     openPluginManager.setFont(LINK_FONT);
359     openPluginManager.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
360
361     JLabel installedPluginsCaption = new JLabel(UIBundle.message("welcome.screen.plugins.panel.my.plugins.label"));
362     installedPluginsCaption.setFont(LINK_FONT);
363     installedPluginsCaption.setForeground(CAPTION_COLOR);
364
365     JPanel installedPluginsPanel = new JPanel(new GridBagLayout());
366     installedPluginsPanel.setBackground(PLUGINS_PANEL_BACKGROUND);
367
368     JLabel bundledPluginsCaption = new JLabel(UIBundle.message("welcome.screen.plugins.panel.bundled.plugins.label"));
369     bundledPluginsCaption.setFont(LINK_FONT);
370     bundledPluginsCaption.setForeground(CAPTION_COLOR);
371
372     JPanel bundledPluginsPanel = new JPanel(new GridBagLayout());
373     bundledPluginsPanel.setBackground(PLUGINS_PANEL_BACKGROUND);
374
375     createListOfPlugins(installedPluginsPanel, bundledPluginsPanel);
376
377     JPanel topPluginsPanel = new JPanel(new GridBagLayout());
378     topPluginsPanel.setBackground(PLUGINS_PANEL_BACKGROUND);
379
380     gBC = new GridBagConstraints(0, 0, 1, 1, 0, 0, NORTHWEST, NONE, PLUGINS_CAPTION_INSETS, 0, 0);
381     topPluginsPanel.add(pluginsCaption, gBC);
382
383     JLabel emptyLabel_1 = new JLabel();
384     emptyLabel_1.setBackground(PLUGINS_PANEL_BACKGROUND);
385     gBC = new GridBagConstraints(1, 0, 1, 1, 1, 0, NORTHWEST, NONE, NO_INSETS, 0, 0);
386     topPluginsPanel.add(emptyLabel_1, gBC);
387
388     gBC = new GridBagConstraints(2, 0, 1, 1, 0, 0, NORTHWEST, NONE, new Insets(22, 0, 0, 10), 0, 0);
389     topPluginsPanel.add(openPluginManager, gBC);
390
391     gBC = new GridBagConstraints(0, 0, 1, 1, 0, 0, NORTHWEST, HORIZONTAL, NO_INSETS, 0, 0);
392     myPluginsPanel.add(topPluginsPanel, gBC);
393
394     gBC = new GridBagConstraints(0, 1, 1, 1, 0, 0, NORTHWEST, NONE, new Insets(20, 25, 0, 0), 0, 0);
395     myPluginsPanel.add(installedPluginsCaption, gBC);
396     gBC = new GridBagConstraints(0, 2, 1, 1, 1, 0, NORTHWEST, NONE, new Insets(0, 5, 0, 0), 0, 0);
397     myPluginsPanel.add(installedPluginsPanel, gBC);
398
399     gBC = new GridBagConstraints(0, 3, 1, 1, 0, 0, NORTHWEST, NONE, new Insets(20, 25, 0, 0), 0, 0);
400     myPluginsPanel.add(bundledPluginsCaption, gBC);
401     gBC = new GridBagConstraints(0, 4, 1, 1, 1, 0, NORTHWEST, NONE, new Insets(0, 5, 0, 0), 0, 0);
402     myPluginsPanel.add(bundledPluginsPanel, gBC);
403
404     JPanel emptyPanel_1 = new JPanel();
405     emptyPanel_1.setBackground(PLUGINS_PANEL_BACKGROUND);
406     gBC = new GridBagConstraints(0, 5, 1, 1, 1, 1, NORTHWEST, BOTH, NO_INSETS, 0, 0);
407     myPluginsPanel.add(emptyPanel_1, gBC);
408   }
409
410   private void createListOfPlugins(final JPanel installedPluginsPanel, final JPanel bundledPluginsPanel) {
411     //Create the list of installed plugins
412     List<IdeaPluginDescriptor> installedPlugins =
413       new ArrayList<IdeaPluginDescriptor>(Arrays.asList(PluginManager.getPlugins()));
414
415     if (installedPlugins.size() == 0) {
416       addListItemToPlugins(installedPluginsPanel,
417                            italic(UIBundle.message("welcome.screen.plugins.panel.no.plugins.currently.installed.message.text")));
418       addListItemToPlugins(bundledPluginsPanel,
419                            italic(UIBundle.message("welcome.screen.plugins.panel.all.bundled.plugins.were.uninstalled.message.text")));
420     }
421     else {
422       final Comparator<IdeaPluginDescriptor> pluginsComparator = new Comparator<IdeaPluginDescriptor>() {
423         public int compare(final IdeaPluginDescriptor o1, final IdeaPluginDescriptor o2) {
424           final boolean e1 = o1.isEnabled();
425           final boolean e2 = o2.isEnabled();
426           if (e1 && !e2) return -1;
427           if (!e1 && e2) return 1;
428           return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
429         }
430       };
431       Collections.sort(installedPlugins, pluginsComparator);
432
433       int embeddedPlugins = 0;
434       int installedPluginsCount = 0;
435
436       for (IdeaPluginDescriptor plugin : installedPlugins) {
437         if (plugin.getName().equals("IDEA CORE") || ((IdeaPluginDescriptorImpl)plugin).isUseCoreClassLoader()) {
438           // this is not really a plugin, so it shouldn't be displayed
439           continue;
440         }
441         if (plugin.isBundled()) {
442           embeddedPlugins++;
443           addListItemToPlugins(bundledPluginsPanel, (IdeaPluginDescriptorImpl)plugin);
444         }
445         else {
446           installedPluginsCount++;
447           addListItemToPlugins(installedPluginsPanel, (IdeaPluginDescriptorImpl)plugin);
448         }
449       }
450       if (embeddedPlugins == 0) {
451         addListItemToPlugins(bundledPluginsPanel,
452                              italic(UIBundle.message("welcome.screen.plugins.panel.all.bundled.plugins.were.uninstalled.message.text")));
453       }
454       if (installedPluginsCount == 0) {
455         addListItemToPlugins(installedPluginsPanel,
456                              italic(UIBundle.message("welcome.screen.plugins.panel.no.plugins.currently.installed.message.text")));
457       }
458     }
459   }
460
461   @SuppressWarnings({"HardCodedStringLiteral"})
462   private static String italic(final String message) {
463     return "<i>" + message + "</i>";
464   }
465
466   private void addListItemToPlugins(final JPanel bundledPluginsPanel, final String title) {
467     addListItemToPlugins(bundledPluginsPanel, title, null, null, true, false);
468   }
469
470   private void addListItemToPlugins(final JPanel bundledPluginsPanel, final IdeaPluginDescriptorImpl plugin) {
471     addListItemToPlugins(bundledPluginsPanel, plugin.getName(), plugin.getDescription(),
472                          plugin.getUrl(), plugin.isEnabled(), PluginManager.isIncompatible(plugin));
473   }
474
475   public void addListItemToPlugins(final JPanel panel,
476                                    String name,
477                                    @Nullable String description,
478                                    @Nullable final String url,
479                                    final boolean enabled,
480                                    final boolean incompatible) {
481     if (StringUtil.isEmptyOrSpaces(name)) {
482       return;
483     }
484     else {
485       name = name.trim();
486     }
487
488     final int y = myPluginsIdx += 2;
489
490     JLabel imageLabel = new JLabel(); // There used to be a logo, which is removed and I'm (max) lazy enough fixing gridbag
491     GridBagConstraints gBC = new GridBagConstraints(0, y, 1, 1, 0, 0, NORTHWEST, NONE, new Insets(15, 20, 0, 0), 0, 0);
492     panel.add(imageLabel, gBC);
493
494     name = name + " " + (incompatible ? UIBundle.message("welcome.screen.incompatible.plugins.description")
495                                       : (enabled ? "" : UIBundle.message("welcome.screen.disabled.plugins.description")));
496     String shortenedName = adjustStringBreaksByWidth(name, LINK_FONT, false, PLUGIN_NAME_MAX_WIDTH, PLUGIN_NAME_MAX_ROWS);
497     JLabel logoName = new JLabel(shortenedName);
498     logoName.setFont(LINK_FONT);
499     logoName.setForeground(enabled ? CAPTION_COLOR : DISABLED_CAPTION_COLOR);
500     if (shortenedName.endsWith(___HTML_SUFFIX)) {
501       logoName.setToolTipText(adjustStringBreaksByWidth(name, UIUtil.getToolTipFont(), false, MAX_TOOLTIP_WIDTH, 0));
502     }
503
504     JPanel logoPanel = new JPanel(new BorderLayout());
505     logoPanel.setBackground(PLUGINS_PANEL_BACKGROUND);
506     logoPanel.add(logoName, BorderLayout.WEST);
507     gBC = new GridBagConstraints(1, y, 1, 1, 0, 0, NORTHWEST, NONE, new Insets(15, 7, 0, 0), 0, 0);
508     panel.add(logoPanel, gBC);
509
510     if (!StringUtil.isEmptyOrSpaces(url)) {
511       JLabel learnMore = new JLabel(UIBundle.message("welcome.screen.plugins.panel.learn.more.link"));
512       learnMore.setFont(LINK_FONT);
513       learnMore.setForeground(enabled ? CAPTION_COLOR : DISABLED_CAPTION_COLOR);
514       learnMore.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
515       learnMore.setToolTipText(UIBundle.message("welcome.screen.plugins.panel.learn.more.tooltip.text"));
516       new ClickListener() {
517         @Override
518         public boolean onClick(MouseEvent e, int clickCount) {
519           try {
520             BrowserUtil.launchBrowser(url);
521           }
522           catch (IllegalThreadStateException ignore) {
523           }
524           return true;
525         }
526       }.installOn(learnMore);
527
528       logoPanel.add(new JLabel(" "), BorderLayout.CENTER);
529       logoPanel.add(learnMore, BorderLayout.EAST);
530     }
531
532     if (!StringUtil.isEmpty(description)) {
533       //noinspection ConstantConditions
534       description = description.trim();
535       if (description.startsWith(HTML_PREFIX)) {
536         description = description.replaceAll(HTML_PREFIX, "");
537         if (description.endsWith(HTML_SUFFIX)) {
538           description = description.replaceAll(HTML_SUFFIX, "");
539         }
540       }
541       description = description.replaceAll(ESC_NEW_LINE, "");
542       String shortenedDcs = adjustStringBreaksByWidth(description, TEXT_FONT, false, PLUGIN_DSC_MAX_WIDTH, PLUGIN_DSC_MAX_ROWS);
543       JLabel pluginDescription = new JLabel(shortenedDcs);
544       pluginDescription.setFont(TEXT_FONT);
545       if (shortenedDcs.endsWith(___HTML_SUFFIX)) {
546         pluginDescription.setToolTipText(adjustStringBreaksByWidth(description, UIUtil.getToolTipFont(), false, MAX_TOOLTIP_WIDTH, 0));
547       }
548
549       gBC = new GridBagConstraints(1, y + 1, 1, 1, 0, 0, NORTHWEST, HORIZONTAL, new Insets(5, 7, 0, 0), 5, 0);
550       panel.add(pluginDescription, gBC);
551     }
552   }
553
554   /**
555    * This method checks the width of the given string with given font applied, breaks the string into the specified number of lines if necessary,
556    * and/or cuts it, so that the string does not exceed the given width (with ellipsis concatenated at the end if needed).<br>
557    * It also removes all of the formatting HTML tags, except <b>&lt;br&gt;</b> and <b>&lt;li&gt;</b> (they are used for correct line breaks).
558    * Returns the resulting or original string surrounded by <b>&lt;html&gt;</b> tags.
559    *
560    * @param string        not <code>null</code> {@link String String} value, otherwise the "Not specified." string is returned.
561    * @param font          not <code>null</code> {@link Font Font} object.
562    * @param isAntiAliased <code>boolean</code> value to denote whether the font is anti-aliased or not.
563    * @param maxWidth      <code>int</code> value specifying maximum width of the resulting string in pixels.
564    * @param maxRows       <code>int</code> value specifying the number of rows. If the value is positive, the string is modified to not exceed
565    *                      the specified number, and method adds an ellipsis instead of the exceeding part. If the value is zero or negative,
566    *                      the entire string is broken into lines until its end.
567    * @return the resulting or original string ({@link String String}) surrounded by <b>&lt;html&gt;</b> tags.
568    */
569   @SuppressWarnings({"HardCodedStringLiteral"})
570   private static String adjustStringBreaksByWidth(String string,
571                                                   final Font font,
572                                                   final boolean isAntiAliased,
573                                                   final int maxWidth,
574                                                   final int maxRows) {
575     string = string.trim();
576     if (StringUtil.isEmpty(string)) {
577       return "<html>" + UIBundle.message("welcome.screen.text.not.specified.message") + "</html>";
578     }
579
580     string = string.replaceAll("<li>", " <>&gt; ");
581     string = string.replaceAll("<br>", " <>");
582     string = string.replaceAll("(<[^>]+?>)", " ");
583     string = string.replaceAll("[\\s]{2,}", " ");
584     Rectangle2D r = font.getStringBounds(string, new FontRenderContext(new AffineTransform(), isAntiAliased, false));
585
586     if (r.getWidth() > maxWidth) {
587
588       StringBuilder prefix = new StringBuilder();
589       String suffix = string;
590       int maxIdxPerLine = (int)(maxWidth / r.getWidth() * string.length());
591       int lengthLeft = string.length();
592       int rows = maxRows;
593       if (rows <= 0) {
594         rows = string.length() / maxIdxPerLine + 1;
595       }
596
597       while (lengthLeft > maxIdxPerLine && rows > 1) {
598         int i;
599         for (i = maxIdxPerLine; i > 0; i--) {
600           if (suffix.charAt(i) == ' ') {
601             prefix.append(suffix.substring(0, i)).append("<br>");
602             suffix = suffix.substring(i + 1, suffix.length());
603             lengthLeft = suffix.length();
604             if (maxRows > 0) {
605               rows--;
606             }
607             else {
608               rows = lengthLeft / maxIdxPerLine + 1;
609             }
610             break;
611           }
612         }
613         if (i == 0) {
614           if (rows > 1 && maxRows <= 0) {
615             prefix.append(suffix.substring(0, maxIdxPerLine)).append("<br>");
616             suffix = suffix.substring(maxIdxPerLine, suffix.length());
617             lengthLeft = suffix.length();
618             rows--;
619           }
620           else {
621             break;
622           }
623         }
624       }
625       if (suffix.length() > maxIdxPerLine) {
626         suffix = suffix.substring(0, maxIdxPerLine - 3);
627         for (int i = suffix.length() - 1; i > 0; i--) {
628           if (suffix.charAt(i) == ' ') {
629             if ("...".equals(suffix.substring(i - 3, i))) {
630               suffix = suffix.substring(0, i - 1);
631               break;
632             }
633             else if (suffix.charAt(i - 1) == '>') {
634               //noinspection AssignmentToForLoopParameter
635               i--;
636             }
637             else if (suffix.charAt(i - 1) == '.') {
638               suffix = suffix.substring(0, i) + "..";
639               break;
640             }
641             else {
642               suffix = suffix.substring(0, i) + "...";
643               break;
644             }
645           }
646         }
647       }
648       string = prefix + suffix;
649     }
650     string = string.replaceAll(" <>", "<br>");
651     return HTML_PREFIX + string + HTML_SUFFIX;
652   }
653
654   @SuppressWarnings({"HardCodedStringLiteral"})
655   private static String underlineHtmlText(final String commandLink) {
656     return "<html><nobr><u>" + commandLink + "</u></nobr></html>";
657   }
658
659   private class ActionGroupDescriptor {
660     private int myIdx = -1;
661     private int myCount = 0;
662     private final JPanel myPanel;
663     private final int myColumnIdx;
664
665     public ActionGroupDescriptor(final String caption, final int columnIndex) {
666       JPanel panel = new JPanel(new GridBagLayout()) {
667         public Dimension getPreferredSize() {
668           return getMinimumSize();
669         }
670       };
671       panel.setBackground(MAIN_PANEL_BACKGROUND);
672
673       JLabel actionGroupCaption = new JLabel(caption);
674       actionGroupCaption.setFont(GROUP_CAPTION_FONT);
675       actionGroupCaption.setForeground(CAPTION_COLOR);
676
677       GridBagConstraints gBC = new GridBagConstraints(0, 0, 2, 1, 0, 0, NORTHWEST, NONE, ACTION_GROUP_CAPTION_INSETS, 0, 0);
678       panel.add(actionGroupCaption, gBC);
679       myPanel = panel;
680       myColumnIdx = columnIndex;
681     }
682
683     public void addButton(final MyActionButton button, String commandLink, String description) {
684       GridBagConstraints gBC;
685
686       final int y = myIdx += 2;
687       gBC = new GridBagConstraints(0, y, 1, 2, 0, 0, NORTHWEST, NONE, ACTION_ICON_INSETS, ACTION_BUTTON_PADDING, ACTION_BUTTON_PADDING);
688       myPanel.add(button, gBC);
689       button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
690       button.setupWithinPanel(myMainPanel, MAIN_GROUP, myCount, myColumnIdx);
691       myCount++;
692
693       JLabel name = new JLabel(underlineHtmlText(commandLink));
694       new ClickListener() {
695         @Override
696         public boolean onClick(MouseEvent event, int clickCount) {
697           button.onPress(event);
698           return true;
699         }
700       }.installOn(name);
701
702       name.setForeground(CAPTION_COLOR);
703       name.setFont(LINK_FONT);
704       name.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
705       gBC = new GridBagConstraints(1, y, 1, 1, 0, 0, SOUTHWEST, NONE, ACTION_NAME_INSETS, 5, 0);
706       myPanel.add(name, gBC);
707
708       description = wrapWithHtml(description);
709       JLabel shortDescription = new JLabel(description);
710       shortDescription.setFont(TEXT_FONT);
711       gBC = new GridBagConstraints(1, y + 1, 1, 1, 0, 0, NORTHWEST, HORIZONTAL, ACTION_DESCRIPTION_INSETS, 5, 0);
712       myPanel.add(shortDescription, gBC);
713     }
714
715     private String wrapWithHtml(final String description) {
716       return HTML_PREFIX + description + HTML_SUFFIX;
717     }
718
719     private void appendActionsFromGroup(final ActionGroup group) {
720       final AnAction[] actions = group.getChildren(null);
721       PresentationFactory factory = new PresentationFactory();
722       for (final AnAction action : actions) {
723         if (action instanceof ActionGroup) {
724           final ActionGroup childGroup = (ActionGroup)action;
725           appendActionsFromGroup(childGroup);
726         }
727         else {
728           Presentation presentation = factory.getPresentation(action);
729           action.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(myMainPanel),
730                                           ActionPlaces.WELCOME_SCREEN, presentation, ActionManager.getInstance(), 0));
731           if (presentation.isVisible()) {
732             appendButtonForAction(action);
733           }
734         }
735       }
736     }
737
738     public void appendButtonForAction(final AnAction action) {
739       final Presentation presentation = action.getTemplatePresentation();
740       final Icon icon = presentation.getIcon();
741       final String text = presentation.getText();
742       MyActionButton button = new ButtonWithExtension(icon, "") {
743         protected void onPress(InputEvent e, MyActionButton button) {
744           final ActionManager actionManager = ActionManager.getInstance();
745           AnActionEvent evt = new AnActionEvent(
746             null,
747             DataManager.getInstance().getDataContext(e.getComponent()),
748             ActionPlaces.WELCOME_SCREEN,
749             action.getTemplatePresentation(),
750             actionManager,
751             0
752           );
753           action.beforeActionPerformedUpdate(evt);
754           if (evt.getPresentation().isEnabled()) {
755             action.actionPerformed(evt);
756           }
757         }
758       };
759
760       addButton(button, text, presentation.getDescription());
761     }
762
763     public JPanel getPanel() {
764       return myPanel;
765     }
766
767     public int getIdx() {
768       return myIdx;
769     }
770   }
771
772   private abstract class MyActionButton extends JComponent implements ActionButtonComponent {
773     private int myGroupIdx;
774     private int myRowIdx;
775     private int myColumnIdx;
776     private final String myDisplayName;
777     private final Icon myIcon;
778
779     private MyActionButton(Icon icon, String displayName) {
780       myDisplayName = displayName;
781       myIcon = new LabeledIcon(icon != null ? icon : AllIcons.General.ConfigurableDefault, getDisplayName(), null);
782     }
783
784     private void setupWithinPanel(JPanel panel, int groupIdx, int rowIdx, int columnIdx) {
785       myGroupIdx = groupIdx;
786       myRowIdx = rowIdx;
787       myColumnIdx = columnIdx;
788       setToolTipText(null);
789       setupListeners(panel);
790     }
791
792     protected String getDisplayName() {
793       return myDisplayName != null ? myDisplayName : "";
794     }
795
796     public Dimension getMaximumSize() {
797       return ACTION_BUTTON_SIZE;
798     }
799
800     public Dimension getMinimumSize() {
801       return ACTION_BUTTON_SIZE;
802     }
803
804     public Dimension getPreferredSize() {
805       return ACTION_BUTTON_SIZE;
806     }
807
808     protected void paintComponent(Graphics g) {
809       UIUtil.applyRenderingHints(g);
810       super.paintComponent(g);
811       ActionButtonLook look = ActionButtonLook.IDEA_LOOK;
812       paintBackground(g);
813       look.paintIcon(g, this, myIcon);
814       paintBorder(g);
815     }
816
817     protected Color getNormalButtonColor() {
818       return ACTION_BUTTON_COLOR;
819     }
820
821     protected void paintBackground(Graphics g) {
822       Dimension dimension = getSize();
823       int state = getPopState();
824       if (state != NORMAL) {
825         if (state == POPPED) {
826           g.setColor(BUTTON_POPPED_COLOR);
827           g.fillRect(0, 0, dimension.width, dimension.height);
828         }
829         else {
830           g.setColor(BUTTON_PUSHED_COLOR);
831           g.fillRect(0, 0, dimension.width, dimension.height);
832         }
833       }
834       else {
835         g.setColor(getNormalButtonColor());
836         g.fillRect(0, 0, dimension.width, dimension.height);
837       }
838       if (state == PUSHED) {
839         g.setColor(BUTTON_PUSHED_COLOR);
840         g.fillRect(0, 0, dimension.width, dimension.height);
841       }
842     }
843
844     public int getPopState() {
845       if (myPressedButton == this) return PUSHED;
846       if (myPressedButton != null) return NORMAL;
847       if (mySelectedColumn == myColumnIdx &&
848           mySelectedRow == myRowIdx &&
849           mySelectedGroup == myGroupIdx) {
850         return POPPED;
851       }
852       return NORMAL;
853     }
854
855     private void setupListeners(final JPanel panel) {
856       addMouseListener(new MouseAdapter() {
857         public void mousePressed(MouseEvent e) {
858           myPressedButton = MyActionButton.this;
859           panel.repaint();
860         }
861
862         public void mouseReleased(MouseEvent e) {
863           if (myPressedButton == MyActionButton.this) {
864             myPressedButton = null;
865             onPress(e);
866           }
867           else {
868             myPressedButton = null;
869           }
870
871           panel.repaint();
872         }
873
874         public void mouseExited(MouseEvent e) {
875           mySelectedColumn = -1;
876           mySelectedRow = -1;
877           mySelectedGroup = -1;
878           panel.repaint();
879         }
880       });
881
882       addMouseMotionListener(new MouseMotionListener() {
883         public void mouseDragged(MouseEvent e) {
884         }
885
886         public void mouseMoved(MouseEvent e) {
887           mySelectedColumn = myColumnIdx;
888           mySelectedRow = myRowIdx;
889           mySelectedGroup = myGroupIdx;
890           panel.repaint();
891         }
892       });
893     }
894
895     protected abstract void onPress(InputEvent e);
896   }
897
898   private abstract class ButtonWithExtension extends MyActionButton {
899     private ButtonWithExtension(Icon icon, String displayName) {
900       super(icon, displayName);
901     }
902
903     protected void onPress(InputEvent e) {
904       onPress(e, this);
905     }
906
907     protected abstract void onPress(InputEvent e, MyActionButton button);
908   }
909
910   private static JScrollPane scrollPane(final JPanel panel, @Nullable final Color borderColor) {
911     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(panel);
912     scrollPane.setBorder(borderColor != null ?
913                          BorderFactory.createLineBorder(borderColor, 1) : BorderFactory.createEmptyBorder());
914     scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
915     scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
916     return scrollPane;
917   }
918
919   private static class OpenProjectTransferHandler extends TransferHandler {
920     private final JComponent myComponent;
921
922     public OpenProjectTransferHandler(final JComponent component) {
923       myComponent = component;
924     }
925
926     public boolean canImport(final TransferSupport support) {
927       return FileCopyPasteUtil.isFileListFlavorSupported(support.getDataFlavors());
928     }
929
930     public boolean importData(final TransferSupport support) {
931       final String projectPath = getProjectPath(support);
932       if (projectPath == null) return false;
933       ApplicationManager.getApplication().invokeLater(new Runnable() {
934         public void run() {
935           ReopenProjectAction action = new ReopenProjectAction(projectPath, "Any", "Any");
936           DataContext dataContext = DataManager.getInstance().getDataContext(myComponent);
937           AnActionEvent e = new AnActionEvent(null, dataContext, "", action.getTemplatePresentation(), ActionManager.getInstance(), 0);
938           action.actionPerformed(e);
939         }
940       });
941       return true;
942     }
943
944     @Nullable
945     private static String getProjectPath(final TransferSupport support) {
946       if (!FileCopyPasteUtil.isFileListFlavorSupported(support.getDataFlavors())) return null;
947       List<File> files = FileCopyPasteUtil.getFileList(support.getTransferable());
948       if (files == null) return null;
949       File file = ContainerUtil.getFirstItem(files);
950       return file != null && isProjectFileOrDir(file) ? file.getAbsolutePath() : null;
951     }
952
953     private static boolean isProjectFileOrDir(File file) {
954       if (file.isFile() && file.getName().toLowerCase().endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION)) return true;
955       if (file.isDirectory() && new File(file, Project.DIRECTORY_STORE_FOLDER).isDirectory()) return true;
956       return false;
957     }
958   }
959 }