Java tutorial
/* * 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(); } } }