package de.lmu.ifi.dbs.elki.gui.minigui; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2011 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team 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 . */ import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; import java.util.logging.Level; import javax.swing.AbstractListModel; import javax.swing.BoxLayout; import javax.swing.ComboBoxModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import de.lmu.ifi.dbs.elki.KDDTask; import de.lmu.ifi.dbs.elki.gui.util.DynamicParameters; import de.lmu.ifi.dbs.elki.gui.util.LogPanel; import de.lmu.ifi.dbs.elki.gui.util.ParameterTable; import de.lmu.ifi.dbs.elki.gui.util.ParametersModel; import de.lmu.ifi.dbs.elki.gui.util.SavedSettingsFile; import de.lmu.ifi.dbs.elki.logging.Logging; import de.lmu.ifi.dbs.elki.utilities.FormatUtil; import de.lmu.ifi.dbs.elki.utilities.optionhandling.ParameterException; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.SerializedParameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.TrackParameters; import de.lmu.ifi.dbs.elki.workflow.LoggingStep; import de.lmu.ifi.dbs.elki.workflow.OutputStep; /** * Minimal GUI built around a table-based parameter editor. * * @author Erich Schubert * * @apiviz.composedOf SettingsComboboxModel * @apiviz.composedOf LoggingStep * @apiviz.owns de.lmu.ifi.dbs.elki.gui.util.ParameterTable * @apiviz.owns de.lmu.ifi.dbs.elki.gui.util.DynamicParameters */ public class MiniGUI extends JPanel { /** * Serial version */ private static final long serialVersionUID = 1L; /** * Filename for saved settings */ public static final String SAVED_SETTINGS_FILENAME = "MiniGUI-saved-settings.txt"; /** * Newline used in output. */ public static final String NEWLINE = System.getProperty("line.separator"); /** * ELKI logger for the GUI */ protected static final Logging logger = Logging.getLogger(MiniGUI.class); /** * Logging output area. */ protected LogPanel outputArea; /** * The parameter table */ protected ParameterTable parameterTable; /** * Parameter storage */ protected DynamicParameters parameters; /** * Settings storage */ protected SavedSettingsFile store = new SavedSettingsFile(SAVED_SETTINGS_FILENAME); /** * Combo box for saved settings */ protected JComboBox savedCombo; /** * Model to link the combobox with */ protected SettingsComboboxModel savedSettingsModel; /** * The "run" button. */ protected JButton runButton; /** * Constructor */ public MiniGUI() { super(); this.setLayout(new GridBagLayout()); { // Button panel JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); // Combo box for saved settings savedSettingsModel = new SettingsComboboxModel(store); savedCombo = new JComboBox(savedSettingsModel); savedCombo.setEditable(true); savedCombo.setSelectedItem("[Saved Settings]"); buttonPanel.add(savedCombo); // button to load settings JButton loadButton = new JButton("Load"); loadButton.setMnemonic(KeyEvent.VK_L); loadButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String key = savedSettingsModel.getSelectedItem(); ArrayList settings = store.get(key); if(settings != null) { outputArea.clear(); outputArea.publish("Parameters: " + FormatUtil.format(settings, " ") + NEWLINE, Level.INFO); doSetParameters(settings); } } }); buttonPanel.add(loadButton); // button to save settings JButton saveButton = new JButton("Save"); saveButton.setMnemonic(KeyEvent.VK_S); saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String key = savedSettingsModel.getSelectedItem(); // Stop editing the table. parameterTable.editCellAt(-1, -1); store.put(key, parameters.serializeParameters()); try { store.save(); } catch(IOException e1) { logger.exception(e1); } savedSettingsModel.update(); } }); buttonPanel.add(saveButton); // button to remove saved settings JButton removeButton = new JButton("Remove"); removeButton.setMnemonic(KeyEvent.VK_E); removeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String key = savedSettingsModel.getSelectedItem(); store.remove(key); try { store.save(); } catch(IOException e1) { logger.exception(e1); } savedCombo.setSelectedItem("[Saved Settings]"); savedSettingsModel.update(); } }); buttonPanel.add(removeButton); // button to launch the task runButton = new JButton("Run Task"); runButton.setMnemonic(KeyEvent.VK_R); runButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startTask(); } }); buttonPanel.add(runButton); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 1; constraints.weightx = 1.0; constraints.weighty = 0.01; add(buttonPanel, constraints); } { // Setup parameter storage and table model this.parameters = new DynamicParameters(); ParametersModel parameterModel = new ParametersModel(parameters); parameterModel.addTableModelListener(new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { // logger.debug("Change event."); updateParameterTable(); } }); // Create parameter table parameterTable = new ParameterTable(parameterModel, parameters); // Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(parameterTable); // Add the scroll pane to this panel. GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 1; constraints.weighty = 1; add(scrollPane, constraints); } { // setup text output area outputArea = new LogPanel(); // Create the scroll pane and add the table to it. JScrollPane outputPane = new JScrollPane(outputArea); outputPane.setPreferredSize(new Dimension(800, 400)); // Add the output pane to the bottom GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraints.gridx = 0; constraints.gridy = 2; constraints.weightx = 1; constraints.weighty = 1; add(outputPane, constraints); // reconfigure logging outputArea.becomeDefaultLogger(); } // refresh Parameters ArrayList ps = new ArrayList(); doSetParameters(ps); // load saved settings (we wanted to have the logger first!) try { store.load(); savedSettingsModel.update(); } catch(FileNotFoundException e) { // Ignore - probably didn't save any settings yet. } catch(IOException e) { logger.exception(e); } } /** * Serialize the parameter table and run setParameters() */ protected void updateParameterTable() { parameterTable.setEnabled(false); ArrayList params = parameters.serializeParameters(); outputArea.clear(); outputArea.publish("Parameters: " + FormatUtil.format(params, " ") + NEWLINE, Level.INFO); doSetParameters(params); parameterTable.setEnabled(true); } /** * Do the actual setParameters invocation. * * @param params Parameters */ protected void doSetParameters(List params) { SerializedParameterization config = new SerializedParameterization(params); TrackParameters track = new TrackParameters(config); track.tryInstantiate(LoggingStep.class); track.tryInstantiate(KDDTask.class); config.logUnusedParameters(); // config.logAndClearReportedErrors(); if(config.getErrors().size() > 0) { reportErrors(config); runButton.setEnabled(false); } else { runButton.setEnabled(true); } List remainingParameters = config.getRemainingParameters(); // update table: parameterTable.setEnabled(false); parameters.updateFromTrackParameters(track); // Add remaining parameters if(remainingParameters != null && !remainingParameters.isEmpty()) { DynamicParameters.RemainingOptions remo = new DynamicParameters.RemainingOptions(); try { remo.setValue(FormatUtil.format(remainingParameters, " ")); } catch(ParameterException e) { logger.exception(e); } BitSet bits = new BitSet(); bits.set(DynamicParameters.BIT_INVALID); bits.set(DynamicParameters.BIT_SYNTAX_ERROR); parameters.addParameter(remo, remo.getValue(), bits, 0); } parameterTable.revalidate(); parameterTable.setEnabled(true); } /** * Do a full run of the KDDTask with the specified parameters. */ protected void startTask() { parameterTable.editCellAt(-1, -1); parameterTable.setEnabled(false); final ArrayList params = parameters.serializeParameters(); parameterTable.setEnabled(true); runButton.setEnabled(false); outputArea.clear(); outputArea.publish("Running: " + FormatUtil.format(params, " ") + NEWLINE, Level.INFO); SwingWorker r = new SwingWorker() { @Override public Void doInBackground() { SerializedParameterization config = new SerializedParameterization(params); config.tryInstantiate(LoggingStep.class); KDDTask task = config.tryInstantiate(KDDTask.class); try { config.logUnusedParameters(); if(config.getErrors().size() == 0) { task.run(); } else { reportErrors(config); } } catch(Exception e) { logger.exception(e); } return null; } @Override protected void done() { super.done(); runButton.setEnabled(true); } }; r.execute(); } /** * Report errors in a single error log record. * * @param config Parameterization */ protected void reportErrors(SerializedParameterization config) { StringBuffer buf = new StringBuffer(); buf.append("Could not run task because of configuration errors:" + NEWLINE + NEWLINE); for(ParameterException e : config.getErrors()) { buf.append(e.getMessage() + NEWLINE); } logger.warning(buf.toString()); config.clearErrors(); } /** * Create the GUI and show it. For thread safety, this method should be * invoked from the event-dispatching thread. * * args Command line arguments */ protected static void createAndShowGUI(String[] args) { // Create and set up the window. JFrame frame = new JFrame("ELKI MiniGUI"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // Class cls = // ClassLoader.getSystemClassLoader().loadClass("org.jdesktop.swinghelper.debug.CheckThreadViolationRepaintManager"); // RepaintManager.setCurrentManager((RepaintManager) cls.newInstance()); } catch(Exception e) { // ignore } try { frame.setIconImage(new ImageIcon(KDDTask.class.getResource("elki-icon.png")).getImage()); } catch(Exception e) { // Ignore - icon not found is not fatal. } // Create and set up the content pane. MiniGUI newContentPane = new MiniGUI(); if (args != null && args.length > 0) { newContentPane.doSetParameters(Arrays.asList(args)); } newContentPane.setOpaque(true); // content panes must be opaque frame.setContentPane(newContentPane); // Display the window. frame.pack(); frame.setVisible(true); } /** * Main method that just spawns the UI. * * @param args command line parameters */ public static void main(final String[] args) { OutputStep.setDefaultHandlerVisualizer(); javax.swing.SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowGUI(args); } }); } /** * Class to interface between the saved settings list and a JComboBox * * @author Erich Schubert * * @apiviz.composedOf de.lmu.ifi.dbs.elki.gui.util.SavedSettingsFile */ class SettingsComboboxModel extends AbstractListModel implements ComboBoxModel { /** * Serial version */ private static final long serialVersionUID = 1L; /** * Settings storage */ protected SavedSettingsFile store; /** * Selected entry */ protected String selected = null; /** * Constructor * * @param store Store to access */ public SettingsComboboxModel(SavedSettingsFile store) { super(); this.store = store; } @Override public String getSelectedItem() { return selected; } @Override public void setSelectedItem(Object anItem) { if(anItem instanceof String) { selected = (String) anItem; } } @Override public Object getElementAt(int index) { return store.getElementAt(index).first; } @Override public int getSize() { return store.size(); } /** * Force an update */ public void update() { fireContentsChanged(this, 0, getSize() + 1); } } }