Java tutorial
/** * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package richtercloud.document.scanner.gui; import au.com.southsky.jfreesane.OptionValueType; import au.com.southsky.jfreesane.SaneDevice; import au.com.southsky.jfreesane.SaneException; import au.com.southsky.jfreesane.SaneOption; import au.com.southsky.jfreesane.SaneWord; import java.awt.Dialog; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.DefaultComboBoxModel; import javax.swing.JDialog; import javax.swing.MutableComboBoxModel; import org.apache.commons.lang3.tuple.ImmutableTriple; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import richtercloud.reflection.form.builder.message.MessageHandler; /** * Provides configuration for scan mode and resolution of SANE device with GUI * components in a {@link JDialog}. * * Option value changes are performed on the referenced {@link SaneDevice} * directly in order to KISS. * * @author richter */ public class ScannerEditDialog extends javax.swing.JDialog { private static final long serialVersionUID = 1L; private MutableComboBoxModel<String> modeComboBoxModel = new DefaultComboBoxModel<>(); private MutableComboBoxModel<String> resolutionComboBoxModel = new DefaultComboBoxModel<>(); private MutableComboBoxModel<String> documentSourceComboBoxModel = new DefaultComboBoxModel<>(); private final static Logger LOGGER = LoggerFactory.getLogger(ScannerEditDialog.class); private final SaneDevice device; private final MessageHandler messageHandler; private final static String MODE_OPTION_NAME = "mode"; private final static String RESOLUTION_OPTION_NAME = "resolution"; public final static String DOCUMENT_SOURCE_OPTION_NAME = "source"; public ScannerEditDialog(Dialog parent, final SaneDevice device, Map<SaneDevice, Map<SaneOption, Object>> changedOptions, MessageHandler messageHandler) throws IOException, SaneException { super(parent, true //modal ); if (device == null) { throw new IllegalArgumentException("device mustn't be null"); } this.device = device; if (messageHandler == null) { throw new IllegalArgumentException("messageHandler mustn't be null"); } this.messageHandler = messageHandler; init(device, changedOptions); } /** * Creates new form ScannerEditDialog * @param parent * @param device * @param changedOptions * @param messageHandler * @throws java.io.IOException if {@link SaneDevice#open() } fails * @throws au.com.southsky.jfreesane.SaneException if * {@link SaneDevice#open() } fails */ public ScannerEditDialog(java.awt.Frame parent, final SaneDevice device, Map<SaneDevice, Map<SaneOption, Object>> changedOptions, MessageHandler messageHandler) throws IOException, SaneException { super(parent, true //modal ); if (device == null) { throw new IllegalArgumentException("device mustn't be null"); } this.device = device; if (messageHandler == null) { throw new IllegalArgumentException("messageHandler mustn't be null"); } this.messageHandler = messageHandler; init(device, changedOptions); } private void init(final SaneDevice device, Map<SaneDevice, Map<SaneOption, Object>> changedOptions) throws IOException, SaneException { initComponents(); if (!device.isOpen()) { device.open(); } Triple<String, Integer, String> defaultValuePair = configureDefaultOptionValues(device, changedOptions, false); for (String mode : device.getOption("mode").getStringConstraints()) { modeComboBoxModel.addElement(mode); } this.modeComboBox.setSelectedItem(defaultValuePair.getLeft()); for (SaneWord resolution : device.getOption("resolution").getWordConstraints()) { resolutionComboBoxModel.addElement(String.valueOf(resolution.integerValue())); } this.resolutionComboBox.setSelectedItem(String.valueOf(defaultValuePair.getMiddle())); List<String> documentSourceConstraints = device.getOption("source").getStringConstraints(); for (String documentSource : documentSourceConstraints) { this.documentSourceComboBoxModel.addElement(documentSource); } if (documentSourceConstraints.contains("Automatic Document Feeder")) { this.documentSourceComboBox.setSelectedItem("Automatic Document Feeder"); } //add ItemListener after setup this.modeComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { try { String mode = (String) ScannerEditDialog.this.modeComboBox.getSelectedItem(); ScannerEditDialog.this.device.getOption("mode").setStringValue(mode); } catch (IOException | SaneException ex) { //not supposed to happen throw new RuntimeException(ex); } } }); this.resolutionComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { try { int resolution = Integer .valueOf((String) ScannerEditDialog.this.resolutionComboBox.getSelectedItem()); ScannerEditDialog.this.device.getOption("resolution").setIntegerValue(resolution); } catch (IOException | SaneException ex) { //not supposed to happen throw new RuntimeException(ex); } } }); this.documentSourceComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { try { String documentSource = (String) ScannerEditDialog.this.documentSourceComboBox .getSelectedItem(); ScannerEditDialog.this.device.getOption("source").setStringValue(documentSource); } catch (IOException | SaneException ex) { throw new RuntimeException(ex); } } }); } /** * Sets convenient default values on the scanner ("color" scan mode and the * resolution value closest to 300 DPI). Readability and writability of * options are checked. The type of the options are checked as well in order * to fail with a helpful error message in case of an errornous SANE * implementation. * * For a list of SANE options see * http://www.sane-project.org/html/doc014.html. * * @param device * @param changedOptions * @param overwrite overwrite values which have already been set according * to {@code changedOptions} * @throws IOException * @throws SaneException * @throws IllegalArgumentException if the option denoted by * {@link #MODE_OPTION_NAME} or {@link #RESOLUTION_OPTION_NAME} isn't * readable or writable * @return the mode, resolution and document source in a {@link Triple} */ public static Triple<String, Integer, String> configureDefaultOptionValues(SaneDevice device, Map<SaneDevice, Map<SaneOption, Object>> changedOptions, boolean overwrite) throws IOException, SaneException { Map<SaneOption, Object> changedOptionsDeviceMap = changedOptions.get(device); if (changedOptionsDeviceMap == null) { changedOptionsDeviceMap = new HashMap<>(); changedOptions.put(device, changedOptionsDeviceMap); } if (!device.isOpen()) { device.open(); } SaneOption modeOption = device.getOption(MODE_OPTION_NAME); if (!modeOption.isReadable()) { throw new IllegalArgumentException(String.format("option '%s' isn't readable", MODE_OPTION_NAME)); } if (!modeOption.getType().equals(OptionValueType.STRING)) { throw new IllegalArgumentException(String.format( "Option '%s' isn't of type STRING. This indicates an errornous SANE implementation. Can't proceed.", MODE_OPTION_NAME)); } String mode = (String) changedOptionsDeviceMap.get(modeOption); if (overwrite || mode == null) { for (String modeConstraint : modeOption.getStringConstraints()) { if ("color".equalsIgnoreCase(modeConstraint.trim())) { mode = modeConstraint; break; } } if (mode == null) { mode = modeOption.getStringConstraints().get(0); } if (!modeOption.isWriteable()) { throw new IllegalArgumentException(String.format("Option '%s' isn't writable.", MODE_OPTION_NAME)); } LOGGER.debug(String.format("setting default mode '%s' on device '%s'", mode, device)); modeOption.setStringValue(mode); changedOptionsDeviceMap.put(modeOption, mode); } SaneOption resolutionOption = device.getOption(RESOLUTION_OPTION_NAME); if (!resolutionOption.isReadable()) { throw new IllegalArgumentException( String.format("Option '%s' isn't readable.", RESOLUTION_OPTION_NAME)); } if (!resolutionOption.getType().equals(OptionValueType.INT)) { throw new IllegalArgumentException(String.format( "Option '%s' isn't of type INT. This indicates an errornous SANE implementation. Can't proceed.", RESOLUTION_OPTION_NAME)); } Integer resolution = (Integer) changedOptionsDeviceMap.get(resolutionOption); if (overwrite || resolution == null) { int resolutionDifference = Integer.MAX_VALUE, resolutionWish = 300; for (SaneWord resolutionConstraint : resolutionOption.getWordConstraints()) { int resolutionConstraintValue = resolutionConstraint.integerValue(); int resolutionConstraintDifference = Math.abs(resolutionWish - resolutionConstraintValue); if (resolutionConstraintDifference < resolutionDifference) { resolution = resolutionConstraintValue; resolutionDifference = resolutionConstraintDifference; if (resolutionDifference == 0) { //not possible to find more accurate values break; } } } assert resolution != null; if (!resolutionOption.isWriteable()) { throw new IllegalArgumentException( String.format("option '%s' isn't writable", RESOLUTION_OPTION_NAME)); } LOGGER.debug(String.format("setting default resolution '%d' on device '%s'", resolution, device)); resolutionOption.setIntegerValue(resolution); changedOptionsDeviceMap.put(resolutionOption, resolution); } SaneOption documentSourceOption = device.getOption(DOCUMENT_SOURCE_OPTION_NAME); if (!documentSourceOption.isReadable()) { throw new IllegalArgumentException( String.format("Option '%s' isn't readable.", DOCUMENT_SOURCE_OPTION_NAME)); } if (!documentSourceOption.getType().equals(OptionValueType.STRING)) { throw new IllegalArgumentException( String.format("Option '%s' " + "isn't of type STRING. This indicates an errornous SANE " + "implementation. Can't proceed.", DOCUMENT_SOURCE_OPTION_NAME)); } String documentSource = (String) changedOptionsDeviceMap.get(documentSourceOption); if (overwrite || documentSource == null) { for (String documentSourceConstraint : documentSourceOption.getStringConstraints()) { if (documentSourceConstraint.equalsIgnoreCase("ADF") || documentSourceConstraint.equalsIgnoreCase("Automatic document feeder")) { documentSource = documentSourceConstraint; break; } } if (documentSource == null) { documentSource = documentSourceOption.getStringConstraints().get(0); } assert documentSource != null; if (!documentSourceOption.isWriteable()) { throw new IllegalArgumentException( String.format("option '%s' isn't writable", DOCUMENT_SOURCE_OPTION_NAME)); } LOGGER.debug( String.format("setting default document source '%d' on device '%s'", documentSource, device)); documentSourceOption.setStringValue(documentSource); changedOptionsDeviceMap.put(documentSourceOption, documentSource); } return new ImmutableTriple<>(mode, resolution, documentSource); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { modeComboBoxLabel = new javax.swing.JLabel(); modeComboBox = new javax.swing.JComboBox<>(); resolutionComboBox = new javax.swing.JComboBox<>(); resolutionComboBoxLabel = new javax.swing.JLabel(); closeButton = new javax.swing.JButton(); documentSourceComboBox = new javax.swing.JComboBox<>(); documentSourceComboBoxLabel = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); modeComboBoxLabel.setText("Mode"); modeComboBox.setModel(modeComboBoxModel); resolutionComboBox.setModel(resolutionComboBoxModel); resolutionComboBoxLabel.setText("Resolution"); closeButton.setText("Close"); closeButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { closeButtonActionPerformed(evt); } }); documentSourceComboBox.setModel(documentSourceComboBoxModel); documentSourceComboBoxLabel.setText("Document source"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup().addGap(0, 0, Short.MAX_VALUE) .addComponent(closeButton)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(resolutionComboBoxLabel).addComponent(modeComboBoxLabel) .addComponent(documentSourceComboBoxLabel)) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(documentSourceComboBox, 0, 228, Short.MAX_VALUE) .addComponent(resolutionComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(modeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) .addContainerGap())); layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(modeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(modeComboBoxLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(resolutionComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(resolutionComboBoxLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(documentSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(documentSourceComboBoxLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(closeButton).addContainerGap())); pack(); }// </editor-fold>//GEN-END:initComponents private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed this.setVisible(false); }//GEN-LAST:event_closeButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton closeButton; private javax.swing.JComboBox<String> documentSourceComboBox; private javax.swing.JLabel documentSourceComboBoxLabel; private javax.swing.JComboBox<String> modeComboBox; private javax.swing.JLabel modeComboBoxLabel; private javax.swing.JComboBox<String> resolutionComboBox; private javax.swing.JLabel resolutionComboBoxLabel; // End of variables declaration//GEN-END:variables }