// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.updater; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.net.URL; import java.util.Iterator; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.WindowConstants; import javax.swing.text.html.HTMLDocument; import org.infinity.NearInfinity; import org.infinity.gui.LinkButton; import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; /** * Shows information about available updates and providing options how to deal with them. */ public class UpdateCheck extends JDialog { public enum UpdateAction { UPDATE, DOWNLOAD, CANCEL } private final JTextField tfCurrentVersion = new JTextField(); private final JTextField tfCurrentPath = new JTextField(); private final JTextField tfNewVersion = new JTextField(); private final JTextField tfNewDate = new JTextField(); private final JTextField tfNewSize = new JTextField(); private final JButton bDownload = new JButton("Manual download"); private final JButton bCancel = new JButton("Cancel"); private final Listeners listeners = new Listeners(); private final UpdateInfo updateInfo; private UpdateAction retVal = UpdateAction.CANCEL; /** Shows update check dialog and returns the action selected by the user. */ public static UpdateAction showDialog(Window owner, UpdateInfo updateInfo) { UpdateCheck dlg = null; try { try { dlg = new UpdateCheck(owner, updateInfo); dlg.setVisible(true); return dlg.retVal; } catch (NullPointerException e) { JOptionPane.showMessageDialog(owner, "No updates available.", "Check for updates", JOptionPane.INFORMATION_MESSAGE); } } finally { dlg = null; } return UpdateAction.CANCEL; } private UpdateCheck(Window owner, UpdateInfo updateInfo) { super(owner, "New update available", Dialog.ModalityType.APPLICATION_MODAL); if (updateInfo == null) { throw new NullPointerException(); } this.updateInfo = updateInfo; init(); } private Listeners getListeners() { return listeners; } // Returns the currently assigned Configuration object private UpdateInfo getUpdateInfo() { return updateInfo; } private void init() { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setResizable(true); setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); // ESC closes dialog getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE"); getRootPane().getActionMap().put("ESCAPE", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { cancel(); } }); // configuring controls JLabel lCurrentVersion = new JLabel("Current version:"); lCurrentVersion.setFont(lCurrentVersion.getFont().deriveFont(Font.PLAIN)); tfCurrentVersion.setEditable(false); tfCurrentVersion.setFont(lCurrentVersion.getFont()); tfCurrentVersion.setText(NearInfinity.getVersion()); JLabel lCurrentPath = new JLabel("Current path:"); lCurrentPath.setFont(lCurrentPath.getFont().deriveFont(Font.PLAIN)); tfCurrentPath.setEditable(false); tfCurrentPath.setFont(lCurrentPath.getFont()); tfCurrentPath.setText(Utils.getJarFileName(NearInfinity.class)); JLabel lNewVersion = new JLabel("New version:"); lNewVersion.setFont(lNewVersion.getFont().deriveFont(Font.BOLD)); tfNewVersion.setEditable(false); tfNewVersion.setFont(lNewVersion.getFont()); tfNewVersion.setText(getUpdateInfo().getRelease().getVersion()); JLabel lNewDate = new JLabel("Release date:"); lNewDate.setFont(lNewDate.getFont().deriveFont(Font.BOLD)); tfNewDate.setEditable(false); tfNewDate.setFont(lNewDate.getFont()); tfNewDate.setText(getUpdateInfo().getRelease().getTimeStamp().getTime().toString()); JLabel lNewSize = new JLabel("Download size:"); lNewSize.setFont(lNewSize.getFont().deriveFont(Font.BOLD)); tfNewSize.setEditable(false); tfNewSize.setFont(lNewDate.getFont()); int fileSize = -1; try { WindowBlocker.blockWindow(NearInfinity.getInstance(), true); try { fileSize = Utils.getFileSizeUrl(new URL(getUpdateInfo().getRelease().getLink()), Updater.getInstance().getProxy()); } catch (Exception e) { } } finally { WindowBlocker.blockWindow(NearInfinity.getInstance(), false); } if (fileSize > 0) { tfNewSize.setText(String.format("%1$.2f MB", (float)fileSize / 1048576.0f)); } else { tfNewSize.setText("n/a"); } bDownload.addActionListener(getListeners()); bDownload.setEnabled(getUpdateInfo().getRelease().getDownloadLink() != null); bCancel.addActionListener(getListeners()); // creating version overview section JPanel pOverview = new JPanel(new GridBagLayout()); gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pOverview.add(lCurrentVersion, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pOverview.add(tfCurrentVersion, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); pOverview.add(lCurrentPath, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pOverview.add(tfCurrentPath, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(24, 0, 0, 0), 0, 0); pOverview.add(lNewVersion, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(24, 8, 0, 0), 0, 0); pOverview.add(tfNewVersion, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); pOverview.add(lNewDate, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pOverview.add(tfNewDate, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); pOverview.add(lNewSize, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 0), 0, 0); pOverview.add(tfNewSize, gbc); // creating related links section JPanel pLinks = new JPanel(new GridBagLayout()); if (getUpdateInfo().getGeneral().getInformationCount() > 0) { pLinks.setBorder(BorderFactory.createTitledBorder("Related links: ")); for (int i = 0, count = getUpdateInfo().getGeneral().getInformationCount(); i < count; i++) { String text = getUpdateInfo().getGeneral().getInformationName(i); String url = getUpdateInfo().getGeneral().getInformationLink(i); LinkButton lb = new LinkButton(text, url); lb.setToolTipText(url); lb.setFont(lb.getFont().deriveFont(lb.getFont().getSize2D() + 1.0f)); gbc = ViewerUtil.setGBC(gbc, 0, i, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 8, 8), 0, 0); pLinks.add(lb, gbc); } } else { pLinks.setVisible(false); } // creating changelog section JPanel pChangelog = null; if (getUpdateInfo().getRelease().hasChangeLog()) { pChangelog = new JPanel(new GridBagLayout()); JTextPane viewer = new JTextPane(new HTMLDocument()); viewer.setEditable(false); viewer.setBackground(getBackground()); viewer.setContentType("text/html"); StringBuilder sb = new StringBuilder(); sb.append("<html><head>"); sb.append("<style type=\"text/css\">\n"); sb.append("h1 { margin: 5px 5px 0px 5px; padding: 0px; font-family: Verdana,Arial,sans-serif; font-size: medium; }"); sb.append("ul { margin: 5px 25px; font-family: Verdana,Arial,sans-serif; font-size: small; }"); sb.append("li { padding-top: 5px; }"); sb.append("</style></head>"); sb.append("<body>"); sb.append("<h1>").append("Near Infinity ").append(getUpdateInfo().getRelease().getVersion()).append(":</h1>"); sb.append("<ul>"); for (Iterator<String> iter = getUpdateInfo().getRelease().getChangelog().iterator(); iter.hasNext();) { sb.append("<li>").append(iter.next()).append("</li>"); } sb.append("</ul>"); sb.append("</body></html>"); viewer.setText(sb.toString()); viewer.setCaretPosition(0); // prevent viewer to screw up dialog dimensions viewer.setPreferredSize(new Dimension(100, 50)); viewer.setSize(viewer.getPreferredSize()); JScrollPane sp = new JScrollPane(viewer); gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pChangelog.add(sp, gbc); } // putting info sections into tabbed pane JPanel pInfo = new JPanel(new GridBagLayout()); gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); pInfo.add(pOverview, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); pInfo.add(pLinks, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pInfo.add(new JPanel(), gbc); JTabbedPane tabPane = new JTabbedPane(JTabbedPane.TOP); tabPane.addTab("Overview", pInfo); if (pChangelog != null) { tabPane.addTab("Changelog", pChangelog); } else { tabPane.addTab("Changelog", new JPanel()); tabPane.setEnabledAt(1, false); } // creating dialog button section JPanel pButtons = new JPanel(new GridBagLayout()); gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pButtons.add(new JPanel(), gbc); gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pButtons.add(bDownload, gbc); gbc = ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pButtons.add(bCancel, gbc); gbc = ViewerUtil.setGBC(gbc, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pButtons.add(new JPanel(), gbc); // putting all together gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 0, 8), 0, 0); add(tabPane, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 8), 0, 0); add(pButtons, gbc); pack(); setMinimumSize(getPreferredSize()); setLocationRelativeTo(getOwner()); } // Opens download link in default browser private void download() { boolean bRet = false; try { String link = getUpdateInfo().getRelease().getDownloadLink(); if (!Utils.isUrlValid(link)) { link = getUpdateInfo().getRelease().getLink(); if (!Utils.isUrlValid(link)) { String msg = "Download link is not available.\n" + "Please visit the official website to download the latest version of Near Infinity."; JOptionPane.showMessageDialog(getOwner(), msg, "Unexpected error", JOptionPane.ERROR_MESSAGE); return; } } bRet = Utils.openWebPage(new URL(link)); } catch (Exception e) { // registering error while opening web page } if (bRet == false) { JOptionPane.showMessageDialog(getOwner(), "Error opening download link in browser.", "Error", JOptionPane.ERROR_MESSAGE); } retVal = UpdateAction.DOWNLOAD; setVisible(false); } // Closes dialog without triggering actions private void cancel() { retVal = UpdateAction.CANCEL; setVisible(false); } //-------------------------- INNER CLASSES -------------------------- private class Listeners implements ActionListener { public Listeners() {} @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == bDownload) { download(); } else if (e.getSource() == bCancel) { cancel(); } } } }