Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / actions / AboutPopup.java
1 /*
2  * Copyright 2000-2016 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.ide.actions;
17
18 import com.intellij.ide.BrowserUtil;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.ui.UISettings;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.AnActionEvent;
23 import com.intellij.openapi.actionSystem.CustomShortcutSet;
24 import com.intellij.openapi.application.ApplicationInfo;
25 import com.intellij.openapi.application.ex.ApplicationInfoEx;
26 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
27 import com.intellij.openapi.ide.CopyPasteManager;
28 import com.intellij.openapi.project.DumbAwareAction;
29 import com.intellij.openapi.ui.GraphicsConfig;
30 import com.intellij.openapi.ui.popup.JBPopup;
31 import com.intellij.openapi.ui.popup.JBPopupFactory;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.util.IconLoader;
34 import com.intellij.openapi.util.SystemInfo;
35 import com.intellij.openapi.util.registry.Registry;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.ui.Gray;
38 import com.intellij.ui.JBColor;
39 import com.intellij.ui.LicensingFacade;
40 import com.intellij.ui.UI;
41 import com.intellij.ui.awt.RelativePoint;
42 import com.intellij.util.Alarm;
43 import com.intellij.util.text.DateFormatUtil;
44 import com.intellij.util.ui.GraphicsUtil;
45 import com.intellij.util.ui.JBUI;
46 import com.intellij.util.ui.UIUtil;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import javax.swing.*;
51 import java.awt.*;
52 import java.awt.datatransfer.StringSelection;
53 import java.awt.event.MouseAdapter;
54 import java.awt.event.MouseEvent;
55 import java.awt.event.MouseMotionAdapter;
56 import java.text.SimpleDateFormat;
57 import java.util.*;
58 import java.util.List;
59
60 /**
61  * @author Konstantin Bulenkov
62  */
63 public class AboutPopup {
64   private static final String COPY_URL = "copy://";
65   private static JBPopup ourPopup;
66
67   public static void show(@Nullable Window window) {
68     ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance();
69
70     final JPanel panel = new JPanel(new BorderLayout());
71     Icon image = IconLoader.getIcon(appInfo.getAboutImageUrl());
72     if (appInfo.showLicenseeInfo()) {
73       final InfoSurface infoSurface = new InfoSurface(image);
74       infoSurface.setPreferredSize(new Dimension(image.getIconWidth(), image.getIconHeight()));
75       panel.add(infoSurface, BorderLayout.NORTH);
76
77       new DumbAwareAction() {
78         @Override
79         public void actionPerformed(AnActionEvent e) {
80           copyInfoToClipboard(infoSurface.getText());
81         }
82       }.registerCustomShortcutSet(CustomShortcutSet.fromString("meta C", "control C"), panel);
83     }
84     else {
85       panel.add(new JLabel(image), BorderLayout.NORTH);
86     }
87
88     RelativePoint location;
89     if (window != null) {
90       Rectangle r = window.getBounds();
91       location = new RelativePoint(window, new Point((r.width - image.getIconWidth()) / 2, (r.height - image.getIconHeight()) / 2));
92     }
93     else {
94       Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
95       location = new RelativePoint(new Point((r.width - image.getIconWidth()) / 2, (r.height - image.getIconHeight()) / 2));
96     }
97
98     ourPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel)
99       .setRequestFocus(true)
100       .setFocusable(true)
101       .setResizable(false)
102       .setMovable(false)
103       .setModalContext(false)
104       .setShowShadow(true)
105       .setShowBorder(false)
106       .setCancelKeyEnabled(true)
107       .setCancelOnClickOutside(true)
108       .setCancelOnOtherWindowOpen(true)
109       .createPopup();
110
111     Disposer.register(ourPopup, new Disposable() {
112       @Override
113       public void dispose() {
114         ourPopup = null;
115       }
116     });
117
118     ourPopup.show(location);
119   }
120
121   private static void copyInfoToClipboard(String text) {
122     try {
123       CopyPasteManager.getInstance().setContents(new StringSelection(text));
124     }
125     catch (Exception ignore) { }
126   }
127
128   private static class InfoSurface extends JPanel {
129     private final Color myColor;
130     private final Color myLinkColor;
131     private final Icon myImage;
132     private Font myFont;
133     private Font myBoldFont;
134     private final List<AboutBoxLine> myLines = new ArrayList<AboutBoxLine>();
135     private StringBuilder myInfo = new StringBuilder();
136     private final List<Link> myLinks = new ArrayList<Link>();
137     private Link myActiveLink;
138     private boolean myShowCopy = false;
139     private float myShowCopyAlpha;
140     private Alarm myAlarm = new Alarm();
141
142     public InfoSurface(Icon image) {
143       ApplicationInfoImpl appInfo = (ApplicationInfoImpl)ApplicationInfoEx.getInstanceEx();
144
145       myImage = image;
146       //noinspection UseJBColor
147       myColor = Color.white;
148       myLinkColor = appInfo.getAboutLinkColor() != null ? appInfo.getAboutLinkColor() : UI.getColor("link.foreground");
149
150       setOpaque(false);
151       setBackground(myColor);
152       setFocusable(true);
153       Calendar cal = appInfo.getBuildDate();
154       myLines.add(new AboutBoxLine(appInfo.getFullApplicationName(), true, null));
155       appendLast();
156
157       String buildInfo = IdeBundle.message("about.box.build.number", appInfo.getBuild().asString());
158       String buildDate = "";
159       if (appInfo.getBuild().isSnapshot()) {
160         buildDate = new SimpleDateFormat("HH:mm, ").format(cal.getTime());
161       }
162       buildDate += DateFormatUtil.formatAboutDialogDate(cal.getTime());
163       buildInfo += IdeBundle.message("about.box.build.date", buildDate);
164       myLines.add(new AboutBoxLine(buildInfo));
165       appendLast();
166
167       myLines.add(new AboutBoxLine(""));
168
169       LicensingFacade provider = LicensingFacade.getInstance();
170       if (provider != null) {
171         myLines.add(new AboutBoxLine(provider.getLicensedToMessage(), true, null));
172         for (String message : provider.getLicenseRestrictionsMessages()) {
173           myLines.add(new AboutBoxLine(message));
174         }
175       }
176
177       myLines.add(new AboutBoxLine(""));
178
179       Properties properties = System.getProperties();
180       String javaVersion = properties.getProperty("java.runtime.version", properties.getProperty("java.version", "unknown"));
181       String arch = properties.getProperty("os.arch", "");
182       myLines.add(new AboutBoxLine(IdeBundle.message("about.box.jre", javaVersion, arch)));
183       appendLast();
184
185       String vmVersion = properties.getProperty("java.vm.name", "unknown");
186       String vmVendor = properties.getProperty("java.vendor", "unknown");
187       myLines.add(new AboutBoxLine(IdeBundle.message("about.box.vm", vmVersion, vmVendor)));
188       appendLast();
189
190       String thirdParty = appInfo.getThirdPartySoftwareURL();
191       if (thirdParty != null) {
192         myLines.add(new AboutBoxLine(""));
193         myLines.add(new AboutBoxLine(""));
194         myLines.add(new AboutBoxLine("Powered by ").keepWithNext());
195         myLines.add(new AboutBoxLine("open-source software", false, thirdParty));
196       }
197
198       addMouseListener(new MouseAdapter() {
199         @Override
200         public void mousePressed(MouseEvent event) {
201           if (myActiveLink != null) {
202             event.consume();
203             if (COPY_URL.equals(myActiveLink.myUrl)) {
204               copyInfoToClipboard(myInfo.toString());
205               if (ourPopup != null) {
206                 ourPopup.cancel();
207               }
208               return;
209             }
210             BrowserUtil.browse(myActiveLink.myUrl);
211           }
212         }
213
214         final static double maxAlpha = 0.5;
215         final static double fadeStep = 0.05;
216         final static int animationDelay = 15;
217
218         @Override
219         public void mouseEntered(MouseEvent e) {
220           if (!myShowCopy) {
221             myShowCopy = true;
222             myAlarm.cancelAllRequests();
223             myAlarm.addRequest(new Runnable() {
224               @Override
225               public void run() {
226                 if (myShowCopyAlpha < maxAlpha) {
227                   myShowCopyAlpha += fadeStep;
228                   repaint();
229                   myAlarm.addRequest(this, animationDelay);
230                 }
231               }
232             }, animationDelay);
233           }
234         }
235
236         @Override
237         public void mouseExited(MouseEvent e) {
238           if (myShowCopy) {
239             myShowCopy = false;
240             myAlarm.cancelAllRequests();
241             myAlarm.addRequest(new Runnable() {
242               @Override
243               public void run() {
244                 if (myShowCopyAlpha > 0) {
245                   myShowCopyAlpha -= fadeStep;
246                   repaint();
247                   myAlarm.addRequest(this, animationDelay);
248                 }
249               }
250             }, animationDelay);
251           }
252         }
253       });
254
255       addMouseMotionListener(new MouseMotionAdapter() {
256         @Override
257         public void mouseMoved(MouseEvent event) {
258           boolean hadLink = (myActiveLink != null);
259           myActiveLink = null;
260           for (Link link : myLinks) {
261             if (link.myRectangle.contains(event.getPoint())) {
262               myActiveLink = link;
263               if (!hadLink) {
264                 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
265               }
266               break;
267             }
268           }
269           if (hadLink && myActiveLink == null) {
270             setCursor(Cursor.getDefaultCursor());
271           }
272         }
273       });
274     }
275
276     private void appendLast() {
277       myInfo.append(myLines.get(myLines.size() - 1).getText()).append("\n");
278     }
279
280     @Override
281     protected void paintChildren(Graphics g) {
282       super.paintChildren(g);
283
284       Graphics2D g2 = (Graphics2D)g;
285       UISettings.setupAntialiasing(g);
286
287       Font labelFont = JBUI.Fonts.label();
288       if (SystemInfo.isWindows) {
289         labelFont = JBUI.Fonts.create("Tahoma", 12);
290       }
291
292       int startFontSize = Registry.is("ide.new.about") ? 14 : 10;
293       for (int labelSize = JBUI.scale(startFontSize); labelSize != JBUI.scale(6); labelSize -= 1) {
294         myLinks.clear();
295         g2.setPaint(myColor);
296         myImage.paintIcon(this, g2, 0, 0);
297
298         g2.setColor(myColor);
299         TextRenderer renderer = createTextRenderer(g2);
300         UIUtil.setupComposite(g2);
301         myFont = labelFont.deriveFont(Font.PLAIN, labelSize);
302         myBoldFont = labelFont.deriveFont(Font.BOLD, labelSize + 1);
303         try {
304           renderer.render(30, 0, myLines);
305           break;
306         }
307         catch (TextRenderer.OverflowException ignore) { }
308       }
309
310       ApplicationInfo appInfo = ApplicationInfo.getInstance();
311       Rectangle aboutLogoRect = appInfo.getAboutLogoRect();
312       if (aboutLogoRect != null) {
313         myLinks.add(new Link(aboutLogoRect, appInfo.getCompanyURL()));
314       }
315
316       if (appInfo instanceof ApplicationInfoImpl) {
317         g2.setColor(((ApplicationInfoImpl)appInfo).getCopyrightForeground());
318         if (SystemInfo.isMac) {
319           g2.setFont(JBUI.Fonts.miniFont());
320         }
321         else {
322           g2.setFont(JBUI.Fonts.create("Tahoma", 10));
323         }
324       } else {
325         g2.setColor(JBColor.BLACK);
326       }
327
328       if (Registry.is("ide.new.about")) {
329         g2.setColor(Gray.x33);
330         g2.setFont(JBUI.Fonts.label(12));
331       }
332       final int copyrightX = Registry.is("ide.new.about") ? JBUI.scale(140) : JBUI.scale(30);
333       final int copyrightY = Registry.is("ide.new.about") ? JBUI.scale(390) : JBUI.scale(284);
334       g2.drawString("\u00A9 2000\u2013" + Calendar.getInstance(Locale.US).get(Calendar.YEAR) + " JetBrains s.r.o. All rights reserved.", copyrightX, copyrightY);
335     }
336
337     @NotNull
338     private TextRenderer createTextRenderer(Graphics2D g) {
339       if (Registry.is("ide.new.about")) {
340         return new TextRenderer(18, 200, 500, 220, g);
341       }
342       return new TextRenderer(0, 165, 398, 120, g);
343     }
344
345     public String getText() {
346       return myInfo.toString();
347     }
348
349     private class TextRenderer {
350       private final int xBase;
351       private final int yBase;
352       private final int w;
353       private final int h;
354       private final Graphics2D g2;
355
356       private int x = 0;
357       private int y = 0;
358       private FontMetrics fontmetrics;
359       private int fontAscent;
360       private int fontHeight;
361       private Font font;
362
363       public class OverflowException extends Exception { }
364
365       public TextRenderer(final int xBase, final int yBase, final int w, final int h, final Graphics2D g2) {
366         this.xBase = JBUI.scale(xBase);
367         this.yBase = JBUI.scale(yBase);
368         this.w = JBUI.scale(w);
369         this.h = JBUI.scale(h);
370         this.g2 = g2;
371
372         if (SystemInfo.isWindows) {
373           g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
374         }
375       }
376
377       public void render(int indentX, int indentY, List<AboutBoxLine> lines) throws OverflowException {
378         x = indentX;
379         y = indentY;
380         ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance();
381         boolean showCopyButton = myShowCopy || myShowCopyAlpha > 0;
382         for (AboutBoxLine line : lines) {
383           final String s = line.getText();
384           setFont(line.isBold() ? myBoldFont : myFont);
385           if (line.getUrl() != null) {
386             g2.setColor(myLinkColor);
387             FontMetrics metrics = g2.getFontMetrics(font);
388             myLinks.add(new Link(new Rectangle(xBase + x, yBase + y - fontAscent, metrics.stringWidth(s + " "), fontHeight), line.getUrl()));
389           }
390           else {
391             g2.setColor(Registry.is("ide.new.about") ? Gray.x33 : appInfo.getAboutForeground());
392           }
393           renderString(s, indentX);
394           if (showCopyButton) {
395             final FontMetrics metrics = g2.getFontMetrics(myFont);
396             String copyString = "Copy to Clipboard";
397             final int width = metrics.stringWidth(copyString);
398             g2.setFont(myFont);
399             g2.setColor(myLinkColor);
400             final int xOffset = myImage.getIconWidth() - width - 10;
401             final GraphicsConfig config = GraphicsUtil.paintWithAlpha(g2, myShowCopyAlpha);
402             g2.drawString(copyString, xOffset, yBase + y);
403             config.restore();
404             myLinks.add(new Link(new Rectangle(xOffset, yBase + y - fontAscent, width, fontHeight), COPY_URL));
405             showCopyButton = false;
406           }
407           if (!line.isKeepWithNext() && !line.equals(lines.get(lines.size()-1))) {
408             lineFeed(indentX, s);
409           }
410         }
411       }
412
413       private void renderString(final String s, final int indentX) throws OverflowException {
414         final List<String> words = StringUtil.split(s, " ");
415         for (String word : words) {
416           int wordWidth = fontmetrics.stringWidth(word);
417           if (x + wordWidth >= w) {
418             lineFeed(indentX, word);
419           }
420           else {
421             char c = ' ';
422             final int cW = fontmetrics.charWidth(c);
423             if (x + cW < w) {
424               g2.drawChars(new char[]{c}, 0, 1, xBase + x, yBase + y);
425               x += cW;
426             }
427           }
428           renderWord(word, indentX);
429         }
430       }
431
432       private void renderWord(final String s, final int indentX) throws OverflowException {
433         for (int j = 0; j != s.length(); ++j) {
434           final char c = s.charAt(j);
435           final int cW = fontmetrics.charWidth(c);
436           if (x + cW >= w) {
437             lineFeed(indentX, s);
438           }
439           g2.drawChars(new char[]{c}, 0, 1, xBase + x, yBase + y);
440           x += cW;
441         }
442       }
443
444       private void lineFeed(int indent, final String s) throws OverflowException {
445         x = indent;
446         if (s.length() == 0) {
447           y += fontHeight / 3;
448         }
449         else {
450           y += fontHeight;
451         }
452         if (y >= h) {
453           throw new OverflowException();
454         }
455       }
456
457       private void setFont(Font font) {
458         this.font = font;
459         fontmetrics = g2.getFontMetrics(font);
460         g2.setFont(font);
461         fontAscent = fontmetrics.getAscent();
462         fontHeight = fontmetrics.getHeight();
463       }
464     }
465
466     private static class AboutBoxLine {
467       private final String myText;
468       private final boolean myBold;
469       private final String myUrl;
470       private boolean myKeepWithNext;
471
472       public AboutBoxLine(final String text, final boolean bold, final String url) {
473         myText = text;
474         myBold = bold;
475         myUrl = url;
476       }
477
478       public AboutBoxLine(final String text) {
479         myText = text;
480         myBold = false;
481         myUrl = null;
482       }
483
484       public String getText() {
485         return myText;
486       }
487
488       public boolean isBold() {
489         return myBold;
490       }
491
492       public String getUrl() {
493         return myUrl;
494       }
495
496       public boolean isKeepWithNext() {
497         return myKeepWithNext;
498       }
499
500       public AboutBoxLine keepWithNext() {
501         myKeepWithNext = true;
502         return this;
503       }
504     }
505
506     private static class Link {
507       private final Rectangle myRectangle;
508       private final String myUrl;
509
510       private Link(Rectangle rectangle, String url) {
511         myRectangle = rectangle;
512         myUrl = url;
513       }
514     }
515   }
516 }