/*
* RapidMiner
*
* Copyright (C) 2001-2008 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.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.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
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.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.tools.LogService;
import com.rapidminer.tools.ParameterService;
import com.rapidminer.tools.Tools;
/**
* 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
* @version $Id: SwingTools.java,v 1.34 2008/08/05 08:14:28 ingomierswa Exp $
*/
public class SwingTools {
/** 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 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);
/** The Rapid-I orange color. */
public static final Color RAPID_I_ORANGE = new Color(242, 146, 0);
/** The Rapid-I brown color. */
public static final Color RAPID_I_BROWN = new Color(97, 66, 11);
/** 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);
/** Contains the small frame icons in all possible sizes. */
private static final List<Image> ALL_FRAME_ICONS = new LinkedList<Image>();
private static final String[] FRAME_ICON_SIZES = {
"16", "24", "32", "48", "64", "128"
};
private static String frameIconBaseName = "rapidminer_frame_icon_";
private static String iconType = RapidMinerGUI.LOOK_AND_FEELS[RapidMinerGUI.LOOK_AND_FEEL_MODERN];
static {
reloadFrameIcons();
}
public static void setupFrameIcons(String _iconBaseName) {
frameIconBaseName = _iconBaseName;
reloadFrameIcons();
}
private static void reloadFrameIcons() {
try {
ALL_FRAME_ICONS.clear();
for (String size : FRAME_ICON_SIZES) {
URL url = Tools.getResource(frameIconBaseName + size + ".png");
if (url != null) {
ALL_FRAME_ICONS.add(ImageIO.read(url));
}
}
} catch (IOException e) {
// ignore this and do not use frame icons
LogService.getGlobal().logWarning("Cannot load frame icons. Skipping...");
}
}
/**
* Returns the list of all available program icon sizes.
*/
public static List<Image> getFrameIconList() {
return ALL_FRAME_ICONS;
}
/** Returns the list of all possible frame icons. */
public static void setFrameIcon(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[] { ALL_FRAME_ICONS });
} catch (Throwable e) {
// ignore this and use single small icon
if (ALL_FRAME_ICONS.size() > 0)
frame.setIconImage(ALL_FRAME_ICONS.get(0));
}
} else {
if (ALL_FRAME_ICONS.size() > 0)
frame.setIconImage(ALL_FRAME_ICONS.get(0));
}
}
/** Returns the list of all possible frame icons. */
public static void setDialogIcon(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[] { ALL_FRAME_ICONS });
} catch (Throwable e) {
// ignore this and use no or parent icon
}
}
}
/** Creates a red gradient paint. */
public static GradientPaint makeRedPaint(double width, 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(double width, 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(double width, double height) {
return new GradientPaint(0f, 0f, LIGHT_YELLOW, (float) width / 2, (float) height / 2, LIGHTEST_YELLOW, true);
}
public static void setIconType(String newIconType) {
iconType = newIconType;
}
/** Tries to load the icon for the given resource. Returns null (and writes a warning) if the
* resource file cannot be loaded. This method automatically adds the icon path and the
* correct icon type (modern or classic). The given names must contain '/' instead of backslashes! */
public static ImageIcon createIcon(String iconName) {
return createImage("icons/" + iconType + "/" + iconName);
}
/** Tries to load the image for the given resource. Returns null (and writes a warning) if the
* resource file cannot be loaded. */
public static ImageIcon createImage(String imageName) {
URL url = Tools.getResource(imageName);
if (url != null) {
return new ImageIcon(url);
} else {
LogService.getGlobal().log("Cannot load image '" + imageName + "': icon will not be displayed", LogService.STATUS);
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(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. */
public static String transformToolTipText(String description, boolean addHTMLTags, int lineLength) {
String completeText = description.trim();
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();
}
/** Transforms the given class name array into a comma separated string of the short class names. */
public static String getStringFromClassArray(Class[] classes) {
StringBuffer outputString = new StringBuffer();
if (classes == null)
outputString.append("none");
else {
for (int i = 0; i < classes.length; i++) {
if (i != 0)
outputString.append(", ");
outputString.append(Tools.classNameWOPackage(classes[i]));
}
}
if (outputString.length() == 0)
outputString.append("none");
return outputString.toString();
}
/** Adds linebreaks 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();
}
/** Shows a very simple error message without any Java exception hints. */
public static void showVerySimpleErrorMessage(String message) {
JOptionPane.showMessageDialog(RapidMinerGUI.getMainFrame(), addLinebreaks(message), "Error", JOptionPane.ERROR_MESSAGE);
}
/** 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 erro message methods should never be
* invoked by operators or similar. */
public static void showSimpleErrorMessage(String message, Throwable e) {
JOptionPane.showMessageDialog(RapidMinerGUI.getMainFrame(), addLinebreaks(message) + Tools.classNameWOPackage(e.getClass()) + " caught:" + Tools.getLineSeparator() + addLinebreaks(e.getMessage()), "Error", JOptionPane.ERROR_MESSAGE);
}
/** Shows the final error message dialog. This dialog also allows to send a bugreport if
* the error was not (definitely) a user error. */
public static void showFinalErrorMessage(String message, Throwable e) {
JDialog dialog = ErrorDialog.create(message, e);
dialog.setLocationRelativeTo(RapidMinerGUI.getMainFrame());
dialog.setVisible(true);
}
/** Opens a file chooser with a reasonable start directory. If the extension is null, no file filters will be used. */
public static File chooseFile(Component parent, File file, boolean open, String extension, String extensionDescription) {
return chooseFile(parent, 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(Component parent, File file, boolean open, boolean onlyDirs, String extension, String extensionDescription) {
return chooseFile(parent,
file,
open,
onlyDirs,
extension == null ? null : new String[] { extension },
extensionDescription == null ? null : new String[] { extensionDescription });
}
/** Returns the user selected file. */
private static File chooseFile(Component parent, File file, boolean open, boolean onlyDirs, String[] extensions, String[] extensionDescriptions) {
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, file, open, onlyDirs, filters);
}
/**
* 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, File file, boolean open, boolean onlyDirs, FileFilter[] fileFilters) {
if (parent == null)
parent = RapidMinerGUI.getMainFrame();
JFileChooser fileChooser = createFileChooser(file, onlyDirs, fileFilters);
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);
}
}
File parentFile = selectedFile.getParentFile();
if (parentFile != null) {
List<Bookmark> bookmarks = null;
File bookmarksFile = new File(ParameterService.getUserRapidMinerDir(), ".bookmarks");
if (bookmarksFile.exists()) {
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.getAbsolutePath()));
Collections.sort(bookmarks);
BookmarkIO.writeBookmarks(bookmarks, bookmarksFile);
}
}
return selectedFile;
default:
return null;
}
}
/**
* 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(File file, boolean onlyDirs, FileFilter[] fileFilters) {
File directory = null;
if (file != null) {
if (file.isDirectory()) {
directory = file;
} else {
directory = file.getAbsoluteFile().getParentFile();
}
} else {
File processFile = null;
MainFrame mainFrame = RapidMinerGUI.getMainFrame();
if (mainFrame != null)
processFile = (mainFrame.getProcess() != null) ? mainFrame.getProcess().getProcessFile() : null;
if (processFile != null) {
directory = processFile.getAbsoluteFile().getParentFile();
} else {
directory = ParameterService.getUserWorkspace();
if (directory == null) {
FileSystemView fsv = FileSystemView.getFileSystemView();
directory = fsv.getDefaultDirectory();
}
}
}
JFileChooser fileChooser = new JFileChooser(directory);
if (onlyDirs)
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fileFilters != null) {
fileChooser.setAcceptAllFileFilterUsed(true);
for (int i = 0; i < fileFilters.length; i++)
fileChooser.addChoosableFileFilter(fileFilters[i]);
}
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(String title, String text) {
JPanel panel = new JPanel(new java.awt.BorderLayout());
JLabel label = new JLabel("<html><h2>" + title + "</h2>" + (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;
}
/**
* 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.
*/
public static String text2DisplayHtml(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 html tag.
*/
public static String text2SimpleHtml(String htmlText) {
if (htmlText == null)
return null;
String 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;
}
}
// apend 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(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, double max, 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;
}
}