/**************************************************************************** * Copyright (C) 2013 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.richclient.gui.manage; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Properties; import java.util.Vector; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.swing.BorderFactory; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.border.Border; import javax.swing.border.TitledBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableModel; /** * Aggregator class for settings entries. * The entries form a group with an optional caption. * * @author Tobias Wich <tobias.wich@ecsec.de> */ public abstract class SettingsGroup extends JPanel { private static final long serialVersionUID = 1L; protected final Properties properties; private final JPanel container; private final HashMap<Component, JLabel> fieldLabels; private int itemIdx; /** * Creates an instance bound to a set of properties. * * @param title Optional title to display as group caption. * @param properties Properties bound to the entries in this group. */ public SettingsGroup(@Nullable String title, @Nonnull Properties properties) { this.properties = properties; this.fieldLabels = new HashMap<Component, JLabel>(); Border frameBorder = BorderFactory.createEmptyBorder(2, 2, 2, 2); if (title != null) { TitledBorder titleBorder = BorderFactory.createTitledBorder(frameBorder, title); titleBorder.setTitleJustification(TitledBorder.LEADING); titleBorder.setTitlePosition(TitledBorder.TOP); titleBorder.setTitleFont(new JLabel().getFont().deriveFont(Font.BOLD)); frameBorder = titleBorder; } setBorder(frameBorder); setLayout(new BorderLayout()); // configure tuple container container = new JPanel(); add(container, BorderLayout.NORTH); GridBagLayout layout = new GridBagLayout(); layout.columnWidths = new int[]{0, 10, 0, 0}; layout.rowHeights = new int[]{0, 0}; layout.columnWeights = new double[]{0.0, 0.0, 1.0, Double.MIN_VALUE}; layout.rowWeights = new double[]{0.0, Double.MIN_VALUE}; container.setLayout(layout); } /** * Saves the bound properties. * This method is abstract, so that the overriding class can decide how and where to save the properties. * * @throws IOException Thrown in case the properties could not be written to the output device. * @throws SecurityException Thrown in case the permission to save the properties is missing. */ protected abstract void saveProperties() throws IOException, SecurityException; /** * Enables or disables entries in the group. * The entry is identified by its input field which is returned in the add functions. When disabled the entry * disappears. * * @param element Input element identifying the entry. * @param enabled True whe element should be enabled, false otherwise. * @see #addInputItem(java.lang.String, java.lang.String, java.lang.String) * @see #addBoolItem(java.lang.String, java.lang.String, java.lang.String) * @see #addSelectionItem(java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) * @see #addMultiSelectionItem(java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) */ protected void setEnabledComponent(Component element, boolean enabled) { JLabel label = fieldLabels.get(element); label.setVisible(enabled); element.setVisible(enabled); } /** * Adds an input field to the group. * The specified property is bound to the input and updates when the value changes. * * @param name Name displayed on the label besides the input element. * @param description Optional tooltip description visible when hovering the label. * @param property Property entry this element is bound to. * @return The input element which has been created and added to the entry. */ protected JTextField addInputItem(@Nonnull String name, @Nullable String description, final @Nonnull String property) { JLabel label = addLabel(name, description); String value = properties.getProperty(property); value = value == null ? "" : value; final JTextField input = new JTextField(value); fieldLabels.put(input, label); // add listener for value changes input.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { properties.setProperty(property, input.getText()); } @Override public void removeUpdate(DocumentEvent e) { properties.setProperty(property, input.getText()); } @Override public void changedUpdate(DocumentEvent e) { // ignore } }); addComponent(input); itemIdx++; return input; } @Deprecated private void addListInputItem(@Nonnull String name, @Nullable String description, final @Nonnull String property) { JLabel label = addLabel(name, description); String values = properties.getProperty(property); String[] entries; if (values == null) { entries = new String[0]; } else { entries = values.split(";"); } Vector<Vector<String>> rowData = new Vector<Vector<String>>(); Vector<String> columnData = new Vector<String>(); columnData.add("Provider ID"); columnData.add("URL"); final DefaultTableModel model = new DefaultTableModel() { @Override public void setValueAt(Object aValue, int row, int column) { super.setValueAt(aValue, row, column); if (! aValue.toString().trim().isEmpty()) { if (shouldAddRow(row, column)) { addRow(new Object[] {}); } } } private boolean shouldAddRow(int lastEditedRow, int lastEditedColumn) { // true if we are in the last row return lastEditedRow == getRowCount() - 1; } }; model.addTableModelListener(new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { StringBuilder sb = new StringBuilder(); for (int rowNumber = 0; rowNumber < model.getRowCount(); rowNumber++) { for (int columnNumber = 0; columnNumber < model.getColumnCount(); columnNumber++) { Object valueAt = model.getValueAt(rowNumber, columnNumber); if (valueAt != null && !valueAt.toString().trim().isEmpty()) { sb.append(valueAt.toString()); if (columnNumber == model.getColumnCount() - 1) { sb.append(";"); } else { sb.append(","); } } } } properties.setProperty(property, sb.toString()); } }); for (String entry : entries) { if (entry.split(",").length < 2) { continue; } String key = entry.split(",")[0]; String value = entry.split(",")[1]; Vector<String> row = new Vector<String>(); row.add(key); row.add(value); rowData.add(row); } JTable jTable = new JTable(model); model.setDataVector(rowData, columnData); model.addRow(new Object[] {}); fieldLabels.put(jTable, label); addComponent(jTable); itemIdx++; } protected JTable addScalarListItem(@Nonnull String name, @Nullable String desc, final @Nonnull String property) { JLabel label = addLabel(name, desc); String value = properties.getProperty(property); ArrayList<String> entries = new ArrayList<String>(10); if (value != null) { String[] arrayEntries = value.split("\n"); Collections.addAll(entries, arrayEntries); // remove leading and trailing ws and remove empty entries ListIterator<String> it = entries.listIterator(); while (it.hasNext()) { String next = it.next(); next = next.trim(); if (next.isEmpty()) { it.remove(); } else { it.set(next); } } } final JTable input = new JTable(entries.size() + 1, 2); // fill in the values from entries for (int i = 0; i < entries.size(); i++) { input.getModel().setValueAt(entries.get(i), i, 0); JButton removeButton = new JButton("x"); input.getModel().setValueAt(removeButton, i, 1); } fieldLabels.put(input, label); // TODO: add listener addComponent(input); itemIdx++; return input; } /** * Adds a check box to the group. * The specified property is bound to the input and updates when the value changes. * * @param name Name displayed on the label besides the input element. * @param description Optional tooltip description visible when hovering the label. * @param property Property entry this element is bound to. * @return The check box element which has been created and added to the entry. */ protected JCheckBox addBoolItem(@Nonnull String name, @Nullable String description, final @Nonnull String property) { JLabel label = addLabel(name, description); String value = properties.getProperty(property); Boolean boolValue = Boolean.parseBoolean(value); final JCheckBox input = new JCheckBox(); input.setSelected(boolValue); fieldLabels.put(input, label); // add listener for value changes input.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { properties.setProperty(property, Boolean.valueOf(input.isSelected()).toString()); } }); addComponent(input); itemIdx++; return input; } /** * Adds a selection field to the group. * The specified property is bound to the input and updates when the value changes. * * @param name Name displayed on the label besides the input element. * @param description Optional tooltip description visible when hovering the label. * @param property Property entry this element is bound to. * @param values * @return The selection element which has been created and added to the entry. */ protected JComboBox addSelectionItem(@Nonnull String name, @Nullable String description, final @Nonnull String property, @Nonnull String... values) { addLabel(name, description); final JComboBox comboBox = new JComboBox(); comboBox.setModel(new DefaultComboBoxModel(values)); comboBox.setSelectedItem(properties.getProperty(property)); comboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { properties.setProperty(property, (String) e.getItem()); } } }); addComponent(comboBox); itemIdx++; return comboBox; } /** * Adds a selection field capable of selecting multiple values to the group. * The specified property is bound to the input and updates when the value changes. * * @param name Name displayed on the label besides the input element. * @param description Optional tooltip description visible when hovering the label. * @param property Property entry this element is bound to. * @param values Selectable values. * @return The selection element which has been created and added to the entry. */ protected JComboBox addMultiSelectionItem(@Nonnull String name, @Nullable String description, final @Nonnull String property, @Nonnull List<String> values) { addLabel(name, description); // TODO: implement throw new UnsupportedOperationException("Not implemented yet."); } private JLabel addLabel(@Nonnull String name, @Nullable String description) { JLabel label = new JLabel(name); label.setToolTipText(description); label.setFont(label.getFont().deriveFont(Font.PLAIN)); GridBagConstraints constraints = new GridBagConstraints(); constraints.insets = new Insets(5, 10, 0, 5); constraints.gridx = 0; constraints.gridy = itemIdx; constraints.anchor = GridBagConstraints.WEST; container.add(label, constraints); return label; } private void addComponent(@Nonnull Component component) { GridBagConstraints constraints = new GridBagConstraints(); constraints.insets = new Insets(5, 3, 0, 5); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 2; constraints.gridy = itemIdx; container.add(component, constraints); } }