/* * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.ide.actions; import com.intellij.ide.BrowserUtil; import com.intellij.ide.IdeBundle; import com.intellij.ide.ui.UISettings; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CustomShortcutSet; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.ui.GraphicsConfig; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.ui.LicensingFacade; import com.intellij.ui.UI; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.Alarm; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.GraphicsUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.accessibility.AccessibleContextUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.accessibility.AccessibleAction; import javax.accessibility.AccessibleContext; import javax.swing.*; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.text.SimpleDateFormat; import java.util.*; import java.util.List; /** * @author Konstantin Bulenkov */ public class AboutPopup { private static final String COPY_URL = "copy://"; private static JBPopup ourPopup; public static void show(@Nullable Window window, boolean showDebugInfo) { ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance(); final PopupPanel panel = new PopupPanel(new BorderLayout()); Icon image = IconLoader.getIcon(appInfo.getAboutImageUrl()); if (appInfo.showLicenseeInfo()) { final InfoSurface infoSurface = new InfoSurface(image, showDebugInfo); infoSurface.setPreferredSize(new Dimension(image.getIconWidth(), image.getIconHeight())); panel.setInfoSurface(infoSurface); } else { panel.add(new JLabel(image), BorderLayout.NORTH); } RelativePoint location; if (window != null) { Rectangle r = window.getBounds(); location = new RelativePoint(window, new Point((r.width - image.getIconWidth()) / 2, (r.height - image.getIconHeight()) / 2)); } else { Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds(); location = new RelativePoint(new Point((r.width - image.getIconWidth()) / 2, (r.height - image.getIconHeight()) / 2)); } ourPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel) .setRequestFocus(true) .setFocusable(true) .setResizable(false) .setMovable(false) .setModalContext(false) .setShowShadow(true) .setShowBorder(false) .setCancelKeyEnabled(true) .setCancelOnClickOutside(true) .setCancelOnOtherWindowOpen(true) .createPopup(); Disposer.register(ourPopup, new Disposable() { @Override public void dispose() { ourPopup = null; } }); ourPopup.show(location); } private static void copyInfoToClipboard(String text) { try { CopyPasteManager.getInstance().setContents(new StringSelection(text)); } catch (Exception ignore) { } } private static class InfoSurface extends JPanel { private final Color myColor; private final Color myLinkColor; private final Icon myImage; private final boolean myShowDebugInfo; private Font myFont; private Font myBoldFont; private final List<AboutBoxLine> myLines = new ArrayList<>(); private StringBuilder myInfo = new StringBuilder(); private final List<Link> myLinks = new ArrayList<>(); private Link myActiveLink; private boolean myShowCopy = false; private float myShowCopyAlpha; private Alarm myAlarm = new Alarm(); public InfoSurface(Icon image, final boolean showDebugInfo) { ApplicationInfoImpl appInfo = (ApplicationInfoImpl)ApplicationInfoEx.getInstanceEx(); myImage = image; //noinspection UseJBColor myColor = Color.white; myLinkColor = appInfo.getAboutLinkColor() != null ? appInfo.getAboutLinkColor() : UI.getColor("link.foreground"); myShowDebugInfo = showDebugInfo; setOpaque(false); setBackground(myColor); setFocusable(true); Calendar cal = appInfo.getBuildDate(); myLines.add(new AboutBoxLine(appInfo.getFullApplicationName(), true, null)); appendLast(); String buildInfo = IdeBundle.message("about.box.build.number", appInfo.getBuild().asString()); String buildDate = ""; if (appInfo.getBuild().isSnapshot()) { buildDate = new SimpleDateFormat("HH:mm, ").format(cal.getTime()); } buildDate += DateFormatUtil.formatAboutDialogDate(cal.getTime()); buildInfo += IdeBundle.message("about.box.build.date", buildDate); myLines.add(new AboutBoxLine(buildInfo)); appendLast(); myLines.add(new AboutBoxLine("")); LicensingFacade provider = LicensingFacade.getInstance(); if (provider != null) { myLines.add(new AboutBoxLine(provider.getLicensedToMessage(), true, null)); appendLast(); for (String message : provider.getLicenseRestrictionsMessages()) { myLines.add(new AboutBoxLine(message)); appendLast(); } } myLines.add(new AboutBoxLine("")); Properties properties = System.getProperties(); String javaVersion = properties.getProperty("java.runtime.version", properties.getProperty("java.version", "unknown")); String arch = properties.getProperty("os.arch", ""); myLines.add(new AboutBoxLine(IdeBundle.message("about.box.jre", javaVersion, arch))); appendLast(); String vmVersion = properties.getProperty("java.vm.name", "unknown"); String vmVendor = properties.getProperty("java.vendor", "unknown"); myLines.add(new AboutBoxLine(IdeBundle.message("about.box.vm", vmVersion, vmVendor))); appendLast(); String thirdParty = appInfo.getThirdPartySoftwareURL(); if (thirdParty != null) { myLines.add(new AboutBoxLine("")); myLines.add(new AboutBoxLine("")); myLines.add(new AboutBoxLine("Powered by ").keepWithNext()); myLines.add(new AboutBoxLine("open-source software", false, thirdParty)); } addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent event) { if (myActiveLink != null) { event.consume(); if (COPY_URL.equals(myActiveLink.myUrl)) { copyInfoToClipboard(getText()); if (ourPopup != null) { ourPopup.cancel(); } return; } BrowserUtil.browse(myActiveLink.myUrl); } } final static double maxAlpha = 0.5; final static double fadeStep = 0.05; final static int animationDelay = 15; @Override public void mouseEntered(MouseEvent e) { if (!myShowCopy) { myShowCopy = true; myAlarm.cancelAllRequests(); myAlarm.addRequest(new Runnable() { @Override public void run() { if (myShowCopyAlpha < maxAlpha) { myShowCopyAlpha += fadeStep; repaint(); myAlarm.addRequest(this, animationDelay); } } }, animationDelay); } } @Override public void mouseExited(MouseEvent e) { if (myShowCopy) { myShowCopy = false; myAlarm.cancelAllRequests(); myAlarm.addRequest(new Runnable() { @Override public void run() { if (myShowCopyAlpha > 0) { myShowCopyAlpha -= fadeStep; repaint(); myAlarm.addRequest(this, animationDelay); } } }, animationDelay); } } }); addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent event) { boolean hadLink = (myActiveLink != null); myActiveLink = null; for (Link link : myLinks) { if (link.myRectangle.contains(event.getPoint())) { myActiveLink = link; if (!hadLink) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } break; } } if (hadLink && myActiveLink == null) { setCursor(Cursor.getDefaultCursor()); } } }); } private void appendLast() { myInfo.append(myLines.get(myLines.size() - 1).getText()).append("\n"); } @Override protected void paintChildren(Graphics g) { super.paintChildren(g); Graphics2D g2 = (Graphics2D)g; UISettings.setupAntialiasing(g); Font labelFont = JBUI.Fonts.label(); if (SystemInfo.isWindows) { labelFont = JBUI.Fonts.create("Tahoma", 12); } int startFontSize = Registry.is("ide.new.about") ? 14 : 10; for (int labelSize = JBUI.scale(startFontSize); labelSize != JBUI.scale(6); labelSize -= 1) { myLinks.clear(); g2.setPaint(myColor); myImage.paintIcon(this, g2, 0, 0); g2.setColor(myColor); TextRenderer renderer = createTextRenderer(g2); UIUtil.setupComposite(g2); myFont = labelFont.deriveFont(Font.PLAIN, labelSize); myBoldFont = labelFont.deriveFont(Font.BOLD, labelSize + 1); try { renderer.render(30, 0, myLines); break; } catch (TextRenderer.OverflowException ignore) { } } ApplicationInfo appInfo = ApplicationInfo.getInstance(); Rectangle aboutLogoRect = appInfo.getAboutLogoRect(); if (aboutLogoRect != null) { myLinks.add(new Link(aboutLogoRect, appInfo.getCompanyURL())); } if (appInfo instanceof ApplicationInfoImpl) { g2.setColor(((ApplicationInfoImpl)appInfo).getCopyrightForeground()); if (SystemInfo.isMac) { g2.setFont(JBUI.Fonts.miniFont()); } else { g2.setFont(JBUI.Fonts.create("Tahoma", 10)); } } else { g2.setColor(JBColor.BLACK); } if (Registry.is("ide.new.about")) { g2.setColor(Gray.x33); g2.setFont(JBUI.Fonts.label(12)); } final int copyrightX = Registry.is("ide.new.about") ? JBUI.scale(140) : JBUI.scale(30); final int copyrightY = Registry.is("ide.new.about") ? JBUI.scale(390) : JBUI.scale(284); g2.drawString(getCopyrightText(), copyrightX, copyrightY); if (myShowDebugInfo) { g2.setColor(((ApplicationInfoEx)appInfo).getAboutForeground()); for (Link link : myLinks) { g2.drawRect(link.myRectangle.x, link.myRectangle.y, link.myRectangle.width, link.myRectangle.height); } } } @NotNull private String getCopyrightText() { ApplicationInfo appInfo = ApplicationInfo.getInstance(); return "\u00A9 2000\u2013" + Calendar.getInstance(Locale.US).get(Calendar.YEAR) + " JetBrains s.r.o. All rights reserved."; } @NotNull private TextRenderer createTextRenderer(Graphics2D g) { if (Registry.is("ide.new.about")) { return new TextRenderer(18, 200, 500, 220, g); } return new TextRenderer(0, 165, 398, 120, g); } public String getText() { return myInfo.toString() + SystemInfo.getOsNameAndVersion(); } private class TextRenderer { private final int xBase; private final int yBase; private final int w; private final int h; private final Graphics2D g2; private int x = 0; private int y = 0; private FontMetrics fontmetrics; private int fontAscent; private int fontHeight; private Font font; public class OverflowException extends Exception { } public TextRenderer(final int xBase, final int yBase, final int w, final int h, final Graphics2D g2) { this.xBase = JBUI.scale(xBase); this.yBase = JBUI.scale(yBase); this.w = JBUI.scale(w); this.h = JBUI.scale(h); this.g2 = g2; if (SystemInfo.isWindows) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } } public void render(int indentX, int indentY, List<AboutBoxLine> lines) throws OverflowException { x = indentX; y = indentY; ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance(); boolean showCopyButton = myShowCopy || myShowCopyAlpha > 0; for (AboutBoxLine line : lines) { final String s = line.getText(); setFont(line.isBold() ? myBoldFont : myFont); if (line.getUrl() != null) { g2.setColor(myLinkColor); FontMetrics metrics = g2.getFontMetrics(font); myLinks.add(new Link(new Rectangle(xBase + x, yBase + y - fontAscent, metrics.stringWidth(s + " "), fontHeight), line.getUrl())); } else { g2.setColor(Registry.is("ide.new.about") ? Gray.x33 : appInfo.getAboutForeground()); } renderString(s, indentX); if (showCopyButton) { final FontMetrics metrics = g2.getFontMetrics(myFont); String copyString = "Copy to Clipboard"; final int width = metrics.stringWidth(copyString); g2.setFont(myFont); g2.setColor(myLinkColor); final int xOffset = myImage.getIconWidth() - width - 10; final GraphicsConfig config = GraphicsUtil.paintWithAlpha(g2, Math.max(0, Math.min(1, myShowCopyAlpha))); g2.drawString(copyString, xOffset, yBase + y); config.restore(); myLinks.add(new Link(new Rectangle(xOffset, yBase + y - fontAscent, width, fontHeight), COPY_URL)); showCopyButton = false; } if (!line.isKeepWithNext() && !line.equals(lines.get(lines.size()-1))) { lineFeed(indentX, s); } } } private void renderString(final String s, final int indentX) throws OverflowException { final List<String> words = StringUtil.split(s, " "); for (String word : words) { int wordWidth = fontmetrics.stringWidth(word); if (x + wordWidth >= w) { lineFeed(indentX, word); } else { char c = ' '; final int cW = fontmetrics.charWidth(c); if (x + cW < w) { g2.drawChars(new char[]{c}, 0, 1, xBase + x, yBase + y); x += cW; } } renderWord(word, indentX); } } private void renderWord(final String s, final int indentX) throws OverflowException { for (int j = 0; j != s.length(); ++j) { final char c = s.charAt(j); Font f = null; FontMetrics fm = null; try { if (!g2.getFont().canDisplay(c)) { f = g2.getFont(); fm = fontmetrics; g2.setFont(new Font("Monospaced", f.getStyle(), f.getSize())); fontmetrics = g2.getFontMetrics(); } final int cW = fontmetrics.charWidth(c); if (x + cW >= w) { lineFeed(indentX, s); } g2.drawChars(new char[]{c}, 0, 1, xBase + x, yBase + y); x += cW; } finally { if (f != null) { g2.setFont(f); fontmetrics = fm; } } } } private void lineFeed(int indent, final String s) throws OverflowException { x = indent; if (s.length() == 0) { y += fontHeight / 3; } else { y += fontHeight; } if (y >= h) { throw new OverflowException(); } } private void setFont(Font font) { this.font = font; fontmetrics = g2.getFontMetrics(font); g2.setFont(font); fontAscent = fontmetrics.getAscent(); fontHeight = fontmetrics.getHeight(); } } private static class AboutBoxLine { private final String myText; private final boolean myBold; private final String myUrl; private boolean myKeepWithNext; public AboutBoxLine(final String text, final boolean bold, final String url) { myText = text; myBold = bold; myUrl = url; } public AboutBoxLine(final String text) { myText = text; myBold = false; myUrl = null; } public String getText() { return myText; } public boolean isBold() { return myBold; } public String getUrl() { return myUrl; } public boolean isKeepWithNext() { return myKeepWithNext; } public AboutBoxLine keepWithNext() { myKeepWithNext = true; return this; } } private static class Link { private final Rectangle myRectangle; private final String myUrl; private Link(Rectangle rectangle, String url) { myRectangle = rectangle; myUrl = url; } } @Override public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleInfoSurface(); } return accessibleContext; } protected class AccessibleInfoSurface extends AccessibleJPanel { @Override public String getAccessibleName() { String text = "System Information\n" + getText() + "\n" + getCopyrightText(); return AccessibleContextUtil.replaceLineSeparatorsWithPunctuation(text); } } } public static class PopupPanel extends JPanel { private InfoSurface myInfoSurface; public PopupPanel(LayoutManager layout) { super(layout); } @Override public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessiblePopupPanel(); } return accessibleContext; } public void setInfoSurface(InfoSurface infoSurface) { myInfoSurface = infoSurface; add(infoSurface, BorderLayout.NORTH); new DumbAwareAction() { @Override public void actionPerformed(AnActionEvent e) { copyInfoToClipboard(myInfoSurface.getText()); } }.registerCustomShortcutSet(CustomShortcutSet.fromString("meta C", "control C"), this); } protected class AccessiblePopupPanel extends AccessibleJPanel implements AccessibleAction { @Override public String getAccessibleName() { ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance(); return "About " + appInfo.getFullApplicationName(); } @Override public String getAccessibleDescription() { if (myInfoSurface != null) { return "Press Copy key to copy system information to clipboard"; } return null; } @Override public AccessibleAction getAccessibleAction() { return this; } @Override public int getAccessibleActionCount() { if(myInfoSurface != null) return 1; return 0; } @Override public String getAccessibleActionDescription(int i) { if (i == 0 && myInfoSurface != null) return "Copy system information to clipboard"; return null; } @Override public boolean doAccessibleAction(int i) { if (i == 0 && myInfoSurface != null) { copyInfoToClipboard(myInfoSurface.getText()); return true; } return false; } } } }