diff options
Diffstat (limited to 'elki/src/main/java/de/lmu/ifi/dbs/elki/gui')
29 files changed, 449 insertions, 163 deletions
diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/GUIUtil.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/GUIUtil.java index b7d4ee18..210f4e2e 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/GUIUtil.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/GUIUtil.java @@ -35,6 +35,7 @@ import de.lmu.ifi.dbs.elki.logging.Logging; * GUI utilities. * * @author Erich Schubert + * @since 0.5.5 */ public final class GUIUtil { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractParameterConfigurator.java index d1d89d69..9905ad59 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractParameterConfigurator.java @@ -43,6 +43,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * Abstract class to produce a configurator for a particular parameter. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.has StockIcon * diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractSingleParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractSingleParameterConfigurator.java index de040cc7..bb298994 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractSingleParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/AbstractSingleParameterConfigurator.java @@ -34,6 +34,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * Base class for MiniGUI input helpers * * @author Erich Schubert + * @since 0.4.0 * * @param <T> Parameter type */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassListParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassListParameterConfigurator.java index ddc25f2e..0d76aa95 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassListParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassListParameterConfigurator.java @@ -56,6 +56,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * Additionally, the classes can in turn have additional parameters. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.uses ClassListParameter * @apiviz.uses ClassTree diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassParameterConfigurator.java index 7bc81fed..07f1e14f 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ClassParameterConfigurator.java @@ -59,6 +59,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * Additionally, the classes can in turn have additional parameters. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.uses ClassParameter */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ConfiguratorPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ConfiguratorPanel.java index 873417a0..b07aed06 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ConfiguratorPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ConfiguratorPanel.java @@ -48,6 +48,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * A panel that contains configurators for parameters. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.landmark * @apiviz.has de.lmu.ifi.dbs.elki.gui.configurator.ParameterConfigurator diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/EnumParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/EnumParameterConfigurator.java index 416b47bb..95ae3a04 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/EnumParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/EnumParameterConfigurator.java @@ -40,6 +40,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.EnumParameter; * TODO: offer radio buttons when just a few choices are available? * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.uses EnumParameter */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FileParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FileParameterConfigurator.java index f7218f35..9d3d53e5 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FileParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FileParameterConfigurator.java @@ -43,6 +43,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.FileParameter; * Provide a configuration panel to choose a file with a file selector button. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.uses FileParameter */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FlagParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FlagParameterConfigurator.java index 052c6ac6..5b3d42c7 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FlagParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/FlagParameterConfigurator.java @@ -37,6 +37,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag; * Provide a configuration panel to modify a boolean via a checkbox. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.uses Flag */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ParameterConfigurator.java index 217f3632..88b93d25 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/ParameterConfigurator.java @@ -29,12 +29,40 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.ListParamet import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.TrackParameters; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; +/** + * Interface for different configuration assistants for the multistep GUI. + * + * @author Erich Schubert + * @since 0.4.0 + */ public interface ParameterConfigurator { + /** + * Add a parameter to the panel. + * + * @param owner Owning ("parent") object + * @param param Parameter + * @param track Parameter tracker + */ public void addParameter(Object owner, Parameter<?> param, TrackParameters track); + /** + * Add a change listener + * + * @param listener Change listener + */ public void addChangeListener(ChangeListener listener); + /** + * Remove a change listener + * + * @param listener Change listener + */ public void removeChangeListener(ChangeListener listener); + /** + * Append the parameters to a list. + * + * @param params Parameter list (output) + */ public void appendParameters(ListParameterization params); }
\ No newline at end of file diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/TextParameterConfigurator.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/TextParameterConfigurator.java index 27c9d531..6c86f18a 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/TextParameterConfigurator.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/configurator/TextParameterConfigurator.java @@ -38,6 +38,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * Provide a configuration panel to input an arbitrary text parameter. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.uses Parameter */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/icons/StockIcon.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/icons/StockIcon.java index c0c7b91c..de450f4a 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/icons/StockIcon.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/icons/StockIcon.java @@ -36,6 +36,7 @@ import de.lmu.ifi.dbs.elki.logging.LoggingUtil; * Stock icon library for use in the GUI. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.landmark */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/minigui/MiniGUI.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/minigui/MiniGUI.java index 6edaa95d..7cba1e97 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/minigui/MiniGUI.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/minigui/MiniGUI.java @@ -4,7 +4,7 @@ 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) 2015 + Copyright (C) 2016 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team @@ -36,7 +36,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; @@ -46,8 +45,10 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingWorker; import javax.swing.event.TableModelEvent; @@ -81,6 +82,7 @@ import de.lmu.ifi.dbs.elki.workflow.OutputStep; * Minimal GUI built around a table-based parameter editor. * * @author Erich Schubert + * @since 0.3 * * @apiviz.composedOf SettingsComboboxModel * @apiviz.composedOf LoggingStep @@ -140,6 +142,11 @@ public class MiniGUI extends AbstractApplication { protected SavedSettingsFile store = new SavedSettingsFile(SAVED_SETTINGS_FILENAME); /** + * Combo box for choosing the application to run. + */ + protected JComboBox<String> appCombo; + + /** * Combo box for saved settings. */ protected JComboBox<String> savedCombo; @@ -160,12 +167,22 @@ public class MiniGUI extends AbstractApplication { private Class<? extends AbstractApplication> maincls = KDDCLIApplication.class; /** + * Command line output field. + */ + private JTextField commandLine; + + /** + * Prefix for application package. + */ + private String APP_PREFIX = AbstractApplication.class.getPackage().getName() + "."; + + /** * Constructor. */ public MiniGUI() { super(); // Create and set up the window. - frame = new JFrame("ELKI MiniGUI"); + frame = new JFrame("ELKI MiniGUI Command Line Builder"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); try { frame.setIconImage(new ImageIcon(KDDTask.class.getResource("elki-icon.png")).getImage()); @@ -178,138 +195,11 @@ public class MiniGUI extends AbstractApplication { panel.setOpaque(true); // content panes must be opaque panel.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<String> 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) { - LOG.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) { - LOG.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; - panel.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(frame, 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; - panel.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; - panel.add(outputPane, constraints); - } + setupAppChooser(); + setupParameterTable(); + setupLoadSaveButtons(); + setupCommandLine(); + setupLoggingArea(); // load saved settings (we wanted to have the logger first!) try { @@ -349,14 +239,222 @@ public class MiniGUI extends AbstractApplication { } /** + * Setup the application chooser. + */ + private void setupAppChooser() { + // Configurator to choose the main application + appCombo = new JComboBox<>(); + for(Class<?> clz : ELKIServiceRegistry.findAllImplementations(AbstractApplication.class)) { + String nam = clz.getCanonicalName(); + if(nam == null || clz.getCanonicalName().contains("GUI")) { + continue; + } + if(nam.startsWith(APP_PREFIX)) { + nam = nam.substring(APP_PREFIX.length()); + } + appCombo.addItem(nam); + } + appCombo.setEditable(true); + String sel = maincls.getCanonicalName(); + if(sel.startsWith(APP_PREFIX)) { + sel = sel.substring(APP_PREFIX.length()); + } + appCombo.setSelectedItem(sel); + appCombo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if("comboBoxChanged".equals(e.getActionCommand())) { + Class<? extends AbstractApplication> clz = ELKIServiceRegistry.findImplementation(AbstractApplication.class, (String) appCombo.getSelectedItem()); + if(clz != null) { + maincls = clz; + updateParameterTable(); + } + else { + LOG.warning("Main class name not found."); + } + } + } + }); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = .01; + panel.add(appCombo, constraints); + } + + /** + * Setup the parameter table + */ + private void setupParameterTable() { + // 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(frame, 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 = 1; + constraints.weightx = 1; + constraints.weighty = 1; + panel.add(scrollPane, constraints); + } + + /** + * Create the load and save buttons. + */ + private void setupLoadSaveButtons() { + // 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<String> settings = store.get(key); + if(settings != null) { + 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); + ArrayList<String> list = new ArrayList<>(parameters.size() * 2 + 1); + list.add(maincls.getCanonicalName()); + parameters.serializeParameters(list); + store.put(key, list); + try { + store.save(); + } + catch(IOException e1) { + LOG.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) { + LOG.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 = 2; + constraints.weightx = 1.0; + constraints.weighty = 0.01; + panel.add(buttonPanel, constraints); + } + + /** + * Setup command line field + */ + private void setupCommandLine() { + // setup text output area + commandLine = new JTextField(); + commandLine.setEditable(false); // FIXME: Make editable! + + // Add the output pane to the bottom + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + constraints.gridx = 0; + constraints.gridy = 3; + constraints.weightx = 1; + constraints.weighty = .01; + panel.add(commandLine, constraints); + } + + /** + * Setup logging area + */ + private void setupLoggingArea() { + // 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 = 4; + constraints.weightx = 1; + constraints.weighty = 1; + panel.add(outputPane, constraints); + } + + /** * Serialize the parameter table and run setParameters(). */ protected void updateParameterTable() { parameterTable.setEnabled(false); - ArrayList<String> params = parameters.serializeParameters(); - outputArea.clear(); - outputArea.publish("Parameters: " + FormatUtil.format(params, " ") + NEWLINE, Level.INFO); - doSetParameters(params); + ArrayList<String> list = new ArrayList<String>(parameters.size() * 2 + 1); + list.add(maincls.getCanonicalName()); + parameters.serializeParameters(list); + doSetParameters(list); parameterTable.setEnabled(true); } @@ -366,6 +464,16 @@ public class MiniGUI extends AbstractApplication { * @param params Parameters */ protected void doSetParameters(List<String> params) { + if(params.size() > 0) { + String first = params.get(0); + if(!first.startsWith("-")) { + Class<? extends AbstractApplication> c = ELKIServiceRegistry.findImplementation(AbstractApplication.class, first); + if(c != null) { + maincls = c; + params.remove(0); + } + } + } SerializedParameterization config = new SerializedParameterization(params); TrackParameters track = new TrackParameters(config); track.tryInstantiate(LoggingStep.class); @@ -380,6 +488,13 @@ public class MiniGUI extends AbstractApplication { List<String> remainingParameters = config.getRemainingParameters(); + outputArea.clear(); + String mainnam = maincls.getCanonicalName(); + if(mainnam.startsWith(APP_PREFIX)) { + mainnam = mainnam.substring(APP_PREFIX.length()); + } + commandLine.setText(format(mainnam, params)); + // update table: parameterTable.removeEditor(); parameterTable.setEnabled(false); @@ -403,6 +518,97 @@ public class MiniGUI extends AbstractApplication { } /** + * Format objects to a command line. + * + * @param params Parameters to format (Strings, or list of strings) + * @return Formatted string + */ + private String format(Object... params) { + StringBuilder buf = new StringBuilder(); + for(Object p : params) { + if(p instanceof String) { + formatTo(buf, (String) p); + } + else if(p instanceof List) { + formatTo(buf, (List<?>) p); + } + else { + LOG.warning("Incorrect object type: " + p.getClass()); + } + } + return buf.toString(); + } + + /** + * Format a list of strings to a buffer. + * + * @param buf Output buffer + * @param params List of strings + */ + private void formatTo(StringBuilder buf, List<?> params) { + for(Object p : params) { + if(p instanceof String) { + formatTo(buf, (String) p); + } + else { + LOG.warning("Incorrect object type: " + p.getClass()); + } + } + } + + /** + * Format a single string for the command line. + * + * @param buf Output buffer + * @param s String + */ + private void formatTo(StringBuilder buf, String s) { + if(s == null || s.length() == 0) { + return; + } + if(buf.length() > 0) { + buf.append(' '); + } + // Test for escaping necessary + int escape = 0; + for(int i = 0, l = s.length(); i < l; i++) { + char c = s.charAt(i); + if(c == '\\') { + escape |= 8; + } + else if(c <= ' ' || c >= 128 || c == '<' || c == '>' || c == '|' || c == '$') { + escape |= 1; + } + else if(c == '"') { + escape |= 2; + } + else if(c == '\'') { + escape |= 4; + } + } + if(escape == 0) { + buf.append(s); // No escaping. + } + else if((escape & 10) == 0) { + buf.append('"').append(s).append('"'); + } + else if((escape & 12) == 0) { + buf.append('\'').append(s).append('\''); + } + else { // Full escaping. + buf.append('"'); + for(int i = 0, l = s.length(); i < l; i++) { + char c = s.charAt(i); + if(c == '"' || c == '\\' || c == '$') { + buf.append('\\'); + } + buf.append(c); + } + buf.append('"'); + } + } + + /** * Auto-load the last task from the history file. */ protected void loadLatest() { @@ -420,14 +626,13 @@ public class MiniGUI extends AbstractApplication { protected void startTask() { parameterTable.editCellAt(-1, -1); parameterTable.setEnabled(false); - final ArrayList<String> params = parameters.serializeParameters(); + final ArrayList<String> params = new ArrayList<>(parameters.size() * 2); + parameters.serializeParameters(params); parameterTable.setEnabled(true); runButton.setEnabled(false); outputArea.clear(); - outputArea.publish("Running: " + FormatUtil.format(params, " ") + NEWLINE, Level.INFO); - SwingWorker<Void, Void> r = new SwingWorker<Void, Void>() { @Override public Void doInBackground() { @@ -493,6 +698,34 @@ public class MiniGUI extends AbstractApplication { * @param args command line parameters */ public static void main(final String[] args) { + // Detect the common problem of an incomplete class path: + try { + Class<?> clz = ClassLoader.getSystemClassLoader().loadClass("de.lmu.ifi.dbs.elki.database.ids.DBIDUtil"); + clz.getMethod("newHashSet").invoke(null); + } + catch(ReflectiveOperationException e) { + StringBuilder msg = new StringBuilder(); + msg.append("Your Java class path is incomplete.\n"); + if(e.getCause() != null) { + msg.append(e.getCause().toString()).append("\n"); + } + else { + msg.append(e.toString()).append("\n"); + } + msg.append("Make sure you have all the required jars on the classpath.\n"); + msg.append("On the home page, you can find a 'elki-bundle' which should include everything."); + JOptionPane.showMessageDialog(null, msg, "ClassPath incomplete", JOptionPane.ERROR_MESSAGE); + return; + } + // Detect the broken Ubuntu jAyatana hack; + String toolopt = System.getenv("JAVA_TOOL_OPTION"); + if(toolopt != null && toolopt.indexOf("jayatana") >= 0) { + StringBuilder msg = new StringBuilder(); + msg.append("The Ubuntu JAyatana 'global menu support' hack is known to cause problems with many Java applications.\n"); + msg.append("Please unset JAVA_TOOL_OPTION."); + JOptionPane.showMessageDialog(null, msg, "Incompatible with JAyatana", JOptionPane.ERROR_MESSAGE); + return; + } GUIUtil.logUncaughtExceptions(LOG); GUIUtil.setLookAndFeel(); OutputStep.setDefaultHandlerVisualizer(); @@ -549,7 +782,7 @@ public class MiniGUI extends AbstractApplication { * * @apiviz.composedOf de.lmu.ifi.dbs.elki.gui.util.SavedSettingsFile */ - class SettingsComboboxModel extends AbstractListModel<String>implements ComboBoxModel<String> { + class SettingsComboboxModel extends AbstractListModel<String> implements ComboBoxModel<String> { /** * Serial version. */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/MultiStepGUI.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/MultiStepGUI.java index dacbe361..36aa79fe 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/MultiStepGUI.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/MultiStepGUI.java @@ -61,6 +61,7 @@ import de.lmu.ifi.dbs.elki.workflow.OutputStep; * Experimenter-style multi step GUI. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.landmark * @apiviz.composedOf AlgorithmTabPanel diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/AlgorithmTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/AlgorithmTabPanel.java index c0dda863..fb9bd116 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/AlgorithmTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/AlgorithmTabPanel.java @@ -34,6 +34,7 @@ import de.lmu.ifi.dbs.elki.workflow.AlgorithmStep; * Panel to handle data processing * * @author Erich Schubert + * @since 0.4.0 */ public class AlgorithmTabPanel extends ParameterTabPanel { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/EvaluationTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/EvaluationTabPanel.java index 65d53d20..8a8d10b5 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/EvaluationTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/EvaluationTabPanel.java @@ -35,6 +35,7 @@ import de.lmu.ifi.dbs.elki.workflow.EvaluationStep; * Panel to handle result evaluation * * @author Erich Schubert + * @since 0.4.0 */ public class EvaluationTabPanel extends ParameterTabPanel { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/InputTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/InputTabPanel.java index 7a3ea1f5..cd83a860 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/InputTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/InputTabPanel.java @@ -31,6 +31,7 @@ import de.lmu.ifi.dbs.elki.workflow.InputStep; * Panel to handle data input. * * @author Erich Schubert + * @since 0.4.0 */ public class InputTabPanel extends ParameterTabPanel { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/LoggingTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/LoggingTabPanel.java index 2dcc38d4..902995ce 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/LoggingTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/LoggingTabPanel.java @@ -24,9 +24,8 @@ package de.lmu.ifi.dbs.elki.gui.multistep.panels; */ import de.lmu.ifi.dbs.elki.application.AbstractApplication; -import de.lmu.ifi.dbs.elki.logging.LoggingConfiguration; -import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.logging.Logging.Level; +import de.lmu.ifi.dbs.elki.logging.LoggingConfiguration; import de.lmu.ifi.dbs.elki.utilities.optionhandling.WrongParameterValueException; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag; @@ -36,6 +35,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.StringParameter; * Panel to handle logging * * @author Erich Schubert + * @since 0.4.0 */ public class LoggingTabPanel extends ParameterTabPanel { /** @@ -62,7 +62,7 @@ public class LoggingTabPanel extends ParameterTabPanel { // FIXME: add second level of verbosity! if (config.grab(debugParam)) { try { - LoggingUtil.parseDebugParameter(debugParam); + AbstractApplication.Parameterizer.parseDebugParameter(debugParam); } catch (WrongParameterValueException e) { de.lmu.ifi.dbs.elki.logging.LoggingUtil.exception(e); } diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/OutputTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/OutputTabPanel.java index cedc5765..29fdf7ff 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/OutputTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/OutputTabPanel.java @@ -35,6 +35,7 @@ import de.lmu.ifi.dbs.elki.workflow.OutputStep; * Panel to handle result output / visualization * * @author Erich Schubert + * @since 0.4.0 */ public class OutputTabPanel extends ParameterTabPanel { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/ParameterTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/ParameterTabPanel.java index caf9c066..f6985f4f 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/ParameterTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/ParameterTabPanel.java @@ -51,6 +51,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.TrackParame * Abstract panel, showing particular options. * * @author Erich Schubert + * @since 0.4.0 */ public abstract class ParameterTabPanel extends JPanel implements ChangeListener { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/SavedSettingsTabPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/SavedSettingsTabPanel.java index 0ea2a34f..ec7b66e6 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/SavedSettingsTabPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/multistep/panels/SavedSettingsTabPanel.java @@ -47,6 +47,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.SerializedP * Tab panel to manage saved settings. * * @author Erich Schubert + * @since 0.4.0 * * @apiviz.composedOf SavedSettingsTabPanel.SettingsComboboxModel */ diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ClassTree.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ClassTree.java index 8c524cdd..3c2a6373 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ClassTree.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ClassTree.java @@ -31,15 +31,16 @@ import javax.swing.tree.TreeNode; /** * Build a tree of available classes for use in Swing UIs. - * + * * @author Erich Schubert - * + * @since 0.7.0 + * * @apiviz.has TreeNode */ public class ClassTree { /** * Build the class tree for a given set of choices. - * + * * @param choices Class choices * @param rootpkg Root package name (to strip / hide) * @return Root node. @@ -59,7 +60,8 @@ public class ClassTree { for(Class<?> impl : choices) { String name = impl.getName(); name = (prefix != null && name.startsWith(prefix)) ? name.substring(prefix.length()) : name; - MutableTreeNode c = new ClassNode(impl.getName().substring(impl.getPackage().getName().length() + 1), name); + int plen = (impl.getPackage() != null) ? impl.getPackage().getName().length() + 1 : 0; + MutableTreeNode c = new ClassNode(impl.getName().substring(plen), name); MutableTreeNode p = null; int l = name.lastIndexOf('.'); @@ -96,7 +98,7 @@ public class ClassTree { /** * Simplify the tree. - * + * * @param cur Current node * @param prefix Prefix to add * @return Replacement node @@ -133,9 +135,9 @@ public class ClassTree { /** * Tree node representing a single class. - * + * * @author Erich Schubert - * + * * @apiviz.exclude */ public static class PackageNode extends DefaultMutableTreeNode { @@ -151,7 +153,7 @@ public class ClassTree { /** * Current class name. - * + * * @param display Displayed name * @param pkgname Actual class name */ @@ -162,7 +164,7 @@ public class ClassTree { /** * Return the package name. - * + * * @return Package name */ public String getPackageName() { @@ -172,9 +174,9 @@ public class ClassTree { /** * Tree node representing a single class. - * + * * @author Erich Schubert - * + * * @apiviz.exclude */ public static class ClassNode extends DefaultMutableTreeNode { @@ -190,7 +192,7 @@ public class ClassTree { /** * Current class name. - * + * * @param display Displayed name * @param clsname Actual class name */ @@ -201,7 +203,7 @@ public class ClassTree { /** * Return the class name. - * + * * @return Class name */ public String getClassName() { diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/DynamicParameters.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/DynamicParameters.java index 2aae45d4..10cc79a3 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/DynamicParameters.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/DynamicParameters.java @@ -39,6 +39,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.StringParameter; * correct. * * @author Erich Schubert + * @since 0.3 * * @apiviz.owns de.lmu.ifi.dbs.elki.gui.util.DynamicParameters.RemainingOptions */ @@ -206,10 +207,9 @@ public class DynamicParameters { /** * Serialize parameters into an array list to pass to setParameters() * - * @return Array list of String parameters. + * @param p Output list (will not be emptied) */ - public synchronized ArrayList<String> serializeParameters() { - ArrayList<String> p = new ArrayList<>(2 * parameters.size()); + public synchronized void serializeParameters(ArrayList<String> p) { for(Node t : parameters) { if(t.param != null) { if(t.param instanceof RemainingOptions) { @@ -232,7 +232,6 @@ public class DynamicParameters { } } } - return p; } /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPane.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPane.java index 17c52cea..c42a2ed7 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPane.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPane.java @@ -46,6 +46,7 @@ import de.lmu.ifi.dbs.elki.logging.progress.ProgressLogRecord; * {@link #becomeDefaultLogger()} to register as default logger in ELKI. * * @author Erich Schubert + * @since 0.3 * * @apiviz.uses LoggingConfiguration * @apiviz.uses de.lmu.ifi.dbs.elki.logging.ELKILogRecord diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPanel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPanel.java index dbe87b58..9507c8eb 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPanel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/LogPanel.java @@ -48,6 +48,7 @@ import de.lmu.ifi.dbs.elki.logging.progress.StepProgress; * Panel that contains a text logging pane ({@link LogPane}) and progress bars. * * @author Erich Schubert + * @since 0.3 * * @apiviz.owns LogPane * @apiviz.uses LoggingConfiguration diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParameterTable.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParameterTable.java index a08341db..a5782ca9 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParameterTable.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParameterTable.java @@ -73,6 +73,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * Class showing a table of ELKI parameters. * * @author Erich Schubert + * @since 0.3 * * @apiviz.composedOf ParametersModel * @apiviz.owns de.lmu.ifi.dbs.elki.gui.util.ParameterTable.ColorfulRenderer diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParametersModel.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParametersModel.java index d864cc9b..66593c20 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParametersModel.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/ParametersModel.java @@ -32,6 +32,7 @@ import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter; * A Swing TableModel that uses a {@link DynamicParameters} object as storage. * * @author Erich Schubert + * @since 0.3 */ public class ParametersModel extends AbstractTableModel { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/SavedSettingsFile.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/SavedSettingsFile.java index f8f4c990..6f31a2c6 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/SavedSettingsFile.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/SavedSettingsFile.java @@ -39,6 +39,7 @@ import de.lmu.ifi.dbs.elki.utilities.pairs.Pair; * Class to manage saved settings in a text file. * * @author Erich Schubert + * @since 0.3 */ public class SavedSettingsFile implements Iterable<Pair<String, ArrayList<String>>> { /** diff --git a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java index 19413c53..6bfb6a2a 100644 --- a/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java +++ b/elki/src/main/java/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java @@ -62,6 +62,7 @@ import javax.swing.tree.TreeModel; * Popup menu that contains a JTree. * * @author Erich Schubert + * @since 0.7.0 */ public class TreePopup extends JPopupMenu { /** |