net.refractions.udig.document.ui.DocumentView.java Source code

Java tutorial

Introduction

Here is the source code for net.refractions.udig.document.ui.DocumentView.java

Source

/* uDig - User Friendly Desktop Internet GIS client
 * http://udig.refractions.net
 * (C) 2011-2012, Refractions Research Inc.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
 * License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
 */
package net.refractions.udig.document.ui;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.miginfocom.swt.MigLayout;
import net.refractions.udig.catalog.IGeoResource;
import net.refractions.udig.catalog.document.IAbstractDocumentSource;
import net.refractions.udig.catalog.document.IAttachment;
import net.refractions.udig.catalog.document.IAttachmentSource;
import net.refractions.udig.catalog.document.IDocument;
import net.refractions.udig.catalog.document.IDocument.ContentType;
import net.refractions.udig.catalog.document.IDocument.Type;
import net.refractions.udig.catalog.document.IDocumentFolder;
import net.refractions.udig.catalog.document.IDocumentSource;
import net.refractions.udig.catalog.document.IDocumentSource.DocumentInfo;
import net.refractions.udig.catalog.document.IHotlink;
import net.refractions.udig.catalog.document.IHotlinkSource;
import net.refractions.udig.catalog.document.IHotlinkSource.HotlinkDescriptor;
import net.refractions.udig.core.AdapterUtil;
import net.refractions.udig.document.source.ShpDocFactory;
import net.refractions.udig.document.ui.DocumentDialog.Mode;
import net.refractions.udig.project.IMap;
import net.refractions.udig.project.internal.commands.edit.SetAttributeCommand;
import net.refractions.udig.project.ui.ApplicationGIS;
import net.refractions.udig.tool.info.internal.Messages;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.dialogs.ListDialog;
import org.eclipse.ui.dialogs.PropertyDialogAction;
import org.eclipse.ui.part.ViewPart;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.FidFilterImpl;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.filter.text.ecql.ECQL;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.identity.FeatureId;

/**
 * The Document View provides a user interface to view, edit, delete and open attached documents. In
 * this current release, the view uses shapefile specific implementations of {@link IDocumentSource}
 * and {@link IAttachmentSource} as its document sources.
 * 
 * @author paul.pfeiffer
 * @author Naz Chan
 */
public class DocumentView extends ViewPart {

    private TreeViewer viewer;
    private Button attachButton;
    private Button linkButton;
    private Button editButton;
    private Button openButton;
    private Button saveAsButton;
    private Action saveAsAction;
    private Button removeButton;

    private ResourceSelectionProvider resourceSelectionProvider = new ResourceSelectionProvider();
    private IGeoResource geoResource;
    private SimpleFeature feature;
    private IStructuredSelection viewerSelection;
    private IStructuredSelection workbenchSelection;
    private DocumentItemModel itemModel;

    private boolean isActive = false;
    private ISelectionListener workbenchSelectionListener;
    private IPartListener workbenchPartListener;

    private boolean isResourceEnabled = false;
    private boolean isFeatureEnabled = false;
    private boolean isHotlinkEnabled = false;

    private static final int DOCUMENT_INDEX = 0;
    private static final int DOCUMENT_WEIGHT = 40;

    private static final int TYPE_INDEX = 1;
    private static final int TYPE_WEIGHT = 10;

    private static final int DESCRIPTION_INDEX = 2;
    private static final int DESCRIPTION_WEIGHT = 50;

    public DocumentView() {
        this.itemModel = new DocumentItemModel();
    }

    /**
     * Adds workbench listeners. This will give us control over workbench events that drive the
     * view's contents. Eg. workbench selection, view activation, etc.
     */
    private void addWorkbenchListeners() {

        final IWorkbenchPartSite partSite = getSite();
        final ISelectionService selectionService = partSite.getWorkbenchWindow().getSelectionService();

        // Add workbench selection listener
        workbenchSelectionListener = new ISelectionListener() {
            @Override
            public void selectionChanged(IWorkbenchPart part, ISelection selection) {
                if (!(part instanceof DocumentView)) {
                    handleWorkbenchSelection(selection);
                }
            }
        };
        selectionService.addPostSelectionListener(workbenchSelectionListener);

        // Add workbench part listener
        workbenchPartListener = new IPartListener() {
            @Override
            public void partOpened(IWorkbenchPart part) {
                if (part instanceof DocumentView) {
                    handleWorkbenchSelection(selectionService.getSelection());
                }
            }

            @Override
            public void partDeactivated(IWorkbenchPart part) {
                // Nothing
            }

            @Override
            public void partClosed(IWorkbenchPart part) {
                // Nothing
            }

            @Override
            public void partBroughtToTop(IWorkbenchPart part) {
                // Nothing
            }

            @Override
            public void partActivated(IWorkbenchPart part) {
                isActive = (part instanceof DocumentView);
            }
        };
        partSite.getPage().addPartListener(workbenchPartListener);

    }

    /**
     * Removes the workbench listeners. This cleans up the listeners we are adding during startup of
     * the view.
     */
    private void removeWorkbenchListeners() {
        if (workbenchSelectionListener != null) {
            getSite().getWorkbenchWindow().getSelectionService()
                    .removePostSelectionListener(workbenchSelectionListener);
            workbenchSelectionListener = null;
        }
        if (workbenchPartListener != null) {
            getSite().getPage().removePartListener(workbenchPartListener);
            workbenchPartListener = null;
        }
    }

    @Override
    public void createPartControl(final Composite viewParent) {

        setPartName(Messages.docView_name);
        createViewMenu();

        final Composite parent = new Composite(viewParent, SWT.NONE);
        final String treeLayoutConst = "insets 0, fill, wrap 2"; //$NON-NLS-1$
        final String treeColConst = "[85%]0[15%]"; //$NON-NLS-1$
        final String treeRowConst = "[100%]"; //$NON-NLS-1$
        parent.setLayout(new MigLayout(treeLayoutConst, treeColConst, treeRowConst));

        createTreeControlArea(parent);
        createButtonControlArea(parent);
        refreshBtns();

        addWorkbenchListeners();

    }

    /**
     * Creates the menu items for the view's menu.
     */
    private void createViewMenu() {

        saveAsAction = new Action(Messages.docView_saveAs) {
            @Override
            public void run() {
                saveAs();
            }
        };
        saveAsAction.setId(Messages.docView_saveAs);

        final IActionBars actionBars = getViewSite().getActionBars();
        final IMenuManager menuManager = actionBars.getMenuManager();
        menuManager.add(saveAsAction);
        menuManager.add(new Separator());

        PropertyDialogAction resourcePropertyAction = new PropertyDialogAction(getSite(),
                resourceSelectionProvider);
        menuManager.add(resourcePropertyAction);
    }

    /**
     * Creates the tree-table control for displaying the documents.
     * 
     * @param parent
     */
    private void createTreeControlArea(Composite parent) {

        final Tree viewerTree = new Tree(parent, SWT.FULL_SELECTION | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        viewerTree.setLayoutData("grow, h 100%!, w 85%!"); //$NON-NLS-1$
        viewerTree.setHeaderVisible(true);
        viewerTree.setLinesVisible(true);

        final TableLayout viewerTreeLayout = new TableLayout();

        final TreeColumn nameColumn = new TreeColumn(viewerTree, SWT.LEFT, DOCUMENT_INDEX);
        nameColumn.setText(Messages.docView_documentColumn);
        viewerTreeLayout.addColumnData(new ColumnWeightData(DOCUMENT_WEIGHT));

        final TreeColumn detailColumn = new TreeColumn(viewerTree, SWT.CENTER, TYPE_INDEX);
        detailColumn.setText(Messages.docView_typeColumn);
        viewerTreeLayout.addColumnData(new ColumnWeightData(TYPE_WEIGHT));

        final TreeColumn descColumn = new TreeColumn(viewerTree, SWT.LEFT, DESCRIPTION_INDEX);
        descColumn.setText(Messages.docView_descriptionColumn);
        viewerTreeLayout.addColumnData(new ColumnWeightData(DESCRIPTION_WEIGHT));

        viewerTree.setLayout(viewerTreeLayout);

        viewer = new TreeViewer(viewerTree);
        viewer.setContentProvider(new DocumentViewContentProvider());
        viewer.setLabelProvider(new DocumentViewTableLabelProvider());
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                handleListSelection(event.getSelection());
            }
        });
        viewer.addDoubleClickListener(new IDoubleClickListener() {
            @Override
            public void doubleClick(DoubleClickEvent event) {
                handleListDoubleClick(event.getSelection());
            }
        });

        getSite().setSelectionProvider(viewer);

    }

    /**
     * Creates the button controls panel for actions related to documents.
     * 
     * @param parent
     */
    private void createButtonControlArea(Composite parent) {

        final SelectionAdapter btnSelectionListener = new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                handleBtnSelection(e.widget);
            }
        };

        final Composite btnSection = new Composite(parent, SWT.NONE);
        final String btnLayoutConst = "fillx, wrap 1"; //$NON-NLS-1$
        final String btnColConst = ""; //$NON-NLS-1$
        final String btnRowConst = "[][][][][]push[]"; //$NON-NLS-1$
        btnSection.setLayout(new MigLayout(btnLayoutConst, btnColConst, btnRowConst));
        btnSection.setLayoutData("grow"); //$NON-NLS-1$

        final String btnLayoutData = "growx"; //$NON-NLS-1$

        // attach button
        attachButton = new Button(btnSection, SWT.PUSH);
        attachButton.setText(Messages.docView_attach);
        attachButton.setLayoutData(btnLayoutData);
        attachButton.addSelectionListener(btnSelectionListener);

        // link button
        linkButton = new Button(btnSection, SWT.PUSH);
        linkButton.setText(Messages.docView_link);
        linkButton.setLayoutData(btnLayoutData);
        linkButton.addSelectionListener(btnSelectionListener);

        // edit button
        editButton = new Button(btnSection, SWT.PUSH);
        editButton.setText(Messages.docView_edit);
        editButton.setLayoutData(btnLayoutData);
        editButton.addSelectionListener(btnSelectionListener);

        // open button
        openButton = new Button(btnSection, SWT.PUSH);
        openButton.setText(Messages.docView_open);
        openButton.setLayoutData(btnLayoutData);
        openButton.addSelectionListener(btnSelectionListener);

        // save as button
        saveAsButton = new Button(btnSection, SWT.PUSH);
        saveAsButton.setText(Messages.docView_saveAs);
        saveAsButton.setLayoutData(btnLayoutData);
        saveAsButton.addSelectionListener(btnSelectionListener);
        saveAsButton.setVisible(false); // Re: Brett's comment

        // remove button
        removeButton = new Button(btnSection, SWT.PUSH);
        removeButton.setText(Messages.docView_clear);
        removeButton.setLayoutData(btnLayoutData);
        removeButton.addSelectionListener(btnSelectionListener);

    }

    @Override
    public void setFocus() {
        // Do something
    }

    @Override
    public void dispose() {
        removeWorkbenchListeners();
        super.dispose();
    }

    /**
     * Handles list selection changes. This should refresh the UI with respect to the current
     * selection.
     * 
     * @param selection
     */
    private void handleListSelection(ISelection selection) {
        viewerSelection = null;
        if (selection instanceof StructuredSelection) {
            viewerSelection = (StructuredSelection) selection;
        }
        refreshBtns();
    }

    /**
     * Handles list double clicks. This will open the document if the selection is a document and if
     * it is not empty.
     * 
     * @param selection
     */
    private void handleListDoubleClick(ISelection selection) {
        final StructuredSelection structSelection = (StructuredSelection) selection;
        final Object element = structSelection.getFirstElement();
        if (element instanceof IDocument) {
            final IDocument doc = (IDocument) element;
            if (!doc.isEmpty()) {
                open();
            }
        }
    }

    /**
     * Refreshes the buttons with respect to the current list selection.
     */
    private void refreshBtns() {
        setBtns(false);
        refreshBtnsOnType();
    }

    /**
     * Sets the button's enablement.
     * 
     * @param isEnabled
     */
    private void setBtns(boolean isEnabled) {

        openButton.setEnabled(isEnabled);
        attachButton.setEnabled(isEnabled);
        linkButton.setEnabled(isEnabled);
        editButton.setEnabled(isEnabled);
        removeButton.setEnabled(isEnabled);

        saveAsButton.setEnabled(isEnabled);
        saveAsAction.setEnabled(isEnabled);
    }

    /**
     * Refreshes the buttons with respect to the selection's type (if {@link IDocument} or
     * {@link IDocumentFolder}), document type (refer to {@link Type}) and document content type
     * (refer to {@link ContentType}). And also with respect to the selection's document source. Refer to
     * {@link IAbstractDocumentSource}.
     */
    private void refreshBtnsOnType() {

        if (viewerSelection != null) {
            if (viewerSelection.size() == 1) {

                final Object element = viewerSelection.getFirstElement();
                final IAbstractDocumentSource folderSource = getDocumentSource();

                attachButton.setEnabled(DocSourceUtils.canAttach(folderSource));
                linkButton.setEnabled(DocSourceUtils.canLink(folderSource));

                if (element instanceof IDocument) {

                    final IDocument doc = (IDocument) element;
                    final Type docType = doc.getType();

                    editButton.setEnabled(DocSourceUtils.canUpdate(doc.getSource()));

                    if (Type.ATTACHMENT == docType && ContentType.FILE == doc.getContentType()) {
                        saveAsButton.setEnabled(true);
                        saveAsAction.setEnabled(true);
                    }

                    if (!doc.isEmpty()) {
                        openButton.setEnabled(true);
                    }

                    final boolean isHotlink = (Type.HOTLINK == docType);
                    removeButton.setText(isHotlink ? Messages.docView_clear : Messages.docView_delete);
                    removeButton.setEnabled(isHotlink ? !doc.isEmpty() : true);
                    if (removeButton.isEnabled()) {
                        removeButton.setEnabled(DocSourceUtils.canRemove(doc.getSource()));
                    }

                }

            } else if (viewerSelection.size() > 1) {
                int count = 0;
                for (Object obj : viewerSelection.toList()) {
                    if (obj instanceof IDocumentFolder) {
                        count++;
                    }
                }
                final boolean isAllFolders = (count == viewerSelection.size());
                removeButton.setEnabled(!isAllFolders);
            }
        }

    }

    /**
     * Handles workbench selection changes. This should refresh the current list with respect to the
     * selection. As the workbench selection change event happens ALOT of times, it is being checked
     * here is the selection is the same as the previous one as to reduce the number of list refreshes.
     * 
     * @param selection
     */
    private void handleWorkbenchSelection(ISelection selection) {

        if (selection != null) {
            if (selection instanceof IStructuredSelection) {
                final IStructuredSelection newSelection = (IStructuredSelection) selection;
                if (workbenchSelection == null) {
                    // Go Update!
                    updateList(UpdateType.UPDATE, newSelection);
                } else {
                    final boolean isSameCount = (workbenchSelection.size() == newSelection.size());
                    if (isSameCount) {
                        // Check and update!
                        updateList(UpdateType.CHECK_UPDATE, newSelection);
                    } else {
                        // Go Update!
                        updateList(UpdateType.UPDATE, newSelection);
                    }
                }
                return;
            }
        }
        // Clear list!
        updateList(UpdateType.CLEAR, null);

    }

    private enum UpdateType {
        CLEAR, UPDATE, CHECK_UPDATE
    }

    /**
     * Creates a job to get the related documents of the selection. This transitions the processing
     * to another thread.
     * 
     * @param option
     * @param selection
     */
    private void updateList(final UpdateType option, final IStructuredSelection selection) {

        final Job getDocsJob = new Job(Messages.DocumentView_retrieveDocsProgressMsg) {
            @Override
            protected IStatus run(IProgressMonitor monitor) {

                switch (option) {
                case CLEAR:
                    workbenchSelection = null;
                    itemModel = null;
                    break;
                case UPDATE:
                    workbenchSelection = selection;
                    itemModel = new DocumentItemModel();
                    itemModel.setItems(getItems(monitor));

                    break;
                case CHECK_UPDATE:
                    if (!isSameSelection(selection)) {
                        workbenchSelection = selection;
                        itemModel = new DocumentItemModel();
                        itemModel.setItems(getItems(monitor));
                    }
                    break;
                default:
                    break;
                }

                updateListCallback();
                return Status.OK_STATUS;

            }
        };
        getDocsJob.schedule();

    }

    /**
     * Updates the document list with the related documents of the selection. This transitions the
     * processing back to the UI thread.
     */
    private void updateListCallback() {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (viewer != null) {
                    final Tree viewerTree = viewer.getTree();
                    if (viewerTree != null && !viewerTree.isDisposed()) {
                        viewer.setInput(itemModel);
                        viewer.expandAll();
                        if (isActive) {
                            viewerTree.setFocus();
                        }
                    }
                }
            }
        });
    }

    /**
     * Checks if the selection is the same as the previous one.
     * 
     * @param newSelection
     * @return true if same, otherwise false
     */
    private boolean isSameSelection(IStructuredSelection newSelection) {

        final Iterator<?> wbIterator = workbenchSelection.iterator();
        final Iterator<?> nwIterator = newSelection.iterator();
        while (nwIterator.hasNext() && wbIterator.hasNext()) {
            final Object wbObj = (Object) wbIterator.next();
            final Object nwObj = (Object) nwIterator.next();
            if (!isSameSelectionObj(wbObj, nwObj)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Checks if the selection object is the same as the previous selection's object in the same
     * index.
     * 
     * @param obj1
     * @param obj2
     * @return true if same, otherwise false
     */
    private boolean isSameSelectionObj(Object obj1, Object obj2) {

        final IProgressMonitor monitor = new NullProgressMonitor();

        final IGeoResource resource1 = toGeoResource(obj1, monitor);
        final IGeoResource resource2 = toGeoResource(obj2, monitor);
        if ((resource1 != null && resource2 != null) && (resource1.getID().equals(resource2.getID()))) {

            final FidFilterImpl filter1 = toFilter(obj1, monitor);
            final FidFilterImpl filter2 = toFilter(obj2, monitor);

            if (filter1 == null && filter2 == null) {
                return true;
            } else if ((filter1 != null && filter2 != null) && (filter1.equals(filter2))) {
                return true;
            }

        }

        return false;
    }

    void setGeoResourceInternal(IGeoResource geoResource) {
        this.geoResource = geoResource;
        this.resourceSelectionProvider.setSelection(geoResource);
    }

    /**
     * Gets the document items from the current selection.
     * 
     * @return document items
     */
    private List<Object> getItems(IProgressMonitor monitor) {

        final List<Object> items = new ArrayList<Object>();
        for (Iterator<?> iterator = workbenchSelection.iterator(); iterator.hasNext();) {

            final Object obj = iterator.next();
            setGeoResourceInternal(toGeoResource(obj, monitor));
            if (geoResource != null) {

                feature = getFeature(geoResource, toFilter(obj, monitor));
                if (feature != null) {

                    final IHotlinkSource hotlinkSource = toSource(geoResource, IHotlinkSource.class, monitor);
                    isHotlinkEnabled = hotlinkSource != null && hotlinkSource.isEnabled();
                    final IAttachmentSource attachmentSource = toSource(geoResource, IAttachmentSource.class,
                            monitor);
                    isFeatureEnabled = attachmentSource != null && attachmentSource.isEnabled();

                    if (isFeatureEnabled || isHotlinkEnabled) {
                        final String featureLabel = getFeatureLabel(geoResource, feature);
                        final IDocumentFolder folder = ShpDocFactory.createFolder(feature, featureLabel,
                                attachmentSource);
                        if (isFeatureEnabled) {
                            // Set so that source's document list is same with folder's
                            folder.setDocuments(attachmentSource.getDocuments(feature, monitor));
                        }
                        if (isHotlinkEnabled) {
                            folder.insertDocuments(hotlinkSource.getDocuments(feature, monitor), 0);
                        }
                        items.add(folder);
                    }

                }

                final IDocumentSource docSource = toSource(geoResource, IDocumentSource.class, monitor);
                isResourceEnabled = docSource != null && docSource.isEnabled();

                if (isResourceEnabled) {
                    final IDocumentFolder folder = ShpDocFactory.createFolder(null, geoResource.getTitle(),
                            docSource);
                    // Set so that source's document list is same with folder's
                    folder.setDocuments(docSource.getDocuments(monitor));
                    items.add(folder);
                }

            }
        }

        return items;

    }

    private static final String FEATURE_LABEL = "FEATURE_LABEL"; //$NON-NLS-1$

    /**
     * Gets the feature label by running the feature label expression set for the feature.
     * 
     * @param resource
     * @param feature
     * @return feature label
     */
    private String getFeatureLabel(IGeoResource resource, SimpleFeature feature) {

        final String labelExpression = (String) resource.getPersistentProperties().get(FEATURE_LABEL);

        if (labelExpression != null) {
            try {
                final Expression exp = ECQL.toExpression(labelExpression);
                final String featureLabel = (String) exp.evaluate(feature);
                if (featureLabel != null && featureLabel.trim().length() > 0) {
                    return featureLabel;
                }
            } catch (CQLException e) {
                e.printStackTrace();
            }
        }

        return feature.getID();

    }

    /**
     * Resolves the object to a geo resource. This returns null if it is not able to resolve.
     * 
     * @param obj
     * @param monitor
     * @return geo resource
     */
    private IGeoResource toGeoResource(Object obj, IProgressMonitor monitor) {
        if (obj != null) {
            final AdapterUtil adapterUtil = AdapterUtil.instance;
            if (adapterUtil.canAdaptTo(obj, IGeoResource.class)) {
                try {
                    return adapterUtil.adaptTo(IGeoResource.class, obj, monitor);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * Resolves the object to a document source. This returns null if it is not able to resolve.
     * 
     * @param geoResource
     * @param type
     * @param monitor
     * @return document source
     */
    private <T> T toSource(IGeoResource geoResource, Class<T> type, IProgressMonitor monitor) {
        if (geoResource != null) {
            if (geoResource.canResolve(type)) {
                try {
                    return geoResource.resolve(type, monitor);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * Resolves the object to a feature ID filter. This returns null if it is unable to resolve.
     * 
     * @param obj
     * @param monitor
     * @return feature ID filter
     */
    private FidFilterImpl toFilter(Object obj, IProgressMonitor monitor) {
        if (obj != null) {
            final AdapterUtil adapterUtil = AdapterUtil.instance;
            try {
                return adapterUtil.adaptTo(FidFilterImpl.class, obj, monitor);
            } catch (ClassCastException e) {
                // Selection does not include a feature
                // e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Gets the feature from the geo resource given the filter.
     * 
     * @param geoResource
     * @param filter
     * @return feature
     */
    private SimpleFeature getFeature(IGeoResource geoResource, FidFilterImpl filter) {
        if (filter != null) {
            try {
                if (geoResource.canResolve(SimpleFeatureStore.class)) {
                    final SimpleFeatureStore featureSource = geoResource.resolve(SimpleFeatureStore.class,
                            new NullProgressMonitor());
                    final SimpleFeatureCollection featureCollection = featureSource.getFeatures(filter);
                    final SimpleFeatureIterator featureIterator = featureCollection.features();
                    try {
                        if (featureIterator.hasNext()) {
                            return featureIterator.next();
                        }
                    } finally {
                        if (featureIterator != null) {
                            featureIterator.close();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Handles button selection (click) actions.
     * 
     * @param btn
     */
    private void handleBtnSelection(Widget btn) {
        if (openButton == btn) {
            open();
        } else if (attachButton == btn) {
            attach();
        } else if (linkButton == btn) {
            link();
        } else if (editButton == btn) {
            edit();
        } else if (removeButton == btn) {
            remove();
        } else if (saveAsButton == btn) {
            saveAs();
        }
    }

    /**
     * Opens the documents in the current selection.
     */
    private void open() {
        final IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
        final IDocument doc = (IDocument) selection.getFirstElement();
        if (ContentType.ACTION == doc.getContentType()) {
            openAction(doc);
        } else {
            doc.open();
        }
    }

    /**
     * Opens the action document - action selection dialog that allows the user to select the action
     * to be opened.
     * 
     * @param doc
     */
    private void openAction(IDocument doc) {

        final IHotlink hotlinkDoc = (IHotlink) doc;
        final List<HotlinkDescriptor> descriptors = hotlinkDoc.getDescriptors();

        if (descriptors.size() > 1) {
            final ListDialog dialog = new ListDialog(openButton.getShell());
            dialog.setTitle(Messages.DocumentView_openActionDialogTitle);
            dialog.setMessage(Messages.DocumentView_openActionDialogMessage);
            dialog.setLabelProvider(new LabelProvider() {
                @Override
                public String getText(Object element) {
                    final HotlinkDescriptor descriptor = (HotlinkDescriptor) element;
                    return DocUtils.getLabelAndDescDisplay(descriptor.getLabel(), descriptor.getDescription());
                }
            });
            dialog.setContentProvider(new ArrayContentProvider());
            dialog.setInput(descriptors.toArray());
            dialog.setInitialElementSelections(Collections.singletonList(descriptors.get(0)));
            if (Dialog.OK == dialog.open()) {
                final Object[] results = dialog.getResult();
                if (results != null && results.length > 0) {
                    final HotlinkDescriptor descriptor = (HotlinkDescriptor) results[0];
                    openAction(hotlinkDoc, descriptor);
                }
            }
        } else if (descriptors.size() == 1) {
            final HotlinkDescriptor descriptor = descriptors.get(0);
            openAction(hotlinkDoc, descriptor);
        }

    }

    /**
     * Opens the action document. This gets the value from the hotlink document and the action
     * definition from the desriptor.
     * 
     * @param hotlinkDoc
     * @param descriptor
     */
    private void openAction(IHotlink hotlinkDoc, HotlinkDescriptor descriptor) {
        final String action = descriptor.getConfig().replace(DocumentPropertyPage.ACTION_PARAM,
                hotlinkDoc.getContent().toString());
        Program.launch(action);
    }

    /**
     * Adds a new document. This opens the {@link DocumentDialog} to allow the user to input details
     * of the document to be added.
     */
    private void attach() {

        final IDocumentFolder folder = getDocumentFolder();
        if (folder != null) {
            final Map<String, Object> params = new HashMap<String, Object>();
            params.put(DocumentDialog.P_TYPE, Type.ATTACHMENT);
            params.put(DocumentDialog.P_MODE, Mode.ADD);
            params.put(DocumentDialog.P_TEMPLATES, itemModel.getTemplates(folder));

            final IAbstractDocumentSource source = folder.getSource();
            final boolean isFeatureDoc = (source instanceof IAttachmentSource);
            final DocumentDialog docDialog = openDocDialog(new HashMap<String, Object>(), params, isFeatureDoc);
            if (docDialog != null) {
                addDocument(folder, docDialog.getDocInfo());
            }
        }

    }

    /**
     * Links a new document. This opens the {@link DocumentDialog} to allow the user to input details
     * of the document to be linked.
     */
    private void link() {

        final IDocumentFolder folder = getDocumentFolder();
        if (folder != null) {
            final IAbstractDocumentSource source = folder.getSource();

            final Map<String, Object> params = new HashMap<String, Object>();
            params.put(DocumentDialog.P_TYPE, Type.LINKED);
            params.put(DocumentDialog.P_MODE, Mode.ADD);
            params.put(DocumentDialog.P_TEMPLATES, itemModel.getTemplates(folder));
            final List<ContentType> contentTypes = new ArrayList<ContentType>();
            if (DocSourceUtils.canLinkFile(source)) {
                contentTypes.add(ContentType.FILE);
            }
            if (DocSourceUtils.canLinkWeb(source)) {
                contentTypes.add(ContentType.WEB);
            }
            if (contentTypes != null && contentTypes.size() > 0) {
                params.put(DocumentDialog.P_CONTENT_TYPES, contentTypes);
            }

            final boolean isFeatureDoc = (source instanceof IAttachmentSource);
            final DocumentDialog docDialog = openDocDialog(new HashMap<String, Object>(), params, isFeatureDoc);
            if (docDialog != null) {
                addDocument(folder, docDialog.getDocInfo());
            }
        }

    }

    /**
     * Adds a new document to the document folder with the given document info.
     */
    private void addDocument(final IDocumentFolder folder, final DocumentInfo info) {

        final Job addDocJob = new Job(Messages.DocumentView_addDocProgressMsg) {
            @Override
            protected IStatus run(IProgressMonitor monitor) {

                IDocument doc = null;
                if (folder.getSource() instanceof IDocumentSource) {
                    final IDocumentSource resourceDocSource = (IDocumentSource) folder.getSource();
                    doc = resourceDocSource.add(info, monitor);
                } else if (folder.getSource() instanceof IAttachmentSource) {
                    final IAttachmentSource featureDocSource = (IAttachmentSource) folder.getSource();
                    doc = featureDocSource.add(feature, info, monitor);
                }

                addDocumentCallback(doc);
                return Status.OK_STATUS;

            }
        };
        addDocJob.schedule();

    }

    /**
     * Refreshes the documents list after adding a document. This transitions the processing back to
     * the UI thread.
     */
    private void addDocumentCallback(final IDocument doc) {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (doc == null) {
                    MessageDialog.openError(attachButton.getShell(), Messages.DocumentView_addDocPopupTitle,
                            Messages.DocumentView_addDocError);
                } else {
                    viewer.refresh();
                    viewer.expandAll();
                }
            }
        });
    }

    /**
     * Edits an existing document. This opens the {@link DocumentDialog} to allow the user to input details
     * of the document to be edited.
     */
    private void edit() {

        final Object obj = viewerSelection.getFirstElement();
        if (obj instanceof IDocument) {
            final IDocument doc = (IDocument) obj;
            if (Type.HOTLINK == doc.getType()) {
                editHotlink(doc);
            } else {
                editDocument(doc);
            }
        }

    }

    /**
     * Edits an existing document.
     * 
     * @param doc
     */
    private void editDocument(final IDocument doc) {

        final Map<String, Object> values = new HashMap<String, Object>();
        if (!doc.isEmpty()) {
            values.put(DocumentDialog.V_INFO, doc.getContent().toString());
        }
        values.put(DocumentDialog.V_CONTENT_TYPE, doc.getContentType());
        values.put(DocumentDialog.V_LABEL, doc.getLabel());
        values.put(DocumentDialog.V_DESCRIPTION, doc.getDescription());
        values.put(DocumentDialog.V_TEMPLATE, doc.isTemplate());

        final Map<String, Object> params = new HashMap<String, Object>();
        params.put(DocumentDialog.P_TYPE, doc.getType());
        params.put(DocumentDialog.P_MODE, Mode.EDIT);
        params.put(DocumentDialog.P_TEMPLATES, itemModel.getTemplates(doc));

        final IAbstractDocumentSource source = doc.getSource();
        final boolean isFeatureDoc = (source instanceof IAttachmentSource);
        final DocumentDialog docDialog = openDocDialog(values, params, isFeatureDoc);
        if (docDialog != null) {
            final Job editDocJob = new Job(Messages.DocumentView_updateDocProgressMsg) {
                @Override
                protected IStatus run(IProgressMonitor monitor) {
                    boolean isUpdated = false;
                    final DocumentInfo info = docDialog.getDocInfo();
                    if (source instanceof IDocumentSource) {
                        final IDocumentSource resourceDocSource = (IDocumentSource) source;
                        isUpdated = resourceDocSource.update(doc, info, monitor);
                    } else if (source instanceof IAttachmentSource) {
                        final IAttachmentSource featureDocSource = (IAttachmentSource) source;
                        isUpdated = featureDocSource.update(feature, doc, info, monitor);
                    }
                    editDocumentCallback(isUpdated);
                    return Status.OK_STATUS;
                }
            };
            editDocJob.schedule();
        }

    }

    /**
     * Refreshes the documents list after updating a document. This transitions the processing back
     * to the UI thread.
     */
    private void editDocumentCallback(final boolean isUpdated) {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (!isUpdated) {
                    MessageDialog.openError(attachButton.getShell(), Messages.DocumentView_updateDocPopupTitle,
                            Messages.DocumentView_updateDocError);
                } else {
                    viewer.refresh();
                    viewer.expandAll();
                }
            }
        });
    }

    /**
     * Edits an existing hotlink document.
     * 
     * @param doc
     */
    private void editHotlink(final IDocument doc) {

        final IHotlink hotlinkDoc = (IHotlink) doc;
        final String attributeName = hotlinkDoc.getAttributeName();

        final Map<String, Object> values = new HashMap<String, Object>();
        if (!doc.isEmpty()) {
            values.put(DocumentDialog.V_INFO, doc.getContent().toString());
        }
        values.put(DocumentDialog.V_CONTENT_TYPE, doc.getContentType());
        values.put(DocumentDialog.V_ATTRIBUTE, attributeName);
        values.put(DocumentDialog.V_LABEL, doc.getLabel());
        values.put(DocumentDialog.V_DESCRIPTION, doc.getDescription());
        if (ContentType.ACTION == doc.getContentType()) {
            values.put(DocumentDialog.V_ACTIONS, hotlinkDoc.getDescriptors());
        }

        final Map<String, Object> params = new HashMap<String, Object>();
        params.put(DocumentDialog.P_TYPE, Type.HOTLINK);
        params.put(DocumentDialog.P_MODE, Mode.EDIT);
        if (ContentType.FILE == doc.getContentType()) {
            params.put(DocumentDialog.P_TEMPLATES, itemModel.getTemplates(doc));
        }

        final DocumentDialog docDialog = openDocDialog(values, params, true);
        if (docDialog != null) {
            final Job editHotlinkJob = new Job(Messages.DocumentView_updateHotlinkProgressMsg) {
                @Override
                protected IStatus run(IProgressMonitor monitor) {
                    boolean isUpdated = false;
                    final IHotlinkSource featureDocSource = (IHotlinkSource) doc.getSource();
                    switch (doc.getContentType()) {
                    case FILE:
                        isUpdated = featureDocSource.setFile(feature, attributeName, docDialog.getFileInfo(),
                                monitor);
                        break;
                    case WEB:
                        isUpdated = featureDocSource.setLink(feature, attributeName, docDialog.getUrlInfo(),
                                monitor);
                        break;
                    case ACTION:
                        isUpdated = featureDocSource.setAction(feature, attributeName, docDialog.getInfo(),
                                monitor);
                        break;
                    default:
                        break;
                    }
                    if (isUpdated) {
                        isUpdated = set(attributeName, feature.getAttribute(attributeName), monitor);
                    }
                    itemModel.getClass();
                    editHotlinkCallback(isUpdated);
                    return Status.OK_STATUS;
                }
            };
            editHotlinkJob.schedule();
        }

    }

    /**
     * Refreshes the documents list after updating a hotlink. This transitions the processing back
     * to the UI thread.
     */
    private void editHotlinkCallback(final boolean isUpdated) {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (!isUpdated) {
                    MessageDialog.openError(attachButton.getShell(), Messages.DocumentView_setHotlinkPopupTitle,
                            Messages.DocumentView_setHotlinkError);
                } else {
                    viewer.refresh();
                    viewer.expandAll();
                }
            }
        });
    }

    /**
     * Opens the {@link DocumentDialog} for inputting document metadata.
     * 
     * @param values
     * @param params
     * @return document info
     */
    private DocumentDialog openDocDialog(Map<String, Object> values, Map<String, Object> params,
            boolean isFeatureDoc) {
        if (isFeatureDoc && feature != null) {
            params.put(DocumentDialog.P_FEATURE_NAME, getFeatureLabel(geoResource, feature));
        }
        params.put(DocumentDialog.P_RESOURCE_NAME, geoResource.getTitle());
        final DocumentDialog docDialog = new DocumentDialog(editButton.getShell(), values, params);
        final int result = docDialog.open();
        if (Dialog.OK == result) {
            return docDialog;
        }
        return null;
    }

    /**
     * Removes the documents in the current selection.
     */
    private void remove() {

        final Map<IAbstractDocumentSource, ArrayList<IDocument>> docMap = new HashMap<IAbstractDocumentSource, ArrayList<IDocument>>();

        final Iterator<?> iterator = viewerSelection.iterator();
        while (iterator.hasNext()) {
            final Object obj = iterator.next();
            if (obj instanceof IDocument) {
                final IDocument doc = (IDocument) obj;
                if (docMap.containsKey(doc.getSource())) {
                    docMap.get(doc.getSource()).add(doc);
                } else {
                    final ArrayList<IDocument> docs = new ArrayList<IDocument>();
                    docs.add(doc);
                    docMap.put(doc.getSource(), docs);
                }
            }
        }

        for (final IAbstractDocumentSource source : docMap.keySet()) {
            final ArrayList<IDocument> docs = docMap.get(source);
            for (final IDocument doc : docs) {

                boolean doDelete = true;
                if (Type.ATTACHMENT == doc.getType() && ContentType.FILE == doc.getContentType()
                        && !doc.isEmpty()) {
                    doDelete = MessageDialog.openConfirm(removeButton.getShell(),
                            Messages.docView_deleteAttachConfirmTitle, Messages.docView_deleteAttachConfirmMsg);
                }

                if (doDelete) {
                    if (source instanceof IDocumentSource) {
                        final Job removeLayerDocJob = new Job(Messages.DocumentView_removeDocProgressMsg) {
                            @Override
                            protected IStatus run(IProgressMonitor monitor) {
                                final IDocumentSource docSource = (IDocumentSource) source;
                                final boolean isRemoved = docSource.remove(doc, monitor);
                                removeDocumentCallback(isRemoved);
                                return Status.OK_STATUS;
                            }
                        };
                        removeLayerDocJob.schedule();
                    } else if (source instanceof IAttachmentSource) {
                        final IAttachmentSource featureDocSource = (IAttachmentSource) source;
                        final Job removeFeatureDocJob = new Job(Messages.DocumentView_removeDocProgressMsg) {
                            @Override
                            protected IStatus run(IProgressMonitor monitor) {
                                final boolean isRemoved = featureDocSource.remove(feature, doc, monitor);
                                removeDocumentCallback(isRemoved);
                                return Status.OK_STATUS;
                            }
                        };
                        removeFeatureDocJob.schedule();
                    } else if (source instanceof IHotlinkSource) {
                        final IHotlinkSource featureHotlinkSource = (IHotlinkSource) source;
                        final Job clearHotlinkJob = new Job(Messages.DocumentView_clearHotlinkProgressMsg) {
                            @Override
                            protected IStatus run(IProgressMonitor monitor) {
                                final IHotlink hotlinkDoc = (IHotlink) doc;
                                final String attributeName = hotlinkDoc.getAttributeName();
                                boolean isCleared = featureHotlinkSource.clear(feature, attributeName, monitor);
                                if (isCleared) {
                                    isCleared = set(attributeName, null, monitor);
                                }
                                clearHotlinkCallback(isCleared);
                                return Status.OK_STATUS;
                            }
                        };
                        clearHotlinkJob.schedule();
                    }
                }

            }
        }

    }

    /**
     * Refreshes the documents list after clearing a hotlink. This transitions the processing back
     * to the UI thread.
     */
    private void clearHotlinkCallback(final boolean isCleared) {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (!isCleared) {
                    MessageDialog.openError(attachButton.getShell(), Messages.DocumentView_clearHotlinkPopupTitle,
                            Messages.DocumentView_clearHotlinkError);
                } else {
                    viewer.refresh();
                    viewer.expandAll();
                }
            }
        });
    }

    /**
     * Refreshes the documents list after removing a document. This transitions the processing back
     * to the UI thread.
     */
    private void removeDocumentCallback(final boolean isRemoved) {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (!isRemoved) {
                    MessageDialog.openError(attachButton.getShell(), Messages.DocumentView_removeDocPopupTitle,
                            Messages.DocumentView_removeDocError);
                } else {
                    viewer.refresh();
                    viewer.expandAll();
                }
            }
        });
    }

    /**
     * Sets the attribute value of the feature.
     * 
     * @param fid
     * @param attributeName
     * @param obj
     */
    private boolean set(final String attributeName, final Object obj, IProgressMonitor monitor) {
        final FeatureId fid = feature.getIdentifier();
        final IMap map = ApplicationGIS.getActiveMap();
        if (map != null) {
            return setOnMap(map, fid, attributeName, obj);
        } else {
            return setOnGeoResource(fid, attributeName, obj, monitor);
        }
    }

    /**
     * Sets the attribute value of the feature given that the layer in on the map.
     * 
     * @param map
     * @param fid
     * @param attributeName
     * @param obj
     */
    private boolean setOnMap(final IMap map, final FeatureId fid, final String attributeName, final Object obj) {
        final SetAttributeCommand cmd = new SetAttributeCommand(attributeName, obj);
        map.sendCommandASync(cmd);
        return true;
    }

    /**
     * Sets the attribute value of the feature directly to the geoResource.
     * 
     * @param fid
     * @param attributeName
     * @param obj
     */
    private boolean setOnGeoResource(final FeatureId fid, final String attributeName, final Object obj,
            IProgressMonitor monitor) {

        try {
            if (geoResource.canResolve(SimpleFeatureStore.class)) {
                final Filter filter = CommonFactoryFinder.getFilterFactory2().id(fid);
                final SimpleFeatureStore featureStore = geoResource.resolve(SimpleFeatureStore.class, monitor);
                featureStore.modifyFeatures(attributeName, obj, filter);
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;

    }

    /**
     * Saves the current file document's file as another file.
     */
    private void saveAs() {

        final Object element = viewerSelection.getFirstElement();
        if (element instanceof IAttachment) {
            final IAttachment attachDoc = (IAttachment) element;
            final File file = (File) attachDoc.getContent();

            final FileDialog dialog = new FileDialog(saveAsButton.getShell(), SWT.SAVE);
            dialog.setText(Messages.DocumentView_saveAsDialogTitle);
            dialog.setOverwrite(true);
            dialog.setFileName(DocUtils.getSaveAsFilename(file));
            final String filePath = dialog.open();
            if (filePath != null) {
                final File newFile = new File(DocUtils.cleanFilename(filePath, file));
                final boolean isSaved = attachDoc.saveAs(newFile);
                if (isSaved) {
                    // Program.launch(file.getAbsolutePath());
                    MessageDialog.openInformation(saveAsButton.getShell(),
                            Messages.DocumentView_saveAsSuccessDialogTitle,
                            Messages.DocumentView_saveAsSuccessDialogMsg);
                } else {
                    MessageDialog.openError(saveAsButton.getShell(), Messages.DocumentView_saveAsErrorDialogTitle,
                            Messages.DocumentView_saveAsErrorDialogMsg);
                }
            }
        }

    }

    // Utility inner classes

    /**
     * The label provider for the document item tree viewer in the {@link DocumentView}.
     */
    private class DocumentViewTableLabelProvider implements ITableLabelProvider {

        private DocumentImageProvider imageProvider;

        public DocumentViewTableLabelProvider() {
            imageProvider = new DocumentImageProvider();
        }

        @Override
        public Image getColumnImage(Object element, int columnIndex) {
            switch (columnIndex) {
            case DOCUMENT_INDEX:
                if (element instanceof IDocumentFolder) {
                    return imageProvider.createFolderImage();
                } else if (element instanceof IDocument) {
                    final IDocument doc = (IDocument) element;
                    return imageProvider.createDocumentImage(doc);
                }
                return imageProvider.createDefaultImage();
            }
            return null;
        }

        @Override
        public String getColumnText(Object element, int columnIndex) {
            if (element instanceof IDocumentFolder) {
                final IDocumentFolder folder = (IDocumentFolder) element;
                switch (columnIndex) {
                case DOCUMENT_INDEX:
                    return DocUtils.toCamelCase(folder.getName());
                }
            } else if (element instanceof IDocument) {
                final IDocument doc = (IDocument) element;
                switch (columnIndex) {
                case DOCUMENT_INDEX:
                    return DocUtils.getDocStr(doc);
                case TYPE_INDEX:
                    if (doc.isTemplate()) {
                        return Messages.DocumentView_templateLbl;
                    }
                    return DocUtils.toCamelCase(doc.getContentType().toString());
                case DESCRIPTION_INDEX:
                    final String description = doc.getDescription();
                    if (description != null) {
                        return description;
                    }
                    return ""; //$NON-NLS-1$
                }
            }
            return null;
        }

        @Override
        public void dispose() {
            // Nothing
        }

        @Override
        public boolean isLabelProperty(Object element, String property) {
            return false;
        }

        @Override
        public void addListener(ILabelProviderListener listener) {
            // Nothing
        }

        @Override
        public void removeListener(ILabelProviderListener listener) {
            // Nothing
        }

    }

    /**
     * The content provider for the document item tree viewer in the {@link DocumentView}.
     */
    private class DocumentViewContentProvider implements ITreeContentProvider {

        @Override
        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof DocumentItemModel) {
                final DocumentItemModel itemModel = (DocumentItemModel) inputElement;
                return itemModel.getItems().toArray(new Object[0]);
            }
            return null;
        }

        @Override
        public Object[] getChildren(Object element) {
            if (element instanceof IDocumentFolder) {
                final IDocumentFolder folder = (IDocumentFolder) element;
                return folder.getItems().toArray(new Object[0]);
            }
            return null;
        }

        @Override
        public Object getParent(Object element) {
            return null;
        }

        @Override
        public boolean hasChildren(Object element) {
            if (element instanceof IDocumentFolder) {
                final IDocumentFolder folder = (IDocumentFolder) element;
                return folder.getItems().size() > 0;
            }
            return false;
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // Do input change stuff here
        }

        @Override
        public void dispose() {
            // Do dispose stuff here
        }

    }

    /**
     * The data model for the document item tree viewer in the {@link DocumentView}.
     */
    private class DocumentItemModel {

        private List<Object> items;

        public DocumentItemModel() {
            items = new ArrayList<Object>();
        }

        public List<Object> getItems() {
            return items;
        }

        public void setItems(List<Object> items) {
            this.items = items;
        }

        public IDocumentFolder getFolder(IDocument doc) {
            for (Object item : items) {
                if (item instanceof IDocumentFolder) {
                    final IDocumentFolder folder = (IDocumentFolder) item;
                    for (IDocument folderDoc : folder.getDocuments()) {
                        if (folderDoc.equals(doc)) {
                            return folder;
                        }
                    }
                    // if (folder.getSource().equals(doc.getSource())) {
                    // return folder;
                    // }
                }
            }
            return null;
        }

        public List<IDocument> getTemplates(IDocumentFolder folder) {
            return getTemplates(folder.getSource(), folder, null);
        }

        public List<IDocument> getTemplates(IDocument doc) {
            return getTemplates(doc.getSource(), getFolder(doc), doc);
        }

        private List<IDocument> getTemplates(IAbstractDocumentSource source, IDocumentFolder folder,
                IDocument doc) {
            if (source instanceof IDocumentSource) {
                return getTemplatesInternal(folder, doc);
            } else {
                return getTemplatesInternal(doc);
            }
        }

        private List<IDocument> getTemplatesInternal(IDocumentFolder folder, IDocument refDoc) {

            final List<IDocument> templates = new ArrayList<IDocument>();
            for (IDocument doc : folder.getDocuments()) {
                if (!doc.equals(refDoc) && doc.isTemplate()) {
                    templates.add(doc);
                }
            }
            return templates;
        }

        private List<IDocument> getTemplatesInternal(IDocument refDoc) {
            final List<IDocument> templates = new ArrayList<IDocument>();
            for (Object item : items) {
                if (item instanceof IDocumentFolder) {
                    final IDocumentFolder folder = (IDocumentFolder) item;
                    templates.addAll(getTemplatesInternal(folder, refDoc));
                }
            }
            return templates;
        }

    }

    /**
     * Gets the document folder containing the current selection. Or the current selection if it is
     * a folder.
     * 
     * @return document folder
     */
    private IDocumentFolder getDocumentFolder() {
        if (viewerSelection != null) {
            final Object obj = viewerSelection.getFirstElement();
            IDocumentFolder folder = null;
            if (obj instanceof IDocumentFolder) {
                folder = (IDocumentFolder) obj;
            } else if (obj instanceof IDocument) {
                folder = itemModel.getFolder((IDocument) obj);
            }
            return folder;
        }
        return null;
    }

    /**
     * Gets the document source of the folder containing the current selection. This should also be
     * the same document source of the current selection if it is not a folder.
     * 
     * @return document source
     */
    private IAbstractDocumentSource getDocumentSource() {
        final IDocumentFolder folder = getDocumentFolder();
        if (folder != null) {
            return folder.getSource();
        }
        return null;
    }

    /**
     * Selection provider that reports back the {@link #geoReosurce}.
     */
    public class ResourceSelectionProvider implements ISelectionProvider {
        protected ListenerList listeners;
        ISelection selection;

        public ResourceSelectionProvider() {
            listeners = new ListenerList(ListenerList.IDENTITY);
            selection = StructuredSelection.EMPTY;
        }

        @Override
        public void addSelectionChangedListener(ISelectionChangedListener listener) {
            listeners.add(listener);
        }

        @Override
        public void removeSelectionChangedListener(ISelectionChangedListener listener) {
            listeners.remove(listener);
        }

        @Override
        public ISelection getSelection() {
            return this.selection;
        }

        public void setSelection(IGeoResource resource) {
            if (resource != null) {
                this.selection = new StructuredSelection(DocumentView.this.geoResource);
            } else {
                this.selection = StructuredSelection.EMPTY; // no dice!
            }
        }

        @Override
        public void setSelection(ISelection selection) {
            this.selection = selection;
            fire();
        }

        private void fire() {
            SelectionChangedEvent event = null;
            for (Object item : listeners.getListeners()) {
                ISelectionChangedListener listener = (ISelectionChangedListener) item;
                if (event == null) {
                    event = new SelectionChangedEvent(this, this.selection);
                }
                listener.selectionChanged(event);
            }
        }
    }
}