org.opendatakit.briefcase.ui.MainBriefcaseWindow.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.briefcase.ui.MainBriefcaseWindow.java

Source

/*
 * Copyright (C) 2011 University of Washington.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.opendatakit.briefcase.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.FocusTraversalPolicy;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.UIManager;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opendatakit.aggregate.parser.BaseFormParserForJavaRosa;
import org.opendatakit.briefcase.model.BriefcasePreferences;
import org.opendatakit.briefcase.model.ExportAbortEvent;
import org.opendatakit.briefcase.model.FileSystemException;
import org.opendatakit.briefcase.model.TerminationFuture;
import org.opendatakit.briefcase.model.TransferAbortEvent;
import org.opendatakit.briefcase.util.FileSystemUtils;

public class MainBriefcaseWindow implements WindowListener {
    private static final String BRIEFCASE_VERSION = "ODK Briefcase - " + BriefcasePreferences.VERSION;

    private JFrame frame;
    private JTextField txtBriefcaseDir;
    private JButton btnChoose;
    private PullTransferPanel gatherPanel;
    private PushTransferPanel uploadPanel;
    private ExportPanel exportPanel;
    private final TerminationFuture exportTerminationFuture = new TerminationFuture();
    private final TerminationFuture transferTerminationFuture = new TerminationFuture();

    public static final String AGGREGATE_URL = "aggregate_url";
    public static final String DATE_FORMAT = "yyyy/MM/dd";
    public static final String EXCLUDE_MEDIA_EXPORT = "exclude_media_export";
    public static final String EXPORT_DIRECTORY = "export_directory";
    public static final String EXPORT_END_DATE = "export_end_date";
    public static final String EXPORT_FILENAME = "export_filename";
    public static final String EXPORT_START_DATE = "export_start_date";
    public static final String FORM_ID = "form_id";
    public static final String ODK_PASSWORD = "odk_password";
    public static final String ODK_USERNAME = "odk_username";
    public static final String OVERWRITE_CSV_EXPORT = "overwrite_csv_export";
    public static final String STORAGE_DIRECTORY = "storage_directory";
    public static final String ODK_DIR = "odk_directory";
    public static final String HELP = "help";
    public static final String VERSION = "version";
    public static final String PEM_FILE = "pem_file";

    private static final Log log = LogFactory.getLog(BaseFormParserForJavaRosa.class.getName());

    private JTabbedPane tabbedPane;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {

        if (args.length == 0) {

            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        // Set System L&F
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

                        MainBriefcaseWindow window = new MainBriefcaseWindow();
                        window.frame.setTitle(BRIEFCASE_VERSION);
                        ImageIcon icon = new ImageIcon(
                                MainBriefcaseWindow.class.getClassLoader().getResource("odk_logo.png"));
                        window.frame.setIconImage(icon.getImage());
                        window.frame.setVisible(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        } else {
            Options options = addOptions();
            CommandLineParser parser = new BasicParser();
            CommandLine cmd = null;

            try {
                cmd = parser.parse(options, args);
            } catch (ParseException e1) {
                log.error("Launch Failed: " + e1.getMessage());
                showHelp(options);
                System.exit(1);
            }

            if (cmd.hasOption(HELP)) {
                showHelp(options);
                System.exit(1);
            }

            if (cmd.hasOption(VERSION)) {
                showVersion();
                System.exit(1);
            }

            // required for all operations
            if (!cmd.hasOption(FORM_ID) || !cmd.hasOption(STORAGE_DIRECTORY)) {
                log.error(FORM_ID + " and " + STORAGE_DIRECTORY + " are required");
                showHelp(options);
                System.exit(1);
            }

            // pull from collect or aggregate, not both
            if (cmd.hasOption(ODK_DIR) && cmd.hasOption(AGGREGATE_URL)) {
                log.error("Can only have one of " + ODK_DIR + " or " + AGGREGATE_URL);
                showHelp(options);
                System.exit(1);
            }

            // pull from aggregate
            if (cmd.hasOption(AGGREGATE_URL) && (!(cmd.hasOption(ODK_USERNAME) && cmd.hasOption(ODK_PASSWORD)))) {
                log.error(ODK_USERNAME + " and " + ODK_PASSWORD + " required when " + AGGREGATE_URL
                        + " is specified");
                showHelp(options);
                System.exit(1);
            }

            // export files
            if (cmd.hasOption(EXPORT_DIRECTORY) && !cmd.hasOption(EXPORT_FILENAME)
                    || !cmd.hasOption(EXPORT_DIRECTORY) && cmd.hasOption(EXPORT_FILENAME)) {
                log.error(EXPORT_DIRECTORY + " and " + EXPORT_FILENAME + " are both required to export");
                showHelp(options);
                System.exit(1);
            }

            if (cmd.hasOption(EXPORT_END_DATE)) {
                if (!testDateFormat(cmd.getOptionValue(EXPORT_END_DATE))) {
                    log.error("Invalid date format in " + EXPORT_END_DATE);
                    showHelp(options);
                    System.exit(1);
                }
            }
            if (cmd.hasOption(EXPORT_START_DATE)) {
                if (!testDateFormat(cmd.getOptionValue(EXPORT_START_DATE))) {
                    log.error("Invalid date format in " + EXPORT_START_DATE);
                    showHelp(options);
                    System.exit(1);
                }
            }

            BriefcaseCLI bcli = new BriefcaseCLI(cmd);
            bcli.run();
        }
    }

    class FolderActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            // briefcase...
            establishBriefcaseStorageLocation(true);
        }

    }

    void setFullUIEnabled(boolean state) {
        String path = BriefcasePreferences.getBriefcaseDirectoryIfSet();
        if (path != null) {
            txtBriefcaseDir.setText(path + File.separator + FileSystemUtils.BRIEFCASE_DIR);
        } else {
            txtBriefcaseDir.setText("");
        }
        if (state) {
            exportPanel.updateComboBox();
            uploadPanel.updateFormStatuses();
            exportPanel.setEnabled(true);
            gatherPanel.setEnabled(true);
            uploadPanel.setEnabled(true);
            tabbedPane.setEnabled(true);
        } else {
            exportPanel.setEnabled(false);
            gatherPanel.setEnabled(false);
            uploadPanel.setEnabled(false);
            tabbedPane.setEnabled(false);
        }
    }

    /**
     * Create the application.
     */
    public MainBriefcaseWindow() {
        frame = new JFrame();
        frame.setBounds(100, 100, 680, 595);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.addWindowListener(new WindowListener() {
            @Override
            public void windowOpened(WindowEvent e) {
            }

            @Override
            public void windowClosing(WindowEvent e) {
            }

            @Override
            public void windowClosed(WindowEvent e) {
            }

            @Override
            public void windowIconified(WindowEvent e) {
            }

            @Override
            public void windowDeiconified(WindowEvent e) {
            }

            @Override
            public void windowActivated(WindowEvent e) {
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
            }
        });

        JLabel lblBriefcaseDirectory = new JLabel(MessageStrings.BRIEFCASE_STORAGE_LOCATION);

        txtBriefcaseDir = new JTextField();
        txtBriefcaseDir.setFocusable(false);
        txtBriefcaseDir.setEditable(false);
        txtBriefcaseDir.setColumns(10);

        btnChoose = new JButton("Change...");
        btnChoose.addActionListener(new FolderActionListener());

        tabbedPane = new JTabbedPane(JTabbedPane.TOP);
        GroupLayout groupLayout = new GroupLayout(frame.getContentPane());
        groupLayout.setHorizontalGroup(groupLayout.createParallelGroup(Alignment.LEADING).addGroup(groupLayout
                .createSequentialGroup().addContainerGap()
                .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
                        .addComponent(tabbedPane, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 628,
                                Short.MAX_VALUE)
                        .addGroup(groupLayout.createSequentialGroup().addComponent(lblBriefcaseDirectory).addGap(18)
                                .addComponent(txtBriefcaseDir, GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE)
                                .addGap(18).addComponent(btnChoose)))
                .addContainerGap()));
        groupLayout.setVerticalGroup(groupLayout.createParallelGroup(Alignment.LEADING)
                .addGroup(groupLayout.createSequentialGroup().addContainerGap()
                        .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
                                .addComponent(txtBriefcaseDir, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
                                        GroupLayout.PREFERRED_SIZE)
                                .addComponent(btnChoose).addComponent(lblBriefcaseDirectory))
                        .addGap(33).addComponent(tabbedPane, GroupLayout.DEFAULT_SIZE, 446, Short.MAX_VALUE)
                        .addContainerGap()));

        gatherPanel = new PullTransferPanel(transferTerminationFuture);
        tabbedPane.addTab(PullTransferPanel.TAB_NAME, null, gatherPanel, null);
        PullTransferPanel.TAB_POSITION = 0;

        uploadPanel = new PushTransferPanel(transferTerminationFuture);
        tabbedPane.addTab(PushTransferPanel.TAB_NAME, null, uploadPanel, null);
        PushTransferPanel.TAB_POSITION = 1;

        exportPanel = new ExportPanel(exportTerminationFuture);
        tabbedPane.addTab(ExportPanel.TAB_NAME, null, exportPanel, null);
        frame.getContentPane().setLayout(groupLayout);
        ExportPanel.TAB_POSITION = 2;

        frame.addWindowListener(this);
        setFullUIEnabled(false);

        frame.setFocusTraversalPolicy(new FocusTraversalPolicy() {

            @Override
            public Component getComponentAfter(Container arg0, Component arg1) {
                ArrayList<Component> componentOrdering = new ArrayList<Component>();
                for (;;) {
                    int nextPanel = PullTransferPanel.TAB_POSITION;
                    componentOrdering.clear();
                    componentOrdering.add(txtBriefcaseDir);
                    componentOrdering.add(btnChoose);
                    componentOrdering.add(tabbedPane);
                    int idx = tabbedPane.getSelectedIndex();
                    if (idx == PullTransferPanel.TAB_POSITION) {
                        componentOrdering.addAll(gatherPanel.getTraversalOrdering());
                        nextPanel = PushTransferPanel.TAB_POSITION;
                    } else if (idx == PushTransferPanel.TAB_POSITION) {
                        componentOrdering.addAll(uploadPanel.getTraversalOrdering());
                        nextPanel = ExportPanel.TAB_POSITION;
                    } else if (idx == ExportPanel.TAB_POSITION) {
                        componentOrdering.addAll(exportPanel.getTraversalOrdering());
                        nextPanel = PullTransferPanel.TAB_POSITION;
                    }
                    componentOrdering.add(btnChoose);
                    boolean found = false;
                    for (int i = 0; i < componentOrdering.size() - 1; ++i) {
                        if (found || arg1 == componentOrdering.get(i)) {
                            found = true;
                            Component comp = componentOrdering.get(i + 1);
                            if (comp == tabbedPane) {
                                return comp;
                            }
                            if (comp.isVisible() && comp.isEnabled()
                                    && (!(comp instanceof JTextField) || ((JTextField) comp).isEditable())) {
                                return comp;
                            }
                        }
                    }
                    if (!found) {
                        return componentOrdering.get(0);
                    }
                    tabbedPane.setSelectedIndex(nextPanel);
                }
            }

            @Override
            public Component getComponentBefore(Container arg0, Component arg1) {
                ArrayList<Component> componentOrdering = new ArrayList<Component>();
                for (;;) {
                    int nextPanel = PullTransferPanel.TAB_POSITION;
                    componentOrdering.clear();
                    componentOrdering.add(txtBriefcaseDir);
                    componentOrdering.add(btnChoose);
                    componentOrdering.add(tabbedPane);
                    int idx = tabbedPane.getSelectedIndex();
                    if (idx == PullTransferPanel.TAB_POSITION) {
                        componentOrdering.addAll(gatherPanel.getTraversalOrdering());
                        nextPanel = ExportPanel.TAB_POSITION;
                    } else if (idx == PushTransferPanel.TAB_POSITION) {
                        componentOrdering.addAll(uploadPanel.getTraversalOrdering());
                        nextPanel = PullTransferPanel.TAB_POSITION;
                    } else if (idx == ExportPanel.TAB_POSITION) {
                        componentOrdering.addAll(exportPanel.getTraversalOrdering());
                        nextPanel = PushTransferPanel.TAB_POSITION;
                    }
                    componentOrdering.add(btnChoose);
                    boolean found = false;
                    for (int i = componentOrdering.size() - 1; i > 0; --i) {
                        if (found || arg1 == componentOrdering.get(i)) {
                            found = true;
                            Component comp = componentOrdering.get(i - 1);
                            if (comp == tabbedPane) {
                                return comp;
                            }
                            if (comp.isVisible() && comp.isEnabled()
                                    && (!(comp instanceof JTextField) || ((JTextField) comp).isEditable())) {
                                return comp;
                            }
                        }
                    }
                    if (!found) {
                        return componentOrdering.get(componentOrdering.size() - 1);
                    }
                    tabbedPane.setSelectedIndex(nextPanel);
                }
            }

            @Override
            public Component getDefaultComponent(Container arg0) {
                return btnChoose;
            }

            @Override
            public Component getFirstComponent(Container arg0) {
                return btnChoose;
            }

            @Override
            public Component getLastComponent(Container arg0) {
                return tabbedPane;
            }
        });
    }

    public void establishBriefcaseStorageLocation(boolean showDialog) {
        // set the enabled/disabled status of the panels based upon validity of default briefcase directory.
        String briefcaseDir = BriefcasePreferences.getBriefcaseDirectoryIfSet();
        boolean reset = false;
        if (briefcaseDir == null) {
            reset = true;
        } else {
            File dir = new File(briefcaseDir);
            if (!dir.exists() || !dir.isDirectory()) {
                reset = true;
            } else {
                File folder = FileSystemUtils.getBriefcaseFolder();
                if (!folder.exists() || !folder.isDirectory()) {
                    reset = true;
                }

            }
        }

        if (showDialog || reset) {
            // ask for new briefcase location...
            BriefcaseStorageLocationDialog fs = new BriefcaseStorageLocationDialog(MainBriefcaseWindow.this.frame);
            fs.setVisible(true);
            if (fs.isCancelled()) {
                // if we need to reset the briefcase location,
                // and have cancelled, then disable the UI.
                // otherwise the value we have is fine.
                setFullUIEnabled(!reset);
            } else {
                String briefcasePath = BriefcasePreferences.getBriefcaseDirectoryIfSet();
                if (briefcasePath == null) {
                    // we had a bad path -- disable all but Choose...
                    setFullUIEnabled(false);
                } else {
                    setFullUIEnabled(true);
                }
            }
        } else {
            File f = new File(BriefcasePreferences.getBriefcaseDirectoryProperty());
            if (BriefcaseFolderChooser.testAndMessageBadBriefcaseStorageLocationParentFolder(f, frame)) {
                try {
                    FileSystemUtils.assertBriefcaseStorageLocationParentFolder(f);
                    setFullUIEnabled(true);
                } catch (FileSystemException e1) {
                    e1.printStackTrace();
                    ODKOptionPane.showErrorDialog(frame, "Unable to create " + FileSystemUtils.BRIEFCASE_DIR,
                            "Failed to Create " + FileSystemUtils.BRIEFCASE_DIR);
                    // we had a bad path -- disable all but Choose...
                    setFullUIEnabled(false);
                }
            } else {
                // we had a bad path -- disable all but Choose...
                setFullUIEnabled(false);
            }
        }
    }

    @Override
    public void windowActivated(WindowEvent arg0) {
        // NO-OP
    }

    @Override
    public void windowClosed(WindowEvent arg0) {
        // NO-OP
    }

    @Override
    public void windowClosing(WindowEvent arg0) {
        exportTerminationFuture.markAsCancelled(new ExportAbortEvent("Main window closed"));
        transferTerminationFuture.markAsCancelled(new TransferAbortEvent("Main window closed"));
    }

    @Override
    public void windowDeactivated(WindowEvent arg0) {
        // NO-OP
    }

    @Override
    public void windowDeiconified(WindowEvent arg0) {
        // NO-OP
    }

    @Override
    public void windowIconified(WindowEvent arg0) {
        // NO-OP
    }

    @Override
    public void windowOpened(WindowEvent arg0) {
        establishBriefcaseStorageLocation(false);
    }

    static void showHelp(Options options) {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("java -jar briefcase.jar", options);
    }

    static void showVersion() {
        System.out.println("version: " + BriefcasePreferences.VERSION);
    }

    /**
     * Setting up options for Command Line Interface
     * @return
     */
    @SuppressWarnings("static-access")
    static Options addOptions() {
        Options options = new Options();

        Option server = OptionBuilder.withArgName("url").hasArg().withLongOpt(AGGREGATE_URL)
                .withDescription("ODK Aggregate URL (must start with http:// or https://)").create("url");

        Option username = OptionBuilder.withArgName("username").hasArg().withLongOpt(ODK_USERNAME)
                .withDescription("ODK username").create("u");

        Option password = OptionBuilder.withArgName("password").hasArg().withLongOpt(ODK_PASSWORD)
                .withDescription("ODK password").create("p");

        Option formid = OptionBuilder.withArgName("form_id").hasArg().withLongOpt(FORM_ID)
                .withDescription("Form ID of form to download and export").create("id");

        Option storageDir = OptionBuilder.withArgName("/path/to/dir").withLongOpt(STORAGE_DIRECTORY).hasArg()
                .withDescription(
                        "Directory to create or find ODK Briefcase Storage directory (relative path unless it begins with / or C:\\)")
                .create("sd");

        Option exportDir = OptionBuilder.withArgName("/path/to/dir").hasArg().withLongOpt(EXPORT_DIRECTORY)
                .withDescription(
                        "Directory to export the CSV and media files into (relative path unless it begins with / or C:\\)")
                .create("ed");

        Option exportMedia = OptionBuilder.withDescription("Flag to exclude media on export")
                .withLongOpt(EXCLUDE_MEDIA_EXPORT).create("em");

        Option startDate = OptionBuilder.withArgName(DATE_FORMAT).hasArg().withLongOpt(EXPORT_START_DATE)
                .withDescription("Include submission dates after (inclusive) this date in export to CSV")
                .create("start");

        Option endDate = OptionBuilder.withArgName(DATE_FORMAT).hasArg().withLongOpt(EXPORT_END_DATE)
                .withDescription("Include submission dates before (exclusive) this date in export to CSV")
                .create("end");

        Option exportFilename = OptionBuilder.withArgName("name.csv").hasArg().withLongOpt(EXPORT_FILENAME)
                .withDescription("File name for exported CSV").create("f");

        Option overwrite = OptionBuilder.withLongOpt(OVERWRITE_CSV_EXPORT)
                .withDescription("Flag to overwrite CSV on export").create("oc");

        Option help = OptionBuilder.withLongOpt(HELP).withDescription("Print help information (this screen)")
                .create("h");

        Option version = OptionBuilder.withLongOpt(VERSION).withDescription("Print version information")
                .create("v");

        Option odkDir = OptionBuilder.withArgName("/path/to/dir").hasArg().withLongOpt(ODK_DIR)
                .withDescription("/odk directory from ODK Collect (relative path unless it begins with / or C:\\)")
                .create("od");

        Option keyFile = OptionBuilder.withArgName("/path/to/file.pem").hasArg().withLongOpt(PEM_FILE)
                .withDescription("PEM private key file (relative path unless it begins with / or C:\\)")
                .create("pf");

        options.addOption(server);
        options.addOption(username);
        options.addOption(password);
        options.addOption(formid);
        options.addOption(storageDir);
        options.addOption(exportDir);
        options.addOption(exportMedia);
        options.addOption(startDate);
        options.addOption(endDate);
        options.addOption(exportFilename);
        options.addOption(overwrite);
        options.addOption(help);
        options.addOption(version);
        options.addOption(odkDir);
        options.addOption(keyFile);

        return options;
    }

    static boolean testDateFormat(String date) {
        try {
            DateFormat df = new SimpleDateFormat(DATE_FORMAT);
            df.parse(date);
        } catch (java.text.ParseException e) {
            return false;
        }
        return true;
    }
}