/**
* 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.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import com.rapidminer.core.license.ProductConstraintManager;
import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.actions.CloseAllResultsAction;
import com.rapidminer.gui.actions.StoreInRepositoryAction;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.gui.tools.ResourceLabel;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.viewer.metadata.MetaDataStatisticsViewer;
import com.rapidminer.license.ConstraintNotRestrictedException;
import com.rapidminer.license.LicenseConstants;
import com.rapidminer.license.LicenseEvent;
import com.rapidminer.license.LicenseEvent.LicenseEventType;
import com.rapidminer.license.LicenseManagerListener;
import com.rapidminer.license.LicenseManagerRegistry;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.ResultObject;
import com.rapidminer.tools.Tools;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableActionCustomizer;
/**
* Dockable containing a single result.
*
* @author Simon Fischer
*
*/
public class ResultTab extends JPanel implements Dockable {
private static final long serialVersionUID = 1L;
public static final String DOCKKEY_PREFIX = "result_";
/** the max length of dockkey names */
private static final int MAX_DOCKNAME_LENGTH = 80;
private Component label;
private JPanel component;
private final DockKey dockKey;
private final String id;
private ResultObject resultObject = null;
private LicenseManagerListener licenseListener;
public ResultTab(String id) {
setLayout(new BorderLayout());
this.id = id;
this.dockKey = new DockKey(id, "Result " + id);
this.dockKey.setDockGroup(MainFrame.DOCK_GROUP_RESULTS);
this.dockKey.setName(id);
this.dockKey.setFloatEnabled(true);
this.dockKey.setIconDisplayed(true);
// results can be closed with ctrl+w
this.dockKey.putProperty(DockKey.PROPERTY_SHORTCUT_CLOSING_ENABLED, Boolean.TRUE);
DockableActionCustomizer customizer = new DockableActionCustomizer() {
@Override
public void visitTabSelectorPopUp(JPopupMenu popUpMenu, Dockable dockable) {
popUpMenu.add(new JMenuItem(new StoreInRepositoryAction(resultObject)));
popUpMenu.add(new JMenuItem(new CloseAllResultsAction(RapidMinerGUI.getMainFrame())));
}
};
customizer.setTabSelectorPopUpCustomizer(true); // enable tabbed dock custom popup menu
// entries
this.dockKey.setActionCustomizer(customizer);
label = makeStandbyLabel();
add(label, BorderLayout.CENTER);
addLicenseLimitListener();
}
/**
* Adds a license listener that updates all {@ResultLimitPanel}s which might be contained in
* subcomponents.
*/
private void addLicenseLimitListener() {
licenseListener = new LicenseManagerListener() {
@Override
public <S, C> void handleLicenseEvent(LicenseEvent<S, C> event) {
if (event.getType() == LicenseEventType.ACTIVE_LICENSE_CHANGED) {
Integer licenseLimit;
try {
licenseLimit = LicenseManagerRegistry.INSTANCE.get().getConstraintValue(
ProductConstraintManager.INSTANCE.getProduct(), LicenseConstants.DATA_ROW_CONSTRAINT);
} catch (ConstraintNotRestrictedException e) {
licenseLimit = null;
}
if (component != null) {
searchAllChildren(component, licenseLimit);
}
}
}
/**
* Go recursively through all components in search of {@link ResultLimitPanel}s which
* need to update their text.
*/
private void searchAllChildren(Container component, Integer licenseLimit) {
for (Component child : component.getComponents()) {
if (child instanceof ResultLimitPanel) {
SwingTools.invokeLater(() -> {
((ResultLimitPanel) child).licenseUpdated(licenseLimit);
});
} else if (child instanceof Container) {
searchAllChildren((Container) child, licenseLimit);
}
}
}
};
LicenseManagerRegistry.INSTANCE.get().registerLicenseManagerListener(licenseListener);
}
/**
* Creates a component for this object and displays it. This method does not have to be called
* on the EDT. It executes a time consuming task and should be called from a
* {@link ProgressThread}.
*/
public void showResult(final ResultObject resultObject) {
if (resultObject != null) {
this.resultObject = resultObject;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (label != null) {
remove(label);
label = null;
}
if (resultObject != null) {
String name = resultObject.getName();
if (resultObject.getSource() != null) {
name += " (" + resultObject.getSource() + ")";
}
// without this, the name could be too long and cut off the close button (and
// even exit the screen)
name = SwingTools.getShortenedDisplayName(name, MAX_DOCKNAME_LENGTH);
dockKey.setName(name);
dockKey.setTooltip(Tools.toString(resultObject.getProcessingHistory(), " \u2192 "));
label = makeStandbyLabel();
add(label, BorderLayout.CENTER);
} else {
if (id.startsWith(DOCKKEY_PREFIX + "process_")) {
String number = id.substring((DOCKKEY_PREFIX + "process_").length());
label = new ResourceLabel("resulttab.cannot_be_restored_process_result", number);
dockKey.setName("Result #" + number);
} else {
label = new ResourceLabel("resulttab.cannot_be_restored");
dockKey.setName("Result " + id);
}
((JComponent) label).setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(label, BorderLayout.CENTER);
}
// remove old component
if (component != null) {
remove(component);
}
}
});
if (resultObject != null) {
final JPanel newComponent = createComponent(resultObject, null);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (label != null) {
remove(label);
label = null;
}
component = newComponent;
add(component, BorderLayout.CENTER);
if (component instanceof JComponent) {
dockKey.setIcon((Icon) ((JComponent) component)
.getClientProperty(ResultDisplayTools.CLIENT_PROPERTY_RAPIDMINER_RESULT_ICON));
}
}
});
}
}
private static JComponent makeStandbyLabel() {
Box labelBox = new Box(BoxLayout.Y_AXIS);
labelBox.add(Box.createVerticalGlue());
Box horizontalBox = Box.createHorizontalBox();
horizontalBox.add(Box.createHorizontalGlue());
horizontalBox.add(new ResourceLabel("resulttab.creating_display"));
horizontalBox.add(Box.createHorizontalGlue());
labelBox.add(horizontalBox);
labelBox.add(Box.createVerticalGlue());
return labelBox;
}
/**
* Creates an appropriate name, appending a number to make names unique, and calls
* {@link #createVisualizationComponent(IOObject, IOContainer, String)}.
*/
private JPanel createComponent(ResultObject resultObject, IOContainer resultContainer) {
final String resultName = RendererService.getName(resultObject.getClass());
String usedResultName = resultObject.getName();
if (usedResultName == null) {
usedResultName = resultName;
}
this.resultObject = resultObject;
return ResultDisplayTools.createVisualizationComponent(resultObject, resultContainer, id + ": " + usedResultName);
}
@Override
public Component getComponent() {
return this;
}
@Override
public DockKey getDockKey() {
return dockKey;
}
public void freeResources() {
if (component != null) {
stopStatisticsRecursively(component);
remove(component);
component = null;
resultObject = null;
}
LicenseManagerRegistry.INSTANCE.get().removeLicenseManagerListener(licenseListener);
}
/**
* Looks recursively for the {@link MetaDataStatisticsViewer} and stops its statistics
* calculation.
*
* @param component
* the component whose children should be searched
*/
private void stopStatisticsRecursively(Container component) {
for (Component child : component.getComponents()) {
if (child instanceof MetaDataStatisticsViewer) {
((MetaDataStatisticsViewer) child).stop();
} else if (child instanceof Container) {
stopStatisticsRecursively((Container) child);
}
}
}
/**
* @return the panel which displays the actual result
*/
public JPanel getResultViewComponent() {
return component;
}
}