8568ce8dee70cd483fb0ea88ec0f67a9b85f8c89
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / welcomeScreen / FlatWelcomeFrame.java
1 /*
2  * Copyright 2000-2015 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.DataManager;
20 import com.intellij.ide.RecentProjectsManager;
21 import com.intellij.internal.statistic.UsageTrigger;
22 import com.intellij.openapi.Disposable;
23 import com.intellij.openapi.MnemonicHelper;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ApplicationNamesInfo;
27 import com.intellij.openapi.application.ex.ApplicationInfoEx;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.project.DumbAwareAction;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.project.ProjectManager;
32 import com.intellij.openapi.project.ProjectManagerAdapter;
33 import com.intellij.openapi.ui.DialogWrapper;
34 import com.intellij.openapi.ui.popup.JBPopupFactory;
35 import com.intellij.openapi.util.*;
36 import com.intellij.openapi.util.registry.Registry;
37 import com.intellij.openapi.wm.IdeFrame;
38 import com.intellij.openapi.wm.IdeRootPaneNorthExtension;
39 import com.intellij.openapi.wm.StatusBar;
40 import com.intellij.openapi.wm.WelcomeScreen;
41 import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
42 import com.intellij.ui.*;
43 import com.intellij.ui.border.CustomLineBorder;
44 import com.intellij.ui.components.JBList;
45 import com.intellij.ui.components.JBSlidingPanel;
46 import com.intellij.ui.components.labels.ActionLink;
47 import com.intellij.ui.components.panels.NonOpaquePanel;
48 import com.intellij.ui.popup.PopupFactoryImpl;
49 import com.intellij.util.IconUtil;
50 import com.intellij.util.NotNullFunction;
51 import com.intellij.util.ui.JBInsets;
52 import com.intellij.util.ui.JBUI;
53 import com.intellij.util.ui.UIUtil;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import javax.swing.event.ListDataEvent;
59 import javax.swing.event.ListDataListener;
60 import javax.swing.event.ListSelectionEvent;
61 import javax.swing.event.ListSelectionListener;
62 import java.awt.*;
63 import java.awt.event.*;
64 import java.io.File;
65 import java.io.InputStream;
66 import java.net.URL;
67 import java.util.HashMap;
68 import java.util.Map;
69
70 /**
71  * @author Konstantin Bulenkov
72  */
73 public class FlatWelcomeFrame extends JFrame implements IdeFrame {
74   private final BalloonLayout myBalloonLayout;
75   private final FlatWelcomeScreen myScreen;
76
77   public FlatWelcomeFrame() {
78     final JRootPane rootPane = getRootPane();
79     myScreen = new FlatWelcomeScreen();
80
81     final IdeGlassPaneImpl glassPane = new IdeGlassPaneImpl(rootPane) {
82       @Override
83       public void addNotify() {
84         super.addNotify();
85         rootPane.remove(getProxyComponent());
86       }
87     };
88
89     setGlassPane(glassPane);
90     glassPane.setVisible(false);
91     //setUndecorated(true);
92     setContentPane(myScreen.getWelcomePanel());
93     setTitle("Welcome to " + ApplicationNamesInfo.getInstance().getFullProductName());
94     AppUIUtil.updateWindowIcon(this);
95     final int width = RecentProjectsManager.getInstance().getRecentProjectsActions(false).length == 0 ? 666 : 777;
96     setSize(JBUI.size(width, 460));
97     setResizable(false);
98     //int x = bounds.x + (bounds.width - getWidth()) / 2;
99     //int y = bounds.y + (bounds.height - getHeight()) / 2;
100     Point location = DimensionService.getInstance().getLocation(WelcomeFrame.DIMENSION_KEY, null);
101     Rectangle screenBounds = ScreenUtil.getScreenRectangle(location != null ? location : new Point(0, 0));
102     setLocation(new Point(
103       screenBounds.x + (screenBounds.width - getWidth()) / 2,
104       screenBounds.y + (screenBounds.height - getHeight()) / 3
105     ));
106
107     //setLocation(x, y);
108     ProjectManager.getInstance().addProjectManagerListener(new ProjectManagerAdapter() {
109       @Override
110       public void projectOpened(Project project) {
111         dispose();
112       }
113     });
114
115     myBalloonLayout = new BalloonLayoutImpl(rootPane, new JBInsets(8, 8, 8, 8));
116
117     WelcomeFrame.setupCloseAction(this);
118     MnemonicHelper.init(this);
119     Disposer.register(ApplicationManager.getApplication(), new Disposable() {
120       @Override
121       public void dispose() {
122         FlatWelcomeFrame.this.dispose();
123       }
124     });
125   }
126
127   @Override
128   public void dispose() {
129     saveLocation(getBounds());
130     super.dispose();
131     Disposer.dispose(myScreen);
132     WelcomeFrame.resetInstance();
133   }
134
135   private static void saveLocation(Rectangle location) {
136     Point middle = new Point(location.x + location.width / 2, location.y = location.height / 2);
137     DimensionService.getInstance().setLocation(WelcomeFrame.DIMENSION_KEY, middle, null);
138   }
139
140   @Override
141   public StatusBar getStatusBar() {
142     return null;
143   }
144
145   public static Color getMainBackground() {
146     return new JBColor(0xf7f7f7, 0x45474a);
147   }
148
149   public static Color getProjectsBackground() {
150     return new JBColor(Gray.xFF, Gray.x39);
151   }
152
153   public static Color getLinkNormalColor() {
154     return new JBColor(Gray._0, Gray.xBB);
155   }
156
157   public static Color getListSelectionColor(boolean hasFocus) {
158     return hasFocus ? new JBColor(0x3875d6, 0x4b6eaf) : new JBColor(Gray.xDD, Gray.x45);
159   }
160
161   public static Color getActionLinkSelectionColor() {
162     return new JBColor(0xdbe5f5, 0x485875);
163   }
164
165   public static JBColor getSeparatorColor() {
166     return new JBColor(Gray.xEC, new Color(72, 75, 78));
167   }
168
169   private class FlatWelcomeScreen extends JPanel implements WelcomeScreen {
170     private JBSlidingPanel mySlidingPanel = new JBSlidingPanel();
171
172     public FlatWelcomeScreen() {
173       super(new BorderLayout());
174       mySlidingPanel.add("root", this);
175       setBackground(getMainBackground());
176       if (RecentProjectsManager.getInstance().getRecentProjectsActions(false, isUseProjectGroups()).length > 0) {
177         final JComponent recentProjects = createRecentProjects();
178         add(recentProjects, BorderLayout.WEST);
179         final JList projectsList = UIUtil.findComponentOfType(recentProjects, JList.class);
180         if (projectsList != null) {
181           projectsList.getModel().addListDataListener(new ListDataListener() {
182             @Override
183             public void intervalAdded(ListDataEvent e) {
184             }
185
186             @Override
187             public void intervalRemoved(ListDataEvent e) {
188               removeIfNeeded();
189             }
190
191             private void removeIfNeeded() {
192               if (RecentProjectsManager.getInstance().getRecentProjectsActions(false, isUseProjectGroups()).length == 0) {
193                 FlatWelcomeScreen.this.remove(recentProjects);
194                 FlatWelcomeScreen.this.revalidate();
195                 FlatWelcomeScreen.this.repaint();
196               }
197             }
198
199             @Override
200             public void contentsChanged(ListDataEvent e) {
201               removeIfNeeded();
202             }
203           });
204           projectsList.addFocusListener(new FocusListener() {
205             @Override
206             public void focusGained(FocusEvent e) {
207               projectsList.repaint();
208             }
209
210             @Override
211             public void focusLost(FocusEvent e) {
212               projectsList.repaint();
213             }
214           });
215         }
216       }
217       add(createBody(), BorderLayout.CENTER);
218     }
219
220     @Override
221     public JComponent getWelcomePanel() {
222       return mySlidingPanel;
223     }
224
225     private JComponent createBody() {
226       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
227       panel.add(createLogo(), BorderLayout.NORTH);
228       panel.add(createActionPanel(), BorderLayout.CENTER);
229       panel.add(createSettingsAndDocs(), BorderLayout.SOUTH);
230       return panel;
231     }
232
233     private JComponent createSettingsAndDocs() {
234       JPanel panel = new NonOpaquePanel(new BorderLayout());
235       NonOpaquePanel toolbar = new NonOpaquePanel();
236       AnAction register = ActionManager.getInstance().getAction("Register");
237       boolean registeredVisible = false;
238       if (register != null) {
239         Presentation presentation = register.getTemplatePresentation();
240         register.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(this),
241                                           ActionPlaces.WELCOME_SCREEN, presentation, ActionManager.getInstance(), 0));
242         if (presentation.isEnabled()) {
243           ActionLink registerLink = new ActionLink("Register", register);
244           registerLink.setNormalColor(getLinkNormalColor());
245           NonOpaquePanel button = new NonOpaquePanel(new BorderLayout());
246           button.setBorder(JBUI.Borders.empty(4, 10, 4, 10));
247           button.add(registerLink);
248           installFocusable(button, register, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, true);
249           NonOpaquePanel wrap = new NonOpaquePanel();
250           wrap.setBorder(JBUI.Borders.empty(0, 10, 0, 0));
251           wrap.add(button);
252           panel.add(wrap, BorderLayout.WEST);
253           registeredVisible = true;
254         }
255       }
256
257       toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
258       toolbar.add(createActionLink("Configure", IdeActions.GROUP_WELCOME_SCREEN_CONFIGURE, AllIcons.General.GearPlain, !registeredVisible));
259       toolbar.add(createActionLink("Get Help", IdeActions.GROUP_WELCOME_SCREEN_DOC, null, false));
260
261       panel.add(toolbar, BorderLayout.EAST);
262
263
264       panel.setBorder(JBUI.Borders.empty(0, 0, 8, 11));
265       return panel;
266     }
267
268     private JComponent createActionLink(final String text, final String groupId, Icon icon, boolean focusListOnLeft) {
269       final Ref<ActionLink> ref = new Ref<ActionLink>(null);
270       AnAction action = new AnAction() {
271         @Override
272         public void actionPerformed(@NotNull AnActionEvent e) {
273           ActionGroup configureGroup = (ActionGroup)ActionManager.getInstance().getAction(groupId);
274           final PopupFactoryImpl.ActionGroupPopup popup = (PopupFactoryImpl.ActionGroupPopup)JBPopupFactory.getInstance()
275             .createActionGroupPopup(null, new IconsFreeActionGroup(configureGroup), e.getDataContext(), JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false,
276                                     ActionPlaces.WELCOME_SCREEN);
277           popup.showUnderneathOfLabel(ref.get());
278           UsageTrigger.trigger("welcome.screen." + groupId);
279         }
280       };
281       ref.set(new ActionLink(text, icon, action));
282       ref.get().setPaintUnderline(false);
283       ref.get().setNormalColor(getLinkNormalColor());
284       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
285       panel.setBorder(JBUI.Borders.empty(4, 6, 4, 6));
286       panel.add(ref.get());
287       panel.add(createArrow(ref.get()), BorderLayout.EAST);
288       installFocusable(panel, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, focusListOnLeft);
289       return panel;
290     }
291
292     private JComponent createActionPanel() {
293       JPanel actions = new NonOpaquePanel();
294       actions.setBorder(JBUI.Borders.empty(0, 10, 0, 0));
295       actions.setLayout(new BoxLayout(actions, BoxLayout.Y_AXIS));
296       ActionManager actionManager = ActionManager.getInstance();
297       ActionGroup quickStart = (ActionGroup)actionManager.getAction(IdeActions.GROUP_WELCOME_SCREEN_QUICKSTART);
298       DefaultActionGroup group = new DefaultActionGroup();
299       collectAllActions(group, quickStart);
300
301       for (AnAction action : group.getChildren(null)) {
302         JPanel button = new JPanel(new BorderLayout());
303         button.setOpaque(false);
304         button.setBorder(JBUI.Borders.empty(8, 20, 8, 20));
305         Presentation presentation = action.getTemplatePresentation();
306         action.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(this),
307                                         ActionPlaces.WELCOME_SCREEN, presentation, ActionManager.getInstance(), 0));
308         if (presentation.isVisible()) {
309           String text = presentation.getText();
310           if (text.endsWith("...")) {
311             text = text.substring(0, text.length() - 3);
312           }
313           Icon icon = presentation.getIcon();
314           if (icon.getIconHeight() != JBUI.scale(16) || icon.getIconWidth() != JBUI.scale(16)) {
315             icon = JBUI.emptyIcon(16);
316           }
317           action = wrapGroups(action);
318           ActionLink link = new ActionLink(text, icon, action, createUsageTracker(action));
319           link.setPaintUnderline(false);
320           link.setNormalColor(getLinkNormalColor());
321           button.add(link);
322           if (action instanceof WelcomePopupAction) {
323             button.add(createArrow(link), BorderLayout.EAST);
324           }
325           installFocusable(button, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, true);
326           actions.add(button);
327         }
328       }
329
330       WelcomeScreenActionsPanel panel = new WelcomeScreenActionsPanel();
331       panel.actions.add(actions);
332       return panel.root;
333     }
334
335     private AnAction wrapGroups(AnAction action) {
336       if (action instanceof ActionGroup && ((ActionGroup)action).isPopup()) {
337         final Pair<JPanel, JBList> panel = createActionGroupPanel((ActionGroup)action, mySlidingPanel, new Runnable() {
338           @Override
339           public void run() {
340             goBack();
341           }
342         });
343         final Runnable onDone = new Runnable() {
344           @Override
345           public void run() {
346             final JBList list = panel.second;
347             ListScrollingUtil.ensureSelectionExists(list);
348             final ListSelectionListener[] listeners =
349               ((DefaultListSelectionModel)list.getSelectionModel()).getListeners(ListSelectionListener.class);
350
351             //avoid component cashing. This helps in case of LaF change
352             for (ListSelectionListener listener : listeners) {
353               listener.valueChanged(new ListSelectionEvent(list, list.getSelectedIndex(), list.getSelectedIndex(), true));
354             }
355             list.requestFocus();
356           }
357         };
358         final String name = action.getClass().getName();
359         mySlidingPanel.add(name, panel.first);
360         final Presentation p = action.getTemplatePresentation();
361         return new DumbAwareAction(p.getText(), p.getDescription(), p.getIcon()) {
362           @Override
363           public void actionPerformed(@NotNull AnActionEvent e) {
364             mySlidingPanel.getLayout().swipe(mySlidingPanel, name, JBCardLayout.SwipeDirection.FORWARD, onDone);
365           }
366         };
367       }
368       return action;
369     }
370
371     protected void goBack() {
372       mySlidingPanel.swipe("root", JBCardLayout.SwipeDirection.BACKWARD).doWhenDone(new Runnable() {
373         @Override
374         public void run() {
375           mySlidingPanel.getRootPane().setDefaultButton(null);
376         }
377       });
378     }
379
380     private void collectAllActions(DefaultActionGroup group, ActionGroup actionGroup) {
381       for (AnAction action : actionGroup.getChildren(null)) {
382         if (action instanceof ActionGroup && !((ActionGroup)action).isPopup()) {
383           collectAllActions(group, (ActionGroup)action);
384         } else {
385           group.add(action);
386         }
387       }
388     }
389
390     private JComponent createLogo() {
391       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
392       ApplicationInfoEx app = ApplicationInfoEx.getInstanceEx();
393       JLabel logo = new JLabel(IconLoader.getIcon(app.getWelcomeScreenLogoUrl()));
394       logo.setBorder(JBUI.Borders.empty(30,0,10,0));
395       logo.setHorizontalAlignment(SwingConstants.CENTER);
396       panel.add(logo, BorderLayout.NORTH);
397       JLabel appName = new JLabel(ApplicationNamesInfo.getInstance().getFullProductName());
398       Font font = getProductFont();
399       appName.setForeground(JBColor.foreground());
400       appName.setFont(font.deriveFont(JBUI.scale(36f)).deriveFont(Font.PLAIN));
401       appName.setHorizontalAlignment(SwingConstants.CENTER);
402       String appVersion = "Version " + app.getFullVersion();
403
404       if (app.isEAP() && app.getBuild().getBuildNumber() < Integer.MAX_VALUE) {
405         appVersion += " (" + app.getBuild().asString() + ")";
406       }
407
408       JLabel version = new JLabel(appVersion);
409       version.setFont(getProductFont().deriveFont(JBUI.scale(16f)));
410       version.setHorizontalAlignment(SwingConstants.CENTER);
411       version.setForeground(Gray._128);
412
413       panel.add(appName);
414       panel.add(version, BorderLayout.SOUTH);
415       panel.setBorder(JBUI.Borders.empty(0, 0, 20, 0));
416       return panel;
417     }
418
419     private Font getProductFont() {
420       String name = "/fonts/Roboto-Light.ttf";
421       URL url = AppUIUtil.class.getResource(name);
422       if (url == null) {
423         Logger.getInstance(AppUIUtil.class).warn("Resource missing: " + name);
424       } else {
425
426         try {
427           InputStream is = url.openStream();
428           try {
429             return Font.createFont(Font.TRUETYPE_FONT, is);
430           }
431           finally {
432             is.close();
433           }
434         }
435         catch (Throwable t) {
436           Logger.getInstance(AppUIUtil.class).warn("Cannot load font: " + url, t);
437         }
438       }
439       return UIUtil.getLabelFont();
440     }
441
442     private JComponent createRecentProjects() {
443       JPanel panel = new JPanel(new BorderLayout());
444       panel.add(new NewRecentProjectPanel(this), BorderLayout.CENTER);
445       panel.setBackground(getProjectsBackground());
446       panel.setBorder(new CustomLineBorder(getSeparatorColor(), JBUI.insets(0, 0, 0, 1)));
447       return panel;
448     }
449
450     private void installFocusable(final JComponent comp, final AnAction action, final int prevKeyCode, final int nextKeyCode, final boolean focusListOnLeft) {
451       comp.setFocusable(true);
452       comp.setFocusTraversalKeysEnabled(true);
453       comp.addKeyListener(new KeyAdapter() {
454         @Override
455         public void keyPressed(KeyEvent e) {
456           final JList list = UIUtil.findComponentOfType(FlatWelcomeFrame.this.getComponent(), JList.class);
457           if (e.getKeyCode() == KeyEvent.VK_ENTER) {
458             InputEvent event = e;
459             if (e.getComponent() instanceof JComponent) {
460               ActionLink link = UIUtil.findComponentOfType((JComponent)e.getComponent(), ActionLink.class);
461               if (link != null) {
462                 event = new MouseEvent(link, MouseEvent.MOUSE_CLICKED, e.getWhen(), e.getModifiers(), 0, 0, 1, false, MouseEvent.BUTTON1);
463               }
464             }
465             action.actionPerformed(new AnActionEvent(event,
466                                                      DataManager.getInstance().getDataContext(),
467                                                      ActionPlaces.WELCOME_SCREEN,
468                                                      action.getTemplatePresentation().clone(),
469                                                      ActionManager.getInstance(),
470                                                      0));
471           } else if (e.getKeyCode() == prevKeyCode) {
472             focusPrev(comp);
473           } else if (e.getKeyCode() == nextKeyCode) {
474             focusNext(comp);
475           } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
476             if (focusListOnLeft) {
477               if (list != null) {
478                 list.requestFocus();
479               }
480             } else {
481               focusPrev(comp);
482             }
483           } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
484             focusNext(comp);
485           }
486         }
487       });
488       comp.addFocusListener(new FocusListener() {
489         @Override
490         public void focusGained(FocusEvent e) {
491           comp.setOpaque(true);
492           comp.setBackground(getActionLinkSelectionColor());
493         }
494
495         @Override
496         public void focusLost(FocusEvent e) {
497           comp.setOpaque(false);
498           comp.setBackground(getMainBackground());
499         }
500       });
501
502     }
503
504     protected void focusPrev(JComponent comp) {
505       FocusTraversalPolicy policy = FlatWelcomeFrame.this.getFocusTraversalPolicy();
506       if (policy != null) {
507         Component prev = policy.getComponentBefore(FlatWelcomeFrame.this, comp);
508         if (prev != null) {
509           prev.requestFocus();
510         }
511       }
512     }
513
514     protected void focusNext(JComponent comp) {
515       FocusTraversalPolicy policy = FlatWelcomeFrame.this.getFocusTraversalPolicy();
516       if (policy != null) {
517         Component next = policy.getComponentAfter(FlatWelcomeFrame.this, comp);
518         if (next != null) {
519           next.requestFocus();
520         }
521       }
522     }
523
524     @Override
525     public void setupFrame(JFrame frame) {
526
527     }
528
529     @Override
530     public void dispose() {
531
532     }
533
534     private class IconsFreeActionGroup extends ActionGroup {
535       private final ActionGroup myGroup;
536
537       public IconsFreeActionGroup(ActionGroup group) {
538         super(group.getTemplatePresentation().getText(), group.getTemplatePresentation().getDescription(), null);
539         myGroup = group;
540       }
541
542       @Override
543       public boolean isPopup() {
544         return myGroup.isPopup();
545       }
546
547       @NotNull
548       @Override
549       public AnAction[] getChildren(@Nullable AnActionEvent e) {
550         AnAction[] children = myGroup.getChildren(e);
551         AnAction[] patched = new AnAction[children.length];
552         for (int i = 0; i < children.length; i++) {
553           patched[i] = patch(children[i]);
554         }
555         return patched;
556       }
557
558       private AnAction patch(final AnAction child) {
559         if (child instanceof ActionGroup) {
560           return new IconsFreeActionGroup((ActionGroup)child);
561         }
562
563         Presentation presentation = child.getTemplatePresentation();
564         return new AnAction(presentation.getText(),
565                             presentation.getDescription(),
566                             null) {
567           @Override
568           public void actionPerformed(@NotNull AnActionEvent e) {
569             child.actionPerformed(e);
570             UsageTrigger.trigger("welcome.screen." + e.getActionManager().getId(child));
571           }
572
573           @Override
574           public void update(@NotNull AnActionEvent e) {
575             child.update(e);
576             e.getPresentation().setIcon(null);
577           }
578
579           @Override
580           public boolean isDumbAware() {
581             return child.isDumbAware();
582           }
583         };
584       }
585     }
586   }
587
588   public static boolean isUseProjectGroups() {
589     return Registry.is("welcome.screen.project.grouping.enabled");
590   }
591
592   private static Runnable createUsageTracker(final AnAction action) {
593     return new Runnable() {
594       @Override
595       public void run() {
596         UsageTrigger.trigger("welcome.screen." + ActionManager.getInstance().getId(action));
597       }
598     };
599   }
600
601   private static JLabel createArrow(final ActionLink link) {
602     JLabel arrow = new JLabel(AllIcons.General.Combo3);
603     arrow.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
604     arrow.setVerticalAlignment(SwingConstants.BOTTOM);
605     new ClickListener() {
606       @Override
607       public boolean onClick(@NotNull MouseEvent e, int clickCount) {
608         final MouseEvent newEvent = new MouseEvent(link, e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(),
609                                                    e.isPopupTrigger(), e.getButton());
610         link.doClick(newEvent);
611         return true;
612       }
613     }.installOn(arrow);
614     return arrow;
615   }
616
617   @Override
618   public BalloonLayout getBalloonLayout() {
619     return myBalloonLayout;
620   }
621
622   @Override
623   public Rectangle suggestChildFrameBounds() {
624     return getBounds();
625   }
626
627   @Nullable
628   @Override
629   public Project getProject() {
630     return ProjectManager.getInstance().getDefaultProject();
631   }
632
633   @Override
634   public void setFrameTitle(String title) {
635     setTitle(title);
636   }
637
638   @Override
639   public void setFileTitle(String fileTitle, File ioFile) {
640     setTitle(fileTitle);
641   }
642
643   @Override
644   public IdeRootPaneNorthExtension getNorthExtension(String key) {
645     return null;
646   }
647
648   @Override
649   public JComponent getComponent() {
650     return getRootPane();
651   }
652
653   public static void notifyFrameClosed(JFrame frame) {
654     saveLocation(frame.getBounds());
655   }
656
657   public static class WelcomeScreenActionsPanel {
658     private JPanel root;
659     private JPanel actions;
660   }
661
662   public static Pair<JPanel, JBList> createActionGroupPanel(ActionGroup action, final JComponent parent, final Runnable backAction) {
663     JPanel actionsListPanel = new JPanel(new BorderLayout());
664     actionsListPanel.setBackground(getProjectsBackground());
665     final JBList list = new JBList(action.getChildren(null));
666     list.setBackground(getProjectsBackground());
667     list.installCellRenderer(new NotNullFunction<AnAction, JComponent>() {
668       final JLabel label = new JLabel();
669       Map<Icon, Icon> scaled = new HashMap<Icon, Icon>();
670
671       {
672         label.setBorder(JBUI.Borders.empty(3, 7, 3, 7));
673       }
674
675       @NotNull
676       @Override
677       public JComponent fun(AnAction action) {
678         label.setText(action.getTemplatePresentation().getText());
679         Icon icon = action.getTemplatePresentation().getIcon();
680         if (icon.getIconHeight() == 32) {
681           Icon scaledIcon = scaled.get(icon);
682           if (scaledIcon == null) {
683             scaledIcon = IconUtil.scale(icon, 0.5);
684             scaled.put(icon, scaledIcon);
685           }
686           icon = scaledIcon;
687         }
688         label.setIcon(icon);
689         return label;
690       }
691     });
692     JScrollPane pane = ScrollPaneFactory.createScrollPane(list, true);
693     pane.setBackground(getProjectsBackground());
694     actionsListPanel.add(pane, BorderLayout.CENTER);
695     if (backAction != null) {
696       final JLabel back = new JLabel(AllIcons.Actions.Back);
697       back.setBorder(JBUI.Borders.empty(3, 7, 10, 7));
698       back.setHorizontalAlignment(SwingConstants.LEFT);
699       new ClickListener() {
700         @Override
701         public boolean onClick(@NotNull MouseEvent event, int clickCount) {
702           backAction.run();
703           return true;
704         }
705       }.installOn(back);
706       actionsListPanel.add(back, BorderLayout.SOUTH);
707     }
708     final Ref<Component> selected = Ref.create();
709     final JPanel main = new JPanel(new BorderLayout());
710     main.add(actionsListPanel, BorderLayout.WEST);
711
712     ListSelectionListener selectionListener = new ListSelectionListener() {
713       @Override
714       public void valueChanged(ListSelectionEvent e) {
715         if (!selected.isNull()) {
716           main.remove(selected.get());
717         }
718         Object value = list.getSelectedValue();
719         if (value instanceof AbstractActionWithPanel) {
720           JPanel panel = ((AbstractActionWithPanel)value).createPanel();
721           panel.setBorder(JBUI.Borders.empty(7, 10, 7, 10));
722           selected.set(panel);
723           main.add(selected.get());
724
725           for (JButton button : UIUtil.findComponentsOfType(main, JButton.class)) {
726             if (button.getClientProperty(DialogWrapper.DEFAULT_ACTION) == Boolean.TRUE) {
727               parent.getRootPane().setDefaultButton(button);
728               break;
729             }
730           }
731
732           main.revalidate();
733           main.repaint();
734         }
735       }
736     };
737     list.addListSelectionListener(selectionListener);
738     if (backAction != null) {
739       new AnAction() {
740         @Override
741         public void actionPerformed(@NotNull AnActionEvent e) {
742           backAction.run();
743         }
744       }.registerCustomShortcutSet(KeyEvent.VK_ESCAPE, 0, main);
745     }
746     return Pair.create(main, list);
747   }
748 }