/* * SK's Minecraft Launcher * Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors * Please see LICENSE.txt for license information. */ package com.skcraft.launcher.bootstrap; import lombok.NonNull; import lombok.extern.java.Log; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.atomic.AtomicBoolean; import static com.skcraft.launcher.bootstrap.BootstrapUtils.closeQuietly; import static com.skcraft.launcher.bootstrap.SharedLocale.tr; /** * Swing utility methods. */ @Log public final class SwingHelper { private SwingHelper() { } public static String htmlEscape(String str) { return str.replace(">", ">") .replace("<", "<") .replace("&", "&"); } /** * Shows an popup error dialog, with potential extra details shown either immediately * or available on the dialog. * * @param parentComponent the frame from which the dialog is displayed, otherwise * null to use the default frame * @param message the message to display * @param title the title string for the dialog * @see #showMessageDialog(java.awt.Component, String, String, String, int) for details */ public static void showErrorDialog(Component parentComponent, @NonNull String message, @NonNull String title) { showErrorDialog(parentComponent, message, title, null); } /** * Shows an popup error dialog, with potential extra details shown either immediately * or available on the dialog. * * @param parentComponent the frame from which the dialog is displayed, otherwise * null to use the default frame * @param message the message to display * @param title the title string for the dialog * @param throwable the exception, or null if there is no exception to show * @see #showMessageDialog(java.awt.Component, String, String, String, int) for details */ public static void showErrorDialog(Component parentComponent, @NonNull String message, @NonNull String title, Throwable throwable) { String detailsText = null; // Get a string version of the exception and use that for // the extra details text if (throwable != null) { StringWriter sw = new StringWriter(); throwable.printStackTrace(new PrintWriter(sw)); detailsText = sw.toString(); } showMessageDialog(parentComponent, message, title, detailsText, JOptionPane.ERROR_MESSAGE); } /** * Show a message dialog using * {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}. * * <p>The dialog will be shown from the Event Dispatch Thread, regardless of the * thread it is called from. In either case, the method will block until the * user has closed the dialog (or dialog creation fails for whatever reason).</p> * * @param parentComponent the frame from which the dialog is displayed, otherwise * null to use the default frame * @param message the message to display * @param title the title string for the dialog * @param messageType see {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)} * for available message types */ public static void showMessageDialog(final Component parentComponent, @NonNull final String message, @NonNull final String title, final String detailsText, final int messageType) { if (SwingUtilities.isEventDispatchThread()) { // To force the label to wrap, convert the message to broken HTML String htmlMessage = "<html><div style=\"width: 250px\">" + htmlEscape(message); JPanel panel = new JPanel(new BorderLayout(0, detailsText != null ? 20 : 0)); // Add the main message panel.add(new JLabel(htmlMessage), BorderLayout.NORTH); // Add the extra details if (detailsText != null) { JTextArea textArea = new JTextArea(tr("errorDialog.reportError") + "\n\n" + detailsText); JLabel tempLabel = new JLabel(); textArea.setFont(tempLabel.getFont()); textArea.setBackground(tempLabel.getBackground()); textArea.setTabSize(2); textArea.setEditable(false);; JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setPreferredSize(new Dimension(350, 120)); panel.add(scrollPane, BorderLayout.CENTER); } JOptionPane.showMessageDialog( parentComponent, panel, title, messageType); } else { // Call method again from the Event Dispatch Thread try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { showMessageDialog( parentComponent, message, title, detailsText, messageType); } }); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } /** * Asks the user a binary yes or no question. * * @param parentComponent the component * @param message the message to display * @param title the title string for the dialog * @return whether 'yes' was selected */ public static boolean confirmDialog(final Component parentComponent, @NonNull final String message, @NonNull final String title) { if (SwingUtilities.isEventDispatchThread()) { return JOptionPane.showConfirmDialog( parentComponent, message, title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; } else { // Use an AtomicBoolean to pass the result back from the // Event Dispatcher Thread final AtomicBoolean yesSelected = new AtomicBoolean(); try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { yesSelected.set(confirmDialog(parentComponent, title, message)); } }); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (InvocationTargetException e) { throw new RuntimeException(e); } return yesSelected.get(); } } public static BufferedImage readIconImage(Class<?> clazz, String path) { InputStream in = null; try { in = clazz.getResourceAsStream(path); if (in != null) { return ImageIO.read(in); } } catch (IOException e) { } finally { closeQuietly(in); } return null; } public static void setIconImage(Window frame, Class<?> clazz, String path) { BufferedImage image = readIconImage(clazz, path); if (image != null) { frame.setIconImage(image); } } /** * Focus a component. * * <p>The focus call happens in {@link javax.swing.SwingUtilities#invokeLater(Runnable)}.</p> * * @param component the component */ public static void focusLater(@NonNull final Component component) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (component instanceof JTextComponent) { ((JTextComponent) component).selectAll(); } component.requestFocusInWindow(); } }); } }