com.bdaum.zoom.ui.internal.views.AbstractPropertiesView.java Source code

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.ui.internal.views.AbstractPropertiesView.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.views;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.EditingSupport;
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.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.operations.UndoRedoActionGroup;

import com.bdaum.zoom.cat.model.artworkOrObjectShown.ArtworkOrObjectImpl;
import com.bdaum.zoom.cat.model.artworkOrObjectShown.ArtworkOrObjectShownImpl;
import com.bdaum.zoom.cat.model.asset.Asset;
import com.bdaum.zoom.cat.model.asset.TrackRecord;
import com.bdaum.zoom.cat.model.asset.TrackRecordImpl;
import com.bdaum.zoom.cat.model.creatorsContact.ContactImpl;
import com.bdaum.zoom.cat.model.creatorsContact.CreatorsContactImpl;
import com.bdaum.zoom.cat.model.group.CriterionImpl;
import com.bdaum.zoom.cat.model.group.SmartCollection;
import com.bdaum.zoom.cat.model.group.SmartCollectionImpl;
import com.bdaum.zoom.cat.model.location.LocationImpl;
import com.bdaum.zoom.cat.model.locationCreated.LocationCreatedImpl;
import com.bdaum.zoom.cat.model.locationShown.LocationShownImpl;
import com.bdaum.zoom.core.BagChange;
import com.bdaum.zoom.core.CatalogAdapter;
import com.bdaum.zoom.core.Constants;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.Format;
import com.bdaum.zoom.core.IAssetProvider;
import com.bdaum.zoom.core.IndexedMember;
import com.bdaum.zoom.core.QueryField;
import com.bdaum.zoom.core.db.IDbManager;
import com.bdaum.zoom.core.internal.Utilities;
import com.bdaum.zoom.css.ZColumnLabelProvider;
import com.bdaum.zoom.job.OperationJob;
import com.bdaum.zoom.operations.internal.MultiModifyAssetOperation;
import com.bdaum.zoom.ui.AssetSelection;
import com.bdaum.zoom.ui.ILocationDisplay;
import com.bdaum.zoom.ui.internal.HelpContextIds;
import com.bdaum.zoom.ui.internal.Icons;
import com.bdaum.zoom.ui.internal.Icons.Icon;
import com.bdaum.zoom.ui.internal.UiActivator;
import com.bdaum.zoom.ui.internal.UiUtilities;
import com.bdaum.zoom.ui.internal.ZViewerComparator;
import com.bdaum.zoom.ui.internal.dialogs.MetadataLabelProvider;
import com.bdaum.zoom.ui.internal.job.SupplyPropertyJob;
import com.bdaum.zoom.ui.internal.preferences.MetadataPreferencePage;
import com.bdaum.zoom.ui.preferences.PreferenceConstants;

@SuppressWarnings("restriction")
public abstract class AbstractPropertiesView extends BasicView implements ISelectionProvider, IFieldUpdater {

    private final class DetailsViewerFilter extends ViewerFilter {
        @Override
        public boolean select(Viewer aViewer, Object parentElement, Object element) {
            if (element instanceof QueryField) {
                if (((QueryField) element).isHidden() || !isApplicable((QueryField) element))
                    return false;
                switch (mode) {
                case MODE_SHOW_ESSENTIAL:
                    return essentials.contains(element);
                case MODE_SHOW_EDITABLE:
                    return isEditable((QueryField) element);
                }
            }
            return true;
        }
    }

    private final class ContentTypeViewerFilter extends ViewerFilter {

        @Override
        public boolean select(Viewer aViewer, Object parentElement, Object element) {
            return (element instanceof QueryField) ? ((QueryField) element).testFlags(flags) : true;
        }
    }

    public class ViewEditingSupport extends EditingSupport {

        public ViewEditingSupport(ColumnViewer viewer) {
            super(viewer);
        }

        @Override
        protected boolean canEdit(Object element) {
            if (dbIsReadonly())
                return false;
            if (element instanceof IndexedMember)
                return true;
            if (element instanceof QueryField) {
                QueryField qfield = (QueryField) element;
                if (isEditable(qfield) && isApplicable(qfield) && qfield.getChildren().length == 0) {
                    if (qfield.isStruct() && qfield.getCard() != 1)
                        return false;
                    Object value = getFieldValue(qfield);
                    return (value != QueryField.VALUE_MIXED
                            || ((qfield.getCard() == 1 || qfield.getCard() == QueryField.CARD_MODIFIABLEBAG)
                                    && qfield != QueryField.NAME))
                            && value != QueryField.VALUE_NOTHING && value != FieldEntry.PENDING.value;
                }
            }
            return false;
        }

        @Override
        protected CellEditor getCellEditor(Object element) {
            if (element instanceof IndexedMember)
                element = ((IndexedMember) element).getQfield();
            CellEditor cellEditor = null;
            if (element instanceof QueryField) {
                cellEditor = determineCellEditor((QueryField) element, viewer.getTree(), 60);
                if (cellEditor instanceof AbstractMixedBagDialogCellEditor) {
                    List<Asset> assets = getNavigationHistory().getSelectedAssets().getAssets();
                    ((AbstractMixedBagDialogCellEditor) cellEditor)
                            .setCommonItems(((QueryField) element).getCommonItems(assets), assets);
                }
            }
            return cellEditor;
        }

        @Override
        protected Object getValue(Object element) {
            if (element instanceof IndexedMember)
                return ((IndexedMember) element).getValue();
            if (element instanceof QueryField) {
                QueryField qfield = (QueryField) element;
                Object fieldValue = getFieldValue(qfield);
                if (qfield.getCard() == QueryField.CARD_MODIFIABLEBAG)
                    return fieldValue;
                if (fieldValue instanceof Integer && qfield.getEnumeration() == null)
                    return String.valueOf(fieldValue);
                if (fieldValue instanceof Double) {
                    NumberFormat nf = NumberFormat.getInstance();
                    if (qfield.getType() == QueryField.T_CURRENCY) {
                        int digits = Format.getCurrencyDigits();
                        nf.setMaximumFractionDigits(digits);
                        nf.setMinimumFractionDigits(digits);
                    } else
                        nf.setMaximumFractionDigits(qfield.getMaxlength());
                    return nf.format(fieldValue);
                }
                if (fieldValue == QueryField.VALUE_MIXED)
                    return ""; //$NON-NLS-1$
                if (fieldValue != null)
                    return fieldValue;
                if (qfield.getCard() < 0)
                    return EMPTYSTRINGS;
            }
            return ""; //$NON-NLS-1$
        }

        @Override
        protected void setValue(Object element, Object value) {
            if (value == null)
                return;
            IndexedMember indexedMember = null;
            Object oldvalue = null;
            if (element instanceof IndexedMember) {
                indexedMember = (IndexedMember) element;
                oldvalue = indexedMember.getValue();
                element = indexedMember.getQfield();
            }
            if (element instanceof QueryField) {
                QueryField qfield = (QueryField) element;
                int type = qfield.getType();
                if (qfield.getKey() != null && type != QueryField.T_NONE) {
                    if (qfield.getCard() == QueryField.CARD_MODIFIABLEBAG) {
                        if (value instanceof BagChange)
                            updateAssets(value, oldvalue = getFieldValue(qfield), qfield);
                        return;
                    }
                    if (indexedMember == null)
                        oldvalue = getFieldValue(qfield);
                    switch (type) {
                    case QueryField.T_INTEGER:
                    case QueryField.T_POSITIVEINTEGER:
                        if (!(value instanceof Integer))
                            try {
                                value = Integer.valueOf(Integer.parseInt(value.toString()));
                            } catch (NumberFormatException e) {
                                return;
                            }
                        break;
                    case QueryField.T_LONG:
                    case QueryField.T_POSITIVELONG:
                        if (!(value instanceof Long))
                            try {
                                value = Long.valueOf(Long.parseLong(value.toString()));
                            } catch (NumberFormatException e) {
                                return;
                            }
                        break;
                    case QueryField.T_BOOLEAN:
                        if (!(value instanceof Boolean))
                            value = Boolean.valueOf((Boolean.parseBoolean(value.toString())));
                        break;
                    case QueryField.T_FLOAT:
                    case QueryField.T_FLOATB:
                    case QueryField.T_POSITIVEFLOAT:
                    case QueryField.T_CURRENCY:
                        if (!(value instanceof Double)) {
                            NumberFormat nf = NumberFormat.getInstance();
                            nf.setMaximumFractionDigits(8);
                            try {
                                value = new Double(nf.parse(value.toString()).doubleValue());
                            } catch (ParseException e) {
                                return;
                            }
                        }
                        break;
                    case QueryField.T_DATE:
                        if (value == QueryField.EMPTYDATE)
                            value = null;
                        else if (!(value instanceof Date))
                            return;
                    }
                    updateAssetsIfNecessary(qfield, value, oldvalue);
                }
            }
        }
    }

    public class ValueColumnLabelProvider extends ZColumnLabelProvider {

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

        public String getText(Object element) {
            if (element instanceof QueryField) {
                QueryField qfield = (QueryField) element;
                if (qfield.getType() == QueryField.T_NONE)
                    return ""; //$NON-NLS-1$
                Object value = getFieldValue(qfield);
                if (value == FieldEntry.PENDING.value)
                    return (String) value;
                String text = qfield.value2text(value, CLICK_TO_VIEW_DETAILS);
                if (text != null) {
                    if (!text.isEmpty() && value != QueryField.VALUE_MIXED && value != QueryField.VALUE_NOTHING
                            && text != Format.MISSINGENTRYSTRING) {
                        String unit = qfield.getUnit();
                        if (unit != null)
                            return new StringBuilder(text).append(' ').append(unit).toString();
                        if (value instanceof String[] && ((String[]) value).length > 0)
                            return UiUtilities.addExplanation(qfield, (String[]) value, text);
                    }
                    return text;
                }
            } else if (element instanceof IndexedMember)
                return QueryField.serializeStruct(((IndexedMember) element).getValue(), CLICK_TO_VIEW_DETAILS);
            else if (element instanceof TrackRecord)
                return QueryField.TRACK.getFormatter().toString(element);
            else if (element instanceof String) {
                String s = (String) element;
                int p = s.indexOf(':');
                if (p > 0)
                    return s.substring(p + 1).trim();
            }
            return null;
        }
    }

    public class ActionColumnLabelProvider extends ZColumnLabelProvider {

        private final Rectangle ICONSIZE = Icons.query.getImage().getBounds();

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

        @Override
        public String getToolTipText(Object element) {
            if (element instanceof QueryField) {
                String tooltip = null;
                QueryField qfield = (QueryField) element;
                switch (qfield.getAction()) {
                case QueryField.ACTION_QUERY:
                    tooltip = Messages.getString("AbstractPropertiesView.search_similar"); //$NON-NLS-1$
                    break;
                case QueryField.ACTION_MAP:
                    if (UiActivator.getDefault().getLocationDisplay() != null)
                        tooltip = Messages.getString("AbstractPropertiesView.show_in_map"); //$NON-NLS-1$
                    break;
                case QueryField.ACTION_WWW:
                    tooltip = Messages.getString("AbstractPropertiesView.open_in_web_Browser"); //$NON-NLS-1$
                    break;
                case QueryField.ACTION_EMAIL:
                    tooltip = Messages.getString("AbstractPropertiesView.send_an_email"); //$NON-NLS-1$
                    break;
                case QueryField.ACTION_TOFOLDER:
                    tooltip = Messages.getString("AbstractPropertiesView.show_in_folder"); //$NON-NLS-1$
                    break;
                }
                if (tooltip != null)
                    return filterEmptySlots(qfield) ? tooltip : null;
            }
            return null;
        }

        public Image getImage(Object element) {
            if (element instanceof QueryField) {
                Icon icon = null;
                QueryField qfield = (QueryField) element;
                Object value = getFieldValue(qfield);
                if (value == null || value == FieldEntry.PENDING.value || value == QueryField.VALUE_MIXED
                        || value == QueryField.VALUE_NOTHING)
                    return null;
                switch (qfield.getAction()) {
                case QueryField.ACTION_QUERY:
                    icon = Icons.query;
                    break;
                case QueryField.ACTION_MAP:
                    if (UiActivator.getDefault().getLocationDisplay() != null)
                        icon = Icons.map;
                    break;
                case QueryField.ACTION_WWW:
                    icon = Icons.www;
                    break;
                case QueryField.ACTION_EMAIL:
                    icon = Icons.email;
                    break;
                case QueryField.ACTION_TOFOLDER:
                    icon = Icons.folder;
                    break;
                }
                if (icon != null)
                    return filterEmptySlots(qfield) ? icon.getImage() : null;
            } else if (element instanceof TrackRecord && ((TrackRecord) element).getVisit() != null)
                return Icons.www.getImage();
            return null;
        }

        @Override
        protected Rectangle getIconBounds() {
            return ICONSIZE;
        }

    }

    private static final Object[] EMPTYOBJECTS = new Object[0];
    private static final String[] EMPTYSTRINGS = new String[0];

    public class MetadataContentProvider implements ITreeContentProvider {

        public void dispose() {
            // do nothing
        }

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

        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof QueryField) {
                QueryField qfield = (QueryField) parentElement;
                switch (qfield.getType()) {
                case QueryField.T_LOCATION:
                    if (qfield == QueryField.IPTC_LOCATIONSHOWN)
                        return createIndexedGroup(qfield, collectLocationsShown());
                    return EMPTYOBJECTS;
                case QueryField.T_CONTACT:
                    return EMPTYOBJECTS;
                case QueryField.T_STRING:
                    if (qfield == QueryField.EXIF_MAKERNOTES) {
                        Object v = getFieldValue(qfield);
                        if (v == QueryField.VALUE_MIXED)
                            return MIXEDARRAY;
                        return (v instanceof String[]) ? (String[]) v : EMPTYOBJECTS;
                    }
                    if (qfield == QueryField.TRACK)
                        return collectTracks();
                    break;
                case QueryField.T_OBJECT:
                    return createIndexedGroup(qfield, collectArtwork());
                }
                List<QueryField> fields = new ArrayList<QueryField>();
                for (QueryField field : qfield.getChildren())
                    if (field.isUiField())
                        fields.add(field);
                return fields.toArray();
            }
            return EMPTYOBJECTS;
        }

        public Object getParent(Object element) {
            if (element instanceof QueryField)
                return getFieldParent((QueryField) element);
            if (element instanceof IndexedMember)
                return getFieldParent(((IndexedMember) element).getQfield());
            return null;

        }

        public boolean hasChildren(Object element) {
            if (element instanceof QueryField) {
                QueryField qfield = (QueryField) element;
                if (qfield == QueryField.TRACK || qfield == QueryField.EXIF_MAKERNOTES) {
                    Object fieldValue = getFieldValue(qfield);
                    return (fieldValue instanceof String[]) && ((String[]) fieldValue).length > 0;
                }
                return (qfield.isStruct() && qfield.getCard() != 1) ? true : qfield.getChildren().length > 0;
            }
            return false;
        }

        public Object[] getElements(Object inputElement) {
            return (inputElement instanceof QueryField) ? ((QueryField) inputElement).getChildren() : EMPTYOBJECTS;
        }
    }

    private static final String FILTER = "filter"; //$NON-NLS-1$
    private static final String EXPANDED = "expanded"; //$NON-NLS-1$
    private static final String SELECTED = "selected"; //$NON-NLS-1$

    static final String LABELCOL = "label"; //$NON-NLS-1$
    static final String VALUECOL = "value"; //$NON-NLS-1$
    static final String IMAGECOL = "image"; //$NON-NLS-1$
    static final String[] UPDATECOLS = new String[] { LABELCOL, VALUECOL, IMAGECOL };

    private static final String CLICK_TO_VIEW_DETAILS = Messages
            .getString("AbstractPropertiesView.click_to_view_details"); //$NON-NLS-1$

    private TreeViewer viewer;
    private IPreferenceChangeListener preferenceListener;
    private String expandedElements;
    private String selectedElement;

    private Action essentialAction;
    private Action allAction;

    protected int mode = MODE_SHOW_ALL;

    protected static final int MODE_SHOW_ALL = 0;
    protected static final int MODE_SHOW_ESSENTIAL = 1;
    protected static final int MODE_SHOW_EDITABLE = 2;

    private static final int ASYNCTHRESHOLD = 20;

    private Action deleteAction;

    private Action configureAction;

    private Action editableAction;

    protected Map<QueryField, FieldEntry> valueMap = new HashMap<QueryField, FieldEntry>();

    private Action fieldAction;

    private static Set<QueryField> essentials;

    private int flags;
    private Action expandAction;
    private Action collapseAction;

    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        if (memento != null) {
            Integer filt = memento.getInteger(FILTER);
            if (filt != null)
                mode = filt;
            expandedElements = memento.getString(EXPANDED);
            selectedElement = memento.getString(SELECTED);
        }
    }

    @Override
    public void saveState(IMemento memento) {
        if (memento != null) {
            memento.putInteger(FILTER, mode);
            StringBuilder sb = new StringBuilder();
            Object[] expandedElements = viewer.getExpandedElements();
            for (Object element : expandedElements) {
                String id = ((QueryField) element).getId();
                if (sb.length() > 0)
                    sb.append(' ');
                sb.append(id);
            }
            memento.putString(EXPANDED, sb.toString());
            Object element = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
            if (element instanceof QueryField)
                memento.putString(SELECTED, ((QueryField) element).getId());
        }
        super.saveState(memento);
    }

    private static final Object[] MIXEDARRAY = new Object[] { QueryField.VALUE_MIXED };

    public Object[] collectTracks() {
        Object v = QueryField.TRACK.obtainFieldValue(getNavigationHistory().getSelectedAssets().getAssets(), null);
        if (v == null)
            return EMPTYOBJECTS;
        if (v == QueryField.VALUE_MIXED)
            return MIXEDARRAY;
        String[] ids = (String[]) v;
        if (ids.length == 0)
            return EMPTYOBJECTS;
        List<TrackRecordImpl> records = new ArrayList<TrackRecordImpl>(ids.length);
        IDbManager dbManager = Core.getCore().getDbManager();
        for (int i = 0; i < ids.length; i++) {
            TrackRecordImpl record = dbManager.obtainById(TrackRecordImpl.class, ids[i]);
            if (record != null)
                records.add(record);
        }
        if (records.size() > 1)
            Collections.sort(records, new Comparator<TrackRecordImpl>() {
                public int compare(TrackRecordImpl t1, TrackRecordImpl t2) {
                    return t1.getExportDate().compareTo(t2.getExportDate());
                }
            });
        return records.toArray();
    }

    public Object[] collectLocationsShown() {
        IDbManager dbManager = Core.getCore().getDbManager();
        Set<LocationImpl> result = new HashSet<LocationImpl>();
        for (Asset asset : getNavigationHistory().getSelectedAssets().getAssets()) {
            String assetId = asset.getStringId();
            final List<LocationShownImpl> set = dbManager.obtainStructForAsset(LocationShownImpl.class, assetId,
                    false);
            if (set.isEmpty())
                return null;
            String[] ids = new String[set.size()];
            int i = 0;
            for (LocationShownImpl rel : set)
                ids[i++] = rel.getLocation();
            List<LocationImpl> set2 = dbManager.obtainStructByIds(assetId, LocationImpl.class, ids);
            if (set2.isEmpty())
                return null;
            if (result.isEmpty())
                result.addAll(set2);
            else
                result.retainAll(set2);
        }
        return result.toArray();
    }

    public Object[] createIndexedGroup(QueryField qfield, Object[] members) {
        int l = members == null ? 1 : members.length + 1;
        Object[] result = new Object[l];
        if (members != null)
            for (int i = 0; i < members.length; i++)
                result[i] = new IndexedMember(qfield, members[i], i);
        result[l - 1] = new IndexedMember(qfield, null, l - 1);
        return result;
    }

    public Object[] collectArtwork() {
        IDbManager dbManager = Core.getCore().getDbManager();
        Set<ArtworkOrObjectImpl> result = new HashSet<ArtworkOrObjectImpl>();
        for (Asset asset : getNavigationHistory().getSelectedAssets().getAssets()) {
            String assetId = asset.getStringId();
            final List<ArtworkOrObjectShownImpl> set = dbManager
                    .obtainStructForAsset(ArtworkOrObjectShownImpl.class, assetId, false);
            if (set.isEmpty())
                return null;
            String[] ids = new String[set.size()];
            int i = 0;
            for (ArtworkOrObjectShownImpl rel : set)
                ids[i++] = rel.getArtworkOrObject();
            List<ArtworkOrObjectImpl> set2 = dbManager.obtainStructByIds(assetId, ArtworkOrObjectImpl.class, ids);
            if (set2.isEmpty())
                return null;
            if (result.isEmpty())
                result.addAll(set2);
            else
                result.retainAll(set2);
        }
        return result.toArray();
    }

    protected abstract Object getFieldParent(QueryField element);

    public abstract QueryField getRootElement();

    /**
     * This is a callback that will allow us to create the viewer and initialize it.
     */

    @SuppressWarnings("unused")
    @Override
    public void createPartControl(Composite parent) {
        computeFlags();
        if (essentials == null) {
            essentials = new HashSet<QueryField>(100);
            computeEssentials();
        }
        preferenceListener = new IPreferenceChangeListener() {
            public void preferenceChange(PreferenceChangeEvent event) {
                if (PreferenceConstants.ESSENTIALMETADATA.equals(event.getKey())) {
                    computeEssentials();
                    refreshInternal();
                }
            }
        };
        InstanceScope.INSTANCE.getNode(UiActivator.PLUGIN_ID).addPreferenceChangeListener(preferenceListener);
        viewer = new TreeViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        int[] colWidth = getColumnWidths();
        TreeViewerColumn col1 = new TreeViewerColumn(viewer, SWT.NONE);
        final Tree tree = viewer.getTree();
        tree.setLinesVisible(true);
        tree.setHeaderVisible(true);
        final ViewEditingSupport editingSupport = new ViewEditingSupport(viewer);
        col1.getColumn().setWidth(colWidth[0]);
        col1.getColumn().setText(Messages.getString("AbstractPropertiesView.Name")); //$NON-NLS-1$
        col1.setLabelProvider(new MetadataLabelProvider() {
            @Override
            public String getText(Object element) {
                return (editingSupport.canEdit(element)) ? Format.EDITABLEINDICATOR + super.getText(element)
                        : super.getText(element);
            }
        });
        TreeViewerColumn col2 = new TreeViewerColumn(viewer, SWT.NONE);
        col2.getColumn().setWidth(colWidth[1]);
        col2.getColumn().setText(Messages.getString("AbstractPropertiesView.Value")); //$NON-NLS-1$
        col2.setLabelProvider(new ValueColumnLabelProvider());
        col2.setEditingSupport(editingSupport);
        TreeViewerColumn col3 = new TreeViewerColumn(viewer, SWT.NONE);
        col3.getColumn().setWidth(colWidth[2]);
        col3.setLabelProvider(new ActionColumnLabelProvider());
        viewer.setContentProvider(new MetadataContentProvider());
        viewer.setComparator(ZViewerComparator.INSTANCE);
        viewer.setFilters(new ViewerFilter[] { new DetailsViewerFilter(), new ContentTypeViewerFilter() });
        ZColumnViewerToolTipSupport.enableFor(viewer);
        viewer.setInput(getRootElement());
        parent.getDisplay().asyncExec(() -> {
            if (!parent.isDisposed()) {
                if (expandedElements != null) {
                    List<QueryField> elements = new ArrayList<QueryField>();
                    StringTokenizer st = new StringTokenizer(expandedElements);
                    while (st.hasMoreTokens()) {
                        QueryField qf1 = QueryField.findQueryField(st.nextToken());
                        if (qf1 != null)
                            elements.add(qf1);
                    }
                    viewer.setExpandedElements(elements.toArray());
                    expandedElements = null;
                } else
                    viewer.expandToLevel(getExpandLevel());
                if (selectedElement != null) {
                    QueryField qf2 = QueryField.findQueryField(selectedElement);
                    if (qf2 != null)
                        viewer.setSelection(new StructuredSelection(qf2), true);
                    selectedElement = null;
                }
            }
        });
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                updateActions(false);
            }
        });
        UiUtilities.installDoubleClickExpansion(viewer);
        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                int col = -1;
                int x = e.x;
                for (int i = 0; i < 3; i++) {
                    int w = tree.getColumn(i).getWidth();
                    if (x < w) {
                        col = i;
                        break;
                    }
                    x -= w;
                }
                if (col == 2) {
                    TreeItem item = tree.getItem(new Point(e.x, e.y));
                    Object element = item.getData();
                    if (element instanceof QueryField) {
                        QueryField qfield = (QueryField) element;
                        if (qfield.getAction() != QueryField.ACTION_NONE)
                            processAction(qfield, (e.stateMask & SWT.SHIFT) != 0);
                    } else if (element instanceof TrackRecord) {
                        String visit = ((TrackRecord) element).getVisit();
                        if (visit != null)
                            showWebUrl(visit);
                    }
                }
            }
        });
        new ColumnLayoutManager(viewer, getColumnWidths(), getColumnMaxWidths());
        addKeyListener();
        addGestureListener(tree);
        // Create the help context id for the viewer's control
        PlatformUI.getWorkbench().getHelpSystem().setHelp(viewer.getControl(), HelpContextIds.METADATA_VIEW);
        makeActions();
        installListeners();
        hookContextMenu();
        contributeToActionBars();
        final QueryField.Visitor qfVisitor = new QueryField.Visitor() {
            @Override
            public void doVisitorWork(QueryField node) {
                viewer.refresh(node);
            }
        };
        Core.getCore().addCatalogListener(new CatalogAdapter() {
            @Override
            public void assetsModified(BagChange<Asset> changes, final QueryField node) {
                for (Asset asset : getNavigationHistory().getSelectedAssets()) {
                    if (changes == null || changes.hasChanged(asset)) {
                        Shell shell = getSite().getShell();
                        if (shell != null && !shell.isDisposed())
                            shell.getDisplay().asyncExec(() -> {
                                if (!tree.isDisposed()) {
                                    resetCaches();
                                    if (node != null)
                                        viewer.expandToLevel(node, qfVisitor.visit(node));
                                    else
                                        refresh();
                                }
                            });
                        break;
                    }
                }
            }
        });
        updateActions(false);
    }

    protected void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                AbstractPropertiesView.this.fillContextMenu(manager);
            }
        });
        Menu menu = menuMgr.createContextMenu(getControl());
        getControl().setMenu(menu);
        getSite().registerContextMenu(menuMgr, this);
    }

    protected void fillContextMenu(IMenuManager manager) {
        updateActions(true);
        Object firstElement = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
        if (firstElement instanceof QueryField) {
            Icon icon = null;
            String text = null;
            switch (((QueryField) firstElement).getAction()) {
            case QueryField.ACTION_QUERY:
                icon = Icons.query;
                text = Messages.getString("AbstractPropertiesView.search_similar"); //$NON-NLS-1$
                break;
            case QueryField.ACTION_MAP:
                if (UiActivator.getDefault().getLocationDisplay() != null) {
                    icon = Icons.map;
                    text = Messages.getString("AbstractPropertiesView.show_in_map"); //$NON-NLS-1$
                }
                break;
            case QueryField.ACTION_WWW:
                icon = Icons.www;
                text = Messages.getString("AbstractPropertiesView.open_in_web_Browser"); //$NON-NLS-1$
                break;
            case QueryField.ACTION_EMAIL:
                icon = Icons.email;
                text = Messages.getString("AbstractPropertiesView.send_an_email"); //$NON-NLS-1$
                break;
            case QueryField.ACTION_TOFOLDER:
                icon = Icons.folder;
                text = Messages.getString("AbstractPropertiesView.show_in_folder"); //$NON-NLS-1$
                break;
            }
            if (text != null && icon != null && filterEmptySlots((QueryField) firstElement)) {
                fieldAction.setImageDescriptor(icon.getDescriptor());
                fieldAction.setText(text);
                manager.add(fieldAction);
            }
        }
        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    public Control getControl() {
        return viewer.getControl();
    }

    private void computeEssentials() {
        essentials.clear();
        String ess = Platform.getPreferencesService().getString(UiActivator.PLUGIN_ID,
                PreferenceConstants.ESSENTIALMETADATA, "", null); //$NON-NLS-1$
        StringTokenizer st = new StringTokenizer(ess, "\n"); //$NON-NLS-1$
        while (st.hasMoreTokens()) {
            QueryField qfield = QueryField.findQueryField(st.nextToken());
            if (qfield != null && qfield.testFlags(flags))
                while (qfield != null) {
                    essentials.add(qfield);
                    qfield = qfield.getParent();
                }
        }
    }

    private boolean filterEmptySlots(QueryField qfield) {
        Object value = getFieldValue(qfield);
        if (value == null || value == QueryField.VALUE_NOTHING || value == QueryField.VALUE_MIXED
                || value == FieldEntry.PENDING.value)
            return false;
        if (qfield.getEnumeration() != null && Format.MISSINGENTRYSTRING == qfield.formatScalarValue(value))
            return false;
        switch (qfield.getType()) {
        case QueryField.T_STRING:
            return !(value instanceof String && ((String) value).isEmpty());
        case QueryField.T_POSITIVEINTEGER:
            return !(value instanceof Integer && ((Integer) value).intValue() < 0);
        case QueryField.T_POSITIVELONG:
            return !(value instanceof Long && ((Long) value).longValue() < 0);
        case QueryField.T_POSITIVEFLOAT:
        case QueryField.T_CURRENCY:
        case QueryField.T_FLOAT:
        case QueryField.T_FLOATB:
            if (value instanceof Float)
                return !(((Float) value).isNaN());
            if (value instanceof Double)
                return !(((Double) value).isNaN());
            break;
        }
        return true;
    }

    protected void processAction(QueryField qfield, boolean shift) {
        switch (qfield.getAction()) {
        case QueryField.ACTION_QUERY:
            Object fieldValue = getFieldValue(qfield);
            SmartCollection sm = createAdhocQuery(qfield, fieldValue, shift);
            getNavigationHistory().postSelection(new StructuredSelection(sm));
            break;
        case QueryField.ACTION_TOFOLDER:
            Asset asset = getNavigationHistory().getSelectedAssets().getFirstElement();
            sm = Utilities.obtainFolderCollection(Core.getCore().getDbManager(), asset.getUri(), asset.getVolume());
            if (sm != null) {
                try {
                    ((CatalogView) getSite().getPage().showView(CatalogView.ID))
                            .setSelection(new StructuredSelection(sm), true);
                } catch (PartInitException e1) {
                    // should never happen
                }
            }
            break;
        case QueryField.ACTION_MAP:
            ILocationDisplay locationDisplay = UiActivator.getDefault().getLocationDisplay();
            if (locationDisplay != null) {
                Object lat = getFieldValue(QueryField.EXIF_GPSLATITUDE);
                Object lon = getFieldValue(QueryField.EXIF_GPSLONGITUDE);
                if (lat instanceof Double && lon instanceof Double) {
                    double latitude = ((Double) lat).doubleValue();
                    double longitude = ((Double) lon).doubleValue();
                    if (!Double.isNaN(latitude) && !Double.isNaN(longitude)) {
                        LocationImpl loc = new LocationImpl();
                        loc.setLatitude(latitude);
                        loc.setLongitude(longitude);
                        locationDisplay.display(loc);
                    }
                }
            }
            break;
        case QueryField.ACTION_WWW:
            fieldValue = getFieldValue(qfield);
            showWebUrl((String) fieldValue);
            break;
        case QueryField.ACTION_EMAIL:
            fieldValue = getFieldValue(qfield);
            sendMail(fieldValue);
            break;
        }
    }

    private static void sendMail(Object recipients) {
        List<String> to;
        if (recipients instanceof String)
            to = Core.fromStringList((String) recipients, ";"); //$NON-NLS-1$
        else if (recipients instanceof String[])
            to = Arrays.asList((String[]) recipients);
        else
            return;
        UiActivator.getDefault().sendMail(to);
    }

    private static void showWebUrl(String fieldValue) {
        try {
            showWebPage(fieldValue);
        } catch (PartInitException e) {
            UiActivator.getDefault().logError(
                    Messages.getString("AbstractPropertiesView.cannot_instantiate_external_web_browser"), e); //$NON-NLS-1$
        }
    }

    private static void showWebPage(String fieldValue) throws PartInitException {
        IWebBrowser browser = PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser();
        try {
            if (!fieldValue.startsWith("http://")) //$NON-NLS-1$
                fieldValue = "http://" + fieldValue; //$NON-NLS-1$
            browser.openURL(new URL(fieldValue));
        } catch (MalformedURLException e) {
            try {
                browser.openURL(new URL("http://www.google.com/search?q=\"" //$NON-NLS-1$
                        + fieldValue + "\"")); //$NON-NLS-1$
            } catch (MalformedURLException e1) {
                // ignore
            }
        }
    }

    private static SmartCollection createAdhocQuery(QueryField qfield, Object fieldValue, boolean shift) {
        String text = qfield.value2text(fieldValue, CLICK_TO_VIEW_DETAILS);
        if (text == null)
            text = "?"; //$NON-NLS-1$
        String rel = null;
        SmartCollection coll = new SmartCollectionImpl("", true, false, false, true, null, 0, null, 0, null, //$NON-NLS-1$
                Constants.INHERIT_LABEL, null, 0, null);
        if (qfield.getCard() != 1) {
            rel = ":"; //$NON-NLS-1$
            if (fieldValue instanceof LocationImpl[]) {
                LocationImpl[] array = (LocationImpl[]) fieldValue;
                for (int i = 0; i < array.length; i++)
                    coll.addCriterion(new CriterionImpl(qfield.getKey(), null, array[i].getStringId(),
                            QueryField.EQUALS, true));
            } else if (fieldValue instanceof ArtworkOrObjectImpl[]) {
                ArtworkOrObjectImpl[] array = (ArtworkOrObjectImpl[]) fieldValue;
                for (int i = 0; i < array.length; i++)
                    coll.addCriterion(new CriterionImpl(qfield.getKey(), null, array[i].getStringId(),
                            QueryField.EQUALS, true));
            } else if (fieldValue instanceof ContactImpl[]) {
                ContactImpl[] array = (ContactImpl[]) fieldValue;
                for (int i = 0; i < array.length; i++)
                    coll.addCriterion(new CriterionImpl(qfield.getKey(), null, array[i].getStringId(),
                            QueryField.EQUALS, true));
            } else if (fieldValue instanceof int[]) {
                int[] array = (int[]) fieldValue;
                for (int i = 0; i < array.length; i++)
                    coll.addCriterion(new CriterionImpl(qfield.getKey(), null, array[i], QueryField.EQUALS, true));
            } else if (fieldValue instanceof String[]) {
                String[] array = (String[]) fieldValue;
                for (int i = 0; i < array.length; i++)
                    coll.addCriterion(new CriterionImpl(qfield.getKey(), null, array[i], QueryField.EQUALS, true));
            }
        } else {
            if (fieldValue instanceof LocationImpl)
                fieldValue = ((LocationImpl) fieldValue).getStringId();
            else if (fieldValue instanceof ArtworkOrObjectImpl)
                fieldValue = ((ArtworkOrObjectImpl) fieldValue).getStringId();
            else if (fieldValue instanceof ContactImpl)
                fieldValue = ((ContactImpl) fieldValue).getStringId();
            boolean equ = qfield.getTolerance() == 0f;
            coll.addCriterion(new CriterionImpl(qfield.getKey(), null, fieldValue,
                    equ ? QueryField.EQUALS : QueryField.SIMILAR, false));
            rel = equ ? "=" : "=~"; //$NON-NLS-1$ //$NON-NLS-2$
        }
        coll.setName(qfield.getLabel() + rel + text);
        if (shift) {
            IAssetProvider assetProvider = Core.getCore().getAssetProvider();
            if (assetProvider != null) {
                SmartCollectionImpl parentCollection = assetProvider.getParentCollection();
                if (parentCollection != null)
                    coll.setSmartCollection_subSelection_parent(parentCollection);
            }
        }
        return coll;
    }

    @Override
    public void dispose() {
        if (preferenceListener != null)
            InstanceScope.INSTANCE.getNode(UiActivator.PLUGIN_ID)
                    .removePreferenceChangeListener(preferenceListener);
        resetCaches();
        super.dispose();
    }

    protected abstract int[] getColumnWidths();

    @Override
    public void updateActions(boolean force) {
        if ((isVisible() || force) && !viewer.getControl().isDisposed()) {
            if (deleteAction != null) {
                Object firstElement = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
                deleteAction.setEnabled((firstElement instanceof IndexedMember
                        || firstElement instanceof QueryField && ((QueryField) firstElement).isStruct())
                        && !dbIsReadonly());
            }
            updateActions(-1, -1);
        }
    }

    protected abstract int getExpandLevel();

    private void contributeToActionBars() {
        undoContext = PlatformUI.getWorkbench().getOperationSupport().getUndoContext();
        IViewSite viewSite = getViewSite();
        IActionBars bars = viewSite.getActionBars();
        fillLocalToolbar(bars.getToolBarManager());
        fillLocalPullDown(bars.getMenuManager());
        new UndoRedoActionGroup(viewSite, undoContext, true).fillActionBars(bars);
    }

    private void fillLocalToolbar(IToolBarManager manager) {
        if (deleteAction != null) {
            manager.add(expandAction);
            manager.add(collapseAction);
            manager.add(deleteAction);
        }
    }

    protected void fillLocalPullDown(IMenuManager manager) {
        if (essentialAction != null && allAction != null) {
            manager.add(expandAction);
            manager.add(collapseAction);
            manager.add(new Separator());
            manager.add(editableAction);
            manager.add(essentialAction);
            manager.add(allAction);
            manager.add(new Separator());
            manager.add(configureAction);
        }
    }

    @Override
    protected void makeActions() {
        super.makeActions();
        essentialAction = new Action(Messages.getString("AbstractPropertiesView.essential"), //$NON-NLS-1$
                IAction.AS_RADIO_BUTTON) {
            @Override
            public void run() {
                mode = MODE_SHOW_ESSENTIAL;
                refreshInternal();
            }
        };
        essentialAction.setToolTipText(Messages.getString("AbstractPropertiesView.show_only_essential")); //$NON-NLS-1$
        allAction = new Action(Messages.getString("AbstractPropertiesView.all"), IAction.AS_RADIO_BUTTON) { //$NON-NLS-1$
            @Override
            public void run() {
                mode = MODE_SHOW_ALL;
                refreshInternal();
            }
        };
        allAction.setToolTipText(Messages.getString("AbstractPropertiesView.show_all_entries")); //$NON-NLS-1$
        editableAction = new Action(Messages.getString("AbstractPropertiesView.editable"), //$NON-NLS-1$
                IAction.AS_RADIO_BUTTON) {
            @Override
            public void run() {
                mode = MODE_SHOW_EDITABLE;
                refreshInternal();
            }
        };
        editableAction.setToolTipText(Messages.getString("AbstractPropertiesView.editable_tooltip")); //$NON-NLS-1$
        switch (mode) {
        case MODE_SHOW_ALL:
            allAction.setChecked(true);
            break;
        case MODE_SHOW_ESSENTIAL:
            essentialAction.setChecked(true);
            break;
        case MODE_SHOW_EDITABLE:
            editableAction.setChecked(true);
            break;
        }
        deleteAction = new Action(Messages.getString("AbstractPropertiesView.delete"), //$NON-NLS-1$
                Icons.delete.getDescriptor()) {
            @Override
            public void run() {
                deleteSubentry();
            }
        };
        deleteAction.setToolTipText(Messages.getString("AbstractPropertiesView.delete_subentry")); //$NON-NLS-1$
        configureAction = new Action(Messages.getString("AbstractPropertiesView.Configure")) { //$NON-NLS-1$
            @Override
            public void run() {
                PreferencesUtil
                        .createPreferenceDialogOn(getSite().getShell(), MetadataPreferencePage.ID, new String[0],
                                "essential") //$NON-NLS-1$
                        .open();
            }
        };
        configureAction.setToolTipText(Messages.getString("AbstractPropertiesView.Configure_tooltip")); //$NON-NLS-1$
        fieldAction = new Action(Messages.getString("AbstractPropertiesView.find_similar")) { //$NON-NLS-1$
            @Override
            public void runWithEvent(Event event) {
                Object firstElement = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
                if (firstElement instanceof QueryField)
                    processAction((QueryField) firstElement, (event.stateMask & SWT.SHIFT) != 0);
            }
        };
        fieldAction.setToolTipText(Messages.getString("AbstractPropertiesView.find_similar_tooltip")); //$NON-NLS-1$
        expandAction = new Action(Messages.getString("CatalogView.expand_all"), Icons.expandAll.getDescriptor()) { //$NON-NLS-1$
            @Override
            public void run() {
                viewer.expandAll();
            }
        };
        expandAction.setToolTipText(Messages.getString("CatalogView.expand_all_tooltip")); //$NON-NLS-1$
        collapseAction = new Action(Messages.getString("CatalogView.collapse_all"), //$NON-NLS-1$
                Icons.collapseAll.getDescriptor()) {
            @Override
            public void run() {
                viewer.collapseAll();
            }
        };
        collapseAction.setToolTipText(Messages.getString("CatalogView.collapse_all_tree_items")); //$NON-NLS-1$
    }

    protected void deleteSubentry() {
        Object firstElement = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
        List<Asset> assets = getNavigationHistory().getSelectedAssets().getAssets();
        if (firstElement instanceof IndexedMember)
            OperationJob.executeOperation(new MultiModifyAssetOperation(((IndexedMember) firstElement).getQfield(),
                    null, ((IndexedMember) firstElement).getValue(), assets), this);
        else if (firstElement instanceof QueryField) {
            QueryField qfield = (QueryField) firstElement;
            if (qfield.isStruct()) {
                IDbManager dbManager = Core.getCore().getDbManager();
                String oldId = null;
                Object oldValue = null;
                if (qfield == QueryField.IPTC_LOCATIONCREATED) {
                    for (Asset asset : assets) {
                        Iterator<LocationCreatedImpl> it = dbManager.obtainStruct(LocationCreatedImpl.class,
                                asset.getStringId(), true, null, null, false).iterator();
                        if (it.hasNext()) {
                            LocationCreatedImpl rel = it.next();
                            if (oldId == null)
                                oldId = rel.getLocation();
                            else if (!oldId.equals(rel.getLocation()))
                                return;
                        }
                    }
                    if (oldId != null)
                        oldValue = dbManager.obtainById(LocationImpl.class, oldId);
                } else if (qfield == QueryField.IPTC_CONTACT) {
                    for (Asset asset : assets) {
                        Iterator<CreatorsContactImpl> it = dbManager.obtainStruct(CreatorsContactImpl.class,
                                asset.getStringId(), true, null, null, false).iterator();
                        if (it.hasNext()) {
                            CreatorsContactImpl rel = it.next();
                            if (oldId == null)
                                oldId = rel.getContact();
                            else if (!oldId.equals(rel.getContact()))
                                return;
                        }
                    }
                    if (oldId != null)
                        oldValue = dbManager.obtainById(ContactImpl.class, oldId);
                }
                if (oldValue != null)
                    OperationJob.executeOperation(new MultiModifyAssetOperation(qfield, null, oldValue, assets),
                            this);
            }
        }
    }

    /**
     * Passing the focus request to the viewer's control.
     */

    @Override
    public void setFocus() {
        viewer.getControl().setFocus();
    }

    Object getFieldValue(QueryField qfield) {
        return getFieldEntry(qfield).value;
    }

    private FieldEntry getFieldEntry(QueryField qfield) {
        AssetSelection selection = getNavigationHistory().getSelectedAssets();
        int size = selection.size();
        if (size == 0)
            return FieldEntry.NOTHING;
        FieldEntry entry = valueMap.get(qfield);
        if (entry == null) {
            if (size > ASYNCTHRESHOLD) {
                valueMap.put(qfield, FieldEntry.PENDING);
                new SupplyPropertyJob(qfield, selection.getAssets(), this).schedule();
                return FieldEntry.PENDING;
            }
            valueMap.put(qfield, entry = new FieldEntry(qfield.obtainFieldValue(selection.getAssets(), null)));
        }
        return entry;
    }

    boolean isEditable(QueryField qfield) {
        AssetSelection selection = getNavigationHistory().getSelectedAssets();
        if (selection.isEmpty())
            return true;
        return qfield.isEditable(selection.getAssets());
    }

    boolean isApplicable(QueryField qfield) {
        AssetSelection selection = getNavigationHistory().getSelectedAssets();
        if (selection.isEmpty())
            return true;
        return qfield.isApplicable(selection.getAssets());
    }

    public ISelection getSelection() {
        return StructuredSelection.EMPTY;
    }

    public void setSelection(ISelection selection) {
        // do nothing
    }

    @Override
    public boolean assetsChanged() {
        resetCaches();
        return true;
    }

    private void resetCaches() {
        cancelJobs(Constants.PROPERTYPROVIDER);
        valueMap.clear();
    }

    @Override
    public boolean collectionChanged() {
        return false;
    }

    @Override
    public boolean selectionChanged() {
        return false;
    }

    @Override
    public void refresh() {
        computeFlags();
        computeEssentials();
        refreshInternal();
    }

    private void refreshInternal() {
        Object[] expandedElements = viewer.getExpandedElements();
        viewer.setInput(getRootElement());
        if (expandedElements.length == 0)
            viewer.expandToLevel(getExpandLevel());
        else
            viewer.setExpandedElements(expandedElements);
    }

    private void computeFlags() {
        flags = getNavigationHistory().getSelectedAssets().getMediaFlags();
    }

    public boolean isDisposed() {
        return viewer.getControl().isDisposed();
    }

    public Display getDisplay() {
        return viewer.getControl().getDisplay();
    }

    public void updateField(QueryField qfield, FieldEntry fieldEntry) {
        valueMap.put(qfield, fieldEntry);
        if (qfield == QueryField.EXIF_FILESOURCE)
            refresh();
        else
            viewer.update(qfield, UPDATECOLS);
    }

    protected abstract int[] getColumnMaxWidths();

}