package gui.views; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.TreeMap; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.SpinnerNumberModel; import javax.swing.table.DefaultTableModel; import gui.actions.OptionsDialogActions; import gui.utils.GUIErrorHandler; import gui.utils.Images; import gui.views.components.ReplacementsTable; import hextostring.replacement.Replacements; import main.MainOptions; import main.options.Options; import main.options.annotations.CommandLineArgument; import main.options.annotations.CommandLineValue; import main.options.domain.Bounds; import main.options.domain.Domain; import main.options.domain.Values; import main.utils.GenericSort; import main.utils.ReflectionUtils; import main.utils.StringUtils; /** * The dialog containing all the necessary elements to modify the options. * * @author Maxime PIA */ @SuppressWarnings("serial") public class OptionsDialog extends JDialog { private OptionsDialogActions acts = new OptionsDialogActions(this); private MainOptions opts; public static final double DOUBLE_SPINNER_STEP = 0.005; public static final int INTEGER_SPINNER_STEP = 1; public OptionsDialog(MainOptions opts) { setTitle("Options"); setModal(true); setIconImage(Images.DEFAULT_ICON.getImage()); this.opts = opts; appendBody(); setMinimumSize(new Dimension(400, 600)); acts.setCloseAction(); } private void appendBody() { JPanel optionsPanel = new JPanel(new BorderLayout()); optionsPanel.add(getAllTabs(), BorderLayout.NORTH); optionsPanel.add(getButtons(), BorderLayout.EAST); add(optionsPanel); } private JTabbedPane getAllTabs() { JTabbedPane tabs = new JTabbedPane(); Collection<Options> options = GenericSort.apply(opts.getSubOptions(), null); try { for (Options option : options) { addOptionTabs(option, tabs); } } catch (IllegalArgumentException | IllegalAccessException | SecurityException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) { new GUIErrorHandler(e); } tabs.add("replacements", getReplacementsPanel( opts.getConvertOptions().getReplacements(), acts )); return tabs; } private JPanel getButtons() { JPanel buttonsPanel = new JPanel(); buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS)); JButton applyButton = new JButton("Apply"); acts.setApplyButtonAction(applyButton, opts); buttonsPanel.add(applyButton); JButton okButton = new JButton("OK"); acts.setOKButtonAction(okButton, opts); buttonsPanel.add(okButton); JButton cancelButton = new JButton("Cancel"); acts.setCancelButtonAction(cancelButton); buttonsPanel.add(cancelButton); return buttonsPanel; } // Generates the tabs and their content automatically through reflection. private void addOptionTabs(Options optionObject, JTabbedPane tabs) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { List<Field> flagFields = new LinkedList<>(); JPanel optionPanel = new JPanel(); JPanel innerPanel = new JPanel(new GridLayout(0, 2)); boolean empty = true; Collection<Field> optsFields = GenericSort.apply( ReflectionUtils.getAnnotatedFields( optionObject.getClass(), CommandLineArgument.class ), Field.class.getMethod("getName") ); for (Field field : optsFields) { if (field.getAnnotation(CommandLineArgument.class).flags()) { flagFields.add(field); } else { String labelContent = StringUtils.camelToWords(field.getName()); JComponent component = getComponentFromField(field, optionObject); if (component == null) continue; component.setName(labelContent); acts.setOptionComponentAction(component, field, optionObject); innerPanel.add(new JLabel(labelContent)); innerPanel.add(component); empty = false; } } optionPanel.add(innerPanel); if (!empty) { tabs.add( StringUtils.camelToWords( optionObject.getClass().getSimpleName() .replace("Options", "") ), optionPanel ); } for (Field flagField : flagFields) { addFlagTab(optionObject, flagField, tabs); } } private JPanel getReplacementsPanel(Replacements replacements, OptionsDialogActions acts) { JPanel replacementsPanel = new JPanel(new BorderLayout()); final ReplacementsTable replacementsTable = new ReplacementsTable(replacements, acts); replacementsPanel.add( new JScrollPane(replacementsTable), BorderLayout.CENTER ); JPanel buttonsPanel = new JPanel(new FlowLayout()); JButton addReplacementButton = new JButton("Add replacement"); acts.setAddReplacementButtonAction( addReplacementButton, (DefaultTableModel) replacementsTable.getModel() ); buttonsPanel.add(addReplacementButton); JButton deleteSelectionButton = new JButton("Delete selection"); acts.setDeleteSelectionButtonAction( deleteSelectionButton, replacementsTable ); buttonsPanel.add(deleteSelectionButton); replacementsPanel.add(buttonsPanel, BorderLayout.SOUTH); return replacementsPanel; } // Flags are managed differently: every flag corresponds to a checkbox. private void addFlagTab(Options optionObject, Field flagField, JTabbedPane tabs) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchFieldException, SecurityException { JPanel flagPanel = new JPanel(); flagPanel.setName( StringUtils.camelToWords(flagField.getName().replace("Flags", "")) ); flagPanel.setLayout(new BoxLayout(flagPanel, BoxLayout.Y_AXIS)); boolean empty = true; long setFlags = (long) ReflectionUtils .getGetter(optionObject.getClass(), flagField) .invoke(optionObject); List<Field> valueFields = ReflectionUtils.getAnnotatedFields( optionObject.getFieldValueClass(flagField), CommandLineValue.class ); TreeMap<Long, Field> sortedValFields = new TreeMap<>(); for (Field valueField : valueFields) { sortedValFields.put((Long) valueField.get(null), valueField); } for (Field valueField : sortedValFields.values()) { String checkboxName = StringUtils.screamingSnakeToWords(valueField.getName()); final JCheckBox flagCheckBox = new JCheckBox(checkboxName); flagCheckBox.setName(checkboxName); flagCheckBox.setSelected((setFlags & valueField.getLong(null)) > 0); acts.setFlagCheckboxAction( flagCheckBox, flagField, valueField, optionObject ); flagPanel.add(flagCheckBox); empty = false; } if (!empty) { tabs.add( StringUtils.camelToWords( flagField.getName().replace("Flags", "") ), flagPanel ); } } // Provides a component depending on the nature of the option field. private static JComponent getComponentFromField(Field valueField, Options optionsObj) throws IllegalArgumentException, IllegalAccessException, SecurityException { valueField.setAccessible(true); Object value = valueField.get(optionsObj); Domain<?> domain; try { domain = optionsObj.getFieldDomain(valueField); } catch (NoSuchFieldException e) { domain = null; } if ((value.getClass().equals(Double.class) || value.getClass().equals(Integer.class)) && (domain == null || domain instanceof Bounds)) { return getJSpinner(value, (Bounds<?>) domain); } else if (domain == null || domain instanceof Values) { if (value.getClass().equals(Boolean.class)) { return getJCheckBox(value, (Values<?>) domain); } else if (domain != null) { return getJComboBox(value, (Values<?>) domain); } } return null; } private static JComponent getJSpinner(Object value, Bounds<?> domain) { JSpinner spinner = new JSpinner(); SpinnerNumberModel model = new SpinnerNumberModel(); spinner.setModel(model); model.setStepSize( value.getClass().equals(Integer.class) ? INTEGER_SPINNER_STEP : DOUBLE_SPINNER_STEP ); if (domain != null) { model.setMinimum(domain.getMin()); model.setMaximum(domain.getMax()); } spinner.setValue(value); return spinner; } private static JComponent getJComboBox(Object value, Values<?> domain) { JComboBox<Object> cb = new JComboBox<>(domain.getValues()); int eltCounter = 0; for (Object possibleValue : domain.getValues()) { if (value.equals(possibleValue)) { cb.setSelectedIndex(eltCounter); break; } ++eltCounter; } return cb; } private static JComponent getJCheckBox(Object value, Values<?> domain) { JCheckBox cb = new JCheckBox(); cb.setSelected((boolean) value); // Disabled if only one choice. // If the domain is inconsistent with the value, the value has priority. if (domain != null && domain.getValues().length < 2) { cb.setEnabled(false); } return cb; } }