All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.numdata.oss.ui.WindowTools Maven / Gradle / Ivy

There is a newer version: 1.22
Show newest version
/*
 * Copyright (c) 2017, Numdata BV, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Numdata nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NUMDATA BV BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.numdata.oss.ui;

import java.awt.*;
import java.awt.Dimension;
import java.awt.event.*;
import java.util.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.border.*;

import com.numdata.oss.*;
import org.jetbrains.annotations.*;

/**
 * This class provides utility methods for windows/dialogs.
 *
 * @author Peter S. Heijnen
 */
@SuppressWarnings( { "MagicConstant", "unused" } )
public class WindowTools
{
	/**
	 * Constant for 'yes' response from user.
	 *
	 * @see #askConfirmationDialog
	 */
	public static final int YES = 1;

	/**
	 * Constant for 'no' response from user.
	 *
	 * @see #askConfirmationDialog
	 */
	public static final int NO = 0;

	/**
	 * Constant for 'cancel' response from user.
	 *
	 * @see #askConfirmationDialog
	 */
	public static final int CANCEL = -1;

	/**
	 * Format of gemeotry argument used by {@link #setGeometry} method.
	 */
	private static final Pattern GEOMETRY_ARGUMENT = Pattern.compile( "^((-?[0-9]+)x(-?[0-9]+))?(([+-])(\\-?[0-9]+)([+-])(\\-?[0-9]+))?$" );
	//                                                              0   12          3           45     6           7     8

	/**
	 * Utility/Application class is not supposed to be instantiated.
	 */
	private WindowTools()
	{
	}

	/**
	 * Request confirmation from user using a dialog offering 'yes' and 'no'
	 * options.
	 *
	 * The message can contain HTML formatting codes.
	 *
	 * @param owner   Parent component (if {@code null}, a default {@link Frame}
	 *                is used).
	 * @param title   Dialog title.
	 * @param message Content message.
	 *
	 * @return {@link #YES} ({@code 1}) if the user selected 'yes'; {@link #NO}
	 * ({@code 0}) if the user selected 'no'; {@link #CANCEL} ({@code -1}) if
	 * the user closed the dialog without making a choice, or an exception
	 * occurred internally.
	 *
	 * @deprecated Use {@link #showConfirmDialog} instead.
	 */
	@Deprecated
	public static int askConfirmationDialog( @Nullable final Component owner, @Nullable final String title, @Nullable final String message )
	{
		int result;

		try
		{
			final int response = JOptionPane.showConfirmDialog( owner, TextTools.plainTextToHTML( message ), title, JOptionPane.YES_NO_CANCEL_OPTION );
			switch ( response )
			{
				case JOptionPane.YES_OPTION:
					result = YES;
					break;

				case JOptionPane.NO_OPTION:
					result = NO;
					break;

				default:
					result = CANCEL;
					break;
			}
		}
		catch ( final Throwable ignored )
		{
			result = CANCEL;
		}

		return result;
	}

	/**
	 * Center the specified window on screen (don't modify size).
	 *
	 * @param window Target window.
	 */
	public static void center( @NotNull final Window window )
	{
		center( window, window.getWidth(), window.getHeight() );
	}

	/**
	 * Center the specified internal frame on it's container (don't modify
	 * size).
	 *
	 * @param window Target window.
	 */
	public static void center( @NotNull final JInternalFrame window )
	{
		final Container parent = window.getParent();
		if ( parent != null )
		{
			final Insets parentInsets = parent.getInsets();

			final int x = parentInsets.left + ( parent.getWidth() - parentInsets.left - parentInsets.right - window.getWidth() ) / 2;
			final int y = parentInsets.top + ( parent.getHeight() - parentInsets.top - parentInsets.bottom - window.getHeight() ) / 2;

			window.setLocation( x, y );
		}
	}

	/**
	 * Center the specified window on screen and give it the specified width and
	 * height.
	 *
	 * Negative values can be used to define a width and/or height relative to
	 * the screen size, e.g. specifying {@code 600} for the width, and {@code
	 * -200} for the height, will set the window width to 600 pixels, and the
	 * height to the screen height minus 200 pixels.
	 *
	 * @param window Target window.
	 * @param width  Set width of target window to this value.
	 * @param height Set height of target window to this value.
	 */
	public static void center( @NotNull final Window window, final int width, final int height )
	{
		final Rectangle screenBounds = getScreenBounds( window );

		final int acualWidth = ( width < 0 ) ? screenBounds.width + width : width;
		final int actualHeight = ( height < 0 ) ? screenBounds.height + height : height;

		window.setBounds( screenBounds.x + ( screenBounds.width - acualWidth ) / 2, screenBounds.y + ( screenBounds.height - actualHeight ) / 2, acualWidth, actualHeight );
	}

	/**
	 * Centers the given window on its parent window or, if it has no parent, on
	 * the screen.
	 *
	 * @param window Window to be centered.
	 */
	public static void centerOnParent( @NotNull final Window window )
	{
		centerOnComponent( window, window.getParent() );
	}

	/**
	 * Centers the given window on the given component. If no component is
	 * given, the window is centered on the screen instead.
	 *
	 * @param window    Window to be centered.
	 * @param component Component to center the window on.
	 */
	public static void centerOnComponent( @NotNull final Window window, @Nullable final Component component )
	{
		final int x;
		final int y;

		if ( component == null )
		{
			final Rectangle screenBounds = getScreenBounds( window );
			x = screenBounds.x + ( screenBounds.width - window.getWidth() ) / 2;
			y = screenBounds.y + ( screenBounds.height - window.getHeight() ) / 2;
		}
		else
		{
			final Point locationOnScreen = component.getLocationOnScreen();
			final int preferredX = locationOnScreen.x + ( component.getWidth() - window.getWidth() ) / 2;
			final int preferredY = locationOnScreen.y + ( component.getHeight() - window.getHeight() ) / 2;

			final Rectangle screenBounds = getScreenBounds( component );
			x = Math.max( screenBounds.x, Math.min( preferredX, screenBounds.x + screenBounds.width - window.getWidth() ) );
			y = Math.max( screenBounds.y, Math.min( preferredY, screenBounds.y + screenBounds.height - window.getHeight() ) );
		}

		window.setLocation( x, y );
	}

	/**
	 * Do our best to close the specified window. There always seem to be issues
	 * with windows not closing reliably, especially when running from a
	 * browser. This method tries to get the window to close in various ways,
	 * hoping that at least one of them works.
	 *
	 * @param window Window to close.
	 */
	public static void close( @NotNull final Window window )
	{
		SwingUtilities.invokeLater( new Runnable()
		{
			@Override
			public void run()
			{
				try
				{
					window.dispose();
				}
				catch ( final Throwable t )
				{ /* ignore */ }

				try
				{
					window.setVisible( false );
				}
				catch ( final Throwable t )
				{ /* ignore */ }
			}
		} );
	}

	/**
	 * Construct (Swing) frame with the specified title, size, and content. This
	 * method will:
	 *
	 * 
    * *
  • Set the default close operation to {@link JFrame#EXIT_ON_CLOSE}.
  • * *
  • Resize the frame to the specified width and height.
  • * *
  • Place frame in the center of the screen.
  • * *
  • Use the specified component as content (optional). This does not need * to be a container; if it is not, the component is added to the default * content pane.
  • * *
* * @param title Frame title ({@code null} => none). * @param width Frame width. * @param height Frame height. * @param content Content component. * * @return Constructed frame (not yet visible). */ public static JFrame createFrame( @Nullable final String title, final int width, final int height, @Nullable final Component content ) { return createFrame( title, -1, -1, width, height, content ); } /** * Construct (Swing) frame with the specified title, size, and content. This * method will: * *
    * *
  • Set the default close operation to {@link JFrame#EXIT_ON_CLOSE}.
  • * *
  • Resize the frame to the specified width and height.

    * Negative values can be used to define a width and/or height relative to * the screen size, e.g. specifying {@code 600} for the width, and {@code * -200} for the height, will set the window width to 600 pixels, and the * height to the screen height minus 200 pixels.
  • * *
  • Place frame in the frame at the requested location. If either * coordinate of this location is set to a negative value, it will be * centered on screen.
  • * *
  • Use the specified component as content (optional). This does not need * to be a container; if it is not, the component is added to the default * content pane.
  • * *
* * @param title Frame title ({@code null} => none). * @param x X location of frame ({@code -1} => center on screen). * @param y Y location of frame ({@code -1} => center on screen). * @param width Frame width. * @param height Frame height. * @param content Content component. * * @return Constructed frame (not yet visible). */ public static JFrame createFrame( @Nullable final String title, final int x, final int y, final int width, final int height, @Nullable final Component content ) { final JFrame result = new JFrame( title ); result.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); if ( content instanceof Container ) { result.setContentPane( (Container)content ); } else if ( content != null ) { final Container contentPane = result.getContentPane(); contentPane.add( content, BorderLayout.CENTER ); } final Rectangle screenBounds = getScreenBounds( result ); final int actualWidth = ( width < 0 ) ? screenBounds.width + width : width; final int actualHeight = ( height < 0 ) ? screenBounds.height + height : height; final int actualX = ( x < 0 ) ? screenBounds.x + ( screenBounds.width - actualWidth ) / 2 : x; final int actualY = ( y < 0 ) ? screenBounds.y + ( screenBounds.height - actualHeight ) / 2 : y; result.setBounds( actualX, actualY, actualWidth, actualHeight ); return result; } /** * Create a progress window. The window centered and made visible. The * caller should close the dialog when appropriate (typically in a {@code * try}...{@code finally} block). * * @param owner Owner component of child window. * @param title Dialog title. * @param message Message to display in content of dialog. * * @return Progress dialog (already visible), caller needs to close it. */ public static JDialog createProgressWindow( @Nullable final Window owner, @Nullable final String title, @Nullable final String message ) { final JPanel contentPane = new JPanel( new BorderLayout() ); contentPane.setBorder( BorderFactory.createEmptyBorder( 16, 16, 16, 16 ) ); contentPane.add( new JLabel( TextTools.plainTextToHTML( message ), JLabel.CENTER ), BorderLayout.CENTER ); final JDialog result; if ( owner instanceof Frame ) { result = new JDialog( (Frame)owner, title, false ); } else if ( owner instanceof Dialog ) { result = new JDialog( (Dialog)owner, title, false ); } else { result = new JDialog( (Frame)null, title, false ); } result.setContentPane( contentPane ); result.setResizable( false ); packAndCenter( result, 250, 100 ); result.setVisible( true ); if ( SwingUtilities.isEventDispatchThread() ) { final RepaintManager repaintManager = RepaintManager.currentManager( result ); repaintManager.addDirtyRegion( result, 0, 0, result.getWidth(), result.getHeight() ); repaintManager.paintDirtyRegions(); } return result; } /** * Pack and center the specified window on screen. * * Note that a small value will be added to the packed window size (8 * pixels) to improve the window appearance in general and fix small layout * errors (which often cause visual disturbance). * * @param window Target window. * * @see Window#pack() */ public static void packAndCenter( @NotNull final Window window ) { packAndCenter( window, 0, 0, 0, 0 ); } /** * Pack and center the specified window on screen. A minimum width and * height can specified to ensure proper window dimensions. * * Note that a small value will be added to the packed window size (8 * pixels) to improve the window appearance in general and fix small layout * errors (which often cause visual disturbance). * * @param window Target window. * @param minWidth Minimum window width. * @param minHeight Minimum window height. * * @see Window#pack() */ public static void packAndCenter( @NotNull final Window window, final int minWidth, final int minHeight ) { packAndCenter( window, minWidth, minHeight, 0, 0 ); } /** * Pack and center the specified window on screen. A minimum width and * height can specified to ensure proper window dimensions. In addition to * this, values to be added to the packed window size can be specified to * improve the window appearance in general and fix small layout errors * (which often cause visual disturbance). * * Negative values can be used to define a width and/or height relative to * the screen size, e.g. specifying {@code 600} for the width, and {@code * -200} for the height, will set the window width to 600 pixels, and the * height to the screen height minus 200 pixels. * * @param window Target window. * @param minWidth Minimum window width. * @param minHeight Minimum window height. * @param maxWidth Maximum window width. * @param maxHeight Maximum window height. * * @see Window#pack() */ public static void packAndCenter( @NotNull final Window window, final int minWidth, final int minHeight, final int maxWidth, final int maxHeight ) { final Rectangle screenBounds = getScreenBounds( window ); window.pack(); int width = window.getWidth(); if ( minWidth != 0 ) { width = Math.max( ( minWidth < 0 ) ? screenBounds.width + minWidth : minWidth, width ); } width = Math.min( ( maxWidth <= 0 ) ? screenBounds.width + maxWidth : maxWidth, width ); int height = window.getHeight(); if ( minHeight != 0 ) { height = Math.max( ( minHeight < 0 ) ? screenBounds.height + minHeight : minHeight, height ); } height = Math.min( ( maxHeight <= 0 ) ? screenBounds.height + maxHeight : maxHeight, height ); window.setBounds( screenBounds.x + ( screenBounds.width - width ) / 2, screenBounds.y + ( screenBounds.height - height ) / 2, width, height ); } /** * Pack and center the specified window on screen when it is shown. This * delayed operation is often useful when dialog content is changed after * calling this method (typically a constructor), or when a dialog may be * shown multiple times with different contents. * * Note that a small value will be added to the packed window size (8 * pixels) to improve the window appearance in general and fix small layout * errors (which often cause visual disturbance). * * @param window Target window. * * @see Window#pack() */ public static void packAndCenterOnShow( @NotNull final Window window ) { packAndCenterOnShow( window, 0, 0 ); } /** * Pack and center the specified window on screen when it is shown. This * delayed operation is often useful when dialog content is changed after * calling this method (typically a constructor), or when a dialog may be * shown multiple times with different contents. * * A minimum width and height can specified to ensure proper window * dimensions. * * Note that a small value will be added to the packed window size (8 * pixels) to improve the window appearance in general and fix small layout * errors (which often cause visual disturbance). * * @param window Target window. * @param minWidth Minimum window width. * @param minHeight Minimum window height. * * @see Window#pack() */ public static void packAndCenterOnShow( @NotNull final Window window, final int minWidth, final int minHeight ) { window.addWindowListener( new WindowAdapter() { @Override public void windowOpened( final WindowEvent e ) { packAndCenter( window, minWidth, minHeight, 0, 0 ); } } ); } /** * Get bounds of screen associated with the specified component. If * possible, screen insets are applied. * * @param component Component to get screen bounds for. * * @return Screen bounds. */ @NotNull public static Rectangle getScreenBounds( @NotNull final Component component ) { final Toolkit toolkit = component.getToolkit(); final Rectangle bounds; final GraphicsConfiguration graphicsConfiguration = component.getGraphicsConfiguration(); if ( graphicsConfiguration != null ) { bounds = graphicsConfiguration.getBounds(); // System.out.println( "GraphicsConfiguration.bounds=" + bounds ); final Insets insets = toolkit.getScreenInsets( graphicsConfiguration ); // System.out.println( "GraphicsConfiguration.insets=" + insets ); bounds.x += insets.left; bounds.y += insets.top; bounds.width -= insets.left + insets.right; bounds.height -= insets.top + insets.bottom; } else { final Dimension screenSize = toolkit.getScreenSize(); System.out.println( "Toolkit.screenSize=" + screenSize ); bounds = new Rectangle( 0, 0, screenSize.width, screenSize.height - 5 ); } return bounds; } /** * Get {@link Window} component containing the specified component or the * component itself, if it is a {@link Window}. * * @param component Component to get window of. * * @return {@link Window} component ({@code component} or ancestor); {@code * null} if component is not (in) a window. */ @Nullable public static Window getWindow( @Nullable final Component component ) { Window result = null; for ( Component comp = component; comp != null; comp = comp.getParent() ) { if ( comp instanceof Window ) { result = (Window)comp; break; } } return result; } /** * Maximize the specified frame. * * @param frame Frame to maximize. */ public static void maximize( @NotNull final Frame frame ) { maximize( frame, getScreenBounds( frame ) ); } /** * Maximize the specified frame. * * @param frame Frame to maximize. * @param regularBounds Bounds of frame in non-maximized state (used as * fallback if the current platform does not support * native frame maximization). */ public static void maximize( @NotNull final Frame frame, @NotNull final Rectangle regularBounds ) { // NOTE: This is needed to maximize the frame while it's not 'realized' yet. // See also: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4464714 if ( !frame.isVisible() ) { frame.pack(); } frame.setBounds( regularBounds ); frame.setExtendedState( Frame.MAXIMIZED_BOTH ); System.out.println( "frame bounds after maximize: " + frame.getBounds() ); } /** * Handle '{@code -geometry}' argument according to X specifications. This * argument has the form: *
	 *   -geometry WIDTHxHEIGHT+XOFF+YOFF
	 * 
* Where {@code WIDTH} and {@code HEIGHT} specify the preferred size of the * application's main window in pixels. * * {@code XOFF} and {@code YOFF} parts are measured in pixels and are used * to specify the distance of the window from the left or right and top and * bottom edges of the screen, respectively. Both types of offsets are * measured from the indicated edge of the screen to the corresponding edge * of the window. The X offset may be specified in the following ways: * *
* *
{@code +XOFF}
The left edge of the window is to be placed * {@code XOFF} pixels in from the left edge of the screen (i.e., the X * coordinate of the window's origin will be {@code XOFF}). {@code XOFF} may * be negative, in which case the window's left edge will be off the screen. *
* *
{@code -XOFF}
The right edge of the window is to be placed * {@code XOFF} pixels in from the right edge of the screen. {@code XOFF} * may be negative, in which case the window's right edge will be off the * screen.
* * The Y offset has similar meanings:
* *
{@code +YOFF}
The top edge of the window is to be {@code * YOFF} pixels below the top edge of the screen (i.e., the Y coordinate of * the window's origin will be {@code YOFF}). {@code YOFF} may be negative, * in which case the window's top edge will be off the screen.
* *
{@code -YOFF}
The bottom edge of the window is to be {@code * YOFF} pixels above the bottom edge of the screen. {@code YOFF} may be * negative, in which case the window's bottom edge will be off the screen. *
* *
* * Offsets must be given as pairs; in other words, in order to specify * either {@code XOFF} or {@code YOFF} both must be present. Windows can be * placed in the four corners of the screen using the following * specifications: * * * * * * * * * * * *
{@code +0+0}upper left hand corner.
{@code -0+0}upper right hand corner.
{@code -0-0}right hand corner.
{@code +0-0}lower left hand corner.
* * @param geometry Geometry specification ({@code null} or 0-length * string is ignored). * @param windowBounds Window bounds to set according to geometry (should be * initialized to screen bounds). * * @return {@code true} if geometry was specified and applied; {@code false} * if no geometry was specified; * * @throws IllegalArgumentException if the goemetry was badly formatted. * @throws NullPointerException if {@code windowBounds} is {@code null}. */ public static boolean setGeometry( @Nullable final String geometry, @NotNull final Rectangle windowBounds ) { final boolean result; if ( ( geometry != null ) && !geometry.isEmpty() ) { final Matcher matcher = GEOMETRY_ARGUMENT.matcher( geometry ); if ( !matcher.matches() ) { throw new IllegalArgumentException( "badly formatted geometry argument '" + geometry + '\'' ); } if ( matcher.group( 4 ) != null ) { final boolean xNeg = "-".equals( matcher.group( 5 ) ); final int xOff = Integer.parseInt( matcher.group( 6 ) ); final boolean yNeg = "-".equals( matcher.group( 7 ) ); final int yOff = Integer.parseInt( matcher.group( 8 ) ); windowBounds.x = xNeg ? windowBounds.width - xOff : xOff; windowBounds.y = yNeg ? windowBounds.height - yOff : yOff; } if ( ( matcher.group( 1 ) != null ) ) { windowBounds.width = Integer.parseInt( matcher.group( 2 ) ); windowBounds.height = Integer.parseInt( matcher.group( 3 ) ); } result = true; } else { result = false; } return result; } /** * Show information message dialog. * * The message can contain HTML formatting codes. * * @param owner Parent component (if {@code null}, a default {@link Frame} * is used). * @param title Dialog title. * @param message Content message. */ public static void showInfoDialog( @Nullable final Component owner, @Nullable final String title, @Nullable final String message ) { showMessageDialog( owner, title, message, JOptionPane.INFORMATION_MESSAGE, 0 ); } /** * Show error message dialog based on the specified exception. The error is * also printed on {@link System#err}. * * @param owner Parent component (if {@code null}, a default {@link * Frame} is used). * @param throwable Throwable to get title and content (stack trace) * from. * @param stopTraceAt Stop stack trace when this class is encountered * ({@code null} to always show whole stack). */ public static void showErrorDialog( @Nullable final Component owner, @NotNull final Throwable throwable, @Nullable final Class stopTraceAt ) { showErrorDialog( owner, null, throwable, stopTraceAt ); } /** * Show error message dialog based on the specified exception. The error is * also printed on {@link System#err}. * * @param owner Parent component (if {@code null}, a default {@link * Frame} is used). * @param title Dialog title ({@code null} to use exception message, * if any). * @param throwable Throwable to get content (stack trace) from (may be * {@code null} to use title). * @param stopTraceAt Stop stack trace when this class is encountered * ({@code null} to always show whole stack). */ public static void showErrorDialog( @Nullable final Component owner, @Nullable final String title, @Nullable final Throwable throwable, @Nullable final Class stopTraceAt ) { String actualTitle = title; if ( ( actualTitle == null ) && ( throwable != null ) ) { actualTitle = throwable.getLocalizedMessage(); if ( actualTitle == null ) { actualTitle = throwable.toString(); } } final String message = ( throwable == null ) ? actualTitle : ( "
" + StackTools.getStackTrace( throwable, stopTraceAt, true ) );

		showErrorDialog( owner, actualTitle, message );
	}

	/**
	 * Show error message dialog. The error is also printed on {@link
	 * System#err}.
	 *
	 * The message can contain HTML formatting codes.
	 *
	 * @param owner   Parent component (if {@code null}, a default {@link Frame}
	 *                is used).
	 * @param title   Dialog title.
	 * @param message Content message.
	 */
	public static void showErrorDialog( @Nullable final Component owner, @Nullable final String title, @Nullable final String message )
	{
		System.err.println( TextTools.getFixed( "----[ " + title + ']', 80, false, '-' ) );
		System.err.println( message );
		System.err.println( TextTools.getFixed( 80, '-' ) );

		showMessageDialog( owner, title, TextTools.plainTextToHTML( message ), JOptionPane.ERROR_MESSAGE, 0 );
	}

	/**
	 * Show warning message dialog.
	 *
	 * The message can contain HTML formatting codes.
	 *
	 * @param owner   Parent component (if {@code null}, a default {@link Frame}
	 *                is used).
	 * @param title   Dialog title.
	 * @param message Content message.
	 */
	public static void showWarningDialog( @Nullable final Component owner, @Nullable final String title, @Nullable final String message )
	{
		showMessageDialog( owner, title, TextTools.plainTextToHTML( message ), JOptionPane.WARNING_MESSAGE, 0 );
	}

	/**
	 * Show message dialog with optional timeout after which the message dialog
	 * is closed automatically.
	 *
	 * The message can contain HTML formatting codes.
	 *
	 * @param owner       Parent component (if {@code null}, a default {@link
	 *                    Frame} is used).
	 * @param title       Dialog title.
	 * @param message     Content message.
	 * @param messageType Type of message (see {@link JOptionPane}).
	 * @param timeout     Time in milliseconds after which the dialog should be
	 *                    closed automatically (<= 0 to disable this feature).
	 */
	public static void showMessageDialog( @Nullable final Component owner, @Nullable final String title, @Nullable final String message, final int messageType, final int timeout )
	{
		try
		{
			if ( timeout > 0 )
			{
				showTimedMessageDialog( owner, null, null, null, message, timeout );

			}
			else
			{
				JOptionPane.showMessageDialog( owner, message, title, messageType );
			}
		}
		catch ( final Throwable t )
		{
			/* ignore exceptions */
		}
	}

	/**
	 * Show message dialog that is closed automatically after the specified
	 * timeout.
	 *
	 * The message can contain HTML formatting codes.
	 *
	 * @param owner      Parent component (if {@code null}, a default {@link
	 *                   Frame} is used).
	 * @param font       Font to use ({@code null} => default).
	 * @param foreground Foreground color to use ({@code null} => default).
	 * @param background Background color to use ({@code null} => default).
	 * @param message    Content message.
	 * @param timeout    Time in milliseconds after which the dialog should be
	 *                   closed automatically (<= 0 never show dialog).
	 */
	public static void showTimedMessageDialog( @Nullable final Component owner, @Nullable final Font font, @Nullable final Color foreground, @Nullable final Color background, @Nullable final String message, final int timeout )
	{
		if ( timeout > 0 )
		{
			final JDialog dialog = createUndecoratedMessageDialog( owner, true, font, foreground, background, message );

			final Timer timer = new Timer( timeout, new ActionListener()
			{
				@Override
				public void actionPerformed( final ActionEvent event )
				{
					dialog.dispose();
				}
			} );

			timer.start();
			dialog.setVisible( true );
			timer.stop();
			dialog.dispose();
		}
	}

	/**
	 * Create an undecorated message dialog. The returned dialog is not yet
	 * visible.
	 *
	 * The message can contain HTML formatting codes.
	 *
	 * @param owner      Parent component (if {@code null}, a default {@link
	 *                   Frame} is used).
	 * @param modal      Create modal dialog vs. dialog that allows access to
	 *                   other windows.
	 * @param font       Font to use ({@code null} => default).
	 * @param foreground Foreground color to use ({@code null} => default).
	 * @param background Background color to use ({@code null} => default).
	 * @param message    Content message.
	 *
	 * @return Dialog that was created.
	 */
	public static JDialog createUndecoratedMessageDialog( @Nullable final Component owner, final boolean modal, @Nullable final Font font, @Nullable final Color foreground, @Nullable final Color background, @Nullable final String message )
	{
		final JDialog dialog = ( owner instanceof Dialog ) ? new JDialog( (Dialog)owner, modal ) : new JDialog( (Frame)owner, modal );
		dialog.setUndecorated( true );
		dialog.setResizable( false );

		final JLabel label = new JLabel( TextTools.plainTextToHTML( message ) );
		if ( font != null )
		{
			label.setFont( font );
		}

		if ( foreground != null )
		{
			label.setForeground( foreground );
		}

		final JPanel contentPane = new JPanel( new BorderLayout() );
		if ( background != null )
		{
			contentPane.setBackground( background );
		}

		contentPane.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createBevelBorder( BevelBorder.RAISED ), BorderFactory.createEmptyBorder( 16, 16, 16, 16 ) ) );
		contentPane.setOpaque( true );
		contentPane.add( label, BorderLayout.CENTER );
		dialog.setContentPane( contentPane );

		packAndCenter( dialog, 250, 100 );
		return dialog;
	}

	/**
	 * Shows a dialog with the specified attributes, using {@link JOptionPane}.
	 * Contrary to the equivalent method in {@code JOptionPane}, this method
	 * performs localization of button texts using its own resource bundle. By
	 * providing a locale, the dialog's buttons can be properly localized.
	 *
	 * @param locale      Locale to be used.
	 * @param parent      Parent component.
	 * @param message     Message to be shown.
	 * @param title       Title of the dialog.
	 * @param optionType  Options that the user may choose from.
	 * @param messageType Kind of message.
	 *
	 * @return Option selected by the user.
	 *
	 * @see JOptionPane#showConfirmDialog(Component, Object, String, int, int)
	 */
	public static int showConfirmDialog( @NotNull final Locale locale, @Nullable final Component parent, @Nullable final String message, @Nullable final String title, final int optionType, final int messageType )
	{
		final ResourceBundle res = ResourceBundleTools.getBundle( WindowTools.class, locale );

		final String[] options;
		switch ( optionType )
		{
			case JOptionPane.YES_NO_OPTION:
				options = new String[] { res.getString( "yes" ), res.getString( "no" ) };
				break;

			case JOptionPane.YES_NO_CANCEL_OPTION:
				options = new String[] { res.getString( "yes" ), res.getString( "no" ), res.getString( "cancel" ) };
				break;

			case JOptionPane.OK_CANCEL_OPTION:
				options = new String[] { res.getString( "ok" ), res.getString( "cancel" ) };
				break;

			default:
				options = null;
		}

		return JOptionPane.showOptionDialog( parent, message, title, optionType, messageType, null, options, null );
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy