uk.ac.ucl.cs.cmic.giftcloud.uploadapp.ConfigurationDialog.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.ucl.cs.cmic.giftcloud.uploadapp.ConfigurationDialog.java

Source

/*=============================================================================
    
  GIFT-Cloud: A data storage and collaboration platform
    
  Copyright (c) University College London (UCL). All rights reserved.
  Released under the Modified BSD License
  github.com/gift-surg
    
    
  Parts of this software were derived from DicomCleaner,
Copyright (c) 2001-2014, David A. Clunie DBA Pixelmed Publishing. All rights reserved.
    
=============================================================================*/

package uk.ac.ucl.cs.cmic.giftcloud.uploadapp;

import org.apache.commons.lang.StringUtils;
import uk.ac.ucl.cs.cmic.giftcloud.util.GiftCloudReporter;
import uk.ac.ucl.cs.cmic.giftcloud.util.GiftCloudUtils;
import uk.ac.ucl.cs.cmic.giftcloud.util.Optional;

import javax.swing.*;
import javax.swing.event.ListDataListener;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

/**
 * Shows a dialog for setting the properties of the GIFT-Cloud Uploader app
 */
public class ConfigurationDialog {

    private static int textFieldLengthForGiftCloudServerUrl = 32;

    private final UploaderGuiController controller;
    private final GiftCloudPropertiesFromApplication giftCloudProperties;
    private ProjectListModel projectListModel;
    private ResourceBundle resourceBundle;
    private final GiftCloudDialogs giftCloudDialogs;
    private GiftCloudReporter reporter;
    private final JDialog dialog;

    private final BackwardsCompatibleComboBox projectList;
    private final JTextField giftCloudServerText;
    private final JTextField giftCloudUsernameText;
    private final JPasswordField giftCloudPasswordText;
    private final JTextField listeningAETitleField;
    private final JTextField listeningPortField;
    private final JTextField patientListExportFolderField;
    private final JTextField subjectPrefixField;
    private final JPasswordField patientListSpreadsheetPasswordField;
    private final JTextField remoteAETitleField;
    private final JTextField remoteAEHostName;
    private final JTextField remoteAEPortField;
    private final JLabel projectListWaitingLabel;

    private final Component componentToCenterDialogOver;
    private final TemporaryProjectListModel temporaryDropDownListModel;
    private DropDownListModel.EnabledListener<Boolean> projectListEnabledListener = null;

    private boolean isDisposed = false;

    ConfigurationDialog(final Component owner, final UploaderGuiController controller,
            final GiftCloudPropertiesFromApplication giftCloudProperties, final ProjectListModel projectListModel,
            final ResourceBundle resourceBundle, final GiftCloudDialogs giftCloudDialogs,
            final GiftCloudReporter reporter) {
        this.controller = controller;
        this.giftCloudProperties = giftCloudProperties;
        this.projectListModel = projectListModel;
        this.resourceBundle = resourceBundle;
        this.giftCloudDialogs = giftCloudDialogs;
        this.reporter = reporter;
        temporaryDropDownListModel = new TemporaryProjectListModel(projectListModel,
                giftCloudProperties.getLastProject());
        componentToCenterDialogOver = owner;

        dialog = new JDialog();
        dialog.setModal(true);
        dialog.setResizable(false);

        // Call custom dialog close code when the close button is clicked
        dialog.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        dialog.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent ev) {
                closeDialog();
            }
        });

        dialog.setLocationRelativeTo(componentToCenterDialogOver); // without this, appears at TLHC rather then center of parent or screen
        dialog.setTitle(resourceBundle.getString("configurationDialogTitle"));

        final GridBagConstraints sectionTitleConstraints = new GridBagConstraints();
        sectionTitleConstraints.gridx = 0;
        sectionTitleConstraints.gridy = 1;
        sectionTitleConstraints.gridwidth = 2;
        sectionTitleConstraints.weightx = 1;
        sectionTitleConstraints.weighty = 1;
        sectionTitleConstraints.anchor = GridBagConstraints.CENTER;
        sectionTitleConstraints.fill = GridBagConstraints.HORIZONTAL;

        final GridBagConstraints labelConstraints = new GridBagConstraints();
        labelConstraints.gridx = 0;
        labelConstraints.gridy = 0;
        labelConstraints.gridwidth = 1;
        labelConstraints.weightx = 1;
        labelConstraints.weighty = 1;
        labelConstraints.anchor = GridBagConstraints.LINE_START;
        labelConstraints.fill = GridBagConstraints.NONE;

        final GridBagConstraints inputConstraints = new GridBagConstraints();
        inputConstraints.gridx = 1;
        inputConstraints.gridy = 0;
        inputConstraints.gridwidth = 1;
        inputConstraints.weightx = 1;
        inputConstraints.weighty = 1;
        inputConstraints.anchor = GridBagConstraints.LINE_END;
        inputConstraints.fill = GridBagConstraints.HORIZONTAL;

        GridBagConstraints separatorConstraint = new GridBagConstraints();
        separatorConstraint.weightx = 1.0;
        separatorConstraint.fill = GridBagConstraints.HORIZONTAL;
        separatorConstraint.gridwidth = GridBagConstraints.REMAINDER;

        // The panel containing the GIFT-Cloud server configuration
        final JPanel giftCloudServerPanel = new JPanel();
        {
            GridBagLayout projectUploadlayout = new GridBagLayout();
            giftCloudServerPanel.setLayout(projectUploadlayout);
            JLabel serverPanelLabel = new JLabel(resourceBundle.getString("configPanelServerConfig"),
                    SwingConstants.CENTER);
            giftCloudServerPanel.add(serverPanelLabel, sectionTitleConstraints);

            // GIFT-Cloud server URL
            {
                labelConstraints.gridwidth = 1;
                labelConstraints.gridy = 2;
                final JLabel giftCloudServerLabel = new JLabel(resourceBundle.getString("giftCloudServerText"),
                        SwingConstants.RIGHT);
                giftCloudServerLabel.setToolTipText(resourceBundle.getString("giftCloudServerTextToolTipText"));
                giftCloudServerPanel.add(giftCloudServerLabel, labelConstraints);

                giftCloudServerText = new AutoFocusTextField(giftCloudProperties.getGiftCloudUrl().orElse(""),
                        textFieldLengthForGiftCloudServerUrl);
                inputConstraints.gridy = 2;
                giftCloudServerPanel.add(giftCloudServerText, inputConstraints);
            }

            // GIFT-Cloud username
            {
                labelConstraints.gridy = 3;
                final JLabel giftCloudUserNameLabel = new JLabel(resourceBundle.getString("giftCloudUsername"),
                        SwingConstants.RIGHT);
                giftCloudUserNameLabel.setToolTipText(resourceBundle.getString("giftCloudUsernameToolTipText"));
                giftCloudServerPanel.add(giftCloudUserNameLabel, labelConstraints);

                final Optional<String> serverUrl = giftCloudProperties.getLastUserName();
                final String initialServerText = serverUrl.isPresent() ? serverUrl.get() : "";
                giftCloudUsernameText = new AutoFocusTextField(initialServerText);
                inputConstraints.gridy = 3;
                giftCloudServerPanel.add(giftCloudUsernameText, inputConstraints);
            }

            // GIFT-Cloud password
            {
                labelConstraints.gridy = 4;
                final JLabel giftCloudPasswordLabel = new JLabel(resourceBundle.getString("giftCloudPassword"),
                        SwingConstants.RIGHT);
                giftCloudPasswordLabel.setToolTipText(resourceBundle.getString("giftCloudPasswordToolTipText"));
                giftCloudServerPanel.add(giftCloudPasswordLabel, labelConstraints);

                final Optional<char[]> password = giftCloudProperties.getLastPassword();
                final char[] initialPassword = password.isPresent() ? password.get() : "".toCharArray();
                giftCloudPasswordText = new JPasswordField(new String(initialPassword), 16); // Shouldn't create a String but there's no other way to initialize the password field
                inputConstraints.gridy = 4;
                giftCloudServerPanel.add(giftCloudPasswordText, inputConstraints);
            }

            // Project list
            {
                labelConstraints.gridy = 5;
                JLabel projectListLabel = new JLabel(resourceBundle.getString("giftCloudProjectLabelText"),
                        SwingConstants.RIGHT);
                giftCloudServerPanel.add(projectListLabel, labelConstraints);

                inputConstraints.gridy = 5;
                projectList = new BackwardsCompatibleComboBox();
                projectList.setEditable(false);
                projectList.setToolTipText(resourceBundle.getString("giftCloudProjectTooltip"));
                giftCloudServerPanel.add(projectList, inputConstraints);

                labelConstraints.gridx = 1;
                projectListWaitingLabel = new JLabel(resourceBundle.getString("giftCloudProjectWaitingLabelText"),
                        SwingConstants.RIGHT);
                giftCloudServerPanel.add(projectListWaitingLabel, labelConstraints);
                labelConstraints.gridx = 0;
            }

            // Subject prefix
            {
                labelConstraints.gridy = 6;
                JLabel subjectPrefixLabel = new JLabel(resourceBundle.getString("configPanelListenerSubjectPrefix"),
                        SwingConstants.RIGHT);
                subjectPrefixLabel
                        .setToolTipText(resourceBundle.getString("configPanelListenerSubjectPrefixTooltip"));
                giftCloudServerPanel.add(subjectPrefixLabel, labelConstraints);

                inputConstraints.gridy = 6;
                final Optional<String> subjectPrefixText = giftCloudProperties.getSubjectPrefix();
                subjectPrefixField = new AutoFocusTextField(subjectPrefixText.orElse(""));
                giftCloudServerPanel.add(subjectPrefixField, inputConstraints);
            }
        }

        // Local Dicom node configuration
        final JPanel listenerPanel = new JPanel();
        {
            GridBagLayout listenerPanellayout = new GridBagLayout();
            listenerPanel.setLayout(listenerPanellayout);
            JSeparator separator = new JSeparator();
            listenerPanel.add(separator, separatorConstraint);

            JLabel listenerPanelLabel = new JLabel(resourceBundle.getString("configPanelListenerConfig"),
                    SwingConstants.CENTER);
            listenerPanel.add(listenerPanelLabel, sectionTitleConstraints);

            {
                labelConstraints.gridy = 2;
                JLabel listeningAETitleJLabel = new JLabel(resourceBundle.getString("configPanelListenerAe"),
                        SwingConstants.RIGHT);
                listeningAETitleJLabel.setToolTipText(resourceBundle.getString("configPanelListenerAeToolTip"));
                listenerPanellayout.setConstraints(listeningAETitleJLabel, labelConstraints);
                listenerPanel.add(listeningAETitleJLabel);

                inputConstraints.gridy = 2;
                final String listeningAETitleInitialText = giftCloudProperties.getListenerAETitle();
                listeningAETitleField = new AutoFocusTextField(listeningAETitleInitialText);
                listenerPanellayout.setConstraints(listeningAETitleField, inputConstraints);
                listenerPanel.add(listeningAETitleField);
            }
            {
                labelConstraints.gridy = 3;
                JLabel listeningPortJLabel = new JLabel(resourceBundle.getString("configPanelListenerPort"),
                        SwingConstants.RIGHT);
                listeningPortJLabel.setToolTipText(resourceBundle.getString("configPanelListenerPortToolTip"));
                listenerPanellayout.setConstraints(listeningPortJLabel, labelConstraints);
                listenerPanel.add(listeningPortJLabel);

                inputConstraints.gridy = 3;
                final int port = giftCloudProperties.getListeningPort();
                final String portValue = Integer.toString(port);
                listeningPortField = new AutoFocusTextField(portValue);
                listenerPanellayout.setConstraints(listeningPortField, inputConstraints);
                listenerPanel.add(listeningPortField);
            }
            {
                labelConstraints.gridy = 4;
                JLabel patientListExportFolderLabel = new JLabel(
                        resourceBundle.getString("configPanelListenerPatientListExportFolder"),
                        SwingConstants.RIGHT);
                patientListExportFolderLabel.setToolTipText(
                        resourceBundle.getString("configPanelListenerPatientListExportFolderTooltip"));
                listenerPanellayout.setConstraints(patientListExportFolderLabel, labelConstraints);
                listenerPanel.add(patientListExportFolderLabel);

                inputConstraints.gridy = 4;
                final Optional<String> patientListExportFolder = giftCloudProperties.getPatientListExportFolder();
                patientListExportFolderField = new AutoFocusTextField(patientListExportFolder.orElse(""));
                listenerPanellayout.setConstraints(patientListExportFolderField, inputConstraints);
                listenerPanel.add(patientListExportFolderField);
            }

            // Patient list spreadsheet password
            {
                labelConstraints.gridy = 5;
                final JLabel patientListSpreadsheetPasswordLabel = new JLabel(
                        resourceBundle.getString("configPanelListenerPatientListSpreadhsheetPassword"),
                        SwingConstants.RIGHT);
                patientListSpreadsheetPasswordLabel.setToolTipText(
                        resourceBundle.getString("configPanelListenerPatientListSpreadhsheetPasswordTooltip"));
                listenerPanel.add(patientListSpreadsheetPasswordLabel, labelConstraints);

                final Optional<char[]> password = giftCloudProperties.getPatientListPassword();
                final char[] initialPassword = password.isPresent() ? password.get() : "".toCharArray();
                patientListSpreadsheetPasswordField = new JPasswordField(new String(initialPassword), 16); // Shouldn't create a String but there's no other way to initialize the password field
                inputConstraints.gridy = 5;
                listenerPanel.add(patientListSpreadsheetPasswordField, inputConstraints);
            }
        }

        // Remote PACS configuration
        final JPanel remoteAEPanel = new JPanel();

        {
            GridBagLayout pacsPanellayout = new GridBagLayout();
            remoteAEPanel.setLayout(pacsPanellayout);

            JSeparator separator = new JSeparator();
            remoteAEPanel.add(separator, separatorConstraint);

            JLabel remotePanelLabel = new JLabel(resourceBundle.getString("pacsPanelListenerConfig"),
                    SwingConstants.CENTER);
            remoteAEPanel.add(remotePanelLabel, sectionTitleConstraints);

            {
                labelConstraints.gridy = 2;
                JLabel remoteAeTitleLabel = new JLabel(resourceBundle.getString("configPanelPacsAeTitle"),
                        SwingConstants.RIGHT);
                remoteAeTitleLabel.setToolTipText(resourceBundle.getString("configPanelPacsAeTitleTooltip"));
                remoteAEPanel.add(remoteAeTitleLabel, labelConstraints);

                final Optional<String> pacsAeTitle = giftCloudProperties.getPacsAeTitle();
                remoteAETitleField = new AutoFocusTextField(pacsAeTitle.isPresent() ? pacsAeTitle.get() : "");
                inputConstraints.gridy = 2;
                remoteAEPanel.add(remoteAETitleField, inputConstraints);
            }

            {
                labelConstraints.gridy = 3;
                JLabel remoteAeHostLabel = new JLabel(resourceBundle.getString("configPanelPacsHostname"),
                        SwingConstants.RIGHT);
                remoteAeHostLabel.setToolTipText(resourceBundle.getString("configPanelPacsHostnameTooltip"));
                remoteAEPanel.add(remoteAeHostLabel, labelConstraints);

                remoteAEHostName = new AutoFocusTextField(giftCloudProperties.getPacsHostName().orElse(""));
                inputConstraints.gridy = 3;
                remoteAEPanel.add(remoteAEHostName, inputConstraints);
            }

            {
                labelConstraints.gridy = 4;
                JLabel remoteAeTitleLabel = new JLabel(resourceBundle.getString("configPanelPacsPort"),
                        SwingConstants.RIGHT);
                remoteAeTitleLabel.setToolTipText(resourceBundle.getString("configPanelPacsPortTooltip"));
                remoteAEPanel.add(remoteAeTitleLabel, labelConstraints);

                remoteAEPortField = new AutoFocusTextField(Integer.toString(giftCloudProperties.getPacsPort()));
                inputConstraints.gridy = 4;
                remoteAEPanel.add(remoteAEPortField, inputConstraints);
            }

        }

        // The panel containing the cancel and apply buttons
        JPanel buttonPanel = new JPanel();
        JPanel closeButtonPanel = new JPanel();
        {
            final GridBagLayout buttonPanellayout = new GridBagLayout();
            buttonPanel.setLayout(buttonPanellayout);

            JSeparator separator = new JSeparator();
            buttonPanel.add(separator, separatorConstraint);

            closeButtonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));

            JButton cancelButton = new JButton(resourceBundle.getString("cancelSettingsButtonLabelText"));
            cancelButton.setToolTipText(resourceBundle.getString("cancelSettingsButtonToolTipText"));
            closeButtonPanel.add(cancelButton);
            cancelButton.addActionListener(new CancelActionListener());

            JButton applyButton = new JButton(resourceBundle.getString("applySettingsButtonLabelText"));
            applyButton.setToolTipText(resourceBundle.getString("applySettingsButtonToolTipText"));
            closeButtonPanel.add(applyButton);
            applyButton.addActionListener(new ApplyActionListener());

            JButton closeButton = new JButton(resourceBundle.getString("closeSettingsButtonLabelText"));
            closeButton.setToolTipText(resourceBundle.getString("closeSettingsButtonToolTipText"));
            closeButtonPanel.add(closeButton);
            closeButton.addActionListener(new CloseActionListener());

            final GridBagConstraints constraints = new GridBagConstraints();
            constraints.gridx = 0;
            constraints.gridy = 1;
            constraints.weightx = 1;
            constraints.weighty = 1;
            constraints.insets = new Insets(5, 5, 5, 5);
            constraints.fill = GridBagConstraints.HORIZONTAL;
            buttonPanellayout.setConstraints(closeButtonPanel, constraints);
            buttonPanel.add(closeButtonPanel);
        }

        // The main panel of the configuration dialog
        JPanel configPanel = new JPanel();
        {
            final GridBagLayout configPanelLayout = new GridBagLayout();
            configPanel.setLayout(configPanelLayout);
            {
                final GridBagConstraints constraints = new GridBagConstraints();
                constraints.gridx = 0;
                constraints.gridy = 0;
                constraints.weightx = 1;
                constraints.weighty = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                constraints.fill = GridBagConstraints.HORIZONTAL;
                configPanelLayout.setConstraints(giftCloudServerPanel, constraints);
                configPanel.add(giftCloudServerPanel);
            }
            {
                final GridBagConstraints constraints = new GridBagConstraints();
                constraints.gridx = 0;
                constraints.gridy = 1;
                constraints.weightx = 1;
                constraints.weighty = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                constraints.fill = GridBagConstraints.HORIZONTAL;
                configPanelLayout.setConstraints(listenerPanel, constraints);
                configPanel.add(listenerPanel);
            }
            {
                final GridBagConstraints constraints = new GridBagConstraints();
                constraints.gridx = 0;
                constraints.gridy = 2;
                constraints.insets = new Insets(5, 5, 5, 5);
                constraints.fill = GridBagConstraints.HORIZONTAL;
                configPanelLayout.setConstraints(remoteAEPanel, constraints);
                configPanel.add(remoteAEPanel);
            }
            {
                final GridBagConstraints constraints = new GridBagConstraints();
                constraints.gridx = 0;
                constraints.gridy = 3;
                constraints.insets = new Insets(5, 5, 5, 5);
                constraints.fill = GridBagConstraints.HORIZONTAL;
                configPanelLayout.setConstraints(buttonPanel, constraints);
                configPanel.add(buttonPanel);
            }
        }

        projectList.setModel(temporaryDropDownListModel);
        showProjectList(projectListModel.isEnabled());

        // Create a listener to enable/disable the project list when it is set from the server.
        // The reason for this is that the project list is set after logging into the server, which can happen asynchronously after property changes have been applied.
        // If the server was configured in the dialog and apply clicked, it might take a few seconds for the project list to be updated, and we want it to become available when this happens
        projectListEnabledListener = new DropDownListModel.EnabledListener<Boolean>() {
            @Override
            public void statusChanged(final Boolean visibility) {
                showProjectList(projectListModel.isEnabled());
            }
        };

        projectListModel.addListener(projectListEnabledListener);

        GridBagLayout layout = new GridBagLayout();
        dialog.setLayout(layout);
        Container content = dialog.getContentPane();
        content.add(configPanel);
        dialog.pack();
        dialog.setVisible(true);
        dialog.pack();
    }

    private void showProjectList(final boolean enabled) {

        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                projectList.setEnabled(enabled);
                projectList.setVisible(enabled);
                projectListWaitingLabel.setVisible(!enabled);
            }
        });

    }

    public boolean isVisible() {
        return dialog.isDisplayable() && !isDisposed;
    }

    private class CloseActionListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            if (checkAndApplyProperties()) {
                closeDialog();
            }
        }
    }

    private class CancelActionListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            closeDialog();
        }
    }

    private class ApplyActionListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            checkAndApplyProperties();
        }
    }

    private boolean checkAndApplyProperties() {
        final List<String> problems = checkProperties();

        if (problems.isEmpty()) {
            applyProperties();
            return true;
        } else {
            final StringBuilder builder = new StringBuilder();
            for (final String string : problems) {
                builder.append(" - ");
                builder.append(string);
                builder.append("<br>");
            }
            giftCloudDialogs.showError("Please correct the following problems with the settings:",
                    Optional.of(builder.toString()));
            return false;
        }
    }

    private List<String> checkProperties() {
        final List<String> problems = new ArrayList<String>();

        {
            final String listeningAETitle = listeningAETitleField.getText();

            if (StringUtils.isBlank(listeningAETitle)) {
                problems.add(resourceBundle.getString("configPanelListenerAeNotSet"));
            } else if (!ConfigurationDialog.isValidAETitle(listeningAETitle)) {
                problems.add(resourceBundle.getString("configPanelListenerAeError"));
            }
        }
        {
            try {
                final int listeningPort = Integer.parseInt(listeningPortField.getText());
                if (listeningPort < 1024) {
                    problems.add(resourceBundle.getString("configPanelListenerPortSmallError"));
                }
            } catch (NumberFormatException e) {
                problems.add(resourceBundle.getString("configPanelListenerPortError"));
            }
        }
        {
            try {
                final int remotePort = Integer.parseInt(remoteAEPortField.getText());
                if (remotePort < 1024) {
                    problems.add(resourceBundle.getString("configPanelPacsPortSmallError"));
                }
            } catch (NumberFormatException e) {
                problems.add(resourceBundle.getString("configPanelPacsPortError"));
            }
        }

        {
            final String remoteAETitle = remoteAETitleField.getText();
            if (!StringUtils.isBlank(remoteAETitle) && !ConfigurationDialog.isValidAETitle(remoteAETitle)) {
                problems.add(resourceBundle.getString("configPanelPacsAeError"));
            }
        }

        {
            final String serverText = giftCloudServerText.getText();
            if (!StringUtils.isBlank(serverText)) {
                try {
                    final URL giftCloudUrl = new URL(serverText);
                } catch (MalformedURLException e) {
                    problems.add(resourceBundle.getString("giftCloudServerTextError"));
                }
            }
        }

        {
            final String patientListExportFolder = patientListExportFolderField.getText();
            if (!StringUtils.isBlank(patientListExportFolder)) {
                try {
                    if (!GiftCloudUtils.createDirectoryIfNotExisting(new File(patientListExportFolder))) {
                        problems.add(resourceBundle
                                .getString("configPanelListenerPatientListExportFolderCreationError"));
                    } else {
                        if (!GiftCloudUtils.isDirectoryWritable(patientListExportFolder)) {
                            problems.add(
                                    resourceBundle.getString("configPanelListenerPatientListExportFolderError"));
                        }
                    }
                } catch (Throwable t) {
                    problems.add(resourceBundle.getString("configPanelListenerPatientListExportFolderError"));
                }

            }
        }
        return problems;
    }

    private void applyProperties() {

        // Extract out all the new values (this makes the code a little clearer below)
        final int newListeningPortValue = Integer.parseInt(listeningPortField.getText());
        final String newListeningAeTitle = listeningAETitleField.getText();
        final String newGiftCloudUrl = giftCloudServerText.getText();
        final String newGiftCloudUserName = giftCloudUsernameText.getText();
        final char[] newGiftCloudPassword = giftCloudPasswordText.getPassword();
        final char[] newPatientListPassword = patientListSpreadsheetPasswordField.getPassword();
        final String newPatientListExportFolder = patientListExportFolderField.getText();
        final String newSubjectPrefix = subjectPrefixField.getText();
        final int newPacsPort = Integer.parseInt(remoteAEPortField.getText());
        final String newPacsAeTitle = remoteAETitleField.getText();
        final String newPacsHostName = remoteAEHostName.getText();

        // Determine whether to restart the listener service based on changes to the listener or PACS properties
        final boolean restartDicomNode = giftCloudProperties.getListeningPort() != newListeningPortValue
                || !newListeningAeTitle.equals(giftCloudProperties.getListenerAETitle())
                || giftCloudProperties.getPacsPort() != newPacsPort
                || !newPacsAeTitle.equals(giftCloudProperties.getPacsAeTitle().orElse(""))
                || !newPacsHostName.equals(giftCloudProperties.getPacsHostName().orElse(""));

        final boolean restartUploader = !newGiftCloudUrl.equals(giftCloudProperties.getGiftCloudUrl().orElse(""))
                || !newGiftCloudUserName.equals(giftCloudProperties.getLastUserName().orElse(""))
                || !newGiftCloudPassword.equals(giftCloudProperties.getLastPassword().orElse("".toCharArray()))
                || !temporaryDropDownListModel.getSelectedItem().equals(projectListModel.getSelectedItem());

        final boolean forcePatientListExport = StringUtils.isNotBlank(newPatientListExportFolder)
                && (!giftCloudProperties.getPatientListExportFolder().isPresent()
                        || !newPatientListExportFolder
                                .equals(giftCloudProperties.getPatientListExportFolder().get())
                        || !newPatientListPassword
                                .equals(giftCloudProperties.getPatientListPassword().orElse("".toCharArray())));

        // Change the properties (must be done after we access the current values to check for changes)
        giftCloudProperties.setListeningPort(newListeningPortValue);
        giftCloudProperties.setListenerAETitle(newListeningAeTitle);
        giftCloudProperties.setPatientListExportFolder(newPatientListExportFolder);
        giftCloudProperties.setGiftCloudUrl(newGiftCloudUrl);
        giftCloudProperties.setLastUserName(newGiftCloudUserName);
        giftCloudProperties.setLastPassword(newGiftCloudPassword);
        giftCloudProperties.setPatientListPassword(newPatientListPassword);
        giftCloudProperties.setSubjectPrefix(newSubjectPrefix);
        giftCloudProperties.setPacsPort(newPacsPort);
        giftCloudProperties.setPacsAeTitle(newPacsAeTitle);
        giftCloudProperties.setPacsHostName(newPacsHostName);

        // We only update the last project with a valid value. A null value may indicate the project list hasn't been
        // set because the server login details are incorrect or some communication error
        final Object selectedProjectItem = temporaryDropDownListModel.getSelectedItem();
        if (selectedProjectItem instanceof String) {
            final String newProject = (String) selectedProjectItem;
            if (StringUtils.isNotBlank(newProject)) {
                giftCloudProperties.setLastProject(newProject);
            }
        }

        giftCloudProperties.save();

        if (restartDicomNode || restartUploader || forcePatientListExport) {
            Cursor was = dialog.getCursor();
            dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

            if (restartDicomNode) {
                controller.restartDicomService();
            }
            if (restartUploader) {
                controller.invalidateServerAndRestartUploader();
            }
            if (forcePatientListExport) {
                controller.exportPatientList(false);
            }

            dialog.setCursor(was);
        }

    }

    private void closeDialog() {
        projectListModel.removeListener(projectListEnabledListener);
        projectListEnabledListener = null;
        isDisposed = true;
        dialog.dispose();
    }

    public static boolean isValidAETitle(String aet) {
        // Per PS 3.5: Default Character Repertoire excluding character code 5CH (the BACKSLASH \? in ISO-IR 6), and control characters LF, FF, CR and ESC. 16 bytes maximum
        boolean good = true;
        if (aet == null) {
            good = false;
        } else if (aet.length() == 0) {
            good = false;
        } else if (aet.length() > 16) {
            good = false;
        } else if (aet.trim().length() == 0) { // all whitespace is illegal
            good = false;
        } else if (aet.contains("\\")) {
            good = false;
        } else {
            int l = aet.length();
            for (int i = 0; i < l; ++i) {
                int codePoint = aet.codePointAt(i);
                try {
                    Character.UnicodeBlock codeBlock = Character.UnicodeBlock.of(codePoint);
                    if (codeBlock != Character.UnicodeBlock.BASIC_LATIN) {
                        good = false;
                    } else if (Character.isISOControl(codePoint)) {
                        good = false;
                    }
                } catch (IllegalArgumentException e) { // if not a valid code point
                    good = false;
                }
            }
        }
        return good;
    }

    class AutoFocusTextField extends JTextField {

        AutoFocusTextField(String text, int columns) {
            super(text, columns);
            addFocusCallback();
        }

        AutoFocusTextField(final String initialText) {
            super(initialText);
            addFocusCallback();
        }

        private void addFocusCallback() {
            addFocusListener(new java.awt.event.FocusAdapter() {
                public void focusGained(java.awt.event.FocusEvent event) {
                    JTextComponent textComponent = (JTextComponent) (event.getSource());
                    textComponent.selectAll();
                }
            });
        }
    }

    /**
     * Implements a ComboBoxModel which uses an underlying ComboBoxModel for the elements,
     * but does not change its selected item.
     *
     * An example use would be in a dialog with cancel and apply buttons, so that the selection change is only applied if
     * the apply button is pushed, but not if the cancel button is pushed.
     */
    public static class TemporaryProjectListModel implements ComboBoxModel {

        private final ProjectListModel projectListModel;
        private Object selectedItem = null;

        public TemporaryProjectListModel(final ProjectListModel projectListModel,
                final Optional<String> initialSelectedItem) {
            this.projectListModel = projectListModel;
            setSelectedItem(initialSelectedItem.orElse(""));
        }

        @Override
        public void setSelectedItem(Object anItem) {
            selectedItem = anItem;
        }

        @Override
        public Object getSelectedItem() {
            return selectedItem;
        }

        @Override
        public int getSize() {
            return projectListModel.getSize();
        }

        @Override
        public String getElementAt(int index) {
            return (String) (projectListModel.getElementAt(index));
        }

        @Override
        public void addListDataListener(ListDataListener l) {
            projectListModel.addListDataListener(l);
        }

        @Override
        public void removeListDataListener(ListDataListener l) {
            projectListModel.removeListDataListener(l);
        }
    }
}