package ca.sqlpower.swingui;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.http.params.BasicHttpParams;
import org.apache.log4j.Logger;
import ca.sqlpower.util.BrowserUtil;
import ca.sqlpower.util.SQLPowerUtils;
import ca.sqlpower.util.Version;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;
public class SPSUtils {
private static final Logger logger = Logger.getLogger(SPSUtils.class);
/**
* The Key for Multiple select on different Operating System.
*/
public static final int MULTISELECT_MASK;
static{
if (System.getProperty("mrj.version") != null) {
MULTISELECT_MASK = InputEvent.META_DOWN_MASK;
} else {
MULTISELECT_MASK = InputEvent.CTRL_DOWN_MASK;
}
}
/**
* The URL for the SQL Power Architect page.
*/
public static final String SQLP_ARCHITECT_URL = "http://www.sqlpower.ca/page/architect"; //$NON-NLS-1$
/**
* The URL for the SQL Power main page.
*/
public static final String SQLP_URL = "http://www.sqlpower.ca/"; //$NON-NLS-1$
/**
* The URL for the SQL Power forum where users can get help and ask questions.
*/
public static final String FORUM_URL = "http://www.sqlpower.ca/page/enter_forum"; //$NON-NLS-1$
/**
* The URL for the Wabit FAQ page
*/
public static final String WABIT_FAQ_URL = "http://www.sqlpower.ca/page/wabit-faq"; //$NON-NLS-1$
/**
* The URL for the DQGURU FAQ page
*/
public static final String DQGURU_FAQ_URL = "http://www.sqlpower.ca/page/dqguru-faq"; //$NON-NLS-1$
/**
* The URL for the Architect FAQ page
*/
public static final String ARCHITECT_FAQ_URL = "http://www.sqlpower.ca/page/architect-faq"; //$NON-NLS-1$
/**
* The URL for the Wabit getting started page
*/
public static final String WABIT_GS_URL = "http://www.sqlpower.ca/page/wabit-start"; //$NON-NLS-1$
/**
* The URL for the Architect getting started page
*/
public static final String ARCHITECT_GS_URL = "http://www.sqlpower.ca/page/architect-start"; //$NON-NLS-1$
/**
* The URL for the Architect getting started page
*/
public static final String DQGURU_GS_URL = "http://www.sqlpower.ca/page/dqguru-start"; //$NON-NLS-1$
/**
* The URL for the Wabit demo page
*/
public static final String WABIT_DEMO_URL = "http://www.sqlpower.ca/page/wabit-demos"; //$NON-NLS-1$
/**
* The URL for the DQGuru demo page
*/
public static final String DQGURU_DEMO_URL = "http://www.sqlpower.ca/page/dqguru-demos"; //$NON-NLS-1$
/**
* The URL for the Architect demo page
*/
public static final String ARCHITECT_DEMO_URL = "http://www.sqlpower.ca/page/architect-demos"; //$NON-NLS-1$
/**
* The URL for the Wabit user guide page
*/
public static final String WABIT_UG_URL = "http://www.sqlpower.ca/page/wabit-userguide"; //$NON-NLS-1$
/**
* The URL for the Architect user guide page
*/
public static final String ARCHITECT_UG_URL = "http://www.sqlpower.ca/page/architect-userguide"; //$NON-NLS-1$
/**
* The URL for the DQGuru user guide page
*/
public static final String DQGURU_UG_URL = "http://www.sqlpower.ca/page/dqguru-userguide"; //$NON-NLS-1$
/**
* The URL for the Wabit enterprise upgrade
*/
public static final String WABIT_UPGRADE_URL = "http://www.sqlpower.ca/page/wabit-ep"; //$NON-NLS-1$
/**
* The URL for the Wabit enterprise upgrade
*/
public static final String ARCHITECT_UPGRADE_URL = "http://www.sqlpower.ca/page/architect-e"; //$NON-NLS-1$
/**
* The URL for the Wabit enterprise upgrade
*/
public static final String DQGURU_UPGRADE_URL = "http://www.sqlpower.ca/page/dqguru-e"; //$NON-NLS-1$
/**
* The URL for the Wabit premium support
*/
public static final String WABIT_PS_URL = "http://www.sqlpower.ca/page/wabit_support"; //$NON-NLS-1$
/**
* The URL for the Architect premium support
*/
public static final String ARCHITECT_PS_URL = "http://www.sqlpower.ca/page/architect_support"; //$NON-NLS-1$
/**
* The URL for the DQGuru premium support
*/
public static final String DQGURU_PS_URL = "http://www.sqlpower.ca/page/dqguru_support"; //$NON-NLS-1$
public static final Action forumAction = new AbstractAction(Messages.getString("SPSUtils.webSupportActionName"), //$NON-NLS-1$
// Alas this is now static so the size can't be gotten from sprefs...
SPSUtils.createIcon("world","New Project")) { //$NON-NLS-1$ //$NON-NLS-2$
public void actionPerformed(ActionEvent evt) {
try {
BrowserUtil.launch(FORUM_URL);
} catch (IOException e) {
SPSUtils.showExceptionDialogNoReport(getFrameFromActionEvent(evt),
Messages.getString("SPSUtils.couldNotLaunchBrowser"), e); //$NON-NLS-1$
}
}
};
private static JFrame getFrameFromActionEvent(ActionEvent e) {
if (e.getSource() instanceof Component) {
Component c = (Component)e.getSource();
while(c != null) {
if (c instanceof Frame) {
return (JFrame)c;
}
c = (c instanceof JPopupMenu) ? ((JPopupMenu)c).getInvoker() : c.getParent();
}
}
return null;
}
/**
* Short-form convenience method for
* <code>new ArchitectSwingUtils.LabelValueBean(label,value)</code>.
*/
public static LabelValueBean lvb(String label, Object value) {
return new LabelValueBean(label, value);
}
/**
* Useful for combo boxes where you want the user to see the label
* but the code needs the value (only useful when the value's
* toString() method isn't).
*/
public static class LabelValueBean {
String label;
Object value;
public LabelValueBean(String label, Object value) {
this.label = label;
this.value = value;
}
public String getLabel() {
return this.label;
}
public void setLabel(String argLabel) {
this.label = argLabel;
}
public Object getValue() {
return this.value;
}
public void setValue(Object argValue) {
this.value = argValue;
}
/**
* Just returns the label.
*/
public String toString() {
return label;
}
}
/**
* Arrange for an existing JDialog or JFrame to close nicely when the ESC
* key is pressed. Called with an Action, which will become the cancelAction
* of the dialog.
* <p>
* Note: we explicitly close the dialog from this code.
*
* @param w The Window which you want to make cancelable with the ESC key. Must
* be either a JFrame or a JDialog.
* @param cancelAction The action to invoke on cancelation, or null for nothing
* @param disposeOnCancel If true, the window will be disposed after invoking the provided
* action when the ESC key is pressed. Otherwise, the provided action will be invoked,
* but the window won't be closed. If you set this to false, and don't provide an action,
* nothing interesting will happen when ESC is pressed in your dialog.
*/
public static void makeJDialogCancellable(
final Window w,
final Action cancelAction,
final boolean disposeOnCancel) {
JComponent c;
if (w instanceof JFrame) {
c = (JComponent) ((JFrame) w).getRootPane();
} else if (w instanceof JDialog) {
c = (JComponent) ((JDialog) w).getRootPane();
} else {
throw new IllegalArgumentException(
"The window argument has to be either a JFrame or JDialog." + //$NON-NLS-1$
" You provided a " + (w == null ? null : w.getClass().getName())); //$NON-NLS-1$
}
InputMap inputMap = c.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
ActionMap actionMap = c.getActionMap();
inputMap.put(KeyStroke.getKeyStroke("ESCAPE"), "cancel"); //$NON-NLS-1$ //$NON-NLS-2$
actionMap.put("cancel", new AbstractAction() { //$NON-NLS-1$
public void actionPerformed(ActionEvent e) {
if ( cancelAction != null ) {
cancelAction.actionPerformed(e);
}
if (disposeOnCancel){
w.dispose();
}
}
});
}
/**
* Works like {@link #makeJDialogCancellable(Window, Action, boolean)}
* with disposeOnCancel set to true.
*
* @param w The Window to attach the ESC event handler to
* @param cancelAction The action to perform. null is allowed: no custom
* action will be performed, but the dialog will still be disposed on ESC.
*/
public static void makeJDialogCancellable(
final Window w,
final Action cancelAction){
makeJDialogCancellable(w, cancelAction, true);
}
/**
* Returns an ImageIcon with an image from the collection of
* icons in the classpath, or null if the path was invalid. Copied from the Swing
* Tutorial.
*
* @param name The base of the filename from our graphics repository, such as
* "NewTable". See the icons directory.
* @param size Either 16 or 24.
*/
public static ImageIcon createIcon(String name,
String description,
int size) {
return createIcon(name+size, description);
}
/**
* Returns an ImageIcon with an image from the collection of
* icons in the classpath, or null if the path was invalid. Copied from the Swing
* Tutorial.
*
* @param name The base of the filename from our graphics repository, such as
* "NewTable". See the icons directory.
* @param description The description of the icon (maybe not used for anything).
* @return
*/
public static ImageIcon createIcon(String name,
String description) {
String realPath = "/icons/"+name+".png"; //$NON-NLS-1$ //$NON-NLS-2$
logger.debug("Loading resource "+realPath); //$NON-NLS-1$
java.net.URL imgURL = SPSUtils.class.getResource(realPath);
if (imgURL == null) {
realPath = realPath.replace(".png", ".gif"); //$NON-NLS-1$ //$NON-NLS-2$
imgURL = SPSUtils.class.getResource(realPath);
}
if (imgURL != null) {
return new ImageIcon(imgURL, description);
} else {
logger.debug("Couldn't find file: " + realPath); //$NON-NLS-1$
return null;
}
}
public static final FileFilter ARCHITECT_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.architectFileType"), new String[] {"arc", "architect"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
public static final FileFilter TEXT_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.textFileType"), new String[] {"txt"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter SQL_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.sqlFileType"), new String[] {"sql","ddl"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
public static final FileFilter INI_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.iniFileType"), new String[] {"ini"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter EXE_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.exeFileType"), new String[] {"exe"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter JAR_ZIP_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.jarFileType"), new String[] {"jar", "zip"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
public static final FileFilter LOG_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.logFileType"), new String[] {"log"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter XSLT_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.xsltFileType"), new String[] {"xsl", "xslt"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter VELOCITY_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.velocityFileType"), new String[] {"vm"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter XML_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.xmlFileType"), new String[] {"xml"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter PDF_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.pdfFileType"), new String[] {"pdf"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter CSV_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.csvFileType"), new String[] {"csv"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter HTML_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.htmlFileType"), new String[] {"html"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter BATCH_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.batchFileType"), new String[] {"bat"}); //$NON-NLS-1$ //$NON-NLS-2$
public static final FileFilter WABIT_FILE_FILTER =
new FileExtensionFilter(Messages.getString("SPSUtils.wabitFileType"), new String[] {"wabit"}); //$NON-NLS-1$ //$NON-NLS-2$
public static class FileExtensionFilter extends FileFilter {
protected LinkedHashSet<String> extensions;
protected String name;
/**
* Creates a new filter which only accepts directories and
* files whose names end with a dot "." followed by one of the
* given strings.
*
* @param name The name of this filter to show to the user
* @param extensions an array of lowercase filename extensions.
*/
public FileExtensionFilter(String name, String[] extensions) {
this.name = name;
this.extensions = new LinkedHashSet<String>(Arrays.asList(extensions));
}
public String toString() {
StringBuffer s = new StringBuffer();
s.append(name);
s.append(":"); //$NON-NLS-1$
s.append(extensions.toString());
return s.toString();
}
public boolean accept(File f) {
return f.isDirectory() || extensions.contains(getExtension(f));
}
public String getDescription() {
return name;
}
/*
* Get the extension of a file.
*/
public static String getExtension(File f) {
String ext = ""; //$NON-NLS-1$
String s = f.getName();
int i = s.lastIndexOf('.');
if (i > 0 && i < s.length() - 1) {
ext = s.substring(i+1).toLowerCase();
}
return ext;
}
/*
* Get the extension of a filter.
*/
public String getFilterExtension(Integer index) {
List<String> l = new ArrayList<String>(extensions);
int i;
if ( index == null ||
index.intValue() < 0 ||
index.intValue() >= l.size() )
i = 0;
else
i = index.intValue();
if ( l.size() > 0 )
return l.get(i);
return null;
}
}
/**
* Tries very hard to create a JDialog which is owned by the parent
* Window of the given component. However, if the component does not
* have a Window ancestor, or the component has a Window ancestor that
* is not a Frame or Dialog, this method instead creates an unparented
* JDialog which is always-on-top.
* <P>
* This method was shamelessly stolen from the grodbots project,
* http://grodbots.googlecode.com/svn/trunk/src/net/bluecow/robot/RobotUtils.java
*
* @param owningComponent The component that should own this dialog.
* @param title The title for the dialog.
* @return A JDialog that is either owned by the Frame or Dialog ancestor of
* owningComponent, or not owned but set to be alwaysOnTop.
*/
public static JDialog makeOwnedDialog(Component owningComponent, String title) {
Window owner = getWindowInHierarchy(owningComponent);
if (owner instanceof Frame) {
return new JDialog((Frame) owner, title);
} else if (owner instanceof Dialog) {
return new JDialog((Dialog) owner, title);
} else {
JDialog d = new JDialog();
d.setTitle(title);
d.setAlwaysOnTop(true);
return d;
}
}
/**
* Returns the first Window in the hierarchy above or at c,
* or null if c is not contained inside a Window. Different from
* {@link SwingUtilities#getWindowAncestor(Component)} in that
* it checks first if c is a Window. Use this if c could be
* a Window.
*/
public static Window getWindowInHierarchy(Component c) {
if (c instanceof Window) {
return (Window) c;
} else {
return SwingUtilities.getWindowAncestor(c);
}
}
/**
* Displays a dialog box with the given message and exception,
* allowing the user to examine the stack trace, but do NOT generate
* a report back to SQLPower web site. The dialog's
* parent component will be set to null.
*
* @deprecated This method will display a dialog box that is not properly
* parented. Use {@link #showExceptionDialogNoReport(Component, String, Throwable)} instead.
*/
public static JDialog showExceptionDialogNoReport(String string, Throwable ex) {
return displayExceptionDialog(null, string, null, ex);
}
/** Displays a dialog box with the given message and exception,
* returning focus to the given component. Intended for use
* on panels like the CompareDMPanel, so focus works better.
*
* @param parent
* @param message
* @param throwable
*/
public static JDialog showExceptionDialogNoReport(Component parent, String string, Throwable ex) {
return displayExceptionDialog(parent, string, null, ex);
}
/**
* Displays a dialog box with the given message and submessage and exception,
* allowing the user to examine the stack trace, but do NOT generate
* a report back to SQLPower web site.
*
* @param parent The parent component that will own the dialog
* @param message A user visible string that should explain the problem
* @param subMessage A second string to give finer-grained detail to the user
* @param throwable The exception that caused the problem
*/
public static JDialog showExceptionDialogNoReport(Component parent, String message, String subMessage, Throwable throwable) {
return displayExceptionDialog(parent, message, subMessage, throwable);
}
/**
* XXX To get rid of this ugly static variable,
* the Session should handle all errors, and have
* all these methods require an Icon as an argument.
*/
private static ImageIcon masterIcon;
/**
* Sets the default icon on the exception dialogs. This is
* mostly used for when the exception dialogs are handled by
* SPSUtils instead of ASUtils or MMSUtils. The session context
* should set this at creation.
*/
public static void setMasterIcon(ImageIcon masterIcon) {
SPSUtils.masterIcon = masterIcon;
}
/**
* Displays a dialog box with the given message and submessage and exception,
* allowing the user to examine the stack trace, but do NOT generate
* a report back to SQLPower web site.
*
* @param parent The parent component that will own the dialog
* @param message A user visible string that should explain the problem
* @param subMessage A second string to give finer-grained detail to the user
* @param throwable The exception that caused the problem. Should not be null, but this
* method will handle that case gracefully since this is the last line of defense.
*
* @return The JDialog to be displayed with the exception.
*/
private static JDialog displayExceptionDialog(
final Component parent,
final String message,
final String subMessage,
Throwable givenThrowable) {
final Throwable throwable;
if (givenThrowable == null) {
// this is a strange case, but we should handle everything here without blowing up
throwable = new Error("The throwable passed in to the exception handler was null. This is just a placeholder.");
} else {
throwable = givenThrowable;
}
throwable.printStackTrace();
JDialog dialog;
Window owner = parent == null? null: getWindowInHierarchy(parent);
if (owner instanceof JFrame) {
JFrame frame = (JFrame) owner;
dialog = new JDialog(frame, Messages.getString("SPSUtils.errorDialogTitle")); //$NON-NLS-1$
} else if (owner instanceof Dialog) {
dialog = new JDialog((Dialog)owner, Messages.getString("SPSUtils.errorDialogTitle")); //$NON-NLS-1$
} else {
logger.error(
String.format("dialog parent component %s is neither JFrame nor JDialog", owner)); //$NON-NLS-1$
// last desperate attempt to set the icon for the dialog
JFrame frame = new JFrame();
if (masterIcon != null) {
frame.setIconImage(masterIcon.getImage());
}
dialog = new JDialog(frame, Messages.getString("SPSUtils.errorDialogTitle")); //$NON-NLS-1$
}
logger.debug("displayExceptionDialog: showing exception dialog for:", throwable); //$NON-NLS-1$
((JComponent)dialog.getContentPane()).setBorder(
BorderFactory.createEmptyBorder(10, 10, 5, 5));
String exceptionString = SQLPowerUtils.exceptionStackToString(throwable);
JPanel top = new JPanel(new GridLayout(0, 1, 5, 5));
StringBuilder labelText = new StringBuilder();
labelText.append("<html><font color='red' size='+1'>"); //$NON-NLS-1$
labelText.append(message == null ? "Unexpected error" : nlToBR(message)); //$NON-NLS-1$
labelText.append("</font>"); //$NON-NLS-1$
if (subMessage != null) {
labelText.append("<p>"); //$NON-NLS-1$
labelText.append(subMessage);
}
JLabel messageLabel = new JLabel(labelText.toString());
top.add(messageLabel);
JLabel errClassLabel =
new JLabel("<html><b>Exception type</b>: " + nlToBR(throwable.getClass().getName())); //$NON-NLS-1$
top.add(errClassLabel);
String excDetailMessage = throwable.getMessage();
excDetailMessage = trimToClosestNL(excDetailMessage, 100, 25);
if (excDetailMessage != null) {
top.add(new JLabel("<html><b>Detail string</b>: " + nlToBR(excDetailMessage))); //$NON-NLS-1$
if (throwable.getCause() != null) {
Throwable root;
for (root = throwable.getCause(); root.getCause() != null; root = root.getCause()) {
if (root.getMessage() != null) {
String rootCauseMessage = root.getMessage();
rootCauseMessage = trimToClosestNL(rootCauseMessage, 100, 25);
top.add(new JLabel("<html><b>Root Cause</b>: " + nlToBR(rootCauseMessage))); //$NON-NLS-1$
}
}
}
}
final JButton detailsButton = new JButton(Messages.getString("SPSUtils.showExceptionDetailsButton")); //$NON-NLS-1$
final JPanel detailsButtonPanel = new JPanel();
detailsButtonPanel.add(detailsButton);
final JButton forumButton = new JButton(forumAction);
detailsButtonPanel.add(forumButton);
top.add(detailsButtonPanel);
dialog.add(top, BorderLayout.NORTH);
final JScrollPane detailScroller =
new JScrollPane(new JTextArea(exceptionString));
final JPanel messageComponent = new JPanel(new BorderLayout());
messageComponent.add(detailScroller, BorderLayout.CENTER);
messageComponent.setPreferredSize(new Dimension(700, 400));
final JComponent fakeMessageComponent = new JComponent() {
@Override
public Dimension getPreferredSize() {
return new Dimension(700, 0);
}
};
final JDialog finalDialogReference = dialog;
finalDialogReference.add(fakeMessageComponent, BorderLayout.CENTER);
ActionListener detailsAction = new ActionListener() {
boolean showDetails = true;
public void actionPerformed(ActionEvent e) {
if (showDetails) {
finalDialogReference.remove(fakeMessageComponent);
finalDialogReference.add(messageComponent, BorderLayout.CENTER);
detailsButton.setText(Messages.getString("SPSUtils.hideExceptionDetailsButton")); //$NON-NLS-1$
} else /* hide details */ {
finalDialogReference.remove(messageComponent);
finalDialogReference.add(fakeMessageComponent, BorderLayout.CENTER);
detailsButton.setText(Messages.getString("SPSUtils.showExceptionDetailsButton")); //$NON-NLS-1$
}
finalDialogReference.pack();
Rectangle dialogBounds = finalDialogReference.getBounds();
Rectangle screenBounds = finalDialogReference.getGraphicsConfiguration().getBounds();
if ( !screenBounds.contains(dialogBounds) ) {
int x = dialogBounds.x;
int y = dialogBounds.y;
if (screenBounds.x+screenBounds.width < dialogBounds.x + dialogBounds.width){
x = dialogBounds.x - (dialogBounds.x + dialogBounds.width - screenBounds.x - screenBounds.width);
}
if (screenBounds.y+screenBounds.height < dialogBounds.y + dialogBounds.height){
y = dialogBounds.y - (dialogBounds.y + dialogBounds.height - screenBounds.y - screenBounds.height);
}
if (screenBounds.x > x){
x = screenBounds.x;
}
if (screenBounds.y > y){
y = screenBounds.y;
}
finalDialogReference.setLocation(x,y);
}
showDetails = ! showDetails;
}
};
detailsButton.addActionListener(detailsAction);
JButton okButton = new JButton(Messages.getString("SPSUtils.okButton")); //$NON-NLS-1$
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
finalDialogReference.dispose();
finalDialogReference.setVisible(false);
}
});
JPanel bottom = new JPanel();
bottom.add(okButton);
dialog.add(bottom, BorderLayout.SOUTH);
dialog.pack();
dialog.setLocationRelativeTo(owner);
dialog.setVisible(true);
return dialog;
}
/**
* Trims the given message if it's longer than the given limit by finding
* the closest new line within the given offset.
*
* @param msg
* The message to trim. Null is allowed, but obviously no
* trimming will be performed.
* @param msgLimit
* The threshold length for msg. If msg.length() > msgLimit,
* trimming will be performed. Otherwise, no trimming will be
* performed.
* @param offset
* The number of characters beyond msgLimit which can be
* tolerated in the trimmed string. This allows the message to be
* trimmed at the next newline, as long as that newline falls
* within a reasonable distance from the threshold limit.
* @return If msg was shorter than msgLimit, msg is returned. Otherwise, if
* msg was longer than msgLimit, a trimmed version of msg with "..."
* appended will be returned. Finally, if msg was null, null will be
* returned.
*/
private static String trimToClosestNL(String msg, int msgLimit, int offset) {
if (msg == null) return null;
if (msg.length() > msgLimit) {
int lastNL = msg.indexOf("\n", msgLimit - offset); //$NON-NLS-1$
int nextNL = msg.indexOf("\n", msgLimit); //$NON-NLS-1$
int endIndex = lastNL;
if (lastNL < 0 || lastNL > msgLimit + offset) {
endIndex = msgLimit;
} else if ((lastNL >= msgLimit && lastNL <= msgLimit + offset) ||
nextNL < 0 || nextNL > msgLimit + offset){
// use lastNL as endIndex
} else {
if (msgLimit - lastNL > nextNL - msgLimit) {
endIndex = nextNL;
}
}
msg = msg.substring(0, endIndex) + " ..."; //$NON-NLS-1$
}
return msg;
}
/**
* Simple convenience routine to replace all \n's with <br>
* @param s
* @return
*/
static String nlToBR(String s) {
// Do NOT xml-ify the BR tag until Swing's HTML supports this.
logger.debug("String s is " + s); //$NON-NLS-1$
return s.replaceAll("\n", "<br>"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Returns the unqualified name (no package name) of the given object's class.
*
* @param o The object whose class name to extract and return. This argument
* must not be null.
* @return The part of the full class name that follows the last "." character,
* or the whole name if there are no dots (because o's class is in the default
* package).
*/
public static String niceClassName(Object o) {
Class<?> c = o.getClass();
String name = c.getName();
int lastDot = name.lastIndexOf('.');
if (lastDot == -1)
return name;
return name.substring(lastDot + 1);
}
/**
* Shows a file chooser and saves the given Document in the user-selected location.
* If the user chooses to overwrite an existing file, they will be prompted to
* confirm the overwrite.
*
* @param owner The component that will own the file chooser dialog
* @param doc the document to save
* @param filter The filename extension filter
*/
public static boolean saveDocument(Component owner, Document doc, FileExtensionFilter filter) {
JFileChooser fc = new JFileChooser();
fc.setFileFilter(filter);
int returnVal = fc.showSaveDialog(owner);
while (true) {
if (returnVal == JFileChooser.CANCEL_OPTION) {
return false;
} else if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
String fileName = file.getPath();
String fileExt = SPSUtils.FileExtensionFilter.getExtension(file);
if (fileExt.length() == 0) {
file = new File(fileName + "." //$NON-NLS-1$
+ filter.getFilterExtension(new Integer(0)));
}
if (file.exists()) {
int choice = JOptionPane.showOptionDialog(
owner,
Messages.getString("SPSUtils.fileOverwriteConfirmation"), //$NON-NLS-1$
Messages.getString("SPSUtils.confirmOverwriteButton"), //$NON-NLS-1$
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null,
null, null);
boolean wantToOverwrite = (choice == JOptionPane.YES_OPTION);
if (!wantToOverwrite) {
returnVal = fc.showSaveDialog(owner);
continue;
}
}
return writeDocument(doc, file);
}
}
}
/**
* Writes the text of the given document to the given file. Reports any
* exceptions encountered via the {@link #showExceptionDialogNoReport(String, Throwable)}
* dialog.
*
* @param doc The document to save
* @param file The file to save to
* @return True if the save was successful; false otherwise.
*/
public static boolean writeDocument(Document doc, File file) {
PrintWriter out = null;
try {
StringReader sr = new StringReader(doc.getText(0, doc.getLength()));
BufferedReader br = new BufferedReader(sr);
out = new PrintWriter(file);
String s;
while ((s = br.readLine()) != null) {
out.println(s);
}
out.flush();
return true;
} catch (Exception e) {
SPSUtils.showExceptionDialogNoReport(Messages.getString("SPSUtils.couldNotSaveFileError"), e); //$NON-NLS-1$
return false;
} finally {
if (out != null) out.close();
}
}
/**
* This method creates an arrowhead for a line. The formula used is derived
* from solving the system of equations (x2 - x1)^2 + (y2 -y1)^2 = c^2 and
* y1 = m * x1 + n. After solving the equation we get:
* (m^2 + 1) * x1^2 + (-2 * x2 - 2 * y2 * m + 2 * m * n) * x1 +
* (x2 ^ 2 + y2^2 - 2 * y2 * n + n^2 - c^2) = 0.
* We can use this to find roots using the equation:
* -b +/- root(b^2 -4ac) / 2a
*
* @param xHead
* The x position of the head of the line.
* @param yHead
* The y position of the head of the line.
* @param xTail
* The x position of the tail of the line.
* @param yTail
* The y position of the tail of the line.
* @param height
* The height of the arrowhead.
* @param width
* The width of the base of the arrowhead.
* @return A polygon containing the three points for the arrowhead.
*/
public static Polygon createArrowhead(int xHead, int yHead, int xTail, int yTail, int height,
int width) {
Polygon polygon = new Polygon();
polygon.addPoint(xHead, yHead);
if (yHead == yTail) {
if (xHead > xTail) {
polygon.addPoint(xHead - height, yHead - width / 2);
polygon.addPoint(xHead - height, yHead + width / 2);
} else {
polygon.addPoint(xHead + height, yHead - width / 2);
polygon.addPoint(xHead + height, yHead + width / 2);
}
return polygon;
}
if (xHead == xTail) {
if (yHead > yTail) {
polygon.addPoint(xHead - width / 2, yHead - height);
polygon.addPoint(xHead + width / 2, yHead - height);
} else {
polygon.addPoint(xHead - width / 2, yHead + height);
polygon.addPoint(xHead + width / 2, yHead + height);
}
return polygon;
}
double m = (yHead - yTail) / (xHead - xTail);
double n = yHead - m * xHead;
double a = m * m + 1;
double b = -2 * xHead - 2 * yHead * m + 2 * m * n;
double c = xHead * xHead + yHead * yHead - 2 * yHead * n + n * n - height * height;
//Get the root between the head and tail
double xBase = (- b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
if (!((xBase < xHead && xBase > xTail) || (xBase > xHead && xBase < xTail))) {
xBase = (- b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
}
double yBase = m * xBase + n;
logger.debug("The base point is (" + xBase + ", " + yBase + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
double mInv = -1 / m;
double nInv = yBase - mInv * xBase;
a = mInv * mInv + 1;
b = -2 * xBase - 2 * yBase * mInv + 2 * mInv * nInv;
c = xBase * xBase + yBase * yBase - 2 * yBase * nInv + nInv * nInv - (width / 2) * (width /2);
int xPoint = (int)((- b + Math.sqrt(b * b - 4 * a * c)) / (2 * a));
int yPoint = (int)(mInv * xPoint + nInv);
logger.debug(" x is " + xPoint + " y is " + yPoint); //$NON-NLS-1$ //$NON-NLS-2$
polygon.addPoint(xPoint, yPoint);
xPoint = (int)((- b - Math.sqrt(b * b - 4 * a * c)) / (2 * a));
yPoint = (int)(mInv * xPoint + nInv);
polygon.addPoint(xPoint, yPoint);
return polygon;
}
/**
* Updates a potentially-long JMenu with the nth-last items replaced by
* sub-menus. This is done iteratively, so if the menu has lots of items or
* the given height reference is very small, you will end up with an
* arbitrarily-long chain of submenus.
* <p>
* If the menu fits the given window, it will not be modified by this
* method.
*
* @param heightReference
* The size of this window is used to compute where to break up
* the menu. Eventually, this method will also install a listener
* on this window that will reflow the menu when the window
* changes size, but this is not implemented at present.
* @param input
* The JMenu.
*/
public static void breakLongMenu(final Window heightReference, final JMenu input) {
if ( input.getItemCount() <= 0 )
return;
final int windowHeight = heightReference.getSize().height;
final int totalRows = input.getItemCount();
final int preferredHeight = input.getItem(0).getPreferredSize().height;
final int FUDGE = 3; // XXX find a better way to compute this...
int rowsPerSubMenu = (windowHeight/ preferredHeight) - FUDGE;
if ( rowsPerSubMenu < 3 )
rowsPerSubMenu = 3;
if (totalRows <= rowsPerSubMenu) {
return;
}
JMenu parentMenu = input;
JMenu subMenu = new JMenu(Messages.getString("SPSUtils.moreSubmenu")); //$NON-NLS-1$
parentMenu.add(subMenu);
while (input.getItemCount() > rowsPerSubMenu + 1) {
final JMenuItem item = input.getItem(rowsPerSubMenu);
subMenu.add(item); // Note that this removes it from the original menu!
if (subMenu.getItemCount() >= rowsPerSubMenu &&
input.getItemCount() > rowsPerSubMenu + 1 ) {
parentMenu = subMenu;
subMenu = new JMenu(Messages.getString("SPSUtils.moreSubmenu")); //$NON-NLS-1$
parentMenu.add(subMenu);
}
}
/** TODO: Resizing the main window does not change the height of the menu.
* This is left as an exercise for the reader:
* frame.addComponentListener(new ComponentAdapter() {
* @Override
* public void componentResized(ComponentEvent e) {
* JMenu oldMenu = fileMenu;
* // Loop over oldMenu, if JMenu, replace with its elements, recursively...!
* ASUtils.breakLongMenu(fileMenu);
* }
* });
*/
}
/**
* Adjusts the given stroke so its line thickness and dash setup do not
* appear to change regardless of the scale factor in effect. For example, a
* stroke with a line thickness of 1 will take up 1 pixel when drawn at
* viewScale.
*
* @param original
* The original BasicStroke object
* @param viewScale
* The current scale amount for the graphics being drawn in
* @return A new BasicStroke object adjusted to appear the same when drawn
* subject to viewScale as the original looks when drawn subject to the
* identity transform.
*/
public static BasicStroke getAdjustedStroke(BasicStroke original, double viewScale) {
float[] adjustedDashArray = original.getDashArray();
if (adjustedDashArray != null) {
for (int i = 0; i < adjustedDashArray.length; i++) {
adjustedDashArray[i] /= viewScale;
}
}
return new BasicStroke(
(float) (original.getLineWidth() / viewScale),
original.getEndCap(),
original.getLineJoin(),
Math.max(1f, (float) (original.getMiterLimit() / viewScale)),
adjustedDashArray,
original.getDashPhase());
}
/**
* Returns a JPanel containing the given JTree and a JLabel positioned at
* the bottom of the panel containing the SQL Power Logo that when clicked
* on, will open up the SQL Power website in a web browser.
*
* @param tree
* The JTree that will be added to the panel.
* @return A JPanel containing the given JTree and the SQL Power Logo
* branding
*/
public static JPanel getBrandedTreePanel(final JTree tree) {
class ScrollableDelegatePanelClassThingThatsNotAnonymous extends JPanel implements Scrollable {
Scrollable scrollable = tree;
@Override
public Dimension getPreferredSize() {
Dimension realPrefSize = super.getPreferredSize();
JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this);
if (viewport != null) {
realPrefSize.width = Math.max(viewport.getWidth(), realPrefSize.width);
realPrefSize.height = Math.max(viewport.getHeight(), realPrefSize.height);
}
return realPrefSize;
}
public Dimension getPreferredScrollableViewportSize() {
return scrollable.getPreferredScrollableViewportSize();
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return scrollable.getScrollableBlockIncrement(visibleRect,
orientation, direction);
}
public boolean getScrollableTracksViewportHeight() {
return scrollable.getScrollableTracksViewportHeight();
}
public boolean getScrollableTracksViewportWidth() {
return scrollable.getScrollableTracksViewportWidth();
}
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
return scrollable.getScrollableUnitIncrement(visibleRect,
orientation, direction);
}
};
JPanel panel = new ScrollableDelegatePanelClassThingThatsNotAnonymous();
DefaultFormBuilder treeBuilder = new DefaultFormBuilder(new FormLayout("fill:pref:grow", "fill:pref:grow, pref"), panel);
treeBuilder.add(tree);
treeBuilder.nextLine();
JLabel sqlpLabel = getSQLPowerLogoLabel();
treeBuilder.add(sqlpLabel);
return panel;
}
/**
* Gets a JLabel containing the SQL Power Logo that opens up a web browser
* to the SQL Power website when clicked. By default it has a
* {@link Color#WHITE} background, and opaque set to true.
*/
public static JLabel getSQLPowerLogoLabel() {
JLabel sqlpLabel = new JLabel(new ImageIcon(SPSUtils.class.getClassLoader().getResource("ca/sqlpower/swingui/SQLP-90x80.png")));
sqlpLabel.setBackground(Color.WHITE);
sqlpLabel.setOpaque(true);
sqlpLabel.setHorizontalAlignment(SwingConstants.LEFT);
sqlpLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
try {
BrowserUtil.launch(SPSUtils.SQLP_URL);
} catch (IOException e1) {
throw new RuntimeException("Unexpected error in launch", e1); //$NON-NLS-1$
}
}
});
return sqlpLabel;
}
/**
* Modifies the given JSpinner so that when the textfield gains focus, the
* entire text will be selected.
* <p>
* This is a workaround for an existing bug in Swing in which calling
* selectAll() on the TextField of a JSpinner does not select all the text.
* <p>
* The existing bug report is available here: <a
* href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4699955"
* >http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4699955</a>
*
* @param spinner
* The JSpinner instance that you want to make select all text in
* its text field when it gains focus.
*/
public static void makeJSpinnerSelectAllTextOnFocus(JSpinner spinner) {
JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor)spinner.getEditor();
editor.getTextField().addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
if (logger.isDebugEnabled()) {
logger.debug("Focus Gained: " + e);
}
if (e.getSource() instanceof JTextComponent) {
final JTextComponent textComponent = ((JTextComponent)e.getSource());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
textComponent.selectAll();
}
});
}
}
public void focusLost(FocusEvent e) {
if(logger.isDebugEnabled()) {
logger.debug("Focus Lost:" + e);
}
}
});
}
/**
* Runs the given Runnable on the Swing event dispatch thread. If the
* calling thread <i>is</i> the event dispatch thread, doRun is run
* immediately (before this method returns). Otherwise, doRun is appended to
* the end of the Swing event queue.
* <p>
* This method should be particularly handy in event handler code that has
* to deal with the possibility that an event is being received on a thread
* other than the Swing event dispatch thread.
*
* @param doRun
* The doRun run run (the doRun run).
*/
public static void runOnSwingThread(Runnable doRun) {
if (SwingUtilities.isEventDispatchThread()) {
doRun.run();
} else {
SwingUtilities.invokeLater(doRun);
}
}
/**
* Creates a {@link Popup} containing a given {@link JComponent} embedded
* inside a {@link JScrollPane}. Also creates a {@link PopupListenerHandler}
* that handles events dealing with the {@link Popup}. When display, it will
* popup the window at the given location, and it will add scrollbars in the
* right circumstances as well.
*
* @param owningFrame
* The {@link Component} which the popup is being popped up on.
* @param componentToEmbed
* The {@link JComponent} to display.
* @param windowLocation
* The location of where the popup should be displayed.
* @return The {@link PopupListenerHandler} that handles the created
* {@link Popup} window.
*/
public static PopupListenerHandler popupComponent(
final Component owningFrame,
final JComponent componentToEmbed,
final Point windowLocation) {
JScrollPane treeScroll = new JScrollPane(componentToEmbed);
treeScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
treeScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
Point frameLocation = new Point(0, 0);
SwingUtilities.convertPointToScreen(frameLocation, owningFrame);
int popupScreenSpaceY = (windowLocation.y - frameLocation.y);
int maxHeight = (int)(owningFrame.getSize().getHeight() - popupScreenSpaceY);
int width = (int) Math.min(treeScroll.getPreferredSize().getWidth(), owningFrame.getSize().getWidth());
int height = (int) Math.min(treeScroll.getPreferredSize().getHeight(), maxHeight);
treeScroll.setPreferredSize(new java.awt.Dimension(width, height));
double popupWidth = treeScroll.getPreferredSize().getWidth();
int popupScreenSpaceX = (int) (owningFrame.getSize().getWidth() - (windowLocation.x - frameLocation.x));
int x;
if (popupWidth > popupScreenSpaceX) {
x = (int) (windowLocation.x - (popupWidth - popupScreenSpaceX));
} else {
x = windowLocation.x;
}
treeScroll.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.GRAY, Color.GRAY));
JComponent glassPane;
if (owningFrame instanceof JFrame) {
JFrame frame = (JFrame) owningFrame;
if (frame.getGlassPane() == null) {
glassPane = new JPanel();
frame.setGlassPane(glassPane);
} else {
glassPane = (JComponent) frame.getGlassPane();
}
glassPane.setVisible(true);
glassPane.setOpaque(false);
} else {
glassPane = (JComponent) owningFrame;
}
PopupFactory pFactory = new PopupFactory();
final Popup popup = pFactory.getPopup(glassPane, treeScroll, x, windowLocation.y);
return new PopupListenerHandler(popup, glassPane, owningFrame);
}
public static Throwable getRootCause(Throwable t) {
Throwable rootCause = t;
while (rootCause.getCause() != null
&& rootCause != rootCause.getCause()) {
rootCause = rootCause.getCause();
}
return rootCause;
}
private static String convertToHex(byte[] data) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < data.length; i++) {
int halfbyte = (data[i] >>> 4) & 0x0F;
int two_halfs = 0;
do {
if ((0 <= halfbyte) && (halfbyte <= 9)) {
buf.append((char) ('0' + halfbyte));
} else {
buf.append((char) ('a' + (halfbyte - 10)));
}
halfbyte = data[i] & 0x0F;
} while (two_halfs++ < 1);
}
return buf.toString();
}
public static String encodeSha1(String text) {
/*
* Thanks to the olap4j project for this code.
* www.olap4j.org
*/
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e1) {
throw new RuntimeException(e1);
}
}
byte[] sha1hash = new byte[40];
md.update(text.getBytes(), 0, text.length());
sha1hash = md.digest();
return convertToHex(sha1hash);
}
/**
* Checks for a newer version of the product.
*
* @param owner
* A parent for any dialog we create.
* @param version
* The current version of the product.
* @param latestVersionUrl
* A URL to a file location that we can retrieve the latest
* version of this product from. The URL must return some kind of
* XML in the body of the response. The XML must contain the
* following properties.
* <ul>
* <li>currentVersion - The latest version of the product that
* has been released</li>
* <li>downloadUrl - The location of where to download the latest
* version</li>
* <li>releaseNotes - The changes made in the last release</li>
* </ul>
* @param silent
* If false the user will be notified if there are no updates
* available. If true the user will be notified if no updates are
* available. If there is an update available the user will be
* notified regardless of this flag.
* @param stopAutoChecking
* If the user checks the 'stop automatically checking for
* updates' box and this runnable is not null the runnable will
* be called to set the necessary parameters to stop the auto
* check feature when the dialog closes. This value can be null
* if there is no way to stop the auto check.
*/
public static void checkForUpdate(JFrame owner, String productName, Version version,
String latestVersionUrl, boolean silent, boolean setTimeout, final Runnable stopAutoChecking) {
try {
GetMethod request = new GetMethod(latestVersionUrl);
if (setTimeout) {
BasicHttpParams params = new BasicHttpParams();
params.setIntParameter("http.socket.timeout", new Integer(1000));
}
HttpClient connection = new HttpClient();
connection.executeMethod(request);
final Properties results = new Properties();
results.loadFromXML(
new ByteArrayInputStream(
request.getResponseBody()));
Version currentVersion = new Version(
results.getProperty("currentVersion"));
if (currentVersion.compareTo(version) > 0) {
final JDialog dialog = new JDialog(owner, "New " + productName + " version available!");
dialog.setAlwaysOnTop(true);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new MigLayout("fill", "[grow]", "[shrink][grow][shrink][shrink]"));
dialog.setContentPane(panel);
JLabel title = new JLabel("A new version of " + productName + " is available for download.");
title.setFont(title.getFont().deriveFont(16f));
panel.add(
title,
"wrap, gapbottom 10px, center");
JLabel notes = new JLabel(results.getProperty("releaseNotes"));
notes.setBackground(Color.WHITE);
notes.setOpaque(true);
Border gap = BorderFactory.createEmptyBorder(4, 4, 4, 4);
Border blackline = BorderFactory.createLineBorder(Color.black);
Border compound = BorderFactory.createCompoundBorder(blackline, gap);
notes.setBorder(compound);
panel.add(notes, "wrap, center, grow");
final JCheckBox autoCheckCheckBox = new JCheckBox("Stop automatic updates");
if (stopAutoChecking != null) {
panel.add(autoCheckCheckBox, "wrap, left");
}
Box buttons = Box.createHorizontalBox();
JButton downloadButton = new JButton(new AbstractAction("Download Now") {
public void actionPerformed(ActionEvent event) {
try {
BrowserUtil.launch(results.getProperty("downloadUrl"));
} catch (IOException e) {
throw new RuntimeException("Error attempting to launch web browser", e);
} finally {
dialog.dispose();
if (stopAutoChecking != null && autoCheckCheckBox.isSelected()) {
stopAutoChecking.run();
}
}
}
});
JButton cancelButton = new JButton(new AbstractAction("No thanks") {
public void actionPerformed(ActionEvent e) {
dialog.dispose();
if (stopAutoChecking != null && autoCheckCheckBox.isSelected()) {
stopAutoChecking.run();
}
}
});
buttons.add(downloadButton);
buttons.add(cancelButton);
panel.add(buttons, "center");
dialog.pack();
dialog.setLocationRelativeTo(owner);
dialog.setVisible(true);
} else if (!silent) {
JOptionPane.showMessageDialog(
owner,
"No updates available.",
"Update checker",
JOptionPane.INFORMATION_MESSAGE);
}
} catch (Exception ex) {
logger.warn("Failed to check for update.", ex);
if (!silent) {
JOptionPane.showMessageDialog(
owner,
"Failed to check for updates. Are you connected to the internet?",
"Update checker",
JOptionPane.INFORMATION_MESSAGE);
}
}
}
}