/* * RapidMiner * * Copyright (C) 2001-2011 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.processeditor.results; import java.awt.BorderLayout; import java.awt.Component; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.swing.JPanel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import com.rapidminer.BreakpointListener; import com.rapidminer.LoggingListener; import com.rapidminer.Process; import com.rapidminer.ProcessListener; import com.rapidminer.datatable.DataTable; import com.rapidminer.gui.MainFrame; import com.rapidminer.gui.RapidMinerGUI; import com.rapidminer.gui.processeditor.ProcessLogTab; import com.rapidminer.gui.tools.ExtendedJScrollPane; import com.rapidminer.gui.tools.ProgressThread; import com.rapidminer.gui.tools.ResourceDockKey; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.gui.tools.UpdateQueue; import com.rapidminer.gui.tools.dialogs.ConfirmDialog; import com.rapidminer.gui.tools.dialogs.DecisionRememberingConfirmDialog; import com.rapidminer.gui.viewer.DataTableViewer; import com.rapidminer.operator.IOContainer; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.Operator; import com.rapidminer.operator.ResultObject; import com.rapidminer.tools.LogService; import com.vlsolutions.swing.docking.DockKey; import com.vlsolutions.swing.docking.Dockable; import com.vlsolutions.swing.docking.DockableState; import com.vlsolutions.swing.docking.DockingDesktop; import com.vlsolutions.swing.docking.event.DockableStateChangeEvent; import com.vlsolutions.swing.docking.event.DockableStateChangeListener; import com.vlsolutions.swing.docking.event.DockingActionCloseEvent; import com.vlsolutions.swing.docking.event.DockingActionDockableEvent; import com.vlsolutions.swing.docking.event.DockingActionEvent; import com.vlsolutions.swing.docking.event.DockingActionListener; /** {@link ResultDisplay} that adds each result to an individual {@link Dockable}. * In addition, it displays a result history overview. * * @author Simon Fischer * */ public class DockableResultDisplay extends JPanel implements ResultDisplay { private static final long serialVersionUID = 1L; private final DockKey dockKey = new ResourceDockKey(RESULT_DOCK_KEY); private final Map<String,DataTable> dataTables = new HashMap<String,DataTable>(); private final UpdateQueue tableUpdateQueue = new UpdateQueue("ResultDisplayDataTableViewUpdater"); private final ResultOverview overview = new ResultOverview(); public DockableResultDisplay() { this.dockKey.setDockGroup(MainFrame.DOCK_GROUP_RESULTS); setLayout(new BorderLayout()); ExtendedJScrollPane overviewScrollpane = new ExtendedJScrollPane(overview); overviewScrollpane.setBorder(null); overviewScrollpane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); overviewScrollpane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); add(overviewScrollpane, BorderLayout.CENTER); tableUpdateQueue.start(); } public void init(MainFrame mf) { DockingDesktop desktop = mf.getDockingDesktop(); desktop.addDockingActionListener(new DockingActionListener() { @Override public void dockingActionPerformed(DockingActionEvent arg0) { } @Override public boolean acceptDockingAction(DockingActionEvent e) { if ((e instanceof DockingActionCloseEvent) && (((DockingActionDockableEvent) e).getDockable() == DockableResultDisplay.this)) { return SwingTools.showConfirmDialog("result.really_close", ConfirmDialog.YES_NO_OPTION) == ConfirmDialog.YES_OPTION; } else { return true; } } }); desktop.addDockableStateChangeListener(new DockableStateChangeListener() { @Override public void dockableStateChanged(DockableStateChangeEvent e) { if (e.getNewState().isClosed()) { if (e.getNewState().getDockable() instanceof ResultTab) { ResultTab rt = (ResultTab) e.getNewState().getDockable(); rt.freeResources(); RapidMinerGUI.getMainFrame().getPerspectives().removeFromAllPerspectives(rt); } else if (e.getNewState().getDockable() instanceof ProcessLogTab) { ProcessLogTab pt = (ProcessLogTab) e.getNewState().getDockable(); if (pt != null) { if (pt.getDataTable() != null) { dataTables.remove(pt.getDataTable().getName()); } pt.freeResources(); RapidMinerGUI.getMainFrame().getPerspectives().removeFromAllPerspectives(pt); } } } } }); } private boolean isAskingForPerspectiveSwitch = false; private void askForPerspectiveSwitch() { if (isAskingForPerspectiveSwitch || RapidMinerGUI.getMainFrame().getPerspectives().getCurrentPerspective().getName().equals("result")) { return; } else { try { isAskingForPerspectiveSwitch = true; if (DecisionRememberingConfirmDialog.confirmAction("show_results_on_creation", MainFrame.PROPERTY_RAPIDMINER_GUI_AUTO_SWITCH_TO_RESULTVIEW)) { if (SwingUtilities.isEventDispatchThread()) { RapidMinerGUI.getMainFrame().getPerspectives().showPerspective("result"); } else { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { RapidMinerGUI.getMainFrame().getPerspectives().showPerspective("result"); } }); } catch (InterruptedException e) { LogService.getRoot().log(Level.WARNING, "Error switching perspectives: "+e, e); } catch (InvocationTargetException e) { LogService.getRoot().log(Level.WARNING, "Error switching perspectives: "+e, e); } } } } finally { isAskingForPerspectiveSwitch = false; } } } public void showData(final IOContainer container, final String statusMessage) { if ((container == null) || (container.size() == 0)) { return; } final List<IOObject> ioobjects = Arrays.asList(container.getIOObjects()); new ProgressThread("creating_display") { @Override public void run() { try { getProgressListener().setTotal(ioobjects.size()+1); overview.addResults(RapidMinerGUI.getMainFrame().getProcess(), ioobjects, statusMessage); getProgressListener().setCompleted(1); int i = 0; for (IOObject ioobject : ioobjects) { if (ioobject instanceof ResultObject) { i++; showResultNow((ResultObject)ioobject, "process_"+i); getProgressListener().setCompleted(i+1); } } } finally { getProgressListener().complete(); } } }.start(); askForPerspectiveSwitch(); } private static int currentId; public void showResult(final ResultObject result) { showResult(result, "dynamic_" + (currentId++)); askForPerspectiveSwitch(); } /** Creates the display in a ProgressThread. */ private void showResult(final ResultObject result, final String id) { new ProgressThread("creating_display") { @Override public void run() { getProgressListener().setTotal(100); getProgressListener().setCompleted(10); showResultNow(result, id); getProgressListener().setCompleted(100); getProgressListener().complete(); } }.start(); } /** Creates a display on the current thread and displays it (on the EDT). */ private void showResultNow(final ResultObject result, final String id) { ResultTab tab = (ResultTab)RapidMinerGUI.getMainFrame().getDockingDesktop().getContext().getDockableByKey(ResultTab.DOCKKEY_PREFIX+id); if (tab == null) { tab = new ResultTab(ResultTab.DOCKKEY_PREFIX+id); } showTab(tab); tab.showResult(result); } /** Update the DataTableViewers. This does not happen on the EDT * and is executed asynchronously by an {@link UpdateQueue}. */ private void updateDataTables() { final Collection<DataTable> copy = new LinkedList<DataTable>(dataTables.values()); // this is time consuming, so execute off EDT tableUpdateQueue.execute(new Runnable() { public void run() { final Collection<DataTableViewer> viewers = new LinkedList<DataTableViewer>(); for (DataTable table : copy) { viewers.add(new DataTableViewer(table, true, DataTableViewer.PLOT_MODE)); } installDataTableViewers(viewers); } @Override public String toString() { return "Update data table list to size "+copy.size(); } }); } /** Adds the collection of components on the EDT (after removing the old tables. */ private void installDataTableViewers(final Collection<DataTableViewer> viewers) { SwingUtilities.invokeLater(new Runnable() { public void run() { for (DataTableViewer viewer : viewers) { ProcessLogTab tab = (ProcessLogTab) RapidMinerGUI.getMainFrame().getDockingDesktop().getContext().getDockableByKey(ProcessLogTab.DOCKKEY_PREFIX + viewer.getDataTable().getName()); if (tab == null) { tab = new ProcessLogTab(ProcessLogTab.DOCKKEY_PREFIX + viewer.getDataTable().getName()); } showTab(tab); tab.setDataTableViewer(viewer); } } }); } private void showTab(final Dockable dockable) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { RapidMinerGUI.getMainFrame().getPerspectives().showTabInAllPerspectives(dockable, DockableResultDisplay.this); } }); } public void addDataTable(final DataTable dataTable) { DockableResultDisplay.this.dataTables.put(dataTable.getName(), dataTable); updateDataTables(); } private void clear() { clear(true); } private void clear(boolean alsoClearLogs) { if (SwingUtilities.isEventDispatchThread()) { clearNow(alsoClearLogs); } else { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { clearNow(); } }); } catch (InterruptedException e) { LogService.getRoot().log(Level.WARNING, "Interupted while closing result tabs.", e); } catch (InvocationTargetException e) { LogService.getRoot().log(Level.WARNING, "Exception while closing result tabs: "+e, e); } } } private void clearNow() { clearNow(true); } private void clearNow(boolean alsoClearLogs) { List<Dockable> toClose = new LinkedList<Dockable>(); for (DockableState state : RapidMinerGUI.getMainFrame().getDockingDesktop().getContext().getDockables()) { if (state.getDockable().getDockKey().getKey().startsWith(ResultTab.DOCKKEY_PREFIX+"process_") || (alsoClearLogs && state.getDockable().getDockKey().getKey().startsWith(ProcessLogTab.DOCKKEY_PREFIX))) { toClose.add(state.getDockable()); } } if (!toClose.isEmpty() || !dataTables.isEmpty()) { if (DecisionRememberingConfirmDialog.confirmAction("result.close_before_run", RapidMinerGUI.PROPERTY_CLOSE_RESULTS_BEFORE_RUN)) { DockableResultDisplay.this.dataTables.clear(); //updateDataTables(); for (Dockable dockable : toClose) { if (dockable instanceof ResultTab) { ((ResultTab) dockable).freeResources(); } else if (dockable instanceof ProcessLogTab) { ((ProcessLogTab) dockable).freeResources(); } //RapidMinerGUI.getMainFrame().getDockingDesktop().close(dockable); RapidMinerGUI.getMainFrame().getPerspectives().removeFromAllPerspectives(dockable); } } } } @Override public void clearAll() { for (DockableState state : RapidMinerGUI.getMainFrame().getDockingDesktop().getContext().getDockables()) { if (state.getDockable().getDockKey().getKey().startsWith(ResultTab.DOCKKEY_PREFIX) || state.getDockable().getDockKey().getKey().startsWith(ProcessLogTab.DOCKKEY_PREFIX)) { RapidMinerGUI.getMainFrame().getPerspectives().removeFromAllPerspectives(state.getDockable()); } } } // Listeners private final LoggingListener logListener = new LoggingListener() { public void addDataTable(final DataTable dataTable) { DockableResultDisplay.this.addDataTable(dataTable); } public void removeDataTable(final DataTable dataTable) { DockableResultDisplay.this.dataTables.remove(dataTable.getName()); updateDataTables(); } }; private final ProcessListener processListener = new ProcessListener() { @Override public void processEnded(Process process) { } @Override public void processFinishedOperator(Process process, Operator op) { } @Override public void processStartedOperator(Process process, Operator op) { } @Override public void processStarts(Process process) { clear(); } }; private final BreakpointListener breakpointListener = new BreakpointListener() { @Override public void resume() { clear(false); } @Override public void breakpointReached(Process process, Operator op, IOContainer iocontainer, int location) { } }; // Dockable @Override public Component getComponent() { return this; } @Override public DockKey getDockKey() { return dockKey; } // ProcessEditor private Process process; @Override public void processChanged(Process process) { if (this.process != null) { this.process.removeLoggingListener(logListener); this.process.getRootOperator().removeProcessListener(processListener); this.process.removeBreakpointListener(breakpointListener); } this.process = process; if (this.process != null) { this.process.addLoggingListener(logListener); this.process.getRootOperator().addProcessListener(processListener); this.process.addBreakpointListener(breakpointListener); } } @Override public void processUpdated(Process process) { } @Override public void setSelection(List<Operator> selection) { } }