/**
* ThisTime 2.0 (2008-03-30)
* Copyright 2007 Zach Scrivena
* zachscrivena@gmail.com
* http://thistime.sourceforge.net/
*
* Simple clock and timer program.
*
* TERMS AND CONDITIONS:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package thistime;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
/**
* Represent an "About" form.
*/
class About
extends JFrame
implements Observer
{
/** is the new release checker running? */
private boolean checkerRunning;
/** downloader for "latest release" information */
private final WebGet webget;
/**
* Constructor.
*
* @param iconImage
* program icon image to be used by this "About" form
*/
About(
final Image iconImage)
{
/*********************
* INITIALIZE FIELDS *
*********************/
checkerRunning = false;
webget = new WebGet();
webget.addObserver(this);
/******************************
* INITIALIZE FORM COMPONENTS *
******************************/
initComponents();
/***************************
* CONFIGURE FORM SETTINGS *
***************************/
setTitle("About - " + ThisTime.NAME);
setIconImage(iconImage);
try
{
aboutText.setText(ThisTime.DESCRIPTION + "\n\n" +
ResourceManipulator.resourceAsString("/thistime/resources/about_text.txt"));
}
catch (Exception e)
{
aboutText.setText(ThisTime.DESCRIPTION);
SwingManipulator.showErrorDialog(
null,
getTitle(),
"(INTERNAL) Failed to read \"About\" text from JAR file.");
}
aboutText.setToolTipText("About " + ThisTime.NAME);
aboutText.setCaretPosition(0);
aboutText.setFont(new Font(
Font.DIALOG,
Font.PLAIN,
aboutText.getFont().getSize() - 2));
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
closeForm();
}
});
/* button: "Check for New Release" */
checkButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
checkForNewRelease();
}
});
/* button: "Visit Homepage" */
homeButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(new URI(ThisTime.HOMEPAGE));
}
catch (Exception ex)
{
/* ignore */
}
}
}
});
/* button: "Report Bug" */
bugButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(new URI(ThisTime.BUG_REPORT));
}
catch (Exception ex)
{
/* ignore */
}
}
}
});
/* button: "OK" */
closeButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
closeForm();
}
});
/* progress bar */
progress.setStringPainted(true);
progress.setVisible(false);
/* key binding: ESCAPE key */
aboutPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE_CLOSE_BUTTON");
aboutPane.getActionMap().put("ESCAPE_CLOSE_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
closeButton.doClick();
}
});
/* center form on screen */
setLocationRelativeTo(null);
}
/**
* Close the "About" form.
*/
private void closeForm()
{
if (checkerRunning)
{
webget.cancel();
}
setVisible(false);
}
/**
* Check for a new release of the program.
* This method is synchronized.
*/
private synchronized void checkForNewRelease()
{
/* check if the checker is already running */
if (checkerRunning)
{
return;
}
checkerRunning = true;
/* disable "Check for New Release" button */
checkButton.setEnabled(false);
progress.setIndeterminate(true);
progress.setString("Waiting");
progress.setVisible(true);
/* start worker thread to check for new release */
final Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
/* URL for "latest release" information */
URL url = null;
try
{
url = new URL(ThisTime.LATEST_RELEASE_URL);
}
catch (Exception e)
{
SwingManipulator.showWarningDialog(About.this, getTitle(),
"(INTERNAL) Invalid URL for retrieval.");
restoreInterface();
return;
}
final StringBuilder contents = new StringBuilder();
final boolean success = webget.get(url, "latest release information", contents);
if (!success)
{
restoreInterface();
return;
}
final Matcher m = Pattern.compile("(?s)" +
Pattern.quote("<version>") + "(.*)" + Pattern.quote("</version>") + ".*" + /* 1: version */
Pattern.quote("<date>") + "(.*)" + Pattern.quote("</date>") + ".*" + /* 2: date */
Pattern.quote("<link>") + "(.*)" + Pattern.quote("</link>") + ".*" + /* 3: link */
Pattern.quote("<about>") + "(.*)" + Pattern.quote("</about>")) /* 4: about text */
.matcher(contents);
if (m.find())
{
final String version = m.group(1);
final String date = m.group(2);
final String link = m.group(3);
final String about = m.group(4);
if (ThisTime.DATE.compareTo(date) < 0)
{
/* new release is available */
final int choice = SwingManipulator.showOptionTextDialog(About.this,
"A new release of " + ThisTime.NAME + " is available",
ThisTime.NAME + " " + version + " (" + date +
") is available for download. " +
((Double.parseDouble(ThisTime.VERSION) < Double.parseDouble(version)) ?
"This is a major update and is strongly recommended." :
"This is a minor update and is recommended if you are currently experiencing difficulties.") +
"\n\n" + about,
8,
"Check for New Release - " + getTitle(),
JOptionPane.DEFAULT_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null,
new String[] {"Download Now", "Cancel"},
0);
/* download now */
if ((choice == 0) && Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(new URI(link));
}
catch (Exception ex)
{
/* ignore */
}
}
}
else
{
/* this release is up-to-date */
SwingManipulator.showInfoDialog(About.this,
"Check for New Release - " + getTitle(),
"No new release",
"This release of " + ThisTime.NAME + " " + ThisTime.VERSION + " (" +
ThisTime.DATE + ") is already up-to-date.", 5);
}
}
else
{
/* unable to parse */
SwingManipulator.showWarningDialog(About.this,
"Check for New Release - " + getTitle(),
ThisTime.NAME + " is unable to parse the information retrieved from the " +
ThisTime.NAME + " website; please try again later. If the problem persists, please visit the homepage manually.");
}
restoreInterface();
}
/**
* Restore interface before returning.
*/
private void restoreInterface()
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
checkButton.setEnabled(true);
progress.setVisible(false);
checkerRunning = false;
}
});
}
});
t.start();
}
/**
* Respond to an observation event created by webget.
*
* @param o
* observable object generating the event
* @param arg
* description of the observation event
*/
@Override
public void update(
Observable o,
Object arg)
{
final WebGet.Observation obs = (WebGet.Observation) arg;
switch (obs.observationType)
{
case PROGRESS_TEXT:
/* progress update with text only */
SwingManipulator.updateProgressBar(progress, (String) obs.value, -1);
break;
case PROGRESS_TEXT_PERCENT:
/* progress update with text and percentage */
SwingManipulator.updateProgressBar(progress, (String) ((Object[]) obs.value)[0], (Integer) ((Object[]) obs.value)[1]);
break;
case COMPLETED:
/* operation completed */
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
progress.setVisible(false);
}
});
break;
case SIZE:
/* obtained size of remote resource */
/* ignore */
break;
case ERROR:
/* fatal error has occurred; operation aborted */
SwingManipulator.showErrorDialog(About.this, "Check for New Release - " + getTitle(),
"\"" + ((String) obs.value) + "\"\n" +
ThisTime.NAME + " is currently unable to download information about the latest release from the " +
ThisTime.NAME + " website; please try again later. If the problem persists, please visit the homepage manually.");
break;
case WARNING:
/* warning issued; can still proceed with operation */
SwingManipulator.showWarningDialog(About.this, "Check for New Release - " + getTitle(),
"\"" + ((String) obs.value) + "\"\n" +
ThisTime.NAME + " will try to continue checking for a new release.");
break;
}
}
/***************************
* NETBEANS-GENERATED CODE *
***************************/
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents()
{
title = new javax.swing.JLabel();
aboutPane = new javax.swing.JScrollPane();
aboutText = new javax.swing.JTextArea();
closeButton = new javax.swing.JButton();
buttonsPanel = new javax.swing.JPanel();
checkButton = new javax.swing.JButton();
homeButton = new javax.swing.JButton();
bugButton = new javax.swing.JButton();
progress = new javax.swing.JProgressBar();
setResizable(false);
title.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
title.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/splashscreen.png"))); // NOI18N
aboutText.setColumns(20);
aboutText.setEditable(false);
aboutText.setFont(aboutText.getFont());
aboutText.setLineWrap(true);
aboutText.setRows(5);
aboutText.setTabSize(4);
aboutText.setWrapStyleWord(true);
aboutPane.setViewportView(aboutText);
closeButton.setMnemonic('C');
closeButton.setText("Close");
closeButton.setNextFocusableComponent(closeButton);
buttonsPanel.setLayout(new java.awt.GridLayout(1, 0));
checkButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/lightning.png"))); // NOI18N
checkButton.setMnemonic('N');
checkButton.setText("<html>Check for<br /><u>N</u>ew Release</html>");
checkButton.setIconTextGap(8);
checkButton.setNextFocusableComponent(checkButton);
buttonsPanel.add(checkButton);
homeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/house.png"))); // NOI18N
homeButton.setMnemonic('H');
homeButton.setText("<html>Visit<br /><u>H</u>omepage</html>");
homeButton.setIconTextGap(8);
homeButton.setNextFocusableComponent(homeButton);
buttonsPanel.add(homeButton);
bugButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/bug.png"))); // NOI18N
bugButton.setMnemonic('B');
bugButton.setText("<html>Report<br /><u>B</u>ug</hmtl>");
bugButton.setIconTextGap(8);
bugButton.setNextFocusableComponent(bugButton);
buttonsPanel.add(bugButton);
progress.setFont(progress.getFont().deriveFont(progress.getFont().getSize()-2f));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(buttonsPanel, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
.addComponent(aboutPane, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
.addComponent(title, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(progress, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE)
.addComponent(closeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(title)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(aboutPane, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progress, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(closeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 27, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane aboutPane;
private javax.swing.JTextArea aboutText;
private javax.swing.JButton bugButton;
private javax.swing.JPanel buttonsPanel;
private javax.swing.JButton checkButton;
private javax.swing.JButton closeButton;
private javax.swing.JButton homeButton;
private javax.swing.JProgressBar progress;
private javax.swing.JLabel title;
// End of variables declaration//GEN-END:variables
}