/**
* 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.processeditor.results;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.List;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingWorker;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import com.rapidminer.Process;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.flow.processrendering.draw.ProcessDrawUtils;
import com.rapidminer.gui.look.Colors;
import com.rapidminer.gui.look.RapidLookAndFeel;
import com.rapidminer.gui.renderer.DefaultTextRenderer;
import com.rapidminer.gui.renderer.Renderer;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.gui.tools.ExtendedHTMLJEditorPane;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.ResultObject;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.report.Renderable;
import com.rapidminer.report.Reportable;
import com.rapidminer.repository.Entry;
import com.rapidminer.repository.IOObjectEntry;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.tools.ReferenceCache;
/**
* Displays an overview of a single IOObject. Does not remember the IOObject itself.
*
* @author Simon Fischer, Marco Boeck
*
*/
public class SingleResultOverview extends JPanel {
private static final int MAX_RESULT_STRING_LENGTH = 2048;
private static final long serialVersionUID = 1L;
private static final ReferenceCache<ResultObject> RESULT_OBJECT_CACHE = new ReferenceCache<>(20);
private final JLabel title;
private final Component main;
private String repositoryLocation = null;
private RepositoryLocation processFolderLocation = null;
private ReferenceCache<ResultObject>.Reference ioObject;
static final int MIN_HEIGHT = 300;
static final int MIN_WIDTH = 300;
private final Action RESTORE_FROM_REPOSITORY = new ResourceAction("resulthistory.restore_data") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
final ProgressThread downloadProgressThread = new ProgressThread("download_from_repository") {
@Override
public void run() {
try {
RepositoryLocation location = null;
if (RepositoryLocation.isAbsolute(repositoryLocation)) {
location = new RepositoryLocation(repositoryLocation);
} else {
location = new RepositoryLocation(processFolderLocation, repositoryLocation);
}
Entry entry = location.locateEntry();
if (entry instanceof IOObjectEntry) {
IOObjectEntry data = (IOObjectEntry) entry;
ResultObject result = (ResultObject) data.retrieveData(this.getProgressListener());
result.setSource(data.getLocation().toString());
RapidMinerGUI.getMainFrame().getResultDisplay().showResult(result);
} else {
SwingTools.showSimpleErrorMessage("cannot_fetch_data_from_repository", "Not an IOObject.");
}
} catch (Exception e1) {
SwingTools.showSimpleErrorMessage("cannot_fetch_data_from_repository", e1);
}
}
};
downloadProgressThread.start();
}
};
private final Action OPEN_DATA = new ResourceAction("resulthistory.open_data") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
ResultObject referenced = ioObject.get();
if (referenced != null) {
RapidMinerGUI.getMainFrame().getResultDisplay().showResult(referenced);
}
}
};
private BufferedImage img;
public SingleResultOverview(IOObject result, Process process, int resultIndex) {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(0, 0, 10, 0);
gbc.anchor = GridBagConstraints.NORTHWEST;
MetaData metaData = MetaData.forIOObject(result, true);
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5),
new RapidBorder(ProcessDrawUtils.getColorFor(metaData), RapidLookAndFeel.CORNER_DEFAULT_RADIUS, 35)));
setBackground(Colors.WHITE);
if (result instanceof ResultObject) {
this.ioObject = RESULT_OBJECT_CACHE.newReference((ResultObject) result);
}
if (process.getRootOperator().getSubprocess(0).getInnerSinks().getNumberOfPorts() > resultIndex) {
InputPort resultPort = process.getRootOperator().getSubprocess(0).getInnerSinks().getPortByIndex(resultIndex);
IOObject other = resultPort.getAnyDataOrNull();
if (result == other) { // make sure result does not come from collecting unconnected
// outputs, but is really
// from inner sink
if (process.getContext().getOutputRepositoryLocations().size() > resultIndex) {
repositoryLocation = process.getContext().getOutputRepositoryLocations().get(resultIndex).toString();
processFolderLocation = process.getRepositoryLocation() != null
? process.getRepositoryLocation().parent() : null;
}
}
}
String name = result.getClass().getSimpleName();
if (result instanceof ExampleSet) {
main = makeMainLabel("<html>" + metaData.getDescription() + "</html>");
name = ((ResultObject) result).getName();
} else {
name = RendererService.getName(result.getClass());
List<Renderer> renderers = RendererService.getRenderers(name);
if (renderers.isEmpty()) {
main = makeTextRenderer(result);
} else {
Component component = null;
for (Renderer renderer : renderers) {
if (!(renderer instanceof DefaultTextRenderer)) {
IOContainer dummy = new IOContainer();
Reportable reportable = renderer.createReportable(result, dummy, 800, 500);
if (reportable instanceof Renderable) {
updatePreviewImage();
component = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(java.awt.Graphics g) {
if (img != null) {
g.drawImage(img, 0, 0, null);
}
}
};
break;
}
} else {
component = makeTextRenderer(result);
break;
}
}
if (component == null) {
main = makeTextRenderer(result);
} else {
main = component;
}
}
}
StringBuilder b = new StringBuilder();
b.append("<html><strong>").append(name);
b.append("</strong>");
if (result.getSource() != null) {
b.append(" (").append(result.getSource()).append(")");
}
if (repositoryLocation != null && !repositoryLocation.isEmpty()) {
b.append("<br/><small>").append(repositoryLocation).append("</small>");
} else {
b.append("<br/><small>Result not stored in repository.</small>");
}
b.append("</html>");
title = new JLabel(b.toString());
add(title, gbc);
main.setPreferredSize(new Dimension(MIN_WIDTH - 10, MIN_HEIGHT - 65));
gbc.gridy += 1;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(main, gbc);
addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
showContextMenu(e.getPoint());
}
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
showContextMenu(e.getPoint());
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
showContextMenu(e.getPoint());
}
}
private void showContextMenu(Point point) {
JPopupMenu menu = new JPopupMenu();
boolean empty = true;
if (repositoryLocation != null && !repositoryLocation.isEmpty()) {
menu.add(RESTORE_FROM_REPOSITORY);
empty = false;
}
if (ioObject != null && ioObject.get() != null) {
menu.add(OPEN_DATA);
empty = false;
}
if (!empty) {
menu.show(SingleResultOverview.this, (int) point.getX(), (int) point.getY());
}
}
});
addAncestorListener(new AncestorListener() {
@Override
public void ancestorRemoved(AncestorEvent event) {
// not needed
}
@Override
public void ancestorMoved(AncestorEvent event) {
// not needed
}
@Override
public void ancestorAdded(AncestorEvent event) {
// update image (correct size is now known)
updatePreviewImage();
}
});
}
/**
* Create a text renderer for this result.
*
* @param result
* @return
*/
private Component makeTextRenderer(IOObject result) {
if (result instanceof ResultObject) {
String resultString = ((ResultObject) result).toResultString();
if (resultString.length() > MAX_RESULT_STRING_LENGTH) {
resultString = resultString.substring(0, MAX_RESULT_STRING_LENGTH);
}
return makeMainLabel("<html><pre>" + resultString + "</pre></html>");
} else {
return makeMainLabel("No information available.");
}
}
/**
* Creates the main text representation of this result.
*
* @param text
* @return
*/
private Component makeMainLabel(String text) {
JEditorPane label = new ExtendedHTMLJEditorPane("text/html", text);
StyleSheet css = ((HTMLEditorKit) label.getEditorKit()).getStyleSheet();
css.addRule("body {font-family:Sans;font-size:11pt}");
css.addRule("h3 {margin:0; padding:0}");
css.addRule("h4 {margin-bottom:0; margin-top:1ex; padding:0}");
css.addRule("p {margin-top:0; margin-bottom:1ex; padding:0}");
css.addRule("ul {margin-top:0; margin-bottom:1ex; list-style-image: url("
+ getClass().getResource("/com/rapidminer/resources/icons/modern/help/circle.png") + ")}");
css.addRule("ul li {padding-bottom: 2px}");
css.addRule("li.outPorts {padding-bottom: 0px}");
css.addRule("ul li ul {margin-top:0; margin-bottom:1ex; list-style-image: url("
+ getClass().getResource("/com/rapidminer/resources/icons/modern/help/line.png") + ")");
css.addRule("li ul li {padding-bottom:0}");
label.setEditable(false);
label.setBackground(Colors.WHITE);
JScrollPane pane = new JScrollPane(label);
pane.setBackground(Colors.WHITE);
pane.setBorder(null);
return pane;
}
/**
* Updates the preview renderable image in a {@link SwingWorker}.
*/
private void updatePreviewImage() {
final IOObject result = ioObject.get();
if (result != null) {
String name = RendererService.getName(result.getClass());
final List<Renderer> renderers = RendererService.getRenderers(name);
if (renderers.isEmpty()) {
return;
}
SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
int width = Math.max(getSize().width, MIN_WIDTH);
int height = Math.max(getSize().height, MIN_HEIGHT);
for (Renderer renderer : renderers) {
Reportable reportable = renderer.createReportable(result, new IOContainer(), 800, 600);
if (reportable instanceof Renderable) {
Renderable renderable = (Renderable) reportable;
renderable.prepareRendering();
int preferredWidth = renderable.getRenderWidth(800);
int preferredHeight = renderable.getRenderHeight(600);
img = new BufferedImage(preferredWidth, preferredHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = (Graphics2D) img.getGraphics();
graphics.setColor(Colors.WHITE);
graphics.fillRect(0, 0, 5000, 3000);
double scale = Math.min((double) width / (double) preferredWidth,
(double) height / (double) preferredHeight);
graphics.scale(scale, scale);
renderable.render(graphics, preferredWidth, preferredHeight);
break;
}
}
return null;
}
@Override
public void done() {
main.repaint();
}
};
sw.execute();
}
}
}