welcome screen: VCS popup shows at left under the label
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / welcomeScreen / FlatWelcomeFrame.java
1 /*
2  * Copyright 2000-2014 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.openapi.Disposable;
22 import com.intellij.openapi.MnemonicHelper;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.application.Application;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ApplicationNamesInfo;
27 import com.intellij.openapi.application.ModalityState;
28 import com.intellij.openapi.application.ex.ApplicationInfoEx;
29 import com.intellij.openapi.application.ex.ApplicationManagerEx;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.project.DumbAwareRunnable;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.project.ProjectManager;
34 import com.intellij.openapi.project.ProjectManagerAdapter;
35 import com.intellij.openapi.ui.popup.JBPopupFactory;
36 import com.intellij.openapi.util.DimensionService;
37 import com.intellij.openapi.util.Disposer;
38 import com.intellij.openapi.util.IconLoader;
39 import com.intellij.openapi.util.Ref;
40 import com.intellij.openapi.wm.IdeFrame;
41 import com.intellij.openapi.wm.IdeRootPaneNorthExtension;
42 import com.intellij.openapi.wm.StatusBar;
43 import com.intellij.openapi.wm.WelcomeScreen;
44 import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
45 import com.intellij.ui.*;
46 import com.intellij.ui.border.CustomLineBorder;
47 import com.intellij.ui.components.labels.ActionLink;
48 import com.intellij.ui.components.panels.NonOpaquePanel;
49 import com.intellij.ui.popup.PopupFactoryImpl;
50 import com.intellij.util.ui.EmptyIcon;
51 import com.intellij.util.ui.UIUtil;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
54
55 import javax.swing.*;
56 import javax.swing.border.EmptyBorder;
57 import javax.swing.event.ListDataEvent;
58 import javax.swing.event.ListDataListener;
59 import java.awt.*;
60 import java.awt.event.*;
61 import java.io.File;
62 import java.io.InputStream;
63 import java.net.URL;
64
65 /**
66  * @author Konstantin Bulenkov
67  */
68 public class FlatWelcomeFrame extends JFrame implements IdeFrame {
69   private final BalloonLayout myBalloonLayout;
70   private final FlatWelcomeScreen myScreen;
71
72   public FlatWelcomeFrame() {
73     final JRootPane rootPane = getRootPane();
74     myScreen = new FlatWelcomeScreen();
75
76     final IdeGlassPaneImpl glassPane = new IdeGlassPaneImpl(rootPane) {
77       @Override
78       public void addNotify() {
79         super.addNotify();
80         rootPane.remove(getProxyComponent());
81       }
82     };
83
84     setGlassPane(glassPane);
85     glassPane.setVisible(false);
86     //setUndecorated(true);
87     setContentPane(myScreen.getWelcomePanel());
88     setTitle("Welcome to " + ApplicationNamesInfo.getInstance().getFullProductName());
89     AppUIUtil.updateWindowIcon(this);
90     //Rectangle bounds = ScreenUtil.getMainScreenBounds();
91     if (RecentProjectsManager.getInstance().getRecentProjectsActions(false).length > 0) {
92       setSize(666, 460);
93     } else {
94       setSize(555, 460);
95     }
96     setResizable(false);
97     //int x = bounds.x + (bounds.width - getWidth()) / 2;
98     //int y = bounds.y + (bounds.height - getHeight()) / 2;
99     Point location = DimensionService.getInstance().getLocation(WelcomeFrame.DIMENSION_KEY, null);
100     Rectangle screenBounds = ScreenUtil.getScreenRectangle(location != null ? location : new Point(0, 0));
101     setLocation(new Point(
102       screenBounds.x + (screenBounds.width - getWidth()) / 2,
103       screenBounds.y + (screenBounds.height - getHeight()) / 3
104     ));
105
106     //setLocation(x, y);
107     ProjectManager.getInstance().addProjectManagerListener(new ProjectManagerAdapter() {
108       @Override
109       public void projectOpened(Project project) {
110         dispose();
111       }
112     });
113
114     myBalloonLayout = new BalloonLayoutImpl(rootPane, new Insets(8, 8, 8, 8));
115
116     setupCloseAction();
117     new MnemonicHelper().register(this);
118     Disposer.register(ApplicationManager.getApplication(), new Disposable() {
119       @Override
120       public void dispose() {
121         FlatWelcomeFrame.this.dispose();
122       }
123     });
124   }
125
126   @Override
127   public void dispose() {
128     saveLocation(getBounds());
129     super.dispose();
130     Disposer.dispose(myScreen);
131     WelcomeFrame.resetInstance();
132   }
133
134   private static void saveLocation(Rectangle location) {
135     Point middle = new Point(location.x + location.width / 2, location.y = location.height / 2);
136     DimensionService.getInstance().setLocation(WelcomeFrame.DIMENSION_KEY, middle, null);
137   }
138
139   private void setupCloseAction() {
140     setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
141     addWindowListener(
142       new WindowAdapter() {
143         public void windowClosing(final WindowEvent e) {
144           dispose();
145
146           final Application app = ApplicationManager.getApplication();
147           app.invokeLater(new DumbAwareRunnable() {
148             public void run() {
149               if (app.isDisposed()) {
150                 ApplicationManagerEx.getApplicationEx().exit();
151                 return;
152               }
153
154               final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
155               if (openProjects.length == 0) {
156                 ApplicationManagerEx.getApplicationEx().exit();
157               }
158             }
159           }, ModalityState.NON_MODAL);
160         }
161       }
162     );
163   }
164
165   @Override
166   public StatusBar getStatusBar() {
167     return null;
168   }
169   
170   public static Color getMainBackground() {
171     return new JBColor(0xf7f7f7, 0x45474a);
172   }
173   
174   public static Color getProjectsBackground() {
175     return new JBColor(Gray.xFF, Gray.x39);
176   }
177   
178   public static Color getLinkNormalColor() {
179     return new JBColor(Gray._0, Gray.xBB);
180   }
181   
182   public static Color getListSelectionColor(boolean hasFocus) {
183     return hasFocus ? new JBColor(0x3875d6, 0x4b6eaf) : new JBColor(Gray.xDD, Gray.x45);
184   }
185   
186   public static Color getActionLinkSelectionColor() {
187     return new JBColor(0xdbe5f5, 0x485875);
188   }
189
190   public static JBColor getSeparatorColor() {
191     return new JBColor(Gray.xEC, new Color(72, 75, 78));
192   }
193
194   private class FlatWelcomeScreen extends JPanel implements WelcomeScreen {
195     public FlatWelcomeScreen() {
196       super(new BorderLayout());
197       setBackground(getMainBackground());
198       if (RecentProjectsManager.getInstance().getRecentProjectsActions(false).length > 0) {
199         final JComponent recentProjects = createRecentProjects();
200         add(recentProjects, BorderLayout.WEST);
201         final JList projectsList = UIUtil.findComponentOfType(recentProjects, JList.class);
202         if (projectsList != null) {
203           projectsList.getModel().addListDataListener(new ListDataListener() {
204             @Override
205             public void intervalAdded(ListDataEvent e) {             
206             }
207
208             @Override
209             public void intervalRemoved(ListDataEvent e) {
210               removeIfNeeded();
211             }
212
213             private void removeIfNeeded() {
214               if (RecentProjectsManager.getInstance().getRecentProjectsActions(false).length == 0) {
215                 FlatWelcomeScreen.this.remove(recentProjects);
216                 FlatWelcomeScreen.this.revalidate();
217                 FlatWelcomeScreen.this.repaint();
218               }
219             }
220
221             @Override
222             public void contentsChanged(ListDataEvent e) {
223               removeIfNeeded();
224             }
225           });
226           projectsList.addFocusListener(new FocusListener() {
227             @Override
228             public void focusGained(FocusEvent e) {
229               projectsList.repaint();
230             }
231
232             @Override
233             public void focusLost(FocusEvent e) {
234               projectsList.repaint();
235             }
236           });
237         }
238       }
239       add(createBody(), BorderLayout.CENTER);
240     }
241
242     @Override
243     public JComponent getWelcomePanel() {
244       return this;
245     }
246
247     private JComponent createBody() {
248       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
249       panel.add(createLogo(), BorderLayout.NORTH);
250       panel.add(createActionPanel(), BorderLayout.CENTER);
251       panel.add(createSettingsAndDocs(), BorderLayout.SOUTH);
252       return panel;
253     }
254
255     private JComponent createSettingsAndDocs() {
256       JPanel panel = new NonOpaquePanel(new BorderLayout());
257       NonOpaquePanel toolbar = new NonOpaquePanel();
258       AnAction register = ActionManager.getInstance().getAction("Register");
259       boolean registeredVisible = false;
260       if (register != null) {
261         Presentation presentation = register.getTemplatePresentation();
262         register.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(this),
263                                         ActionPlaces.WELCOME_SCREEN, presentation, ActionManager.getInstance(), 0));
264         if (presentation.isEnabled()) {
265           ActionLink registerLink = new ActionLink("Register", register);
266           registerLink.setNormalColor(getLinkNormalColor());
267           NonOpaquePanel button = new NonOpaquePanel(new BorderLayout());
268           button.setBorder(new EmptyBorder(4, 10, 4, 10));
269           button.add(registerLink);
270           installFocusable(button, register, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, true);
271           NonOpaquePanel wrap = new NonOpaquePanel();
272           wrap.setBorder(new EmptyBorder(0, 10, 0, 0));
273           wrap.add(button);
274           panel.add(wrap, BorderLayout.WEST);
275           registeredVisible = true;
276         }
277       }
278
279       toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
280       toolbar.add(createActionLink("Configure", IdeActions.GROUP_WELCOME_SCREEN_CONFIGURE, AllIcons.General.GearPlain, !registeredVisible));
281       toolbar.add(createActionLink("Get Help", IdeActions.GROUP_WELCOME_SCREEN_DOC, null, false));
282       
283       panel.add(toolbar, BorderLayout.EAST);
284       
285
286       panel.setBorder(new EmptyBorder(0,0,8,21));
287       return panel;
288     }
289     
290     private JComponent createActionLink(final String text, final String groupId, Icon icon, boolean focusListOnLeft) {
291       final Ref<ActionLink> ref = new Ref<ActionLink>(null);
292       AnAction action = new AnAction() {
293         @Override
294         public void actionPerformed(@NotNull AnActionEvent e) {
295           ActionGroup configureGroup = (ActionGroup)ActionManager.getInstance().getAction(groupId);
296           final PopupFactoryImpl.ActionGroupPopup popup = (PopupFactoryImpl.ActionGroupPopup)JBPopupFactory.getInstance()
297             .createActionGroupPopup(null, new IconsFreeActionGroup(configureGroup), e.getDataContext(), false, false, false, null,
298                                     10, null);
299           popup.showUnderneathOfLabel(ref.get());
300         }
301       };
302       ref.set(new ActionLink(text, icon, action));
303       ref.get().setPaintUnderline(false);
304       ref.get().setNormalColor(getLinkNormalColor());
305       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
306       panel.setBorder(new EmptyBorder(4, 10, 4, 10));
307       panel.add(ref.get());
308       panel.add(createArrow(ref.get()), BorderLayout.EAST);
309       installFocusable(panel, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, focusListOnLeft);
310       return panel;
311     }
312
313     private JComponent createActionPanel() {
314       JPanel actions = new NonOpaquePanel();
315       actions.setLayout(new BoxLayout(actions, BoxLayout.Y_AXIS));
316       ActionManager actionManager = ActionManager.getInstance();
317       ActionGroup quickStart = (ActionGroup)actionManager.getAction(IdeActions.GROUP_WELCOME_SCREEN_QUICKSTART);
318       DefaultActionGroup group = new DefaultActionGroup();
319       collectAllActions(group, quickStart);
320
321       for (AnAction action : group.getChildren(null)) {
322         JPanel button = new JPanel(new BorderLayout());
323         button.setOpaque(false);
324         button.setBorder(new EmptyBorder(8, 20, 8, 20));
325         Presentation presentation = action.getTemplatePresentation();
326         action.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(this),
327                                         ActionPlaces.WELCOME_SCREEN, presentation, ActionManager.getInstance(), 0));
328         if (presentation.isVisible()) {
329           String text = presentation.getText();
330           if (text.endsWith("...")) {
331             text = text.substring(0, text.length() - 3);
332           }
333           Icon icon = presentation.getIcon();
334           if (icon.getIconHeight() != 16 || icon.getIconWidth() != 16) {
335             icon = EmptyIcon.ICON_16;
336           }
337           ActionLink link = new ActionLink(text, icon, action);
338           link.setPaintUnderline(false);
339           link.setNormalColor(getLinkNormalColor());
340           button.add(link);
341           if (action instanceof WelcomePopupAction) {
342             button.add(createArrow(link), BorderLayout.EAST);
343           }
344           installFocusable(button, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, true);
345           actions.add(button);
346         }
347       }
348
349       actions.setBorder(new EmptyBorder(0, 0, 0, 0));
350       WelcomeScreenActionsPanel panel = new WelcomeScreenActionsPanel();
351       panel.actions.add(actions);
352       return panel.root;
353     }
354
355     private void collectAllActions(DefaultActionGroup group, ActionGroup actionGroup) {
356       for (AnAction action : actionGroup.getChildren(null)) {
357         if (action instanceof ActionGroup) {
358           collectAllActions(group, (ActionGroup)action);
359         } else {
360           group.add(action);
361         }
362       }
363     }
364
365     private JComponent createLogo() {
366       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
367       ApplicationInfoEx app = ApplicationInfoEx.getInstanceEx();
368       JLabel logo = new JLabel(IconLoader.getIcon(app.getWelcomeScreenLogoUrl()));
369       logo.setHorizontalAlignment(SwingConstants.CENTER);
370       panel.add(logo, BorderLayout.NORTH);
371       JLabel appName = new JLabel(ApplicationNamesInfo.getInstance().getFullProductName());
372       Font font = getProductFont();
373       appName.setForeground(JBColor.foreground());
374       appName.setFont(font.deriveFont(36f).deriveFont(Font.PLAIN));
375       appName.setHorizontalAlignment(SwingConstants.CENTER);
376       String appVersion = "Version " + app.getFullVersion();
377       
378       if (app.isEAP() && app.getBuild().getBuildNumber() < Integer.MAX_VALUE) {
379         appVersion += " (" + app.getBuild().asString() + ")";
380       }
381       
382       JLabel version = new JLabel(appVersion);
383       version.setFont(font.deriveFont(16f).deriveFont(Font.PLAIN));
384       version.setHorizontalAlignment(SwingConstants.CENTER);
385       version.setForeground(Gray._128);
386       
387       panel.add(appName);
388       panel.add(version, BorderLayout.SOUTH);
389       panel.setBorder(new EmptyBorder(20, 10, 30, 10));
390       return panel;
391     }
392
393     private Font getProductFont() {
394       String name = "/fonts/Roboto-Light.ttf";
395       URL url = AppUIUtil.class.getResource(name);
396         if (url == null) {
397           Logger.getInstance(AppUIUtil.class).warn("Resource missing: " + name);
398         } else {
399
400         try {
401           InputStream is = url.openStream();
402           try {
403             return Font.createFont(Font.TRUETYPE_FONT, is);
404           }
405           finally {
406             is.close();
407           }
408         }
409         catch (Throwable t) {
410           Logger.getInstance(AppUIUtil.class).warn("Cannot load font: " + url, t);
411         }
412       }
413       return UIUtil.getLabelFont();
414     }
415
416     private JComponent createRecentProjects() {
417       JPanel panel = new JPanel(new BorderLayout());
418       panel.add(new NewRecentProjectPanel(this), BorderLayout.CENTER);
419       panel.setBackground(getProjectsBackground());
420       panel.setBorder(new CustomLineBorder(getSeparatorColor(), 0,0,0,1));
421       return panel;
422     }
423
424     private void installFocusable(final JComponent comp, final AnAction action, final int prevKeyCode, final int nextKeyCode, final boolean focusListOnLeft) {
425       comp.setFocusable(true);
426       comp.setFocusTraversalKeysEnabled(true);
427       comp.addKeyListener(new KeyAdapter() {
428         @Override
429         public void keyPressed(KeyEvent e) {
430           final JList list = UIUtil.findComponentOfType(FlatWelcomeFrame.this.getComponent(), JList.class);
431           if (e.getKeyCode() == KeyEvent.VK_ENTER) {
432             InputEvent event = e;
433             if (e.getComponent() instanceof JComponent) {
434               ActionLink link = UIUtil.findComponentOfType((JComponent)e.getComponent(), ActionLink.class);
435               if (link != null) {
436                 event = new MouseEvent(link, MouseEvent.MOUSE_CLICKED, e.getWhen(), e.getModifiers(), 0, 0, 1, false, MouseEvent.BUTTON1);
437               }
438             }
439             action.actionPerformed(new AnActionEvent(event,
440                                                      DataManager.getInstance().getDataContext(),
441                                                      ActionPlaces.WELCOME_SCREEN,
442                                                      action.getTemplatePresentation().clone(),
443                                                      ActionManager.getInstance(),
444                                                      0));
445           } else if (e.getKeyCode() == prevKeyCode) {
446             focusPrev(comp);
447           } else if (e.getKeyCode() == nextKeyCode) {
448             focusNext(comp);
449           } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
450             if (focusListOnLeft) {
451               if (list != null) {
452                 list.requestFocus();
453               }  
454             } else {
455               focusPrev(comp);
456             }
457           } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
458             focusNext(comp);
459           }
460         }
461       });
462       comp.addFocusListener(new FocusListener() {
463         @Override
464         public void focusGained(FocusEvent e) {
465           comp.setOpaque(true);
466           comp.setBackground(getActionLinkSelectionColor());
467         }
468
469         @Override
470         public void focusLost(FocusEvent e) {
471           comp.setOpaque(false);
472           comp.setBackground(getMainBackground());
473         }
474       });
475
476     }
477
478     protected void focusPrev(JComponent comp) {
479       FocusTraversalPolicy policy = FlatWelcomeFrame.this.getFocusTraversalPolicy();
480       if (policy != null) {
481         Component prev = policy.getComponentBefore(FlatWelcomeFrame.this, comp);
482         if (prev != null) {
483           prev.requestFocus();
484         }
485       }
486     }
487
488     protected void focusNext(JComponent comp) {
489       FocusTraversalPolicy policy = FlatWelcomeFrame.this.getFocusTraversalPolicy();
490       if (policy != null) {
491         Component next = policy.getComponentAfter(FlatWelcomeFrame.this, comp);
492         if (next != null) {
493           next.requestFocus();
494         }
495       }
496     }
497
498     @Override
499     public void setupFrame(JFrame frame) {
500
501     }
502
503     @Override
504     public void dispose() {
505
506     }
507
508     private class IconsFreeActionGroup extends ActionGroup {
509       private final ActionGroup myGroup;
510
511       public IconsFreeActionGroup(ActionGroup group) {
512         myGroup = group;
513       }
514
515       @NotNull
516       @Override
517       public AnAction[] getChildren(@Nullable AnActionEvent e) {
518         AnAction[] children = myGroup.getChildren(e);
519         AnAction[] patched = new AnAction[children.length];
520         for (int i = 0; i < children.length; i++) {
521           patched[i] = patch(children[i]);
522         }
523         return patched;
524       }
525
526       private AnAction patch(final AnAction child) {
527         if (child instanceof ActionGroup) {
528           return new IconsFreeActionGroup((ActionGroup)child);
529         }
530         
531           Presentation presentation = child.getTemplatePresentation();
532         return new AnAction(presentation.getText(),
533                             presentation.getDescription(),
534                             null) {
535           @Override
536           public void actionPerformed(@NotNull AnActionEvent e) {
537             child.actionPerformed(e);
538           }
539
540           @Override
541           public void update(@NotNull AnActionEvent e) {
542             child.update(e);
543             e.getPresentation().setIcon(null);
544           }
545
546           @Override
547           public boolean isDumbAware() {
548             return child.isDumbAware();
549           }
550         };
551       }
552     }
553   }
554
555   private static JLabel createArrow(final ActionLink link) {
556     JLabel arrow = new JLabel(AllIcons.General.Combo3);
557     arrow.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
558     arrow.setVerticalAlignment(SwingConstants.BOTTOM);
559     new ClickListener() {
560       @Override
561       public boolean onClick(@NotNull MouseEvent e, int clickCount) {
562         final MouseEvent newEvent = new MouseEvent(link, e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(),
563                                                    e.isPopupTrigger(), e.getButton());
564         link.doClick(newEvent);
565         return true;
566       }
567     }.installOn(arrow);
568     return arrow;
569   }
570
571   @Override
572   public BalloonLayout getBalloonLayout() {
573     return myBalloonLayout;
574   }
575
576   @Override
577   public Rectangle suggestChildFrameBounds() {
578     return getBounds();
579   }
580
581   @Nullable
582   @Override
583   public Project getProject() {
584     return ProjectManager.getInstance().getDefaultProject();
585   }
586
587   @Override
588   public void setFrameTitle(String title) {
589     setTitle(title);
590   }
591
592   @Override
593   public void setFileTitle(String fileTitle, File ioFile) {
594     setTitle(fileTitle);
595   }
596
597   @Override
598   public IdeRootPaneNorthExtension getNorthExtension(String key) {
599     return null;
600   }
601
602   @Override
603   public JComponent getComponent() {
604     return getRootPane();
605   }
606
607   public static void notifyFrameClosed(JFrame frame) {
608     saveLocation(frame.getBounds());
609   }
610   
611   public static class WelcomeScreenActionsPanel {
612     private JPanel root;
613     private JPanel actions;
614   }
615 }