package org.jtheque.views.impl.components.panel; import org.jtheque.core.Core; import org.jtheque.core.application.Application; import org.jtheque.i18n.Internationalizable; import org.jtheque.i18n.LanguageService; import org.jtheque.ui.utils.AnimationUtils; import org.jtheque.utils.DesktopUtils; import org.jtheque.utils.StringUtils; import org.jtheque.utils.collections.CollectionUtils; import org.jtheque.utils.ui.ImageUtils; import org.jtheque.utils.ui.PaintUtils; import org.jtheque.utils.ui.SizeTracker; import org.jtheque.utils.ui.SwingUtils; import org.jtheque.views.impl.controllers.LicenseController; import org.jtheque.views.impl.models.AboutInfo; import org.jtheque.views.impl.models.AboutModel; import org.jtheque.views.windows.AboutView; import org.jdesktop.swingx.JXPanel; import org.pushingpixels.trident.Timeline; import org.pushingpixels.trident.callback.TimelineCallbackAdapter; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.awt.image.BufferedImage; import java.util.Map; import static org.jtheque.views.impl.models.AboutModel.*; /* * Copyright JTheque (Baptiste Wicht) * * 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. */ /** * An about view using filthy paint with Java 2D. * * @author Baptiste Wicht */ public final class AboutPane extends JXPanel implements AboutView, Internationalizable { private final SizeTracker tracker = new SizeTracker(this); private final Map<String, Shape> shapes = CollectionUtils.newHashMap(3); private final Map<Rectangle, String> urlRectangles = CollectionUtils.newHashMap(5); private BufferedImage logo; private BufferedImage infosImage; private BufferedImage creditsImage; private int xStart = -1; private int yStart = -1; private Font fontName; private Font fontInfos; private int max; private int start; private int paintWidth; private boolean inited; private Timeline timeline; private final AboutModel model = new AboutModel(); @Resource private Core core; @Resource private LanguageService languageService; @Resource private LicenseController licenseController; /** * Create the view. */ @PostConstruct public void create() { setOpaque(false); setVisible(true); languageService.addInternationalizable(this); Application application = core.getApplication(); if (!StringUtils.isEmpty(application.getLogo())) { //We must use directly that, or we can also add prefix file to use as Spring resource logo = ImageUtils.openCompatibleImageFromFileSystem(application.getLogo() + '.' + application.getLogoType().getExtension()); } addMouseListener(new MouseController()); addMouseMotionListener(new MouseMotionController()); init(); timeline = AnimationUtils.createInterpolationAnimation(this, 4 * 1000, "start", 0, model.getCreditsHeight()); shapes.put("license", new GeneralPath()); //Set a default path to avoid NPE if display license is false } /** * Init the about pane. */ private void init() { model.refresh(core, languageService); inited = false; } @Override public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; if (!inited) { initView(g2); } PaintUtils.initHints(g2); Color textColor = new Color(255, 255, 255, (int) (getAlpha() * 255)); if (tracker.hasSizeChanged()) { xStart = (getWidth() - paintWidth) / 2; yStart = (getHeight() - VIEW_HEIGHT) / 2; calcQuitShape(xStart, yStart); } paintBackground(g2); paintTitle(g2, xStart, yStart, textColor); if (logo != null) { PaintUtils.drawAlphaImage(g2, logo, xStart, yStart + 65, getAlpha()); } int y = paintInfos(g2, yStart + 65); y = Math.max(y, 65 + (logo == null ? 0 : logo.getHeight())) + 30; int x = (getWidth() - creditsImage.getWidth()) / 2; paintCredits(g2, x, y); PaintUtils.drawString(g2, model.getCopyright(), xStart, y + 120, fontInfos, textColor); paintLicense(g2, textColor, x, y); tracker.updateSize(); } /** * Paint the infos. * * @param g2 The graphics 2D elements. * @param yStart The y position to start from to paint. * * @return The y position stop of the infos. */ private int paintInfos(Graphics2D g2, int yStart) { int startX = logo == null ? 0 : logo.getWidth() + (getWidth() - paintWidth) / 2 + INFO_MARGIN; if (tracker.hasSizeChanged()) { createBufferInfosImage(yStart, startX); } PaintUtils.drawAlphaImage(g2, infosImage, startX, yStart, getAlpha()); return yStart + infosImage.getHeight(); } /** * Create the image of the informations to improve performance. The image is only modified when the size of the * window has changed. * * @param startY The y position to start painting. * @param startX The x position to start painting. */ private void createBufferInfosImage(int startY, int startX) { infosImage = ImageUtils.createCompatibleImage(paintWidth - 30, model.getInfos().length * (INFO_HEIGHT + 2), BufferedImage.TRANSLUCENT); Graphics g = infosImage.getGraphics(); PaintUtils.initHints((Graphics2D) g); Path2D urlPath = new GeneralPath(); for (int i = 0; i < model.getInfos().length; i++) { AboutInfo info = model.getInfos()[i]; int x = max - info.getLeftWidth(); int y = i * INFO_HEIGHT + INFO_HEIGHT; PaintUtils.drawString(g, info.getLeft() + " : ", x, y, fontInfos); x += info.getLeftWidth() + INFO_MARGIN; if (info.isUrl() || info.isMail()) { PaintUtils.drawString(g, info.getRight(), x, y, SwingUtils.underline(fontInfos)); Rectangle rectangle = new Rectangle(startX, startY + y - 10, info.getRightWidth(), CREDIT_HEIGHT); urlPath.append(rectangle, false); urlRectangles.put(rectangle, info.getRight()); } else { PaintUtils.drawString(g, info.getRight(), x, y, fontInfos); } } shapes.put("url", urlPath); g.dispose(); } /** * Paint the license clickable text. * * @param g2 The graphics 2D elements. * @param textColor The text color. * @param x The x position to start painting the license. * @param y The y position to start painting the license. */ private void paintLicense(Graphics g2, Color textColor, int x, int y) { if (core.getApplication().isLicenseDisplayed()) { int xLicense = x + paintWidth - getFontMetrics(fontInfos).stringWidth(model.getLicense()); PaintUtils.drawString(g2, model.getLicense(), xLicense, y + 150, fontInfos, textColor); shapes.put("license", new GeneralPath( new Rectangle(xLicense, y + 140, getFontMetrics(fontInfos).stringWidth(model.getLicense()), CREDIT_HEIGHT))); } } /** * Paint the credits image. * * @param g2 The graphics 2D elements. * @param x The x position to start painting the credits image. * @param y The y position to start painting the credits image. */ private void paintCredits(Graphics2D g2, int x, int y) { if (start >= creditsImage.getHeight()) { start = 0; } if (start + CREDITS_HEIGHT > creditsImage.getHeight()) { int painted = creditsImage.getHeight() - start; PaintUtils.drawAlphaImage(g2, creditsImage, //Image x, y, x + creditsImage.getWidth(), y + painted, //Destination 0, start, creditsImage.getWidth(), creditsImage.getHeight(), getAlpha()); //Source PaintUtils.drawAlphaImage(g2, creditsImage, //Image x, y + painted, x + creditsImage.getWidth(), y + CREDITS_HEIGHT, //Destination 0, 0, creditsImage.getWidth(), CREDITS_HEIGHT - painted, getAlpha()); //Source } else { PaintUtils.drawAlphaImage(g2, creditsImage, //Image x, y, x + creditsImage.getWidth(), y + CREDITS_HEIGHT, //Destination 0, start, creditsImage.getWidth(), start + CREDITS_HEIGHT, getAlpha()); //Source } } /** * Paint the title of the view. * * @param g2 The graphics 2D elements. * @param xStart The x position to start. * @param yStart The y position to start. * @param textColor The text color. */ private void paintTitle(Graphics g2, int xStart, int yStart, Color textColor) { PaintUtils.drawString(g2, core.getApplication().getI18nProperties().getName(), xStart, yStart, fontName, textColor); PaintUtils.fillRect(g2, xStart, yStart + 20, paintWidth, 3, textColor); } /** * Paint the background. * * @param g2 The graphics 2D elements. */ private void paintBackground(Graphics g2) { PaintUtils.fillRect(g2, 0, 0, getWidth(), getHeight(), new Color(0, 0, 0, (int) (getAlpha() * 255))); } /** * Calculate the quit shape. * * @param xStart The x position to start. * @param yStart The y position to start. */ private void calcQuitShape(int xStart, int yStart) { Path2D path = new GeneralPath(); path.append(new Rectangle(0, 0, getWidth(), yStart), false); path.append(new Rectangle(0, yStart, xStart, VIEW_HEIGHT), false); path.append(new Rectangle(xStart + paintWidth, yStart, getWidth() - paintWidth - xStart, VIEW_HEIGHT), false); path.append(new Rectangle(0, yStart + VIEW_HEIGHT, getWidth(), yStart), false); shapes.put("quit", path); } /** * Init the view. * * @param g2 The graphics 2D elements. */ private void initView(Graphics g2) { fontName = SwingUtils.getDefaultFont().deriveFont(24.0f).deriveFont(Font.BOLD); fontInfos = SwingUtils.getDefaultFont().deriveFont(18.0f).deriveFont(Font.PLAIN); computeWidths(g2); createCreditsImage(g2); inited = true; } /** * Compute the widths. * * @param g2 The graphics 2D elements. */ private void computeWidths(Graphics g2) { FontMetrics metrics = getFontMetrics(fontInfos); int maxRight = 0; for (AboutInfo info : model.getInfos()) { info.setLeftWidth(metrics.stringWidth(info.getLeft())); info.setRightWidth(metrics.stringWidth(info.getRight())); if (info.isUrl() || info.isMail()) { info.setRightWidth(info.getRightWidth() + 5); } max = Math.max(max, info.getLeftWidth()); maxRight = Math.max(maxRight, info.getRightWidth()); } metrics = g2.getFontMetrics(fontName); int logoWidth = logo == null ? 0 : logo.getWidth(); paintWidth = Math.max(metrics.stringWidth(core.getApplication().getI18nProperties().getName()), logoWidth + 30 + max + 20 + maxRight); } /** * Create the credits image. * * @param g2 The graphics 2D elements. */ private void createCreditsImage(Graphics g2) { creditsImage = ImageUtils.createCompatibleImage(paintWidth - 30, model.getCredits().size() * CREDIT_HEIGHT, BufferedImage.TRANSLUCENT); Graphics2D gImage = (Graphics2D) creditsImage.getGraphics(); PaintUtils.initHints(gImage); FontMetrics metrics = g2.getFontMetrics(fontInfos); gImage.setFont(fontInfos); int y = CREDIT_HEIGHT; for (String credit : model.getCredits()) { gImage.drawString(credit, (creditsImage.getWidth() - metrics.stringWidth(credit)) / 2, y); y += CREDIT_HEIGHT; } gImage.dispose(); } /** * Return the start position. Called from Trident. * * @return The start position. */ public int getStart() { return start; } /** * Set the start position. Called from Trident. * * @param start The position. + */ public void setStart(int start) { this.start = start; repaint(); } @Override public void appear() { Timeline fadeAnimation = AnimationUtils.createFadeInAnimation(this); AnimationUtils.startsLoopWhenStop(fadeAnimation, timeline); fadeAnimation.play(); } /** * Make disappear the view. */ public void disappear() { timeline.suspend(); Timeline fadeOut = AnimationUtils.createFadeOutAnimation(this); fadeOut.addCallback(new TimelineCallbackAdapter() { @Override public void onTimelineStateChanged(Timeline.TimelineState oldState, Timeline.TimelineState newState, float durationFraction, float timelinePosition) { if (newState == Timeline.TimelineState.DONE) { setVisible(false); } } }); fadeOut.play(); } @Override public void refreshText(LanguageService languageService) { init(); repaint(); } @Override public Component getImpl() { return this; } /** * The mouse controller of the view. */ private final class MouseController extends MouseAdapter { @Override public void mouseClicked(MouseEvent event) { if (shapes.get("quit").contains(event.getPoint())) { disappear(); } else if (shapes.get("url").contains(event.getPoint())) { for (Map.Entry<Rectangle, String> entry : urlRectangles.entrySet()) { if (entry.getKey().contains(event.getPoint())) { DesktopUtils.browse(entry.getValue()); return; } } } else if (shapes.get("license").contains(event.getPoint())) { disappear(); licenseController.getView().display(); } } } /** * The mouse motion controller for the view. */ private final class MouseMotionController extends MouseMotionAdapter { @Override public void mouseMoved(MouseEvent event) { //Perhaps the view is not entirely loaded if(shapes == null){ return; } if (shapes.get("quit").contains(event.getPoint()) || shapes.get("url").contains(event.getPoint()) || shapes.get("license").contains(event.getPoint())) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { setCursor(Cursor.getDefaultCursor()); } } } }