com.foglyn.ui.FoglynAdvancedSearchPage.java Source code

Java tutorial

Introduction

Here is the source code for com.foglyn.ui.FoglynAdvancedSearchPage.java

Source

/*******************************************************************************
 * Copyright (c) 2008,2011 Peter Stibrany
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Peter Stibrany (pstibrany@gmail.com) - initial API and implementation
 *******************************************************************************/

package com.foglyn.ui;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import net.miginfocom.swt.MigLayout;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositoryQueryPage;
import org.eclipse.mylyn.tasks.ui.wizards.ITaskSearchPageContainer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import com.foglyn.core.AdvancedSearchQuery;
import com.foglyn.core.AdvancedSearchQuery.DateCondition;
import com.foglyn.core.FoglynCorePlugin;
import com.foglyn.core.FoglynRepositoryConnector;
import com.foglyn.fogbugz.FogBugzArea;
import com.foglyn.fogbugz.FogBugzArea.AreaID;
import com.foglyn.fogbugz.FogBugzArea.AreaIDFactory;
import com.foglyn.fogbugz.FogBugzCategory;
import com.foglyn.fogbugz.FogBugzCategory.CategoryID;
import com.foglyn.fogbugz.FogBugzCategory.CategoryIDFactory;
import com.foglyn.fogbugz.FogBugzClient;
import com.foglyn.fogbugz.FogBugzFixFor;
import com.foglyn.fogbugz.FogBugzFixFor.FixForID;
import com.foglyn.fogbugz.FogBugzFixFor.FixForIDFactory;
import com.foglyn.fogbugz.FogBugzPerson;
import com.foglyn.fogbugz.FogBugzPerson.PersonID;
import com.foglyn.fogbugz.FogBugzPerson.PersonIDFactory;
import com.foglyn.fogbugz.FogBugzPriority;
import com.foglyn.fogbugz.FogBugzPriority.PriorityID;
import com.foglyn.fogbugz.FogBugzPriority.PriorityIDFactory;
import com.foglyn.fogbugz.FogBugzProject;
import com.foglyn.fogbugz.FogBugzProject.ProjectID;
import com.foglyn.fogbugz.FogBugzProject.ProjectIDFactory;
import com.foglyn.fogbugz.FogBugzStatus.StatusID;
import com.foglyn.fogbugz.FogBugzStatus.StatusIDFactory;
import com.foglyn.fogbugz.HasID;
import com.foglyn.fogbugz.ID;
import com.foglyn.fogbugz.IDFactory;
import com.foglyn.helpers.AreaFilter;
import com.foglyn.helpers.AreaLabelProvider;
import com.foglyn.helpers.CategoryLabelProvider;
import com.foglyn.helpers.DateConditionComboViewer;
import com.foglyn.helpers.FBDatatypeComboViewer;
import com.foglyn.helpers.FBStatusesComboViewer;
import com.foglyn.helpers.FixForFilter;
import com.foglyn.helpers.FixForLabelProvider;
import com.foglyn.helpers.HelperConstants;
import com.foglyn.helpers.NullValueFilter;
import com.foglyn.helpers.PersonLabelProvider;
import com.foglyn.helpers.PriorityLabelProvider;
import com.foglyn.helpers.ProjectLabelProvider;

public class FoglynAdvancedSearchPage extends AbstractRepositoryQueryPage {
    private static final String PAGE_NAME = "AdvancedSearchPage";

    // Used when storing dialog settings
    private static final String TAGS = "TAGS";
    private static final String SUBSCRIBED = "SUBSCRIBED";
    private static final String STARRED_BY_ME = "STARRED_BY_ME";
    private static final String CORRESPONDENT = "CORRESPONDENT";
    private static final String DUE_DATE = "DUE_DATE";
    private static final String CLOSED_DATE = "CLOSED_DATE";
    private static final String RESOLVED_DATE = "RESOLVED_DATE";
    private static final String EDITED_DATE = "EDITED_DATE";
    private static final String OPENED_DATE = "OPENED_DATE";
    private static final String PRIORITY = "PRIORITY";
    private static final String FIXFOR = "FIXFOR";
    private static final String STATUS = "STATUS";
    private static final String AREA = "AREA";
    private static final String PROJECT = "PROJECT";
    private static final String CATEGORY = "CATEGORY";
    private static final String CLOSED_BY = "CLOSED_BY";
    private static final String LAST_EDITED_BY = "LAST_EDITED_BY";
    private static final String ALSO_EDITED_BY = "ALSO_EDITED_BY";
    private static final String EDITED_BY = "EDITED_BY";
    private static final String OPENED_BY = "OPENED_BY";
    private static final String RESOLVED_BY = "RESOLVED_BY";
    private static final String ASSIGNED_TO = "ASSIGNED_TO";
    private static final String SEARCH_QUERY = "SEARCH_QUERY";

    // is this page open for the first time (i.e., do we need to initialize after opening?)
    private boolean firstTime = true;

    private Text queryTitle;

    private Text searchQuery;

    private FBDatatypeComboViewer<CategoryID, FogBugzCategory> category;
    private FBDatatypeComboViewer<ProjectID, FogBugzProject> project;
    private FBDatatypeComboViewer<AreaID, FogBugzArea> area;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> assignedTo;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> resolvedBy;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> openedBy;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> closedBy;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> editedBy;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> lastEditedBy;
    private FBDatatypeComboViewer<PersonID, FogBugzPerson> alsoEditedBy;
    private FBStatusesComboViewer status;
    private FBDatatypeComboViewer<FixForID, FogBugzFixFor> fixFor;
    private FBDatatypeComboViewer<PriorityID, FogBugzPriority> priority;

    private DateConditionComboViewer due;
    private DateConditionComboViewer opened;
    private DateConditionComboViewer edited;
    private DateConditionComboViewer resolved;
    private DateConditionComboViewer closed;

    private Text tags;

    private Text correspondent;

    private Button starredByMe;
    private Button subscribed;

    private SearchPageChangedListener searchOptionsListener;

    private AdvancedSearchQuery initialQuery;
    private String initialQueryName;

    private Label alsoEditedByLabel;

    private boolean controlsEnabled = true;

    private boolean isFogBugz7Repo;

    public FoglynAdvancedSearchPage(TaskRepository taskRepository) {
        this(taskRepository, null, null);

        setImageDescriptor(FoglynImages.FOGBUGZ_REPOSITORY);
    }

    public FoglynAdvancedSearchPage(TaskRepository taskRepository, IRepositoryQuery reposQuery,
            AdvancedSearchQuery initialQuery) {
        super("FogBugz Search", taskRepository, reposQuery);

        isFogBugz7Repo = com.foglyn.core.Utils.isFogBugz7Repository(taskRepository, null, null);

        this.searchOptionsListener = new SearchPageChangedListener();
        this.initialQuery = initialQuery;
    }

    @Override
    public String getQueryTitle() {
        if (queryTitle == null) {
            return null;
        }

        // useful for query page, not for search page
        return getText(queryTitle);
    }

    public void createControl(Composite parent) {
        Composite control = new Composite(parent, SWT.NO_SCROLL);
        // parent uses Grid Layout
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(control);

        // set layout
        control.setLayout(new MigLayout("wrap 6, gapy unrelated",
                "[right,sizegroup label]" + "[fill,grow,sizegroup grp]" + "indent" + "[right,sizegroup label]"
                        + "[fill,grow,sizegroup grp]" + "indent" + "[right,sizegroup label]"
                        + "[fill,grow,sizegroup grp]"));

        // addSeparator(control, "FogBugz search options");

        if (getSearchContainer() == null) {
            label(control, "Query name:");

            queryTitle = createTextField(control);
            queryTitle.setLayoutData("span");
            if (initialQueryName != null) {
                queryTitle.setText(initialQueryName);
                initialQueryName = null;
            }

            addSeparator(control);
        }

        label(control, "Search &For:");
        searchQuery = createTextField(control);
        searchQuery.setLayoutData("span");

        label(control, "&Project:");
        project = createFBDatatypeCombo(FogBugzProject.class, control, "\u2014 All Projects \u2014",
                new ProjectLabelProvider());

        label(control, "&Area:");
        area = createFBDatatypeCombo(FogBugzArea.class, control, "\u2014 All Areas \u2014",
                new AreaLabelProvider());

        label(control, "&Category:");
        category = createFBDatatypeCombo(FogBugzCategory.class, control, "\u2014 All \u2014",
                new CategoryLabelProvider());

        label(control, "S&tatus:");
        status = createFBStatusesCombo(control, "\u2014 All \u2014");

        label(control, "Priorit&y:");
        priority = createFBDatatypeCombo(FogBugzPriority.class, control, "\u2014 All \u2014",
                new PriorityLabelProvider());

        if (isFogBugz7Repo) {
            label(control, "Milestone:");
        } else {
            label(control, "Fi&x For:");
        }
        fixFor = createFBDatatypeCombo(FogBugzFixFor.class, control, "\u2014 All \u2014",
                new FixForLabelProvider());

        addSeparator(control);

        label(control, "&Opened By:");
        openedBy = createPeopleCombo(control);

        List<DateCondition> dateConditions = Arrays.asList(DateCondition.TODAY, DateCondition.TODAY_OR_YESTERDAY,
                DateCondition.LAST_WEEK, DateCondition.LAST_MONTH, DateCondition.LAST_2_MONTHS,
                DateCondition.LAST_3_MONTHS, DateCondition.LAST_6_MONTHS, DateCondition.LAST_YEAR);

        label(control, "Opened:");
        opened = createDateConditionCombo(control, dateConditions);

        label(control, "&Due:");
        due = createDateConditionCombo(control,
                Arrays.asList(DateCondition.IN_THE_PAST, DateCondition.TODAY, DateCondition.TODAY_OR_TOMORROW,
                        DateCondition.NEXT_WEEK, DateCondition.NEXT_MONTH, DateCondition.NEXT_2_MONTHS,
                        DateCondition.NEXT_3_MONTHS, DateCondition.NEXT_6_MONTHS, DateCondition.NEXT_YEAR));

        label(control, "&Edited By:");
        editedBy = createPeopleCombo(control);

        label(control, "Edited:");
        edited = createDateConditionCombo(control, dateConditions);

        alsoEditedByLabel = label(control, "Also Edited By:");
        alsoEditedBy = createPeopleCombo(control);

        label(control, "&Resolved By:");
        resolvedBy = createPeopleCombo(control);

        label(control, "Resolved:");
        resolved = createDateConditionCombo(control, dateConditions);

        label(control, "Last Edited By:");
        lastEditedBy = createPeopleCombo(control);

        label(control, "Closed By:");
        closedBy = createPeopleCombo(control);

        label(control, "Closed:");
        closed = createDateConditionCombo(control, dateConditions);

        label(control, "Correspondent:");
        correspondent = createTextField(control);

        label(control, "Assi&gned To:");
        assignedTo = createPeopleCombo(control);

        starredByMe = createCheckbox(control, "Starred by me");

        subscribed = createCheckbox(control, "Subscribed by me");

        if (isFogBugz7Repo) {
            label(control, "Tags:");
            tags = createTextField(control);

            // don't wrap if not in search container ... single button looks weird on its own line
            if (inSearchContainer()) {
                tags.setLayoutData("wrap");
            }
        }

        String updateButtonLayout = "width button, span, right";

        if (inSearchContainer()) {
            Button resetButton = new Button(control, SWT.PUSH);
            resetButton.setText("Defaults");
            resetButton.setToolTipText("Reset fields to default values");
            resetButton.setLayoutData("width button, span, split 2, right");
            resetButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    resetWidgetsToDefaults();
                }
            });

            updateButtonLayout = "width button, right";
        }

        Button updateButton = new Button(control, SWT.PUSH);
        updateButton.setText("Update");
        updateButton.setToolTipText("Update attributes from repository");
        updateButton.setLayoutData(updateButtonLayout);
        updateButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                updateRepositoryConfiguration();
            }
        });

        // don't enable AlsoEditedBy until there is 'Edited by' person
        alsoEditedByLabel.setEnabled(false);
        alsoEditedBy.getControl().setEnabled(false);
        editedBy.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                IStructuredSelection ss = (IStructuredSelection) event.getSelection();

                Object selected = ss.getFirstElement();

                if (selected != null && !HelperConstants.NULL_VALUE.equals(selected)) {
                    enableAlsoEditedBy(true);
                } else {
                    enableAlsoEditedBy(false);
                }
            }
        });

        // setup filters
        final AreaFilter areaFilter = new AreaFilter();
        area.addFilter(new NullValueFilter(areaFilter));

        final FixForFilter fixForFilter = new FixForFilter();
        fixFor.addFilter(new NullValueFilter(fixForFilter));

        project.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                ProjectID projectID = project.convertSelection(event.getSelection());

                areaFilter.setProjectID(projectID);
                fixForFilter.setProjectID(projectID);

                refreshViewer(area);
                refreshViewer(fixFor);
            }
        });

        category.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                CategoryID categoryID = category.convertSelection(event.getSelection());

                status.setsetCategoryID(categoryID);
                status.setUsePrefix(categoryID == null);

                refreshViewer(status);
            }
        });

        setControl(control);
    }

    private void enableAlsoEditedBy(boolean enabled) {
        alsoEditedByLabel.setEnabled(enabled);
        alsoEditedBy.getControl().setEnabled(enabled);
    }

    private Label label(Composite parent, String text) {
        Label l = new Label(parent, SWT.RIGHT);
        l.setText(text);
        return l;
    }

    // Don't forget to enable/disable buttons if we are complete.
    @Override
    public void setPageComplete(boolean complete) {
        super.setPageComplete(complete);

        // Update button only if we control it (i.e. our controls are enabled)
        if (getSearchContainer() != null && controlsEnabled) {
            getSearchContainer().setPerformActionEnabled(complete);
        }
    }

    // Caching of error message is done to avoid flickering when super.isPageComplete is called -- it sets error message to
    // null, although we may change it to real message later... in such case, message flickers :-(
    private boolean delaySettingErrorMessage = false;
    private String errorMessageCache;

    @Override
    public void setErrorMessage(String newMessage) {
        errorMessageCache = newMessage;

        if (!delaySettingErrorMessage) {
            super.setErrorMessage(newMessage);
        }
    }

    private void updateErrorMessageFromCache() {
        super.setErrorMessage(errorMessageCache);
    }

    @Override
    public boolean isPageComplete() {
        if (getSearchContainer() == null) {
            // this checks if query name is unique

            // all this trickery with caching error message is to avoid super.isPageComplete setting error message to null
            // if there is no problem, because it causes flickering :-(
            delaySettingErrorMessage = true;

            boolean superComplete = false;
            try {
                superComplete = super.isPageComplete();
            } finally {
                delaySettingErrorMessage = false;
            }

            if (!superComplete) {
                // display error message from super.isPageComplete
                updateErrorMessageFromCache();
                return false;
            }
        }

        if (!isSearchPossible()) {
            setErrorMessage("Please specify search options");
            return false;
        }

        setErrorMessage(null);
        return true;
    }

    private boolean isSearchPossible() {
        List<Object> values = new ArrayList<Object>();

        values.add(getText(searchQuery));

        values.add(assignedTo.getSelectedID());
        values.add(resolvedBy.getSelectedID());
        values.add(openedBy.getSelectedID());

        PersonID eb = editedBy.getSelectedID();
        values.add(eb);

        if (eb != null) {
            values.add(alsoEditedBy.getSelectedID());
        }

        values.add(lastEditedBy.getSelectedID());
        values.add(closedBy.getSelectedID());

        values.add(category.getSelectedID());
        values.add(project.getSelectedID());
        values.add(area.getSelectedID());
        values.add(status.getSelectedStatuses());
        values.add(fixFor.getSelectedID());
        values.add(priority.getSelectedID());

        values.add(opened.getDateCondition());
        values.add(edited.getDateCondition());
        values.add(resolved.getDateCondition());
        values.add(closed.getDateCondition());
        values.add(due.getDateCondition());

        values.add(getText(correspondent));

        if (isFogBugz7Repo && tags != null) {
            List<String> tl = com.foglyn.core.Utils.getTagsFromString(tags.getText());
            if (!tl.isEmpty()) {
                values.add(tl);
            }
        }

        if (starredByMe.getSelection()) {
            values.add(Boolean.TRUE);
        }

        if (subscribed.getSelection()) {
            values.add(Boolean.TRUE);
        }

        // check if any value is selected
        for (Object o : values) {
            if (o != null)
                return true;
        }

        return false;
    }

    @Override
    public void applyTo(IRepositoryQuery query) {
        AdvancedSearchQuery asq = new AdvancedSearchQuery();

        if (queryTitle != null) {
            asq.setQueryTitle(getText(queryTitle));
        } else {
            asq.setQueryTitle("Ad-hoc search query");
        }

        asq.setSearchString(getText(searchQuery));

        asq.setAssignedTo(assignedTo.getSelectedID());
        asq.setResolvedBy(resolvedBy.getSelectedID());
        asq.setOpenedBy(openedBy.getSelectedID());

        PersonID eb = editedBy.getSelectedID();
        asq.setEditedBy(eb);

        if (eb != null) {
            asq.setAlsoEditedBy(alsoEditedBy.getSelectedID());
        } else {
            asq.setAlsoEditedBy(null);
        }

        asq.setLastEditedBy(lastEditedBy.getSelectedID());
        asq.setClosedBy(closedBy.getSelectedID());

        asq.setCategory(category.getSelectedID());
        asq.setProject(project.getSelectedID());
        asq.setArea(area.getSelectedID());
        asq.setStatuses(status.getSelectedStatuses());
        asq.setFixFor(fixFor.getSelectedID());
        asq.setPriority(priority.getSelectedID());

        asq.setOpened(opened.getDateCondition());
        asq.setEdited(edited.getDateCondition());
        asq.setResolved(resolved.getDateCondition());
        asq.setClosed(closed.getDateCondition());
        asq.setDue(due.getDateCondition());

        asq.setCorrespondent(getText(correspondent));
        asq.setStarredByMe(starredByMe.getSelection());
        asq.setSubscribed(subscribed.getSelection());

        if (isFogBugz7Repo && tags != null) {
            asq.setTags(getText(tags));
        }

        asq.saveToQuery(query);
    }

    private String getText(Text text) {
        String t = text.getText();
        if (t != null && t.trim().length() > 0) {
            return t.trim();
        }
        return null;
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);

        if (visible && firstTime) {
            firstTime = false;

            final IRunnableContext ctx = getRunnableContext();

            // run asynchronously -- we allow dialog to get displayed, and initiate fetching of filters
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    if (getControl() != null && !getControl().isDisposed()) {
                        initializePage(ctx);
                    }
                }
            });
        }

        if (visible && getSearchContainer() != null) {
            getSearchContainer().setPerformActionEnabled(isPageComplete());
        }
    }

    private IRunnableContext getRunnableContext() {
        ITaskSearchPageContainer sc = getSearchContainer();
        if (sc != null) {
            return sc.getRunnableContext();
        }

        return getContainer();
    }

    private void initializePage(IRunnableContext runnableContext) {
        final FoglynRepositoryConnector connector = (FoglynRepositoryConnector) TasksUi.getRepositoryManager()
                .getRepositoryConnector(FoglynCorePlugin.CONNECTOR_KIND);

        final AtomicReference<FogBugzClient> clientRef = new AtomicReference<FogBugzClient>(null);

        try {
            IRunnableWithProgress runnable = new IRunnableWithProgress() {
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    try {
                        monitor.beginTask("Fetching repository details", IProgressMonitor.UNKNOWN);
                        FogBugzClient client = connector.getClientManager().getFogbugzClient(getTaskRepository(),
                                monitor);

                        clientRef.set(client);

                        monitor.done();
                    } catch (CoreException e) {
                        throw new InvocationTargetException(e, e.getMessage());
                    }
                }
            };

            runnableContext.run(true, true, runnable);
        } catch (InvocationTargetException e) {
            FoglynUIPlugin.log("Cannot fetch repository data from FogBugz", e);
            setErrorMessage("Cannot fetch repository data from FogBugz");
        } catch (InterruptedException e) {
            FoglynUIPlugin.log("Cannot fetch repository data from FogBugz", e);
            setErrorMessage("Cannot fetch repository data from FogBugz");
        }

        FogBugzClient client = clientRef.get();
        if (client != null) {
            setupComponentsFromClient(client);
        }

        if (inSearchContainer()) {
            restoreState();
        } else if (initialQuery != null) {
            setupComponentsFromInitialQuery();
        }
    }

    private void updateRepositoryConfiguration() {
        final IRunnableContext ctx = getRunnableContext();

        // run asynchronously -- we allow dialog to get displayed, and initiate fetching of filters
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                if (getControl() != null && !getControl().isDisposed()) {
                    updateRepositoryValues(ctx);
                }
            }
        });
    }

    private void updateRepositoryValues(IRunnableContext runnableContext) {
        final FoglynRepositoryConnector connector = (FoglynRepositoryConnector) TasksUi.getRepositoryManager()
                .getRepositoryConnector(FoglynCorePlugin.CONNECTOR_KIND);

        final AtomicReference<FogBugzClient> clientRef = new AtomicReference<FogBugzClient>(null);

        try {
            IRunnableWithProgress runnable = new IRunnableWithProgress() {
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    try {
                        monitor.beginTask("Updating repository configuration", IProgressMonitor.UNKNOWN);
                        connector.updateRepositoryConfiguration(getTaskRepository(), monitor);

                        FogBugzClient client = connector.getClientManager().getFogbugzClient(getTaskRepository(),
                                monitor);

                        clientRef.set(client);

                        monitor.done();
                    } catch (CoreException e) {
                        throw new InvocationTargetException(e, e.getMessage());
                    }
                }
            };

            runnableContext.run(true, true, runnable);
        } catch (InvocationTargetException e) {
            FoglynUIPlugin.log("Cannot fetch repository data from FogBugz", e);
            setErrorMessage("Cannot fetch repository data from FogBugz");
        } catch (InterruptedException e) {
            FoglynUIPlugin.log("Cannot fetch repository data from FogBugz", e);
            setErrorMessage("Cannot fetch repository data from FogBugz");
        }

        FogBugzClient client = clientRef.get();
        if (client != null) {
            setupComponentsFromClient(client);
        }
    }

    private void setupComponentsFromClient(FogBugzClient client) {
        Collection<FogBugzPerson> people = client.getAllPeople();

        assignedTo.setComboValuesFromClientValues(people);
        openedBy.setComboValuesFromClientValues(people);
        closedBy.setComboValuesFromClientValues(people);
        resolvedBy.setComboValuesFromClientValues(people);
        editedBy.setComboValuesFromClientValues(people);
        lastEditedBy.setComboValuesFromClientValues(people);
        alsoEditedBy.setComboValuesFromClientValues(people);
        resolvedBy.setComboValuesFromClientValues(people);

        category.setComboValuesFromClientValues(client.getAllCategories());
        project.setComboValuesFromClientValues(client.getAllProjects());
        area.setComboValuesFromClientValues(client.getAllAreas());
        fixFor.setComboValuesFromClientValues(client.getAllFixFors());
        priority.setComboValuesFromClientValues(client.getAllPriorities());

        status.setStatusComboFromClientValues(client);
    }

    private void setupComponentsFromInitialQuery() {
        if (queryTitle != null) {
            queryTitle.setText(initialQuery.getQueryTitle());
        }

        project.selectOption(initialQuery.getProject());
        area.selectOption(initialQuery.getArea());
        category.selectOption(initialQuery.getCategory());
        status.selectStatuses(initialQuery.getStatuses());
        priority.selectOption(initialQuery.getPriority());
        fixFor.selectOption(initialQuery.getFixFor());

        openedBy.selectOption(initialQuery.getOpenedBy());
        opened.selectDateCondition(initialQuery.getOpened());

        editedBy.selectOption(initialQuery.getEditedBy());
        lastEditedBy.selectOption(initialQuery.getLastEditedBy());
        alsoEditedBy.selectOption(initialQuery.getAlsoEditedBy());
        edited.selectDateCondition(initialQuery.getEdited());

        resolvedBy.selectOption(initialQuery.getResolvedBy());
        resolved.selectDateCondition(initialQuery.getResolved());

        closedBy.selectOption(initialQuery.getClosedBy());
        closed.selectDateCondition(initialQuery.getClosed());

        assignedTo.selectOption(initialQuery.getAssignedTo());

        due.selectDateCondition(initialQuery.getDue());

        if (initialQuery.getSearchString() != null) {
            searchQuery.setText(initialQuery.getSearchString());
        }

        if (initialQuery.getCorrespondent() != null) {
            correspondent.setText(initialQuery.getCorrespondent());
        }

        starredByMe.setSelection(initialQuery.getStarredByMe());
        subscribed.setSelection(initialQuery.getSubscribedByMe());

        if (isFogBugz7Repo && tags != null) {
            tags.setText(initialQuery.getTags());
        }
    }

    private void addSeparator(Composite panel) {
        Label s = new Label(panel, SWT.SEPARATOR | SWT.HORIZONTAL);
        s.setLayoutData("growx, span");
    }

    private void refreshViewer(StructuredViewer viewer) {
        IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();

        viewer.refresh();
        viewer.setSelection(sel);

        IStructuredSelection after = (IStructuredSelection) viewer.getSelection();
        if (after.isEmpty()) {
            viewer.setSelection(HelperConstants.NULL_VALUE_SELECTION);
        }
    }

    private void configureCombo(ComboViewer combo) {
        combo.setInput(Collections.emptyList());
        combo.setSelection(HelperConstants.NULL_VALUE_SELECTION);
        combo.getCombo().setVisibleItemCount(Constants.NUMBER_OF_ENTRIES_IN_COMBOBOX);
        combo.getCombo().setLayoutData("width 10%:pref:50%");
        combo.addSelectionChangedListener(searchOptionsListener);
    }

    private <K extends ID, T extends HasID<K>> FBDatatypeComboViewer<K, T> createFBDatatypeCombo(Class<T> clazz,
            Composite parent, String nullValueLabel, ILabelProvider labelProvider) {
        FBDatatypeComboViewer<K, T> combo = FBDatatypeComboViewer.create(clazz, parent,
                SWT.DROP_DOWN | SWT.READ_ONLY, nullValueLabel, labelProvider);
        configureCombo(combo);
        return combo;
    }

    private FBDatatypeComboViewer<PersonID, FogBugzPerson> createPeopleCombo(Composite parent) {
        return createFBDatatypeCombo(FogBugzPerson.class, parent, "\u2014 Anybody \u2014",
                new PersonLabelProvider());
    }

    private DateConditionComboViewer createDateConditionCombo(Composite control, List<DateCondition> conditions) {
        DateConditionComboViewer combo = DateConditionComboViewer.create(control, SWT.DROP_DOWN | SWT.READ_ONLY,
                "\u2014 Any time \u2014");
        configureCombo(combo);
        combo.setDateConditionsInput(conditions);
        return combo;
    }

    private FBStatusesComboViewer createFBStatusesCombo(Composite parent, String nullValueLabel) {
        FBStatusesComboViewer combo = new FBStatusesComboViewer(parent, SWT.DROP_DOWN | SWT.READ_ONLY,
                nullValueLabel);
        configureCombo(combo);
        return combo;
    }

    private Text createTextField(Composite control) {
        Text text = new Text(control, SWT.SINGLE | SWT.BORDER);
        text.addModifyListener(searchOptionsListener);
        return text;
    }

    private Button createCheckbox(Composite control, String text) {
        Button button = new Button(control, SWT.CHECK);
        button.setText(text);
        button.setLayoutData("skip");

        button.addSelectionListener(searchOptionsListener);

        return button;
    }

    private class SearchPageChangedListener
            implements ISelectionChangedListener, ModifyListener, SelectionListener {
        public void selectionChanged(SelectionChangedEvent event) {
            refresh();
        }

        public void modifyText(ModifyEvent e) {
            refresh();
        }

        public void widgetDefaultSelected(SelectionEvent e) {
            refresh();
        }

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

        private void refresh() {
            if (isControlCreated()) {
                setPageComplete(isPageComplete());
            }
        }
    }

    @Override
    public void setControlsEnabled(boolean enabled) {
        this.controlsEnabled = enabled;

        super.setControlsEnabled(enabled);

        if (enabled) {
            if (editedBy.getSelectedID() != null) {
                enableAlsoEditedBy(true);
            } else {
                enableAlsoEditedBy(false);
            }
        }
    }

    @Override
    public boolean performSearch() {
        if (inSearchContainer()) {
            saveState();
        }

        return super.performSearch();
    }

    @Override
    public void saveState() {
        String repoID = "." + getTaskRepository().getRepositoryUrl();

        IDialogSettings dlgSettings = getDialogSettings();

        dlgSettings.put(SEARCH_QUERY + repoID, getText(searchQuery));
        saveID(dlgSettings, ASSIGNED_TO + repoID, assignedTo.getSelectedID());
        saveID(dlgSettings, RESOLVED_BY + repoID, resolvedBy.getSelectedID());
        saveID(dlgSettings, OPENED_BY + repoID, openedBy.getSelectedID());
        saveID(dlgSettings, EDITED_BY + repoID, editedBy.getSelectedID());

        PersonID alsoEditedByID = null;
        if (editedBy.getSelectedID() != null) {
            alsoEditedByID = alsoEditedBy.getSelectedID();
        }
        saveID(dlgSettings, ALSO_EDITED_BY + repoID, alsoEditedByID);

        saveID(dlgSettings, LAST_EDITED_BY + repoID, lastEditedBy.getSelectedID());
        saveID(dlgSettings, CLOSED_BY + repoID, closedBy.getSelectedID());
        saveID(dlgSettings, CATEGORY + repoID, category.getSelectedID());
        saveID(dlgSettings, PROJECT + repoID, project.getSelectedID());
        saveID(dlgSettings, AREA + repoID, area.getSelectedID());
        saveIDs(dlgSettings, STATUS + repoID, status.getSelectedStatuses());
        saveID(dlgSettings, FIXFOR + repoID, fixFor.getSelectedID());
        saveID(dlgSettings, PRIORITY + repoID, priority.getSelectedID());

        saveDateCondition(dlgSettings, OPENED_DATE + repoID, opened.getDateCondition());
        saveDateCondition(dlgSettings, EDITED_DATE + repoID, edited.getDateCondition());
        saveDateCondition(dlgSettings, RESOLVED_DATE + repoID, resolved.getDateCondition());
        saveDateCondition(dlgSettings, CLOSED_DATE + repoID, closed.getDateCondition());
        saveDateCondition(dlgSettings, DUE_DATE + repoID, due.getDateCondition());

        dlgSettings.put(CORRESPONDENT + repoID, getText(correspondent));
        dlgSettings.put(STARRED_BY_ME + repoID, starredByMe.getSelection());
        dlgSettings.put(SUBSCRIBED + repoID, subscribed.getSelection());

        if (isFogBugz7Repo && tags != null) {
            dlgSettings.put(TAGS + repoID, getText(tags));
        }
    }

    public void restoreState() {
        String repoID = "." + getTaskRepository().getRepositoryUrl();

        IDialogSettings dlgSettings = getDialogSettings();

        project.selectOption(getID(dlgSettings, PROJECT + repoID, new ProjectIDFactory()));
        area.selectOption(getID(dlgSettings, AREA + repoID, new AreaIDFactory()));
        category.selectOption(getID(dlgSettings, CATEGORY + repoID, new CategoryIDFactory()));
        status.selectStatuses(getIDs(dlgSettings, STATUS + repoID, new StatusIDFactory()));
        priority.selectOption(getID(dlgSettings, PRIORITY + repoID, new PriorityIDFactory()));
        fixFor.selectOption(getID(dlgSettings, FIXFOR + repoID, new FixForIDFactory()));

        PersonIDFactory pidf = new PersonIDFactory();

        openedBy.selectOption(getID(dlgSettings, OPENED_BY + repoID, pidf));
        opened.selectDateCondition(getDateCondition(dlgSettings, OPENED_DATE + repoID));

        editedBy.selectOption(getID(dlgSettings, EDITED_BY + repoID, pidf));
        lastEditedBy.selectOption(getID(dlgSettings, LAST_EDITED_BY + repoID, pidf));
        alsoEditedBy.selectOption(getID(dlgSettings, ALSO_EDITED_BY + repoID, pidf));
        edited.selectDateCondition(getDateCondition(dlgSettings, EDITED_DATE + repoID));

        resolvedBy.selectOption(getID(dlgSettings, RESOLVED_BY + repoID, pidf));
        resolved.selectDateCondition(getDateCondition(dlgSettings, RESOLVED_DATE + repoID));

        closedBy.selectOption(getID(dlgSettings, CLOSED_BY + repoID, pidf));
        closed.selectDateCondition(getDateCondition(dlgSettings, CLOSED_DATE + repoID));

        assignedTo.selectOption(getID(dlgSettings, ASSIGNED_TO + repoID, pidf));

        due.selectDateCondition(getDateCondition(dlgSettings, DUE_DATE + repoID));

        if (dlgSettings.get(SEARCH_QUERY + repoID) != null) {
            searchQuery.setText(dlgSettings.get(SEARCH_QUERY + repoID));
        }

        if (dlgSettings.get(CORRESPONDENT + repoID) != null) {
            correspondent.setText(dlgSettings.get(CORRESPONDENT + repoID));
        }

        starredByMe.setSelection(dlgSettings.getBoolean(STARRED_BY_ME + repoID));
        subscribed.setSelection(dlgSettings.getBoolean(SUBSCRIBED + repoID));

        if (isFogBugz7Repo && tags != null) {
            if (dlgSettings.get(TAGS + repoID) != null) {
                tags.setText(dlgSettings.get(TAGS + repoID));
            }
        }
    }

    private DateCondition getDateCondition(IDialogSettings dlgSettings, String string) {
        String value = dlgSettings.get(string);
        if (com.foglyn.core.Utils.isEmpty(value)) {
            return null;
        }

        return DateCondition.valueOf(value);
    }

    private Set<StatusID> getIDs(IDialogSettings dlgSettings, String string, StatusIDFactory statusIDFactory) {
        String[] values = dlgSettings.getArray(string);
        if (values == null) {
            return null;
        }

        Set<StatusID> result = new HashSet<StatusID>();

        for (String s : values) {
            if (!com.foglyn.core.Utils.isEmpty(s)) {
                result.add(statusIDFactory.valueOf(s));
            }
        }

        return result;
    }

    private <T extends ID> T getID(IDialogSettings dlgSettings, String string, IDFactory<T> idFactory) {
        String value = dlgSettings.get(string);
        if (com.foglyn.core.Utils.isEmpty(value)) {
            return null;
        }

        return idFactory.valueOf(value);
    }

    private void saveDateCondition(IDialogSettings dlgSettings, String setting, DateCondition dateCondition) {
        if (dateCondition == null) {
            dlgSettings.put(setting, (String) null);
        } else {
            dlgSettings.put(setting, dateCondition.name());
        }
    }

    private void saveIDs(IDialogSettings dlgSettings, String setting, Set<StatusID> selectedStatuses) {
        if (selectedStatuses == null || selectedStatuses.isEmpty()) {
            dlgSettings.put(setting, (String) null);
        } else {
            dlgSettings.put(setting, com.foglyn.core.Utils.toStrings(selectedStatuses).toArray(new String[0]));
        }
    }

    private void saveID(IDialogSettings dlgSettings, String setting, ID selectedID) {
        if (selectedID == null) {
            dlgSettings.put(setting, (String) null);
        } else {
            dlgSettings.put(setting, selectedID.toString());
        }
    }

    private void resetWidgetsToDefaults() {
        project.selectOption(null);
        area.selectOption(null);
        category.selectOption(null);
        status.selectStatuses(null);
        priority.selectOption(null);
        fixFor.selectOption(null);

        openedBy.selectOption(null);
        opened.selectDateCondition(null);

        editedBy.selectOption(null);
        lastEditedBy.selectOption(null);
        alsoEditedBy.selectOption(null);
        edited.selectDateCondition(null);

        resolvedBy.selectOption(null);
        resolved.selectDateCondition(null);

        closedBy.selectOption(null);
        closed.selectDateCondition(null);

        assignedTo.selectOption(null);

        due.selectDateCondition(null);

        searchQuery.setText("");
        correspondent.setText("");

        starredByMe.setSelection(false);
        subscribed.setSelection(false);

        if (isFogBugz7Repo && tags != null) {
            tags.setText("");
        }
    }

    @Override
    protected IDialogSettings getDialogSettings() {
        IDialogSettings settings = FoglynUIPlugin.getDefault().getDialogSettings();
        IDialogSettings dialogSettings = settings.getSection(PAGE_NAME);
        if (dialogSettings == null) {
            dialogSettings = settings.addNewSection(PAGE_NAME);
        }
        return dialogSettings;
    }

    void setQueryTitle(String queryTitle) {
        this.initialQueryName = queryTitle;
    }
}