it.unibz.instasearch.ui.InstaSearchView.java Source code

Java tutorial

Introduction

Here is the source code for it.unibz.instasearch.ui.InstaSearchView.java

Source

/*
 * Copyright (c) 2009 Andrejs Jermakovics.
 * 
 * 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:
 *     Andrejs Jermakovics - initial implementation
 */
package it.unibz.instasearch.ui;

import it.unibz.instasearch.InstaSearchPlugin;
import it.unibz.instasearch.actions.CheckUpdatesActionDelegate;
import it.unibz.instasearch.actions.ShowExceptionAction;
import it.unibz.instasearch.indexing.Field;
import it.unibz.instasearch.indexing.SearchQuery;
import it.unibz.instasearch.indexing.SearchResultDoc;
import it.unibz.instasearch.jobs.CheckUpdatesJob;
import it.unibz.instasearch.prefs.PreferenceConstants;
import it.unibz.instasearch.ui.ResultContentProvider.MatchLine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import org.eclipse.core.runtime.ILogListener;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
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.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.ViewPart;

/**
 * 
 */
public class InstaSearchView extends ViewPart
        implements ModifyListener, ILogListener, ITreeViewerListener, IPropertyChangeListener {

    /** The view ID */
    public static final String ID = InstaSearchView.class.getName(); // we have just one view

    /** Tree for results */
    private TreeViewer resultViewer;
    /** Textbox for search query */
    private StyledText searchText;

    private IAction openAction;
    private SearchJob searchJob;
    private ExpandCollapseJob expandCollapseJob;

    private ResultContentProvider contentProvider;
    private int lastIncrementalSearchPos = 0;

    // preferences
    private int maxResults;
    private int typingSearchDelay;
    private boolean incrementalSearchEnabled;

    private SearchViewControl searchViewControl;

    /**
     * 
     */
    public InstaSearchView() {
    }

    @Override
    public void init(IViewSite site) throws PartInitException {
        super.init(site);

        scheduleUpdateCheck(); // schedule update check if view is opened

        InstaSearchPlugin.getDefault().getLog().addLogListener(this); // listen for exceptions

        initPrefs();

        InstaSearchPlugin.addPreferenceChangeListener(this);
    }

    private void initPrefs() {
        typingSearchDelay = InstaSearchPlugin.getIntPref(PreferenceConstants.P_TYPING_SEARCH_DELAY);
        maxResults = InstaSearchPlugin.getIntPref(PreferenceConstants.P_SHOWN_FILES_COUNT);
        incrementalSearchEnabled = InstaSearchPlugin.getBoolPref(PreferenceConstants.P_INCREMENTAL_SEARCH);
    }

    @Override
    public void createPartControl(Composite parent) {

        this.searchViewControl = new SearchViewControl(parent, this);

        searchText = searchViewControl.getSearchText();
        resultViewer = searchViewControl.getResultViewer();

        searchText.addModifyListener(this);

        contentProvider = new ResultContentProvider();
        IStyledLabelProvider labelProvider = new ResultLabelProvider(contentProvider);
        IBaseLabelProvider decoratedLabelProvider = new DecoratingStyledCellLabelProvider(labelProvider, null,
                null);

        configureResultViewer(contentProvider, decoratedLabelProvider);
        searchViewControl.setContentProposalAdapter(new SearchContentProposalProvider(contentProvider));

        searchJob = new SearchJob(this);
        expandCollapseJob = new ExpandCollapseJob();

        makeActions();
        hookContextMenu();
        hookDoubleClickAction();
    }

    public void modifyText(ModifyEvent e) {
        searchJob.cancel();

        StyleRange[] styleRanges = createStyledSearchString(searchText.getText());
        searchText.setStyleRanges(styleRanges);

        if (searchViewControl.isShowingSearchTip())
            return; // showing search tip

        searchJob.schedule(getSearchQuery(), false, typingSearchDelay); // start with a delay since user might still be typing

        if (incrementalSearchEnabled)
            doIncrementalSearch();
    }

    private void doIncrementalSearch() {
        IEditorPart editor = InstaSearchUI.getActiveEditor();

        if (editor != null) {
            IFindReplaceTarget target = (IFindReplaceTarget) editor.getAdapter(IFindReplaceTarget.class);

            if (target != null)
                lastIncrementalSearchPos = target.findAndSelect(lastIncrementalSearchPos, searchText.getText(),
                        true, false, false) + searchText.getText().length();
        }
    }

    /**
     * Highlight fields
     * 
     * @param text
     * @return bold ranges 
     */
    private static StyleRange[] createStyledSearchString(String text) {
        //TODO: use parser
        ArrayList<StyleRange> styleRanges = new ArrayList<StyleRange>();
        String lcaseText = text.toLowerCase();

        ArrayList<String> fieldsToHighlight = new ArrayList<String>(Field.values().length);
        for (Field field : Field.values()) // add all field names
            fieldsToHighlight.add(field.toString());

        for (String fieldName : fieldsToHighlight) {
            int pos = lcaseText.indexOf(fieldName + ':'); // should use a parser here

            while (pos != -1) {
                styleRanges.add(new StyleRange(pos, fieldName.length(), null, null, SWT.BOLD));
                pos = lcaseText.indexOf(fieldName + ':', pos + fieldName.length() - 1); // find next
            }
        }

        // ranges must be sorted by start position
        Collections.sort(styleRanges, new Comparator<StyleRange>() {
            public int compare(StyleRange sr1, StyleRange sr2) {
                return sr1.start - sr2.start;
            }
        });

        //TODO: highlight AND, OR 
        return styleRanges.toArray(new StyleRange[styleRanges.size()]);
    }

    void setSearchString(String searchString) {
        searchText.setText(searchString);
    }

    /**
     * Starts the search by giving search string as input to the viewer
     * 
     * @param searchQuery 
     * @param selectLast whether to select the item which is currently last
     */
    void search(SearchQuery searchQuery, boolean selectLast) {
        searchJob.cancel(); // cancel previous search
        searchQuery.setFilter(searchViewControl.getFilter());
        searchJob.schedule(searchQuery, selectLast, 0);
    }

    private SearchQuery getSearchQuery() {
        SearchQuery sq = new SearchQuery(getSearchText(), maxResults);
        sq.setFilter(searchViewControl.getFilter());
        return sq;
    }

    String getSearchText() {
        return searchText.getText().trim();
    }

    TreeViewer getResultViewer() {
        return resultViewer;
    }

    public void treeExpanded(TreeExpansionEvent event) {

        if (event.getElement() instanceof SearchQuery) {
            search((SearchQuery) event.getElement(), true);
        }
    }

    public void treeCollapsed(TreeExpansionEvent event) {
    }

    private void configureResultViewer(ResultContentProvider contentProvider,
            IBaseLabelProvider decoratedLabelProvider) {

        resultViewer.setContentProvider(contentProvider);
        resultViewer.setLabelProvider(decoratedLabelProvider);
        resultViewer.setSorter(null);

        getViewSite().setSelectionProvider(resultViewer);
        resultViewer.addTreeListener(this);

        resultViewer.getControl().addMouseTrackListener(new MouseTrackAdapter() {
            public void mouseHover(MouseEvent e) {
                ViewerCell cell = resultViewer.getCell(new Point(e.x, e.y));

                if (cell != null && cell.getElement() instanceof SearchResultDoc) {
                    SearchResultDoc doc = (SearchResultDoc) cell.getElement();
                    resultViewer.getTree().setToolTipText(doc.getFilePath());
                } else {
                    resultViewer.getTree().setToolTipText("");
                }
            }
        });

        KeyAdapter keyListener = new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                onSearchTextKeyPress(e);
            }
        };

        resultViewer.getControl().addKeyListener(keyListener);
        searchText.addKeyListener(keyListener);
    }

    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu");

        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                fillContextMenu(manager);
            }
        });

        Menu menu = menuMgr.createContextMenu(resultViewer.getControl());
        resultViewer.getControl().setMenu(menu);
        getSite().registerContextMenu(menuMgr, resultViewer);
    }

    private void onSearchTextKeyPress(KeyEvent e) {
        if (e.keyCode == SWT.F5) {
            refreshSearch();
        }
        if (e.keyCode == SWT.DEL) {
            deleteSelectedMatch();
        }
        if (e.keyCode == (int) 'j' && (e.stateMask & SWT.CTRL) != 0) {
            doIncrementalSearch();
        } else if (e.keyCode == SWT.TAB) {
            resultViewer.getTree().setFocus();
        } else if (e.keyCode == SWT.ESC) {
            if (expandCollapseJob.getState() == Job.RUNNING) {
                expandCollapseJob.cancel();
            } else {
                if (searchText.getSelectionText().equals(searchText.getText())) {
                    searchText.setText("");
                } else {
                    searchText.setFocus();
                    searchText.selectAll();
                }
            }
        } else if (e.getSource() == searchText && e.keyCode == SWT.CR && (e.stateMask & SWT.CTRL) != 0) {
            showAllResults();
        }
    }

    private void fillContextMenu(IMenuManager manager) {
        boolean haveSelection = !resultViewer.getSelection().isEmpty();
        SearchQuery sq = (SearchQuery) resultViewer.getInput();

        openAction.setEnabled(haveSelection);
        manager.add(openAction);

        boolean showingItems = resultViewer.getTree().getItemCount() > 0;

        Action expandAll = new Action("Expand All", InstaSearchPlugin.getImageDescriptor("expandall")) {
            public void run() {
                expandAll();
            }
        };
        expandAll.setEnabled(showingItems);
        manager.add(expandAll);

        Action collapseAll = new Action("Collapse All", InstaSearchPlugin.getImageDescriptor("collapseall")) {
            public void run() {
                collapseAll();
            }
        };
        collapseAll.setEnabled(showingItems);
        manager.add(collapseAll);

        Action refresh = new Action("Refresh") {
            public void run() {
                refreshSearch();
            }
        };
        refresh.setAccelerator(SWT.F5);
        manager.add(refresh);

        Action delete = new Action("Delete Match") {
            public void run() {
                deleteSelectedMatch();
            }
        };
        delete.setAccelerator(SWT.DEL);
        manager.add(delete);

        Action moreResults = new Action("More Results...") {
            public void run() {
                showAllResults();
            }
        };
        moreResults.setEnabled(showingItems);
        manager.add(moreResults);

        if (sq == null || !sq.isLimited())
            moreResults.setEnabled(false);

    }

    private void deleteSelectedMatch() {
        if (getResultViewer().getSelection() == null)
            return;
        IStructuredSelection selection = (IStructuredSelection) resultViewer.getSelection();
        getResultViewer().remove(selection.toArray());
    }

    /**
     * 
     */
    public void showAllResults() {
        SearchQuery sq = (SearchQuery) resultViewer.getInput();
        SearchQuery newSq = new SearchQuery(sq);
        newSq.setMaxResults(SearchQuery.UNLIMITED_RESULTS);
        search(newSq, false);
    }

    /**
     * 
     */
    public void expandAll() {
        expandCollapseJob.schedule(true);
    }

    /**
     * 
     */
    public void collapseAll() {
        expandCollapseJob.schedule(false);
    }

    private void openSelection() throws Exception {
        IStructuredSelection selection = (IStructuredSelection) resultViewer.getSelection();
        Object obj = selection.getFirstElement();

        SearchResultDoc doc = null;
        MatchLine selectedLineMatches = null;

        if (obj instanceof SearchResultDoc) {
            doc = (SearchResultDoc) obj;
        } else if (obj instanceof MatchLine) {
            selectedLineMatches = (MatchLine) obj;
            doc = selectedLineMatches.getResultDoc();
        } else if (obj instanceof Exception) {
            InstaSearchUI.showError((Exception) obj);
            return;
        } else if (obj instanceof SearchQuery) {
            search((SearchQuery) obj, true);
            return;
        } else
            return;

        new MatchHighlightJob(doc, selectedLineMatches, contentProvider, searchJob, getSite().getPage()).schedule();
    }

    private void hookDoubleClickAction() {
        resultViewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                openAction.run();
            }
        });
    }

    public void setFocus() {
        searchText.setFocus();
        //searchText.selectAll();
    }

    private void makeActions() {
        openAction = new Action("Open") {
            public void run() {
                try {
                    openSelection();
                } catch (Exception e) {
                    InstaSearchPlugin.log(e);
                }
            }
        };
    }

    private void scheduleUpdateCheck() {
        boolean checkUpdates = InstaSearchPlugin.getBoolPref(PreferenceConstants.P_CHECK_UPDATES);
        if (!checkUpdates)
            return;

        CheckUpdatesJob checkUpdatesJob = new CheckUpdatesJob();
        checkUpdatesJob.setSystem(true);
        checkUpdatesJob.addJobChangeListener(new UpdateJobChangeListener());
        checkUpdatesJob.schedule(InstaSearchPlugin.getIntPref(PreferenceConstants.P_UPDATE_CHECK_DELAY));
    }

    /**
     * Logging an error in the plugin
     * Create an action that allows reporting it
     */
    public void logging(IStatus status, String plugin) {
        IMenuManager menuManager = getViewSite().getActionBars().getMenuManager();

        IContributionItem item = menuManager.find(ShowExceptionAction.ID);

        if (item != null)
            menuManager.remove(item);

        ShowExceptionAction action = new ShowExceptionAction(status);
        action.setText("Report Bug");

        menuManager.add(action);
    }

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

        if (InstaSearchPlugin.getDefault() != null) {
            InstaSearchPlugin.getDefault().getLog().removeLogListener(this);
            InstaSearchPlugin.removePreferenceChangeListener(this);
        }
    }

    private void refreshSearch() {
        InstaSearchPlugin.getInstaSearch().updateIndex();

        SearchQuery input = (SearchQuery) resultViewer.getInput();
        if (input == null)
            return;
        resultViewer.setInput(null); // clear cached search results

        searchJob.cancel();
        searchJob.schedule(input, false, typingSearchDelay);
    }

    public void propertyChange(PropertyChangeEvent event) {
        typingSearchDelay = InstaSearchPlugin.getIntPref(PreferenceConstants.P_TYPING_SEARCH_DELAY);
        maxResults = InstaSearchPlugin.getIntPref(PreferenceConstants.P_SHOWN_FILES_COUNT);
        incrementalSearchEnabled = InstaSearchPlugin.getBoolPref(PreferenceConstants.P_INCREMENTAL_SEARCH);
    }

    /**
     * Waits for {@link CheckUpdatesJob} to finish and notifies if update is available 
     * by placing an Update button in the view's toolbar
     */
    private class UpdateJobChangeListener extends JobChangeAdapter {

        public void done(IJobChangeEvent event) {
            IStatus status = event.getResult();

            if (status.getSeverity() == IStatus.OK) {
                boolean updateAvailable = (status.getCode() == CheckUpdatesJob.UPDATE_AVAILABLE_CODE);
                if (updateAvailable) {
                    getViewSite().getShell().getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            addUpdateAction();
                            setTitleToolTip("New version available");
                            getViewSite().getActionBars().getStatusLineManager().setMessage(getTitleImage(),
                                    "New version available");
                        }
                    });
                }

            }
        }

        private void addUpdateAction() {
            IAction updateAction = CheckUpdatesJob.createUpdateNotificationAction();
            updateAction.setImageDescriptor(InstaSearchPlugin.getImageDescriptor("lightbulb"));

            IToolBarManager mgr = getViewSite().getActionBars().getToolBarManager();
            mgr.add(updateAction);
            mgr.update(true);

            IMenuManager menuMgr = getViewSite().getActionBars().getMenuManager();

            menuMgr.add(updateAction);

            IContributionItem checkUpdatesItem = mgr.find(CheckUpdatesActionDelegate.ID);
            if (checkUpdatesItem != null)
                checkUpdatesItem.setVisible(false); // hide Check for Updates action
        }
    }

    /**
     * Background job that expands/collapses all entries
     */
    private class ExpandCollapseJob extends Job implements ISchedulingRule {

        /**
         */
        public ExpandCollapseJob() {
            super("Expand All");

            setRule(this);
            //setUser(true);

            // listen to searchJob changes. stop expanding on new search
            searchJob.addJobChangeListener(new JobChangeAdapter() {
                public void scheduled(IJobChangeEvent event) {
                    cancel(); // new search
                }

                public void done(IJobChangeEvent event) {
                    cancel(); // canceled search
                }
            });

        }

        public void schedule(boolean expandAll) {

            this.cancel();

            if (!expandAll) {
                resultViewer.collapseAll();
                return;
            }

            if (resultViewer.getTree().getItemCount() == 0) {
                return;
            }

            this.schedule();
        }

        protected IStatus run(IProgressMonitor monitor) {

            Display display = getViewSite().getShell().getDisplay();

            Object[] elements = contentProvider.getElements();
            monitor.beginTask("InstaSearch Expanding", elements.length);

            for (int i = 0; i < elements.length && !monitor.isCanceled(); i++) {
                final Object curDoc = elements[i];
                if (curDoc == null)
                    continue;
                if (!(curDoc instanceof SearchResultDoc))
                    continue;

                contentProvider.getChildren(curDoc); // get lines from file (they become cached)

                Runnable expander = new Runnable() {
                    public void run() {
                        resultViewer.setExpandedState(curDoc, true);
                    }
                };
                display.syncExec(expander); // expand in UI thread

                monitor.worked(1);
            }

            monitor.done();

            return Status.OK_STATUS;
        }

        public boolean contains(ISchedulingRule rule) {
            return rule.getClass() == this.getClass();
        }

        public boolean isConflicting(ISchedulingRule rule) {
            return rule.getClass() == this.getClass();
        }

    }

}