@SuppressWarnings("serial")
public class DiscoveryEvent extends EventObject {

  private ArrayList<Scanner> discoveredScanners;

  public DiscoveryEvent(Object source) {
    super(source);
  }

  public final ArrayList<Scanner> getDiscoveredScanners() {
    return discoveredScanners;
  }

  public final void setDiscoveredScanners(ArrayList<Scanner> discoveredScanners) {
    this.discoveredScanners = discoveredScanners;
  }

} public class DiscoveryJob {

  /**
   * Event Listener List for Discovery Events.
   */
  private EventListenerList listenerList = new EventListenerList();

  /**
   * Discovery Event.
   */
  private DiscoveryEvent discoveryEvent;

  /**
   * Log4J logger.
   */
  private static final Logger LOG = Logger.getLogger(DiscoveryJob.class);

  /**
   * SANE service name.
   */
  public static final String SANE_SERVICE_NAME = "_sane-port._tcp.local.";

  /**
   * Status notification with console output as default implementation.
   */
  private INotification notification = new ConsoleNotificationImpl();

  private SwingWorker<ArrayList<Scanner>, Void> worker;

  private IScanService scanService;

  /**
   * Constructor for SaneDiscovery.
   *
   * @param scanService
   */
  public DiscoveryJob(IScanService scanService) {
    this.scanService = scanService;
  }

  /**
   * @param listener
   *          a discovery listener
   */
  public final void addDiscoveryListener(final DiscoveryListener listener) {
    listenerList.add(DiscoveryListener.class, listener);
  }

  public final void cancel() {
    worker.cancel(true);
  }

  private void detectScanners(JmDNS jmdns, InetAddress address,
      ArrayList<Scanner> discoveredScanners, ServiceInfo serviceInfo) throws IOException,
      SaneException {

    getNotificaiton().message(
        String.format(Localizer.localize("QueryingServerMessage"), serviceInfo.getName() + " ("
            + address.getHostAddress() + ":" + serviceInfo.getPort())
            + ")");

    if (worker.isCancelled()) {
      return;
    }

    SaneSession session = null;

    try {
      session = SaneSession.withRemoteSane(address, serviceInfo.getPort());
      session.setPasswordProvider(getPasswordProvider());
      List<SaneDevice> devices = session.listDevices();

      if ((devices != null) && (devices.size() > 0)) {
        getNotificaiton().message(
            String.format(devices.size() > 1 ? Localizer.localize("FoundDevicesMessage")
                : Localizer.localize("FoundDeviceMessage"), devices.size()));
        for (SaneDevice device : devices) {
          if (worker.isCancelled()) {
            return;
          }
          try {
            Scanner scanner = scanService.create(device, serviceInfo, address.getHostAddress());
            discoveredScanners.add(scanner);
          } catch (Exception ex) {
            getNotificaiton().message(
                address.getHostAddress() + ":" + serviceInfo.getPort() + " - "
                    + ex.getLocalizedMessage());
          }
        }
      }

    } catch (SocketException ex) {
      getNotificaiton()
          .message(
              address.getHostAddress() + ":" + serviceInfo.getPort() + " - "
                  + ex.getLocalizedMessage());
    } finally {
      if (session != null) {
        session.close();
      }
      try {
        jmdns.unregisterAllServices();
      } catch (Exception e) {
        getNotificaiton().message(e.getLocalizedMessage());
        LOG.error(e, e);
      }
      try {
        jmdns.close();
      } catch (IOException e) {
        getNotificaiton().message(e.getLocalizedMessage());
        LOG.error(e, e);
      }
    }
  }

  /**
   * Perform discovery of SANE scanners.
   *
   * @param scanService
   *          an instance of {@link ScanServiceImpl}
   */
  public final synchronized void discover() {

    worker = new SwingWorker<ArrayList<Scanner>, Void>() {

      @Override
      protected ArrayList<Scanner> doInBackground() throws Exception {

        getNotificaiton().message(Localizer.localize("DiscoverSanedServersMessage"));

        JmDNS jmdns = JmDNS.create(getLocalAddress());
        ServiceInfo[] serviceInfoArr = jmdns.list(getSaneServiceIdentity().getServiceName());

        ArrayList<Scanner> discoveredScanners = new ArrayList<Scanner>();

        for (ServiceInfo serviceInfo : serviceInfoArr) {

          if (isCancelled()) {
            return discoveredScanners;
          }

          // try IPV4 addresses before IPV6 ones.
          for (Inet4Address address : serviceInfo.getInet4Addresses()) {
            detectScanners(jmdns, address, discoveredScanners, serviceInfo);
          }

          if (discoveredScanners.size() <= 0) {
            for (Inet6Address address : serviceInfo.getInet6Addresses()) {
              detectScanners(jmdns, address, discoveredScanners, serviceInfo);
            }
          }

        }

        return discoveredScanners;

      }

      @Override
      protected void done() {

        ArrayList<Scanner> discoveredScanners = null;

        try {
          discoveredScanners = get();
          getNotificaiton().message(Localizer.localize("DiscoveryCompleteMessage"));
        } catch (InterruptedException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.error(e, e);
        } catch (ExecutionException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.error(e, e);
        } catch (CancellationException e) {
          getNotificaiton().message(Localizer.localize("DiscoveryCancelledMessage"));
        } finally {
          fireDiscoveryEvent(discoveredScanners);
        }

      }

    };

    worker.execute();

  }

  public final synchronized void discover(final InetAddress address, final int portNumber,
      final String description) throws IOException, SaneException {

    worker = new SwingWorker<ArrayList<Scanner>, Void>() {

      @Override
      protected ArrayList<Scanner> doInBackground() throws Exception {

        getNotificaiton().message(
            String.format(Localizer.localize("QueryingServerMessage"), address.getHostName() + " ("
                + address.getHostAddress() + ":" + portNumber)
                + ")");

        ArrayList<Scanner> discoveredScanners = new ArrayList<Scanner>();
        SaneSession session = null;

        try {
          session = SaneSession.withRemoteSane(address, portNumber);
          session.setPasswordProvider(getPasswordProvider());
          List<SaneDevice> devices = session.listDevices();

          if ((devices != null) && (devices.size() > 0)) {
            getNotificaiton().message(
                String.format(devices.size() > 1 ? Localizer.localize("FoundDevicesMessage")
                    : Localizer.localize("FoundDeviceMessage"), devices.size()));
            for (SaneDevice device : devices) {
              try {
                Scanner scanner = scanService.create(device, address.getHostAddress(), portNumber,
                    description);
                discoveredScanners.add(scanner);
              } catch (Exception ex) {
                getNotificaiton().message(
                    address.getHostAddress() + ":" + portNumber + " - " + ex.getLocalizedMessage());
              }
            }
          }

        } catch (SocketException ex) {
          getNotificaiton().message(
              address.getHostAddress() + ":" + portNumber + " - " + ex.getLocalizedMessage());
        } finally {
          if (session != null) {
            session.close();
          }
        }

        return discoveredScanners;

      }

      @Override
      protected void done() {

        ArrayList<Scanner> discoveredScanners = null;

        try {
          discoveredScanners = get();
          getNotificaiton().message(Localizer.localize("DiscoveryCompleteMessage"));
        } catch (InterruptedException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.error(e, e);
        } catch (ExecutionException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.error(e, e);
        } catch (CancellationException e) {
          getNotificaiton().message(Localizer.localize("DiscoveryCancelledMessage"));
        } finally {
          fireDiscoveryEvent(discoveredScanners);
        }

      }

    };

    worker.execute();

  }

  /**
   * @param discoveredScanners
   *          an {@link ArrayList} of discovered {@link Scanner}(s)
   */
  private void fireDiscoveryEvent(final ArrayList<Scanner> discoveredScanners) {

    Object[] listeners = listenerList.getListenerList();

    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == DiscoveryListener.class) {
        if (discoveryEvent == null) {
          discoveryEvent = new DiscoveryEvent(this);
          discoveryEvent.setDiscoveredScanners(discoveredScanners);
        }
        ((DiscoveryListener) listeners[i + 1]).discoveryEventOccurred(discoveryEvent);
      }
    }

  }

  public final InetAddress getLocalAddress() throws SocketException {
    for (final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); interfaces
        .hasMoreElements();) {
      final NetworkInterface networkInterface = interfaces.nextElement();
      if (networkInterface.isLoopback()) {
        continue;
      }
      for (final InterfaceAddress interfaceAddr : networkInterface.getInterfaceAddresses()) {
        final InetAddress inetAddr = interfaceAddr.getAddress();
        if (!(inetAddr instanceof Inet4Address)) {
          continue;
        }
        return inetAddr;
      }
    }
    return null;
  }

  /**
   * Returns an instance of INotification if set.
   *
   * @return an instance of INotification if set.
   */
  public final INotification getNotificaiton() {
    return notification;
  }

  private SanePasswordProvider getPasswordProvider() {
    return scanService.getPasswordProvider();
  }

  private SaneServiceIdentity getSaneServiceIdentity() {
    return scanService.getSaneServiceIdentity();
  }

  public final boolean isActive() {
    return !(worker.isDone());
  }

  /**
   * @param listener
   *          a discovery listener
   */
  public final void removeDiscoveryListener(final DiscoveryListener listener) {
    listenerList.remove(DiscoveryListener.class, listener);
  }

  /**
   * Sets an instance of INotification.
   *
   * @param notificationImpl
   *          an instance of INotification
   */
  public final void setNotificaiton(final INotification notificationImpl) {
    notification = notificationImpl;
  }

} public interface DiscoveryListener extends EventListener {

  void discoveryEventOccurred(DiscoveryEvent devt);

} public interface INotification {

  void addAbortListener();

  Exception getException();

  boolean isInterrupted();

  void message(String message);

  void setException(Exception exception);

  void setInterrupted(boolean interrupted);

} public final class KnownSaneOptions {

  public static SpinnerModel getBlackThresholdModel(Scanner scanner) {
    SpinnerNumberModel blackThresholdModel = new SpinnerNumberModel(0, MIN_BLACK_THRESHOLD,
        MAX_BLACK_THRESHOLD, 1);

    HashMap<String, FixedOption> fixedOptions = scanner.getFixedOptions();

    FixedOption fixedOption = fixedOptions.get(SANE_NAME_THRESHOLD);

    if (fixedOption == null) {
      return null;
    }

    Constraints constraints = fixedOption.getConstraints();
    Integer maxInt = constraints.getMaximumInteger();
    Integer minInt = constraints.getMinimumInteger();

    blackThresholdModel.setMaximum(maxInt);
    blackThresholdModel.setMinimum(minInt);
    blackThresholdModel.setStepSize(constraints.getQuantumInteger());

    blackThresholdModel.setValue(fixedOption.getValue());

    return blackThresholdModel;
  }

  public static String getColorMode(Scanner scanner) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_MODE);
    if (stringOption == null) {
      return null;
    }
    return stringOption.getValue();
  }

  public static ComboBoxModel<String> getColorModeComponent(Scanner scanner) {

    DefaultComboBoxModel<String> colorModel = new DefaultComboBoxModel<String>();

    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();

    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_MODE);

    if (stringOption == null) {
      return null;
    }

    Constraints constraints = stringOption.getConstraints();
    List<String> values = constraints.getStringList();

    for (String value : values) {
      colorModel.addElement(value);
    }

    if (values.size() > 0) {
      return colorModel;
    } else {
      return null;
    }

  }

  public static String getPageSize(Scanner scanner) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(EPSON_NAME_SCAN_AREA);
    if (stringOption == null) {
      return null;
    }
    return stringOption.getValue();
  }

  public static ComboBoxModel<String> getPageSizeModel(Scanner scanner) {
    DefaultComboBoxModel<String> pageSizeModel = new DefaultComboBoxModel<String>();

    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();

    StringOption stringOption = stringOptions.get(EPSON_NAME_SCAN_AREA);

    if (stringOption == null) {
      return null;
    }

    Constraints constraints = stringOption.getConstraints();
    List<String> values = constraints.getStringList();

    for (String value : values) {
      pageSizeModel.addElement(value);
    }

    if (values.size() > 0) {
      return pageSizeModel;
    } else {
      return null;
    }

  }

  public static Integer getResolution(Scanner scanner) {
    HashMap<String, IntegerOption> integerOptions = scanner.getIntegerOptions();
    IntegerOption integerOption = integerOptions.get(SANE_NAME_SCAN_RESOLUTION);
    if (integerOption == null) {
      integerOption = integerOptions.get(SANE_NAME_SCAN_X_RESOLUTION);
    }
    if (integerOption == null) {
      return null;
    }
    return new Integer(integerOption.getValue());
  }

  public static ComboBoxModel<Integer> getResolutionModel(Scanner scanner) {

    DefaultComboBoxModel<Integer> resolutionModel = new DefaultComboBoxModel<Integer>();

    HashMap<String, IntegerOption> integerOptions = scanner.getIntegerOptions();

    IntegerOption integerOption = integerOptions.get(SANE_NAME_SCAN_RESOLUTION);

    if (integerOption == null) {
      integerOption = integerOptions.get(SANE_NAME_SCAN_X_RESOLUTION);
    }

    if (integerOption == null) {
      return null;
    }

    Constraints constraints = integerOption.getConstraints();
    List<Integer> integerList = constraints.getIntegerList();
    Integer maxInt = constraints.getMaximumInteger();
    Integer minInt = constraints.getMinimumInteger();
    Integer quantum = constraints.getQuantumInteger();

    if (integerList != null) {
      for (Integer value : integerList) {
        resolutionModel.addElement(value);
      }
    } else {
      for (int res = minInt; res >= maxInt; res += quantum) {
        resolutionModel.addElement(res);
      }
    }

    if (resolutionModel.getSize() > 0) {
      return resolutionModel;
    } else {
      return null;
    }

  }

  public static String getSource(Scanner scanner) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_SOURCE);
    if (stringOption == null) {
      stringOption = stringOptions.get(SAMSUNG_NAME_SCAN_SOURCE);
      if (stringOption == null) {
        return null;
      }
    }
    return stringOption.getValue();
  }

  public static ComboBoxModel<String> getSourceModel(Scanner scanner) {
    DefaultComboBoxModel<String> sourceModel = new DefaultComboBoxModel<String>();

    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();

    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_SOURCE);

    if (stringOption == null) {
      return null;
    }

    Constraints constraints = stringOption.getConstraints();
    List<String> values = constraints.getStringList();

    for (String value : values) {
      sourceModel.addElement(value);
    }

    if (values.size() > 0) {
      return sourceModel;
    } else {
      return null;
    }
  }

  public static boolean isAutoCropAvailable(Scanner scanner) {
    HashMap<String, BooleanOption> booleanOptions = scanner.getBooleanOptions();
    BooleanOption booleanOption = booleanOptions.get(EPSON_NAME_AUTOCROP); // epson scanners only?
    return !(booleanOption == null);
  }

  public static boolean isAutoCropSelected(Scanner scanner) {
    HashMap<String, BooleanOption> booleanOptions = scanner.getBooleanOptions();
    BooleanOption booleanOption = booleanOptions.get(EPSON_NAME_AUTOCROP); // epson scanners only?
    if (booleanOption == null) {
      return false;
    } else {
      return booleanOption.getValue();
    }
  }

  private static boolean isAutomaticDocumentFeederString(String value) {
    return value.toLowerCase().contains("adf") || value.toLowerCase().contains("document feeder");
  }

  public static boolean isBatchScanAvailable(Scanner scanner) {
    HashMap<String, BooleanOption> booleanOptions = scanner.getBooleanOptions();
    BooleanOption booleanOption = booleanOptions.get(SANE_KNOWN_OPTION_BATCH_SCAN);
    if (booleanOption == null) {
      booleanOption = booleanOptions.get(EPSON_NAME_ADF_AUTO_SCAN);
      if (booleanOption == null) {
        return false;
      }
    }
    return true;
  }

  public static boolean isBatchScanSelected(Scanner scanner) {
    HashMap<String, BooleanOption> booleanOptions = scanner.getBooleanOptions();
    BooleanOption booleanOption = booleanOptions.get(SANE_KNOWN_OPTION_BATCH_SCAN);
    if (booleanOption == null) {
      booleanOption = booleanOptions.get(EPSON_NAME_ADF_AUTO_SCAN);
      if (booleanOption == null) {
        return false;
      }
    }
    return booleanOption.getValue();
  }

  private static boolean isBinaryColorString(String value) {
    return value.toLowerCase().contains("black & white") || value.toLowerCase().contains("mono")
        || value.toLowerCase().contains("binary") || value.toLowerCase().contains("halftone")
        || value.toLowerCase().contains("thresholded") || value.toLowerCase().contains("lineart");
  }

  private static boolean isColorColorString(String value) {
    return value.toLowerCase().contains("color");
  }

  public static boolean isDuplexScanningAvailable(Scanner scanner) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(SANE_KNOWN_OPTION_DUPLEX);
    if (stringOption == null) {
      stringOption = stringOptions.get(EPSON_NAME_ADF_MODE);
    }
    return !(stringOption == null);
  }

  public static boolean isDuplexScanningEnabled(Scanner scanner) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();

    StringOption stringOption = stringOptions.get(SANE_KNOWN_OPTION_DUPLEX);
    if ((stringOption != null) && (!stringOption.getOptionsOrderValuePair().isActive())) {
      return false;
    }

    if (stringOption == null) {
      stringOption = stringOptions.get(EPSON_NAME_ADF_MODE);
      if ((stringOption == null) || !stringOption.getOptionsOrderValuePair().isActive()) {
        return false;
      }
    }

    return stringOption.getValue().toLowerCase().contains("duplex");
  }

  private static boolean isFlatbedFeederString(String value) {
    return value.toLowerCase().contains("flatbed");
  }

  private static boolean isGrayscaleColorString(String value) {
    return value.toLowerCase().contains("gray") || value.toLowerCase().contains("grey");
  }

  public static boolean isUsingDefaultBlackThreshold(Scanner scanner) {
    HashMap<String, FixedOption> fixedOptions = scanner.getFixedOptions();
    FixedOption fixedOption = fixedOptions.get(SANE_NAME_THRESHOLD);
    if (fixedOption == null) {
      return false;
    }
    return fixedOption.getOptionsOrderValuePair().isActive();
  }

  public static void setAutoCrop(Scanner scanner, boolean autoCropEnabled) {
    HashMap<String, BooleanOption> booleanOptions = scanner.getBooleanOptions();
    BooleanOption booleanOption = booleanOptions.get(EPSON_NAME_AUTOCROP); // epson scanners only?
    if (booleanOption == null) {
      return;
    }
    booleanOption.setValue(autoCropEnabled);
  }

  public static void setBatchScan(Scanner scanner, boolean enabled) {
    HashMap<String, BooleanOption> booleanOptions = scanner.getBooleanOptions();
    BooleanOption booleanOption = booleanOptions.get(SANE_KNOWN_OPTION_BATCH_SCAN);
    if (booleanOption == null) {
      booleanOption = booleanOptions.get(EPSON_NAME_ADF_AUTO_SCAN);
    }
    if (booleanOption == null) {
      return;
    }
    booleanOption.setValue(enabled);
  }

  public static void setBlackThreshold(Scanner scanner, int blackThreshold) {
    HashMap<String, FixedOption> fixedOptions = scanner.getFixedOptions();

    FixedOption fixedOption = fixedOptions.get(SANE_NAME_THRESHOLD);
    if (fixedOption == null) {
      return;
    }
    fixedOption.setValue(blackThreshold);
  }

  public static void setColorMode(Scanner scanner, String colorMode) {

    HashMap<String, IntegerOption> integerOptions = scanner.getIntegerOptions();
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();

    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_MODE);

    if (stringOption == null) {
      return;
    }

    IntegerOption depth = integerOptions.get(SANE_NAME_BIT_DEPTH);

    // black and white
    if (isBinaryColorString(colorMode)) {
      if (depth != null) {
        depth.getOptionsOrderValuePair().setActive(false);
      }
      stringOption.setValue(colorMode);
      return;
    }

    Constraints depthConstraints = depth.getConstraints();

    // grayscale
    if (colorMode.toLowerCase().contains("gray") || colorMode.toLowerCase().contains("grey")) {
      if (depthConstraints != null) {
        List<Integer> values = depthConstraints.getIntegerList();
        if (values.contains(COLOR_DEPTH_8_BIT)) {
          depth.setValue(COLOR_DEPTH_8_BIT);
        } else if (values.contains(COLOR_DEPTH_12_BIT)) {
          depth.setValue(COLOR_DEPTH_12_BIT);
        } else if (values.contains(COLOR_DEPTH_16_BIT)) {
          depth.setValue(COLOR_DEPTH_16_BIT);
        } else if (values.contains(COLOR_DEPTH_24_BIT)) {
          depth.setValue(COLOR_DEPTH_24_BIT);
        }
      }
      stringOption.setValue(colorMode);
      return;
    }

    // color
    if (depthConstraints != null) {
      List<Integer> values = depthConstraints.getIntegerList();
      if (values.contains(COLOR_DEPTH_16_BIT)) {
        depth.setValue(COLOR_DEPTH_16_BIT);
      } else if (values.contains(COLOR_DEPTH_24_BIT)) {
        depth.setValue(COLOR_DEPTH_24_BIT);
      }
    }
    stringOption.setValue(colorMode);

  }

  public static void setDefaultColorMode(Scanner scanner, ColorMode color) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_MODE);
    if (stringOption == null) {
      return;
    }
    Constraints constraints = stringOption.getConstraints();
    if (constraints == null) {
      return;
    }
    List<String> values = constraints.getStringList();
    for (String value : values) {
      switch (color) {
      case BLACK_AND_WHITE:
        if (isBinaryColorString(value)) {
          setColorMode(scanner, value);
          return;
        }
        break;
      case GRAYSCALE:
        if (isGrayscaleColorString(value)) {
          setColorMode(scanner, value);
          return;
        }
        break;
      case COLOR:
        if (isColorColorString(value)) {
          setColorMode(scanner, value);
          return;
        }
        break;
      default:
        break;

      }
    }
  }

  public static void setDefaultScanArea(Scanner scanner, String defaultScanArea) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(EPSON_NAME_SCAN_AREA);
    if (stringOption == null) {
      return;
    }
    Constraints constraints = stringOption.getConstraints();
    if (constraints == null) {
      return;
    }
    List<String> values = constraints.getStringList();
    for (String value : values) {
      if (value.toLowerCase().contains(defaultScanArea.toLowerCase())) {
        setScanArea(scanner, value);
        return;
      }
    }
  }

  public static void setDefaultSource(Scanner scanner, Source source) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_SOURCE);
    if (stringOption == null) {
      return;
    }
    Constraints constraints = stringOption.getConstraints();
    if (constraints == null) {
      return;
    }
    List<String> values = constraints.getStringList();
    for (String value : values) {
      switch (source) {
      case AUTOMATIC_DOCUMENT_FEEDER:
        if (isAutomaticDocumentFeederString(value)) {
          setSource(scanner, value);
          return;
        }
        break;
      case FLATBED:
        if (isFlatbedFeederString(value)) {
          setSource(scanner, value);
          return;
        }
        break;
      default:
        break;
      }
    }
  }

  public static void setDuplex(Scanner scanner, boolean isDuplex) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();

    StringOption stringOption = stringOptions.get(SANE_KNOWN_OPTION_DUPLEX);

    if (stringOption == null) {
      stringOption = stringOptions.get(EPSON_NAME_ADF_MODE);
      if (stringOption == null) {
        return;
      }
    }

    Constraints constraints = stringOption.getConstraints();
    List<String> values = constraints.getStringList();

    if (isDuplex) {
      for (String value : values) {
        if (value.toLowerCase().contains("duplex")) {
          stringOption.setValue(value);
          stringOption.getOptionsOrderValuePair().setActive(true);
          return;
        }
      }
    } else {
      for (String value : values) {
        if (!(value.toLowerCase().contains("duplex"))) {
          stringOption.setValue(value);
          return;
        }
      }
    }

  }

  public static void setResolution(Scanner scanner, Integer resolution) {
    HashMap<String, IntegerOption> integerOptions = scanner.getIntegerOptions();
    if (integerOptions.get(SANE_NAME_SCAN_X_RESOLUTION) != null) {
      integerOptions.get(SANE_NAME_SCAN_X_RESOLUTION).setValue(resolution);
      integerOptions.get(SANE_NAME_SCAN_Y_RESOLUTION).setValue(resolution);
    }
    if (integerOptions.get(SANE_NAME_SCAN_RESOLUTION) != null) {
      integerOptions.get(SANE_NAME_SCAN_RESOLUTION).setValue(resolution);
    }
  }

  public static void setScanArea(Scanner scanner, String pageSize) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(EPSON_NAME_SCAN_AREA);
    if (stringOption == null) {
      return;
    }
    stringOption.setValue(pageSize);
  }

  public static void setSource(Scanner scanner, String source) {
    HashMap<String, StringOption> stringOptions = scanner.getStringOptions();
    StringOption stringOption = stringOptions.get(SANE_NAME_SCAN_SOURCE);
    if (stringOption == null) {
      stringOption = stringOptions.get(SAMSUNG_NAME_SCAN_SOURCE);
      if (stringOption == null) {
        return;
      }
    }
    stringOption.setValue(source);
  }

  public static void setUsingDefaultBlackThreshold(Scanner scanner,
      boolean usingDefaultBlackThreshold) {
    HashMap<String, FixedOption> fixedOptions = scanner.getFixedOptions();
    FixedOption fixedOption = fixedOptions.get(SANE_NAME_THRESHOLD);
    if (fixedOption == null) {
      return;
    }
    fixedOption.getOptionsOrderValuePair().setActive(usingDefaultBlackThreshold);
  }

  private static final int MAX_BLACK_THRESHOLD = 128;

  private static final int MIN_BLACK_THRESHOLD = -128;

  private static final int COLOR_DEPTH_8_BIT = 8;

  private static final int COLOR_DEPTH_12_BIT = 12;

  private static final int COLOR_DEPTH_16_BIT = 16;

  private static final int COLOR_DEPTH_24_BIT = 24;

  private static final String SANE_KNOWN_OPTION_DUPLEX = "duplex";

  private static final String SANE_KNOWN_OPTION_BATCH_SCAN = "batch-scan";

  public static final String EPSON_NAME_ADF_AUTO_SCAN = "adf-auto-scan"; // batch-scan ??

  public static final String EPSON_NAME_ADF_MODE = "adf-mode"; // duplex ??

  public static final String EPSON_NAME_AUTOCROP = "autocrop";

  public static final String EPSON_NAME_SCAN_AREA = "scan-area";

  public static final String SAMSUNG_NAME_SCAN_SOURCE = "doc-source"; // source ??

  public static final String SANE_NAME_BIT_DEPTH = "depth";

  public static final String SANE_NAME_SCAN_MODE = "mode";

  public static final String SANE_NAME_SCAN_RESOLUTION = "resolution";

  public static final String SANE_NAME_SCAN_SOURCE = "source";

  public static final String SANE_NAME_SCAN_X_RESOLUTION = "x-resolution";

  public static final String SANE_NAME_SCAN_Y_RESOLUTION = "y-resolution";

  public static final String SANE_NAME_THRESHOLD = "threshold";

  private KnownSaneOptions() {
  }

} public interface IScanService {

  void configure(SaneDevice saneDevice, Scanner scanner) throws IOException;

  Scanner create(SaneDevice saneDevice, ServiceInfo serviceInfo, String hostAddress)
      throws IOException, SaneException;

  Scanner create(SaneDevice saneDevice, String hostAddress, int portNumber, String description)
      throws IOException, SaneException;

  SanePasswordProvider getPasswordProvider();

  SaneServiceIdentity getSaneServiceIdentity();

  void setPasswordProvider(SanePasswordProvider passwordProvider);

  void setSaneServiceIdentity(SaneServiceIdentity saneService);

  void setScannerOptions(SaneDevice saneDevice, Scanner scanner) throws IOException, SaneException;

} @SuppressWarnings("serial")
public class ScanEvent extends EventObject {

  private BufferedImage bufferedImage;
  private int pagesToScan;
  private int pageNumber;
  private String batchPrefix;

  public ScanEvent(Object source) {
    super(source);
  }

  public final String getBatchPrefix() {
    return batchPrefix;
  }

  public final BufferedImage getBufferedImage() {
    return bufferedImage;
  }

  public final int getPageNumber() {
    return pageNumber;
  }

  public final int getPagesToScan() {
    return pagesToScan;
  }

  public final void setAcquiredImage(BufferedImage acquiredImage) {
    bufferedImage = acquiredImage;
  }

  public final void setBatchPrefix(String batchPrefix) {
    this.batchPrefix = batchPrefix;
  }

  public final void setPageNumber(int pageNumber) {
    this.pageNumber = pageNumber;
  }

  public final void setPagesToScan(int pagesToScan) {
    this.pagesToScan = pagesToScan;
  }

} public interface ScanEventListener extends EventListener {

  void eventOccurred(ScanEvent sevt);

} public class ScanJob {

  /**
   * Log4J logger.
   */
  private static final Logger LOG = Logger.getLogger(ScanJob.class);

  /**
   * Event Listener List for Scan Events.
   */
  private EventListenerList listenerList = new EventListenerList();

  /**
   * Scan Event.
   */
  private ScanEvent scanEvent;

  /**
   * Status notification with console output as default implementation.
   */
  private INotification notification = new ConsoleNotificationImpl();

  private Scanner scanner;

  private SwingWorker<Void, Void> worker;

  private int pagesToScan = Integer.MAX_VALUE;

  private IScanService scanService;

  private boolean useADF;

  private String batchPrefix;

  public ScanJob(IScanService scanServiceImpl, Scanner scanner) {
    scanService = scanServiceImpl;
    this.scanner = scanner;
  }

  public final void acquire(final ScanListener rateLimitedScanListener) {

    worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {

        getNotificaiton().message(Localizer.localize("ScanStartingMessage"));

        String hostAddrStr = scanner.getRemoteAddress();
        int port = scanner.getRemotePortNumber();

        SaneSession session = null;
        SaneDevice device = null;

        try {
          session = SaneSession.withRemoteSane(InetAddress.getByName(hostAddrStr), port);
          session.setPasswordProvider(getPasswordProvider());
          device = session.getDevice(scanner.getName());

          try {
            if (!(device.isOpen())) {
              device.open();
            }
          } catch (IOException e) {
            getNotificaiton().message(e.getLocalizedMessage());
            LOG.warn(e, e);
            return null;
          }

          try {
            scanService.configure(device, scanner);
          } catch (IOException ex) {
            getNotificaiton().message(ex.getLocalizedMessage());
            LOG.warn(ex, ex);
            // continue if we can't set a setting.
          }

          int pageCount = 1;
          while (!isCancelled()) {
            try {
              BufferedImage image = device.acquireImage(rateLimitedScanListener);
              getNotificaiton().message(
                  String.format(Localizer.localize("ScannedPageMessage"), pageCount));
              fireScanEvent(image, pageCount);
            } catch (SaneException e) {
              if (e.getStatus() == SaneStatus.STATUS_NO_DOCS) {
                break;
              } else {
                getNotificaiton().message(e.getLocalizedMessage());
                LOG.warn(e, e);
                break;
              }
            } catch (IOException e) {
              getNotificaiton().message(e.getLocalizedMessage());
              LOG.warn(e, e);
              break;
            }
            if (!useADF && (pageCount >= pagesToScan)) {
              break;
            }
            pageCount++;
          }
        } catch (UnknownHostException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
          return null;
        } catch (IOException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
          return null;
        } finally {
          try {
            if ((device != null) && device.isOpen()) {
              device.close();
            }
          } catch (IOException e) {
            getNotificaiton().message(e.getLocalizedMessage());
            LOG.warn(e, e);
          }
          if (session != null) {
            session.close();
          }
        }
        return null;
      }

      @Override
      protected void done() {

        try {
          get();
          getNotificaiton().message(Localizer.localize("ScanCompleteMessage"));
        } catch (InterruptedException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
        } catch (ExecutionException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
        } catch (CancellationException e) {
          getNotificaiton().message(Localizer.localize("ScanCancelledMessage"));
        }

        fireScanEvent(null, 0);

      }
    };

    worker.execute();

  }

  public final void addScanListener(final ScanEventListener listener) {
    listenerList.add(ScanEventListener.class, listener);
  }

  public final void cancel() {
    worker.cancel(true);
  }

  public final void checkOptions() throws Exception {

    String hostAddr = scanner.getRemoteAddress();
    int port = scanner.getRemotePortNumber();
    SaneSession session = null;
    SaneDevice device = null;

    try {
      session = SaneSession.withRemoteSane(InetAddress.getByName(hostAddr), port);
      session.setPasswordProvider(getPasswordProvider());
      device = session.getDevice(scanner.getName());
      if (!(device.isOpen())) {
        device.open();
      }
      scanService.configure(device, scanner);
      scanService.setScannerOptions(device, scanner);
    } catch (UnknownHostException e) {
      LOG.warn(e, e);
      throw e;
    } catch (SaneException e) {
      LOG.warn(e, e);
      throw e;
    } catch (IOException e) {
      LOG.warn(e, e);
      throw e;
    } finally {
      try {
        if ((device != null) && device.isOpen()) {
          device.close();
        }
      } catch (IOException e) {
        LOG.warn(e, e);
      }
      if (session != null) {
        try {
          session.close();
        } catch (IOException e) {
          LOG.warn(e, e);
        }
      }
    }

  }

  private void fireScanEvent(final BufferedImage bufferedImage, int pageNumber) {

    Object[] listeners = listenerList.getListenerList();

    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == ScanEventListener.class) {
        if (scanEvent == null) {
          scanEvent = new ScanEvent(this);
        }
        scanEvent.setAcquiredImage(bufferedImage);
        scanEvent.setPagesToScan(pagesToScan);
        scanEvent.setPageNumber(pageNumber);
        scanEvent.setBatchPrefix(getBatchPrefix());
        ((ScanEventListener) listeners[i + 1]).eventOccurred(scanEvent);
      }
    }

  }

  private String getBatchPrefix() {
    return batchPrefix;
  }

  /**
   * Returns an instance of INotification if set.
   *
   * @return an instance of INotification if set.
   */
  public final INotification getNotificaiton() {
    return notification;
  }

  private SanePasswordProvider getPasswordProvider() {
    return scanService.getPasswordProvider();
  }

  public final boolean isActive() {
    return !(worker.isDone());
  }

  /**
   * @param listener
   *          a scan listener
   */
  public final void removeScanEventListener(final ScanEventListener listener) {
    listenerList.remove(ScanEventListener.class, listener);
  }

  public final void setBatchPrefix(String batchPrefix) {
    this.batchPrefix = batchPrefix;
  }

  public final void setButtonValue(String key) {
    String hostAddr = scanner.getRemoteAddress();
    int port = scanner.getRemotePortNumber();
    SaneSession session = null;
    SaneDevice device = null;

    try {
      session = SaneSession.withRemoteSane(InetAddress.getByName(hostAddr), port);
      session.setPasswordProvider(getPasswordProvider());
      device = session.getDevice(scanner.getName());

      if (!(device.isOpen())) {
        device.open();
      }

      SaneOption saneOption = device.getOption(key);
      saneOption.setButtonValue(); // TODO: BUG??? doesn't do anything???

    } catch (UnknownHostException e) {
      LOG.warn(e, e);
    } catch (SaneException e) {
      LOG.warn(e, e);
    } catch (IOException e) {
      LOG.warn(e, e);
    } finally {
      try {
        if ((device != null) && device.isOpen()) {
          device.close();
        }
      } catch (IOException e) {
        LOG.warn(e, e);
      }
      if (session != null) {
        try {
          session.close();
        } catch (IOException e) {
          LOG.warn(e, e);
        }
      }
    }

  }

  /**
   * Sets an instance of INotification.
   *
   * @param notificationImpl
   *          an instance of INotification
   */
  public final void setNotificaiton(final INotification notificationImpl) {
    notification = notificationImpl;
  }

  public final void setPagesToScan(int pagesToScan) {
    this.pagesToScan = pagesToScan;
  }

  public final void setUseADF(boolean useADF) {
    this.useADF = useADF;
  }

} @SuppressWarnings("serial")
public class CustomSettingsDialog extends JDialog {

  private class CustomTableCellEditor extends DefaultCellEditor {

    private OptionsOrderValuePair valuePair;

    public CustomTableCellEditor(JCheckBox checkBox, final OptionsOrderValuePair vp) {
      super(checkBox);
      checkBox.removeActionListener(delegate);
      valuePair = vp;
      delegate.setValue(checkBox);
      delegate = new EditorDelegate() {
        @Override
        public void setValue(Object val) {
          if (val instanceof JCheckBox) {
            if (value == null) {
              value = val;
            }
          }
        }

        @Override
        public boolean stopCellEditing() {
          updateOption(valuePair, (Component) value);
          return true;
        }
      };
      checkBox.addActionListener(delegate);
      setClickCountToStart(1);
    }

    public CustomTableCellEditor(final JComboBox<?> comboBox, final OptionsOrderValuePair vp) {
      super(comboBox);
      comboBox.removeActionListener(delegate);
      valuePair = vp;
      delegate.setValue(comboBox);
      delegate = new EditorDelegate() {
        @Override
        public void setValue(Object val) {
          if (val instanceof JComboBox) {
            if (value == null) {
              value = val;
            }
          }
        }

        @Override
        public boolean stopCellEditing() {
          updateOption(valuePair, (Component) value);
          return true;
        }
      };
      comboBox.addActionListener(delegate);
      setClickCountToStart(1);
    }

    public CustomTableCellEditor(JTextField textField, final OptionsOrderValuePair vp) {
      super(textField);
      textField.removeActionListener(delegate);
      valuePair = vp;
      delegate.setValue(textField);
      delegate = new EditorDelegate() {
        @Override
        public void setValue(Object val) {
          if (val instanceof JTextField) {
            if (value == null) {
              value = val;
            }
          }
        }

        @Override
        public boolean stopCellEditing() {
          updateOption(valuePair, (Component) value);
          return true;
        }
      };
      textField.addActionListener(delegate);
      setClickCountToStart(1);
    }

    @Override
    public Object getCellEditorValue() {
      if (editorComponent instanceof JCheckBox) {
        return editorComponent;
      } else if (editorComponent instanceof JTextField) {
        return editorComponent;
      } else if (editorComponent instanceof JCheckBox) {
        return editorComponent;
      } else if (editorComponent instanceof JSpinner) {
        return editorComponent;
      } else if (editorComponent instanceof JButton) {
        return editorComponent;
      } else {
        return editorComponent;
      }
    }

  }

  public class CustomTableCellRenderer extends DefaultTableCellRenderer {

    @Override
    public final Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column) {
      if (value instanceof Component) {
        return (Component) value;
      } else {
        return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
      }
    }

  }

  private class JTableCustomComponent extends JTable {

    @Override
    public TableCellEditor getCellEditor(int row, int column) {
      Object value = getValueAt(row, column);
      if (value instanceof JCheckBox) {
        return new CustomTableCellEditor((JCheckBox) value, scanner.getOptionOrdering().get(row));
      } else if (value instanceof JComboBox) {
        return new CustomTableCellEditor((JComboBox<?>) value, scanner.getOptionOrdering().get(row));
      } else if (value instanceof JTextField) {
        return new CustomTableCellEditor((JTextField) value, scanner.getOptionOrdering().get(row));
      } else if (value instanceof JSpinner) {
        return new SpinnerEditor((JSpinner) value, scanner.getOptionOrdering().get(row));
      } else {
        return getDefaultEditor(value.getClass());
      }
    }

    @Override
    public TableCellRenderer getCellRenderer(int row, int column) {
      Object obj = getValueAt(row, column);
      if (obj == null) {
        return getDefaultRenderer(null);
      }
      Class<?> cellClass = obj.getClass();
      return getDefaultRenderer(cellClass);
    }

    @Override
    public boolean isCellEditable(int row, int column) {
      Class<?> cellClass = getValueAt(row, column).getClass();
      return (cellClass == JCheckBox.class) || (cellClass == JTextField.class)
          || (cellClass == JSpinner.class) || (cellClass == JComboBox.class);
    }

  }

  private class SpinnerEditor extends DefaultCellEditor {

    private OptionsOrderValuePair valuePair;

    private JSpinner spinner;

    public SpinnerEditor(final JSpinner spinner, final OptionsOrderValuePair vp) {
      super(new JTextField());
      this.spinner = spinner;
      valuePair = vp;
      spinner.addChangeListener(new ChangeListener() {
        @Override
        public void stateChanged(ChangeEvent e) {
          updateOption(valuePair, spinner);
        }
      });
      setClickCountToStart(1);
    }

    @Override
    public Object getCellEditorValue() {
      return spinner;
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
        int row, int column) {
      if (value instanceof Integer) {
        spinner.setValue(value);
      } else if (value instanceof Double) {
        spinner.setValue(value);
      }
      return spinner;
    }

  }

  private static DefaultTableModel getTableModel() {
    return new DefaultTableModel(new String[] { Localizer.localize("OptionNameHeaderText"),
        Localizer.localize("OptionActiveStatusHeaderText"),
        Localizer.localize("OptionModifyHeaderText") }, 0) {

      private boolean[] columnEditable = new boolean[] { false, false, true };

      @Override
      public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnEditable[columnIndex];
      }
    };
  }

  private static final int BOUNDS_WIDTH = 700;

  private static final int BOUNDS_HEIGHT = 500;

  private final Dimension bounds = new Dimension(BOUNDS_WIDTH, BOUNDS_HEIGHT);

  /**
   * Log4J logger.
   */
  private static final Logger LOG = Logger.getLogger(CustomSettingsDialog.class);

  private static final int ROW_HEIGHT = 30;

  private final JPanel contentPanel = new JPanel();
  private int dialogResult = JOptionPane.CANCEL_OPTION;

  private JTableCustomComponent optionNameTable;

  private DefaultTableModel optionNameTableModel = getTableModel();

  private Scanner scanner;

  private ArrayList<OptionsOrderValuePair> optionOrdering;

  private IScanService scanService;

  private XStream xstream;

  public CustomSettingsDialog(Component parent) {
    initComponents();
    pack();
    setLocationRelativeTo(parent);
  }

  private void cancelActionPerformed(ActionEvent e) {
    dispose();
  }

  private void cellButtonClicked(String key) {
    ScanJob scanJob = new ScanJob(scanService, scanner);
    scanJob.setButtonValue(key);
  }

  private void checkOptionsActionPerformed(ActionEvent e) {

    final INotification notificaiton = new DialogNotificationImpl(getRootPane()
        .getTopLevelAncestor());

    SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {
        notificaiton.message(Localizer.localize("CheckingOptionsText"));
        ScanJob scanJob = new ScanJob(scanService, scanner);
        try {
          scanJob.checkOptions();
        } catch (Exception ex) {
          showCheckOptionsErrorMessage(ex);
        }
        updateTableModel();
        optionNameTable.revalidate();
        return null;
      }

      @Override
      protected void done() {
        try {
          get();
          ((JDialog) notificaiton).setVisible(false);
        } catch (Exception ex) {
          LOG.error(ex, ex);
        } finally {
          ((JDialog) notificaiton).dispose();
        }
      }
    };

    worker.execute();

    ((JDialog) notificaiton).setModal(true);
    ((JDialog) notificaiton).setVisible(true);

  }

  private void exportActionPerformed(ActionEvent e) {

    final File file = getSaveFile("scanner", "xml");

    if (file == null) {
      return;
    }

    final INotification notificaiton = new DialogNotificationImpl(getRootPane()
        .getTopLevelAncestor());

    SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {
        notificaiton.message(Localizer.localize("SavingScannerPreferencesMessageText"));
        PreferencesUtils.exportScannerXML(xstream, scanner, file);
        return null;
      }

      @Override
      protected void done() {
        try {
          get();
          showSaveSuccessMessage();
          ((JDialog) notificaiton).setVisible(false);
        } catch (Exception ex) {
          LOG.error(ex, ex);
          showSaveErrorMessage(ex);
        } finally {
          ((JDialog) notificaiton).dispose();
        }
      }
    };

    worker.execute();

    ((JDialog) notificaiton).setModal(true);
    ((JDialog) notificaiton).setVisible(true);

  }

  public final int getDialogResult() {
    return dialogResult;
  }

  private File getLoadFile(String filename, String extension) {
    FileDialog fd = new FileDialog((JDialog) getRootPane().getTopLevelAncestor(),
        Localizer.localize("LoadScannerOptionsFromFileTitle"), FileDialog.LOAD);
    fd.setDirectory(".");
    FilenameExtensionFilter filter = new FilenameExtensionFilter();
    filter.addExtension("xml");
    fd.setFilenameFilter(filter);
    fd.setModal(true);
    fd.setVisible(true);
    if (fd.getFile() == null) {
      return null;
    }
    return new File(fd.getDirectory() + File.separator + fd.getFile());
  }

  private Component getOptionComponent(OptionsOrderValuePair vp) {

    // TODO: finish adding the constraints code.

    switch (vp.getSaneOptionType()) {

    case STRING:

      StringOption stringOption = scanner.getStringOptions().get(vp.getKey());

      Constraints constraints = stringOption.getConstraints();
      if (constraints != null) {
        List<String> values = constraints.getStringList();
        if (values != null) {

          DefaultComboBoxModel<String> dcbm = new DefaultComboBoxModel<String>();
          for (String value : values) {
            dcbm.addElement(value);
          }
          JComboBox<String> comboBox = new JComboBox<String>();
          comboBox.setModel(dcbm);
          comboBox.setSelectedItem(stringOption.getValue());
          comboBox.setFont(UIManager.getFont("ComboBox.font"));
          comboBox.setToolTipText(stringOption.getDescription());
          comboBox.setEditable(false);
          return comboBox;
        }
      }

      JTextField tf = new JTextField();
      tf.setText(stringOption.getValue());
      return tf;

    case INTEGER:
      JSpinner integerSpinner = new JSpinner();
      IntegerOption integerOptions = scanner.getIntegerOptions().get(vp.getKey());
      int minInt = (integerOptions.getConstraints() != null)
          && (integerOptions.getConstraints().getMinimumInteger() != null) ? integerOptions
          .getConstraints().getMinimumInteger() : 1;
      int maxInt = (integerOptions.getConstraints() != null)
          && (integerOptions.getConstraints().getMaximumInteger() != null) ? integerOptions
          .getConstraints().getMaximumInteger() : Integer.MAX_VALUE;
      int stepInt = (integerOptions.getConstraints() != null)
          && (integerOptions.getConstraints().getQuantumInteger() != null) ? integerOptions
          .getConstraints().getQuantumInteger() : 1;
      integerSpinner.setModel(new SpinnerNumberModel(new Integer(integerOptions.getValue()),
          new Integer(minInt), new Integer(maxInt), new Integer(stepInt)));
      integerSpinner.setToolTipText(integerOptions.getDescription());
      integerSpinner.setFont(UIManager.getFont("Spinner.font"));
      return integerSpinner;

    case BOOLEAN:
      JCheckBox checkBox = new JCheckBox();
      BooleanOption booleanOptions = scanner.getBooleanOptions().get(vp.getKey());
      checkBox.setSelected(booleanOptions.getValue());
      checkBox.setToolTipText(booleanOptions.getDescription());
      checkBox.setFont(UIManager.getFont("CheckBox.font"));
      return checkBox;

    case FIXED:
      JSpinner fixedSpinner = new JSpinner();
      FixedOption fixedOptions = scanner.getFixedOptions().get(vp.getKey());
      double minDouble = (fixedOptions.getConstraints() != null)
          && (fixedOptions.getConstraints().getMinimumFixed() != null) ? fixedOptions
          .getConstraints().getMinimumFixed() : 1;
      double maxDouble = (fixedOptions.getConstraints() != null)
          && (fixedOptions.getConstraints().getMaximumFixed() != null) ? fixedOptions
          .getConstraints().getMaximumFixed() : Double.MAX_VALUE;
      double stepDouble = (fixedOptions.getConstraints() != null)
          && (fixedOptions.getConstraints().getQuantumFixed() != null) ? fixedOptions
          .getConstraints().getQuantumFixed() : 1;
      fixedSpinner.setModel(new SpinnerNumberModel(new Double(fixedOptions.getValue()), new Double(
          minDouble), new Double(maxDouble), new Double(stepDouble)));
      fixedSpinner.setToolTipText(fixedOptions.getDescription());
      fixedSpinner.setFont(UIManager.getFont("Spinner.font"));
      return fixedSpinner;

    case BUTTON:
      JButton button = new JButton();
      button.setText(Localizer.localize("RunButtonText"));
      String key = vp.getKey();
      button.setActionCommand(key);
      button.setFont(UIManager.getFont("Button.font"));
      button.setToolTipText(getToolTipText(key));
      button.addMouseListener(new MouseListener() {
        @Override
        public void mouseClicked(MouseEvent e) {
          if (e.isConsumed()) {
            return;
          }
          cellButtonClicked(((JButton) (e.getSource())).getActionCommand());
          e.consume();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }

        @Override
        public void mousePressed(MouseEvent e) {
        }

        @Override
        public void mouseReleased(MouseEvent e) {
        }
      });
      return button;

    case GROUP:
      return null;
    default:
      break;

    }

    return null;

  }

  private File getSaveFile(String filename, String extension) {
    FileDialog fd = new FileDialog((JDialog) getRootPane().getTopLevelAncestor(),
        Localizer.localize("SaveScannerOptionsToFileTitle"), FileDialog.SAVE);
    fd.setDirectory(".");
    FilenameExtensionFilter filter = new FilenameExtensionFilter();
    filter.addExtension("xml");
    fd.setFilenameFilter(filter);
    fd.setFile(filename + "." + extension);
    fd.setModal(true);
    fd.setVisible(true);
    if (fd.getFile() == null) {
      return null;
    }
    return new File(fd.getDirectory() + File.separator + fd.getFile());
  }

  public final Scanner getScanner() {
    return scanner;
  }

  public final IScanService getScanService() {
    return scanService;
  }

  private String getToolTipText(String key) {
    ButtonOption buttonOption = scanner.getButtonOptions().get(key);
    return buttonOption.getDescription();
  }

  public final XStream getXstream() {
    return xstream;
  }

  private void importActionPerformed(ActionEvent e) {

    final File file = getLoadFile("scanner", "xml");

    if (file == null) {
      return;
    }

    final INotification notificaiton = new DialogNotificationImpl(getRootPane()
        .getTopLevelAncestor());

    SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {
        notificaiton.message(Localizer.localize("LoadingScannerPreferencesMessageText"));
        String remoteAddress = scanner.getRemoteAddress();
        int remotePort = scanner.getRemotePortNumber();
        String description = scanner.getDescription();
        scanner = PreferencesUtils.importScannerXML(xstream, file);
        scanner.setRemoteAddress(remoteAddress);
        scanner.setRemotePortNumber(remotePort);
        scanner.setDescription(description);
        updateTableModel();
        optionNameTable.revalidate();
        return null;
      }

      @Override
      protected void done() {
        try {
          get();
          showLoadSuccessMessage();
          ((JDialog) notificaiton).setVisible(false);
        } catch (Exception ex) {
          LOG.error(ex, ex);
          showLoadErrorMessage(ex);
        } finally {
          ((JDialog) notificaiton).dispose();
        }
      }
    };

    worker.execute();

    ((JDialog) notificaiton).setModal(true);
    ((JDialog) notificaiton).setVisible(true);

  }

  private void initComponents() {
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    setTitle(Localizer.localize("CustomSettingsDialogTitle"));
    setBounds(0, 0, bounds.width, bounds.height);
    setPreferredSize(bounds);
    setSize(bounds);
    setMinimumSize(bounds);
    getContentPane().setLayout(new BorderLayout());
    contentPanel.setBorder(new EmptyBorder(12, 12, 12, 12));
    getContentPane().add(contentPanel, BorderLayout.CENTER);
    GridBagLayout gbl_contentPanel = new GridBagLayout();
    gbl_contentPanel.columnWidths = new int[] { 80, 0 };
    gbl_contentPanel.rowHeights = new int[] { 0, 0, 0 };
    gbl_contentPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
    gbl_contentPanel.rowWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE };
    contentPanel.setLayout(gbl_contentPanel);
    {
      JScrollPane optionNameScrollPane = new JScrollPane();
      GridBagConstraints gbc_optionNameScrollPane = new GridBagConstraints();
      gbc_optionNameScrollPane.insets = new Insets(0, 0, 5, 0);
      gbc_optionNameScrollPane.fill = GridBagConstraints.BOTH;
      gbc_optionNameScrollPane.gridx = 0;
      gbc_optionNameScrollPane.gridy = 0;
      contentPanel.add(optionNameScrollPane, gbc_optionNameScrollPane);
      {
        optionNameTable = new JTableCustomComponent();
        optionNameTable.setFont(UIManager.getFont("Table.font"));
        optionNameTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        optionNameTable.getTableHeader().setFont(UIManager.getFont("TableHeader.font"));
        optionNameScrollPane.setViewportView(optionNameTable);
      }
    }
    {
      JPanel listActionButtonPanel = new JPanel();
      GridBagConstraints gbc_listActionButtonPanel = new GridBagConstraints();
      gbc_listActionButtonPanel.fill = GridBagConstraints.BOTH;
      gbc_listActionButtonPanel.gridx = 0;
      gbc_listActionButtonPanel.gridy = 1;
      contentPanel.add(listActionButtonPanel, gbc_listActionButtonPanel);
      GridBagLayout gbl_listActionButtonPanel = new GridBagLayout();
      gbl_listActionButtonPanel.columnWidths = new int[] { 0, 0, 0, 0, 0, 0, 0 };
      gbl_listActionButtonPanel.rowHeights = new int[] { 25, 0 };
      gbl_listActionButtonPanel.columnWeights = new double[] { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
          Double.MIN_VALUE };
      gbl_listActionButtonPanel.rowWeights = new double[] { 0.0, Double.MIN_VALUE };
      listActionButtonPanel.setLayout(gbl_listActionButtonPanel);
      {
        JButton checkOptionsButton = new JButton(Localizer.localize("CheckOptionsButtonText"));
        checkOptionsButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            checkOptionsActionPerformed(e);
          }
        });
        checkOptionsButton.setFont(UIManager.getFont("Button.font"));
        checkOptionsButton.setMargin(new Insets(1, 5, 1, 5));
        checkOptionsButton.setIcon(new ImageIcon(CustomSettingsDialog.class
            .getResource("/com/famfamfam/silk/brick.png")));
        GridBagConstraints gbc_checkOptionsButton = new GridBagConstraints();
        gbc_checkOptionsButton.insets = new Insets(0, 0, 0, 5);
        gbc_checkOptionsButton.gridx = 2;
        gbc_checkOptionsButton.gridy = 0;
        listActionButtonPanel.add(checkOptionsButton, gbc_checkOptionsButton);
      }
      {
        JButton exportButton = new JButton(Localizer.localize("ExportSettingsButtonText"));
        GridBagConstraints gbc_exportButton = new GridBagConstraints();
        gbc_exportButton.insets = new Insets(0, 0, 0, 5);
        gbc_exportButton.gridx = 3;
        gbc_exportButton.gridy = 0;
        listActionButtonPanel.add(exportButton, gbc_exportButton);
        exportButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            exportActionPerformed(e);
          }
        });
        exportButton.setIcon(new ImageIcon(CustomSettingsDialog.class
            .getResource("/com/famfamfam/silk/table_save.png")));
        exportButton.setFont(UIManager.getFont("Button.font"));
        exportButton.setMargin(new Insets(1, 5, 1, 5));
      }
      {
        JButton importSettingsButton = new JButton(Localizer.localize("ImportSettingsButtonText"));
        GridBagConstraints gbc_importSettingsButton = new GridBagConstraints();
        gbc_importSettingsButton.insets = new Insets(0, 0, 0, 5);
        gbc_importSettingsButton.gridx = 4;
        gbc_importSettingsButton.gridy = 0;
        listActionButtonPanel.add(importSettingsButton, gbc_importSettingsButton);
        importSettingsButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            importActionPerformed(e);
          }
        });
        importSettingsButton.setIcon(new ImageIcon(CustomSettingsDialog.class
            .getResource("/com/famfamfam/silk/table_go.png")));
        importSettingsButton.setFont(UIManager.getFont("Button.font"));
        importSettingsButton.setMargin(new Insets(1, 5, 1, 5));
      }
    }
    {
      JPanel buttonPane = new JPanel();
      buttonPane.setBorder(new EmptyBorder(0, 12, 12, 12));
      getContentPane().add(buttonPane, BorderLayout.SOUTH);
      GridBagLayout gbl_buttonPane = new GridBagLayout();
      gbl_buttonPane.columnWidths = new int[] { 355, 54, 81, 0 };
      gbl_buttonPane.rowHeights = new int[] { 25, 0 };
      gbl_buttonPane.columnWeights = new double[] { 1.0, 0.0, 0.0, Double.MIN_VALUE };
      gbl_buttonPane.rowWeights = new double[] { 0.0, Double.MIN_VALUE };
      buttonPane.setLayout(gbl_buttonPane);
      {
        JButton okButton = new JButton(Localizer.localize("OK"));
        okButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            okActionPerformed(e);
          }
        });
        okButton.setIcon(new ImageIcon(CustomSettingsDialog.class
            .getResource("/com/famfamfam/silk/tick.png")));
        okButton.setFont(UIManager.getFont("Button.font"));
        okButton.setMargin(new Insets(1, 5, 1, 5));
        GridBagConstraints gbc_okButton = new GridBagConstraints();
        gbc_okButton.anchor = GridBagConstraints.NORTHWEST;
        gbc_okButton.insets = new Insets(0, 0, 0, 5);
        gbc_okButton.gridx = 1;
        gbc_okButton.gridy = 0;
        buttonPane.add(okButton, gbc_okButton);
        getRootPane().setDefaultButton(okButton);
      }
      {
        JButton cancelButton = new JButton(Localizer.localize("Cancel"));
        cancelButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            cancelActionPerformed(e);
          }
        });
        cancelButton.setIcon(new ImageIcon(CustomSettingsDialog.class
            .getResource("/com/famfamfam/silk/cross.png")));
        cancelButton.setFont(UIManager.getFont("Button.font"));
        cancelButton.setMargin(new Insets(1, 5, 1, 5));
        GridBagConstraints gbc_cancelButton = new GridBagConstraints();
        gbc_cancelButton.anchor = GridBagConstraints.NORTHWEST;
        gbc_cancelButton.gridx = 2;
        gbc_cancelButton.gridy = 0;
        buttonPane.add(cancelButton, gbc_cancelButton);
      }
    }
  }

  public final void initialize() {
    updateTableModel();
  }

  private void okActionPerformed(ActionEvent e) {
    dialogResult = JOptionPane.OK_OPTION;
    dispose();
  }

  public final void setScanner(Scanner scanner) {
    this.scanner = PreferencesUtils.copy(scanner);
  }

  public final void setScanService(IScanService scanService) {
    this.scanService = scanService;
  }

  public final void setXstream(XStream xstream) {
    this.xstream = xstream;
  }

  private void showCheckOptionsErrorMessage(Exception e) {
    Misc.showErrorMsg(getRootPane().getTopLevelAncestor(), String.format(
        Localizer.localize("CheckOptionsFailureMessageText"), e.getLocalizedMessage()));
  }

  private void showLoadErrorMessage(Exception e) {
    Misc.showErrorMsg(getRootPane().getTopLevelAncestor(),
        String.format(Localizer.localize("LoadFailureMessageText"), e.getLocalizedMessage()));
  }

  private void showLoadSuccessMessage() {
    Misc.showSuccessMsg(getRootPane().getTopLevelAncestor(),
        Localizer.localize("LoadSuccessMessageText"));
  }

  private void showSaveErrorMessage(Exception e) {
    Misc.showErrorMsg(getRootPane().getTopLevelAncestor(),
        String.format(Localizer.localize("FailureSavingMessage"), e.getLocalizedMessage()));
  }

  private void showSaveSuccessMessage() {
    Misc.showSuccessMsg(getRootPane().getTopLevelAncestor(),
        Localizer.localize("SaveSuccessMessage"));
  }

  private void updateOption(OptionsOrderValuePair vp, Component component) {

    switch (vp.getSaneOptionType()) {

    case STRING:
      StringOption stringOption = scanner.getStringOptions().get(vp.getKey());
      if (component instanceof JTextField) {
        stringOption.setValue(((JTextField) component).getText());
      } else if (component instanceof JComboBox) {
        stringOption.setValue((String) ((JComboBox<?>) component).getSelectedItem());
      }
      break;

    case INTEGER:
      IntegerOption integerOptions = scanner.getIntegerOptions().get(vp.getKey());
      integerOptions.setValue((Integer) ((JSpinner) component).getValue());
      break;

    case BOOLEAN:
      BooleanOption booleanOptions = scanner.getBooleanOptions().get(vp.getKey());
      booleanOptions.setValue(((JCheckBox) component).isSelected());
      break;

    case FIXED:
      FixedOption fixedOptions = scanner.getFixedOptions().get(vp.getKey());
      fixedOptions.setValue((Double) ((JSpinner) component).getValue());
      break;

    case BUTTON:
      break;

    case GROUP:
      break;

    default:
      break;

    }

  }

  private void updateTableModel() {
    optionOrdering = scanner.getOptionOrdering();
    int rowCount = optionNameTableModel.getRowCount();
    if (rowCount > 0) {
      for (int i = rowCount - 1; i >= 0; i--) {
        optionNameTableModel.removeRow(i);
      }
    }
    for (OptionsOrderValuePair vp : optionOrdering) {
      optionNameTableModel.addRow(new Object[] {
          vp.getKey(),
          vp.isActive() ? Localizer.localize("OptionActiveStatusText") : Localizer
              .localize("OptionInactiveStatusText"), getOptionComponent(vp) });
    }
    optionNameTable.setDefaultRenderer(JCheckBox.class, new CustomTableCellRenderer());
    optionNameTable.setDefaultRenderer(JTextField.class, new CustomTableCellRenderer());
    optionNameTable.setDefaultRenderer(JSpinner.class, new CustomTableCellRenderer());
    optionNameTable.setDefaultRenderer(JComboBox.class, new CustomTableCellRenderer());
    optionNameTable.setDefaultRenderer(JButton.class, new CustomTableCellRenderer());
    optionNameTable.setModel(optionNameTableModel);
    optionNameTable.addMouseListener(new JTableButtonMouseListener(optionNameTable));
    optionNameTable.setCellSelectionEnabled(false);
    optionNameTable.setRowHeight(ROW_HEIGHT);
    optionNameTable.getTableHeader().setReorderingAllowed(false);
    optionNameTable.revalidate();
  }

} @SuppressWarnings("serial")
public class GlobalSettingsDialog extends JDialog {

  private static final int BOUNDS_WIDTH = 550;
  private static final int BOUNDS_HEIGHT = 450;

  private final Dimension bounds = new Dimension(BOUNDS_WIDTH, BOUNDS_HEIGHT);

  private final JPanel contentPanel = new JPanel();
  private JTextField mDNSNameTextField;

  private String saneServiceName;

  private HashMap<String, Login> logins;

  private DefaultListModel<String> loginListModel = new DefaultListModel<String>();
  private JList<String> loginList;

  private int dialogResult = JOptionPane.CANCEL_OPTION;

  public GlobalSettingsDialog(Component parent) {
    initComponents();
    pack();
    setLocationRelativeTo(parent);
  }

  private void addLoginActionPerformed(ActionEvent e) {
    LoginDialog loginDialog = new LoginDialog(this);
    Login login = new Login();
    loginDialog.setUsername(login.getUsername());
    loginDialog.setPassword(login.getPassword());
    loginDialog.setModal(true);
    loginDialog.setVisible(true);
    if (loginDialog.getDialogResult() == JOptionPane.OK_OPTION) {
      login.setUsername(loginDialog.getUsername().trim());
      login.setPassword(loginDialog.getPassword().trim());
      String resource = loginDialog.getResource().trim();
      if (!(logins.containsKey(resource))) {
        logins.put(resource, login);
        loginListModel.addElement(resource);
      }
      loginList.revalidate();
    }
  }

  private void cancelActionPerformed(ActionEvent e) {
    dispose();
  }

  private void editLoginActionPerformed(ActionEvent e) {
    String resource = loginList.getSelectedValue();
    if (resource == null) {
      return;
    }
    LoginDialog loginDialog = new LoginDialog(this);

    Login login = logins.get(resource);
    loginDialog.setResource(resource);
    loginDialog.setUsername(login.getUsername());
    loginDialog.setPassword(login.getPassword());

    loginDialog.setModal(true);
    loginDialog.setVisible(true);
    if (loginDialog.getDialogResult() == JOptionPane.OK_OPTION) {
      login.setUsername(loginDialog.getUsername().trim());
      login.setPassword(loginDialog.getPassword().trim());
      loginList.revalidate();
    }
  }

  public final int getDialogResult() {
    return dialogResult;
  }

  public final HashMap<String, Login> getLogins() {
    return logins;
  }

  public final HashMap<String, Login> getSaneLogins() {
    return logins;
  }

  public final String getSaneServiceName() {
    return saneServiceName;
  }

  public final String getServiceName() {
    return mDNSNameTextField.getText();
  }

  private void initComponents() {
    setTitle(Localizer.localize("GlobalSettingsDialogTitle"));
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    setResizable(false);
    setBounds(0, 0, bounds.width, bounds.height);
    setMinimumSize(bounds);
    setPreferredSize(bounds);
    setSize(bounds);
    getContentPane().setLayout(new BorderLayout());
    contentPanel.setBorder(new EmptyBorder(12, 12, 12, 12));
    getContentPane().add(contentPanel, BorderLayout.CENTER);
    GridBagLayout gbl_contentPanel = new GridBagLayout();
    gbl_contentPanel.columnWidths = new int[] { 222, 0 };
    gbl_contentPanel.rowHeights = new int[] { 0, 0, 0 };
    gbl_contentPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
    gbl_contentPanel.rowWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE };
    contentPanel.setLayout(gbl_contentPanel);
    {
      JPanel loginPanel = new JPanel();
      loginPanel.setBorder(new CompoundBorder(new TitledBorder(Localizer
          .localize("ScannerBackendLoginsTitle")), new EmptyBorder(5, 5, 5, 5)));
      GridBagConstraints gbc_loginPanel = new GridBagConstraints();
      gbc_loginPanel.insets = new Insets(0, 0, 5, 0);
      gbc_loginPanel.fill = GridBagConstraints.BOTH;
      gbc_loginPanel.gridx = 0;
      gbc_loginPanel.gridy = 0;
      contentPanel.add(loginPanel, gbc_loginPanel);
      GridBagLayout gbl_loginPanel = new GridBagLayout();
      gbl_loginPanel.columnWidths = new int[] { 333, 0 };
      gbl_loginPanel.rowHeights = new int[] { 3, 0, 0 };
      gbl_loginPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
      gbl_loginPanel.rowWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE };
      loginPanel.setLayout(gbl_loginPanel);
      {
        JScrollPane loginScrollPane = new JScrollPane();
        GridBagConstraints gbc_loginScrollPane = new GridBagConstraints();
        gbc_loginScrollPane.insets = new Insets(0, 0, 5, 0);
        gbc_loginScrollPane.fill = GridBagConstraints.BOTH;
        gbc_loginScrollPane.gridx = 0;
        gbc_loginScrollPane.gridy = 0;
        loginPanel.add(loginScrollPane, gbc_loginScrollPane);
        {
          loginList = new JList<String>();
          loginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
          loginList.setFont(UIManager.getFont("List.font"));
          loginScrollPane.setViewportView(loginList);
        }
      }
      {
        JPanel loginActionsPanel = new JPanel();
        GridBagConstraints gbc_loginActionsPanel = new GridBagConstraints();
        gbc_loginActionsPanel.fill = GridBagConstraints.BOTH;
        gbc_loginActionsPanel.gridx = 0;
        gbc_loginActionsPanel.gridy = 1;
        loginPanel.add(loginActionsPanel, gbc_loginActionsPanel);
        GridBagLayout gbl_loginActionsPanel = new GridBagLayout();
        gbl_loginActionsPanel.columnWidths = new int[] { 0, 0, 0, 0, 0 };
        gbl_loginActionsPanel.rowHeights = new int[] { 0, 0 };
        gbl_loginActionsPanel.columnWeights = new double[] { 1.0, 0.0, 0.0, 0.0, Double.MIN_VALUE };
        gbl_loginActionsPanel.rowWeights = new double[] { 0.0, Double.MIN_VALUE };
        loginActionsPanel.setLayout(gbl_loginActionsPanel);
        {
          JButton addLoginButton = new JButton(Localizer.localize("Add"));
          addLoginButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
              addLoginActionPerformed(e);
            }
          });
          addLoginButton.setIcon(new ImageIcon(GlobalSettingsDialog.class
              .getResource("/com/famfamfam/silk/add.png")));
          addLoginButton.setFont(UIManager.getFont("Button.font"));
          addLoginButton.setMargin(new Insets(1, 5, 1, 5));
          GridBagConstraints gbc_addLoginButton = new GridBagConstraints();
          gbc_addLoginButton.insets = new Insets(0, 0, 0, 5);
          gbc_addLoginButton.gridx = 1;
          gbc_addLoginButton.gridy = 0;
          loginActionsPanel.add(addLoginButton, gbc_addLoginButton);
        }
        {
          JButton removeLoginButton = new JButton(Localizer.localize("Remove"));
          removeLoginButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
              removeLoginActionPerformed(e);
            }
          });
          removeLoginButton.setIcon(new ImageIcon(GlobalSettingsDialog.class
              .getResource("/com/famfamfam/silk/delete.png")));
          removeLoginButton.setFont(UIManager.getFont("Button.font"));
          removeLoginButton.setMargin(new Insets(1, 5, 1, 5));
          GridBagConstraints gbc_removeLoginButton = new GridBagConstraints();
          gbc_removeLoginButton.insets = new Insets(0, 0, 0, 5);
          gbc_removeLoginButton.gridx = 2;
          gbc_removeLoginButton.gridy = 0;
          loginActionsPanel.add(removeLoginButton, gbc_removeLoginButton);
        }
        {
          JButton editLoginButton = new JButton(Localizer.localize("Edit"));
          editLoginButton.setIcon(new ImageIcon(GlobalSettingsDialog.class
              .getResource("/com/famfamfam/silk/pencil.png")));
          editLoginButton.setFont(UIManager.getFont("Button.font"));
          editLoginButton.setMargin(new Insets(1, 5, 1, 5));
          editLoginButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
              editLoginActionPerformed(e);
            }
          });
          GridBagConstraints gbc_editLoginButton = new GridBagConstraints();
          gbc_editLoginButton.gridx = 3;
          gbc_editLoginButton.gridy = 0;
          loginActionsPanel.add(editLoginButton, gbc_editLoginButton);
        }
      }
    }
    {
      JPanel mDNSPanel = new JPanel();
      mDNSPanel.setBorder(new CompoundBorder(new TitledBorder(Localizer
          .localize("MulticastDNSServiceNameTitle")), new EmptyBorder(5, 5, 5, 5)));
      GridBagConstraints gbc_mDNSPanel = new GridBagConstraints();
      gbc_mDNSPanel.fill = GridBagConstraints.BOTH;
      gbc_mDNSPanel.gridx = 0;
      gbc_mDNSPanel.gridy = 1;
      contentPanel.add(mDNSPanel, gbc_mDNSPanel);
      GridBagLayout gbl_mDNSPanel = new GridBagLayout();
      gbl_mDNSPanel.columnWidths = new int[] { 0, 0 };
      gbl_mDNSPanel.rowHeights = new int[] { 0, 0, 0 };
      gbl_mDNSPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
      gbl_mDNSPanel.rowWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE };
      mDNSPanel.setLayout(gbl_mDNSPanel);
      {
        JLabel mDNSDescriptionLabel = new JLabel(
            Localizer.localize("MulticastDNSServiceNameDescription"));
        mDNSDescriptionLabel.setFont(UIManager.getFont("Label.font"));
        GridBagConstraints gbc_mDNSDescriptionLabel = new GridBagConstraints();
        gbc_mDNSDescriptionLabel.fill = GridBagConstraints.HORIZONTAL;
        gbc_mDNSDescriptionLabel.insets = new Insets(0, 0, 5, 0);
        gbc_mDNSDescriptionLabel.gridx = 0;
        gbc_mDNSDescriptionLabel.gridy = 0;
        mDNSPanel.add(mDNSDescriptionLabel, gbc_mDNSDescriptionLabel);
      }
      {
        JPanel mDNSServiceNamePanel = new JPanel();
        GridBagConstraints gbc_mDNSServiceNamePanel = new GridBagConstraints();
        gbc_mDNSServiceNamePanel.fill = GridBagConstraints.BOTH;
        gbc_mDNSServiceNamePanel.gridx = 0;
        gbc_mDNSServiceNamePanel.gridy = 1;
        mDNSPanel.add(mDNSServiceNamePanel, gbc_mDNSServiceNamePanel);
        GridBagLayout gbl_mDNSServiceNamePanel = new GridBagLayout();
        gbl_mDNSServiceNamePanel.columnWidths = new int[] { 266, 0, 0 };
        gbl_mDNSServiceNamePanel.rowHeights = new int[] { 25, 0 };
        gbl_mDNSServiceNamePanel.columnWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE };
        gbl_mDNSServiceNamePanel.rowWeights = new double[] { 0.0, Double.MIN_VALUE };
        mDNSServiceNamePanel.setLayout(gbl_mDNSServiceNamePanel);
        {
          mDNSNameTextField = new JTextField();
          GridBagConstraints gbc_mDNSNameTextField = new GridBagConstraints();
          gbc_mDNSNameTextField.fill = GridBagConstraints.BOTH;
          gbc_mDNSNameTextField.insets = new Insets(0, 0, 0, 5);
          gbc_mDNSNameTextField.gridx = 0;
          gbc_mDNSNameTextField.gridy = 0;
          mDNSServiceNamePanel.add(mDNSNameTextField, gbc_mDNSNameTextField);
          mDNSNameTextField.setColumns(10);
        }
        {
          JButton mDNSRestoreButton = new JButton(Localizer.localize("RestoreDefaultButtonText"));
          mDNSRestoreButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
              mDNSRestoreActionPerformed(e);
            }
          });
          mDNSRestoreButton.setIcon(new ImageIcon(GlobalSettingsDialog.class
              .getResource("/com/famfamfam/silk/arrow_rotate_anticlockwise.png")));
          mDNSRestoreButton.setFont(UIManager.getFont("Button.font"));
          mDNSRestoreButton.setMargin(new Insets(1, 5, 1, 5));
          GridBagConstraints gbc_mDNSRestoreButton = new GridBagConstraints();
          gbc_mDNSRestoreButton.gridx = 1;
          gbc_mDNSRestoreButton.gridy = 0;
          mDNSServiceNamePanel.add(mDNSRestoreButton, gbc_mDNSRestoreButton);
        }
      }
    }
    {
      JPanel buttonPane = new JPanel();
      buttonPane.setBorder(new EmptyBorder(0, 12, 12, 12));
      buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
      getContentPane().add(buttonPane, BorderLayout.SOUTH);
      {
        JButton cancelButton = new JButton(Localizer.localize("Cancel"));
        cancelButton.setIcon(new ImageIcon(GlobalSettingsDialog.class
            .getResource("/com/famfamfam/silk/cross.png")));
        cancelButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            cancelActionPerformed(e);
          }
        });
        {
          JButton saveButton = new JButton("Save");
          buttonPane.add(saveButton);
          saveButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
              saveActionPerformed(e);
            }
          });
          saveButton.setIcon(new ImageIcon(GlobalSettingsDialog.class
              .getResource("/com/famfamfam/silk/disk.png")));
          saveButton.setFont(UIManager.getFont("Button.font"));
          saveButton.setMargin(new Insets(1, 5, 1, 5));
        }
        cancelButton.setFont(UIManager.getFont("Button.font"));
        cancelButton.setMargin(new Insets(1, 5, 1, 5));
        buttonPane.add(cancelButton);
      }
    }
  }

  public final void initialize() {
    popuplateLoginList();
    restoreServiceName();
  }

  private void mDNSRestoreActionPerformed(ActionEvent e) {
    mDNSNameTextField.setText(DiscoveryJob.SANE_SERVICE_NAME);
  }

  private void popuplateLoginList() {
    Set<String> resources = logins.keySet();
    for (String resource : resources) {
      loginListModel.addElement(resource);
    }
    loginList.setModel(loginListModel);
  }

  private void removeLoginActionPerformed(ActionEvent e) {
    String resource = loginList.getSelectedValue();
    if (resource == null) {
      return;
    }
    loginListModel.removeElement(resource);
    logins.remove(resource);
    loginList.revalidate();
  }

  private void restoreServiceName() {
    mDNSNameTextField.setText(saneServiceName);
  }

  private void saveActionPerformed(ActionEvent e) {
    dialogResult = JOptionPane.OK_OPTION;
    dispose();
  }

  public final void setLogins(HashMap<String, Login> logins) {
    this.logins = logins;
  }

  public final void setSaneServiceName(String saneServiceName) {
    this.saneServiceName = saneServiceName;
  }
} @SuppressWarnings("serial")
public class ScannerSettingsDialog extends JDialog {

  /**
   * Log4J logger.
   */
  private static final Logger LOG = Logger.getLogger(ScannerSettingsDialog.class);

  private static final int BOUNDS_WIDTH = 400;
  private static final int BOUNDS_HEIGHT = 260;

  private final Dimension bounds = new Dimension(BOUNDS_WIDTH, BOUNDS_HEIGHT);

  private JTextField remoteAddressTextField;
  private JSpinner portNumberSpinner;

  private int dialogResult = JOptionPane.CANCEL_OPTION;
  private JTextField descriptionTextField;

  public ScannerSettingsDialog(Component parent) {
    initComponents();
    pack();
    setLocationRelativeTo(parent);
  }

  private void cancelButtonActionPerformed(ActionEvent e) {
    dispose();
  }

  public final String getDescription() {
    return descriptionTextField.getText().trim();
  }

  public final int getDialogResult() {
    return dialogResult;
  }

  public final int getPortNumber() {
    return (Integer) portNumberSpinner.getValue();
  }

  public final String getRemoteAddress() {
    return remoteAddressTextField.getText().trim();
  }

  private void initComponents() {
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    setResizable(false);
    setBounds(0, 0, bounds.width, bounds.height);
    setPreferredSize(bounds);
    setSize(bounds);
    setMinimumSize(bounds);

    JPanel contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(12, 12, 12, 12));
    getContentPane().add(contentPane, BorderLayout.CENTER);
    GridBagLayout gbl_contentPane = new GridBagLayout();
    gbl_contentPane.columnWidths = new int[] { 0, 0 };
    gbl_contentPane.rowHeights = new int[] { 0, 0, 0 };
    gbl_contentPane.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
    gbl_contentPane.rowWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE };
    contentPane.setLayout(gbl_contentPane);

    JPanel containerPanel = new JPanel();
    GridBagConstraints gbc_containerPanel = new GridBagConstraints();
    gbc_containerPanel.insets = new Insets(0, 0, 5, 0);
    gbc_containerPanel.fill = GridBagConstraints.BOTH;
    gbc_containerPanel.gridx = 0;
    gbc_containerPanel.gridy = 0;
    contentPane.add(containerPanel, gbc_containerPanel);
    GridBagLayout gbl_containerPanel = new GridBagLayout();
    gbl_containerPanel.columnWidths = new int[] { 116, 0, 0 };
    gbl_containerPanel.rowHeights = new int[] { 0, 15, 0, 0 };
    gbl_containerPanel.columnWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE };
    gbl_containerPanel.rowWeights = new double[] { 1.0, 1.0, 1.0, Double.MIN_VALUE };
    containerPanel.setLayout(gbl_containerPanel);

    JLabel descriptionLabel = new JLabel(Localizer.localize("DescriptionLabelText"));
    descriptionLabel.setFont(UIManager.getFont("Label.font"));
    GridBagConstraints gbc_descriptionLabel = new GridBagConstraints();
    gbc_descriptionLabel.anchor = GridBagConstraints.EAST;
    gbc_descriptionLabel.insets = new Insets(0, 0, 5, 5);
    gbc_descriptionLabel.gridx = 0;
    gbc_descriptionLabel.gridy = 0;
    containerPanel.add(descriptionLabel, gbc_descriptionLabel);

    JPanel descriptionTextAreaPanel = new JPanel();
    GridBagConstraints gbc_descriptionTextAreaPanel = new GridBagConstraints();
    gbc_descriptionTextAreaPanel.insets = new Insets(0, 0, 5, 0);
    gbc_descriptionTextAreaPanel.fill = GridBagConstraints.BOTH;
    gbc_descriptionTextAreaPanel.gridx = 1;
    gbc_descriptionTextAreaPanel.gridy = 0;
    containerPanel.add(descriptionTextAreaPanel, gbc_descriptionTextAreaPanel);
    GridBagLayout gbl_descriptionTextAreaPanel = new GridBagLayout();
    gbl_descriptionTextAreaPanel.columnWidths = new int[] { 114, 0 };
    gbl_descriptionTextAreaPanel.rowHeights = new int[] { 0, 19, 0 };
    gbl_descriptionTextAreaPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
    gbl_descriptionTextAreaPanel.rowWeights = new double[] { 1.0, 1.0, Double.MIN_VALUE };
    descriptionTextAreaPanel.setLayout(gbl_descriptionTextAreaPanel);

    JLabel descriptionTipLabel = new JLabel(Localizer.localize("DescriptionTipLabelText"));
    descriptionTipLabel.setIcon(new ImageIcon(ScannerSettingsDialog.class
        .getResource("/com/famfamfam/silk/lightbulb.png")));
    descriptionTipLabel.setFont(new Font("Dialog", Font.BOLD, 9));
    GridBagConstraints gbc_descriptionTipLabel = new GridBagConstraints();
    gbc_descriptionTipLabel.anchor = GridBagConstraints.SOUTH;
    gbc_descriptionTipLabel.insets = new Insets(0, 0, 5, 0);
    gbc_descriptionTipLabel.gridx = 0;
    gbc_descriptionTipLabel.gridy = 0;
    descriptionTextAreaPanel.add(descriptionTipLabel, gbc_descriptionTipLabel);

    descriptionTextField = new JTextField();
    GridBagConstraints gbc_descriptionTextField = new GridBagConstraints();
    gbc_descriptionTextField.anchor = GridBagConstraints.NORTH;
    gbc_descriptionTextField.fill = GridBagConstraints.HORIZONTAL;
    gbc_descriptionTextField.gridx = 0;
    gbc_descriptionTextField.gridy = 1;
    descriptionTextAreaPanel.add(descriptionTextField, gbc_descriptionTextField);
    descriptionTextField.setHorizontalAlignment(SwingConstants.RIGHT);
    descriptionTextField.setFont(UIManager.getFont("TextField.font"));
    descriptionTextField.setColumns(10);

    JLabel remoteAddressLabel = new JLabel(Localizer.localize("RemoteAddressLabelText"));
    GridBagConstraints gbc_remoteAddressLabel = new GridBagConstraints();
    gbc_remoteAddressLabel.insets = new Insets(0, 0, 5, 5);
    gbc_remoteAddressLabel.anchor = GridBagConstraints.EAST;
    gbc_remoteAddressLabel.gridx = 0;
    gbc_remoteAddressLabel.gridy = 1;
    containerPanel.add(remoteAddressLabel, gbc_remoteAddressLabel);
    remoteAddressLabel.setFont(UIManager.getFont("Label.font"));

    remoteAddressTextField = new JTextField();
    GridBagConstraints gbc_remoteAddressTextField = new GridBagConstraints();
    gbc_remoteAddressTextField.insets = new Insets(0, 0, 5, 0);
    gbc_remoteAddressTextField.fill = GridBagConstraints.HORIZONTAL;
    gbc_remoteAddressTextField.gridx = 1;
    gbc_remoteAddressTextField.gridy = 1;
    containerPanel.add(remoteAddressTextField, gbc_remoteAddressTextField);
    remoteAddressTextField.setFont(UIManager.getFont("TextField.font"));
    remoteAddressTextField.setHorizontalAlignment(SwingConstants.TRAILING);
    try {
      remoteAddressTextField.setText(InetAddress.getLocalHost().getHostAddress());
    } catch (UnknownHostException ex) {
      LOG.warn(ex, ex);
    }
    remoteAddressTextField.setColumns(10);

    JLabel portNumberLabel = new JLabel(Localizer.localize("PortNumberLabelText"));
    GridBagConstraints gbc_portNumberLabel = new GridBagConstraints();
    gbc_portNumberLabel.anchor = GridBagConstraints.EAST;
    gbc_portNumberLabel.insets = new Insets(0, 0, 0, 5);
    gbc_portNumberLabel.gridx = 0;
    gbc_portNumberLabel.gridy = 2;
    containerPanel.add(portNumberLabel, gbc_portNumberLabel);
    portNumberLabel.setFont(UIManager.getFont("Label.font"));

    portNumberSpinner = new JSpinner();
    GridBagConstraints gbc_portNumberSpinner = new GridBagConstraints();
    gbc_portNumberSpinner.fill = GridBagConstraints.HORIZONTAL;
    gbc_portNumberSpinner.gridx = 1;
    gbc_portNumberSpinner.gridy = 2;
    containerPanel.add(portNumberSpinner, gbc_portNumberSpinner);
    portNumberSpinner.setFont(UIManager.getFont("Spinner.font"));
    portNumberSpinner.setModel(new SpinnerNumberModel(6566, 1, 65535, 1));

    JPanel buttonPanel = new JPanel();
    GridBagConstraints gbc_buttonPanel = new GridBagConstraints();
    gbc_buttonPanel.fill = GridBagConstraints.BOTH;
    gbc_buttonPanel.gridx = 0;
    gbc_buttonPanel.gridy = 1;
    contentPane.add(buttonPanel, gbc_buttonPanel);
    GridBagLayout gbl_buttonPanel = new GridBagLayout();
    gbl_buttonPanel.columnWidths = new int[] { 0, 0, 0, 0 };
    gbl_buttonPanel.rowHeights = new int[] { 0, 0 };
    gbl_buttonPanel.columnWeights = new double[] { 1.0, 0.0, 0.0, Double.MIN_VALUE };
    gbl_buttonPanel.rowWeights = new double[] { 0.0, Double.MIN_VALUE };
    buttonPanel.setLayout(gbl_buttonPanel);

    JButton okButton = new JButton(Localizer.localize("OK"));
    okButton.setIcon(new ImageIcon(ScannerSettingsDialog.class
        .getResource("/com/famfamfam/silk/tick.png")));
    okButton.setFont(UIManager.getFont("Button.font"));
    okButton.setMargin(new Insets(1, 5, 1, 5));
    okButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        okButtonActionPerformed(e);
      }
    });
    GridBagConstraints gbc_okButton = new GridBagConstraints();
    gbc_okButton.insets = new Insets(0, 0, 0, 5);
    gbc_okButton.gridx = 1;
    gbc_okButton.gridy = 0;
    buttonPanel.add(okButton, gbc_okButton);

    JButton cancelButton = new JButton(Localizer.localize("Cancel"));
    cancelButton.setIcon(new ImageIcon(ScannerSettingsDialog.class
        .getResource("/com/famfamfam/silk/cross.png")));
    cancelButton.setFont(UIManager.getFont("Button.font"));
    cancelButton.setMargin(new Insets(1, 5, 1, 5));
    cancelButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        cancelButtonActionPerformed(e);
      }
    });
    GridBagConstraints gbc_cancelButton = new GridBagConstraints();
    gbc_cancelButton.gridx = 2;
    gbc_cancelButton.gridy = 0;
    buttonPanel.add(cancelButton, gbc_cancelButton);
  }

  private void okButtonActionPerformed(ActionEvent e) {
    dialogResult = JOptionPane.OK_OPTION;
    dispose();
  }

  public final void setDescription(String description) {
    descriptionTextField.setText(description);
  }

  public final void setRemoteAddress(String remoteAddress) {
    remoteAddressTextField.setText(remoteAddress);
  }

  public final void setRemotePortNumber(Integer portNumber) {
    portNumberSpinner.setValue(portNumber);
  }
} @SuppressWarnings("serial")
public class ScannerListItem extends JComponent {

  public enum ScannerStatus {
    IDLE, BUSY
  }

  private ScanJob scanJob;

  private ScanEventListener scanEventListener;

  private Scanner scanner;

  private ScannerStatus status = ScannerStatus.IDLE;

  public ScannerListItem(Scanner scanner) {
    this.scanner = scanner;
  }

  public final void cancel() {
    if ((scanJob != null) && scanJob.isActive()) {
      scanJob.cancel();
    }
  }

  public final Scanner getScanner() {
    return scanner;
  }

  private String getScannerAddress() {
    return scanner.getRemoteAddress() + ":" + scanner.getRemotePortNumber();
  }

  private String getScannerModel() {
    // de-uglify SANE descriptions by replacing underscores with spaces.
    return (scanner.getVendor() + " " + scanner.getModel()).replace("_", " ");
  }

  public final ScannerStatus getScannerStatus() {
    return status;
  }

  private String getStatus() {
    switch (status) {
    case IDLE:
      return Localizer.localize("IdleScannerStatusText");
    case BUSY:
      return Localizer.localize("BusyScannerStatusText");
    default:
      break;
    }
    return null;
  }

  public final boolean isActive() {
    if (scanJob == null) {
      return false;
    }
    return scanJob.isActive();
  }

  public final void removeListeners() {
    scanJob.removeScanEventListener(scanEventListener);
  }

  public final void setScanEventListener(ScanEventListener scanEventListener) {
    this.scanEventListener = scanEventListener;
  }

  public final void setScanJob(ScanJob scanJob) {
    this.scanJob = scanJob;
  }

  public final void setScannerStatus(ScannerStatus scannerStatus) {
    status = scannerStatus;
  }

  @Override
  public final String toString() {
    if ((scanner.getDescription() != null) && (scanner.getDescription().trim().length() > 0)) {
      return scanner.getDescription() + " (" + getStatus() + ")";
    } else {
      return getScannerModel() + " - " + scanner.getName() + " - " + getScannerAddress() + " ("
          + getStatus() + ")";
    }
  }

} public enum ScannerStatus {
  IDLE, BUSY
} public final class Localizer {

  /**
   * Returns the current locale.
   *
   * @return the current locale
   */
  public static Locale getCurrentLocale() {
    if (currentLocale == null) {
      setCurrentLocale(Locale.getDefault());
    }
    return currentLocale;
  }

  /**
   * Returns a translation for the specified key in the current locale.
   *
   * @param key
   *          a translation key listed in the messages resource file
   * @return a translation for the specified key in the current locale
   */
  public static String localize(String key) {
    try {
      return BUNDLE.getString(key);
    } catch (Exception ex) {
      LOG.warn(ex.getLocalizedMessage(), ex);
      LOG.warn("Missing translation: " + key);
      return key;
    }
  }

  /**
   * Sets the current locale and attempts to load the messages resource file for the locale.
   *
   * @param currentLocale
   *          a locale that has a messages resource
   */
  public static void setCurrentLocale(Locale currentLocale) {
    Localizer.currentLocale = currentLocale;
    try {
      ResourceBundle.getBundle(MESSAGES_RESOURCE, currentLocale);
    } catch (Exception ex) {
      ResourceBundle.getBundle(MESSAGES_RESOURCE, DEFAULT_LOCALE);
      currentLocale = DEFAULT_LOCALE;
    }
  }

  private static final String MESSAGES_RESOURCE = "com.swingsane.i18n.messages";

  private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;

  private static Locale currentLocale = DEFAULT_LOCALE;

  private static final Logger LOG = Logger.getLogger(Localizer.class);

  private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE,
      getCurrentLocale());

  private Localizer() {
  }

} public interface IPreferredDefaults {

  public enum ColorMode {
    BLACK_AND_WHITE, COLOR, GRAYSCALE
  }

  public enum Source {
    AUTOMATIC_DOCUMENT_FEEDER, FLATBED
  }

  int DEFAULT_RESOLUTION = 300;

  Rotation DEFAULT_ROTATION = Rotation.CW_90;

  int DEFAULT_LUMINANCE_THRESHOLD = 165;

  double DEFAULT_DESKEW_THRESHOLD = 2.0d;

  ColorMode getColor();

  double getDefaultDeskewThreshold();

  int getDefaultLuminanceThreshold();

  Rotation getDefaultRotation();

  int getResolution();

  void setColor(ColorMode color);

  void setResolution(int resolution);

  void update(Scanner scanner);

} public interface ISwingSanePreferences extends BasePreferences {

  void cleanUp();

  ApplicationPreferences getApplicationPreferences();

  File getTempDirectory();

} public final class PreferencesUtils {

  private static Constraints copy(Constraints c1) {
    if (c1 == null) {
      return null;
    }
    Constraints c2 = new Constraints();
    c2.setFixedList(copyDoubleList(c1.getFixedList()));
    c2.setIntegerList(copyIntegerList(c1.getIntegerList()));
    c2.setMaximumFixed(copyDouble(c1.getMaximumFixed()));
    c2.setMaximumInteger(copyInteger(c1.getMaximumInteger()));
    c2.setMinimumFixed(copyDouble(c1.getMinimumFixed()));
    c2.setMinimumInteger(copyInteger(c1.getMinimumInteger()));
    c2.setQuantumFixed(copyDouble(c1.getQuantumFixed()));
    c2.setQuantumInteger(copyInteger(c1.getQuantumInteger()));
    c2.setStringList(copyStringList(c1.getStringList()));
    return c2;
  }

  public static HashMap<String, Login> copy(HashMap<String, Login> sl1) {
    HashMap<String, Login> sl2 = new HashMap<String, Login>();
    for (String resource : sl1.keySet()) {
      Login login = new Login();
      login.setUsername(sl1.get(resource).getUsername());
      login.setPassword(sl1.get(resource).getPassword());
      sl2.put(resource, login);
    }
    return sl2;
  }

  public static Option copy(Option o1) {

    Option o2 = null;

    OptionsOrderValuePair vp1 = o1.getOptionsOrderValuePair();
    OptionsOrderValuePair vp2 = new OptionsOrderValuePair();

    vp2.setActive(vp1.isActive());
    vp2.setSaneOptionType(vp1.getSaneOptionType());
    vp2.setKey(vp1.getKey());

    switch (vp1.getSaneOptionType()) {

    case STRING:
      StringOption so1 = (StringOption) o1;
      StringOption so2 = new StringOption();
      so2.setName(so1.getName());
      so2.setDescription(so1.getDescription());
      so2.setConstraints(copy(so1.getConstraints()));
      so2.setConstraintType(so1.getConstraintType());
      so2.setValue(so1.getValue());
      so2.setOptionsOrderValuePair(vp2);
      return so2;

    case INTEGER:
      IntegerOption io1 = (IntegerOption) o1;
      IntegerOption io2 = new IntegerOption();
      io2.setName(io1.getName());
      io2.setDescription(io1.getDescription());
      io2.setConstraints(copy(io1.getConstraints()));
      io2.setConstraintType(io1.getConstraintType());
      io2.setValue(io1.getValue());

      // cast to ArrayList - SaneOptions calls newArrayList
      io2.setValueList(copyIntegerList(io1.getValueList()));

      io2.setOptionsOrderValuePair(vp2);
      return io2;

    case BOOLEAN:
      BooleanOption bo1 = (BooleanOption) o1;
      BooleanOption bo2 = new BooleanOption();
      bo2.setName(bo1.getName());
      bo2.setDescription(bo1.getDescription());
      bo2.setConstraints(copy(bo1.getConstraints()));
      bo2.setConstraintType(bo1.getConstraintType());
      bo2.setValue(bo1.getValue());
      bo2.setOptionsOrderValuePair(vp2);
      return bo2;

    case FIXED:
      FixedOption fo1 = (FixedOption) o1;
      FixedOption fo2 = new FixedOption();
      fo2.setName(fo1.getName());
      fo2.setDescription(fo1.getDescription());
      fo2.setConstraints(copy(fo1.getConstraints()));
      fo2.setConstraintType(fo1.getConstraintType());
      fo2.setValue(fo1.getValue());

      // cast to ArrayList - SaneOptions calls newArrayList
      fo2.setValueList(copyDoubleList(fo1.getValueList()));

      fo2.setOptionsOrderValuePair(vp2);
      return fo2;

    case BUTTON:
      ButtonOption bto1 = (ButtonOption) o1;
      ButtonOption bto2 = new ButtonOption();
      copyBooleanOptions(bto1.getBooleanOptions(), bto2.getBooleanOptions());
      copyIntegerOptions(bto1.getIntegerOptions(), bto2.getIntegerOptions());
      copyStringOptions(bto1.getStringOptions(), bto2.getStringOptions());
      copyFixedOptions(bto1.getFixedOptions(), bto2.getFixedOptions());
      copyButtonOptions(bto1.getButtonOptions(), bto2.getButtonOptions());
      copyGroupOptions(bto1.getGroupOptions(), bto2.getGroupOptions());
      bto2.setName(bto1.getName());
      bto2.setDescription(bto1.getDescription());
      bto2.setOptionsOrderValuePair(vp2);
      bto2.setOptionOrdering(null); // TODO!!!!
      return bto2;

    case GROUP:
      GroupOption go1 = (GroupOption) o1;
      GroupOption go2 = new GroupOption();
      copyBooleanOptions(go1.getBooleanOptions(), go2.getBooleanOptions());
      copyIntegerOptions(go1.getIntegerOptions(), go2.getIntegerOptions());
      copyStringOptions(go1.getStringOptions(), go2.getStringOptions());
      copyFixedOptions(go1.getFixedOptions(), go2.getFixedOptions());
      copyGroupOptions(go1.getGroupOptions(), go2.getGroupOptions());
      copyGroupOptions(go1.getGroupOptions(), go2.getGroupOptions());
      go2.setName(go1.getName());
      go2.setDescription(go1.getDescription());
      go2.setOptionsOrderValuePair(vp2);
      go2.setOptionOrdering(null); // TODO!!!!
      return go2;

    default:
      break;

    }

    return o2;

  }

  public static Scanner copy(Scanner s1) {

    Scanner s2 = new Scanner();

    s2.setGuid(s1.getGuid());
    s2.setDescription(s1.getDescription());
    s2.setBatchPrefix(s1.getBatchPrefix());
    s2.setModel(s1.getModel());
    s2.setName(s1.getName());
    s2.setPagesToScan(s1.getPagesToScan());
    s2.setRemoteAddress(s1.getRemoteAddress());
    s2.setRemotePortNumber(s1.getRemotePortNumber());
    s2.setServiceName(s1.getServiceName());
    s2.setType(s1.getType());
    s2.setUsingCustomOptions(s1.isUsingCustomOptions());
    s2.setVendor(s1.getVendor());

    // deep copy the option maps
    copyBooleanOptions(s1.getBooleanOptions(), s2.getBooleanOptions());
    copyIntegerOptions(s1.getIntegerOptions(), s2.getIntegerOptions());
    copyFixedOptions(s1.getFixedOptions(), s2.getFixedOptions());
    copyStringOptions(s1.getStringOptions(), s2.getStringOptions());
    copyButtonOptions(s1.getButtonOptions(), s2.getButtonOptions());
    copyGroupOptions(s1.getGroupOptions(), s2.getGroupOptions());

    // reconnect the circular referencing
    copyOptionOrdering(s1.getOptionOrdering(), s2.getOptionOrdering(), s1.getBooleanOptions(),
        s1.getIntegerOptions(), s1.getFixedOptions(), s1.getStringOptions(), s1.getButtonOptions(),
        s1.getGroupOptions());

    return s2;

  }

  private static void copyBooleanOptions(HashMap<String, BooleanOption> bo1,
      HashMap<String, BooleanOption> bo2) {
    for (String key : bo1.keySet()) {
      bo2.put(key, (BooleanOption) copy(bo1.get(key)));
    }
  }

  private static void copyButtonOptions(HashMap<String, ButtonOption> bo1,
      HashMap<String, ButtonOption> bo2) {
    for (String key : bo1.keySet()) {
      bo2.put(key, (ButtonOption) copy(bo1.get(key)));
    }
  }

  private static Double copyDouble(Double d1) {
    if (d1 == null) {
      return null;
    }
    return new Double(d1.doubleValue());
  }

  public static ArrayList<Double> copyDoubleList(List<Double> l1) {
    if (l1 == null) {
      return null;
    }
    ArrayList<Double> l2 = new ArrayList<Double>(l1.size());
    for (Double i : l1) {
      l2.add(i.doubleValue());
    }
    return l2;
  }

  private static void copyFixedOptions(HashMap<String, FixedOption> fo1,
      HashMap<String, FixedOption> fo2) {
    for (String key : fo1.keySet()) {
      fo2.put(key, (FixedOption) copy(fo1.get(key)));
    }
  }

  private static void copyGroupOptions(HashMap<String, GroupOption> go1,
      HashMap<String, GroupOption> go2) {
    for (String key : go1.keySet()) {
      go2.put(key, (GroupOption) copy(go1.get(key)));
    }
  }

  private static Integer copyInteger(Integer i1) {
    if (i1 == null) {
      return null;
    }
    return new Integer(i1.intValue());
  }

  public static ArrayList<Integer> copyIntegerList(List<Integer> l1) {
    if (l1 == null) {
      return null;
    }
    ArrayList<Integer> l2 = new ArrayList<Integer>(l1.size());
    for (Integer i : l1) {
      l2.add(i.intValue());
    }
    return l2;
  }

  private static void copyIntegerOptions(HashMap<String, IntegerOption> io1,
      HashMap<String, IntegerOption> io2) {
    for (String key : io1.keySet()) {
      io2.put(key, (IntegerOption) copy(io1.get(key)));
    }
  }

  public static void copyOptionOrdering(ArrayList<OptionsOrderValuePair> oo1,
      ArrayList<OptionsOrderValuePair> oo2, HashMap<String, BooleanOption> booleanOptions,
      HashMap<String, IntegerOption> integerOptions, HashMap<String, FixedOption> fixedOptions,
      HashMap<String, StringOption> stringOptions, HashMap<String, ButtonOption> buttonOptions,
      HashMap<String, GroupOption> groupOptions) {

    for (OptionsOrderValuePair vp1 : oo1) {
      switch (vp1.getSaneOptionType()) {
      case STRING:
        oo2.add(stringOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case INTEGER:
        oo2.add(integerOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case FIXED:
        oo2.add(fixedOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case BOOLEAN:
        oo2.add(booleanOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case BUTTON:
        oo2.add(buttonOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case GROUP:
        oo2.add(groupOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      default:
        break;
      }
    }

  }

  private static ArrayList<String> copyStringList(List<String> l1) {
    if (l1 == null) {
      return null;
    }
    ArrayList<String> l2 = new ArrayList<String>(l1.size());
    for (String str : l1) {
      l2.add(str);
    }
    return l2;
  }

  private static void copyStringOptions(HashMap<String, StringOption> so1,
      HashMap<String, StringOption> so2) {
    for (String key : so1.keySet()) {
      so2.put(key, (StringOption) copy(so1.get(key)));
    }
  }

  public static void exportScannerXML(XStream xstream, Scanner scanner, File file)
      throws IOException {
    String rootNodeName = "swingsane";
    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),
        "UTF-8"));
    out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    ObjectOutputStream oos;
    oos = xstream.createObjectOutputStream(out, rootNodeName);
    oos.writeObject(scanner);
    oos.close();
  }

  public static Scanner importScannerXML(XStream xstream, File file) throws IOException,
  ClassNotFoundException {
    ObjectInputStream s = null;
    FileInputStream fis = null;
    Scanner scanner = null;
    try {
      fis = new FileInputStream(file);
      s = xstream.createObjectInputStream(new InputStreamReader(fis, "UTF-8"));
      scanner = ((Scanner) s.readObject());
      restoreCircularReferences(scanner);
      return scanner;
    } catch (IOException ex) {
      throw ex;
    } finally {
      if (s != null) {
        s.close();
      }
      if (fis != null) {
        fis.close();
      }
    }
  }

  static void restoreCircularReferences(Scanner scanner) {
    ArrayList<OptionsOrderValuePair> ordering = new ArrayList<OptionsOrderValuePair>();
    for (OptionsOrderValuePair vp : scanner.getOptionOrdering()) {
      switch (vp.getSaneOptionType()) {
      case STRING:
        ordering.add(scanner.getStringOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case INTEGER:
        ordering.add(scanner.getIntegerOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case FIXED:
        ordering.add(scanner.getFixedOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case BOOLEAN:
        ordering.add(scanner.getBooleanOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case BUTTON:
        ordering.add(scanner.getButtonOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case GROUP:
        ordering.add(scanner.getGroupOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      default:
        break;
      }
    }
    scanner.setOptionOrdering(ordering);
  }

  public static void update(HashMap<String, Login> sl1, HashMap<String, Login> sl2) {
    sl1.clear();
    sl1.putAll(sl2);
  }

  public static void update(Scanner s1, Scanner s2) {
    s1.setDescription(s2.getDescription());
    s1.setBatchPrefix(s2.getBatchPrefix());
    s1.setBooleanOptions(s2.getBooleanOptions());
    s1.setButtonOptions(s2.getButtonOptions());
    s1.setFixedOptions(s2.getFixedOptions());
    s1.setGroupOptions(s2.getGroupOptions());
    s1.setIntegerOptions(s2.getIntegerOptions());
    s1.setModel(s2.getModel());
    s1.setName(s2.getName());
    s1.setOptionOrdering(s2.getOptionOrdering());
    s1.setPagesToScan(s2.getPagesToScan());
    s1.setRemoteAddress(s2.getRemoteAddress());
    s1.setRemotePortNumber(s2.getRemotePortNumber());
    s1.setServiceName(s2.getServiceName());
    s1.setStringOptions(s2.getStringOptions());
    s1.setType(s2.getType());
    s1.setUsingCustomOptions(s2.isUsingCustomOptions());
    s1.setVendor(s2.getVendor());
  }

  private PreferencesUtils() {
  }

} @XStreamAlias("applicationPreferences")
public class ApplicationPreferences {

  private ArrayList<Scanner> scannerList;

  private HashMap<String, Login> saneLogins;

  private SaneServiceIdentity saneServiceIdentity;

  public final HashMap<String, Login> getSaneLogins() {
    if (saneLogins == null) {
      saneLogins = new HashMap<String, Login>();
    }
    return saneLogins;
  }

  public final SaneServiceIdentity getSaneServiceIdentity() {
    if (saneServiceIdentity == null) {
      saneServiceIdentity = new SaneServiceIdentity();
    }
    return saneServiceIdentity;
  }

  public final ArrayList<Scanner> getScannerList() {
    if (scannerList == null) {
      scannerList = new ArrayList<Scanner>();
    }
    return scannerList;
  }

  public final void setSaneService(SaneServiceIdentity saneService) {
    saneServiceIdentity = saneService;
  }

} @XStreamAlias("login")
public class Login {

  private String username;

  private String password;

  public final String getPassword() {
    return password;
  }

  public final String getUsername() {
    return username;
  }

  public final void setPassword(String password) {
    this.password = password;
  }

  public final void setUsername(String username) {
    this.username = username;
  }

} @XStreamAlias("scanner")
public class Scanner extends ScannerOptions {

  private String guid;
  private String description;
  private String serviceName;
  private String remoteAddress;
  private int remotePortNumber;
  private String vendor;
  private String model;
  private String name;
  private String type;
  private int pagesToScan;
  private boolean usingCustomOptions;
  private String batchPrefix = Localizer.localize("ScanFileNamePrefix") + "_"
      + Localizer.localize("TimeStampToken") + "_" + Localizer.localize("PageNumberToken") + "_"
      + Localizer.localize("PageCountToken");

  public Scanner() {
    setGuid((new RandomGUID()).toString());
  }

  public final String getBatchPrefix() {
    return batchPrefix;
  }

  public final String getDescription() {
    return description;
  }

  public final String getGuid() {
    return guid;
  }

  public final String getModel() {
    return model;
  }

  public final String getName() {
    return name;
  }

  public final int getPagesToScan() {
    return pagesToScan;
  }

  public final String getRemoteAddress() {
    return remoteAddress;
  }

  public final int getRemotePortNumber() {
    return remotePortNumber;
  }

  public final String getServiceName() {
    return serviceName;
  }

  public final String getType() {
    return type;
  }

  public final String getVendor() {
    return vendor;
  }

  public final boolean isUsingCustomOptions() {
    return usingCustomOptions;
  }

  public final void setBatchPrefix(String batchPrefix) {
    this.batchPrefix = batchPrefix;
  }

  public final void setDescription(String description) {
    this.description = description;
  }

  public void setGuid(String guid) {
    this.guid = guid;
  }

  public final void setModel(String model) {
    this.model = model;
  }

  public final void setName(String name) {
    this.name = name;
  }

  public final void setPagesToScan(int pagesToScan) {
    this.pagesToScan = pagesToScan;
  }

  public final void setRemoteAddress(String remoteAddress) {
    this.remoteAddress = remoteAddress;
  }

  public final void setRemotePortNumber(int remotePortNumber) {
    this.remotePortNumber = remotePortNumber;
  }

  public final void setServiceName(String serviceName) {
    this.serviceName = serviceName;
  }

  public final void setType(String type) {
    this.type = type;
  }

  public final void setUsingCustomOptions(boolean usingCustomOptions) {
    this.usingCustomOptions = usingCustomOptions;
  }

  public final void setVendor(String vendor) {
    this.vendor = vendor;
  }

} public final class Misc {

  public static void openURL(String url) {
    String osName = System.getProperty("os.name");

    try {
      if (osName.startsWith("Mac OS")) {
        Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
        Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class });
        openURL.invoke(null, new Object[] { url });
      } else if (osName.startsWith("Windows")) {
        Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
      } else { // assume Unix or Linux
        String[] browsers = { "chromium-browser", "google-chrome", "google-chrome-stable",
            "chrome", "/opt/google/chrome/chrome", "firefox", "opera", "konqueror", "epiphany",
            "mozilla", "netscape" };
        String browser = null;
        for (int count = 0; (count < browsers.length) && (browser == null); count++) {
          if (Runtime.getRuntime().exec(new String[] { "which", browsers[count] }).waitFor() == 0) {
            browser = browsers[count];
          }
        }
        if (browser == null) {
          throw new Exception(Localizer.localize("LaunchWebBrowserNotFound"));
        } else {
          Runtime.getRuntime().exec(new String[] { browser, url });
        }
      }

    } catch (Exception e) {
      JOptionPane.showMessageDialog(null, e.getMessage());
    }

  }

  public static boolean showConfirmDialog(final Component parent, final String title,
      final String message, final String confirmButtonText, final String cancelButtonText) {

    String options[] = new String[] { confirmButtonText, cancelButtonText };

    int result = JOptionPane.showOptionDialog(parent, message, title, JOptionPane.DEFAULT_OPTION,
        JOptionPane.WARNING_MESSAGE, null, options, options[0]);

    return result == 0;

  }

  public static void showErrorMsg(final Component parent, final String message) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        JOptionPane.showMessageDialog(parent, message, Localizer.localize("ErrorMessageTitle"),
            JOptionPane.ERROR_MESSAGE);
      }
    });
  }

  public static void showSuccessMsg(final Component parent, final String message) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        JOptionPane.showMessageDialog(parent, message, Localizer.localize("SuccessMessageTitle"),
            JOptionPane.INFORMATION_MESSAGE);
      }
    });
  }

  private Misc() {
  }

}
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.apache.log4j.Logger;
import au.com.southsky.jfreesane.RateLimitingScanListeners;
import au.com.southsky.jfreesane.SaneDevice;
import au.com.southsky.jfreesane.SaneException;
import au.com.southsky.jfreesane.ScanListener;
import au.com.southsky.jfreesane.ScanListenerAdapter;
import com.swingsane.business.discovery.DiscoveryEvent;
import com.swingsane.business.discovery.DiscoveryJob;
import com.swingsane.business.discovery.DiscoveryListener;
import com.swingsane.business.notification.INotification;
import com.swingsane.business.options.KnownSaneOptions;
import com.swingsane.business.scanning.IScanService;
import com.swingsane.business.scanning.ScanEvent;
import com.swingsane.business.scanning.ScanEventListener;
import com.swingsane.business.scanning.ScanJob;
import com.swingsane.gui.dialog.CustomSettingsDialog;
import com.swingsane.gui.dialog.GlobalSettingsDialog;
import com.swingsane.gui.dialog.ScannerSettingsDialog;
import com.swingsane.gui.list.ScannerListItem;
import com.swingsane.gui.list.ScannerListItem.ScannerStatus;
import com.swingsane.i18n.Localizer;
import com.swingsane.preferences.IPreferredDefaults;
import com.swingsane.preferences.ISwingSanePreferences;
import com.swingsane.preferences.PreferencesUtils;
import com.swingsane.preferences.model.ApplicationPreferences;
import com.swingsane.preferences.model.Login;
import com.swingsane.preferences.model.Scanner;
import com.swingsane.util.Misc;
import com.thoughtworks.xstream.XStream;
      }
    });

    JCheckBox useCustomSettingsCheckBox = components.getUseCustomSettingsCheckBox();
    useCustomSettingsCheckBox.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        useCustomSettingsActionPerformed(e);
      }
    });

    JButton customSettingsButton = components.getCustomSettingsButton();
    customSettingsButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        customSettingsActionPerformed(e);
      }
    });

    JButton addScannerButton = components.getAddScannerButton();
    addScannerButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        addScannerButtonActionPerformed(e);
      }
    });

    JButton globalSettingsButton = components.getGlobalSettingsButton();
    globalSettingsButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        showGlobalSettingsDialog();
      }
    });

    JButton saveSettingsButton = components.getSaveSettingsButton();
    saveSettingsButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        saveSettingsButtonActionPerformed(e);
      }
    });

    JButton editScannerButton = components.getEditScannerButton();
    editScannerButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        editScannerActionPerformed(e);
      }
    });

    JButton removeScannerButton = components.getRemoveScannerButton();
    removeScannerButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        removeScannerButtonActionPerformed(e);
      }
    });

    JButton scanButton = components.getScanButton();
    scanButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        scanActionPerformed(e);
      }
    });
    scanButton.setEnabled(false);

    JButton cancelScanButton = components.getCancelScanButton();
    cancelScanButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        cancelScan(e);
      }
    });
    cancelScanButton.setEnabled(false);

    JList<ScannerListItem> scannerList = components.getScannerList();
    scannerList.addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent e) {
        scannerListSelectionChanged(e);
      }
    });

    JButton detectScannersButton = components.getDetectScannersButton();
    detectScannersButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        detectScannersActionPerformed(e);
      }
    });
    detectScannersButton.setEnabled(true);

    JButton detectCancelButton = components.getCancelDetectScannersButton();
    detectCancelButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        cancelDetection(e);
      }
    });
    detectCancelButton.setEnabled(false);

  }

  public final boolean isAddScannerConfirmed(Scanner scanner) {
    return Misc.showConfirmDialog(
        components.getRootComponent(),
        Localizer.localize("ConfirmAddScannerTitle"),
        String.format(Localizer.localize("ConfirmAddScannerMessage"), scanner.getName(),
            scanner.getRemoteAddress() + ":" + scanner.getRemotePortNumber()),
        Localizer.localize("OK"), Localizer.localize("Cancel"));
  }

  public final boolean isBusy() {
    if (scannerListModel == null) {
      return false;
    }
    for (int i = 0; i < scannerListModel.getSize(); i++) {
      ScannerListItem scannerListItem = scannerListModel.getElementAt(i);
      ScanEventListener[] scanListeners = scannerListItem.getListeners(ScanEventListener.class);