com.bdaum.zoom.ui.internal.dialogs.EditMetaDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.ui.internal.dialogs.EditMetaDialog.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-2017 Berthold Daum  
 */

package com.bdaum.zoom.ui.internal.dialogs;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
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.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

import com.bdaum.aoModeling.runtime.IIdentifiableObject;
import com.bdaum.aoModeling.runtime.IdentifiableObject;
import com.bdaum.zoom.batch.internal.BatchActivator;
import com.bdaum.zoom.cat.model.Meta_type;
import com.bdaum.zoom.cat.model.artworkOrObjectShown.ArtworkOrObjectImpl;
import com.bdaum.zoom.cat.model.artworkOrObjectShown.ArtworkOrObjectShownImpl;
import com.bdaum.zoom.cat.model.asset.AssetImpl;
import com.bdaum.zoom.cat.model.creatorsContact.ContactImpl;
import com.bdaum.zoom.cat.model.creatorsContact.CreatorsContactImpl;
import com.bdaum.zoom.cat.model.group.Criterion;
import com.bdaum.zoom.cat.model.group.CriterionImpl;
import com.bdaum.zoom.cat.model.group.GroupImpl;
import com.bdaum.zoom.cat.model.group.SmartCollection;
import com.bdaum.zoom.cat.model.group.SmartCollectionImpl;
import com.bdaum.zoom.cat.model.group.SortCriterionImpl;
import com.bdaum.zoom.cat.model.group.exhibition.ExhibitionImpl;
import com.bdaum.zoom.cat.model.group.slideShow.SlideShowImpl;
import com.bdaum.zoom.cat.model.group.webGallery.WebGalleryImpl;
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.cat.model.meta.Category;
import com.bdaum.zoom.cat.model.meta.CategoryImpl;
import com.bdaum.zoom.cat.model.meta.Meta;
import com.bdaum.zoom.cat.model.meta.MetaImpl;
import com.bdaum.zoom.cat.model.meta.WatchedFolder;
import com.bdaum.zoom.cat.model.meta.WatchedFolderImpl;
import com.bdaum.zoom.common.GeoMessages;
import com.bdaum.zoom.core.BagChange;
import com.bdaum.zoom.core.CatalogListener;
import com.bdaum.zoom.core.Constants;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.Format;
import com.bdaum.zoom.core.ISpellCheckingService;
import com.bdaum.zoom.core.IVolumeManager;
import com.bdaum.zoom.core.QueryField;
import com.bdaum.zoom.core.db.IDbManager;
import com.bdaum.zoom.core.internal.CoreActivator;
import com.bdaum.zoom.core.internal.LocationConstants;
import com.bdaum.zoom.core.internal.Theme;
import com.bdaum.zoom.core.internal.Utilities;
import com.bdaum.zoom.core.internal.lire.Algorithm;
import com.bdaum.zoom.core.internal.lucene.ILuceneService;
import com.bdaum.zoom.css.CSSProperties;
import com.bdaum.zoom.css.ZColumnLabelProvider;
import com.bdaum.zoom.fileMonitor.internal.filefilter.WildCardFilter;
import com.bdaum.zoom.image.ImageConstants;
import com.bdaum.zoom.job.OperationJob;
import com.bdaum.zoom.operations.DbOperation;
import com.bdaum.zoom.operations.internal.CreateLocationFolderOperation;
import com.bdaum.zoom.operations.internal.CreateTimelineOperation;
import com.bdaum.zoom.operations.internal.ManageKeywordsOperation;
import com.bdaum.zoom.operations.internal.ModifyKeywordOperation;
import com.bdaum.zoom.operations.internal.ReplaceKeywordOperation;
import com.bdaum.zoom.program.BatchUtilities;
import com.bdaum.zoom.ui.ILocationDisplay;
import com.bdaum.zoom.ui.Ui;
import com.bdaum.zoom.ui.dialogs.AcousticMessageDialog;
import com.bdaum.zoom.ui.dialogs.ZInputDialog;
import com.bdaum.zoom.ui.dialogs.ZTitleAreaDialog;
import com.bdaum.zoom.ui.internal.HelpContextIds;
import com.bdaum.zoom.ui.internal.Icons;
import com.bdaum.zoom.ui.internal.SortColumnManager;
import com.bdaum.zoom.ui.internal.UiActivator;
import com.bdaum.zoom.ui.internal.UiConstants;
import com.bdaum.zoom.ui.internal.UiUtilities;
import com.bdaum.zoom.ui.internal.VocabManager;
import com.bdaum.zoom.ui.internal.ZViewerComparator;
import com.bdaum.zoom.ui.internal.commands.LastImportCommand;
import com.bdaum.zoom.ui.internal.operations.ModifyMetaOperation;
import com.bdaum.zoom.ui.internal.preferences.GeneralPreferencePage;
import com.bdaum.zoom.ui.internal.preferences.KeyPreferencePage;
import com.bdaum.zoom.ui.internal.views.ZColumnViewerToolTipSupport;
import com.bdaum.zoom.ui.internal.widgets.CheckboxButton;
import com.bdaum.zoom.ui.internal.widgets.CheckedText;
import com.bdaum.zoom.ui.internal.widgets.CompressionGroup;
import com.bdaum.zoom.ui.internal.widgets.ExpandCollapseGroup;
import com.bdaum.zoom.ui.internal.widgets.FilterField;
import com.bdaum.zoom.ui.internal.widgets.FlatGroup;
import com.bdaum.zoom.ui.internal.widgets.IInputAdvisor;
import com.bdaum.zoom.ui.internal.widgets.RadioButtonGroup;
import com.bdaum.zoom.ui.internal.widgets.TextWithVariableGroup;
import com.bdaum.zoom.ui.internal.widgets.WidgetFactory;
import com.bdaum.zoom.ui.internal.wizards.ImportFileSelectionPage;
import com.bdaum.zoom.ui.internal.wizards.WatchedFolderWizard;
import com.bdaum.zoom.ui.widgets.CGroup;
import com.bdaum.zoom.ui.widgets.CLink;
import com.bdaum.zoom.ui.widgets.NumericControl;

@SuppressWarnings("restriction")
public class EditMetaDialog extends ZTitleAreaDialog {

    public class DetailSelectionAdapter implements Listener {

        private final Class<? extends IIdentifiableObject> clazz;
        private final String label;
        private LinkedHashMap<String, Integer> values = new LinkedHashMap<>();

        public DetailSelectionAdapter(Class<? extends IIdentifiableObject> clazz, String label) {
            this.clazz = clazz;
            this.label = label;
            new DetailsJob(clazz, values).schedule();
        }

        @Override
        public void handleEvent(Event e) {
            detailsProgressBar.setData(clazz);
            detailsGroup.setText(label);
            detailsGroup.setVisible(true);
            try {
                Job.getJobManager().join(clazz, null);
            } catch (OperationCanceledException | InterruptedException e1) {
                // should not happen
            }
            Shell shell = getShell();
            if (!shell.isDisposed()) {
                shell.getDisplay().asyncExec(() -> {
                    if (!shell.isDisposed()) {
                        int i = 0;
                        for (Map.Entry<String, Integer> entry : values.entrySet())
                            setDetails(i++, entry.getKey(), entry.getValue());
                        detailsProgressBar.setVisible(false);
                    }
                });
            }
        }

    }

    public class DetailsJob extends Job {

        private final Class<? extends IIdentifiableObject> clazz;
        private LinkedHashMap<String, Integer> values;

        public DetailsJob(Class<? extends IIdentifiableObject> clazz, LinkedHashMap<String, Integer> values) {
            super(Messages.EditMetaDialog_collecting_details);
            this.clazz = clazz;
            this.values = values;
            setSystem(true);
            setPriority(Job.INTERACTIVE);
        }

        @Override
        public boolean belongsTo(Object family) {
            return family == clazz;
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            if (clazz == GroupImpl.class) {
                List<GroupImpl> groups = dbManager.obtainObjects(GroupImpl.class);
                final Shell shell = getShell();
                int work = groups.size();
                int incr = Math.max(1, (work + 15) / 16);
                int main = 0;
                int subgroup = 0;
                int system = 0;
                int user = 0;
                int i = 0;
                for (GroupImpl group : groups) {
                    if (group.getGroup_subgroup_parent() == null)
                        ++main;
                    else
                        ++subgroup;
                    if (group.getSystem())
                        ++system;
                    else
                        ++user;
                    values.put(Messages.EditMetaDialog_main_groups, main);
                    values.put(Messages.EditMetaDialog_subgroups, subgroup);
                    values.put(Messages.EditMetaDialog_system_groups, system);
                    values.put(Messages.EditMetaDialog_user_groups, user);
                    if (updateProgressBar(shell, ++i, work, incr, monitor))
                        return Status.CANCEL_STATUS;
                }
            } else if (clazz == SmartCollectionImpl.class) {
                int main = 0;
                int subCollection = 0;
                int system = 0;
                int user = 0;
                int persons = 0;
                int albums = 0;
                int timeline = 0;
                int locations = 0;
                int imports = 0;
                int directory = 0;
                int offline = 0;
                int local = 0;
                int auto = 0;
                List<SmartCollectionImpl> collections = dbManager.obtainObjects(SmartCollectionImpl.class);
                final int work = collections.size();
                int incr = Math.max(1, (work + 31) / 32);
                final Shell shell = getShell();
                IVolumeManager volumeManager = Core.getCore().getVolumeManager();
                String uriKey = QueryField.URI.getKey() + '=';
                String volumeKey = QueryField.VOLUME.getKey() + '=';
                int i = 0;
                for (SmartCollectionImpl sm : collections) {
                    if (sm.getSmartCollection_subSelection_parent() == null)
                        ++main;
                    else
                        ++subCollection;
                    if (sm.getSystem()) {
                        ++system;
                    } else
                        ++user;
                    if (sm.getAlbum()) {
                        if (sm.getSystem())
                            ++persons;
                        else
                            ++albums;
                    } else {
                        String id = sm.getStringId();
                        if (id.startsWith(uriKey)) {
                            ++directory;
                            if (volumeManager.findExistingFile(id.substring(uriKey.length()), null) == null)
                                ++offline;
                            else
                                ++local;
                        } else if (id.startsWith(volumeKey)) {
                            ++directory;
                            if (volumeManager.isOffline(id.substring(volumeKey.length())))
                                ++offline;
                            else
                                ++local;
                        } else {
                            if (id.startsWith(Constants.GROUP_ID_AUTOSUB))
                                ++auto;
                            List<Criterion> crits = sm.getCriterion();
                            if (!crits.isEmpty()) {
                                String field = crits.get(0).getField();
                                if (sm.getSystem()) {
                                    if (field.equals(QueryField.IPTC_DATECREATED.getKey()))
                                        ++timeline;
                                    else if (field.equals(QueryField.IPTC_LOCATIONCREATED.getKey()))
                                        ++locations;
                                } else if (field.equals(QueryField.IMPORTDATE.getKey()))
                                    ++imports;
                            }
                        }
                    }
                    values.put(Messages.EditMetaDialog_main_collections, main);
                    values.put(Messages.EditMetaDialog_subcollections, subCollection);
                    values.put(Messages.EditMetaDialog_import_folders, imports);
                    values.put(Messages.EditMetaDialog_system_collections, system);
                    values.put(Messages.EditMetaDialog_directories, directory);
                    values.put(Messages.EditMetaDialog_local_dir, local);
                    values.put(Messages.EditMetaDialog_offline_dir, offline);
                    values.put(Messages.EditMetaDialog_locations_folders, locations);
                    values.put(Messages.EditMetaDialog_timeline_folders, timeline);
                    values.put(Messages.EditMetaDialog_person_folders, persons);
                    values.put(Messages.EditMetaDialog_user_collections, user);
                    values.put(Messages.EditMetaDialog_albums, albums);
                    values.put(Messages.EditMetaDialog_created_by_rule, auto);
                    values.put(Messages.EditMetaDialog_other_collections, user - albums - auto);
                    if (updateProgressBar(shell, ++i, work, incr, monitor))
                        return Status.CANCEL_STATUS;
                }
            } else if (clazz == LocationImpl.class) {
                Set<String> regions = new HashSet<String>(10);
                Set<String> countries = new HashSet<String>(51);
                Set<String> cities = new HashSet<String>(777);
                List<LocationImpl> collections = dbManager.obtainObjects(LocationImpl.class);
                final int work = collections.size();
                int incr = Math.max(1, (work + 31) / 32);
                final Shell shell = getShell();
                int i = 0;
                for (LocationImpl loc : collections) {
                    String worldRegionCode = loc.getWorldRegionCode();
                    String worldRegion = loc.getWorldRegion();
                    if (worldRegionCode == null && worldRegion != null)
                        worldRegionCode = LocationConstants.worldRegionToContinent.get(worldRegion);
                    regions.add(worldRegionCode);
                    String countryISOCode = loc.getCountryISOCode();
                    if (countryISOCode != null && countryISOCode.length() < 3)
                        countryISOCode = LocationConstants.iso2Toiso3.get(countryISOCode);
                    if (countryISOCode == null)
                        countryISOCode = loc.getCountryName();
                    countries.add(countryISOCode);
                    cities.add(loc.getCity());
                    values.put(Messages.EditMetaDialog_world_regions, regions.size());
                    values.put(Messages.EditMetaDialog_countries, countries.size());
                    values.put(Messages.EditMetaDialog_cities, cities.size());
                    if (updateProgressBar(shell, ++i, work, incr, monitor))
                        return Status.CANCEL_STATUS;
                }
            }
            return Status.OK_STATUS;
        }

        private boolean updateProgressBar(final Shell shell, final int i, int work, int incr,
                IProgressMonitor monitor) {
            if (!monitor.isCanceled() && !shell.isDisposed()) {
                if (i % incr == 0)
                    shell.getDisplay().syncExec(() -> {
                        if (detailsProgressBar.getData() == clazz) {
                            int j = 0;
                            for (Map.Entry<String, Integer> entry : values.entrySet())
                                setDetails(j++, entry.getKey(), entry.getValue());
                            detailsProgressBar.setMaximum(work);
                            detailsProgressBar.setSelection(i);
                            detailsProgressBar.setVisible(true);
                        }
                    });
                return false;
            }
            return true;
        }
    }

    public class KeywordContentProvider implements ITreeContentProvider {

        private Map<Character, List<String>> chapters;

        public void dispose() {
            chapters = null;
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            chapters = null;
        }

        public Object[] getElements(Object inputElement) {
            @SuppressWarnings("unchecked")
            Set<String> availableKeywords = (Set<String>) inputElement;
            String[] available = filterKeywords(availableKeywords);
            if (flatKeywordGroup.isFlat())
                return available;
            if (chapters == null) {
                chapters = new HashMap<Character, List<String>>();
                for (String kw : available) {
                    if (!kw.isEmpty()) {
                        Character chapterTitle = Character.toUpperCase(kw.charAt(0));
                        List<String> elements = chapters.get(chapterTitle);
                        if (elements == null)
                            chapters.put(chapterTitle, elements = new ArrayList<String>());
                        elements.add(kw);
                    }
                }
            }
            return chapters.keySet().toArray();
        }

        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof Character) {
                List<String> elements = chapters.get(parentElement);
                if (elements != null)
                    return elements.toArray();
            }
            return EMPTY;
        }

        public Object getParent(Object element) {
            if (!flatKeywordGroup.isFlat()) {
                String kw = (String) element;
                if (!kw.isEmpty()) {
                    char firstChar = Character.toUpperCase(kw.charAt(0));
                    for (Character title : chapters.keySet())
                        if (title.charValue() == firstChar)
                            return title;
                }
            }
            return null;
        }

        public boolean hasChildren(Object element) {
            return getChildren(element).length > 0;
        }

    }

    public class StructGroup implements DisposeListener {

        public class LocationTreeJob extends Job {

            public LocationTreeJob() {
                super(Messages.EditMetaDialog_building_location_tree);
                setSystem(true);
                setPriority(Job.INTERACTIVE);
            }

            @Override
            public boolean belongsTo(Object family) {
                return family == StructGroup.this;
            }

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                locations = dbManager.obtainObjects(LocationImpl.class);
                root = new ArrayList<LocationNode>();
                for (LocationImpl location : locations) {
                    if (monitor.isCanceled())
                        return Status.CANCEL_STATUS;
                    buildLocationTree(root, null, location, LocationNode.CONTINENT, true);
                }
                colorLocationTree(root);
                String expansions = settings.get(TREEEXPANSION + type);
                List<String> expList = expansions == null ? null : Core.fromStringList(expansions, ";"); //$NON-NLS-1$
                final List<LocationNode> nodeList = expList == null ? null : new ArrayList<>(expList.size());
                if (expList != null)
                    for (String s : expList) {
                        for (LocationNode rootNode : root) {
                            LocationNode node = rootNode.find(s);
                            if (node != null)
                                nodeList.add(node);
                        }
                    }
                final Control control = locTreeViewer.getControl();
                if (!control.isDisposed())
                    control.getDisplay().asyncExec(() -> {
                        if (!control.isDisposed()) {
                            locTreeViewer.setInput(root);
                            if (nodeList != null)
                                locTreeViewer.setExpandedElements(nodeList.toArray());
                            else
                                locTreeViewer.expandAll();
                        }
                    });
                return Status.OK_STATUS;
            }

        }

        private TreeViewer locTreeViewer;
        private Composite stackComposite;
        private StackLayout stackLayout;
        private Composite flatComposite;
        private Composite treeComposite;
        private List<LocationNode> root;
        private StructComponent flatComponent;
        private Button addButton;
        private Button editButton;
        private Button removeButton;
        private Button showButton;
        private Button mapButton;
        private Button emailButton;
        private Button webButton;
        private final int type;
        private List<LocationImpl> locations = new ArrayList<LocationImpl>(0);
        private final String item;
        private FlatGroup radioGroup;
        private boolean enabled;
        private Button cleanButton;
        private Set<String> usedObjects;

        public StructGroup(Composite sComposite, final int type, String item, boolean enabled) {
            this.type = type;
            this.item = item;
            this.enabled = enabled;
            usedObjects = new HashSet<>(537);
            switch (type) {
            case QueryField.T_LOCATION:
                for (LocationCreatedImpl rel : dbManager.obtainObjects(LocationCreatedImpl.class))
                    usedObjects.add(rel.getLocation());
                for (LocationShownImpl rel : dbManager.obtainObjects(LocationShownImpl.class))
                    usedObjects.add(rel.getLocation());
                break;
            case QueryField.T_CONTACT:
                for (CreatorsContactImpl rel : dbManager.obtainObjects(CreatorsContactImpl.class))
                    usedObjects.add(rel.getContact());
                break;
            case QueryField.T_OBJECT:
                for (ArtworkOrObjectShownImpl rel : dbManager.obtainObjects(ArtworkOrObjectShownImpl.class))
                    usedObjects.add(rel.getArtworkOrObject());
                break;
            }
            sComposite.addDisposeListener(this);
            radioGroup = new FlatGroup(sComposite, SWT.NONE, settings, HIERARCHICAL_STRUCT + type);
            radioGroup.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false));
            radioGroup.addListener(new Listener() {

                @Override
                public void handleEvent(Event event) {
                    if (type == QueryField.T_LOCATION)
                        updateLocationStack();
                    else
                        flatComponent.update();
                }
            });
            if (type == QueryField.T_LOCATION) {
                Composite locComposite = new Composite(sComposite, SWT.NONE);
                locComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
                locComposite.setLayout(new GridLayout(1, false));
                Composite viewerAndButtonComposite = new Composite(locComposite, SWT.NONE);
                viewerAndButtonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                viewerAndButtonComposite.setLayout(new GridLayout(2, false));

                stackComposite = new Composite(viewerAndButtonComposite, SWT.NONE);
                stackComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                stackLayout = new StackLayout();
                stackComposite.setLayout(stackLayout);
                flatComposite = new Composite(stackComposite, SWT.NONE);
                flatComposite.setLayout(new GridLayout(2, false));
                flatComponent = new StructComponent(dbManager, flatComposite, null, type, false, structOverlayMap,
                        radioGroup, usedObjects, 1, settings);
                treeComposite = new Composite(stackComposite, SWT.NONE);
                treeComposite.setLayout(new GridLayout(2, false));
                createLocationTree(treeComposite);
                createButtonBar(viewerAndButtonComposite);
                new LocationTreeJob().schedule(500);
            } else {
                flatComponent = new StructComponent(dbManager, sComposite, null, type, false, structOverlayMap,
                        radioGroup, usedObjects, 1, settings);
                createButtonBar(sComposite);
            }
            flatComponent.addDoubleClickListener(new IDoubleClickListener() {
                public void doubleClick(DoubleClickEvent event) {
                    editStruct(type);
                }
            });
            flatComponent.addSelectionChangedListener(new ISelectionChangedListener() {
                public void selectionChanged(SelectionChangedEvent event) {
                    updateStructButtons();
                }
            });
            updateLocationStack();
        }

        public void fillValues() {
            flatComponent.fillValues();
        }

        @SuppressWarnings("unused")
        private void createButtonBar(Composite parent) {
            final Composite bar = new Composite(parent, SWT.NONE);
            bar.setLayout(new GridLayout());
            bar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
            Label label = new Label(bar, SWT.NONE);
            label.setLayoutData(new GridData(50, 15));
            addButton = createPushButton(bar, Messages.EditMetaDialog_add,
                    NLS.bind(Messages.EditMetaDialog_add_x, item));
            addButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    EditStructDialog dialog = new EditStructDialog(getShell(), null, type, -1, structOverlayMap,
                            Messages.EditMetaDialog_add_n);
                    if (dialog.open() == Window.OK) {
                        IdentifiableObject result = dialog.getResult();
                        newStructMap.put(result.getStringId(), result);
                        flatComponent.add(result);
                        if (result instanceof LocationImpl) {
                            buildLocationTree(root, null, (LocationImpl) result, LocationNode.CONTINENT, true);
                            colorLocationTree(root);
                            setTreeInput(root);
                            geographic = null;
                        }
                    }
                }
            });
            editButton = createPushButton(bar, Messages.EditMetaDialog_edit,
                    NLS.bind(Messages.EditMetaDialog_edit_selected_x, item));
            editButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    editStruct(type);
                }
            });
            removeButton = createPushButton(bar, Messages.EditMetaDialog_remove,
                    NLS.bind(Messages.EditMetaDialog_remove_x, item));
            removeButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    removeItem();
                    if (type == QueryField.T_LOCATION)
                        geographic = null;
                }
            });
            cleanButton = createPushButton(bar, Messages.EditMetaDialog_clean_up,
                    Messages.EditMetaDialog_clean_up_tooltip);
            cleanButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    cleanup(type);
                    if (type == QueryField.T_LOCATION)
                        geographic = null;
                }
            });
            new Label(bar, SWT.SEPARATOR | SWT.HORIZONTAL);
            showButton = createPushButton(bar, Messages.EditMetaDialog_show_images,
                    NLS.bind(Messages.EditMetaDialog_show_x, item));
            showButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    if (workbenchPage != null) {
                        Ui.getUi().getNavigationHistory(workbenchPage.getWorkbenchWindow())
                                .postSelection(new StructuredSelection(createAdhocQuery(getSelectedElement())));
                        close();
                    }
                }
            });
            mapButton = createPushButton(bar, Messages.EditMetaDialog_show_in_map,
                    NLS.bind(Messages.EditMetaDialog_show_x_in_map, item));
            mapButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    Object selectedElement = getSelectedElement();
                    LocationImpl loc = (LocationImpl) getModelElement(selectedElement);
                    if (loc == null && selectedElement instanceof LocationNode) {
                        LocationNode node = (LocationNode) selectedElement;
                        loc = new LocationImpl();
                        while (node != null && !node.isUnknown()) {
                            switch (node.getLevel()) {
                            case LocationNode.COUNTRY:
                                loc.setCountryISOCode(node.getKey());
                                String name = node.getName();
                                int p = name.lastIndexOf('(');
                                loc.setCountryName(p >= 0 ? name.substring(0, p).trim() : name);
                                break;
                            case LocationNode.STATE:
                                loc.setProvinceOrState(node.getName());
                                break;
                            case LocationNode.CITY:
                                loc.setCity(node.getName());
                                break;
                            case LocationNode.DETAIL:
                                loc.setDetails(node.getName());
                                break;
                            }
                            node = node.getParent();
                        }
                    }
                    if (loc != null) {
                        ILocationDisplay display = UiActivator.getDefault().getLocationDisplay();
                        if (display != null)
                            display.display(loc);
                        close();
                    }
                }
            });
            if (type != QueryField.T_LOCATION)
                mapButton.setVisible(false);
            emailButton = createPushButton(bar, Messages.EditMetaDialog_send_email,
                    NLS.bind(Messages.EditMetaDialog_Email_x, item));
            emailButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    String[] email = ((ContactImpl) getSelectedElement()).getEmail();
                    if (email != null && email.length > 0)
                        UiActivator.getDefault().sendMail(Arrays.asList(email));
                }
            });
            webButton = createPushButton(bar, Messages.EditMetaDialog_visit_web_site,
                    NLS.bind(Messages.EditMetaDialog_visit_x, item));
            webButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    String[] web = ((ContactImpl) getSelectedElement()).getWebUrl();
                    if (web != null && web.length == 1) {
                        try {
                            PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser()
                                    .openURL(new URL(Core.furnishWebUrl(web[0])));
                        } catch (PartInitException ex) {
                            UiActivator.getDefault().logError(Messages.EditMetaDialog_cannot_open_web_browser, ex);
                        } catch (MalformedURLException ex) {
                            UiActivator.getDefault().logError(Messages.EditMetaDialog_invalid_web_url, ex);
                        }
                    }
                }
            });
            if (type != QueryField.T_CONTACT) {
                emailButton.setVisible(false);
                webButton.setVisible(false);
            }
        }

        protected void cleanup(int type) {
            List<IIdentifiableObject> objects = new LinkedList<>(flatComponent.getObjects());
            BusyIndicator.showWhile(getShell().getDisplay(), () -> {
                Iterator<IIdentifiableObject> it = objects.iterator();
                while (it.hasNext()) {
                    IIdentifiableObject object = it.next();
                    if (usedObjects.contains(object.getStringId()))
                        it.remove();
                }
            });
            if (objects.isEmpty())
                AcousticMessageDialog.openInformation(getShell(), Messages.EditMetaDialog_clean_up,
                        Messages.EditMetaDialog_clean_up_msg);
            else {
                CleanupDialog dialog = new CleanupDialog(getShell(), objects);
                if (dialog.open() == OK) {
                    dbManager.safeTransaction(objects, null);
                    flatComponent.removeAll(objects);
                    if (type == QueryField.T_LOCATION) {
                        Job.getJobManager().cancel(this);
                        try {
                            Job.getJobManager().join(this, null);
                        } catch (OperationCanceledException | InterruptedException e) {
                            // ignore
                        }
                        new LocationTreeJob().schedule();
                    }
                }
            }
        }

        protected SmartCollection createAdhocQuery(Object sel) {
            SmartCollectionImpl coll = new SmartCollectionImpl("", true, false, true, false, null, 0, null, 0, null, //$NON-NLS-1$
                    Constants.INHERIT_LABEL, null, 0, null);
            if (sel instanceof LocationNode && ((LocationNode) sel).getLocation() == null) {
                LocationNode node = (LocationNode) sel;
                QueryField subfield;
                switch (node.getLevel()) {
                case LocationNode.CONTINENT:
                    subfield = QueryField.LOCATION_WORLDREGIONCODE;
                    break;
                case LocationNode.COUNTRY:
                    subfield = QueryField.LOCATION_COUNTRYCODE;
                    break;
                case LocationNode.STATE:
                    subfield = QueryField.LOCATION_STATE;
                    break;
                default:
                    subfield = QueryField.LOCATION_CITY;
                    break;
                }
                String key = node.getKey();
                coll.setName(QueryField.LOCATION_TYPE.getLabel() + '.' + subfield.getLabel() + '=' + key);
                coll.addCriterion(new CriterionImpl(QueryField.IPTC_LOCATIONSHOWN.getKey(), subfield.getKey(), key,
                        QueryField.EQUALS, false));
                coll.addCriterion(new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(), subfield.getKey(),
                        key, QueryField.EQUALS, false));
            } else {
                IdentifiableObject item = getModelElement(sel);
                if (item instanceof LocationImpl) {
                    coll.setName(QueryField.LOCATION_TYPE.getLabel() + '='
                            + StructComponent.getStructText(item, structOverlayMap));
                    coll.addCriterion(new CriterionImpl(QueryField.IPTC_LOCATIONSHOWN.getKey(), null,
                            item.getStringId(), QueryField.EQUALS, false));
                    coll.addCriterion(new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(), null,
                            item.getStringId(), QueryField.EQUALS, false));
                } else if (item instanceof ContactImpl) {
                    coll.setName(Messages.EditMetaDialog_contactquery
                            + StructComponent.getStructText(item, structOverlayMap));
                    coll.addCriterion(new CriterionImpl(QueryField.IPTC_CONTACT.getKey(), null, item.getStringId(),
                            QueryField.EQUALS, false));
                } else if (item instanceof ArtworkOrObjectImpl) {
                    coll.setName(Messages.EditMetaDialog_artworkquery
                            + StructComponent.getStructText(item, structOverlayMap));
                    coll.addCriterion(new CriterionImpl(QueryField.IPTC_ARTWORK.getKey(), null, item.getStringId(),
                            QueryField.EQUALS, false));
                }
            }
            coll.addSortCriterion(new SortCriterionImpl(QueryField.IPTC_DATECREATED.getKey(), null, true));
            return coll;
        }

        private void removeItem() {
            Object sel = getSelectedElement();
            IdentifiableObject modelElement = getModelElement(sel);
            if (modelElement != null) {
                if (isElementInUse(modelElement)) {
                    QueryField qfield = QueryField.getStructParent(type);
                    if (qfield != null
                            && !AcousticMessageDialog
                                    .openConfirm(getShell(),
                                            NLS.bind(Messages.EditMetaDialog_n_in_use, qfield.getLabel()),
                                            NLS.bind(
                                                    Messages.EditMetaDialog_the_selected_x_is_used
                                                            + Messages.EditMetaDialog_want_to_delte,
                                                    qfield.getLabel())))
                        return;
                }
                structOverlayMap.put(modelElement.getStringId(), null);
                flatComponent.remove(modelElement);
                if (locTreeViewer != null) {
                    buildLocationTree(root, null, (LocationImpl) modelElement, LocationNode.CONTINENT, false);
                    colorLocationTree(root);
                    setTreeInput(root);
                }
                return;
            }
            if (sel instanceof LocationNode) {
                removeChildren((LocationNode) sel, -1);
                root = new ArrayList<LocationNode>();
                for (LocationImpl location : locations)
                    buildLocationTree(root, null, location, LocationNode.CONTINENT, true);
                colorLocationTree(root);
                setTreeInput(root);
            }
        }

        private void setTreeInput(List<LocationNode> root) {
            ISelection selection = locTreeViewer.getSelection();
            Object[] expandedElements = locTreeViewer.getExpandedElements();
            locTreeViewer.setInput(root);
            locTreeViewer.setExpandedElements(expandedElements);
            locTreeViewer.setSelection(selection);
        }

        private int removeChildren(LocationNode node, int policy) {
            List<LocationNode> children = node.getChildren();
            if (children != null) {
                for (LocationNode child : children) {
                    LocationImpl modelElement = (LocationImpl) getModelElement(child);
                    if (modelElement != null) {
                        if (isElementInUse(modelElement)) {
                            QueryField qfield = QueryField.getStructParent(type);
                            if (qfield == null)
                                return policy;
                            if (policy != 2) {
                                AcousticMessageDialog dialog = new AcousticMessageDialog(getShell(),
                                        NLS.bind(Messages.EditMetaDialog_n_in_use, qfield.getLabel()), null,
                                        NLS.bind(Messages.EditMetaDialog_the_selected_x_is_used
                                                + Messages.EditMetaDialog_want_to_delte, qfield.getLabel()),
                                        AcousticMessageDialog.QUESTION,
                                        new String[] { Messages.EditMetaDialog_no, Messages.EditMetaDialog_yes,
                                                Messages.EditMetaDialog_all, IDialogConstants.CANCEL_LABEL },
                                        0);
                                int ret = dialog.open();
                                switch (ret) {
                                case 2:
                                    policy = 2;
                                    //$FALL-THROUGH$
                                case 1:
                                    break;
                                default:
                                    return ret;
                                }
                            }
                        }
                        structOverlayMap.put(modelElement.getStringId(), null);
                        flatComponent.remove(modelElement);
                    } else {
                        int ret = removeChildren(child, policy);
                        if (ret == 3)
                            return ret;
                    }
                }
            }
            return policy;
        }

        private void createLocationTree(Composite parent) {
            ExpandCollapseGroup expandCollapseGroup = new ExpandCollapseGroup(parent, SWT.NONE);
            expandCollapseGroup.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false, 2, 1));
            locTreeViewer = new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.SINGLE);
            expandCollapseGroup.setViewer(locTreeViewer);
            GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
            layoutData.heightHint = 200;
            locTreeViewer.getControl().setLayoutData(layoutData);
            locTreeViewer.setContentProvider(new LocTreeContentProvider());
            locTreeViewer.setComparator(ZViewerComparator.INSTANCE);
            locTreeViewer.setLabelProvider(new ZColumnLabelProvider() {
                @Override
                public String getText(Object element) {
                    return element.toString();
                }

                @Override
                protected Color getForeground(Object element) {
                    if (element instanceof LocationNode && ((LocationNode) element).isUnused())
                        return locTreeViewer.getControl().getDisplay().getSystemColor(SWT.COLOR_RED);
                    return super.getForeground(element);
                }
            });
            UiUtilities.installDoubleClickExpansion(locTreeViewer);
            locTreeViewer.getControl().addKeyListener(keyListener);
            locTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
                public void selectionChanged(SelectionChangedEvent event) {
                    updateStructButtons();
                    if (cntrlDwn) {
                        if (editButton.isEnabled()) {
                            LocationNode node = (LocationNode) ((IStructuredSelection) locTreeViewer.getSelection())
                                    .getFirstElement();
                            if (node != null && node.getLocation() != null)
                                editStruct(QueryField.T_LOCATION);
                        }
                        cntrlDwn = false;
                    }
                }
            });
        }

        private Object getSelectedElement() {
            if (stackLayout != null && stackLayout.topControl == treeComposite)
                return ((IStructuredSelection) locTreeViewer.getSelection()).getFirstElement();
            return flatComponent.getSelectedElement();
        }

        private void editStruct(final int aType) {
            if (!readonly) {
                boolean massUpdate = false;
                Object selectedElement = getSelectedElement();
                IdentifiableObject modelElement = getModelElement(selectedElement);
                if (modelElement == null && selectedElement instanceof LocationNode
                        && !((LocationNode) selectedElement).isUnknown()) {
                    modelElement = getFirstModelElement((LocationNode) selectedElement);
                    massUpdate = true;
                }
                if (modelElement != null) {
                    int level = (selectedElement instanceof LocationNode)
                            ? ((LocationNode) selectedElement).getLevel()
                            : -1;
                    QueryField qField = QueryField.getStructParent(aType);
                    if (qField != null) {
                        EditStructDialog dialog = new EditStructDialog(getShell(), modelElement, aType, level,
                                structOverlayMap, NLS.bind(Messages.EditMetaDialog_edit_x, qField.getLabel()));
                        if (dialog.open() == Window.OK && dialog.isUpdated()) {
                            IdentifiableObject result = dialog.getResult();
                            if (massUpdate) {
                                updateChildren((LocationNode) selectedElement, level, result.getStringId());
                                root = new ArrayList<LocationNode>();
                                for (LocationImpl location : locations)
                                    buildLocationTree(root, null, location, LocationNode.CONTINENT, true);
                                colorLocationTree(root);
                                setTreeInput(root);
                            } else
                                updateElement(selectedElement);
                            if (type == QueryField.T_LOCATION)
                                geographic = null;
                        }
                    }
                }
            }
        }

        private void updateChildren(LocationNode node, int level, String protoTypeId) {
            List<LocationNode> children = node.getChildren();
            if (children != null) {
                Map<QueryField, Object> prototypeMap = structOverlayMap.get(protoTypeId);
                for (LocationNode child : children) {
                    LocationImpl modelElement = (LocationImpl) getModelElement(child);
                    if (modelElement != null) {
                        String id = modelElement.getStringId();
                        Map<QueryField, Object> fieldMap = structOverlayMap.get(id);
                        if (fieldMap == null) {
                            fieldMap = new HashMap<QueryField, Object>();
                            structOverlayMap.put(id, fieldMap);
                        }
                        switch (level) {
                        case LocationNode.CITY:
                            fieldMap.put(QueryField.LOCATION_CITY, prototypeMap.get(QueryField.LOCATION_CITY));
                            //$FALL-THROUGH$
                        case LocationNode.STATE:
                            fieldMap.put(QueryField.LOCATION_STATE, prototypeMap.get(QueryField.LOCATION_STATE));
                            //$FALL-THROUGH$
                        case LocationNode.COUNTRY:
                            fieldMap.put(QueryField.LOCATION_COUNTRYCODE,
                                    prototypeMap.get(QueryField.LOCATION_COUNTRYCODE));
                            fieldMap.put(QueryField.LOCATION_COUNTRYNAME,
                                    prototypeMap.get(QueryField.LOCATION_COUNTRYNAME));
                            fieldMap.put(QueryField.LOCATION_WORLDREGION,
                                    prototypeMap.get(QueryField.LOCATION_WORLDREGION));
                            fieldMap.put(QueryField.LOCATION_WORLDREGIONCODE,
                                    prototypeMap.get(QueryField.LOCATION_WORLDREGIONCODE));
                            break;
                        }
                    } else
                        updateChildren(child, level, protoTypeId);
                }
            }
        }

        private IdentifiableObject getFirstModelElement(LocationNode node) {
            List<LocationNode> children = node.getChildren();
            if (children != null)
                for (LocationNode child : children) {
                    IdentifiableObject modelElement = getModelElement(child);
                    if (modelElement != null)
                        return modelElement;
                    modelElement = getFirstModelElement(child);
                    if (modelElement != null)
                        return modelElement;
                }
            return null;
        }

        private IdentifiableObject getModelElement(Object selectedElement) {
            if (selectedElement instanceof LocationNode)
                return ((LocationNode) selectedElement).getLocation();
            if (selectedElement instanceof IdentifiableObject)
                return (IdentifiableObject) selectedElement;
            return null;
        }

        private void updateElement(Object selectedElement) {
            flatComponent.update(selectedElement, null);
            flatComponent.setSelection(new StructuredSelection(selectedElement));
            if (locTreeViewer != null) {
                locTreeViewer.update(selectedElement, null);
                locTreeViewer.setSelection(new StructuredSelection(selectedElement));
            }
        }

        protected void updateLocationStack() {
            if (stackLayout != null) {
                stackLayout.topControl = radioGroup.isFlat() ? flatComposite : treeComposite;
                stackComposite.layout();
            }
            updateStructButtons();
        }

        private void updateStructButtons() {
            Object sel = getSelectedElement();
            boolean canShow = sel instanceof LocationNode || sel instanceof IdentifiableObject;
            boolean selected = false;
            boolean editable = false;
            boolean removable = false;
            IdentifiableObject model = null;
            if (sel != null && !(sel instanceof Character)) {
                model = getModelElement(sel);
                selected = (model != null);
                boolean isKnownLocation = sel instanceof LocationNode && !((LocationNode) sel).isUnknown();
                editable = selected || isKnownLocation && ((LocationNode) sel).getLevel() >= LocationNode.COUNTRY;
                removable = selected || isKnownLocation
                        && (((LocationNode) sel).getLevel() >= LocationNode.COUNTRY || ((LocationNode) sel).unused);
            }
            editButton.setEnabled(editable && enabled);
            showButton.setEnabled(canShow);
            removeButton.setEnabled(removable && enabled);
            addButton.setEnabled(enabled);
            boolean mapEnabled = editable;
            mapButton.setEnabled(mapEnabled);
            mapButton.setVisible(UiActivator.getDefault().getLocationDisplay() != null);
            boolean emailEnabled = false;
            boolean webEnabled = false;
            if (model != null && type == QueryField.T_CONTACT) {
                String[] email = ((ContactImpl) model).getEmail();
                emailEnabled = email != null && email.length > 0;
                String[] web = ((ContactImpl) model).getWebUrl();
                webEnabled = web != null && web.length == 1;
            }
            webButton.setEnabled(webEnabled);
            emailButton.setEnabled(emailEnabled);
        }

        private void buildLocationTree(List<LocationNode> base, LocationNode parent, LocationImpl location,
                int level, boolean add) {
            if (base == null)
                return;
            String locId = location.getStringId();
            if (structOverlayMap.containsKey(locId) && structOverlayMap.get(locId) == null)
                return;
            if (level < LocationNode.DETAIL) {
                boolean unknown = false;
                String key;
                String name;
                switch (level) {
                case LocationNode.CONTINENT:
                    key = getUpdatedValue(location, QueryField.LOCATION_WORLDREGIONCODE,
                            location.getWorldRegionCode());
                    name = getUpdatedValue(location, QueryField.LOCATION_WORLDREGION, location.getWorldRegion());
                    if (key == null && name != null)
                        key = LocationConstants.worldRegionToContinent.get(name);
                    if (key != null)
                        name = GeoMessages.getString(GeoMessages.PREFIX + key);
                    if (name == null || name.isEmpty()) {
                        name = Messages.EditMetaDialog_unknown_worldregion;
                        unknown = true;
                    }
                    break;
                case LocationNode.COUNTRY:
                    String iso = getUpdatedValue(location, QueryField.LOCATION_COUNTRYCODE,
                            location.getCountryISOCode());
                    String cname = getUpdatedValue(location, QueryField.LOCATION_COUNTRYNAME,
                            location.getCountryName());
                    if (iso != null && iso.length() < 3)
                        iso = LocationConstants.iso2Toiso3.get(iso);
                    key = iso == null ? cname : iso;
                    StringBuilder sb = new StringBuilder();
                    if (cname != null)
                        sb.append(cname);
                    if (iso != null && !iso.isEmpty())
                        sb.append(" (").append(iso).append(')').toString(); //$NON-NLS-1$
                    if (sb.length() == 0) {
                        name = Messages.EditMetaDialog_unknown_country;
                        unknown = true;
                    } else
                        name = sb.toString();
                    break;
                case LocationNode.STATE:
                    key = getUpdatedValue(location, QueryField.LOCATION_STATE, location.getProvinceOrState());
                    name = key;
                    if (name == null || name.isEmpty()) {
                        name = Messages.EditMetaDialog_unknown_state;
                        unknown = true;
                    }
                    break;
                default:
                    key = getUpdatedValue(location, QueryField.LOCATION_CITY, location.getCity());
                    name = key;
                    if (name == null || name.isEmpty()) {
                        name = Messages.EditMetaDialog_unknown_city;
                        unknown = true;
                    }
                    break;
                }
                if (key == null)
                    key = ""; //$NON-NLS-1$
                LocationNode current = null;
                int size = base.size();
                for (int i = 0; i < size; i++)
                    if (base.get(i).getKey().equals(key)) {
                        current = base.get(i);
                        break;
                    }
                if (current == null && add)
                    base.add(current = new LocationNode(parent, key, name, level, null, unknown, false));
                if (current != null)
                    buildLocationTree(current.getChildren(), current, location, level + 1, add);
            } else if (add)
                base.add(new LocationNode(parent, "", null, level, location, false, !usedObjects.contains(locId))); //$NON-NLS-1$
            else
                for (Iterator<LocationNode> it = base.iterator(); it.hasNext();)
                    if (it.next().getLocation().equals(location)) {
                        it.remove();
                        break;
                    }
        }

        private boolean colorLocationTree(List<LocationNode> base) {
            if (base == null || base.isEmpty())
                return false;
            boolean allUnused = true;
            int size = base.size();
            for (int i = 0; i < size; i++) {
                LocationNode node = base.get(i);
                if (colorLocationTree(node.children))
                    node.setUnused(true);
                allUnused &= node.isUnused();
            }
            return allUnused;
        }

        public void saveSettings() {
            if (radioGroup != null)
                radioGroup.saveSettings();
            flatComponent.saveSettings();
            if (locTreeViewer != null) {
                Object[] expandedElements = locTreeViewer.getExpandedElements();
                StringBuilder sb = new StringBuilder();
                StringBuilder sbp = new StringBuilder();
                for (int i = 0; i < expandedElements.length; i++) {
                    LocationNode node = (LocationNode) expandedElements[i];
                    sbp.setLength(0);
                    node.buildPath(sbp);
                    if (sb.length() > 0)
                        sb.append(';');
                    sb.append(sbp);
                }
                settings.put(TREEEXPANSION + type, sb.toString());
            }
        }

        public void widgetDisposed(DisposeEvent e) {
            Job.getJobManager().cancel(this);
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
            updateStructButtons();
        }

    }

    public class LocTreeContentProvider implements ITreeContentProvider {

        public void dispose() {
            // do nothing
        }

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

        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof Collection<?>)
                return ((Collection<?>) inputElement).toArray();
            return EMPTYOBJECTARRAY;
        }

        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof LocationNode) {
                List<LocationNode> children = ((LocationNode) parentElement).getChildren();
                if (children != null)
                    return children.toArray();
            }
            return EMPTYOBJECTARRAY;
        }

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

        public boolean hasChildren(Object element) {
            if (element instanceof LocationNode) {
                List<LocationNode> children = ((LocationNode) element).getChildren();
                return (children != null && !children.isEmpty());
            }
            return false;
        }

    }

    public class LocationNode {
        public static final int CONTINENT = 0;
        public static final int COUNTRY = 1;
        public static final int STATE = 2;
        public static final int CITY = 3;
        public static final int DETAIL = 4;

        private String name;
        private String key;
        private List<LocationNode> children;
        private final LocationNode parent;
        private final LocationImpl location;
        private final int level;
        private final boolean unknown;
        private boolean unused;

        public LocationNode(LocationNode parent, String key, String name, int level, LocationImpl location,
                boolean unknown, boolean unused) {
            this.parent = parent;
            this.key = key;
            this.name = name;
            this.level = level;
            this.location = location;
            this.unknown = unknown;
            this.unused = unused;
        }

        public LocationNode find(String s) {
            String start;
            int p = s.indexOf('>');
            if (p >= 0)
                start = s.substring(0, p);
            else
                start = s;
            if (start.equals(name)) {
                if (p < 0)
                    return this;
                if (children != null)
                    for (LocationNode child : children) {
                        LocationNode found = child.find(s.substring(p + 1));
                        if (found != null)
                            return found;
                    }
            }
            return null;
        }

        public void buildPath(StringBuilder sbp) {
            if (sbp.length() > 0)
                sbp.insert(0, '>');
            sbp.insert(0, name);
            if (parent != null)
                parent.buildPath(sbp);
        }

        public void setUnused(boolean unused) {
            this.unused = unused;
        }

        public boolean isUnused() {
            return unused;
        }

        public LocationNode getParent() {
            return parent;
        }

        @Override
        public String toString() {
            if (name != null)
                return name;
            StringBuilder sb = new StringBuilder();
            append(sb, getUpdatedValue(location, QueryField.LOCATION_DETAILS, location.getDetails()));
            String sublocation = location.getSublocation();
            if (sublocation != null && !sublocation.equals(location.getCity()))
                append(sb, sublocation);
            Double latitude = location.getLatitude();
            if (latitude != null && !Double.isNaN(latitude))
                append(sb, Format.latitudeFormatter.toString(latitude));
            Double longitude = location.getLongitude();
            if (longitude != null && !Double.isNaN(longitude))
                append(sb, Format.longitudeFormatter.toString(longitude));
            Double altitude = location.getAltitude();
            if (altitude != null && !Double.isNaN(altitude))
                append(sb, Format.altitudeFormatter.toString(altitude));
            append(sb, location.getPlusCode());
            return (sb.length() == 0) ? NO_DETAILS : sb.toString();
        }

        private void append(StringBuilder sb, String s) {
            if (s != null && !s.isEmpty()) {
                if (sb.length() > 0)
                    sb.append("; "); //$NON-NLS-1$
                sb.append(s);
            }
        }

        public boolean addChild(LocationNode e) {
            return getChildren().add(e);
        }

        public String getName() {
            return name;
        }

        public List<LocationNode> getChildren() {
            if (children == null)
                children = new ArrayList<EditMetaDialog.LocationNode>();
            return children;
        }

        public String getKey() {
            return key;
        }

        public int getLevel() {
            return level;
        }

        public LocationImpl getLocation() {
            return location;
        }

        public boolean isUnknown() {
            return unknown;
        }

    }

    public static int WELCOME = -1;
    public static int OVERVIEW = 0;
    public static int THUMBNAILS = 1;
    public static int CATEGORIES = 2;
    public static int KEYWORDS = 3;
    public static int INDEXING = 4;
    public static int LOCATIONS = 5;
    public static int ARTWORK = 6;
    public static int CONTACTS = 7;
    public static int STATISTICS = 8;
    public static int WATCHEDFOLDERS = 9;

    private static final Object[] EMPTY = new Object[0];

    private static final long HIGHWATERMARK = 2L * 1024L * 1024L * 1024L - 205L * 1024L * 1024L;
    private static final String NO_DETAILS = Messages.EditMetaDialog_no_details;

    private static final String HIERARCHICAL_STRUCT = "hierarchicalStruct"; //$NON-NLS-1$
    private static final String TREEEXPANSION = "treeExpansion"; //$NON-NLS-1$

    private Text yearlySeqNoField;
    private Text seqNoField;
    private Button fromPreviewButton;
    private TextWithVariableGroup backupField;
    protected static final IInputValidator keywordValidator = new KeywordValidator();
    static final String[] KEYWORDEXTENSIONS = new String[] { "*" //$NON-NLS-1$
            + Constants.KEYWORDFILEEXTENSION + ";*" //$NON-NLS-1$
            + Constants.KEYWORDFILEEXTENSION.toUpperCase(), Messages.EditMetaDialog_0 };
    private Text fileName;
    private Text creationDate;
    private Text lastImport;
    private CheckedText ownerInformation;
    private CheckedText description;
    private Text userFieldLabel1;
    private Text userFieldLabel2;
    private Meta meta;
    private TreeViewer keywordViewer;
    private TreeViewer catTreeViewer;
    private Button categoryRemoveButton;
    private Button categoryEditButton;
    private Button categoryAddButton;
    private Button categoryRefineButton;
    private Button keywordLoadButton;
    private Button keywordSaveButton;
    private Button saveCatButton;
    private Button loadCatButton;
    private Map<String, Category> categories;
    private ComboViewer timelineViewer;
    private IWorkbenchPage workbenchPage;
    private Label versionLabel;
    private Label catSizeField;
    private Label freeSpaceField;
    private Label freeSegmentsField;
    private Label collectionsField;
    private Label imagesField;
    private static final NumberFormat nf = NumberFormat.getNumberInstance();
    protected static final SimpleDateFormat df = new SimpleDateFormat(
            Messages.EditMetaDialog_observation_date_format);
    private static final int PREVIOUS = 99;
    private static final int NEXT = 98;
    protected static final Object EMPTYSTRINGARRAY = new String[0];
    protected final Object[] EMPTYOBJECTARRAY = new Object[0];
    private static final long ONEDAY = 86400000L;
    private static final String SETTINGSID = "com.bdaum.zoom.ui.editMetaDialog"; //$NON-NLS-1$
    protected static final String[] VOCABEXTENSIONS = null;
    private Label locationsField;
    private Label contactsField;
    private Label artworksField;
    private Label exhibitionsField;
    private Label slideshowsField;
    private Label webgalleriesField;
    private TableViewer watchedFolderViewer;
    private Button addFolderButton;
    private Button removeFolderButton;
    private List<WatchedFolder> watchedFolders = new ArrayList<>();
    private List<String> vocabularies = new ArrayList<>();
    private ArrayList<WatchedFolder> folderBackup;
    private CheckboxButton pauseButton;
    private CheckboxButton readOnlyButton;
    private boolean newDb = true;
    private CheckboxButton autoWatchButton;
    private Button keywordDeleteButton;
    private Button keywordAddButton;
    private Set<String> keywords;
    protected List<DbOperation> todo = new ArrayList<DbOperation>(3);
    private Button keywordCollectButton;
    private Button keywordShowButton;
    private CTabFolder tabFolder;
    private boolean[] visited;
    private ComboViewer languageCombo;
    private NumericControl latencyField;
    private Button keywordModifyButton;
    private CheckboxButton bgimportField;
    private Label personsField;
    private StructGroup locationGroup;
    private Button createTimeLineButton;
    private ComboViewer locationViewer;
    private Button createLocationFoldersButton;
    private CompressionGroup compressionGroup;
    private CheckboxTableViewer simViewer;
    private ContainerCheckedTreeViewer textIndexViewer;
    private CheckboxButton noIndexButton;
    private CheckboxButton slideTitleButton;
    private CheckboxButton slideDescrButton;
    private CheckboxButton exhibitionDescrButton;
    private CheckboxButton exhibitionTitleButton;
    private CheckboxButton webgalleryDescrButton;
    private CheckboxButton webgalleryTitleButton;
    private CheckboxButton personsButton;
    private CGroup textGroup;
    private CheckboxButton exhibitionCredButton;
    private CheckboxButton webgalleryAltButton;
    private int initialPage;
    private CheckboxButton addToKeywordsButton;
    private Map<String, Map<QueryField, Object>> structOverlayMap = new HashMap<String, Map<QueryField, Object>>();
    private Map<String, IIdentifiableObject> newStructMap = new HashMap<String, IIdentifiableObject>();
    private CheckboxButton fileNameButton;
    private Button keywordReplaceButton;
    private FlatGroup flatKeywordGroup;
    private StructGroup artworkGroup;
    private StructGroup contactsGroup;
    private Label groupField;
    private Label[] detailFields;
    private ProgressBar detailsProgressBar;
    private CGroup detailsGroup;
    private ExpandCollapseGroup keywordExpandCollapseGroup;
    private Button editFolderButton;
    private IDialogSettings settings;
    protected boolean excludeGeographic;
    private Set<String> geographic;
    private ComboViewer themeField;
    private Meta previousMeta;
    private Theme theme;
    private List<String> categoryChanges = new ArrayList<>();
    private CLink configureKeywordLink;
    private RadioButtonGroup initButtonGroup;
    private RadioButtonGroup thumbSizeGroup;
    private RadioButtonGroup sharpenButtonGroup;
    private Button vocabAddButton;
    private Button vocabRemoveButton;
    private Button vocabViewButton;
    private TableViewer vocabViewer;
    private Button vocabEnforceButton;
    private VocabManager vocabManager;
    private boolean essentialAlgos = true;
    private Set<String> cbirAlgorithms = new HashSet<String>(CoreActivator.getDefault().getCbirAlgorithms());
    private Composite simComp;
    private boolean cntrlDwn;
    private KeyAdapter keyListener = new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            if (e.keyCode == SWT.CTRL)
                cntrlDwn = true;
        }

        @Override
        public void keyReleased(KeyEvent e) {
            if (e.keyCode == SWT.CTRL)
                cntrlDwn = false;
        }
    };
    private CLink backupIntervalLink;

    public EditMetaDialog(Shell parentShell, IWorkbenchPage workbenchPage, IDbManager dbManager, boolean newDb,
            Meta previousMeta) {
        super(parentShell, newDb ? HelpContextIds.NEWCAT_PROPERTIES_DIALOG : HelpContextIds.CAT_PROPERTIES_DIALOG);
        this.workbenchPage = workbenchPage;
        this.dbManager = dbManager;
        this.newDb = newDb;
        this.previousMeta = previousMeta;
        this.theme = previousMeta != null ? CoreActivator.getDefault().getCurrentTheme() : null;
        this.settings = getDialogSettings(UiActivator.getDefault(), SETTINGSID);
    }

    @Override
    public void create() {
        super.create();
        setTitle(Messages.EditMetaDialog_edit_cat_properties);
        setMessage(Messages.EditMetaDialog_configure_this_cat);
        updateButtons();
        if (newDb) {
            final Shell c = getShell();
            c.getDisplay().timerExec(300, () -> {
                if (!c.isDisposed() && c.isListening(SWT.Help))
                    c.notifyListeners(SWT.Help, new Event());
            });
        }
    }

    @Override
    protected Control createDialogArea(final Composite parent) {
        Composite comp = (Composite) super.createDialogArea(parent);
        comp.setLayout(new GridLayout());
        final Composite editArea = createHeaderGroup(comp);
        tabFolder = new CTabFolder(editArea, SWT.BORDER);
        GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
        layoutData.verticalIndent = 5;
        tabFolder.setLayoutData(layoutData);
        int pgCnt = 0;
        if (newDb) {
            createWelcomeGroup(tabFolder);
            WELCOME = pgCnt++;
        }
        createOverviewGroup(tabFolder);
        OVERVIEW = pgCnt++;
        createThumbnailsGroup(tabFolder);
        THUMBNAILS = pgCnt++;
        createCategoriesGroup(parent, tabFolder);
        CATEGORIES = pgCnt++;
        createKeywordsGroup(tabFolder);
        KEYWORDS = pgCnt++;
        if (Core.getCore().getDbFactory().getLireServiceVersion() >= 0) {
            createIndexingGroup(tabFolder);
            INDEXING = pgCnt++;
        }
        locationGroup = createStructComponent(tabFolder, QueryField.T_LOCATION, Messages.EditMetaDialog_locations,
                Messages.EditMetaDialog_locations_tooltip, Messages.EditMetaDialog_location);
        LOCATIONS = pgCnt++;
        artworkGroup = createStructComponent(tabFolder, QueryField.T_OBJECT, Messages.EditMetaDialog_artwork,
                Messages.EditMetaDialog_artworks_tooltip, Messages.EditMetaDialog_object);
        ARTWORK = pgCnt++;
        contactsGroup = createStructComponent(tabFolder, QueryField.T_CONTACT, Messages.EditMetaDialog_contacts,
                Messages.EditMetaDialog_contacts_tooltip, Messages.EditMetaDialog_contact);
        CONTACTS = pgCnt++;
        createStatisticsGroup(tabFolder);
        STATISTICS = pgCnt++;
        createWatchedFolderGroup(tabFolder);
        WATCHEDFOLDERS = pgCnt++;
        visited = new boolean[pgCnt];
        tabFolder.setSimple(false);
        tabFolder.setSelection(initialPage);
        initHeader();
        initPages(initialPage);
        tabFolder.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                tabChanged();
            }
        });
        updateTabItems();
        if (CATEGORIES == initialPage)
            catTreeViewer.expandAll();
        return comp;
    }

    private void createWelcomeGroup(CTabFolder folder) {
        final Composite composite = createTabPage(folder, Messages.EditMetaDialog_welcome,
                Messages.EditMetaDialog_welcome_tooltip, 1);
        Composite comp = new Composite(composite, SWT.NONE);
        comp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        GridLayout layout = new GridLayout(1, false);
        layout.marginWidth = 50;
        layout.marginHeight = 100;
        comp.setLayout(layout);
        Label label = new Label(comp, SWT.WRAP);
        label.setFont(JFaceResources.getHeaderFont());
        label.setAlignment(SWT.CENTER);
        label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        label.setText(Messages.EditMetaDialog_you_have_opted);
        if (previousMeta != null) {
            initButtonGroup = new RadioButtonGroup(comp, Messages.EditMetaDialog_initial_values, SWT.NONE,
                    Messages.EditMetaDialog_default_values, Messages.EditMetaDialog_copy_values);
            initButtonGroup.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
            initButtonGroup.setSelection(1);
        }
    }

    @SuppressWarnings("unused")
    private void createIndexingGroup(CTabFolder folder) {
        final Composite ixComp = createTabPage(folder, Messages.EditMetaDialog_indexing,
                Messages.EditMetaDialog_configure_index, 1, 0);
        final Composite composite = new Composite(ixComp, SWT.NONE);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        composite.setLayout(new GridLayout());
        noIndexButton = WidgetFactory.createCheckButton(composite, Messages.EditMetaDialog_no_indexing, null);
        noIndexButton.addListener(new Listener() {

            @Override
            public void handleEvent(Event event) {
                updateIndexingControls();
            }
        });
        simComp = new Composite(composite, SWT.NONE);
        simComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        GridLayout layout = new GridLayout(1, false);
        layout.marginWidth = 0;
        simComp.setLayout(layout);
        Label label = new Label(simComp, SWT.WRAP);
        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        label.setText(Messages.EditMetaDialog_influences_speed);
        label = new Label(simComp, SWT.WRAP);
        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        label.setText(Messages.EditMetaDialog_configure_index_warning);
        Composite vGroup = new Composite(simComp, SWT.NONE);
        vGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        layout = new GridLayout(2, false);
        layout.marginWidth = 0;
        vGroup.setLayout(layout);
        CGroup simGroup = new CGroup(vGroup, SWT.NONE);
        simGroup.setText(Messages.EditMetaDialog_similarity_algos);
        simGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
        simGroup.setLayout(new GridLayout());
        simViewer = CheckboxTableViewer.newCheckList(simGroup, SWT.FULL_SELECTION | SWT.SINGLE | SWT.V_SCROLL);
        GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
        layoutData.heightHint = 200;
        layoutData.widthHint = 400;
        simViewer.getControl().setLayoutData(layoutData);
        simViewer.setContentProvider(ArrayContentProvider.getInstance());
        ZColumnViewerToolTipSupport.enableFor(simViewer, ToolTip.NO_RECREATE);
        simViewer.setComparator(ZViewerComparator.INSTANCE);
        simViewer.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getToolTipText(Object element) {
                if (element instanceof Algorithm && UiActivator.getDefault().getShowHover())
                    return ((Algorithm) element).getDescription();
                return super.getToolTipText(element);
            }

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

            @Override
            public Image getImage(Object element) {
                if (element instanceof Algorithm && ((Algorithm) element).isAi())
                    return Icons.neural.getImage();
                return null;
            }
        });
        Button algoButton = new Button(simGroup, SWT.PUSH);
        algoButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
        algoButton.setText(Messages.EditMetaDialog_show_more);
        algoButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                essentialAlgos = !essentialAlgos;
                algoButton.setText(
                        essentialAlgos ? Messages.EditMetaDialog_show_more : Messages.EditMetaDialog_show_less);
                updateSelectedAlgorithms();
                Object[] checkedElements = simViewer.getCheckedElements();
                simViewer.setInput(simViewer.getInput());
                simViewer.setCheckedElements(checkedElements);
                simGroup.layout(true, true);
            }
        });
        simViewer.addCheckStateListener(new ICheckStateListener() {
            @Override
            public void checkStateChanged(CheckStateChangedEvent event) {
                validateAlgorithms();
            }
        });
        simViewer.setFilters(new ViewerFilter() {
            @Override
            public boolean select(Viewer viewer, Object parentElement, Object element) {
                if (essentialAlgos && element instanceof Algorithm)
                    return ((Algorithm) element).isEssential()
                            || cbirAlgorithms.contains(((Algorithm) element).getName());
                return true;
            }
        });
        simViewer.setInput(Core.getCore().getDbFactory().getLireService(true).getSupportedSimilarityAlgorithms());
        textGroup = new CGroup(vGroup, SWT.NONE);
        textGroup.setText(Messages.EditMetaDialog_fields_in_text_search);
        textGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        textGroup.setLayout(new GridLayout());
        textIndexViewer = new ContainerCheckedTreeViewer(textGroup, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
        setViewerLayout(textIndexViewer, SWT.DEFAULT, 1);
        textIndexViewer.setLabelProvider(new MetadataLabelProvider());
        textIndexViewer.setContentProvider(new MetadataContentProvider());
        textIndexViewer.setFilters(new ViewerFilter[] { new ViewerFilter() {
            @Override
            public boolean select(Viewer aViewer, Object parentElement, Object element) {
                if (element instanceof QueryField && ((QueryField) element).getLabel() != null)
                    return isFullTextSearch((QueryField) element);
                return false;
            }

            private boolean isFullTextSearch(QueryField qf) {
                if (qf.hasChildren()) {
                    for (QueryField child : qf.getChildren())
                        if (isFullTextSearch(child))
                            return true;
                    return false;
                }
                return qf.isFullTextSearch();
            }
        } });
        textIndexViewer.setComparator(ZViewerComparator.INSTANCE);
        UiUtilities.installDoubleClickExpansion(textIndexViewer);
        textIndexViewer.setInput(QueryField.ALL);
        textIndexViewer.expandToLevel(1);
        textIndexViewer.expandToLevel(QueryField.IMAGE_ALL, 1);
        Composite bGroup = new Composite(textGroup, SWT.NONE);
        bGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        bGroup.setLayout(new GridLayout(4, false));
        fileNameButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_file_name, null);
        personsButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_persons_shown,
                new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 3, 1));
        Label sep = new Label(bGroup, SWT.SEPARATOR | SWT.HORIZONTAL);
        sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 4, 1));

        Label lab = new Label(bGroup, SWT.NONE);
        lab.setFont(JFaceResources.getBannerFont());
        lab.setText(Messages.EditMetaDialog_slide_shows);
        lab.setLayoutData(new GridData(140, SWT.DEFAULT));
        slideTitleButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_titles, null);
        slideDescrButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_descriptions, null);
        new Label(bGroup, SWT.NONE);
        lab = new Label(bGroup, SWT.NONE);
        lab.setFont(JFaceResources.getBannerFont());
        lab.setText(Messages.EditMetaDialog_exhibitions);
        exhibitionTitleButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_titles, null);
        exhibitionDescrButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_descriptions, null);
        exhibitionCredButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_credits, null);
        lab = new Label(bGroup, SWT.NONE);
        lab.setFont(JFaceResources.getBannerFont());
        lab.setText(Messages.EditMetaDialog_webgalleries);
        webgalleryTitleButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_titles, null);
        webgalleryDescrButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_descriptions, null);
        webgalleryAltButton = WidgetFactory.createCheckButton(bGroup, Messages.EditMetaDialog_alt_texts, null);
    }

    protected void validateAlgorithms() {
        Object[] checkedElements = simViewer.getCheckedElements();
        String errorMessage = null;
        if (checkedElements.length == 0)
            errorMessage = Messages.EditMetaDialog_select_at_least_one;
        else {
            for (Object element : checkedElements) {
                if (element instanceof Algorithm) {
                    Algorithm aiAlgorithm = (Algorithm) element;
                    if (!aiAlgorithm.isEnabled())
                        errorMessage = NLS.bind(Messages.EditMetaDialog_ai_enabled, aiAlgorithm.getName());
                    else if (!aiAlgorithm.isAccountValid())
                        errorMessage = NLS.bind(Messages.EditMetaDialog_account_valid, aiAlgorithm.getName());
                    if (errorMessage != null)
                        break;
                }
            }
        }

        setErrorMessage(errorMessage);
        getButton(OK).setEnabled(errorMessage == null);
    }

    private void updateIndexingControls() {
        boolean enabled = !noIndexButton.getSelection();
        simComp.setVisible(enabled);
        textGroup.setVisible(enabled);
    }

    protected void updateTabItems() {
        CTabItem selection = tabFolder.getSelection();
        for (CTabItem item : tabFolder.getItems())
            item.setFont(selection == item ? JFaceResources.getBannerFont() : JFaceResources.getDefaultFont());
    }

    private Composite createHeaderGroup(Composite comp) {
        final Composite header = new Composite(comp, SWT.NONE);
        header.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        header.setLayout(new GridLayout(4, false));
        // Line 1
        new Label(header, SWT.NONE).setText(Messages.EditMetaDialog_file_name);
        Composite catGroup = new Composite(header, SWT.NONE);
        catGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.marginWidth = 0;
        catGroup.setLayout(gridLayout);

        fileName = new Text(catGroup, SWT.READ_ONLY | SWT.BORDER);
        fileName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        fileName.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDoubleClick(MouseEvent e) {
                BatchUtilities.showInFolder(new File(fileName.getText()));
            }
        });
        versionLabel = new Label(catGroup, SWT.NONE);

        GridData data = new GridData(SWT.END, SWT.CENTER, false, false);
        data.horizontalIndent = 20;
        versionLabel.setLayoutData(data);
        // Line 2
        new Label(header, SWT.NONE).setText(Messages.EditMetaDialog_catalog_theme);
        themeField = new ComboViewer(header, SWT.DROP_DOWN | SWT.READ_ONLY);
        themeField.getControl().setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
        themeField.setContentProvider(ArrayContentProvider.getInstance());
        themeField.setLabelProvider(new LabelProvider());
        themeField.setInput(CoreActivator.getDefault().getThemes().values());
        final BundleContext bundleContext = UiActivator.getDefault().getBundle().getBundleContext();
        final ServiceReference<?> ref = bundleContext.getServiceReference(ISpellCheckingService.class.getName());
        if (ref != null) {
            Label label = new Label(header, SWT.RIGHT);
            label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
            label.setText(Messages.EditMetaDialog_language);
            final ISpellCheckingService service = (ISpellCheckingService) bundleContext.getService(ref);
            Collection<String> supportedLanguages = service.getSupportedLanguages();
            bundleContext.ungetService(ref);
            if (supportedLanguages != null && !supportedLanguages.isEmpty()) {
                languageCombo = new ComboViewer(header);
                languageCombo.setContentProvider(ArrayContentProvider.getInstance());
                languageCombo.setLabelProvider(new LabelProvider() {
                    @Override
                    public String getText(Object element) {
                        if (element instanceof String) {
                            String prefix = (String) element;
                            String variant = ""; //$NON-NLS-1$
                            int p = prefix.indexOf('_');
                            if (p > 0) {
                                p = prefix.indexOf('_', p + 1);
                                if (p > 0) {
                                    String v = prefix.substring(p + 1);
                                    if ("frami".equals(v)) //$NON-NLS-1$
                                        v = Messages.EditMetaDialog_frami;
                                    variant = " (" + v + ')'; //$NON-NLS-1$
                                    prefix = prefix.substring(0, p);
                                }
                            }
                            for (Locale locale : Locale.getAvailableLocales())
                                if (locale.toString().equals(prefix))
                                    return locale.getDisplayName() + variant;
                        }
                        return super.getText(element);
                    }
                });
                languageCombo.setComparator(ZViewerComparator.INSTANCE);
                languageCombo.setInput(supportedLanguages);
            } else {
                CLink link = new CLink(header, SWT.NONE);
                link.setText(Messages.EditMetaDialog_intall_dict);
                link.addListener(new Listener() {
                    @Override
                    public void handleEvent(Event event) {
                        try {
                            String language = Locale.getDefault().getLanguage();
                            String url = System.getProperty("com.bdaum.zoom.dictionaries." + language); //$NON-NLS-1$
                            if (url == null)
                                url = System.getProperty("com.bdaum.zoom.dictionaries"); //$NON-NLS-1$
                            PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser()
                                    .openURL(new URL(url));
                        } catch (PartInitException e1) {
                            // do nothing
                        } catch (MalformedURLException e1) {
                            // should never happen
                        }
                    }
                });
            }
        } else {
            Label label = new Label(header, SWT.NONE);
            label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 2, 1));
        }
        // Line 3
        Composite line3Comp = new Composite(header, SWT.NONE);
        line3Comp.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 2, 1));
        GridLayout layout = new GridLayout(3, false);
        layout.marginHeight = layout.marginWidth = 0;
        line3Comp.setLayout(layout);

        new Label(line3Comp, SWT.NONE).setText(Messages.EditMetaDialog_creation_date);

        creationDate = new Text(line3Comp, SWT.READ_ONLY | SWT.BORDER);
        creationDate.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));

        readOnlyButton = WidgetFactory.createCheckButton(line3Comp, Messages.EditMetaDialog_write_protected,
                new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 1, 1));
        readOnlyButton.addListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                readonly = readOnlyButton.getSelection();
                updateFolderButtons();
                updateButtons();
                updateFields();
            }
        });
        readOnlyButton.setEnabled(!newDb);

        Label label = new Label(header, SWT.RIGHT);
        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        label.setText(Messages.EditMetaDialog_last_import);

        lastImport = new Text(header, SWT.READ_ONLY | SWT.BORDER);
        lastImport.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDoubleClick(MouseEvent e) {
                IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                if (window != null) {
                    LastImportCommand command = new LastImportCommand();
                    command.init(window);
                    command.run();
                }
            }
        });
        // Line 4
        new Label(header, SWT.NONE).setText(Messages.EditMetaDialog_seqno);

        seqNoField = new Text(header, SWT.READ_ONLY | SWT.BORDER);
        seqNoField.setLayoutData(new GridData(80, SWT.DEFAULT));

        label = new Label(header, SWT.RIGHT);
        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        label.setText(Messages.EditMetaDialog_yearlyseqno);

        yearlySeqNoField = new Text(header, SWT.READ_ONLY | SWT.BORDER);
        yearlySeqNoField.setLayoutData(new GridData(80, SWT.DEFAULT));

        // Line 5
        new Label(header, SWT.NONE).setText(Messages.EditMetaDialog_backup_file);
        Composite backupGroup = new Composite(header, SWT.NONE);
        backupGroup.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 3, 1));
        gridLayout = new GridLayout(3, false);
        gridLayout.marginWidth = 0;
        backupGroup.setLayout(gridLayout);
        backupField = new TextWithVariableGroup(backupGroup, null, 400, Constants.BV_ALL, false, null, null, null); // $NON-NLS-1$
        backupIntervalLink = new CLink(backupGroup, SWT.NONE);
        backupIntervalLink.setText(Messages.EditMetaDialog_configure_interval);
        backupIntervalLink.addListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                PreferencesUtil
                        .createPreferenceDialogOn(getShell(), GeneralPreferencePage.ID, new String[0], "backup") //$NON-NLS-1$
                        .open();
                updateBackupTooltip();
            }
        });
        final Composite editArea = new Composite(comp, SWT.NONE);
        editArea.setLayout(new GridLayout());
        editArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        return editArea;
    }

    protected void updateFields() {
        ownerInformation.setEnabled(!readonly);
        themeField.getCombo().setEnabled(!readonly);
        description.setEnabled(!readonly);
        userFieldLabel1.setEnabled(!readonly);
        userFieldLabel2.setEnabled(!readonly);
        bgimportField.setEnabled(!readonly);
        timelineViewer.getCombo().setEnabled(!readonly);
        if (createTimeLineButton != null)
            createTimeLineButton.setEnabled(!readonly);
        locationViewer.getCombo().setEnabled(!readonly);
        if (createLocationFoldersButton != null)
            createLocationFoldersButton.setEnabled(!readonly);
        thumbSizeGroup.setEnabled(!readonly);
        fromPreviewButton.setEnabled(!readonly);
        configureKeywordLink.setEnabled(!readonly);
        addToKeywordsButton.setEnabled(!readonly);
        watchedFolderViewer.getTable().setEnabled(!readonly);
        latencyField.setEnabled(!readonly);
        pauseButton.setEnabled(!readonly);
        if (languageCombo != null)
            languageCombo.getControl().setEnabled(!readonly);
        bgimportField.setEnabled(!readonly);
        autoWatchButton.setEnabled(!readonly);
        locationGroup.setEnabled(!readonly);
        locationViewer.getCombo().setEnabled(!readonly);
        compressionGroup.setEnabled(!readonly);
        flatKeywordGroup.setEnabled(!readonly);
        artworkGroup.setEnabled(!readonly);
        contactsGroup.setEnabled(!readonly);
    }

    private void createOverviewGroup(final CTabFolder folder) {
        final Composite composite = createTabPage(folder, Messages.EditMetaDialog_overview,
                Messages.EditMetaDialog_owner_info, 1, 0);

        GridData layoutData = new GridData(SWT.FILL, SWT.CENTER, true, false);
        layoutData.heightHint = 40;
        layoutData.widthHint = 400;
        ownerInformation = new CheckedText(composite, SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.WRAP);
        ownerInformation.setSpellingOptions(8, ISpellCheckingService.DESCRIPTIONOPTIONS);
        ownerInformation.setLayoutData(layoutData);
        new Label(composite, SWT.NONE).setText(Messages.EditMetaDialog_description);

        layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
        layoutData.heightHint = 100;
        layoutData.widthHint = 400;
        description = new CheckedText(composite, SWT.V_SCROLL | SWT.BORDER | SWT.MULTI | SWT.WRAP);
        description.setLayoutData(layoutData);
        final Composite optionsGroup = new Composite(composite, SWT.NONE);
        optionsGroup.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
        optionsGroup.setLayout(new GridLayout(7, false));

        new Label(optionsGroup, SWT.NONE).setText(Messages.EditMetaDialog_create_timeline);

        timelineViewer = new ComboViewer(optionsGroup);
        timelineViewer.setContentProvider(ArrayContentProvider.getInstance());
        timelineViewer.setLabelProvider(new LabelProvider() {

            @Override
            public String getText(Object element) {
                if (Meta_type.timeline_year.equals(element))
                    return Messages.EditMetaDialog_by_year;
                if (Meta_type.timeline_month.equals(element))
                    return Messages.EditMetaDialog_by_month;
                if (Meta_type.timeline_day.equals(element))
                    return Messages.EditMetaDialog_by_day;
                if (Meta_type.timeline_week.equals(element))
                    return Messages.EditMetaDialog_by_week;
                if (Meta_type.timeline_weekAndDay.equals(element))
                    return Messages.EditMetaDialog_by_week_and_day;
                return Messages.EditMetaDialog_none;
            }
        });
        timelineViewer.setInput(Meta_type.timelineALLVALUES);
        if (!newDb) {
            createTimeLineButton = new Button(optionsGroup, SWT.PUSH);
            createTimeLineButton.setText(Messages.EditMetaDialog_create_now);
            createTimeLineButton.setToolTipText(Messages.EditMetaDialog_recreate_timeline);
            createTimeLineButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    OperationJob.executeOperation(
                            new CreateTimelineOperation(
                                    (String) timelineViewer.getStructuredSelection().getFirstElement()),
                            EditMetaDialog.this);
                }
            });
        }
        timelineViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                updateCreateNowButtons();
            }
        });
        new Label(optionsGroup, SWT.NONE).setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        new Label(optionsGroup, SWT.NONE).setText(Messages.EditMetaDialog_create_loc_folders);

        locationViewer = new ComboViewer(optionsGroup);
        locationViewer.setContentProvider(ArrayContentProvider.getInstance());
        locationViewer.setLabelProvider(new ZColumnLabelProvider() {

            @Override
            public String getText(Object element) {
                if (Meta_type.locationFolders_country.equals(element))
                    return Messages.EditMetaDialog_byCountry;
                if (Meta_type.locationFolders_state.equals(element))
                    return Messages.EditMetaDialog_byState;
                if (Meta_type.locationFolders_city.equals(element))
                    return Messages.EditMetaDialog_byCity;
                return Messages.EditMetaDialog_none;
            }
        });
        locationViewer.setInput(Meta_type.locationFoldersALLVALUES);
        createLocationFoldersButton = new Button(optionsGroup, SWT.PUSH);
        createLocationFoldersButton.setText(Messages.EditMetaDialog_create_now);
        createLocationFoldersButton.setToolTipText(Messages.EditMetaDialog_recreate_locations);
        createLocationFoldersButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                OperationJob.executeOperation(
                        new CreateLocationFolderOperation(
                                (String) locationViewer.getStructuredSelection().getFirstElement()),
                        EditMetaDialog.this);
            }
        });
        locationViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                updateCreateNowButtons();
            }
        });

        Composite userfieldGroup = new Composite(composite, SWT.NONE);
        userfieldGroup.setLayoutData(new GridData(GridData.FILL_BOTH));
        userfieldGroup.setLayout(new GridLayout(2, false));
        new Label(userfieldGroup, SWT.NONE).setText(Messages.EditMetaDialog_user_field_lab_1);

        userFieldLabel1 = new Text(userfieldGroup, SWT.BORDER);
        userFieldLabel1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        new Label(userfieldGroup, SWT.NONE).setText(Messages.EditMetaDialog_user_field_lab_2);

        userFieldLabel2 = new Text(userfieldGroup, SWT.BORDER);
        userFieldLabel2.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
    }

    protected void updateCreateNowButtons() {
        String timeline = (String) timelineViewer.getStructuredSelection().getFirstElement();
        if (createTimeLineButton != null) {
            if (timeline == null)
                createTimeLineButton.setEnabled(false);
            else {
                createTimeLineButton.setEnabled(true);
                if (Meta_type.timeline_no.equals(timeline))
                    createTimeLineButton.setText(Messages.EditMetaDialog_remove_now);
                else
                    createTimeLineButton.setText(Messages.EditMetaDialog_create_now);
            }
        }
        String locfolder = (String) locationViewer.getStructuredSelection().getFirstElement();
        if (locfolder == null)
            createLocationFoldersButton.setEnabled(false);
        else {
            createLocationFoldersButton.setEnabled(true);
            if (Meta_type.timeline_no.equals(locfolder))
                createLocationFoldersButton.setText(Messages.EditMetaDialog_remove_now);
            else
                createLocationFoldersButton.setText(Messages.EditMetaDialog_create_now);
        }
    }

    private void computeStatistics() {
        Map<String, Long> statistics = dbManager.getStatistics();
        long totalSize = statistics.get(IDbManager.TOTALSIZE);
        catSizeField.setText(nf.format(totalSize));
        if (totalSize > HIGHWATERMARK) {
            freeSpaceField.setData(CSSProperties.ID, CSSProperties.ERRORS);
            freeSpaceField.setForeground(freeSpaceField.getDisplay().getSystemColor(SWT.COLOR_RED));
        } else {
            freeSpaceField.setData(CSSProperties.ID, null);
            freeSpaceField.setForeground(freeSpaceField.getParent().getForeground());
        }
        freeSpaceField.setText(nf.format(statistics.get(IDbManager.FREESPACE)));
        freeSegmentsField.setText(nf.format(statistics.get(IDbManager.FREESPACEENTRIES)));
        fillCountField(GroupImpl.class, groupField);
        fillCountField(SmartCollectionImpl.class, collectionsField);
        fillCountField(AssetImpl.class, imagesField);
        fillCountField(LocationImpl.class, locationsField);
        fillCountField(ContactImpl.class, contactsField);
        fillCountField(ArtworkOrObjectImpl.class, artworksField);
        fillCountField(ExhibitionImpl.class, exhibitionsField);
        fillCountField(SlideShowImpl.class, slideshowsField);
        fillCountField(WebGalleryImpl.class, webgalleriesField);
        List<SmartCollectionImpl> albums = dbManager.obtainObjects(SmartCollectionImpl.class, false, "system", true, //$NON-NLS-1$
                QueryField.EQUALS, "album", true, QueryField.EQUALS); //$NON-NLS-1$
        personsField.setText(nf.format(albums.size()));
    }

    private void fillCountField(Class<? extends IdentifiableObject> clazz, Label field) {
        field.setText(nf.format(dbManager.obtainObjects(clazz).size()));
    }

    private void createStatisticsGroup(CTabFolder folder) {
        final Composite stComp = createTabPage(folder, Messages.EditMetaDialog_Statistics,
                Messages.EditMetaDialog_statistics_tooltip, 2, 0);
        Composite composite = new Composite(stComp, SWT.NONE);
        composite.setLayout(new GridLayout(3, false));
        detailsGroup = new CGroup(stComp, SWT.NONE);
        detailsGroup.setText(Messages.EditMetaDialog_details);
        GridData layoutData = new GridData(SWT.FILL, SWT.CENTER, true, true);
        layoutData.horizontalIndent = 30;
        detailsGroup.setLayoutData(layoutData);
        detailsGroup.setLayout(new GridLayout(2, false));
        detailFields = new Label[16];
        for (int i = 0; i < detailFields.length; i++)
            detailFields[i] = createDetailsField(detailsGroup, 100);
        detailsProgressBar = new ProgressBar(detailsGroup, SWT.HORIZONTAL);
        GridData data = new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1);
        data.heightHint = 5;
        detailsProgressBar.setLayoutData(data);
        detailsProgressBar.setVisible(false);
        detailsGroup.setVisible(false);

        catSizeField = createTextField(composite, Messages.EditMetaDialog_Cat_file_size, 100, null, null);
        freeSpaceField = createTextField(composite, Messages.EditMetaDialog_free_space, 100, null, null);
        Listener defragListener = dbManager.isEmbedded() ? new Listener() {
            @Override
            public void handleEvent(Event e) {
                if (AcousticMessageDialog.openQuestion(getShell(), Messages.EditMetaDialog_cat_maintenance,
                        Messages.EditMetaDialog_defrag_continue)) {
                    File file = dbManager.getFile();
                    BatchActivator.setFastExit(false);
                    if (UiActivator.getDefault().preCatClose(CatalogListener.TUNE,
                            Messages.EditMetaDialog_closing_for_defrag, Messages.EditMetaDialog_operations_running,
                            false)) {
                        dbManager = CoreActivator.getDefault().openDatabase(file.getAbsolutePath());
                        CoreActivator.getDefault().setCatFile(file);
                        CoreActivator.getDefault().fireCatalogOpened(false);
                        UiActivator.getDefault().postCatOpen();
                        UiActivator.getDefault().postCatInit(false);
                        computeStatistics();
                    }
                    BatchActivator.setFastExit(true);
                }
            }
        } : null;
        freeSegmentsField = createTextField(composite, Messages.EditMetaDialog_free_segments, 100, defragListener,
                Messages.EditMetaDialog_defrag_now);
        createSeparator(composite);
        groupField = createTextField(composite, Messages.EditMetaDialog_groups, 100,
                new DetailSelectionAdapter(GroupImpl.class, Messages.EditMetaDialog_group_details), null);
        collectionsField = createTextField(composite, Messages.EditMetaDialog_collections, 100,
                new DetailSelectionAdapter(SmartCollectionImpl.class, Messages.EditMetaDialog_collection_details),
                null);
        imagesField = createTextField(composite, Messages.EditMetaDialog_images, 100, null, null);
        createSeparator(composite);
        locationsField = createTextField(composite, Messages.EditMetaDialog_locations, 100,
                new DetailSelectionAdapter(LocationImpl.class, Messages.EditMetaDialog_location_details), null);
        contactsField = createTextField(composite, Messages.EditMetaDialog_contacts, 100, null, null);
        artworksField = createTextField(composite, Messages.EditMetaDialog_artworks, 100, null, null);
        personsField = createTextField(composite, Messages.EditMetaDialog_persons, 100, null, null);
        createSeparator(composite);
        exhibitionsField = createTextField(composite, Messages.EditMetaDialog_exhibitions, 100, null, null);
        slideshowsField = createTextField(composite, Messages.EditMetaDialog_slideshows, 100, null, null);
        webgalleriesField = createTextField(composite, Messages.EditMetaDialog_webgalleries, 100, null, null);
        composite.pack();
    }

    protected void setDetails(int i, String label, int value) {
        Label textField = detailFields[i];
        ((Label) textField.getData(UiConstants.LABEL)).setText(label);
        textField.setText(value < 0 ? "" : String.valueOf(value)); //$NON-NLS-1$
        for (int j = i + 1; j < detailFields.length; j++) {
            ((Label) detailFields[j].getData(UiConstants.LABEL)).setText(""); //$NON-NLS-1$
            detailFields[j].setText(""); //$NON-NLS-1$
        }
    }

    @SuppressWarnings("unused")
    private void createWatchedFolderGroup(CTabFolder folder) {
        final Composite wfComp = createTabPage(folder, Messages.EditMetaDialog_watched_folders,
                Messages.EditMetaDialog_watchedFolders_tooltip, 1, 0);
        final Composite composite = new Composite(wfComp, SWT.NONE);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        composite.setLayout(new GridLayout(2, false));
        watchedFolderViewer = new TableViewer(composite,
                SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION);
        setViewerLayout(watchedFolderViewer, 200, 1);
        watchedFolderViewer.getTable().setLinesVisible(true);
        watchedFolderViewer.getTable().setHeaderVisible(true);
        TableViewerColumn col0 = createColumn(Messages.EditMetaDialog_path, 240);
        col0.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof WatchedFolder) {
                    String uri = ((WatchedFolder) element).getUri();
                    try {
                        File file = new File(new URI(uri));
                        String path = file.getPath();
                        if (!path.endsWith(File.separator))
                            path += File.separator;
                        if (!file.exists())
                            path += Messages.EditMetaDialog_offline;
                        return path;
                    } catch (URISyntaxException e) {
                        // ignore
                    }
                    return uri;
                }
                return null;
            }
        });
        TableViewerColumn col1 = createColumn(Messages.EditMetaDialog_volume, 80);
        col1.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof WatchedFolder) {
                    String volume = ((WatchedFolder) element).getVolume();
                    return volume == null ? "" : volume; //$NON-NLS-1$
                }
                return null;
            }
        });
        TableViewerColumn col2 = createColumn(Messages.EditMetaDialog_type, 60);
        col2.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof WatchedFolder) {
                    return ((WatchedFolder) element).getTransfer() ? Messages.EditMetaDialog_transfer
                            : Messages.EditMetaDialog_storage;
                }
                return null;
            }
        });
        TableViewerColumn col3 = createColumn(Messages.EditMetaDialog_recursive, 80);
        col3.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof WatchedFolder)
                    return ((WatchedFolder) element).getRecursive() ? Messages.EditMetaDialog_c_yes
                            : Messages.EditMetaDialog_c_no;
                return null;
            }
        });
        TableViewerColumn col4 = createColumn(Messages.EditMetaDialog_last_observation, 120);
        col4.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof WatchedFolder)
                    return df.format(new Date(((WatchedFolder) element).getLastObservation()));
                return null;
            }
        });
        TableViewerColumn col5 = createColumn(Messages.EditMetaDialog_file_filter, 400);
        col5.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof WatchedFolder) {
                    WatchedFolder wf = (WatchedFolder) element;
                    if (!wf.getTransfer())
                        return UiUtilities.getFilters(wf);
                    StringBuilder sb = new StringBuilder();
                    int skipPolicy = wf.getSkipPolicy();
                    if (skipPolicy < 0 && skipPolicy >= ImportFileSelectionPage.SKIPPOLICIES.length)
                        skipPolicy = 0;
                    sb.append(ImportFileSelectionPage.SKIPPOLICIES[skipPolicy]);
                    sb.append(" | ").append(wf.getTargetDir()); //$NON-NLS-1$
                    return sb.toString();
                }
                return null;
            }
        });
        watchedFolderViewer.setContentProvider(ArrayContentProvider.getInstance());
        new SortColumnManager(watchedFolderViewer, new int[] { SWT.UP, SWT.UP, SWT.UP, SWT.NONE, SWT.UP, SWT.NONE },
                0);
        watchedFolderViewer.setComparator(new ZViewerComparator() {
            @Override
            public int compare(Viewer viewer, Object e1, Object e2) {
                Table table = watchedFolderViewer.getTable();
                TableColumn sortColumn = table.getSortColumn();
                if (sortColumn == table.getColumn(4)) {
                    int sortDirection = table.getSortDirection();
                    int a = (sortDirection & SWT.DOWN) != 0 ? 1 : -1;
                    long t1 = ((WatchedFolder) e1).getLastObservation();
                    long t2 = ((WatchedFolder) e2).getLastObservation();
                    return t1 == t2 ? 0 : t1 < t2 ? a : -a;
                }
                return super.compare(viewer, e1, e2);
            }
        });
        watchedFolderViewer.getControl().addKeyListener(keyListener);
        watchedFolderViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                updateFolderButtons();
                if (cntrlDwn) {
                    if (editFolderButton.isEnabled())
                        editWatchedFolder();
                    cntrlDwn = false;
                }
            }
        });
        watchedFolderViewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                if (!cntrlDwn && editFolderButton.isEnabled())
                    editWatchedFolder();
            }
        });
        Composite buttonComp = new Composite(composite, SWT.NONE);
        buttonComp.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, true));
        buttonComp.setLayout(new GridLayout());
        addFolderButton = new Button(buttonComp, SWT.PUSH);
        addFolderButton.setText(Messages.EditMetaDialog_add);
        addFolderButton.setToolTipText(Messages.EditMetaDialog_add_watched_folder);
        addFolderButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                WatchedFolder folder = new WatchedFolderImpl(null, null, 0L, true, null, false, null, false, 0,
                        null, 2, null, null, Constants.FILESOURCE_DIGITAL_CAMERA, false);
                WatchedFolderWizard wizard = new WatchedFolderWizard(true);
                WizardDialog wizardDialog = new WizardDialog(getShell(), wizard);
                wizard.init(null, new StructuredSelection(folder));
                if (wizardDialog.open() == WizardDialog.OK) {
                    try {
                        folder.setStringId(Utilities.computeWatchedFolderId(new File(new URI(folder.getUri())),
                                folder.getVolume()));
                        watchedFolders.add(folder);
                        watchedFolderViewer.setInput(watchedFolders);
                    } catch (URISyntaxException e1) {
                        // should never happend
                    }
                }
            }
        });
        editFolderButton = new Button(buttonComp, SWT.PUSH);
        editFolderButton.setText(Messages.EditMetaDialog_edit_watched);
        editFolderButton.setToolTipText(Messages.EditMetaDialog_edit_watched_tooltip);
        editFolderButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                editWatchedFolder();
            }
        });

        removeFolderButton = new Button(buttonComp, SWT.PUSH);
        removeFolderButton.setText(Messages.EditMetaDialog_remove);
        removeFolderButton.setToolTipText(Messages.EditMetaDialog_remove_watched_folder);
        removeFolderButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                WatchedFolderImpl firstElement = (WatchedFolderImpl) watchedFolderViewer.getStructuredSelection()
                        .getFirstElement();
                watchedFolders.remove(firstElement);
                watchedFolderViewer.remove(firstElement);
            }
        });
        categoryRemoveButton.setEnabled(!readonly);
        Composite optionComp = new Composite(buttonComp, SWT.NONE);
        optionComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        optionComp.setLayout(new GridLayout(2, false));
        new Label(optionComp, SWT.NONE).setText(Messages.EditMetaDialog_latency);
        latencyField = new NumericControl(optionComp, SWT.NONE);
        latencyField.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        latencyField.setMaximum(60);
        latencyField.setMinimum(1);
        latencyField.setIncrement(1);
        pauseButton = WidgetFactory.createCheckButton(optionComp, Messages.EditMetaDialog_paused,
                new GridData(SWT.BEGINNING, SWT.END, true, false, 2, 1));

        // Footer
        bgimportField = WidgetFactory.createCheckButton(composite, Messages.EditMetaDialog_show_bg_imports,
                new GridData(SWT.BEGINNING, SWT.END, true, false, 2, 1));

        autoWatchButton = WidgetFactory.createCheckButton(composite,
                Messages.EditMetaDialog_automatically_add_imported_folders,
                new GridData(SWT.BEGINNING, SWT.END, true, false, 2, 1));
    }

    private void updateFolderButtons() {
        boolean enabled = !watchedFolderViewer.getSelection().isEmpty() && !readonly;
        removeFolderButton.setEnabled(enabled);
        editFolderButton.setEnabled(enabled);
        addFolderButton.setEnabled(!readonly);
    }

    private static void setViewerLayout(ContentViewer viewer, int height, int columns) {
        GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true, columns, 1);
        layoutData.heightHint = height;
        viewer.getControl().setLayoutData(layoutData);
    }

    private TableViewerColumn createColumn(String lab, int w) {
        TableViewerColumn col = new TableViewerColumn(watchedFolderViewer, SWT.NONE);
        col.getColumn().setText(lab);
        col.getColumn().setWidth(w);
        return col;
    }

    private static void createSeparator(Composite composite) {
        new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL)
                .setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
    }

    private static Composite createTabPage(CTabFolder folder, String lab, String descr, int columns, int space) {
        Composite pageComp = createTabPage(folder, lab, descr, columns);
        Label header = new Label(pageComp, SWT.NONE);
        header.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false, columns - space, 1));
        header.setText(descr);
        return pageComp;
    }

    protected static Composite createTabPage(CTabFolder folder, String lab, String descr, int columns) {
        Composite pageComp = UiUtilities.createTabPage(folder, lab, descr);
        pageComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        GridLayout layout = new GridLayout(columns, false);
        layout.verticalSpacing = 10;
        pageComp.setLayout(layout);
        return pageComp;
    }

    @SuppressWarnings("unused")
    private static Label createTextField(final Composite parent, String lab, int w, Listener listener,
            String linkLabel) {
        Label label = new Label(parent, SWT.NONE);
        GridData data = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
        data.horizontalIndent = 10;
        label.setLayoutData(data);
        label.setText(lab);
        Label textField = new Label(parent, SWT.RIGHT);
        textField.setLayoutData(new GridData(w, SWT.DEFAULT));
        if (listener != null) {
            CLink link = new CLink(parent, SWT.NONE);
            data = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
            data.horizontalIndent = 50;
            link.setLayoutData(data);
            link.setText(linkLabel != null ? linkLabel : Messages.EditMetaDialog_details);
            link.addListener(listener);
        } else
            new Label(parent, SWT.NONE);
        return textField;
    }

    private static Label createDetailsField(final Composite parent, int w) {
        Label label = new Label(parent, SWT.NONE);
        GridData data = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
        data.horizontalIndent = 10;
        data.widthHint = 250;
        label.setLayoutData(data);
        Label textField = new Label(parent, SWT.RIGHT);
        textField.setLayoutData(new GridData(w, SWT.DEFAULT));
        textField.setData(UiConstants.LABEL, label);
        return textField;
    }

    private void createThumbnailsGroup(final CTabFolder folder) {
        final Composite thComp = createTabPage(folder, Messages.EditMetaDialog_thumbnails,
                Messages.EditMetaDialog_thumbnail_tooltip, 1, 0);

        final CGroup thButtonComp = UiUtilities.createGroup(thComp, 1,
                Messages.EditMetaDialog_thumbnail_resolution);
        thumbSizeGroup = new RadioButtonGroup(thButtonComp, Messages.EditMetaDialog_high_res_will_increase, 2,
                Messages.EditMetaDialog_low, Messages.EditMetaDialog_medium, Messages.EditMetaDialog_high,
                Messages.EditMetaDialog_very_high);

        // Preview
        final Composite composite = new Composite(thComp, SWT.NONE);
        composite.setLayout(new GridLayout(2, false));
        new Label(composite, SWT.NONE).setText(Messages.EditMetaDialog_from_preview);
        fromPreviewButton = new Button(composite, SWT.CHECK);

        // Sharpening
        final CGroup shButtonComp = UiUtilities.createGroup(thComp, 8,
                Messages.EditMetaDialog_sharpen_during_import);
        sharpenButtonGroup = new RadioButtonGroup(shButtonComp, null, SWT.HORIZONTAL,
                Messages.EditMetaDialog_dont_sharpen, Messages.EditMetaDialog_light_sharpen,
                Messages.EditMetaDialog_medium_sharpen, Messages.EditMetaDialog_heavy_sharpen);
        final CGroup comprGroup = CGroup.create(thComp, 1, Messages.EditMetaDialog_compression);
        compressionGroup = new CompressionGroup(comprGroup, true);
    }

    @SuppressWarnings("unused")
    private void createKeywordsGroup(final CTabFolder folder) {
        final Composite kwComp = createTabPage(folder, Messages.EditMetaDialog_keywords,
                Messages.EditMetaDialog_keyword_tooltip, 2, 0);
        CGroup kwGroup = new CGroup(kwComp, SWT.NONE);
        kwGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        kwGroup.setLayout(new GridLayout(2, false));
        kwGroup.setText(Messages.EditMetaDialog_cat_keywords);
        Label catKwLabel = new Label(kwGroup, SWT.NONE);
        catKwLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        catKwLabel.setFont(JFaceResources.getFontRegistry().get(UiConstants.SELECTIONFONT));
        flatKeywordGroup = new FlatGroup(kwGroup, SWT.NONE, settings, "hierarchicalKeywords"); //$NON-NLS-1$
        flatKeywordGroup.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false));
        flatKeywordGroup.addListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                keywordViewer.setInput(keywords);
                keywordViewer.expandAll();
                keywordExpandCollapseGroup.setVisible(!flatKeywordGroup.isFlat());
            }
        });
        Composite viewerGroup = new Composite(kwGroup, SWT.NONE);
        viewerGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        GridLayout layout = new GridLayout(2, false);
        layout.marginHeight = layout.marginWidth = 0;
        viewerGroup.setLayout(layout);
        final FilterField filterField = new FilterField(viewerGroup);
        filterField.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                keywordViewer.setInput(keywords);
                keywordViewer.expandAll();
            }
        });
        final CheckboxButton excludeButton = WidgetFactory.createCheckButton(viewerGroup,
                Messages.KeywordGroup_exclude_geographic, new GridData(SWT.END, SWT.CENTER, true, false));
        excludeButton.addListener(new Listener() {

            @Override
            public void handleEvent(Event event) {
                excludeGeographic = excludeButton.getSelection();
                Object[] expandedElements = keywordViewer.getExpandedElements();
                keywordViewer.setInput(keywords);
                keywordViewer.setExpandedElements(expandedElements);
            }
        });
        keywordExpandCollapseGroup = new ExpandCollapseGroup(viewerGroup, SWT.NONE,
                new GridData(SWT.END, SWT.BEGINNING, true, false, 2, 1));
        keywordViewer = new TreeViewer(viewerGroup, SWT.V_SCROLL | SWT.BORDER);
        keywordExpandCollapseGroup.setViewer(keywordViewer);
        keywordExpandCollapseGroup.setVisible(!flatKeywordGroup.isFlat());
        setViewerLayout(keywordViewer, 220, 2);
        keywordViewer.setContentProvider(new KeywordContentProvider());
        keywordViewer.setLabelProvider(new KeywordLabelProvider(getVocabManager(), null));
        ZColumnViewerToolTipSupport.enableFor(keywordViewer);
        keywordViewer.setComparator(ZViewerComparator.INSTANCE);
        UiUtilities.installDoubleClickExpansion(keywordViewer);
        keywordViewer.setFilters(new ViewerFilter[] { new ViewerFilter() {
            @Override
            public boolean select(Viewer aViewer, Object parentElement, Object element) {
                WildCardFilter filter = filterField.getFilter();
                return filter == null || element instanceof Character || filter.accept((String) element);
            }
        } });
        keywordViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                updateButtons();
            }
        });
        configureKeywordLink = new CLink(viewerGroup, SWT.NONE);
        configureKeywordLink.setText(Messages.EditMetaDialog_configure_keyword_filter);
        configureKeywordLink.addListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                PreferencesUtil.createPreferenceDialogOn(getShell(), KeyPreferencePage.ID, new String[0], null)
                        .open();
            }
        });
        final Composite buttonGroup = new Composite(kwGroup, SWT.NONE);
        buttonGroup.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        buttonGroup.setLayout(new GridLayout());

        keywordAddButton = createPushButton(buttonGroup, Messages.EditMetaDialog_add,
                Messages.EditMetaDialog_add_keyword_tooltip);
        keywordAddButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                ZInputDialog dialog = createKeywordDialog(Messages.EditMetaDialog_add_keyword,
                        Messages.EditMetaDialog_enter_a_new_keyword, ""); //$NON-NLS-1$
                if (dialog.open() == Window.OK)
                    addKeywordToViewer(keywordViewer, dialog.getValue(), true);
            }
        });
        keywordModifyButton = createPushButton(buttonGroup, Messages.EditMetaDialog_edit,
                Messages.EditMetaDialog_modify_keyword_tooltip);
        keywordModifyButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                editKeyword();
            }
        });
        keywordViewer.getControl().addKeyListener(keyListener);
        keywordViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(final SelectionChangedEvent event) {
                updateButtons();
                if (cntrlDwn) {
                    if (keywordModifyButton.isEnabled())
                        editKeyword();
                    cntrlDwn = false;
                }
            }
        });
        keywordReplaceButton = createPushButton(buttonGroup, Messages.EditMetaDialog_replace,
                Messages.EditMetaDialog_replace_tooltip);
        keywordReplaceButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                String kw = (String) ((IStructuredSelection) keywordViewer.getSelection()).getFirstElement();
                if (kw != null) {
                    KeywordDialog dialog = new KeywordDialog(getShell(),
                            NLS.bind(Messages.EditMetaDialog_replace_keyword, kw), null, keywords, null);
                    if (dialog.open() == Window.OK) {
                        BagChange<String> result = dialog.getResult();
                        boolean found = false;
                        Set<String> added = result.getAdded();
                        if (added != null)
                            for (String s : added)
                                if (kw.equals(s)) {
                                    found = true;
                                    break;
                                }
                        if (!found)
                            removeKeywordFromViewer(keywordViewer, kw);
                        Set<String> addedKeywords = result.getAdded();
                        String[] replacement = addedKeywords.toArray(new String[addedKeywords.size()]);
                        int i = 0;
                        for (String k : replacement)
                            addKeywordToViewer(keywordViewer, k, i++ == 0);
                        todo.add(new ReplaceKeywordOperation(kw, replacement));
                    }
                }
            }
        });
        keywordDeleteButton = createPushButton(buttonGroup, Messages.EditMetaDialog_delete_keyword,
                Messages.EditMetaDialog_delete_keyword_tooltip);
        keywordDeleteButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                BusyIndicator.showWhile(keywordDeleteButton.getDisplay(), () -> {
                    IStructuredSelection selection = (IStructuredSelection) keywordViewer.getSelection();
                    String kw = (String) selection.getFirstElement();
                    if (kw != null) {
                        List<AssetImpl> set = dbManager.obtainObjects(AssetImpl.class,
                                QueryField.IPTC_KEYWORDS.getKey(), kw, QueryField.EQUALS);
                        removeKeywordFromViewer(keywordViewer, kw);
                        if (!set.isEmpty()) {
                            KeywordDeleteDialog dialog = new KeywordDeleteDialog(getShell(), kw, set);
                            if (dialog.open() != Window.OK) {
                                keywords.add(kw);
                                return;
                            }
                            if (dialog.getPolicy() == KeywordDeleteDialog.REMOVE)
                                todo.add(new ManageKeywordsOperation(
                                        new BagChange<String>(null, null, Collections.singleton(kw), null), set));
                        }
                    }
                });
            }
        });
        keywordShowButton = createPushButton(buttonGroup, Messages.EditMetaDialog_show_images,
                Messages.EditMetaDialog_show_keyword_tooltip);
        keywordShowButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = (IStructuredSelection) keywordViewer.getSelection();
                String kw = (String) selection.getFirstElement();
                if (kw != null) {
                    SmartCollectionImpl sm = new SmartCollectionImpl(kw, false, false, true, false, null, 0, null,
                            0, null, Constants.INHERIT_LABEL, null, 0, null);
                    sm.addCriterion(new CriterionImpl(QueryField.IPTC_KEYWORDS.getKey(), null, kw,
                            QueryField.EQUALS, false));
                    sm.addSortCriterion(new SortCriterionImpl(QueryField.IPTC_DATECREATED.getKey(), null, true));
                    Ui.getUi().getNavigationHistory(workbenchPage.getWorkbenchWindow())
                            .postSelection(new StructuredSelection(sm));
                    close();
                }
            }
        });
        new Label(buttonGroup, SWT.SEPARATOR | SWT.HORIZONTAL);

        keywordCollectButton = createPushButton(buttonGroup, Messages.EditMetaDialog_collect,
                Messages.EditMetaDialog_collect_keyword_tooltip);
        keywordCollectButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                KeywordCollectDialog dialog = new KeywordCollectDialog(getShell(), keywords);
                if (dialog.open() == Window.OK) {
                    int i = 0;
                    for (String kw : dialog.getToAdd())
                        addKeywordToViewer(keywordViewer, kw, i++ == 0);
                    for (String kw : dialog.getToRemove())
                        removeKeywordFromViewer(keywordViewer, kw);
                }
            }
        });

        keywordLoadButton = createPushButton(buttonGroup, Messages.EditMetaDialog_load,
                NLS.bind(Messages.EditMetaDialog_load_keyword_tooltip, Constants.KEYWORDFILEEXTENSION));
        keywordLoadButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent ev) {
                FileDialog dialog = new FileDialog(getShell(), SWT.OPEN);
                dialog.setFilterExtensions(KEYWORDEXTENSIONS);
                dialog.setFilterNames(
                        new String[] {
                                Constants.APPNAME + Messages.EditMetaDialog_zoom_keyword_file
                                        + Constants.KEYWORDFILEEXTENSION + ')',
                                Messages.EditMetaDialog_all_files });
                String filename = dialog.open();
                if (filename != null) {
                    try (InputStream in = new BufferedInputStream(new FileInputStream(filename))) {
                        List<String> list = Utilities.loadKeywords(in);
                        keywords.clear();
                        keywords.addAll(list);
                    } catch (IOException e) {
                        // ignore
                    }
                    keywordViewer.setInput(keywords);
                    keywordViewer.expandAll();
                }
            }
        });
        keywordSaveButton = createPushButton(buttonGroup, Messages.EditMetaDialog_save,
                NLS.bind(Messages.EditMetaDialog_save_keyword_tooltip, Constants.KEYWORDFILEEXTENSION));
        keywordSaveButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
                dialog.setFilterExtensions(KEYWORDEXTENSIONS);
                dialog.setFilterNames(
                        new String[] {
                                Constants.APPNAME + Messages.EditMetaDialog_zoom_keyword_file
                                        + Constants.KEYWORDFILEEXTENSION + ")", //$NON-NLS-1$
                                Messages.EditMetaDialog_all_files });
                dialog.setFileName("*" + Constants.KEYWORDFILEEXTENSION); //$NON-NLS-1$
                dialog.setOverwrite(true);
                String filename = dialog.open();
                if (filename != null)
                    Utilities.saveKeywords(keywords, new File(filename));
            }
        });
        addToKeywordsButton = WidgetFactory.createCheckButton(kwGroup, Messages.EditMetaDialog_add_to_keywords,
                new GridData(SWT.BEGINNING, SWT.END, true, false, 2, 1));
        CGroup vocabGroup = new CGroup(kwComp, SWT.NONE);
        vocabGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        vocabGroup.setLayout(new GridLayout(2, false));
        vocabGroup.setText(Messages.EditMetaDialog_controlled_vocabs);
        Composite vocabViewerGroup = new Composite(vocabGroup, SWT.NONE);
        GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
        layoutData.verticalIndent = 20;
        vocabViewerGroup.setLayoutData(layoutData);
        vocabViewerGroup.setLayout(new GridLayout(2, false));

        vocabViewer = new TableViewer(vocabViewerGroup, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
        setViewerLayout(vocabViewer, 150, 1);
        TableViewerColumn col1 = new TableViewerColumn(vocabViewer, SWT.NONE);
        col1.getColumn().setWidth(300);
        col1.setLabelProvider(new ZColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                return element.toString();
            }

            @Override
            public Image getImage(Object element) {
                if (element instanceof String && !new File((String) element).exists())
                    return Icons.error.getImage();
                return null;
            }

            @Override
            public String getToolTipText(Object element) {
                if (element instanceof String && UiActivator.getDefault().getShowHover()) {
                    File file = new File((String) element);
                    if (!file.exists())
                        return Messages.EditMetaDialog_file_does_not_exist;
                }
                return super.getToolTipText(element);
            }

            @Override
            public Image getToolTipImage(Object element) {
                return getImage(element);
            }
        });
        vocabViewer.setContentProvider(ArrayContentProvider.getInstance());
        vocabViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                updateButtons();
            }
        });
        vocabViewer.addDoubleClickListener(new IDoubleClickListener() {
            @Override
            public void doubleClick(DoubleClickEvent event) {
                viewVocab();
            }
        });
        ColumnViewerToolTipSupport.enableFor(vocabViewer);
        Composite vocabButtonGroup = new Composite(vocabViewerGroup, SWT.NONE);
        vocabButtonGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        vocabButtonGroup.setLayout(new GridLayout());
        vocabAddButton = createPushButton(vocabButtonGroup, Messages.EditMetaDialog_add,
                Messages.EditMetaDialog_add_vocab);
        vocabAddButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                FileDialog dialog = new FileDialog(getShell(), SWT.OPEN);
                dialog.setText(Messages.EditMetaDialog_select_vocab);
                dialog.setFilterExtensions(KEYWORDEXTENSIONS);
                dialog.setFilterNames(
                        new String[] {
                                Constants.APPNAME + Messages.EditMetaDialog_zoom_keyword_file
                                        + Constants.KEYWORDFILEEXTENSION + ')',
                                Messages.EditMetaDialog_all_files });
                String file = dialog.open();
                if (file != null) {
                    boolean found = false;
                    for (String s : vocabularies)
                        if (s.equals(file)) {
                            found = true;
                            break;
                        }
                    if (!found) {
                        vocabularies.add(file);
                        vocabViewer.add(file);
                    }
                    vocabViewer.setSelection(new StructuredSelection(file), true);
                    if (vocabManager != null)
                        vocabManager.reset(vocabularies);
                    updateKeywordViewer();
                }
            }
        });
        vocabRemoveButton = createPushButton(vocabButtonGroup, Messages.EditMetaDialog_remove,
                Messages.EditMetaDialog_remove_vocab);
        vocabRemoveButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                @SuppressWarnings("unchecked")
                Iterator<Object> it = vocabViewer.getStructuredSelection().iterator();
                while (it.hasNext()) {
                    Object file = it.next();
                    vocabularies.remove(file);
                    vocabViewer.remove(file);
                }
                if (vocabManager != null)
                    vocabManager.reset(vocabularies);
                updateKeywordViewer();
            }
        });
        vocabViewButton = createPushButton(vocabButtonGroup, Messages.EditMetaDialog_view_vocab,
                Messages.EditMetaDialog_view_vocab_tooltip);
        vocabViewButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                viewVocab();
            }
        });
        new Label(vocabButtonGroup, SWT.SEPARATOR | SWT.HORIZONTAL);
        vocabEnforceButton = createPushButton(vocabButtonGroup, Messages.EditMetaDialog_enforce,
                Messages.EditMetaDialog_enforce_tooltip);
        vocabEnforceButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                List<String[]> changes = new ArrayList<>();
                VocabManager vManager = getVocabManager();
                for (String kw : keywords) {
                    String mapped = vManager.getVocab(kw);
                    if (!kw.equals(mapped))
                        changes.add(new String[] { kw, mapped });
                }
                VocabEnforceDialog dialog = new VocabEnforceDialog(getShell(), changes);
                if (dialog.open() == VocabEnforceDialog.OK) {
                    BusyIndicator.showWhile(getShell().getDisplay(), () -> {
                        int policy = dialog.getPolicy();
                        for (String[] change : dialog.getChanges()) {
                            String kw = change[0];
                            keywords.remove(kw);
                            if (change[1] != null) {
                                keywords.add(change[1]);
                                if (policy == KeywordDeleteDialog.REMOVE)
                                    todo.add(new ReplaceKeywordOperation(kw, new String[] { change[1] }));
                            } else if (policy == KeywordDeleteDialog.REMOVE)
                                todo.add(new ManageKeywordsOperation(
                                        new BagChange<String>(null, null, Collections.singleton(kw), change),
                                        null));
                            updateKeywordViewer();
                        }

                    });
                }
            }
        });
    }

    protected VocabManager getVocabManager() {
        if (vocabManager == null)
            vocabManager = new VocabManager(vocabularies, this);
        return vocabManager;
    }

    protected void updateKeywordViewer() {
        Object[] expandedElements = keywordViewer.getExpandedElements();
        keywordViewer.setInput(keywords);
        keywordViewer.setExpandedElements(expandedElements);
    }

    protected void editKeyword() {
        if (!readonly) {
            IStructuredSelection selection = (IStructuredSelection) keywordViewer.getSelection();
            String kw = (String) selection.getFirstElement();
            if (kw != null) {
                ZInputDialog dialog = createKeywordDialog(Messages.EditMetaDialog_edit_keyword,
                        Messages.EditMetaDialog_modify_existing_keyword, kw);
                if (dialog.open() == Window.OK) {
                    removeKeywordFromViewer(keywordViewer, kw);
                    String newKw = dialog.getValue();
                    addKeywordToViewer(keywordViewer, newKw, true);
                    todo.add(new ModifyKeywordOperation(kw, newKw));
                }
            }
        }
    }

    private String[] filterKeywords(Collection<String> keywords) {
        if (!excludeGeographic)
            return keywords.toArray(new String[keywords.size()]);
        Set<String> geographic = getGeographicKeywords();
        List<String> filtered = new ArrayList<>(keywords.size());
        for (String kw : keywords)
            if (!geographic.contains(kw))
                filtered.add(kw);
        return filtered.toArray(new String[filtered.size()]);
    }

    public Set<String> getGeographicKeywords() {
        if (geographic == null) {
            geographic = new HashSet<>();
            List<LocationImpl> locations = Core.getCore().getDbManager().obtainObjects(LocationImpl.class);
            for (LocationImpl location : locations)
                Utilities.extractKeywords(location, geographic);
        }
        return geographic;
    }

    private void addKeywordToViewer(TreeViewer viewer, String kw, boolean select) {
        if (keywords.add(kw)) {
            if (flatKeywordGroup.isFlat())
                viewer.add(null, kw);
            else {
                Object[] expandedElements = viewer.getExpandedElements();
                viewer.setInput(keywords);
                viewer.setExpandedElements(expandedElements);
            }
        }
        if (select)
            keywordViewer.setSelection(new StructuredSelection(kw));
    }

    private void removeKeywordFromViewer(TreeViewer viewer, String kw) {
        if (keywords.remove(kw)) {
            if (flatKeywordGroup.isFlat())
                viewer.remove(kw);
            else {
                Object[] expandedElements = viewer.getExpandedElements();
                viewer.setInput(keywords);
                viewer.setExpandedElements(expandedElements);
            }
        }
    }

    private static Button createPushButton(final Composite parent, final String label, String tooltip) {
        Button button = new Button(parent, SWT.PUSH);
        button.setLayoutData(new GridData(SWT.BEGINNING, SWT.TOP, false, false));
        button.setText(label);
        button.setToolTipText(tooltip);
        return button;
    }

    @SuppressWarnings("unused")
    private void createCategoriesGroup(final Composite parent, final CTabFolder folder) {
        final Composite catComposite = createTabPage(folder, Messages.EditMetaDialog_categories,
                Messages.EditMetaDialog_category_tooltip, 2, 0);
        ExpandCollapseGroup expandCollapseGroup = new ExpandCollapseGroup(catComposite, SWT.NONE);
        new Label(catComposite, SWT.NONE);
        catTreeViewer = new TreeViewer(catComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.SINGLE);
        expandCollapseGroup.setViewer(catTreeViewer);
        setViewerLayout(catTreeViewer, 200, 1);
        catTreeViewer.setContentProvider(new CategoryContentProvider());
        catTreeViewer.setComparator(ZViewerComparator.INSTANCE);
        catTreeViewer.setLabelProvider(new CategoryLabelProvider());
        UiUtilities.installDoubleClickExpansion(catTreeViewer);
        final Composite buttonGroup = new Composite(catComposite, SWT.NONE);
        buttonGroup.setLayout(new GridLayout());
        buttonGroup.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
        categoryAddButton = createPushButton(buttonGroup, Messages.EditMetaDialog_add,
                Messages.EditMetaDialog_add_category);
        categoryAddButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                EditCategoryDialog inputDialog = new EditCategoryDialog(parent.getShell(), null, categories, null,
                        null, false);
                if (inputDialog.open() == Window.OK) {
                    String label = inputDialog.getLabel();
                    Category category = new CategoryImpl(label);
                    category.setSynonyms(inputDialog.getSynonyms());
                    categories.put(label, category);
                    catTreeViewer.setInput(categories);
                }
            }
        });
        categoryRefineButton = createPushButton(buttonGroup, Messages.EditMetaDialog_refine,
                Messages.EditMetaDialog_refine_category);
        categoryRefineButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                Category firstElement = (Category) ((IStructuredSelection) catTreeViewer.getSelection())
                        .getFirstElement();
                EditCategoryDialog inputDialog = new EditCategoryDialog(parent.getShell(), null, categories, null,
                        firstElement, false);
                if (inputDialog.open() == Window.OK) {
                    Category subCategory = new CategoryImpl(inputDialog.getLabel());
                    subCategory.setSynonyms(inputDialog.getSynonyms());
                    firstElement.putSubCategory(subCategory);
                    catTreeViewer.add(firstElement, subCategory);
                    catTreeViewer.expandToLevel(firstElement, 2);
                }
            }
        });

        categoryEditButton = createPushButton(buttonGroup, Messages.EditMetaDialog_edit,
                Messages.EditMetaDialog_edit_selected_category);
        categoryEditButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                editCategory(parent);
            }
        });
        catTreeViewer.getControl().addKeyListener(keyListener);
        catTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(final SelectionChangedEvent event) {
                updateButtons();
                if (cntrlDwn) {
                    if (categoryEditButton.isEnabled())
                        editCategory(parent);
                    cntrlDwn = false;
                }
            }
        });
        categoryRemoveButton = createPushButton(buttonGroup, Messages.EditMetaDialog_remove,
                Messages.EditMetaDialog_remove_category);
        categoryRemoveButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                CategoryImpl firstElement = (CategoryImpl) ((IStructuredSelection) catTreeViewer.getSelection())
                        .getFirstElement();
                Category subCategory_parent = firstElement.getCategory_subCategory_parent();
                if (subCategory_parent != null)
                    subCategory_parent.removeSubCategory(firstElement.getLabel());
                else
                    categories.remove(firstElement.getLabel());
                catTreeViewer.remove(firstElement);
            }
        });

        new Label(buttonGroup, SWT.HORIZONTAL | SWT.SEPARATOR).setText(Messages.EditMetaDialog_label);
        final String[] catExtensions = new String[] { "*" //$NON-NLS-1$
                + Constants.CATEGORYFILEEXTENSION + ";*" //$NON-NLS-1$
                + Constants.CATEGORYFILEEXTENSION.toUpperCase() };
        loadCatButton = createPushButton(buttonGroup, Messages.EditMetaDialog_load,
                NLS.bind(Messages.EditMetaDialog_load_category, Constants.CATEGORYFILEEXTENSION));
        loadCatButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                FileDialog dialog = new FileDialog(getShell(), SWT.OPEN);
                dialog.setFilterExtensions(catExtensions);
                dialog.setFilterNames(new String[] { Constants.APPNAME + Messages.EditMetaDialog_zoom_cat_file
                        + Constants.CATEGORYFILEEXTENSION + ")" }); //$NON-NLS-1$
                String filename = dialog.open();
                if (filename != null)
                    try (InputStream in = new BufferedInputStream(new FileInputStream(filename))) {
                        Utilities.loadCategories(dbManager, categories, in, null);
                    } catch (IOException e1) {
                        // ignore
                    }
            }
        });

        saveCatButton = createPushButton(buttonGroup, Messages.EditMetaDialog_save,
                NLS.bind(Messages.EditMetaDialog_save_category, Constants.CATEGORYFILEEXTENSION));
        saveCatButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
                dialog.setFilterExtensions(catExtensions);
                dialog.setFilterNames(new String[] { Constants.APPNAME + Messages.EditMetaDialog_zoom_cat_file
                        + Constants.CATEGORYFILEEXTENSION + ")" }); //$NON-NLS-1$
                dialog.setFileName("*" + Constants.CATEGORYFILEEXTENSION); //$NON-NLS-1$
                dialog.setOverwrite(true);
                String filename = dialog.open();
                if (filename != null)
                    Utilities.saveCategories(meta, new File(filename));
            }
        });
    }

    private StructGroup createStructComponent(final CTabFolder folder, final int type, String label, String descr,
            String item) {
        return new StructGroup(createTabPage(folder, label, descr, 2, 1), type, item, !readonly);
    }

    protected boolean isElementInUse(IdentifiableObject element) {
        String id = element.getStringId();
        if (element instanceof LocationImpl) {
            if (!dbManager.obtainObjects(LocationCreatedImpl.class, "location", //$NON-NLS-1$
                    id, QueryField.EQUALS).isEmpty())
                return true;
            return (!dbManager.obtainObjects(LocationShownImpl.class, "location", id, QueryField.EQUALS) //$NON-NLS-1$
                    .isEmpty());
        }
        if (element instanceof ContactImpl)
            return (!dbManager.obtainObjects(CreatorsContactImpl.class, "contact", id, QueryField.EQUALS) //$NON-NLS-1$
                    .isEmpty());
        if (element instanceof ArtworkOrObjectImpl)
            return (!dbManager
                    .obtainObjects(ArtworkOrObjectShownImpl.class, "artworkOrObject", id, QueryField.EQUALS) //$NON-NLS-1$
                    .isEmpty());
        return false;
    }

    protected void updateButtons() {
        boolean sel = !catTreeViewer.getSelection().isEmpty();
        categoryRefineButton.setEnabled(sel && !readonly);
        categoryEditButton.setEnabled(sel);
        categoryRemoveButton.setEnabled(sel && !readonly);
        IStructuredSelection kwSelection = (IStructuredSelection) keywordViewer.getSelection();
        boolean kwSel = kwSelection.getFirstElement() instanceof String;
        keywordAddButton.setEnabled(!readonly);
        keywordModifyButton.setEnabled(!readonly && kwSel);
        keywordDeleteButton.setEnabled(!readonly && kwSel);
        keywordReplaceButton.setEnabled(!readonly && kwSelection.size() == 1 && kwSel);
        keywordShowButton.setEnabled(kwSel);
        keywordLoadButton.setEnabled(!readonly);
        categoryAddButton.setEnabled(!readonly);
        categoryEditButton.setEnabled(!readonly);
        vocabAddButton.setEnabled(!readonly);
        boolean vocabSel = !vocabViewer.getSelection().isEmpty();
        vocabRemoveButton.setEnabled(vocabSel && !readonly);
        vocabViewButton.setEnabled(vocabSel);
        vocabEnforceButton.setEnabled(vocabularies != null && !vocabularies.isEmpty());
        loadCatButton.setEnabled(!readonly);
        boolean enabled = newDb
                ? visited[OVERVIEW] && visited[THUMBNAILS] && visited[CATEGORIES] && visited[KEYWORDS]
                        && visited[INDEXING]
                : true;
        getButton(IDialogConstants.OK_ID).setEnabled(enabled);
        getShell().setModified(enabled);
        int selectionIndex = tabFolder.getSelectionIndex();
        getButton(PREVIOUS).setEnabled(selectionIndex > WELCOME + 1);
        getButton(NEXT).setEnabled(selectionIndex < tabFolder.getTabList().length - 1);
    }

    private void initHeader() {
        meta = dbManager.getMeta(true);
        readOnlyButton.setSelection(meta.getReadonly());
        fileName.setText(dbManager.getFileName());
        versionLabel.setText(NLS.bind(Messages.EditMetaDialog_catversion, meta.getVersion()));
        creationDate.setText(Constants.DFDT.format(meta.getCreationDate()));
        lastImport.setText(Constants.DFDT.format(meta.getLastImport()));
        seqNoField.setText(String.valueOf(meta.getLastSequenceNo()));
        yearlySeqNoField.setText(String.valueOf(meta.getLastYearSequenceNo()));
        backupField.setText(meta.getBackupLocation());
        updateBackupTooltip();
        String themeId = previousMeta != null ? previousMeta.getThemeID() : meta.getThemeID();
        Theme theme = themeId != null ? CoreActivator.getDefault().getThemes().get(themeId) : null;
        if (theme == null)
            theme = CoreActivator.getDefault().getCurrentTheme();
        themeField.setSelection(new StructuredSelection(theme));
        String language = meta.getLocale();
        if (language != null && languageCombo != null)
            languageCombo.setSelection(new StructuredSelection(language));
    }

    private void initPages(int index) {
        if (index < 0)
            return;
        if (visited[index])
            return;
        visited[index] = true;
        meta = dbManager.getMeta(true);
        if (index == OVERVIEW) {
            ownerInformation.setText(meta.getOwner());
            description.setText(meta.getDescription());
            userFieldLabel1.setText(meta.getUserFieldLabel1());
            userFieldLabel2.setText(meta.getUserFieldLabel2());
            timelineViewer.setSelection(new StructuredSelection(meta.getTimeline()));
            String locationFolders = meta.getLocationFolders();
            locationViewer.setSelection(new StructuredSelection(
                    locationFolders == null ? Meta_type.locationFolders_no : locationFolders));
            updateCreateNowButtons();
        } else if (index == THUMBNAILS) {
            fromPreviewButton.setSelection(meta.getThumbnailFromPreview());
            String res = meta.getThumbnailResolution();
            if (res.equals(Meta_type.thumbnailResolution_low))
                thumbSizeGroup.setSelection(0);
            else if (res.equals(Meta_type.thumbnailResolution_medium))
                thumbSizeGroup.setSelection(1);
            else if (res.equals(Meta_type.thumbnailResolution_high))
                thumbSizeGroup.setSelection(2);
            else if (res.equals(Meta_type.thumbnailResolution_veryHigh))
                thumbSizeGroup.setSelection(3);
            switch (meta.getSharpen()) {
            case ImageConstants.SHARPEN_LIGHT:
                sharpenButtonGroup.setSelection(1);
                break;
            case ImageConstants.SHARPEN_MEDIUM:
                sharpenButtonGroup.setSelection(2);
                break;
            case ImageConstants.SHARPEN_HEAVY:
                sharpenButtonGroup.setSelection(3);
                break;
            default:
                sharpenButtonGroup.setSelection(0);
                break;
            }
            compressionGroup.fillValues(meta.getJpegQuality(), meta.getWebpCompression());
        } else if (index == CATEGORIES) {
            if (categories == null)
                categories = Utilities.cloneCategories(meta.getCategory());
            catTreeViewer.setInput(categories);
        } else if (index == KEYWORDS) {
            if (meta.getVocabularies() != null)
                vocabularies.addAll(meta.getVocabularies());
            vocabViewer.setInput(vocabularies);
            keywords = new HashSet<String>(meta.getKeywords());
            keywordViewer.setInput(keywords);
            keywordViewer.expandAll();
            addToKeywordsButton.setSelection(booleanValue(meta.getPersonsToKeywords()));
        } else if (index == INDEXING) {
            noIndexButton.setSelection(meta.getNoIndex());
            for (Algorithm algo : Core.getCore().getDbFactory().getLireService(true)
                    .getSupportedSimilarityAlgorithms())
                if (cbirAlgorithms.contains(algo.getName()))
                    simViewer.setChecked(algo, true);
            Set<String> indexedTextFields = CoreActivator.getDefault().getIndexedTextFields();
            List<QueryField> fields = new ArrayList<QueryField>(indexedTextFields.size());
            for (String field : indexedTextFields) {
                QueryField qf = QueryField.findQueryField(field);
                if (qf != null) {
                    fields.add(qf);
                    textIndexViewer.setChecked(qf, true);
                }
            }
            slideTitleButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_SLIDE_TITLE));
            slideDescrButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_SLIDE_DESCR));
            exhibitionTitleButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_EXH_TITLE));
            exhibitionDescrButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_EXH_DESCR));
            exhibitionCredButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_EXH_CREDITS));
            webgalleryTitleButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_WEBGAL_TITLE));
            webgalleryDescrButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_WEBGAL_DESCR));
            webgalleryAltButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_WEBGAL_ALT));
            personsButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_PERSON_SHOWN));
            fileNameButton.setSelection(indexedTextFields.contains(ILuceneService.INDEX_FILENAME));
            updateIndexingControls();
            validateAlgorithms();
        } else if (index == LOCATIONS)
            locationGroup.fillValues();
        else if (index == ARTWORK)
            artworkGroup.fillValues();
        else if (index == CONTACTS)
            contactsGroup.fillValues();
        else if (index == STATISTICS)
            tabFolder.getDisplay().timerExec(100, () -> {
                if (!tabFolder.isDisposed())
                    computeStatistics();
            });
        else if (index == WATCHEDFOLDERS) {
            bgimportField.setSelection(meta.getCumulateImports());
            watchedFolders.clear();
            if (meta.getWatchedFolder() != null) {
                CoreActivator activator = CoreActivator.getDefault();
                for (String id : meta.getWatchedFolder()) {
                    WatchedFolder folder = activator.getObservedFolder(id);
                    if (folder != null)
                        watchedFolders.add(folder);
                }
            }
            autoWatchButton.setSelection(meta.getAutoWatch());
            folderBackup = new ArrayList<WatchedFolder>(watchedFolders);
            // make new instances
            watchedFolders.clear();
            for (WatchedFolder f : folderBackup) {
                WatchedFolderImpl newWatchedFolder = new WatchedFolderImpl(f.getUri(), f.getVolume(),
                        f.getLastObservation(), f.getRecursive(), f.getFilters(), f.getTransfer(), f.getArtist(),
                        f.getSkipDuplicates(), f.getSkipPolicy(), f.getTargetDir(), f.getSubfolderPolicy(),
                        f.getSelectedTemplate(), f.getCue(), f.getFileSource(), f.getTethered());
                try {
                    newWatchedFolder.setStringId(
                            Utilities.computeWatchedFolderId(new File(new URI(f.getUri())), f.getVolume()));
                } catch (URISyntaxException e) {
                    // should never happen
                }
                newWatchedFolder.setMeta_parent(((MetaImpl) meta).getStringId());
                watchedFolders.add(newWatchedFolder);
            }
            watchedFolderViewer.setInput(watchedFolders);
            int folderWatchLatency = meta.getFolderWatchLatency();
            if (folderWatchLatency == 0)
                folderWatchLatency = 30;
            latencyField.setSelection(folderWatchLatency);
            pauseButton.setSelection(meta.getPauseFolderWatch());
            updateFolderButtons();
        }
    }

    private static boolean booleanValue(Boolean bool) {
        return bool == null ? false : bool.booleanValue();
    }

    private void updateBackupTooltip() {
        Date lastBackup = meta.getLastBackup();
        if (lastBackup != null) {
            Date nextBackup = new Date(
                    lastBackup.getTime() + CoreActivator.getDefault().getBackupInterval() * ONEDAY);
            String tooltip = NLS.bind(Messages.EditMetaDialog_backup_tooltip, Constants.DFDT.format(lastBackup),
                    Constants.DFDT.format(nextBackup));
            backupField.setToolTipText(tooltip);
            backupIntervalLink.setToolTipText(tooltip);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T getUpdatedValue(IIdentifiableObject obj, QueryField qf, T dflt) {
        Map<QueryField, Object> fieldMap = structOverlayMap.get(obj.getStringId());
        if (fieldMap != null && fieldMap.containsKey(qf))
            return (T) fieldMap.get(qf);
        return dflt;
    }

    @Override
    public boolean close() {
        Job.getJobManager().cancel(this);
        return super.close();
    }

    @Override
    protected void okPressed() {
        locationGroup.saveSettings();
        contactsGroup.saveSettings();
        artworkGroup.saveSettings();
        flatKeywordGroup.saveSettings();
        String ownerInf;
        String descr;
        String uField1;
        String uField2;
        String timelineOption;
        String locationOption;
        if (visited[OVERVIEW]) {
            ownerInf = ownerInformation.getText();
            descr = description.getText();
            uField1 = userFieldLabel1.getText();
            uField2 = userFieldLabel2.getText();
            timelineOption = (String) timelineViewer.getStructuredSelection().getFirstElement();
            if (timelineOption == null)
                timelineOption = Meta_type.timeline_no;
            locationOption = (String) locationViewer.getStructuredSelection().getFirstElement();
            if (locationOption == null)
                locationOption = Meta_type.locationFolders_no;
        } else {
            ownerInf = meta.getOwner();
            descr = meta.getDescription();
            uField1 = meta.getUserFieldLabel1();
            uField2 = meta.getUserFieldLabel2();
            timelineOption = meta.getTimeline();
            locationOption = meta.getLocationFolders();
        }
        String resolution;
        int sharpen;
        int jpegQuality;
        boolean fromPreview;
        boolean webpCompression;
        if (visited[THUMBNAILS]) {
            resolution = Meta_type.thumbnailResolution_medium;
            int selection = thumbSizeGroup.getSelection();
            if (selection == 0)
                resolution = Meta_type.thumbnailResolution_low;
            else if (selection == 2)
                resolution = Meta_type.thumbnailResolution_high;
            else if (selection == 3)
                resolution = Meta_type.thumbnailResolution_veryHigh;
            sharpen = ImageConstants.SHARPEN_NONE;
            selection = sharpenButtonGroup.getSelection();
            if (selection == 1)
                sharpen = ImageConstants.SHARPEN_LIGHT;
            else if (selection == 2)
                sharpen = ImageConstants.SHARPEN_MEDIUM;
            else if (selection == 3)
                sharpen = ImageConstants.SHARPEN_HEAVY;
            jpegQuality = compressionGroup.getJpegQuality();
            webpCompression = compressionGroup.getUseWebp();
            fromPreview = fromPreviewButton.getSelection();
        } else {
            resolution = meta.getThumbnailResolution();
            sharpen = meta.getSharpen();
            jpegQuality = meta.getJpegQuality();
            webpCompression = meta.getWebpCompression();
            fromPreview = meta.getThumbnailFromPreview();
        }
        Map<String, Category> cats;
        if (visited[CATEGORIES]) {
            cats = categories;
        } else {
            cats = meta.getCategory();
        }
        boolean addToKeywords;
        Set<String> kWords;
        if (visited[KEYWORDS]) {
            addToKeywords = addToKeywordsButton.getSelection();
            kWords = keywords;
        } else {
            addToKeywords = booleanValue(meta.getPersonsToKeywords());
            kWords = meta.getKeywords();
        }
        Set<String> indexedTextFields;
        boolean noIndex;
        if (visited[INDEXING]) {
            noIndex = noIndexButton.getSelection();
            updateSelectedAlgorithms();
            cbirAlgorithms.add(""); // indicate that it has been set //$NON-NLS-1$
            indexedTextFields = new HashSet<String>();
            for (Object el : textIndexViewer.getCheckedElements()) {
                String key = ((QueryField) el).getKey();
                if (key != null)
                    indexedTextFields.add(key);
            }
            if (slideTitleButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_SLIDE_TITLE);
            if (slideDescrButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_SLIDE_DESCR);
            if (exhibitionTitleButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_EXH_TITLE);
            if (exhibitionDescrButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_EXH_DESCR);
            if (exhibitionCredButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_EXH_CREDITS);
            if (webgalleryTitleButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_WEBGAL_TITLE);
            if (webgalleryDescrButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_WEBGAL_DESCR);
            if (webgalleryAltButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_WEBGAL_ALT);
            if (personsButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_PERSON_SHOWN);
            if (fileNameButton.getSelection())
                indexedTextFields.add(ILuceneService.INDEX_FILENAME);
            indexedTextFields.add(""); // indicate that it has //$NON-NLS-1$
            // been set
        } else {
            noIndex = meta.getNoIndex();
            cbirAlgorithms = meta.getCbirAlgorithms();
            indexedTextFields = meta.getIndexedTextFields();
        }
        List<WatchedFolder> wFolders;
        List<WatchedFolder> fBackup;
        int latency;
        boolean autoWatch;
        boolean pause;
        boolean bgimport;
        if (visited[WATCHEDFOLDERS]) {
            fBackup = folderBackup;
            wFolders = watchedFolders;
            latency = latencyField.getSelection();
            pause = pauseButton.getSelection();
            autoWatch = autoWatchButton.getSelection();
            bgimport = bgimportField.getSelection();
        } else {
            wFolders = null;
            fBackup = null;
            latency = meta.getFolderWatchLatency();
            pause = meta.getPauseFolderWatch();
            autoWatch = meta.getAutoWatch();
            bgimport = meta.getCumulateImports();
        }
        String language = languageCombo == null ? null
                : (String) languageCombo.getStructuredSelection().getFirstElement();
        Theme theme = (Theme) themeField.getStructuredSelection().getFirstElement();
        ModifyMetaOperation op = new ModifyMetaOperation(meta, newDb, structOverlayMap, newStructMap,
                backupField.getText(), ownerInf, theme == null ? null : theme.getId(), descr, uField1, uField2,
                bgimport, timelineOption, locationOption, kWords, cats, resolution, fromPreview, fBackup, wFolders,
                latency, pause, readonly, autoWatch, sharpen, webpCompression, jpegQuality, noIndex, language,
                cbirAlgorithms, indexedTextFields, addToKeywords, categoryChanges, vocabularies);
        OperationJob.executeOperation(op, workbenchPage.getActivePart());
        for (DbOperation operation : todo)
            OperationJob.executeOperation(operation, workbenchPage.getActivePart());
        super.okPressed();
    }

    protected void updateSelectedAlgorithms() {
        cbirAlgorithms.clear();
        for (Object el : simViewer.getCheckedElements())
            cbirAlgorithms.add(((Algorithm) el).getName());
    }

    private void editCategory(final Composite parent) {
        if (!readonly) {
            Category firstElement = (Category) ((IStructuredSelection) catTreeViewer.getSelection())
                    .getFirstElement();
            EditCategoryDialog inputDialog = new EditCategoryDialog(parent.getShell(), firstElement, categories,
                    null, firstElement.getCategory_subCategory_parent(), false);
            if (inputDialog.open() == Window.OK) {
                String label = inputDialog.getLabel();
                firstElement.setSynonyms(inputDialog.getSynonyms());
                if (inputDialog.getApply()) {
                    categoryChanges.add(firstElement.getLabel());
                    categoryChanges.add(label);
                }
                Category subCategory_parent = firstElement.getCategory_subCategory_parent();
                if (subCategory_parent != null) {
                    subCategory_parent.removeSubCategory(firstElement.getLabel());
                    firstElement.setLabel(label);
                    subCategory_parent.putSubCategory(firstElement);
                } else {
                    categories.remove(firstElement.getLabel());
                    firstElement.setLabel(label);
                    categories.put(firstElement.getLabel(), firstElement);
                }
                catTreeViewer.update(firstElement, null);
            }
        }
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        createButton(parent, PREVIOUS, Messages.EditMetaDialog_previous, false);
        createButton(parent, NEXT, Messages.EditMetaDialog_next, true);
        super.createButtonsForButtonBar(parent);
    }

    @Override
    protected void buttonPressed(int buttonId) {
        switch (buttonId) {
        case PREVIOUS:
            int selectionIndex = tabFolder.getSelectionIndex();
            if (selectionIndex > WELCOME + 1) {
                tabFolder.setSelection(selectionIndex - 1);
                tabChanged();
            }
            return;
        case NEXT:
            selectionIndex = tabFolder.getSelectionIndex();
            tabFolder.setSelection(selectionIndex + 1);
            tabChanged();
            return;
        }
        super.buttonPressed(buttonId);
    }

    private ZInputDialog createKeywordDialog(String title, String message, String dflt) {
        final String[] kws = keywords.toArray(new String[keywords.size()]);
        IInputValidator validator = new IInputValidator() {
            public String isValid(String newText) {
                if (newText.isEmpty())
                    return Messages.EditMetaDialog_please_enter_a_keyword;
                if (keywords.contains(newText))
                    return Messages.EditMetaDialog_keyword_already_exsists;
                return null;
            }
        };
        IInputAdvisor advisor = new IInputAdvisor() {
            public String getAdvice(String newText) {
                int l = newText.length();
                if (kws.length > 0 && l > 0) {
                    Utilities.sortForSimilarity(kws, newText);
                    StringBuilder sb = new StringBuilder();
                    int i = 0;
                    for (String kw : kws) {
                        int ld = Utilities.LD(newText, kw);
                        if (2 * ld > l || (3 * ld + 2 > l && l >= 5))
                            break;
                        if (++i > 3)
                            break;
                        if (sb.length() > 0)
                            sb.append(", "); //$NON-NLS-1$
                        sb.append(kw);
                    }
                    if (sb.length() > 0)
                        return NLS.bind(Messages.EditMetaDialog_similar_keywords, sb);
                }
                return null;
            }
        };
        return new ZInputDialog(getShell(), title, message, dflt, advisor, validator, false);
    }

    public void setInitialPage(int page) {
        this.initialPage = page;
    }

    public void editWatchedFolder() {
        WatchedFolderWizard wizard = new WatchedFolderWizard(true);
        WizardDialog wizardDialog = new WizardDialog(getShell(), wizard);
        wizard.init(null, watchedFolderViewer.getStructuredSelection());
        if (wizardDialog.open() == WizardDialog.OK)
            watchedFolderViewer.update(wizard.getResult(), null);
    }

    public Map<String, Category> getCategories() {
        return categories;
    }

    public void setCategories(Map<String, Category> categories) {
        this.categories = categories;
    }

    protected void tabChanged() {
        cntrlDwn = false;
        int tabIndex = tabFolder.getSelectionIndex();
        if (newDb) {
            if (initButtonGroup != null && initButtonGroup.getSelection() == 1 && initButtonGroup.isEnabled(1)) {
                copyMeta();
                categories = meta.getCategory();
                initHeader();
            }
            initPages(tabIndex);
            if (initButtonGroup != null)
                initButtonGroup.setEnabled(false);
            if (tabIndex == CATEGORIES) {
                Theme newTheme = (Theme) themeField.getStructuredSelection().getFirstElement();
                boolean themeChanged = newTheme != theme;
                theme = newTheme;
                if (meta.getCategory() == null || meta.getCategory().isEmpty()) {
                    InputStream catin = null;
                    try {
                        if (!themeChanged) {
                            Map<String, Category> oldCategories = previousMeta.getCategory();
                            if (oldCategories != null && !oldCategories.isEmpty()) {
                                ByteArrayOutputStream out = new ByteArrayOutputStream();
                                Utilities.saveCategories(previousMeta, out);
                                catin = new ByteArrayInputStream(out.toByteArray());
                            }
                        }
                        if (catin == null)
                            catin = Utilities.openPropertyFile(theme.getCategories());
                        if (catin != null) {
                            categories = new HashMap<>();
                            Utilities.loadCategories(dbManager, categories, catin, null);
                            catTreeViewer.setInput(categories);
                        }
                    } finally {
                        try {
                            if (catin != null)
                                catin.close();
                        } catch (IOException e1) {
                            // do nothing
                        }
                    }
                }
                if (meta.getKeywords() == null || meta.getKeywords().isEmpty()) {
                    InputStream kwin = null;
                    try {
                        if (!themeChanged) {
                            Set<String> oldKeywords = previousMeta.getKeywords();
                            if (oldKeywords != null && !oldKeywords.isEmpty()) {
                                ByteArrayOutputStream out = new ByteArrayOutputStream();
                                Utilities.saveKeywords(oldKeywords, out);
                                kwin = new ByteArrayInputStream(out.toByteArray());
                            }
                        }
                        if (kwin == null)
                            kwin = Utilities.openPropertyFile(theme.getKeywords());
                        if (kwin != null) {
                            List<String> loadedKeywords = Utilities.loadKeywords(kwin);
                            if (loadedKeywords != null)
                                keywords = new HashSet<String>(loadedKeywords);
                            keywordViewer.setInput(keywords);
                            keywordViewer.expandAll();
                        }
                    } finally {
                        try {
                            if (kwin != null)
                                kwin.close();
                        } catch (IOException e1) {
                            // do nothing
                        }
                    }
                }
            }
        } else
            initPages(tabIndex);
        updateButtons();
        updateTabItems();
    }

    private void copyMeta() {
        if (previousMeta != null) {
            File cfile = dbManager.getFile();
            List<Object> toBeStored = new ArrayList<Object>();
            Meta newMeta = dbManager.getMeta(true);
            toBeStored.add(newMeta);
            CoreActivator.getDefault().copyMeta(previousMeta, cfile, toBeStored, newMeta);
            dbManager.safeTransaction(null, toBeStored);
        }
    }

    protected void viewVocab() {
        List<String> selected = new ArrayList<>();
        @SuppressWarnings("unchecked")
        Iterator<Object> it = vocabViewer.getStructuredSelection().iterator();
        while (it.hasNext())
            selected.add((String) it.next());
        if (!selected.isEmpty())
            new ViewVocabDialog(getShell(), new VocabManager(selected, EditMetaDialog.this).getVocabTree(),
                    selected.size() != 1 ? null : new File(selected.get(0)), false).open();
    }

}