/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.tools;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import com.rapidminer.RapidMiner;
import com.rapidminer.gui.ApplicationFrame;
import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.look.fc.Bookmark;
import com.rapidminer.gui.look.fc.BookmarkIO;
import com.rapidminer.gui.tools.components.ToolTipWindow;
import com.rapidminer.gui.tools.components.ToolTipWindow.TipProvider;
import com.rapidminer.gui.tools.components.ToolTipWindow.TooltipLocation;
import com.rapidminer.gui.tools.dialogs.ConfirmDialog;
import com.rapidminer.gui.tools.dialogs.ErrorDialog;
import com.rapidminer.gui.tools.dialogs.ExtendedErrorDialog;
import com.rapidminer.gui.tools.dialogs.InputDialog;
import com.rapidminer.gui.tools.dialogs.InputValidator;
import com.rapidminer.gui.tools.dialogs.LongMessageDialog;
import com.rapidminer.gui.tools.dialogs.MessageDialog;
import com.rapidminer.gui.tools.dialogs.RepositoryEntryInputDialog;
import com.rapidminer.gui.tools.dialogs.ResultViewDialog;
import com.rapidminer.gui.tools.dialogs.SelectionInputDialog;
import com.rapidminer.gui.tools.syntax.SyntaxStyle;
import com.rapidminer.gui.tools.syntax.SyntaxUtilities;
import com.rapidminer.gui.tools.syntax.TextAreaDefaults;
import com.rapidminer.gui.tools.syntax.Token;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.ports.Port;
import com.rapidminer.operator.ports.Ports;
import com.rapidminer.tools.FileSystemService;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.ParameterService;
import com.rapidminer.tools.ParentResolvingMap;
import com.rapidminer.tools.StringColorMap;
import com.rapidminer.tools.SystemInfoUtilities;
import com.rapidminer.tools.SystemInfoUtilities.OperatingSystem;
import com.rapidminer.tools.Tools;
import com.rapidminer.tools.plugin.Plugin;
import com.rapidminer.tools.usagestats.ActionStatisticsCollector;
/**
* This helper class provides some static methods and properties which might be useful for several
* GUI classes. These methods include
* <ul>
* <li>the creation of gradient paints</li>
* <li>displaying (simple) error messages</li>
* <li>creation of file chosers</li>
* <li>creation of text panels</li>
* <li>escaping HTML messages</li>
* </ul>
*
* @author Ingo Mierswa
*/
public class SwingTools {
/**
* A container class for results generated by an implementation of {@link ResultRunnable}.
*/
private static class ResultContainer<T> {
public T value;
}
/**
* A simple interface which has a run method that returns a result.
*
* @since 6.5.0
*
*/
public static interface ResultRunnable<T> {
T run();
}
/** whether we are on a Mac or not */
private static final boolean IS_MAC = SystemInfoUtilities.getOperatingSystem() == OperatingSystem.OSX;
/** Defines the maximal length of characters in a line of the tool tip text. */
private static final int TOOL_TIP_LINE_LENGTH = 100;
/** Defines the extra height for each row in a table. */
public static final int TABLE_ROW_EXTRA_HEIGHT = 4;
/**
* Defines the extra height for rows in a table with components. If an {@link ExtendedJTable} is
* used, this amount can be added additionally to the amount of {@link #TABLE_ROW_EXTRA_HEIGHT}
* which is already added in the constructor.
*/
public static final int TABLE_WITH_COMPONENTS_ROW_EXTRA_HEIGHT = 10;
/** Some color constants for Java Look and Feel. */
public static final Color DARKEST_YELLOW = new Color(250, 219, 172);
/** Some color constants for Java Look and Feel. */
public static final Color DARK_YELLOW = new Color(250, 226, 190);
/** Some color constants for Java Look and Feel. */
public static final Color LIGHT_YELLOW = new Color(250, 233, 207);
/** Some color constants for Java Look and Feel. */
public static final Color LIGHTEST_YELLOW = new Color(250, 240, 225);
/** Some color constants for Java Look and Feel. */
public static final Color TRANSPARENT_YELLOW = new Color(255, 245, 230, 190);
/** Some color constants for Java Look and Feel. */
public static final Color VERY_DARK_BLUE = new Color(172, 172, 212);
/** Some color constants for Java Look and Feel. */
public static final Color DARK_GREEN = new Color(0, 130, 50);
/** Some color constants for Java Look and Feel. */
public static final Color DARKEST_BLUE = new Color(182, 202, 242);
/** Some color constants for Java Look and Feel. */
public static final Color DARK_BLUE = new Color(199, 213, 242);
/** Some color constants for Java Look and Feel. */
public static final Color LIGHT_BLUE = new Color(216, 224, 242);
/** Some color constants for Java Look and Feel. */
public static final Color LIGHTEST_BLUE = new Color(233, 236, 242);
/** Some color constants for Java Look and Feel. */
public static final Color FAINT_YELLOW = new Color(250, 240, 225, 150);
/** Some color constants for Java Look and Feel. */
public static final Color FAINTER_YELLOW = new Color(255, 245, 230, 50);
/** Some color constants for Java Look and Feel. */
public static final Color FAINT_BLUE = new Color(225, 230, 235, 150);
/** Some color constants for Java Look and Feel. */
public static final Color FAINTER_BLUE = new Color(230, 235, 240, 50);
/**
* The RapidMiner orange color.
*
* @deprecated since RapidMiner 6.0, replaced by RAPIDMINER_ORANGE
*/
@Deprecated
public static final Color RAPID_I_ORANGE = new Color(242, 146, 0);
/**
* The RapidMiner brown color.
*
* @deprecated since RapidMiner 6.0, replaced by RAPIDMINER_GRAY
*/
@Deprecated
public static final Color RAPID_I_BROWN = new Color(97, 66, 11);
/**
* The RapidMiner beige color.
*
* @deprecated since RapidMiner 6.0, replaced by RAPIDMINER_LIGHT_GRAY
*/
@Deprecated
public static final Color RAPID_I_BEIGE = new Color(202, 188, 165);
/** The RapidMiner Orange. */
public static final Color RAPIDMINER_ORANGE = new Color(241, 96, 34);
/** The RapidMiner Light Orange. */
public static final Color RAPIDMINER_LIGHT_ORANGE = new Color(235, 122, 3);
/** The RapidMiner Yellow. */
public static final Color RAPIDMINER_YELLOW = new Color(244, 232, 11);
/** The RapidMiner Orange. */
public static final Color RAPIDMINER_GRAY = new Color(50, 53, 62);
/** The RapidMiner Light Orange. */
public static final Color RAPIDMINER_LIGHT_GRAY = new Color(121, 124, 130);
/** Some color constants for Java Look and Feel. */
public static final Color LIGHTEST_RED = new Color(250, 210, 210);
/** A brown font color. */
public static final Color BROWN_FONT_COLOR = new Color(63, 53, 24);
/** A brown font color. */
public static final Color LIGHT_BROWN_FONT_COLOR = new Color(113, 103, 74);
/** This set stores all lookup paths for icons */
private static Set<String> iconPaths = new LinkedHashSet<>(Collections.singleton("icons/"));
/** Contains the small frame icons in all possible sizes. */
private static List<Image> allFrameIcons = new LinkedList<>();
private static FrameIconProvider frameIconProvider;
private static final String DEFAULT_FRAME_ICON_BASE_NAME = "rapidminer_frame_icon_";
private static ParentResolvingMap<String, Color> GROUP_TO_COLOR_MAP = new StringColorMap();
/** step width used when cropping a string */
private static final int RESIZE_STEP_WIDTH = 3;
/** separator used between two parts of a cropped string */
public static final String SEPARATOR = "[...]";
/** delay before showing the tool tip for help icons */
private static final int TOOL_TIP_DELAY = 80;
/**
* the path to the icon that is used when displaying help texts, the icon itself cannot be a
* constant since icons are cached and the cache is only initialized later
*/
private static final String HELP_ICON_PATH = "13/" + I18N.getGUIMessage("gui.label.operator_pararameters.help_icon");
/**
* the property that can be used on windows to disable clear type for a component, see
* {@link #disableClearType(JComponent)}
*/
private static final Object AA_TEXT_PROPERTY = getAaTextProperty();
/** The replacement icon for missing icons */
private static final String UNKNOWN_ICON_NAME = "symbol_questionmark.png";
private static final URL UNKNOWN_ICON_URL = Tools.getResource("icons/16/" + UNKNOWN_ICON_NAME);
private static final ImageIcon UNKNOWN_ICON = new ImageIcon(UNKNOWN_ICON_URL);
/** The prefix for mono color icons */
private static final String MONO_COLOR_ICON_PREFIX = "/mono";
/** The prefix for retina icons */
private static final String RETINA_ICON_PREFIX = "/@2x";
private static enum Scaling {
DEFAULT, RETINA
}
private static Scaling scaling = Scaling.DEFAULT;
static {
setupFrameIcons(DEFAULT_FRAME_ICON_BASE_NAME);
try {
GROUP_TO_COLOR_MAP.parseProperties("com/rapidminer/resources/groups.properties", "group.", ".color",
OperatorDescription.class.getClassLoader(), null);
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.tools.SwingTools.loading_operator_group_colors_error");
}
// Detect apple retina display. Will return 2.0 with retina, 1.0 without and null if not
// running an apple system.
if (isRetina()) {
scaling = Scaling.RETINA;
}
}
public static void setFrameIconProvider(final FrameIconProvider _frameIconProvider) {
frameIconProvider = _frameIconProvider;
reloadFrameIcons();
}
public static void setupFrameIcons(final String frameIconBaseName) {
setFrameIconProvider(new DefaultFrameIconProvider(frameIconBaseName));
reloadFrameIcons();
}
private static void reloadFrameIcons() {
if (frameIconProvider == null) {
allFrameIcons = new LinkedList<>();
} else {
allFrameIcons = frameIconProvider.getFrameIcons();
}
}
/**
* Tries to determine whether we are on an Apple Retina display.
*
* @return {@code true} iff we are on OS X and a scaling factor is set
*/
private static boolean isRetina() {
if (SystemInfoUtilities.getOperatingSystem() == OperatingSystem.OSX) {
try {
// The lines below might throw a HeadlessException. Do not move outside of try
// block!
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice device = env.getDefaultScreenDevice();
Field field = device.getClass().getDeclaredField("scale");
if (field != null) {
field.setAccessible(true);
Object scale = field.get(device);
// On OS X the scale field contains the scaling factor of the device (e.g., 2
// for '@2x' Retina devices).
if (scale instanceof Integer && ((Integer) scale).intValue() >= 2) {
return true;
}
}
} catch (HeadlessException headless) {
// we do not care about scaling factors on headless systems
} catch (Exception e) {
LogService.getRoot().log(Level.INFO, "com.rapidminer.gui.tools.SwingTools.retina_detection_error", e);
}
}
return false;
}
/**
* Returns the list of all available program icon sizes.
*/
public static List<Image> getFrameIconList() {
return allFrameIcons;
}
/** Returns the list of all possible frame icons. */
public static void setFrameIcon(final JFrame frame) {
Method iconImageMethod = null;
try {
iconImageMethod = frame.getClass().getMethod("setIconImages", new Class[] { List.class });
} catch (Throwable e) {
// ignore this and use single small icon below
}
if (iconImageMethod != null) {
try {
iconImageMethod.invoke(frame, new Object[] { allFrameIcons });
} catch (Throwable e) {
// ignore this and use single small icon
if (allFrameIcons.size() > 0) {
frame.setIconImage(allFrameIcons.get(0));
}
}
} else {
if (allFrameIcons.size() > 0) {
frame.setIconImage(allFrameIcons.get(0));
}
}
}
/** Returns the list of all possible frame icons. */
public static void setDialogIcon(final JDialog dialog) {
Method iconImageMethod = null;
try {
iconImageMethod = dialog.getClass().getMethod("setIconImages", new Class[] { List.class });
} catch (Throwable e) {
// ignore this and use no icons or parent icon
}
if (iconImageMethod != null) {
try {
iconImageMethod.invoke(dialog, new Object[] { allFrameIcons });
} catch (Throwable e) {
// ignore this and use no or parent icon
}
}
}
/** Creates a red gradient paint. */
public static GradientPaint makeRedPaint(final double width, final double height) {
return new GradientPaint(0f, 0f, new Color(200, 50, 50), (float) width / 2, (float) height / 2,
new Color(255, 100, 100), true);
}
/** Creates a blue gradient paint. */
public static GradientPaint makeBluePaint(final double width, final double height) {
return new GradientPaint(0f, 0f, LIGHT_BLUE, (float) width / 2, (float) height / 2, LIGHTEST_BLUE, true);
}
/** Creates a yellow gradient paint. */
public static GradientPaint makeYellowPaint(final double width, final double height) {
return new GradientPaint(0f, 0f, LIGHT_YELLOW, (float) width / 2, (float) height / 2, LIGHTEST_YELLOW, true);
}
private static final Map<String, ImageIcon> ICON_CACHE = new HashMap<>(1000);
private static final Object ICON_LOCK = new Object();
private static final String BRACKETS = " [...] ";
/**
* Tries to load the icon for the given resource. Returns {@code null} (and writes a warning) if
* the resource file cannot be loaded. This method automatically adds all icon paths specified
* since startup time. The default /icons is always searched. Additional paths might be
* specified by {@link SwingTools#addIconStoragePath(String)}.
*
* The given names must contain '/' instead of backslashes!
*/
public static ImageIcon createIcon(final String iconName) {
return createIcon(iconName, false);
}
/**
* Tries to load the icon for the given resource. Returns {@code null} (and writes a warning) if
* the resource file cannot be loaded. This method automatically adds all icon paths specified
* since startup time. The default /icons is always searched. Additional paths might be
* specified by {@link SwingTools#addIconStoragePath(String)}.
*
* The given names must contain '/' instead of backslashes!
*
* @param iconName
* the name of the icon including the size path component
* @param preferMonochrome
* if {@code true} will chose a monochrome version over a colored version if it
* exists (looking in the {@code [size]/mono} folder). Will fall back to colored if
* no monochrome version exists.
*/
public static ImageIcon createIcon(final String iconName, final boolean preferMonochrome) {
ImageIcon icon = null;
for (String path : iconPaths) {
if (preferMonochrome) {
String newIconName = null;
if (iconName.contains("16/")) {
newIconName = iconName.replaceFirst("16/", "16/mono/");
} else if (iconName.contains("24/")) {
newIconName = iconName.replaceFirst("24/", "24/mono/");
} else if (iconName.contains("32/")) {
newIconName = iconName.replaceFirst("32/", "32/mono/");
} else if (iconName.contains("48/")) {
newIconName = iconName.replaceFirst("48/", "48/mono/");
}
icon = createImage(path + newIconName);
} else {
icon = createImage(path + iconName);
}
return icon;
}
return icon;
}
/**
* This method returns the path of the icon given.
*
* @param iconName
* @return
*/
public static String getIconPath(final String iconName) {
for (String path : iconPaths) {
ImageIcon icon = createImage(path + iconName);
if (icon != null) {
URL resource = Tools.getResource(path + iconName);
if (resource != null) {
return resource.toString();
}
}
}
return UNKNOWN_ICON_URL.toString();
}
/**
* This method adds a path to the set of paths which are searched for icons if the
* {@link SwingTools#createIcon(String)} is called.
*/
public static void addIconStoragePath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
if (!path.endsWith("/")) {
path = path + "/";
}
iconPaths.add(path);
}
/**
* Tries to load the image for the given resource. Returns {@code null} (and writes a warning)
* if the resource file cannot be loaded.
*/
public static ImageIcon createImage(final String imageName) {
return createImage(imageName, true);
}
/**
* Same as {@link #createImage(String)} but can return {@code null} if no placeholder should be
* used. Uses a retina image, if running on a retina display and an icon is available.
*
* @param imageName
* @param usePlaceholder
* if {@code true} will try to replace missing icons via placeholder. Otherwise,
* returns {@code null}
* @return
*/
private static ImageIcon createImage(final String imageName, final boolean usePlaceholder) {
synchronized (ICON_LOCK) {
if (ICON_CACHE.containsKey(imageName)) {
return ICON_CACHE.get(imageName);
}
// Whenever the image name points to a folder, return null
// Before 7.2.3 this would return an uninitialized icon but that causes Swing NPEs
int indexOfLastSlash = imageName.lastIndexOf("/");
if (indexOfLastSlash != -1 && indexOfLastSlash == imageName.length() - 1) {
// we cannot return an uninitialized icon here. This would cause an NPE inside Swing
// internals (disabled icon painting code)
// therefore, always return null regardless of whether the user wants a placeholder
return null;
}
// Try to load high-resolution icon (if appropriate)
if (scaling == Scaling.RETINA) {
// an icon path of the format ".../size(/mono)/icon.png" is expected
if (indexOfLastSlash != -1) {
String prefix = imageName.substring(0, indexOfLastSlash);
if (prefix.endsWith(MONO_COLOR_ICON_PREFIX)) {
prefix = prefix.substring(0, prefix.length() - MONO_COLOR_ICON_PREFIX.length());
}
int indexOfSlashBeforeResolution = prefix.lastIndexOf("/");
if (indexOfSlashBeforeResolution >= 0) {
String potentialSizeString = prefix.substring(indexOfSlashBeforeResolution + 1);
try {
int iconSize = Integer.parseInt(potentialSizeString);
// load icon from high-dpi subfolder
StringBuilder scaledIconName = new StringBuilder(32);
scaledIconName.append(imageName.substring(0, indexOfLastSlash));
scaledIconName.append(RETINA_ICON_PREFIX);
scaledIconName.append(imageName.substring(indexOfLastSlash, imageName.length()));
URL scaledIconUrl = Tools.getResource(scaledIconName.toString());
if (scaledIconUrl != null) {
ImageIcon icon = new ScaledImageIcon(scaledIconUrl, iconSize, iconSize);
ICON_CACHE.put(imageName, icon);
return icon;
}
} catch (NumberFormatException | NullPointerException e) {
// Do nothing and fall back to normal icon. The NPE might occur if
// the security manager intercepts the lookup of the icon (e.g., if
// the icon is missing on OS X).
}
}
}
}
// try to load standard-resolution icon
URL iconUrl = Tools.getResource(imageName);
if (iconUrl != null) {
try {
ImageIcon icon = new ImageIcon(iconUrl);
ICON_CACHE.put(imageName, icon);
return icon;
} catch (NullPointerException e) {
// Do nothing and fall back to placeholder icon. The NPE might occur if
// the security manager intercepts the lookup of the icon (e.g., if
// the icon is missing on OS X).
}
}
// fall back to placeholder or null
if (usePlaceholder) {
LogService.getRoot().log(Level.FINE, "com.rapidminer.gui.tools.SwingTools.loading_image_error", imageName);
if (indexOfLastSlash != -1) {
String errorIconName = imageName.substring(0, indexOfLastSlash + 1) + UNKNOWN_ICON_NAME;
iconUrl = Tools.getResource(errorIconName);
if (iconUrl != null) {
ImageIcon icon = new ImageIcon(iconUrl);
return icon;
}
}
// return default 16x16 unknown icon
return UNKNOWN_ICON;
} else {
return null;
}
}
}
/**
* This method transforms the given tool tip text into HTML. Lines are splitted at linebreaks
* and additional line breaks are added after ca. {@link #TOOL_TIP_LINE_LENGTH} characters.
*/
public static String transformToolTipText(final String description) {
return transformToolTipText(description, true, TOOL_TIP_LINE_LENGTH);
}
/**
* This method transforms the given tool tip text into HTML. Lines are splitted at linebreaks
* and additional line breaks are added after ca. {@link #TOOL_TIP_LINE_LENGTH} characters.
*
* @param escapeSlashes
* Inidicates if forward slashes ("/") are escaped by the html code "/"
*/
public static String transformToolTipText(final String description, final boolean escapeSlashes) {
return transformToolTipText(description, true, TOOL_TIP_LINE_LENGTH, escapeSlashes);
}
/**
* This method transforms the given tool tip text into HTML. Lines are splitted at linebreaks
* and additional line breaks are added after ca. {@link #TOOL_TIP_LINE_LENGTH} characters.
*
* @param escapeSlashes
* Inidicates if forward slashes ("/") are escaped by the html code "/"
* @param escapeHTML
* Indicates if previously added html tags are escaped
*/
public static String transformToolTipText(final String description, final boolean escapeSlashes,
final boolean escapeHTML) {
return transformToolTipText(description, true, TOOL_TIP_LINE_LENGTH, escapeSlashes, escapeHTML);
}
public static String transformToolTipText(final String description, final boolean addHTMLTags, final int lineLength) {
return transformToolTipText(description, addHTMLTags, lineLength, false);
}
/**
* This method transforms the given tool tip text into HTML. Lines are splitted at linebreaks
* and additional line breaks are added after ca. lineLength characters.
*
* @param escapeSlashes
* Inidicates if forward slashes ("/") are escaped by the html code "/"
*/
public static String transformToolTipText(final String description, final boolean addHTMLTags, final int lineLength,
final boolean escapeSlashes) {
return transformToolTipText(description, addHTMLTags, lineLength, escapeSlashes, true);
}
/**
* This method transforms the given tool tip text into HTML. Lines are splitted at linebreaks
* and additional line breaks are added after ca. lineLength characters.
*
* @param escapeSlashes
* Inidicates if forward slashes ("/") are escaped by the html code "/"
* @param escapeHTML
* Indicates if previously added html tags are escaped TODO: Use
* <div style="width:XXXpx">
*/
public static String transformToolTipText(final String description, final boolean addHTMLTags, final int lineLength,
final boolean escapeSlashes, final boolean escapeHTML) {
String completeText = description.trim();
if (escapeHTML) {
completeText = Tools.escapeHTML(completeText);
}
if (escapeSlashes) {
completeText = completeText.replaceAll("/", "/");
}
StringBuffer result = new StringBuffer();
if (addHTMLTags) {
result.append("<html>");
}
// line.separator does not work here (transform and use \n)
completeText = Tools.transformAllLineSeparators(completeText);
String[] lines = completeText.split("\n");
for (String text : lines) {
boolean first = true;
while (text.length() > lineLength) {
int spaceIndex = text.indexOf(" ", lineLength);
if (!first) {
result.append("<br>");
}
first = false;
if (spaceIndex >= 0) {
result.append(text.substring(0, spaceIndex));
text = text.substring(spaceIndex + 1);
} else {
result.append(text);
text = "";
}
}
if (!first && text.length() > 0) {
result.append("<br>");
}
result.append(text);
result.append("<br>");
}
if (addHTMLTags) {
result.append("</html>");
}
return result.toString();
}
/** Adds line breaks after {@link #TOOL_TIP_LINE_LENGTH} letters. */
public static String addLinebreaks(String message) {
if (message == null) {
return null;
}
String completeText = message.trim();
StringBuffer result = new StringBuffer();
// line.separator does not work here (transform and use \n)
completeText = Tools.transformAllLineSeparators(completeText);
String[] lines = completeText.split("\n");
for (String text : lines) {
boolean first = true;
while (text.length() > TOOL_TIP_LINE_LENGTH) {
int spaceIndex = text.indexOf(" ", TOOL_TIP_LINE_LENGTH);
if (!first) {
result.append(Tools.getLineSeparator());
}
first = false;
if (spaceIndex >= 0) {
result.append(text.substring(0, spaceIndex));
text = text.substring(spaceIndex + 1);
} else {
result.append(text);
text = "";
}
}
if (!first && text.length() > 0) {
result.append(Tools.getLineSeparator());
}
result.append(text);
result.append(Tools.getLineSeparator());
}
return result.toString();
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.results.-key-.icon
*/
public static void showResultsDialog(final String i18nKey, final JComponent results, final Object... i18nArgs) {
showResultsDialog(ApplicationFrame.getApplicationFrame(), i18nKey, results, i18nArgs);
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.results.-key-.icon
*
* @since 7.5.0
*/
public static void showResultsDialog(final Window owner, final String i18nKey, final JComponent results,
final Object... i18nArgs) {
invokeLater(new Runnable() {
@Override
public void run() {
ResultViewDialog dialog = new ResultViewDialog(owner, i18nKey, results, i18nArgs);
dialog.setVisible(true);
}
});
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.message.-key-.icon
*/
public static void showMessageDialog(final String key, final Object... keyArguments) {
showMessageDialog(key, null, keyArguments);
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.message.-key-.icon
*
* @since 7.5.0
*/
public static void showMessageDialog(final Window owner, final String key, final Object... keyArguments) {
showMessageDialog(owner, key, null, keyArguments);
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.message.-key-.icon
*/
public static void showMessageDialog(final String key, final JComponent component, final Object... keyArguments) {
showMessageDialog(ApplicationFrame.getApplicationFrame(), key, component, keyArguments);
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.message.-key-.icon
*
* @since 7.5.0
*/
public static void showMessageDialog(final Window owner, final String key, final JComponent component,
final Object... keyArguments) {
invokeLater(new Runnable() {
@Override
public void run() {
MessageDialog dialog = new MessageDialog(owner, key, component, keyArguments);
dialog.setVisible(true);
}
});
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.confirm.-key-.icon
*
* See {@link ConfirmDialog} for details on the mode options.
*/
public static int showConfirmDialog(final String key, final int mode, final Object... keyArguments) {
return showConfirmDialog(ApplicationFrame.getApplicationFrame(), key, mode, keyArguments);
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.confirm.-key-.icon
*
* See {@link ConfirmDialog} for details on the mode options.
*
* @since 7.5.0
*/
public static int showConfirmDialog(final Window owner, final String key, final int mode, final Object... keyArguments) {
return invokeAndWaitWithResult(new ResultRunnable<Integer>() {
@Override
public Integer run() {
ConfirmDialog dialog = new ConfirmDialog(owner, key, mode, false, keyArguments);
dialog.setVisible(true);
return dialog.getReturnOption();
}
});
}
/**
* This method will present a dialog to enter a text. This text will be returned if the user
* confirmed the edit. Otherwise {@code null} is returned. The key will be used for the
* properties gui.dialog.input.-key-.title, gui.dialog.input.-key-.message and
* gui.dialog.input.-key-.icon
*/
public static String showInputDialog(final String key, final String text, final Object... keyArguments) {
return showInputDialog(ApplicationFrame.getApplicationFrame(), key, text, keyArguments);
}
/**
* This method will present a dialog to enter a text. This text will be returned if the user
* confirmed the edit. Otherwise {@code null} is returned. The key will be used for the
* properties gui.dialog.input.-key-.title, gui.dialog.input.-key-.message and
* gui.dialog.input.-key-.icon
*
* @since 7.5.0
*/
public static String showInputDialog(final Window owner, final String key, final String text,
final Object... keyArguments) {
return showInputDialog(owner, key, text, null, keyArguments);
}
/**
* This method will present a dialog to enter a text. This text will be returned if the user
* confirmed the edit and the validator does not report an error. Otherwise {@code null} is
* returned. The key will be used for the properties gui.dialog.input.-key-.title,
* gui.dialog.input.-key-.message and gui.dialog.input.-key-.icon
*
* @since 7.0.0
*/
public static String showInputDialog(final Window owner, final String key, final String text,
final InputValidator<String> inputValidator, final Object... keyArguments) {
return invokeAndWaitWithResult(new ResultRunnable<String>() {
@Override
public String run() {
InputDialog dialog = new InputDialog(owner, key, text, inputValidator, keyArguments);
dialog.setVisible(true);
if (dialog.wasConfirmed()) {
return dialog.getInputText();
} else {
return null;
}
}
});
}
/**
* This method will present a repository entry dialog to enter a text. This text will be
* returned if the user confirmed the edit. Otherwise {@code null} is returned. Prevents invalid
* repository names. The key will be used for the properties gui.dialog.input.-key-.title,
* gui.dialog.input.-key-.message and gui.dialog.input.-key-.icon
*/
public static String showRepositoryEntryInputDialog(final String key, final String text, final Object... keyArguments) {
return showRepositoryEntryInputDialog(ApplicationFrame.getApplicationFrame(), key, text, keyArguments);
}
/**
* This method will present a repository entry dialog to enter a text. This text will be
* returned if the user confirmed the edit. Otherwise {@code null} is returned. Prevents invalid
* repository names. The key will be used for the properties gui.dialog.input.-key-.title,
* gui.dialog.input.-key-.message and gui.dialog.input.-key-.icon
*
* @since 7.5.0
*/
public static String showRepositoryEntryInputDialog(final Window owner, final String key, final String text,
final Object... keyArguments) {
return invokeAndWaitWithResult(new ResultRunnable<String>() {
@Override
public String run() {
RepositoryEntryInputDialog dialog = new RepositoryEntryInputDialog(owner, key, text, keyArguments);
dialog.setVisible(true);
if (dialog.wasConfirmed()) {
return dialog.getInputText();
} else {
return null;
}
}
});
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.input.-key-.icon
*/
public static <T> T showInputDialog(final String key, final T[] selectionValues, final T initialSelectionValue,
final Object... keyArguments) {
return showInputDialog(ApplicationFrame.getApplicationFrame(), key, selectionValues, initialSelectionValue,
keyArguments);
}
/**
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.input.-key-.icon
*
* @since 7.5.0
*/
public static <T> T showInputDialog(final Window owner, final String key, final T[] selectionValues,
final T initialSelectionValue, final Object... keyArguments) {
return showInputDialog(owner, key, false, Arrays.asList(selectionValues), initialSelectionValue, null, keyArguments);
}
/**
* This will open a simple input dialog, where a comboBox presents the given values. The
* Combobox might be editable depending on parameter setting.
*
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.input.-key-.icon
*/
public static <T> T showInputDialog(final String key, final boolean editable, final T[] selectionValues,
final T initialSelectionValue, final Object... keyArguments) {
return showInputDialog(ApplicationFrame.getApplicationFrame(), key, editable, selectionValues, initialSelectionValue,
null, keyArguments);
}
/**
* This will open a simple input dialog, where a comboBox presents the given values. The
* Combobox might be editable depending on parameter setting.
*
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.input.-key-.icon
*
* @since 7.5.0
*/
public static <T> T showInputDialog(final Window owner, final String key, final boolean editable,
final T[] selectionValues, final T initialSelectionValue, final Object... keyArguments) {
return showInputDialog(owner, key, editable, Arrays.asList(selectionValues), initialSelectionValue, null,
keyArguments);
}
/**
* This will open a simple input dialog, where a comboBox presents the given values. The
* Combobox might be editable depending on parameter setting. The selection will be returned if
* the user confirmed the dialog and the validator does not report an error. Otherwise
* {@code null} is returned.
*
* The key will be used for the properties gui.dialog.-key-.title and
* gui.dialog.input.-key-.icon
*
* @since 7.0.0
*/
public static <T> T showInputDialog(final Window owner, final String key, final boolean editable,
final Collection<T> selectionValues, final T initialSelectionValue, final InputValidator<T> inputValidator,
final Object... keyArguments) {
return invokeAndWaitWithResult(new ResultRunnable<T>() {
@Override
public T run() {
SelectionInputDialog<T> dialog = new SelectionInputDialog<>(owner, key, editable, selectionValues,
initialSelectionValue, inputValidator, keyArguments);
dialog.setVisible(true);
if (dialog.wasConfirmed()) {
return dialog.getInputSelection();
} else {
return null;
}
}
});
}
/**
* Shows a very simple error message without any Java exception hints.
*
* @param key
* the I18n-key which will be used to display the internationalized message
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showVerySimpleErrorMessage(final String key, final Object... arguments) {
showVerySimpleErrorMessage(ApplicationFrame.getApplicationFrame(), key, arguments);
}
/**
* Shows a very simple error message without any Java exception hints.
*
* @param owner
* the owner of the opened dialog
* @param key
* the I18n-key which will be used to display the internationalized message
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
* @since 7.5.0
*/
public static void showVerySimpleErrorMessage(final Window owner, final String key, final Object... arguments) {
invokeLater(new Runnable() {
@Override
public void run() {
ErrorDialog dialog = new ErrorDialog(owner, key, arguments);
dialog.setModal(true);
dialog.setVisible(true);
}
});
}
public static void showVerySimpleErrorMessageAndWait(final String key, final Object... arguments) {
showVerySimpleErrorMessageAndWait(ApplicationFrame.getApplicationFrame(), key, arguments);
}
/**
* @since 7.5.0
*/
public static void showVerySimpleErrorMessageAndWait(final Window owner, final String key, final Object... arguments) {
invokeAndWait(new Runnable() {
@Override
public void run() {
ErrorDialog dialog = new ErrorDialog(owner, key, arguments);
dialog.setModal(true);
dialog.setVisible(true);
}
});
}
/**
* This is the normal method which could be used by GUI classes for errors caused by some
* exception (e.g. IO issues). Of course these error message methods should never be invoked by
* operators or similar.
*
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showSimpleErrorMessage(final String key, final Throwable e, final Object... arguments) {
showSimpleErrorMessage(key, e, true, arguments);
}
/**
* This is the normal method which could be used by GUI classes for errors caused by some
* exception (e.g. IO issues). Of course these error message methods should never be invoked by
* operators or similar.
*
* @param owner
* the owner of the opened dialog
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
* @since 7.5.0
*/
public static void showSimpleErrorMessage(final Window owner, final String key, final Throwable e,
final Object... arguments) {
showSimpleErrorMessage(owner, key, e, true, arguments);
}
/**
* This is the normal method which could be used by GUI classes for errors caused by some
* exception (e.g. IO issues). Of course these error message methods should never be invoked by
* operators or similar.
*
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param displayExceptionMessage
* indicates if the exception message will be displayed in the dialog or just in the
* detailed panel
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showSimpleErrorMessage(final String key, final Throwable e, final boolean displayExceptionMessage,
final Object... arguments) {
showSimpleErrorMessage(ApplicationFrame.getApplicationFrame(), key, e, displayExceptionMessage, arguments);
}
/**
* This is the normal method which could be used by GUI classes for errors caused by some
* exception (e.g. IO issues). Of course these error message methods should never be invoked by
* operators or similar.
*
* @param owner
* the owner of the opened dialog
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param displayExceptionMessage
* indicates if the exception message will be displayed in the dialog or just in the
* detailed panel
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
* @since 7.5.0
*/
public static void showSimpleErrorMessage(final Window owner, final String key, final Throwable e,
final boolean displayExceptionMessage, final Object... arguments) {
ActionStatisticsCollector.getInstance().log(ActionStatisticsCollector.TYPE_ERROR, key,
e != null ? e.getClass().getName() : null);
// if debug mode is enabled, send exception to logger
if ("true".equals(ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_DEBUGMODE))) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.tools.SwingTools.show_simple_get_message", e.getMessage()), e);
}
invokeLater(new Runnable() {
@Override
public void run() {
ExtendedErrorDialog dialog = new ExtendedErrorDialog(owner, key, e, displayExceptionMessage, arguments);
dialog.setVisible(true);
}
});
}
/**
* This is the normal method which could be used by GUI classes for errors caused by some
* exception (e.g. IO issues). Of course these error message methods should never be invoked by
* operators or similar. The key is constructed as gui.dialog.error.-key- and uses .title and
* .icon properties
*
* @param key
* the I18n-key which will be used to display the internationalized message
* @param errorMessage
* the error message associated to this message
* @param displayExceptionMessage
* indicates if the exception message will be displayed in the dialog or just in the
* detailed panel
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showSimpleErrorMessage(final String key, final String errorMessage, final Object... arguments) {
showSimpleErrorMessage(ApplicationFrame.getApplicationFrame(), key, errorMessage, arguments);
}
/**
* This is the normal method which could be used by GUI classes for errors caused by some
* exception (e.g. IO issues). Of course these error message methods should never be invoked by
* operators or similar. The key is constructed as gui.dialog.error.-key- and uses .title and
* .icon properties
*
* @param owner
* the owner of the opened dialog
* @param key
* the I18n-key which will be used to display the internationalized message
* @param errorMessage
* the error message associated to this message
* @param displayExceptionMessage
* indicates if the exception message will be displayed in the dialog or just in the
* detailed panel
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showSimpleErrorMessage(final Window owner, final String key, final String errorMessage,
final Object... arguments) {
invokeLater(new Runnable() {
@Override
public void run() {
ExtendedErrorDialog dialog = new ExtendedErrorDialog(owner, key, errorMessage, arguments);
dialog.setVisible(true);
}
});
// if debug mode is enabled, print throwable into logger
if (ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_DEBUGMODE).equals("true")) {
LogService.getRoot().log(Level.WARNING, errorMessage);
}
}
/**
* Shows the final error message dialog. This dialog also allows to send a bug report if the
* error was not (definitely) a user error.
*
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param displayExceptionMessage
* indicates if the exception message will be displayed in the dialog or just in the
* detailed panel
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showFinalErrorMessage(final String key, final Throwable e, final boolean displayExceptionMessage,
final Object... objects) {
showFinalErrorMessage(ApplicationFrame.getApplicationFrame(), key, e, displayExceptionMessage, objects);
}
/**
* Shows the final error message dialog. This dialog also allows to send a bug report if the
* error was not (definitely) a user error.
*
* @param owner
* the owner of the opened dialog
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param displayExceptionMessage
* indicates if the exception message will be displayed in the dialog or just in the
* detailed panel
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
* @since 7.5.0
*/
public static void showFinalErrorMessage(final Window owner, final String key, final Throwable e,
final boolean displayExceptionMessage, final Object... objects) {
// if debug modus is enabled, print throwable into logger
if (ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_DEBUGMODE).equals("true")) {
LogService.getRoot().log(Level.SEVERE, e.getMessage(), e);
}
invokeLater(new Runnable() {
@Override
public void run() {
ExtendedErrorDialog dialog = new ExtendedErrorDialog(owner, key, e, displayExceptionMessage, objects);
dialog.setVisible(true);
}
});
}
/**
* Shows the final error message dialog. This dialog also allows to send a bug report if the
* error was not (definitely) a user error.
*
* @param key
* the I18n-key which will be used to display the internationalized message
* @param e
* the exception associated to this message
* @param arguments
* additional arguments for the internationalized message, which replace
* <code>{0}</code>, <code>{1}</code>, etcpp.
*/
public static void showFinalErrorMessage(final String key, final Throwable e, final Object... objects) {
showFinalErrorMessage(key, e, false, objects);
}
/**
* Opens a file chooser with a reasonable start directory. If the extension is null, no file
* filters will be used.
*/
public static File chooseFile(final Component parent, final File file, final boolean open, final String extension,
final String extensionDescription) {
return chooseFile(parent, null, file, open, extension, extensionDescription);
}
public static File chooseFile(final Component parent, final String i18nKey, final File file, final boolean open,
final String extension, final String extensionDescription) {
return chooseFile(parent, i18nKey, file, open, false, extension, extensionDescription);
}
/**
* Opens a file chooser with a reasonable start directory. If the extension is null, no file
* filters will be used. This method allows choosing directories.
*/
public static File chooseFile(final Component parent, final File file, final boolean open, final boolean onlyDirs,
final String extension, final String extensionDescription) {
return chooseFile(parent, null, file, open, onlyDirs, extension, extensionDescription);
}
public static File chooseFile(final Component parent, final String i18nKey, final File file, final boolean open,
final boolean onlyDirs, final String extension, final String extensionDescription) {
return chooseFile(parent, i18nKey, file, open, onlyDirs, extension == null ? null : new String[] { extension },
extensionDescription == null ? null : new String[] { extensionDescription });
}
public static File chooseFile(final Component parent, final String i18nKey, final File file, final boolean open,
final boolean onlyDirs, final String extension, final String extensionDescription,
final boolean acceptAllFiles) {
return chooseFile(parent, i18nKey, file, open, onlyDirs, extension == null ? null : new String[] { extension },
extensionDescription == null ? null : new String[] { extensionDescription }, acceptAllFiles);
}
/** Returns the user selected file. */
public static File chooseFile(final Component parent, final File file, final boolean open, final boolean onlyDirs,
final String[] extensions, final String[] extensionDescriptions) {
return chooseFile(parent, null, file, open, onlyDirs, extensions, extensionDescriptions);
}
/**
* @param addDefaultAllFileExtensionFilter
* if set to <code>true</code> and more than one file extension is provided a new
* filter that contains all file extension will be added as default filter. This
* makes sense for file reading operations that can read files with different file
* extensions. For file writing operations it is however not recommended as the new
* file filter will not add the correct file ending when entering the path of a file
* that does not exist.
* @return the user selected file.
*/
public static File chooseFile(final Component parent, final File file, final boolean open, final boolean onlyDirs,
final String[] extensions, final String[] extensionDescriptions,
final boolean addDefaultAllFileExtensionFilter) {
return chooseFile(parent, null, file, open, onlyDirs, addDefaultAllFileExtensionFilter, extensions,
extensionDescriptions);
}
public static File chooseFile(final Component parent, final String i18nKey, final File file, final boolean open,
final boolean onlyDirs, final String[] extensions, final String[] extensionDescriptions) {
return chooseFile(parent, i18nKey, file, open, onlyDirs, false, extensions, extensionDescriptions);
}
public static File chooseFile(final Component parent, final String i18nKey, final File file, final boolean open,
final boolean onlyDirs, boolean addDefaultAllFileExtensionFilter, final String[] extensions,
final String[] extensionDescriptions) {
List<FileFilter> fileFilters = new LinkedList<>();
if (extensions != null) {
int i = 0;
if (addDefaultAllFileExtensionFilter && extensions.length > 1) {
/*
* In case multiple formats are supported add a filter which shows all supported
* formats first
*/
fileFilters.add(new SimpleFileFilter(getAllExtensionFilterDescription(extensions), extensions, -1));
}
for (String extension : extensions) {
if (extension != null) {
fileFilters
.add(new SimpleFileFilter(extensionDescriptions[i] + " (*." + extension + ")", "." + extension));
}
++i;
}
}
return chooseFile(parent, i18nKey, file, open, onlyDirs, fileFilters.toArray(new FileFilter[fileFilters.size()]),
true);
}
private static String getAllExtensionFilterDescription(String[] extensions) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (String extension : extensions) {
if (!first) {
builder.append(", ");
}
builder.append("*.");
builder.append(extension);
first = false;
}
return builder.toString();
}
public static File chooseFile(final Component parent, final String i18nKey, final File file, final boolean open,
final boolean onlyDirs, final String[] extensions, final String[] extensionDescriptions,
final boolean acceptAllFiles) {
FileFilter[] filters = null;
if (extensions != null) {
filters = new FileFilter[extensions.length];
for (int i = 0; i < extensions.length; i++) {
filters[i] = new SimpleFileFilter(extensionDescriptions[i] + " (*." + extensions[i] + ")",
"." + extensions[i]);
}
}
return chooseFile(parent, i18nKey, file, open, onlyDirs, filters, acceptAllFiles);
}
/**
* Opens a file chooser with a reasonable start directory. onlyDirs indidcates if only files or
* only can be selected.
*
* @param file
* The initially selected value of the file chooser dialog
* @param open
* Open or save dialog?
* @param onlyDirs
* Only allow directories to be selected
* @param fileFilters
* List of FileFilters to use
*/
private static File chooseFile(Component parent, String i18nKey, File file, boolean open, boolean onlyDirs,
FileFilter[] fileFilters, boolean acceptAllFiles) {
if (parent == null) {
parent = RapidMinerGUI.getMainFrame();
}
String key = "file_chooser." + (i18nKey != null ? i18nKey : open ? onlyDirs ? "open_directory" : "open" : "save");
JFileChooser fileChooser = createFileChooser(key, file, onlyDirs, fileFilters);
fileChooser.setAcceptAllFileFilterUsed(acceptAllFiles);
int returnValue = open ? fileChooser.showOpenDialog(parent) : fileChooser.showSaveDialog(parent);
switch (returnValue) {
case JFileChooser.APPROVE_OPTION:
// check extension
File selectedFile = fileChooser.getSelectedFile();
FileFilter selectedFilter = fileChooser.getFileFilter();
String extension = null;
if (selectedFilter instanceof SimpleFileFilter) {
SimpleFileFilter simpleFF = (SimpleFileFilter) selectedFilter;
extension = simpleFF.getExtension();
}
if (extension != null) {
if (!selectedFile.getAbsolutePath().toLowerCase().endsWith(extension.toLowerCase())) {
selectedFile = new File(selectedFile.getAbsolutePath() + extension);
}
}
storeLastDirectory(selectedFile.toPath());
return selectedFile;
default:
return null;
}
}
/**
* Stores the directory of the selectedFile under the bookmark "--- Last Directory"
*
* @param selectedFile
* the file defining the last selected directory
* @since 7.0
*/
public static void storeLastDirectory(Path selectedFile) {
if (selectedFile != null) {
Path parentFile = selectedFile.getParent();
if (parentFile != null) {
File bookmarksFile = new File(FileSystemService.getUserRapidMinerDir(), ".bookmarks");
if (bookmarksFile.exists()) {
List<Bookmark> bookmarks = BookmarkIO.readBookmarks(bookmarksFile);
Iterator<Bookmark> b = bookmarks.iterator();
while (b.hasNext()) {
Bookmark bookmark = b.next();
if (bookmark.getName().equals("--- Last Directory")) {
b.remove();
}
}
bookmarks.add(new Bookmark("--- Last Directory", parentFile.toAbsolutePath().toString()));
Collections.sort(bookmarks);
BookmarkIO.writeBookmarks(bookmarks, bookmarksFile);
}
}
}
}
/**
* Creates file chooser with a reasonable start directory. You may use the following code
* snippet in order to retrieve the file:
*
* <pre>
* if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION)
* File selectedFile = fileChooser.getSelectedFile();
* </pre>
*
* Usually, the method {@link #chooseFile(Component, File, boolean, boolean, FileFilter[])} or
* one of the convenience wrapper methods can be used to do this. This method is only useful if
* one is interested, e.g., in the selected file filter.
*
* @param file
* The initially selected value of the file chooser dialog
* @param onlyDirs
* Only allow directories to be selected
* @param fileFilters
* List of FileFilters to use
*/
public static JFileChooser createFileChooser(final String i18nKey, final File file, final boolean onlyDirs,
final FileFilter[] fileFilters) {
File directory = null;
if (file != null) {
if (file.isDirectory()) {
directory = file;
} else {
directory = file.getAbsoluteFile().getParentFile();
}
} else {
directory = FileSystemView.getFileSystemView().getDefaultDirectory();
}
JFileChooser fileChooser = new ExtendedJFileChooser(i18nKey, directory);
if (onlyDirs) {
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
if (fileFilters != null) {
fileChooser.setAcceptAllFileFilterUsed(true);
for (FileFilter fileFilter : fileFilters) {
fileChooser.addChoosableFileFilter(fileFilter);
}
if (fileFilters.length > 0) {
fileChooser.setFileFilter(fileFilters[0]);
}
}
if (file != null) {
fileChooser.setSelectedFile(file);
}
return fileChooser;
}
/**
* Creates a panel with title and text. The panel has a border layout and the text is placed
* into the NORTH section.
*/
public static JPanel createTextPanel(final String title, final String text) {
JPanel panel = new JPanel(new java.awt.BorderLayout());
JLabel label = new JLabel("<html><h3>" + title + "</h3>" + (text != null ? "<p>" + text + "</p>" : "") + "</html>");
label.setBorder(BorderFactory.createEmptyBorder(11, 11, 11, 11));
label.setFont(label.getFont().deriveFont(java.awt.Font.PLAIN));
panel.add(label, java.awt.BorderLayout.NORTH);
return panel;
}
// ================================================================================
// /**
// * Replaces simple html tags and quotes by RapidMiner specific text
// elements.
// * These can be used in XML files without confusing an XML parser.
// */
// public static String html2RapidMinerText(String html) {
// if (html == null)
// return null;
// String result = html.replaceAll("<", "#ylt#");
// result = result.replaceAll(">", "#ygt#");
// result = result.replaceAll("\"", "#yquot#");
// result = result.replaceAll(Tools.getLineSeparator(), "");
// return result;
// }
/**
* Returns enlarged string, with a suffix and a prefix of blanks to reach the given minimal text
* length
*
* Example:
*
* enlargeString("1234", 8) -> " 1234 "
*
*
* @param string
* The string to enlarge
* @param minimalTextLength
* Minimal length of the text
*/
public static String enlargeString(String string, int minimalTextLength) {
if (string == null || string.isEmpty()) {
throw new IllegalArgumentException("No string given");
}
if (minimalTextLength < 0) {
throw new IllegalArgumentException("Number of blanks has to be positive");
}
int n = minimalTextLength - string.length();
// text is large enough
if (n <= 0) {
return string;
}
int nrSpaces = (int) Math.ceil(n / 2.0);
String spaces = String.format("%" + nrSpaces + "s", "");
return spaces + string + spaces;
}
/**
* Replaces the RapidMiner specific tag elements by normal HTML tags. The given text is also
* embedded in an HTML and body tag with an appropriated style sheet definition.
*
* Currently, the only replaced tag is <icon>NAME</icon> which will be replaced by
* <img src="path/to/NAME"/>.
*
*/
public static String text2DisplayHtml(final String text) {
String result = "<html><head><style type=text/css>body { font-family:sans-serif; font-size:12pt; }</style></head><body>"
+ text + "</body></html>";
result = text2SimpleHtml(result);
// result = result.replaceAll("#yquot#", """);
while (result.indexOf("<icon>") != -1) {
int startIndex = result.indexOf("<icon>");
int endIndex = result.indexOf("</icon>");
String start = result.substring(0, startIndex);
String end = result.substring(endIndex + 7);
String icon = result.substring(startIndex + 6, endIndex).trim().toLowerCase();
java.net.URL url = Tools.getResource("icons/" + icon + ".png");
if (url != null) {
result = start + "<img src=\"" + url + "\"/>" + end;
} else {
result = start + end;
}
}
return result;
}
/**
* Replaces the RapidMiner specific tag elements by normal HTML tags. This method does not embed
* the given text in a root HTML tag.
*/
private static String text2SimpleHtml(String htmlText) {
if (htmlText == null) {
return null;
}
String replaceString = htmlText;
// replaceString = htmlText.replaceAll("#ygt#", ">");
// replaceString = replaceString.replaceAll("#ylt#", "<");
StringBuffer result = new StringBuffer();
boolean afterClose = true;
int currentLineLength = 0;
for (int i = 0; i < replaceString.length(); i++) {
char c = replaceString.charAt(i);
// skip white space after close
if (afterClose) {
if (c == ' ') {
continue;
}
}
// opening bracket
if (c == '<') {
if (!afterClose) {
result.append(Tools.getLineSeparator());
currentLineLength = 0;
}
}
// append char
afterClose = false;
result.append(c);
currentLineLength++;
// break long lines
if (currentLineLength > 70 && c == ' ') {
result.append(Tools.getLineSeparator());
currentLineLength = 0;
}
// closing bracket
if (c == '>') {
result.append(Tools.getLineSeparator());
currentLineLength = 0;
afterClose = true;
}
}
return result.toString();
}
/**
* Returns a color equivalent to the value of <code>value</code>. The value has to be normalized
* between 0 and 1.
*/
public static Color getPointColor(final double value) {
return new Color(Color.HSBtoRGB((float) (0.68 * (1.0d - value)), 1.0f, 1.0f)); // all
// colors
}
/**
* Returns a color equivalent to the value of <code>value</code>. The value will be normalized
* between 0 and 1 using the parameters max and min. Which are the minimum and maximum of the
* complete dataset.
*/
public static Color getPointColor(double value, final double max, final double min) {
value = (value - min) / (max - min);
return getPointColor(value);
}
/** Returns JEditTextArea defaults with adapted syntax color styles. */
public static TextAreaDefaults getTextAreaDefaults() {
TextAreaDefaults defaults = TextAreaDefaults.getDefaults();
defaults.styles = getSyntaxStyles();
return defaults;
}
/**
* Returns adapted syntax color and font styles matching RapidMiner colors.
*/
public static SyntaxStyle[] getSyntaxStyles() {
SyntaxStyle[] styles = SyntaxUtilities.getDefaultSyntaxStyles();
styles[Token.COMMENT1] = new SyntaxStyle(new Color(0x990033), true, false);
styles[Token.COMMENT2] = new SyntaxStyle(Color.black, true, false);
styles[Token.KEYWORD1] = new SyntaxStyle(Color.black, false, true);
styles[Token.KEYWORD2] = new SyntaxStyle(new Color(255, 51, 204), false, false);
styles[Token.KEYWORD3] = new SyntaxStyle(new Color(255, 51, 204), false, false);
styles[Token.LITERAL1] = new SyntaxStyle(new Color(51, 51, 255), false, false);
styles[Token.LITERAL2] = new SyntaxStyle(new Color(51, 51, 255), false, false);
styles[Token.LABEL] = new SyntaxStyle(new Color(0x990033), false, true);
styles[Token.OPERATOR] = new SyntaxStyle(Color.black, false, true);
styles[Token.INVALID] = new SyntaxStyle(Color.red, false, true);
return styles;
}
public static String toHTMLString(final Ports<? extends Port> ports) {
StringBuilder b = new StringBuilder();
boolean first = true;
for (Port port : ports.getAllPorts()) {
if (!first) {
b.append(", ");
} else {
first = false;
}
b.append(port.getName());
String desc = port.getDescription();
if (desc.length() > 0) {
b.append(": ");
b.append(port.getDescription());
}
}
return b.toString();
}
public static void showLongMessage(final String i18nKey, final String message) {
showLongMessage(ApplicationFrame.getApplicationFrame(), i18nKey, message);
}
/**
* @since 7.5.0
*/
public static void showLongMessage(final Window owner, final String i18nKey, final String message) {
invokeLater(new Runnable() {
@Override
public void run() {
LongMessageDialog dialog = new LongMessageDialog(owner, i18nKey, message);
dialog.setVisible(true);
}
});
}
public static void setEnabledRecursive(final Component c, final boolean enabled) {
c.setEnabled(enabled);
if (c instanceof Container) {
for (Component child : ((Container) c).getComponents()) {
setEnabledRecursive(child, enabled);
}
}
}
public static void setOpaqueRecursive(final Component c, final boolean enabled) {
if (c instanceof JComponent) {
((JComponent) c).setOpaque(enabled);
}
if (c instanceof Container) {
for (Component child : ((Container) c).getComponents()) {
setOpaqueRecursive(child, enabled);
}
}
}
public static void setProcessEditorsEnabled(final boolean enabled) {
MainFrame mainFrame = RapidMinerGUI.getMainFrame();
setEnabledRecursive(mainFrame.getProcessPanel().getComponent(), enabled);
setEnabledRecursive(mainFrame.getPropertyPanel().getComponent(), enabled);
setEnabledRecursive(mainFrame.getOperatorTree(), enabled);
setEnabledRecursive(mainFrame.getProcessContextEditor().getComponent(), enabled);
setEnabledRecursive(mainFrame.getXMLEditor(), enabled);
mainFrame.getActions().enableActions();
}
public static Color getOperatorColor(final Operator operator) {
OperatorDescription operatorDescription = operator.getOperatorDescription();
String groupKey = operatorDescription.getGroup();
/*
* Operators that use the extension tree root need to be handled differently.
*/
if (operatorDescription.isUsingExtensionTreeRoot()
&& groupKey.startsWith(OperatorDescription.EXTENSIONS_GROUP_IDENTIFIER)) {
// remove extension group identifier
groupKey = groupKey.substring(groupKey.indexOf('.') + 1, groupKey.length());
Color operatorColor = GROUP_TO_COLOR_MAP.get(groupKey, operatorDescription.getProvider());
if (operatorColor != StringColorMap.DEFAULT_COLOR) {
return operatorColor;
} else {
// either remove extension name (if more groups are present) or use extension name
// in case of top-level operator
groupKey = groupKey.substring(groupKey.indexOf('.') + 1, groupKey.length());
}
}
return GROUP_TO_COLOR_MAP.get(groupKey, operatorDescription.getProvider());
}
public static Color getOperatorColor(final String operatorGroup) {
return GROUP_TO_COLOR_MAP.get(operatorGroup);
}
/**
* This method adds the colors of the given property file to the global group colors
*/
public static void registerAdditionalGroupColors(final String groupProperties, final String pluginName,
final ClassLoader classLoader, Plugin provider) {
try {
GROUP_TO_COLOR_MAP.parseProperties(groupProperties, "group.", ".color", classLoader, provider);
} catch (IOException e) {
LogService.getRoot().warning("Cannot load operator group colors for plugin " + pluginName + ".");
}
}
/**
* Returns <code>true</code> if the given {@link MouseEvent} only exited to child components of
* the specified parent {@link Component}; <code>false</code> otherwise. This fixes the problem
* that Swing triggers MouseExited events when moving the mouse for example over a button inside
* the panel on which the mouse listener is.
*
* @param parent
* @param e
* @return
*/
public static boolean isMouseEventExitedToChildComponents(final Component parent, final MouseEvent e) {
if (parent == null) {
throw new IllegalArgumentException("parent must not be null!");
}
return SwingUtilities.getDeepestComponentAt(parent, e.getX(), e.getY()) != null;
}
/**
* Darkens the given {@link Color} by the specified factor.
*
* @param color
* @param factor
* @return
*/
public static Color darkenColor(final Color color, final float factor) {
// convert to H(ue) S(aturation) B(rightness), which is designed for
// this kind of operation
float hsb[] = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
// turn down brightness and return color
return Color.getHSBColor(hsb[0], hsb[1], factor * hsb[2]);
}
/**
* Changes the saturation of the given {@link Color} by multiplying the saturation with the
* specified factor.
*
* @param color
* the color to change
* @param factor
* the factor to multiply the current saturation with
* @return color with changed saturation
*/
public static Color saturateColor(final Color color, final float factor) {
// convert to H(ue) S(aturation) B(rightness), which is designed for
// this kind of operation
float hsb[] = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
// adjust saturation and return color
return Color.getHSBColor(hsb[0], factor * hsb[1], hsb[2]);
}
/**
* Makes the given {@link Color} a bit brighter, though not as much as {@link Color#brighter()}.
*
* @param color
* @return
*/
public static Color brightenColor(final Color color) {
// convert to H(ue) S(aturation) B(rightness), which is designed for
// this kind of operation
float hsb[] = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
// turn up brightness and return color
return Color.getHSBColor(hsb[0], hsb[1], 0.5f * (1f + hsb[2]));
}
/**
* Darkens the given {@link Color} a bit, though not as much as {@link Color#darker()}.
*
* @param color
* @return
*/
public static Color darkenColor(final Color color) {
return darkenColor(color, 0.9f);
}
/**
* Returns the hex value of this color to use in html with starting #
*
* @param color
* the color that should be converted to a hex string representation
* @return the hex value of this color to use in html
* @since 7.0
*/
public static String getColorHexValue(Color color) {
return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
}
/**
* Paints the given overlay icon over the base icon. This can be used for example to cross out
* icons. Provided the overlay icon has a transparent background, the base icon will still be
* visible.
*
* @param baseIcon
* @param overlayIcon
* @return
*/
public static ImageIcon createOverlayIcon(final ImageIcon baseIcon, final ImageIcon overlayIcon) {
if (baseIcon == null) {
throw new IllegalArgumentException("baseIcon must not be null!");
}
if (overlayIcon == null) {
throw new IllegalArgumentException("overlayIcon must not be null!");
}
BufferedImage bufferedImg = new BufferedImage(baseIcon.getIconWidth(), baseIcon.getIconHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) bufferedImg.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform identityAT = new AffineTransform();
g2.drawImage(baseIcon.getImage(), identityAT, null);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
g2.drawImage(overlayIcon.getImage(), identityAT, null);
g2.dispose();
return new ImageIcon(bufferedImg);
}
/**
* Creates an icon of the specified size and the given shape and colors.
*
* @param color
* the color which is used to fill the shape
* @param borderColor
* the color for the shape border
* @param width
* the width of the icon
* @param height
* the height of the icon
* @param shape
* the shape of the icon
* @return the icon, never {@code null}
*/
public static ImageIcon createIconFromColor(final Color color, final Color borderColor, final int width,
final int height, final Shape shape) {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fill(shape);
g2.setColor(borderColor);
g2.draw(shape);
g2.dispose();
return new ImageIcon(img);
}
/**
* If the input name exceeds the maximum length, it is shortened by cutting the input into two
* shortened parts and adding [...] in between.
*
* @param maxLength
* the maximum allowed input length
* @return the provided input if it does not exceed the maximum input length, otherwise the
* shortened input is returned.
*/
public static String getShortenedDisplayName(String input, int maxLength) {
if (input.length() > maxLength + BRACKETS.length()) {
StringBuilder builder = new StringBuilder();
builder.append(input.substring(0, maxLength / 2));
builder.append(BRACKETS);
builder.append(input.substring(input.length() - maxLength / 2 - 1));
return builder.toString();
}
return input;
}
/**
* Converts {@link KeyStroke} to a human-readable string.
*
* @param keyStroke
* the keystroke
* @return a human-readable string like 'Ctrl+E'
*/
public static String formatKeyStroke(KeyStroke keyStroke) {
StringBuilder builder = new StringBuilder();
String modifierString = KeyEvent.getKeyModifiersText(keyStroke.getModifiers());
String keyString = KeyEvent.getKeyText(keyStroke.getKeyCode());
if (modifierString != null && !modifierString.trim().isEmpty()) {
builder.append(modifierString);
builder.append("+");
}
builder.append(keyString);
return builder.toString();
}
/**
* Gets stripped text for a JComponent object
*
* @since 6.4.0
* @param jComponent
* The component object used for the calculation of the width
* @param text
* The text to show
* @param maxWidth
* Maximum width of component
* @param suffixLength
* Minimum length of the suffix after the separator
*/
public static String getStrippedJComponentText(JComponent jComponent, String text, int maxWidth, int suffixLength) {
if (jComponent == null || text == null || text.isEmpty()) {
return "";
}
if (maxWidth < 0) {
throw new IllegalArgumentException("Maximum width of JComponent has to be positive");
}
int prefixLength = text.length() - suffixLength;
while (getStringWidth(jComponent, text) > maxWidth) {
prefixLength -= RESIZE_STEP_WIDTH;
text = stripString(text, prefixLength, suffixLength);
if (prefixLength < suffixLength) {
break;
}
}
return text;
}
/**
* Gets precalculated width of string used in jComponent with the default font.
*
* @since 6.4.0
* @param jComponent
* The component, whose font is used
* @param string
* The string, whose width is calculated
*/
public static int getStringWidth(JComponent jComponent, String string) {
return getStringWidth(jComponent, string, jComponent.getFont());
}
/**
* Gets precalculated width of string used in jComponent.
*
* @since 6.4.0
* @param jComponent
* The component, whose font is used
* @param string
* The string, whose width is calculated
* @param font
* The font of the string
*/
public static int getStringWidth(JComponent jComponent, String string, Font font) {
if (jComponent == null) {
return 0;
}
if (string == null || string.isEmpty()) {
return 0;
}
return jComponent.getFontMetrics(font).stringWidth(string);
}
/**
* Returns stripped string, if input string is too long.
*
* Examples:
*
* stripString("1234", 2, 2) -> 1234
*
* stripString("1234", 2, 1) -> 12[...]4
*
* @since 6.4.0
* @param string
* The string to split
* @param beginLength
* Minimum length of prefix
* @param endLength
* Minimum length of suffix
*/
public static String stripString(String string, int beginLength, int endLength) {
if (string == null || string.isEmpty()) {
return "";
}
if (beginLength < 0) {
beginLength = 0;
}
if (endLength < 0) {
endLength = 0;
}
if (string.length() > beginLength + endLength) {
return string.substring(0, beginLength) + SEPARATOR + string.substring(string.length() - endLength);
} else {
return string;
}
}
/**
* Returns whether or not the control modifier is down during the event. This takes the OS into
* account, so on Mac it will check if the meta modifier is down during the event.
*
* @param e
* the event
* @return {@code true} if the control modifier is down on Windows/Linux or the meta modifier is
* down on Mac; {@code false} otherwise
*/
public static boolean isControlOrMetaDown(KeyEvent e) {
if (!IS_MAC) {
return e.isControlDown();
} else {
return e.isMetaDown();
}
}
/**
* Returns whether or not the control modifier is down during the event. This takes the OS into
* account, so on Mac it will check if the meta modifier is down during the event.
*
* @param e
* the event
* @return {@code true} if the control modifier is down on Windows/Linux or the meta modifier is
* down on Mac; {@code false} otherwise
*/
public static boolean isControlOrMetaDown(MouseEvent e) {
if (!IS_MAC) {
return e.isControlDown();
} else {
return e.isMetaDown();
}
}
/**
* Invokes a runnable on the EDT and waits until the execution has finished. If this method is
* called in the EDT the runnable is executed directly. Otherwise
* {@link SwingUtilities#invokeAndWait(Runnable)} is used to execute the runnable.
*
* @param runnable
* the {@link Runnable} to execute
*/
public static void invokeAndWait(final Runnable runnable) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
} else {
try {
SwingUtilities.invokeAndWait(runnable);
} catch (InvocationTargetException | InterruptedException ex) {
LogService.getRoot().log(Level.WARNING, "com.rapidminer.gui.tools.SwingTools.edt_event_failed", ex);
}
}
}
/**
* Invokes a runnable on the EDT and waits until the execution has finished. If this method is
* called in the EDT the runnable is executed directly. Otherwise
* {@link SwingUtilities#invokeAndWait(Runnable)} is used to execute the runnable.
*
* @param resultRunnable
* the {@link ResultRunnable} to execute
* @return the value returned by {@link ResultRunnable}
* @since 6.5.0
*/
public static <T> T invokeAndWaitWithResult(final ResultRunnable<T> resultRunnable) {
final ResultContainer<T> resultContainer = new ResultContainer<>();
if (SwingUtilities.isEventDispatchThread()) {
resultContainer.value = resultRunnable.run();
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
resultContainer.value = resultRunnable.run();
}
});
} catch (InvocationTargetException | InterruptedException ex) {
LogService.getRoot().log(Level.WARNING, "com.rapidminer.gui.tools.SwingTools.edt_event_failed", ex);
}
}
return resultContainer.value;
}
/**
* Invokes a runnable on the EDT via {@link SwingUtilities#invokeLater(Runnable)} or directly if
* this method is called from within the EDT.
*
* @param runnable
* the {@link Runnable} to execute
* @since 6.5.0
*/
public static void invokeLater(final Runnable runnable) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
} else {
SwingUtilities.invokeLater(runnable);
}
}
/**
* Adds a help icon to the provided JPanel which shows a tooltip when hovering over it.
*
* @param tooltipContent
* the content of the tooltip
* @param labelPanel
* the panel which will be used to add the label. The panel needs to have a
* {@link BorderLayout} as layout manager as the label will be added with the
* constraint {@link BorderLayout#EAST}.
* @param owner
* the dialog owner of the labelPanel
* @since 7.0.0
*/
public static void addTooltipHelpIconToLabel(final String tooltipContent, JPanel labelPanel, final JDialog owner) {
final JLabel helpLabel = initializeHelpLabel(labelPanel);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TipProvider tipProvider = new TipProvider() {
@Override
public String getTip(Object id) {
if (id == null) {
return null;
} else {
return tooltipContent;
}
}
@Override
public Object getIdUnder(Point point) {
return helpLabel;
}
@Override
public Component getCustomComponent(Object id) {
return null;
}
};
setupTooltip(tipProvider, owner, helpLabel);
}
});
}
/**
* Adds a tooltip associated with the tipProvider to the helpLabel.
*
* @param tipProvider
* provides the tooltip.
* @param owner
* the owner of the dialog of which contains the helpLabel
* @param helpLabel
* the label for which to display the tooltip
* @since 7.0.0
*/
public static void setupTooltip(TipProvider tipProvider, final JDialog owner, final JLabel helpLabel) {
ToolTipWindow toolTipWindow = new ToolTipWindow(owner, tipProvider, helpLabel, TooltipLocation.BELOW);
toolTipWindow.setOnlyWhenFocussed(false);
toolTipWindow.setToolTipDelay(TOOL_TIP_DELAY);
}
/**
* Creates a helpLabel and adds it to the labelPanel. The helpLabel shows a help icon.
*
* @param labelPanel
* the panel which will be used to add the label. The panel needs to have a
* {@link BorderLayout} as layout manager as the label will be added with the
* constraint {@link BorderLayout#EAST}.
* @return the helpLabel for further use
* @since 7.0.0
*/
public static JLabel initializeHelpLabel(JPanel labelPanel) {
JPanel helpPanel = new JPanel();
helpPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTH;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(0, 3, 0, 0);
final JLabel helpLabel = new JLabel();
helpLabel.setIcon(createIcon(HELP_ICON_PATH));
helpLabel.setFocusable(false);
gbc.anchor = GridBagConstraints.CENTER;
helpPanel.add(helpLabel, gbc);
gbc.gridy += 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weighty = 1.0;
gbc.insets = new Insets(0, 0, 0, 0);
helpPanel.add(new JLabel(), gbc);
labelPanel.add(helpPanel, BorderLayout.EAST);
return helpLabel;
}
/**
* Attempts to disable the default clear type rendering on Windows such that rendering hints can
* be used. Has no effect on operating systems other than Microsoft Windows. Might fail
* silently.
*
* @param component
* the component for which to disable clear type in order to use rendering hints
*/
public static void disableClearType(JComponent component) {
if (SystemInfoUtilities.getOperatingSystem() == OperatingSystem.WINDOWS && AA_TEXT_PROPERTY != null) {
component.putClientProperty(AA_TEXT_PROPERTY, null);
}
}
/**
* Looks up the internal property {@code SwingUtilities2.AA_TEXT_PROPERTY_KEY}, returns
* {@code null} if not defined or if running on an operating system other than Microsoft
* Windows.
* <p>
* The implementation uses reflection to look up the property, since it it not part of the
* official Swing specification and might not be available.
*
* @return the property to disable clear type on windows or {@code null}
* @see http://stackoverflow.com/questions/18764585/text-antialiasing-broken-in-java-1-7-windows
*/
private static Object getAaTextProperty() {
Object aatextProperty = null;
if (SystemInfoUtilities.getOperatingSystem() == OperatingSystem.WINDOWS) {
try {
Class<?> clazz = Class.forName("sun.swing.SwingUtilities2");
Field field = clazz.getField("AA_TEXT_PROPERTY_KEY");
if (field != null) {
aatextProperty = field.get(null);
}
} catch (Throwable e) {
// do nothing, cannot use the property
}
}
return aatextProperty;
}
}