org.bbaw.pdr.ae.export.swt.FileSelectionGroup.java Source code

Java tutorial

Introduction

Here is the source code for org.bbaw.pdr.ae.export.swt.FileSelectionGroup.java

Source

/**
 * This file is part of Archiv-Editor.
 *
 * The software Archiv-Editor serves as a client user interface for working with
 * the Person Data Repository. See: pdr.bbaw.de
 *
 * The software Archiv-Editor was developed at the Berlin-Brandenburg Academy
 * of Sciences and Humanities, Jgerstr. 22/23, D-10117 Berlin.
 * www.bbaw.de
 *
 * Copyright (C) 2010-2013  Berlin-Brandenburg Academy
 * of Sciences and Humanities
 *
 * The software Archiv-Editor was developed by @author: Christoph Plutte.
 *
 * Archiv-Editor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Archiv-Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Archiv-Editor.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 */
package org.bbaw.pdr.ae.export.swt;

import java.io.File;

import org.bbaw.pdr.ae.common.AEConstants;
import org.bbaw.pdr.ae.common.CommonActivator;
import org.bbaw.pdr.ae.common.NLMessages;
import org.bbaw.pdr.ae.export.pluggable.AeExportCoreProvider;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;

//TODO: doku
/**
 * {@link AeExportCoreProvider#getFileHistory(String)}
 * {@link SWT#SAVE}
 * {@link #init(String, int)}
 * @author jhoeper
 *
 */
public class FileSelectionGroup extends ContentViewer implements IPdrWidgetStructure {
    /**
     * Opens a {@link FileDialog} for selection of a file from disk,
     * passes resulting file name to the corresponding {@link ComboViewer} and
     * asks {@link AeExportCoreProvider} to save it to the responsible file name history.  
     * @author jhoeper
     *
     */
    private class SelectButtonListener extends SelectionAdapter {
        @Override
        public void widgetSelected(SelectionEvent event) {
            String[] segments = getFileLocation();
            // create file browser dialog
            FileDialog dialog = new FileDialog(container.getShell(), selectMode);
            dialog.setFilterPath(segments[0]);
            dialog.setFileName(segments[1]);
            dialog.setText(NLMessages.getString("export.fileselector.browse.caption"));
            dialog.setFilterExtensions(provider.getPluginFiletypes(pluginId,
                    (selectMode == SWT.SAVE) ? AeExportCoreProvider.EXT_OUT : AeExportCoreProvider.EXT_IN,
                    configuration));
            // open dialog
            String dest = dialog.open();
            // process resulting file name
            if (dest != null) {
                // update filename combo
                filenameCombo.insert(dest, 0);
                filenameCombo.getCombo().select(0);
                // save file name to history of recent files
                provider.addToHistory(pluginId, configuration, dest);
            }
        }
    }

    private class FilenameChangedListener implements ISelectionChangedListener, ModifyListener {
        public FilenameChangedListener() {
            filenameCombo.addSelectionChangedListener(this);
            filenameCombo.getCombo().addModifyListener(this);
        }

        @Override
        public void selectionChanged(SelectionChangedEvent event) {
            //System.out.println("combo selection changed");
            //FIXME: viel zu frh! damit wird automatisch der wizard angesprochen, der dann rckmeldung
            // von allen seinen GUI-komponenten will, die aber noch gar nicht alle fertig sind!
            validate();
        }

        @Override
        public void modifyText(ModifyEvent event) {
            //System.out.println("output file string changed");
            validate();
        }
    }

    // logger
    ILog log = AEConstants.ILOGGER;

    private Group container;
    private Label dsclabel;
    private Button checkEnable;
    private ComboViewer filenameCombo;
    private Button browseButton;
    //TODO: 'overwrite existent files' check button?

    private String pluginId;
    // corresponds to input/output sets in extension point
    private String configuration;
    // whatever the plugin specifies as default file for a configuration
    private String default_filename;

    private AeExportCoreProvider provider;

    private WizardPage wizardPage;

    //   open/save to file
    private int selectMode;
    private boolean isvalid;

    private String message;

    //TODO doc wizard
    /**
     * <p>Creates a new group of widgets for file selection functionality.</p>
     * <p>The framing container will be a simple {@link Group} with a border
     * as defined by the SWT style {@link SWT#SHADOW_ETCHED_IN}. In order to
     * have all involved widgets set up, {@link #init(String, int, int)} has to
     * be called before use. If a custom SWT style is desired to have for
     * the widget group, the constructor {@link #FileSelectionGroup(Composite, 
     * int, String)} can be used alternatively. That one also allows for 
     * specifying the mode of file access this template is desired to provide
     * (<code>SWT.SAVE/SWT.OPEN</code>). The default is <code>SWT.OPEN</code>.
     * </p>
     * <p>Two arguments are required: the {@link Composite} in which the newly
     * created GUI elements are to be embedded, and the symbolic name of the
     * plugin contributing the export wizard which this file selection group
     * is for. The plugin name is neccessary for identifying the internal
     * file history, which unless further specification is the very one that is
     * made available by the core export plugin's {@link AeExportCoreProvider}
     * for every registered export plugin.</p>
     * <p>Export plugins that wish to use this GUI template can retrieve their
     * own plugin id by calling
     * <blockquote><code>FrameworkUtil.getBundle(getClass()).getSymbolicName();
     * </code></blockquote>
     * The plugin's standard file history can be and is in fact by this 
     * construtor obtained via {@link 
     * AeExportCoreProvider#getFileHistory(String)}.  
     * </p>
     * @param parent Composite into which the newly created group will be 
     * embedded
     * @param plugin The symbolic name of the plugin contributing the dialog 
     * that wishes to use this template 
     */
    public FileSelectionGroup(String plugin, WizardPage page) {
        this(plugin, page, SWT.BORDER | SWT.SHADOW_ETCHED_IN | SWT.OPEN);
    }

    //TODO doc wizard
    /**
     * <p>Creates a new group of widgets for file selection functionality.</p>
     * <p>Customizable version of {@link #FileSelectionGroup(Composite, String)}.
     * Both constructors do exactly the same. The main reason for this one to
     * exist is the extra information that the <code>style</code> argument can
     * deliver. If in the binary representation of <code>style</code>, the bit
     * for the style flag {@link SWT#CHECK} is set, the template will be 
     * equipped with an extra check box button for enabling/disabling
     * the entire ensemble. With the flag {@link SWT#SAVE} set, the template
     * will act as a selection tool for output files. Without, its default
     * behaviour as a widget ensemble for opening existent files is kept.</p>
     * <p>On further requirements, please read the base constructor {@link 
     * #FileSelectionGroup(Composite, String)}'s documentation.</p>
     * @param parent Composite into which the newly created group will be 
     * embedded
     * @param style Combination of desired SWT style flags by bitwise OR. If 
     * <code>style</code> contains the bit for {@link SWT#CHECK}, the template
     * will be equipped with an extra check box button for enabling/disabling
     * the entire ensemble.
     * @param plugin The symbolic name of the plugin contributing the dialog 
     * that wishes to use this template 
     */
    public FileSelectionGroup(String plugin, WizardPage page, Composite parent, int style) {
        container = new Group(parent, style | SWT.SHADOW_ETCHED_IN);
        container.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
        container.setLayout(new GridLayout(3, false));

        this.wizardPage = page;
        pluginId = plugin;
        selectMode = (style & SWT.SAVE) == SWT.SAVE ? SWT.SAVE : SWT.OPEN;
        configuration = AeExportCoreProvider.DEF_SET;
    }

    //TODO doc
    /**
     * Creates an instance for the given plugin and wizard page and applies 
     * style to it
     * @param plugin
     * @param page
     * @param style
     */
    public FileSelectionGroup(String plugin, WizardPage page, int style) {
        this(plugin, page, (Composite) page.getControl(), style);
    }

    /**
     * Initializes the embedded widgets serving as the components of this
     * file selector template.
     * <p>The arrangement of the template components will be more or less like 
     * this:
     * <blockquote>
     * <table style="border:1px solid;"><tr>
     * <td style="border:1px solid;">label</td>
     * <td style="border:1px solid;">combo viewer</td>
     * <td style="border:1px solid;">button</td></tr></table>
     * </blockquote>
     * </p>
     * @param label Descriptive text label
     * @param colspan Number of columns the template is desired to cover within
     * its parent's layout
     * @see #init(String, String, int)
     */
    public void init(String label, int colspan) {
        // initialize connection to central export functionalities
        provider = AeExportCoreProvider.getInstance();

        ((GridData) container.getLayoutData()).horizontalSpan = colspan;

        if ((container.getStyle() & SWT.CHECK) == SWT.CHECK) {
            checkEnable = new Button(container, SWT.CHECK);
            checkEnable.setText(label);
            //TODO: enabler listener
        } else {
            dsclabel = new Label(container, SWT.LEFT);
            dsclabel.setText(label);
        }

        filenameCombo = new ComboViewer(container, SWT.ALL);
        Combo combo = filenameCombo.getCombo();
        combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        combo.setEnabled(true);
        filenameCombo.setLabelProvider(new LabelProvider());
        filenameCombo.setContentProvider(ArrayContentProvider.getInstance());

        default_filename = provider.getWizardProvider(wizardPage.getWizard()).getDefaultFilename(configuration);
        log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
                " Default file name for configuration '" + configuration + "': " + default_filename));

        String[] recent = provider.getHistoryAsArray(pluginId, configuration);
        log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
                "  Recent file list for configuration '" + configuration + "': " + recent.length));
        // if history could be restored
        if (recent.length > 0)
            filenameCombo.setInput(recent);
        else if (default_filename != null)
            filenameCombo.setInput(new String[] { default_filename });
        if (combo.getItemCount() > 0)
            combo.setText(combo.getItem(0));
        //validate();
        new FilenameChangedListener();

        //TODO: listener on losing focus!!!

        browseButton = new Button(container, SWT.PUSH);
        browseButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        browseButton.setText(NLMessages.getString("export.fileselector.browse.button"));
        browseButton.addSelectionListener(new SelectButtonListener());
        // check if everything is correct.
        validate();
    }

    //TODO: doc
    public void init(String label, int colspan, String filetypeSetName) {
        this.configuration = filetypeSetName;
        init(label, colspan);
    }

    /**
     * Does exactly the same as {@link #init(String, int)}, but also sets
     * a text label for the widget group itself, meaning that there will be
     * an additional description displayed, most likely, but dependent on the
     * running platform's look and feel, being placed on the surrounding border. 
     * @param boxlabel Top-level description label for this widget group
     * @param label Descriptive label for the functionality this template 
     * provides
     * @param colspan Number of columns this template should cover in its 
     * parent's layout
     */
    public void init(String boxlabel, String label, int colspan) {
        container.setText(boxlabel);
        init(label, colspan);
    }

    //TODO:doc
    public void init(String boxlabel, String label, int colspan, String filetypeSetName) {
        this.configuration = filetypeSetName;

        init(boxlabel, label, colspan);
    }

    /**
     * Returns a <code>String[]</code> array of length 2, the elements of
     * which represent a directory path and a base filename.
     * <p>If the {@link ComboViewer} widget of this {@link FileSelectionGroup} 
     * currently contains a valid file system path, the text content of the
     * combo is segmented into an absolute directory and a relative file name.
     * TODO
     * If no directory segment can be derived from the combo's textual content,
     * but a valid file name is available, then the latter will be understood
     * relatively to the directory specified by {@link AEConstants#AE_HOME}</p> 
     * @return <code>String[]{directory, filename}</code>
     */
    public String[] getFileLocation() {

        File file = resolvPath();

        IPath path = new Path(file.getAbsolutePath());

        String filename = path.lastSegment();
        String dir = path.removeLastSegments(1).toOSString();

        /*      System.out.println("File location:");
              System.out.println(dir);
              System.out.println(filename);*/

        return new String[] { dir, filename };
    }

    /**
     * Return a resolved {@link File} representation of the widget's current 
     * content. If this {@link FileSelectionGroup} is configured as an output
     * file selector and points to an existing file, a dialog is raised
     * to let the user decide whether to overwrite the file. 
     * @return null if the file exists and the user doesn't agree in 
     * overwriting it, {@link File} otherwise
     */
    public File getFile() {

        File file = resolvPath();
        // raise dialog in case file is to be overwritten
        if (selectMode == SWT.SAVE)
            if (file.exists())
                return MessageDialog.openConfirm(container.getShell(),
                        NLMessages.getString("export.fileselector.file_exists.caption"),
                        file.getAbsolutePath() + NLMessages.getString("Export_Dialog_FileExistsQuestion")) ? file
                                : null;
        return file;
    }

    /**
     * determines whether the current widget content represents a file usable
     * as a input resource.
     */
    private void validateIn(File file) {
        if (file.isDirectory())
            setInvalid(NLMessages.getString("export.fileselector.no_filename")); // Export_Filename_ErrorNoName
        else if (!file.exists())
            setInvalid(NLMessages.getString("export.fileselector.no_file"));
        else if (!file.canRead())
            setInvalid(NLMessages.getString("export.fileselector.not_permitted"));
        else
            setValid();
    }

    /**
     * determines whether the current widget content can be used as an output
     * file locator
     */
    private void validateOut(File file) {
        //System.out.println("Validate output file location "+file.getPath());
        if (file.isDirectory()) {
            setInvalid(NLMessages.getString("export.fileselector.no_filename"));
        } else if (file.exists()) {
            if (!file.canWrite())
                setInvalid(NLMessages.getString("export.fileselector.not_permitted"));
            else
                setValid();
        } else if (!file.getParentFile().canWrite())
            setInvalid(NLMessages.getString("export.fileselector.not_permitted"));
        else
            setValid();
    }

    /**
     * Evaluates the current contents of the involved widgets and decides on
     * overall validity.
     */
    private void validate() {
        //System.out.println("validating input");

        File file = resolvPath();
        if (file == null) {
            setInvalid(NLMessages.getString("export.fileselector.invalid_filename"));
            return;
        }

        if (selectMode == SWT.OPEN)
            validateIn(file);
        else if (selectMode == SWT.SAVE)
            validateOut(file);

    }

    //TODO
    /**
     * Tries to resolve the user input to a valid, absolute file location
     * identifier which then is returned as a {@link File} object.
     * <p>This is really only about having a clean file identifier. There is no 
     * validation whatsoever involved in terms of read/write permissions, 
     * file/directory resource or filetype extension.</p>   
     * @return {@link File} object representing the resolved user input, 
     * {@link AEConstants#AE_HOME} if said input cannot be resolved
     */
    public File resolvPath() {
        IPath path = Path.EMPTY;
        String input = filenameCombo.getCombo().getText();
        //TODO: return directory? is that smart? given that this will be
        //forwarded to method that rely on getting a proper file?
        /*if (!path.isValidPath(input)) 
           return new File(AEConstants.AE_HOME + AEConstants.FS);*/
        path = new Path(input);
        if (!path.isAbsolute()) {
            path = new Path(AEConstants.AE_HOME).append(path);
            path.makeAbsolute();
        }
        return new File(path.toOSString());
    }

    /**
     * Indicates whether or not this widget ensemble is in an overall state
     * that allows for i.e. involved wizard dialogs to continue. 
     * @return <code>true</code> if the widget group's contents are legal
     */
    @Override
    public boolean isValid() {
        //System.out.println(this.getClass().getCanonicalName()+(isvalid ? " is valid." : " is not valid"));
        return isvalid;
    }

    /**
     * Mark this widget valid. Also make wizardcontainer update its controls, which
     * means that the responsible wizard is made to reevaluate its 
     * {@link IWizard#canFinish()}, which can be overwritten in order to consider
     * the {@link IPdrWidgetStructure#isValid()} status of further widgets involved. 
     */
    private void setValid() {
        isvalid = true;
        this.message = null;
        IWizardContainer wizardContainer = this.wizardPage.getWizard().getContainer();
        try {
            wizardContainer.updateButtons();
            wizardContainer.updateMessage();
        } catch (Exception e) {
            log.log(new Status(IStatus.WARNING, CommonActivator.PLUGIN_ID,
                    " Wizard noch nicht bereit? " + wizardPage.getClass().getCanonicalName()));
        }
    }

    /**
     * Mark this widget invalid.
     * @param message Complementary error message.
     */
    private void setInvalid(String message) {
        //System.out.println("Mark file selector as invalid");
        isvalid = false;
        this.message = message;
        IWizardContainer wizardContainer = this.wizardPage.getWizard().getContainer();
        wizardContainer.updateButtons();
        wizardContainer.updateMessage();
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public Control getControl() {
        if (filenameCombo != null)
            return filenameCombo.getControl();
        else
            return null;
    }

    @Override
    public ISelection getSelection() {
        if (filenameCombo != null)
            return new StructuredSelection(filenameCombo.getCombo().getText());
        return null;
    }

    @Override
    public void refresh() {
        // TODO Auto-generated method stub

    }

    @Override
    public void setSelection(ISelection selection, boolean reveal) {
        // TODO Auto-generated method stub

    }

    @Override
    public void performFinish() {
        if (filenameCombo != null) {
            provider.addToHistory(pluginId, configuration, resolvPath().getAbsolutePath());
            String[] locators = getFileLocation();
            provider.getSettings(pluginId).put(AeExportCoreProvider.DEF_DIR, locators[0]);
            provider.getSettings(pluginId).put(AeExportCoreProvider.DEF_FILE, locators[1]);
        }
    }
}