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