com.bdaum.zoom.ui.internal.wizards.ImportFileSelectionPage.java Source code

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.ui.internal.wizards.ImportFileSelectionPage.java

Source

/*
 * This file is part of the ZoRa project: http://www.photozora.org.
 *
 * ZoRa is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * ZoRa is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZoRa; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * (c) 2009 Berthold Daum  
 */
package com.bdaum.zoom.ui.internal.wizards;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
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.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.bdaum.zoom.cat.model.asset.Asset;
import com.bdaum.zoom.cat.model.asset.AssetImpl;
import com.bdaum.zoom.cat.model.meta.LastDeviceImport;
import com.bdaum.zoom.cat.model.meta.LastDeviceImportImpl;
import com.bdaum.zoom.cat.model.meta.Meta;
import com.bdaum.zoom.core.Constants;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.db.IDbManager;
import com.bdaum.zoom.core.internal.CoreActivator;
import com.bdaum.zoom.core.internal.FileInput;
import com.bdaum.zoom.core.internal.FileNameExtensionFilter;
import com.bdaum.zoom.core.internal.IMediaSupport;
import com.bdaum.zoom.core.internal.ImportFromDeviceData;
import com.bdaum.zoom.css.ZColumnLabelProvider;
import com.bdaum.zoom.image.ImageConstants;
import com.bdaum.zoom.mtp.ObjectFilter;
import com.bdaum.zoom.mtp.StorageObject;
import com.bdaum.zoom.ui.dialogs.AcousticMessageDialog;
import com.bdaum.zoom.ui.dialogs.ZProgressMonitorDialog;
import com.bdaum.zoom.ui.dialogs.ZTitleAreaDialog;
import com.bdaum.zoom.ui.internal.HelpContextIds;
import com.bdaum.zoom.ui.internal.Icons;
import com.bdaum.zoom.ui.internal.UiActivator;
import com.bdaum.zoom.ui.internal.UiUtilities;
import com.bdaum.zoom.ui.internal.dialogs.AllNoneGroup;
import com.bdaum.zoom.ui.internal.dialogs.MediaDialog;
import com.bdaum.zoom.ui.internal.widgets.CheckboxButton;
import com.bdaum.zoom.ui.internal.widgets.ExpandCollapseGroup;
import com.bdaum.zoom.ui.internal.widgets.WidgetFactory;
import com.bdaum.zoom.ui.wizards.ColoredWizardPage;

@SuppressWarnings("restriction")
public class ImportFileSelectionPage extends ColoredWizardPage {

    public class Digest {

        private String originalFileName;
        private Date dateTimeOriginal;

        public Digest(String originalFileName, Date dateTimeOriginal) {
            this.originalFileName = originalFileName;
            this.dateTimeOriginal = dateTimeOriginal;
        }

        @Override
        public int hashCode() {
            if (dateTimeOriginal == null)
                return originalFileName.hashCode();
            return 31 * originalFileName.hashCode() * dateTimeOriginal.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Digest) {
                if (dateTimeOriginal == null)
                    return originalFileName.equals(((Digest) obj).originalFileName);
                return originalFileName.equals(((Digest) obj).originalFileName)
                        && dateTimeOriginal.equals(((Digest) obj).dateTimeOriginal);
            }
            return false;
        }

        public String getOriginalFileName() {
            return originalFileName;
        }

        public void setOriginalFileName(String originalFileName) {
            this.originalFileName = originalFileName;
        }

        public Date getDateTimeOriginal() {
            return dateTimeOriginal;
        }

        public void setDateTimeOriginal(Date dateTimeOriginal) {
            this.dateTimeOriginal = dateTimeOriginal;
        }

    }

    public static final String[] SKIPPOLICIES = new String[] { Messages.ImportFileSelectionPage_none,
            Messages.ImportFileSelectionPage_all_raw, Messages.ImportFileSelectionPage_raw_if_jpeg,
            Messages.ImportFileSelectionPage_all_jpeg, Messages.ImportFileSelectionPage_jpeg_if_raw };

    public static class SkippedFormatsDialog extends ZTitleAreaDialog implements SelectionListener {

        private static final int SELECTALL = 9999;
        private static final int SELECTNONE = 9998;
        private final Set<String> skippedFormats;
        private HashSet<String> result;
        private Map<String, Button> buttonMap = new HashMap<String, Button>(90);
        private int skipCount;

        public SkippedFormatsDialog(Shell parentShell, Set<String> skippedFormats) {
            super(parentShell);
            this.skippedFormats = skippedFormats;
            skipCount = skippedFormats.size();
        }

        @Override
        public void create() {
            super.create();
            setTitle(Messages.ImportFileSelectionPage_skipped_files);
            setMessage();
            updateButtons();
        }

        @Override
        protected Control createDialogArea(Composite parent) {
            Composite area = (Composite) super.createDialogArea(parent);
            Composite composite = new Composite(area, SWT.NONE);
            composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
            composite.setLayout(new GridLayout(6, true));
            Set<String> formats = ImageConstants.getAllFormats();
            String[] allFormats = formats.toArray(new String[formats.size()]);
            Arrays.sort(allFormats);
            for (String format : allFormats) {
                Composite checkGroup = new Composite(composite, SWT.NONE);
                GridLayout layout = new GridLayout(2, false);
                layout.marginWidth = 2;
                layout.marginHeight = 2;
                checkGroup.setLayout(layout);
                Button button = new Button(checkGroup, SWT.CHECK);
                button.addSelectionListener(this);
                button.setSelection(skippedFormats.contains(format));
                buttonMap.put(format, button);
                new Label(checkGroup, SWT.NONE)
                        .setText(format.isEmpty() ? Messages.ImportFileSelectionPage_no_ext : format);
            }
            return area;
        }

        @Override
        protected void createButtonsForButtonBar(Composite parent) {
            createButton(parent, SELECTALL, Messages.ImportFileSelectionPage_select_all, false);
            createButton(parent, SELECTNONE, Messages.ImportFileSelectionPage_select_none, false);
            super.createButtonsForButtonBar(parent);
        }

        @Override
        protected void buttonPressed(int buttonId) {
            switch (buttonId) {
            case SELECTALL:
                for (Button button : buttonMap.values())
                    button.setSelection(true);
                updateButtons();
                break;
            case SELECTNONE:
                for (Button button : buttonMap.values())
                    button.setSelection(false);
                updateButtons();
                break;
            }
            super.buttonPressed(buttonId);
        }

        protected void updateButtons() {
            getButton(OK).setEnabled(validate());
        }

        private boolean validate() {
            skipCount = 0;
            for (Button button : buttonMap.values())
                if (button.getSelection())
                    ++skipCount;
            if (skipCount == buttonMap.size()) {
                setErrorMessage(Messages.ImportFileSelectionPage_nothing_to_import);
                getShell().pack();
                return false;
            }
            setErrorMessage(null);
            setMessage();
            getShell().pack();
            return true;
        }

        private void setMessage() {
            setMessage(Messages.ImportFileSelectionPage_Mark_extensions + "\n" //$NON-NLS-1$
                    + (skipCount == 0 ? Messages.ImportFileSelectionPage_no_format_selected
                            : skipCount == 1 ? Messages.ImportFileSelectionPage_one_format_selected
                                    : NLS.bind(Messages.ImportFileSelectionPage_n_formats_selected, skipCount)));
        }

        @Override
        protected void okPressed() {
            result = new HashSet<String>();
            for (Map.Entry<String, Button> entry : buttonMap.entrySet())
                if (entry.getValue().getSelection())
                    result.add(entry.getKey());
            super.okPressed();
        }

        public HashSet<String> getResult() {
            return result;
        }

        public void widgetSelected(SelectionEvent e) {
            updateButtons();
        }

        public void widgetDefaultSelected(SelectionEvent e) {
            // do nothing
        }

    }

    private static final class DcimLabelProvider extends ZColumnLabelProvider {

        @Override
        public String getText(Object element) {
            return element.toString();
        }

        @Override
        public Image getImage(Object element) {
            if ((element instanceof ImportNode))
                switch (((ImportNode) element).type) {
                case Calendar.YEAR:
                case Calendar.MONTH:
                case Calendar.DAY_OF_MONTH:
                case FILE:
                    return null;
                case NODATES:
                    return Icons.images.getImage();
                default:
                    return Icons.folder.getImage();
                }
            return null;
        }
    }

    private final static class DcimContentProvider implements ITreeContentProvider {

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // do nothing
        }

        public void dispose() {
            // do nothing
        }

        public Object[] getElements(Object inputElement) {
            return (inputElement instanceof ImportNode[]) ? (ImportNode[]) inputElement : EMPTY;
        }

        public boolean hasChildren(Object element) {
            return (element instanceof ImportNode) && !((ImportNode) element).children.isEmpty();
        }

        public Object getParent(Object element) {
            return (element instanceof ImportNode) ? ((ImportNode) element).parent : null;
        }

        public Object[] getChildren(Object parentElement) {
            return (parentElement instanceof ImportNode) ? ((ImportNode) parentElement).children.toArray() : EMPTY;
        }
    }

    private class ImportNode {
        String plural;
        String singular;
        List<ImportNode> children = new ArrayList<ImportNode>();
        ImportNode parent;
        String label;
        int type;
        int value;
        private int count;
        long minTime;
        long maxTime;
        List<StorageObject> missing;
        StorageObject[] memberFiles;

        public ImportNode(ImportNode parent, String label, int type, int value, int count, String plural,
                String singular) {
            this.parent = parent;
            this.label = label;
            this.type = type;
            this.value = value;
            this.count = count;
            this.plural = plural;
            this.singular = singular;
            ++nodeCount;
        }

        public String toString() {
            if (type == FILE)
                return label;
            return label
                    + NLS.bind(Messages.ImportFromDeviceWizard_n_images, count, count == 1 ? singular : plural);
        }

        public void add(ImportNode child) {
            children.add(child);
        }

        public StorageObject[] getMissing() {
            return missing == null ? EMPTYFILES : missing.toArray(new StorageObject[missing.size()]);
        }

        public String getPath() {
            if (memberFiles == null || memberFiles.length == 0)
                return ""; //$NON-NLS-1$
            return memberFiles[0].getAbsolutePath();
        }

    }

    private static final Object[] EMPTY = new Object[0];
    private static final StorageObject[] EMPTYFILES = new StorageObject[0];
    private static final String DETECTDUPLICATES = "detectDuplicates"; //$NON-NLS-1$
    private static final String REMOVEMEDIA = "removeMedia"; //$NON-NLS-1$
    private static final String SKIPPOLICY = "skipPolicy"; //$NON-NLS-1$
    private static final String SKIPPEDFORMATS = "skippedFormats"; //$NON-NLS-1$
    private static final SimpleDateFormat sdf = new SimpleDateFormat(
            Messages.ImportFileSelectionPage_import_date_mask);
    private static final int NODATES = 1000;
    private static final int FILE = 999;
    private CheckboxTreeViewer importViewer;
    private GregorianCalendar calendar = new GregorianCalendar();
    private long lastImportTimestamp = -1L;
    private String lastImportPath = ""; //$NON-NLS-1$
    private boolean removeMedia;
    private CheckboxButton duplicatesButton;
    private CheckboxButton removeMediaButton;
    private final boolean media;
    private Combo skipCombo;
    private Text skipFormatsField;
    private Set<String> skippedFormats;
    private Button lastImportButton;
    private Button manageMediaButton;
    private Label volumeLabel;
    private final boolean eject;
    private Button diffButton;
    private ProgressBar progressbar;
    private int nodeCount = 0;
    private ImportNode[] rootElements;
    private int progress;
    protected IProgressMonitor monitor;
    private List<StorageObject> selectedFiles;
    private Label groupedByLabel;
    private boolean byDate = false;
    private boolean byFilename = false;
    private Boolean presetDuplicates;
    private Boolean presetRemoveMedia;
    private Integer presetSkipPolicy;
    private String presetFormats;

    public ImportFileSelectionPage(String pageName, boolean media, boolean eject) {
        super(pageName);
        this.media = media;
        this.eject = eject;
    }

    @SuppressWarnings("unused")
    @Override
    public void createControl(Composite parent) {
        Composite comp = createComposite(parent, 1);
        final Composite viewerComp = new Composite(comp, SWT.NONE);
        viewerComp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        viewerComp.setLayout(new GridLayout());
        if (media) {
            volumeLabel = new Label(viewerComp, SWT.NONE);
            volumeLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            volumeLabel.setFont(JFaceResources.getBannerFont());
        }
        groupedByLabel = new Label(viewerComp, SWT.NONE);
        groupedByLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        Composite innerComp = new Composite(viewerComp, SWT.NONE);
        innerComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        innerComp.setLayout(new GridLayout(2, false));
        ExpandCollapseGroup expandCollapseGroup = new ExpandCollapseGroup(innerComp, SWT.NONE);
        new Label(innerComp, SWT.NONE);
        importViewer = new CheckboxTreeViewer(innerComp, SWT.BORDER | SWT.V_SCROLL);
        expandCollapseGroup.setViewer(importViewer);
        GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
        layoutData.heightHint = 200;
        importViewer.getControl().setLayoutData(layoutData);
        importViewer.setContentProvider(new DcimContentProvider());
        importViewer.setLabelProvider(new DcimLabelProvider());
        importViewer.addCheckStateListener(new ICheckStateListener() {
            public void checkStateChanged(CheckStateChangedEvent event) {
                selectedFiles = null;
                checkHierarchy(event.getElement(), event.getChecked(), false);
                validatePage();
                if (lastImportButton != null)
                    lastImportButton.setEnabled(lastImportTimestamp >= 0 || !lastImportPath.isEmpty());
            }
        });
        UiUtilities.installDoubleClickExpansion(importViewer);
        AllNoneGroup selectComp = new AllNoneGroup(innerComp, new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (e.widget.getData() == AllNoneGroup.ALL) {
                    ImportNode[] rootElements = (ImportNode[]) importViewer.getInput();
                    importViewer.setCheckedElements(rootElements);
                    for (ImportNode node : rootElements)
                        checkHierarchy(node, true, false);
                } else
                    importViewer.setCheckedElements(new ImportNode[0]);
                validatePage();
                if (lastImportButton != null)
                    lastImportButton.setEnabled(lastImportTimestamp >= 0 || !lastImportPath.isEmpty());
            }
        });
        if (media) {
            StorageObject[] dcims = ((ImportFromDeviceWizard) getWizard()).getDcims();
            lastImportButton = WidgetFactory.createPushButton(selectComp,
                    Messages.ImportFromDeviceWizard_all_since_last_import, SWT.BEGINNING);
            diffButton = WidgetFactory.createPushButton(selectComp, Messages.ImportFileSelectionPage_Difference,
                    SWT.BEGINNING);
            diffButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    progressbar.setVisible(true);
                    calculateDiff();
                    progressbar.setVisible(false);
                }
            });
            Label label = new Label(selectComp, SWT.SEPARATOR | SWT.HORIZONTAL);
            label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            manageMediaButton = WidgetFactory.createPushButton(selectComp,
                    Messages.ImportFileSelectionPage_manage_media, SWT.BEGINNING);
            manageMediaButton.setToolTipText(Messages.ImportFileSelectionPage_manage_media_tooltip);
            final Meta meta = Core.getCore().getDbManager().getMeta(true);
            String key = null;
            LastDeviceImport lastImport = null;
            if (dcims.length > 0) {
                lastImportButton.setToolTipText(Messages.ImportFileSelectionPage_name_each_volume_differently);
                key = dcims[0].getMedium();
                lastImport = meta.getLastDeviceImport(key);
                if (lastImport != null) {
                    lastImportTimestamp = lastImport.getTimestamp();
                    if (lastImport.getPath() != null)
                        lastImportPath = lastImport.getPath();
                } else if (!key.isEmpty())
                    editMedia(meta, key, false);
                else
                    AcousticMessageDialog.openInformation(getShell(),
                            Messages.ImportFileSelectionPage_import_from_device,
                            Messages.ImportFileSelectionPage_assign_volume_name);
                updateVolumeLabel(key);
            }
            if (lastImportTimestamp >= 0 || !lastImportPath.isEmpty())
                lastImportButton.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        if (lastImportTimestamp >= 0 || !lastImportPath.isEmpty()) {
                            markSinceLastImport();
                            validatePage();
                        }
                    }
                });
            else
                lastImportButton.setEnabled(false);
            if (meta.getLastDeviceImport() != null && !meta.getLastDeviceImport().isEmpty() || key != null) {
                final String k = key;
                manageMediaButton.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        editMedia(meta, k, true);
                    }
                });
            } else
                manageMediaButton.setEnabled(false);
        }
        duplicatesButton = WidgetFactory.createCheckButton(innerComp,
                Messages.ImportFromDeviceWizard_dont_import_dupes,
                new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 2, 1));
        Composite skipComp = new Composite(comp, SWT.NONE);
        skipComp.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
        Label label = new Label(skipComp, SWT.NONE);
        label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        label.setText(Messages.ImportFileSelectionPage_skipped_file_types);
        if (media) {
            skipComp.setLayout(new GridLayout(2, false));
            skipCombo = new Combo(skipComp, SWT.READ_ONLY);
            skipCombo.setItems(SKIPPOLICIES);
            skipCombo.setVisibleItemCount(5);
        } else {
            skipComp.setLayout(new GridLayout(3, false));
            skipFormatsField = new Text(skipComp, SWT.BORDER | SWT.READ_ONLY | SWT.SINGLE);
            skipFormatsField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            Button skipFormatButton = new Button(skipComp, SWT.PUSH);
            skipFormatButton.setText("..."); //$NON-NLS-1$
            skipFormatButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    SkippedFormatsDialog dialog = new SkippedFormatsDialog(getShell(), skippedFormats);
                    if (dialog.open() == Dialog.OK) {
                        skippedFormats = dialog.getResult();
                        skipFormatsField.setText(Core.toStringList(skippedFormats.toArray(), ";")); //$NON-NLS-1$
                    }
                    super.widgetSelected(e);
                }
            });
        }
        if (eject)
            removeMediaButton = WidgetFactory.createCheckButton(comp, Messages.ImportFromDeviceWizard_remove_media,
                    null);
        if (media) {
            progressbar = new ProgressBar(comp, SWT.HORIZONTAL);
            progressbar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            progressbar.setVisible(false);
        }

        setControl(comp);
        setHelp(media ? HelpContextIds.IMPORT_FROM_DEVICE_WIZARD_FILE_SELECTION
                : HelpContextIds.IMPORT_NEW_STRUCTURE_WIZARD_FILE_SELECTION);
        setTitle(Messages.ImportFileSelectionPage_file_selection);
        setMessage(Messages.ImportFileSelectionPage_select_the_image_files);
        super.createControl(parent);
        try {
            fillValues();
            validatePage();
        } catch (InvocationTargetException e1) {
            UiActivator.getDefault().logError(Messages.ImportFileSelectionPage_internal_error, e1);
        } catch (InterruptedException e1) {
            if (e1.getMessage() != null && !e1.getMessage().isEmpty())
                parent.getDisplay().syncExec(() -> AcousticMessageDialog.openError(getShell(),
                        Messages.ImportFileSelectionPage_import_from_device, e1.getMessage()));
            ((WizardDialog) getWizard().getContainer()).close();
        }
    }

    protected void editMedia(Meta meta, String key, boolean all) {
        List<LastDeviceImport> mediaList = new ArrayList<LastDeviceImport>();
        if (all && meta.getLastDeviceImport() != null)
            for (LastDeviceImport imp : meta.getLastDeviceImport().values()) {
                LastDeviceImportImpl last = new LastDeviceImportImpl(imp.getVolume(), imp.getTimestamp(),
                        imp.getDescription(), imp.getOwner(), imp.getPath(), imp.getDetectDuplicates(),
                        imp.getRemoveMedia(), imp.getSkipPolicy(), imp.getTargetDir(), imp.getSubfolders(),
                        imp.getDeepSubfolders(), imp.getSelectedTemplate(), imp.getCue(), imp.getPrefix(),
                        imp.getPrivacy());
                last.setSkippedFormats(imp.getSkippedFormats());
                last.setKeywords(imp.getKeywords());
                mediaList.add(last);
            }
        if (key != null && (meta.getLastDeviceImport() == null || !meta.getLastDeviceImport().containsKey(key)))
            mediaList.add(new LastDeviceImportImpl(key, 0L, null, null, null, null, null, null, null, null, null,
                    null, null, null, null));
        MediaDialog dialog = new MediaDialog(getContainer().getShell(), mediaList, key, !all);
        if (dialog.open() == MediaDialog.OK) {
            updatePage();
            updateVolumeLabel(key);
            ((ImportFromDeviceWizard) getWizard()).updateValues();
        }
    }

    protected void calculateDiff() {
        IDbManager dbManager = Core.getCore().getDbManager();
        List<AssetImpl> assets = dbManager.obtainAssets();
        int size = assets.size();
        int increment = nodeCount == 0 ? 1 : size / nodeCount;
        int work = 3 * nodeCount;
        progress = 0;
        progressbar.setMaximum(work);
        Set<Digest> cataloged = new HashSet<>(size * 3 / 2);
        int k = 0;
        for (Asset asset : assets) {
            if (asset.getOriginalFileName() != null && asset.getDateTime() != null)
                cataloged.add(new Digest(asset.getOriginalFileName(), asset.getDateTime()));
            if (++k % increment == 0)
                progressbar.setSelection(++progress);
        }
        Digest cand = new Digest(null, null);
        calculateDiff(rootElements, cand, cataloged);
        visualizeDiff(rootElements, importViewer);
    }

    private void visualizeDiff(ImportNode[] nodes, CheckboxTreeViewer viewer) {
        for (ImportNode node : nodes) {
            if (node.type == Calendar.DAY_OF_MONTH) {
                StorageObject[] memberFiles = node.memberFiles;
                if (memberFiles != null) {
                    int mCount = node.missing == null ? 0 : node.missing.size();
                    if (mCount == 0)
                        viewer.setGrayChecked(node, false);
                    else if (mCount != memberFiles.length)
                        viewer.setGrayChecked(node, true);
                    else {
                        viewer.setGrayed(node, false);
                        viewer.setChecked(node, true);
                    }
                }
            } else if (node.type == FILE) {
                int mCount = node.missing == null ? 0 : node.missing.size();
                viewer.setChecked(node, mCount != 1);
            } else if (node.children != null && !node.children.isEmpty()) {
                ImportNode[] children = node.children.toArray(new ImportNode[node.children.size()]);
                visualizeDiff(children, viewer);
                int checked = 0;
                int grayed = 0;
                for (ImportNode child : children)
                    if (viewer.getChecked(child)) {
                        ++checked;
                        if (viewer.getGrayed(child))
                            ++grayed;
                    }
                if (checked == 0)
                    viewer.setGrayChecked(node, false);
                else if (checked < children.length || grayed != 0)
                    viewer.setGrayChecked(node, true);
                else {
                    viewer.setChecked(node, true);
                    viewer.setGrayed(node, false);
                }
            }
            progressbar.setSelection(++progress);
        }
    }

    private void calculateDiff(ImportNode[] nodes, Digest cand, Set<Digest> cataloged) {
        for (ImportNode node : nodes) {
            if (node.type == Calendar.DAY_OF_MONTH) {
                StorageObject[] memberFiles = node.memberFiles;
                if (memberFiles != null) {
                    if (node.missing != null)
                        node.missing.clear();
                    for (StorageObject file : memberFiles) {
                        cand.setOriginalFileName(computeRelativePath(file));
                        cand.setDateTimeOriginal(new Date(file.lastModified()));
                        if (!cataloged.contains(cand)) {
                            if (node.missing == null)
                                node.missing = new ArrayList<>();
                            node.missing.add(file);
                        }
                    }
                }
            } else if (node.type == FILE) {
                if (node.missing != null)
                    node.missing.clear();
                StorageObject file = node.memberFiles[0];
                cand.setOriginalFileName(computeRelativePath(file));
                cand.setDateTimeOriginal(null);
                if (!cataloged.contains(cand)) {
                    if (node.missing == null)
                        node.missing = new ArrayList<>();
                    node.missing.add(file);
                }
            } else if (node.children != null && !node.children.isEmpty())
                calculateDiff(node.children.toArray(new ImportNode[node.children.size()]), cand, cataloged);
            progressbar.setSelection(++progress);
        }
    }

    private static String computeRelativePath(StorageObject file) {
        String path = file.getAbsolutePath();
        int p = path.indexOf("DCIM/"); //$NON-NLS-1$
        if (p >= 0)
            return path.substring(p + 5);
        return path;
    }

    private void updateVolumeLabel(String key) {
        if (key != null && volumeLabel != null) {
            String descr = null;
            StringBuilder sb = new StringBuilder();
            sb.append(Messages.ImportFileSelectionPage_medium).append(key);
            LastDeviceImport imp = Core.getCore().getDbManager().getMeta(true).getLastDeviceImport(key);
            if (imp != null) {
                String owner = imp.getOwner();
                if (owner != null && !owner.isEmpty())
                    sb.append(", ").append( //$NON-NLS-1$
                            NLS.bind(Messages.ImportFileSelectionPage_owner, owner));
                long timestamp = imp.getTimestamp();
                if (timestamp > 0)
                    sb.append(", ") //$NON-NLS-1$
                            .append(NLS.bind(Messages.ImportFileSelectionPage_last_import,
                                    sdf.format(new Date(timestamp))));
                descr = imp.getDescription();
            }
            volumeLabel.setText(sb.toString());
            volumeLabel.setToolTipText(descr == null ? "" : descr); //$NON-NLS-1$
        }
    }

    protected void updatePage() {
        if (lastImportButton != null)
            lastImportButton.setEnabled(true);
    }

    private void markSinceLastImport() {
        ImportNode firstNode = null;
        Object[] checkedElements = importViewer.getCheckedElements();
        for (Object element : checkedElements)
            importViewer.setChecked(element, false);
        List<ImportNode> selectedElements = new ArrayList<ImportNode>(100);
        for (ImportNode rootNode : (ImportNode[]) importViewer.getInput()) {
            ImportNode cand = enableAfterTimestamp(rootNode, importViewer, selectedElements);
            if (cand != null && firstNode == null)
                firstNode = cand;
        }
        importViewer.setCheckedElements(selectedElements.toArray());
        for (ImportNode node : selectedElements) {
            if (firstNode == null)
                firstNode = node;
            checkHierarchy(node, true, false);
        }
        if (firstNode != null)
            importViewer.reveal(firstNode);
        lastImportButton.setEnabled(false);
    }

    private ImportNode enableAfterTimestamp(ImportNode node, CheckboxTreeViewer viewer,
            List<ImportNode> selectedElements) {
        switch (node.type) {
        case Calendar.YEAR:
            if (lastImportTimestamp >= 0 && lastImportTimestamp < node.minTime) {
                selectedElements.add(node);
                break;
            }
            if (lastImportTimestamp >= 0 && lastImportTimestamp <= node.maxTime)
                return enableChildren(node, viewer, selectedElements);
            break;
        case Calendar.MONTH:
            if (lastImportTimestamp >= 0 && lastImportTimestamp < node.minTime) {
                selectedElements.add(node);
                break;
            }
            if (lastImportTimestamp >= 0 && lastImportTimestamp <= node.maxTime)
                return enableChildren(node, viewer, selectedElements);
            break;
        case Calendar.DAY_OF_MONTH:
            if (lastImportTimestamp >= 0 && lastImportTimestamp <= node.maxTime) {
                selectedElements.add(node);
                return node;
            }
            break;
        case NODATES:
            return enableChildren(node, viewer, selectedElements);
        case FILE:
            if (!lastImportPath.isEmpty() && lastImportPath.compareTo(node.getPath()) < 0) {
                selectedElements.add(node);
                return node;
            }
            break;
        default:
            enableChildren(node, viewer, selectedElements);
            break;
        }
        return null;
    }

    private ImportNode enableChildren(ImportNode node, CheckboxTreeViewer viewer,
            List<ImportNode> selectedElements) {
        ImportNode firstNode = null;
        for (Object child : ((ITreeContentProvider) viewer.getContentProvider()).getChildren(node)) {
            ImportNode cand = enableAfterTimestamp((ImportNode) child, viewer, selectedElements);
            if (cand != null && firstNode == null)
                firstNode = cand;
        }
        return firstNode;
    }

    protected boolean needsAdvancedOptions() {
        if (media) {
            int index = skipCombo.getSelectionIndex();
            return index != 1 && index != 2;
        }
        return false;
    }

    void checkHierarchy(Object element, boolean checked, boolean gray) {
        BusyIndicator.showWhile(importViewer.getControl().getDisplay(), () -> {
            doCheckHierarchy(element, checked, gray, true);
        });
    }

    private void doCheckHierarchy(Object element, boolean checked, boolean gray, boolean force) {
        boolean changed = importViewer.getChecked(element) != checked || importViewer.getGrayed(element) != gray;
        importViewer.setGrayed(element, gray);
        importViewer.setChecked(element, checked);
        if (element instanceof ImportNode) {
            ImportNode node = (ImportNode) element;
            if (!gray && changed || force) {
                node.missing = null;
                for (ImportNode child : node.children)
                    doCheckHierarchy(child, checked, gray, false);
            }
            ImportNode parent = node.parent;
            while (parent != null) {
                boolean allChecked = true;
                boolean someChecked = false;
                for (ImportNode member : parent.children)
                    if (importViewer.getGrayed(member)) {
                        allChecked = false;
                        someChecked = true;
                    } else {
                        boolean childChecked = importViewer.getChecked(member);
                        allChecked &= childChecked;
                        someChecked |= childChecked;
                    }
                if (allChecked) {
                    importViewer.setGrayed(parent, false);
                    importViewer.setChecked(parent, true);
                } else
                    importViewer.setGrayChecked(parent, someChecked);
                parent = parent.parent;
            }
        }
    }

    private void fillValues() throws InvocationTargetException, InterruptedException {
        ImportFromDeviceWizard wizard = (ImportFromDeviceWizard) getWizard();
        IDialogSettings dialogSettings = wizard.getDialogSettings();
        rootElements = createInput(wizard.getFiles(), wizard.getDcims());
        importViewer.setInput(rootElements);
        importViewer.expandAll();
        if (lastImportButton != null && lastImportButton.isEnabled()
                && (lastImportTimestamp >= 0 || !lastImportPath.isEmpty()))
            markSinceLastImport();
        else
            for (ImportNode node : rootElements)
                importViewer.setSubtreeChecked(node, true);
        duplicatesButton.setSelection(presetDuplicates = dialogSettings.getBoolean(DETECTDUPLICATES));
        removeMedia = dialogSettings.getBoolean(REMOVEMEDIA);
        if (removeMediaButton != null)
            removeMediaButton.setSelection(presetRemoveMedia = removeMedia);
        if (skipCombo != null)
            try {
                skipCombo.select(presetSkipPolicy = dialogSettings.getInt(SKIPPOLICY));
            } catch (NumberFormatException e) {
                // do nothing
            }
        if (skipFormatsField != null) {
            String[] formats = dialogSettings.getArray(SKIPPEDFORMATS);
            if (formats == null)
                skippedFormats = new HashSet<String>(3);
            else {
                skipFormatsField.setText(presetFormats = Core.toStringList(formats, ";")); //$NON-NLS-1$
                skippedFormats = new HashSet<String>(Arrays.asList(formats));
            }
        }
        updateValues(wizard.getCurrentDevice());
    }

    public void updateValues(LastDeviceImport current) {
        if (current != null) {
            Boolean dd = current.getDetectDuplicates();
            if (duplicatesButton != null && duplicatesButton.getSelection() == presetDuplicates && dd != null)
                duplicatesButton.setSelection(presetDuplicates = dd);
            Boolean rr = current.getRemoveMedia();
            if (rr != null && removeMediaButton != null && removeMediaButton.getSelection() == presetRemoveMedia)
                removeMediaButton.setSelection(presetRemoveMedia = rr);
            Integer sp = current.getSkipPolicy();
            if (sp != null && skipCombo != null && skipCombo.getSelectionIndex() == presetSkipPolicy)
                skipCombo.select(presetSkipPolicy = sp);
            String[] sf = current.getSkippedFormats();
            if (sf != null && skipFormatsField != null) {
                String formats = Core.toStringList(sf, ";"); //$NON-NLS-1$
                if (skipFormatsField.getText().equals(presetFormats)) {
                    skipFormatsField.setText(presetFormats = formats);
                    skippedFormats = new HashSet<String>(Arrays.asList(sf));
                }
            }
        }
    }

    private ImportNode[] createInput(final StorageObject[] files, final StorageObject[] dcims)
            throws InvocationTargetException, InterruptedException {
        nodeCount = 0;
        final ObjectFilter imageFilter = new FileNameExtensionFilter(
                ImageConstants.getSupportedImageFileExtensionsGroups(true), true);
        final List<ImportNode> nodes = new ArrayList<ImportNode>();
        final ZProgressMonitorDialog dialog = new ZProgressMonitorDialog(getShell());
        dialog.create();
        dialog.getShell()
                .setText(Constants.APPLICATION_NAME + " - " + Messages.ImportFileSelectionPage_import_preparation); //$NON-NLS-1$
        dialog.run(true, true, new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                ImportFileSelectionPage.this.monitor = monitor;
                try {
                    monitor.beginTask(Messages.ImportFileSelectionPage_analyzing_folder_contents,
                            IProgressMonitor.UNKNOWN);
                    for (int i = 0; i < dcims.length; i++) {
                        StorageObject file = dcims[i];
                        ImportNode node = new ImportNode(null, file.getAbsolutePath(), -1, 0, 0,
                                Messages.ImportFileSelectionPage_photos, Messages.ImportFileSelectionPage_photo);
                        nodes.add(node);
                        node.count = addChildren(node, file, null, imageFilter, monitor);
                        if (monitor.isCanceled())
                            throw new InterruptedException();
                    }
                    if (files != null && files.length > 0) {
                        ImportNode node = new ImportNode(null, Messages.ImportFileSelectionPage_dropped_files, -1,
                                0, 0, Messages.ImportFileSelectionPage_photos,
                                Messages.ImportFileSelectionPage_photo);
                        nodes.add(node);
                        node.count = addChildren(node, null, files, imageFilter, monitor);
                        if (monitor.isCanceled())
                            throw new InterruptedException();
                    }
                    for (IMediaSupport ms : CoreActivator.getDefault().getMediaSupport()) {
                        ObjectFilter foreignFilter = new FileNameExtensionFilter(ms.getFileExtensions());
                        for (int i = 0; i < dcims.length; i++) {
                            StorageObject file = media ? ms.getMediaFolder(dcims[i]) : dcims[i];
                            if (file == null)
                                continue;
                            ImportNode node = new ImportNode(null, file.getAbsolutePath(), -1, 0, 0, ms.getPlural(),
                                    ms.getName());
                            nodes.add(node);
                            node.count = addChildren(node, file, null, foreignFilter, monitor);
                            if (monitor.isCanceled())
                                throw new InterruptedException();
                        }
                        if (files != null && files.length > 0) {
                            ImportNode node = new ImportNode(null, Messages.ImportFileSelectionPage_dropped_files,
                                    -1, 0, 0, ms.getPlural(), ms.getName());
                            nodes.add(node);
                            node.count = addChildren(node, null, files, foreignFilter, monitor);
                            if (monitor.isCanceled())
                                throw new InterruptedException();
                        }
                    }
                    monitor.done();
                } catch (IOException e) {
                    throw new InterruptedException(Messages.ImportFileSelectionPage_io_error_while_scanning);
                } finally {
                    ImportFileSelectionPage.this.monitor = null;
                }
            }
        });
        groupedByLabel.setText(byDate
                ? byFilename ? Messages.ImportFileSelectionPage_grouped_by_date_or_filename
                        : Messages.ImportFromDeviceWizard_modification_dates
                : Messages.ImportFileSelectionPage_grouped_by_filename);
        return nodes.toArray(new ImportNode[nodes.size()]);
    }

    private int addChildren(ImportNode node, StorageObject folder, StorageObject[] files, ObjectFilter imageFilter,
            IProgressMonitor monitor) throws IOException {
        List<StorageObject> noDates = null;
        int[] years = new int[130];
        long[] mins = new long[130];
        for (int i = 0; i < mins.length; i++)
            mins[i] = Long.MAX_VALUE;
        long[] maxs = new long[130];
        StorageObject[] members = folder != null ? filterObjects(folder.listChildren(), imageFilter, true)
                : filterObjects(files, imageFilter, false);
        int n = 0;
        if (members != null) {
            for (StorageObject child : members) {
                if (monitor.isCanceled())
                    break;
                if (child.isDirectory()) {
                    monitor.subTask(child.getName());
                    ImportNode childNode = new ImportNode(node, child.getName(), -1, 0, 0, node.plural,
                            node.singular);
                    int m = addChildren(childNode, child, null, imageFilter, monitor);
                    if (monitor.isCanceled())
                        break;
                    if (m > 0) {
                        childNode.count = m;
                        n += m;
                        node.add(childNode);
                    }
                } else {
                    long lastModified = child.lastModified();
                    if (lastModified > 0) {
                        calendar.setTimeInMillis(lastModified);
                        int y = calendar.get(Calendar.YEAR) - 1970;
                        ++years[y];
                        mins[y] = Math.min(mins[y], lastModified);
                        maxs[y] = Math.max(maxs[y], lastModified);
                        byDate = true;
                    } else {
                        if (noDates == null)
                            noDates = new ArrayList<StorageObject>();
                        noDates.add(child);
                        byFilename = true;
                    }
                    ++n;
                }
            }
            for (int i = 0; i < years.length; i++)
                if (years[i] > 0) {
                    ImportNode childNode = new ImportNode(node, String.valueOf(i + 1970), Calendar.YEAR, i + 1970,
                            years[i], node.plural, node.singular);
                    childNode.minTime = mins[i];
                    childNode.maxTime = maxs[i];
                    node.add(childNode);
                    addChildrenInPeriod(childNode, folder, members, files, imageFilter, monitor);
                    if (monitor.isCanceled())
                        break;
                }
            if (noDates != null) {
                Collections.sort(noDates, new Comparator<StorageObject>() {
                    @Override
                    public int compare(StorageObject o1, StorageObject o2) {
                        return o1.getAbsolutePath().compareTo(o2.getAbsolutePath());
                    }
                });
                int size = noDates.size();
                ImportNode currentNode = null;
                for (int i = 0; i < size; i++) {
                    StorageObject file = noDates.get(i);
                    if (i % 36 == 0)
                        currentNode = new ImportNode(node, extractFileName(file.getAbsolutePath()), NODATES, 0, 0,
                                node.plural, node.singular);
                    if (i == size - 1 || i % 36 == 35) {
                        currentNode.label += " - " + extractFileName(file.getAbsolutePath()); //$NON-NLS-1$
                        currentNode.count = i % 36 + 1;
                        node.add(currentNode);
                    }
                    ImportNode fileNode = new ImportNode(node, file.getName(), FILE, 0, 1, node.plural,
                            node.singular);
                    fileNode.memberFiles = new StorageObject[] { file };
                    currentNode.add(fileNode);
                }
            }
        } else
            node.label += " - " + Messages.ImportFromDeviceWizard_bad_connection; //$NON-NLS-1$
        return n;
    }

    private static String extractFileName(String path) {
        return path.substring(path.lastIndexOf('/') + 1);
    }

    private static StorageObject[] filterObjects(StorageObject[] files, ObjectFilter imageFilter,
            boolean skipHidden) {
        if (files == null)
            return null;
        List<StorageObject> filtered = new ArrayList<>(files.length);
        for (StorageObject file : files)
            if ((!skipHidden || !file.isHidden()) && imageFilter.accept(file))
                filtered.add(file);
        return filtered.toArray(new StorageObject[filtered.size()]);
    }

    private void addChildrenInPeriod(ImportNode parent, StorageObject folder, StorageObject[] members,
            StorageObject[] files, ObjectFilter imageFilter, IProgressMonitor monitor) {
        int[] dates = new int[32];
        long[] mins = new long[32];
        for (int i = 0; i < mins.length; i++)
            mins[i] = Long.MAX_VALUE;
        long[] maxs = new long[32];
        for (StorageObject child : members) {
            if (monitor.isCanceled())
                break;
            long lastModified = child.lastModified();
            calendar.setTimeInMillis(lastModified);
            int year = calendar.get(Calendar.YEAR);
            int month = calendar.get(Calendar.MONTH);
            switch (parent.type) {
            case Calendar.YEAR:
                if (year == parent.value) {
                    ++dates[month];
                    mins[month] = Math.min(mins[month], lastModified);
                    maxs[month] = Math.max(maxs[month], lastModified);
                }
                break;
            case Calendar.MONTH:
                if (month == parent.value && year == parent.parent.value) {
                    int day = calendar.get(Calendar.DAY_OF_MONTH);
                    ++dates[day];
                    mins[day] = Math.min(mins[day], lastModified);
                    maxs[day] = Math.max(maxs[day], lastModified);
                }
                break;
            }
        }
        int type = (parent.type == Calendar.YEAR) ? Calendar.MONTH : Calendar.DAY_OF_MONTH;
        for (int dm = 0; dm < dates.length; dm++) {
            if (monitor.isCanceled())
                break;
            if (dates[dm] > 0) {
                String label = (type == Calendar.MONTH) ? Constants.DATEFORMATS.getMonths()[dm]
                        : String.valueOf(dm);
                if (type == Calendar.DAY_OF_MONTH)
                    label = Constants.DATEFORMATS
                            .getWeekdays()[new GregorianCalendar(parent.parent.value, parent.value, dm)
                                    .get(Calendar.DAY_OF_WEEK)].substring(0, 3)
                            + ", " + label; //$NON-NLS-1$
                ImportNode childNode = new ImportNode(parent, label, type, dm, dates[dm], parent.plural,
                        parent.singular);
                childNode.minTime = mins[dm];
                childNode.maxTime = maxs[dm];
                parent.add(childNode);
                if ((type == Calendar.MONTH))
                    addChildrenInPeriod(childNode, folder, members, files, imageFilter, monitor);
                else
                    childNode.memberFiles = members;
            }
        }
    }

    @Override
    protected void validatePage() {
        Object[] checkedElements = importViewer.getCheckedElements();
        if (checkedElements.length == 0) {
            setErrorMessage(Messages.ImportFromDeviceWizard_please_select_files);
            setPageComplete(false);
        } else {
            for (Object importNode : checkedElements)
                if (((ImportNode) importNode).count > 0) {
                    setErrorMessage(null);
                    setPageComplete(true);
                    return;
                }
            setErrorMessage(Messages.ImportFileSelectionPage_no_images_to_import);
            setPageComplete(false);
        }
    }

    public String performFinish(ImportFromDeviceData importData) {
        ImportFromDeviceWizard wizard = (ImportFromDeviceWizard) getWizard();
        IDialogSettings dialogSettings = wizard.getDialogSettings();
        LastDeviceImport newDevice = wizard.getNewDevice();
        boolean detectDuplicates = duplicatesButton.getSelection();
        importData.setDetectDuplicates(detectDuplicates);
        dialogSettings.put(DETECTDUPLICATES, detectDuplicates);
        newDevice.setDetectDuplicates(detectDuplicates);
        importData
                .setRemoveMedia(removeMedia = removeMediaButton != null ? removeMediaButton.getSelection() : false);
        dialogSettings.put(REMOVEMEDIA, removeMedia);
        newDevice.setRemoveMedia(removeMedia);
        int policy = 0;
        if (skipCombo != null)
            dialogSettings.put(SKIPPOLICY, policy = Math.max(0, skipCombo.getSelectionIndex()));
        importData.setSkipPolicy(policy);
        newDevice.setSkipPolicy(policy);
        if (skipFormatsField != null) {
            String[] formats = skippedFormats.toArray(new String[skippedFormats.size()]);
            Arrays.sort(formats);
            dialogSettings.put(SKIPPEDFORMATS, formats);
            newDevice.setSkippedFormats(formats);
        }
        List<StorageObject> selectedFiles = getSelectedFiles();
        if (selectedFiles == null)
            return Messages.ImportFromDeviceWizard_device_seems_to_be_offline;
        importData.setFileInput(new FileInput(selectedFiles, false));
        return null;
    }

    public List<StorageObject> getSelectedFiles() {
        if (selectedFiles == null) {
            selectedFiles = new ArrayList<>(100);
            for (Object checked : importViewer.getCheckedElements())
                if (checked instanceof ImportNode) {
                    ImportNode node = ((ImportNode) checked);
                    if (node.type == Calendar.DAY_OF_MONTH) {
                        int day = node.value;
                        int month = node.parent.value;
                        int year = node.parent.parent.value;
                        boolean grayed = importViewer.getGrayed(checked);
                        StorageObject[] listFiles = grayed ? node.getMissing() : node.memberFiles;
                        if (listFiles != null)
                            for (StorageObject file : listFiles) {
                                calendar.setTimeInMillis(file.lastModified());
                                if (calendar.get(Calendar.DAY_OF_MONTH) == day
                                        && calendar.get(Calendar.MONTH) == month
                                        && calendar.get(Calendar.YEAR) == year)
                                    selectedFiles.add(file);
                            }
                    } else if (node.type == FILE)
                        selectedFiles.add(node.memberFiles[0]);
                }
        }
        return selectedFiles;
    }

    public StorageObject getFirstSelectedFile() {
        for (Object checked : importViewer.getCheckedElements())
            if (checked instanceof ImportNode) {
                ImportNode node = ((ImportNode) checked);
                if (node.type == Calendar.DAY_OF_MONTH) {
                    int day = node.value;
                    int month = node.parent.value;
                    int year = node.parent.parent.value;
                    boolean grayed = importViewer.getGrayed(checked);
                    StorageObject[] listFiles = grayed ? node.getMissing() : node.memberFiles;
                    if (listFiles != null)
                        for (StorageObject file : listFiles) {
                            calendar.setTimeInMillis(file.lastModified());
                            if (calendar.get(Calendar.DAY_OF_MONTH) == day && calendar.get(Calendar.MONTH) == month
                                    && calendar.get(Calendar.YEAR) == year)
                                return file;
                        }
                } else if (node.type == FILE)
                    return node.memberFiles[0];
            }
        return null;
    }

    public void cancel() {
        if (monitor != null) {
            monitor.setCanceled(true);
            getWizard().performCancel();
        }
    }

}