add "Check for Update" action to the new welcome screen
[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(), JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false, ActionPlaces.WELCOME_SCREEN);
298           popup.showUnderneathOfLabel(ref.get());
299         }
300       };
301       ref.set(new ActionLink(text, icon, action));
302       ref.get().setPaintUnderline(false);
303       ref.get().setNormalColor(getLinkNormalColor());
304       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
305       panel.setBorder(new EmptyBorder(4, 10, 4, 10));
306       panel.add(ref.get());
307       panel.add(createArrow(ref.get()), BorderLayout.EAST);
308       installFocusable(panel, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, focusListOnLeft);
309       return panel;
310     }
311
312     private JComponent createActionPanel() {
313       JPanel actions = new NonOpaquePanel();
314       actions.setLayout(new BoxLayout(actions, BoxLayout.Y_AXIS));
315       ActionManager actionManager = ActionManager.getInstance();
316       ActionGroup quickStart = (ActionGroup)actionManager.getAction(IdeActions.GROUP_WELCOME_SCREEN_QUICKSTART);
317       DefaultActionGroup group = new DefaultActionGroup();
318       collectAllActions(group, quickStart);
319
320       for (AnAction action : group.getChildren(null)) {
321         JPanel button = new JPanel(new BorderLayout());
322         button.setOpaque(false);
323         button.setBorder(new EmptyBorder(8, 20, 8, 20));
324         Presentation presentation = action.getTemplatePresentation();
325         action.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(this),
326                                         ActionPlaces.WELCOME_SCREEN, presentation, ActionManager.getInstance(), 0));
327         if (presentation.isVisible()) {
328           String text = presentation.getText();
329           if (text.endsWith("...")) {
330             text = text.substring(0, text.length() - 3);
331           }
332           Icon icon = presentation.getIcon();
333           if (icon.getIconHeight() != 16 || icon.getIconWidth() != 16) {
334             icon = EmptyIcon.ICON_16;
335           }
336           ActionLink link = new ActionLink(text, icon, action);
337           link.setPaintUnderline(false);
338           link.setNormalColor(getLinkNormalColor());
339           button.add(link);
340           if (action instanceof WelcomePopupAction) {
341             button.add(createArrow(link), BorderLayout.EAST);
342           }
343           installFocusable(button, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, true);
344           actions.add(button);
345         }
346       }
347
348       actions.setBorder(new EmptyBorder(0, 0, 0, 0));
349       WelcomeScreenActionsPanel panel = new WelcomeScreenActionsPanel();
350       panel.actions.add(actions);
351       return panel.root;
352     }
353
354     private void collectAllActions(DefaultActionGroup group, ActionGroup actionGroup) {
355       for (AnAction action : actionGroup.getChildren(null)) {
356         if (action instanceof ActionGroup) {
357           collectAllActions(group, (ActionGroup)action);
358         } else {
359           group.add(action);
360         }
361       }
362     }
363
364     private JComponent createLogo() {
365       NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
366       ApplicationInfoEx app = ApplicationInfoEx.getInstanceEx();
367       JLabel logo = new JLabel(IconLoader.getIcon(app.getWelcomeScreenLogoUrl()));
368       logo.setHorizontalAlignment(SwingConstants.CENTER);
369       panel.add(logo, BorderLayout.NORTH);
370       JLabel appName = new JLabel(ApplicationNamesInfo.getInstance().getFullProductName());
371       Font font = getProductFont();
372       appName.setForeground(JBColor.foreground());
373       appName.setFont(font.deriveFont(36f).deriveFont(Font.PLAIN));
374       appName.setHorizontalAlignment(SwingConstants.CENTER);
375       String appVersion = "Version " + app.getFullVersion();
376       
377       if (app.isEAP() && app.getBuild().getBuildNumber() < Integer.MAX_VALUE) {
378         appVersion += " (" + app.getBuild().asString() + ")";
379       }
380       
381       JLabel version = new JLabel(appVersion);
382       version.setFont(font.deriveFont(16f).deriveFont(Font.PLAIN));
383       version.setHorizontalAlignment(SwingConstants.CENTER);
384       version.setForeground(Gray._128);
385       
386       panel.add(appName);
387       panel.add(version, BorderLayout.SOUTH);
388       panel.setBorder(new EmptyBorder(20, 10, 30, 10));
389       return panel;
390     }
391
392     private Font getProductFont() {
393       String name = "/fonts/Roboto-Light.ttf";
394       URL url = AppUIUtil.class.getResource(name);
395         if (url == null) {
396           Logger.getInstance(AppUIUtil.class).warn("Resource missing: " + name);
397         } else {
398
399         try {
400           InputStream is = url.openStream();
401           try {
402             return Font.createFont(Font.TRUETYPE_FONT, is);
403           }
404           finally {
405             is.close();
406           }
407         }
408         catch (Throwable t) {
409           Logger.getInstance(AppUIUtil.class).warn("Cannot load font: " + url, t);
410         }
411       }
412       return UIUtil.getLabelFont();
413     }
414
415     private JComponent createRecentProjects() {
416       JPanel panel = new JPanel(new BorderLayout());
417       panel.add(new NewRecentProjectPanel(this), BorderLayout.CENTER);
418       panel.setBackground(getProjectsBackground());
419       panel.setBorder(new CustomLineBorder(getSeparatorColor(), 0,0,0,1));
420       return panel;
421     }
422
423     private void installFocusable(final JComponent comp, final AnAction action, final int prevKeyCode, final int nextKeyCode, final boolean focusListOnLeft) {
424       comp.setFocusable(true);
425       comp.setFocusTraversalKeysEnabled(true);
426       comp.addKeyListener(new KeyAdapter() {
427         @Override
428         public void keyPressed(KeyEvent e) {
429           final JList list = UIUtil.findComponentOfType(FlatWelcomeFrame.this.getComponent(), JList.class);
430           if (e.getKeyCode() == KeyEvent.VK_ENTER) {
431             InputEvent event = e;
432             if (e.getComponent() instanceof JComponent) {
433               ActionLink link = UIUtil.findComponentOfType((JComponent)e.getComponent(), ActionLink.class);
434               if (link != null) {
435                 event = new MouseEvent(link, MouseEvent.MOUSE_CLICKED, e.getWhen(), e.getModifiers(), 0, 0, 1, false, MouseEvent.BUTTON1);
436               }
437             }
438             action.actionPerformed(new AnActionEvent(event,
439                                                      DataManager.getInstance().getDataContext(),
440                                                      ActionPlaces.WELCOME_SCREEN,
441                                                      action.getTemplatePresentation().clone(),
442                                                      ActionManager.getInstance(),
443                                                      0));
444           } else if (e.getKeyCode() == prevKeyCode) {
445             focusPrev(comp);
446           } else if (e.getKeyCode() == nextKeyCode) {
447             focusNext(comp);
448           } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
449             if (focusListOnLeft) {
450               if (list != null) {
451                 list.requestFocus();
452               }  
453             } else {
454               focusPrev(comp);
455             }
456           } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
457             focusNext(comp);
458           }
459         }
460       });
461       comp.addFocusListener(new FocusListener() {
462         @Override
463         public void focusGained(FocusEvent e) {
464           comp.setOpaque(true);
465           comp.setBackground(getActionLinkSelectionColor());
466         }
467
468         @Override
469         public void focusLost(FocusEvent e) {
470           comp.setOpaque(false);
471           comp.setBackground(getMainBackground());
472         }
473       });
474
475     }
476
477     protected void focusPrev(JComponent comp) {
478       FocusTraversalPolicy policy = FlatWelcomeFrame.this.getFocusTraversalPolicy();
479       if (policy != null) {
480         Component prev = policy.getComponentBefore(FlatWelcomeFrame.this, comp);
481         if (prev != null) {
482           prev.requestFocus();
483         }
484       }
485     }
486
487     protected void focusNext(JComponent comp) {
488       FocusTraversalPolicy policy = FlatWelcomeFrame.this.getFocusTraversalPolicy();
489       if (policy != null) {
490         Component next = policy.getComponentAfter(FlatWelcomeFrame.this, comp);
491         if (next != null) {
492           next.requestFocus();
493         }
494       }
495     }
496
497     @Override
498     public void setupFrame(JFrame frame) {
499
500     }
501
502     @Override
503     public void dispose() {
504
505     }
506
507     private class IconsFreeActionGroup extends ActionGroup {
508       private final ActionGroup myGroup;
509
510       public IconsFreeActionGroup(ActionGroup group) {
511         myGroup = group;
512       }
513
514       @NotNull
515       @Override
516       public AnAction[] getChildren(@Nullable AnActionEvent e) {
517         AnAction[] children = myGroup.getChildren(e);
518         AnAction[] patched = new AnAction[children.length];
519         for (int i = 0; i < children.length; i++) {
520           patched[i] = patch(children[i]);
521         }
522         return patched;
523       }
524
525       private AnAction patch(final AnAction child) {
526         if (child instanceof ActionGroup) {
527           return new IconsFreeActionGroup((ActionGroup)child);
528         }
529         
530           Presentation presentation = child.getTemplatePresentation();
531         return new AnAction(presentation.getText(),
532                             presentation.getDescription(),
533                             null) {
534           @Override
535           public void actionPerformed(@NotNull AnActionEvent e) {
536             child.actionPerformed(e);
537           }
538
539           @Override
540           public void update(@NotNull AnActionEvent e) {
541             child.update(e);
542             e.getPresentation().setIcon(null);
543           }
544
545           @Override
546           public boolean isDumbAware() {
547             return child.isDumbAware();
548           }
549         };
550       }
551     }
552   }
553
554   private static JLabel createArrow(final ActionLink link) {
555     JLabel arrow = new JLabel(AllIcons.General.Combo3);
556     arrow.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
557     arrow.setVerticalAlignment(SwingConstants.BOTTOM);
558     new ClickListener() {
559       @Override
560       public boolean onClick(@NotNull MouseEvent e, int clickCount) {
561         final MouseEvent newEvent = new MouseEvent(link, e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(),
562                                                    e.isPopupTrigger(), e.getButton());
563         link.doClick(newEvent);
564         return true;
565       }
566     }.installOn(arrow);
567     return arrow;
568   }
569
570   @Override
571   public BalloonLayout getBalloonLayout() {
572     return myBalloonLayout;
573   }
574
575   @Override
576   public Rectangle suggestChildFrameBounds() {
577     return getBounds();
578   }
579
580   @Nullable
581   @Override
582   public Project getProject() {
583     return ProjectManager.getInstance().getDefaultProject();
584   }
585
586   @Override
587   public void setFrameTitle(String title) {
588     setTitle(title);
589   }
590
591   @Override
592   public void setFileTitle(String fileTitle, File ioFile) {
593     setTitle(fileTitle);
594   }
595
596   @Override
597   public IdeRootPaneNorthExtension getNorthExtension(String key) {
598     return null;
599   }
600
601   @Override
602   public JComponent getComponent() {
603     return getRootPane();
604   }
605
606   public static void notifyFrameClosed(JFrame frame) {
607     saveLocation(frame.getBounds());
608   }
609   
610   public static class WelcomeScreenActionsPanel {
611     private JPanel root;
612     private JPanel actions;
613   }
614 }