/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** 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.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** IBM Corporation - initial API and implementation **
** RSSOwl Development Team - adapted to work with BookMark Explorer **
** **
** ********************************************************************** */
package org.rssowl.ui.internal.views.explorer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
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.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.progress.WorkbenchJob;
import org.rssowl.ui.internal.Application;
import org.rssowl.ui.internal.ContextMenuCreator;
import org.rssowl.ui.internal.Controller;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.views.explorer.BookMarkFilter.SearchTarget;
/**
* A simple control that provides a text widget. The contents of the text widget
* are used to drive a <code>PatternFilter</code> that is part of a TreeViewer.
*
* @author bpasero
*/
class BookMarkSearchbar extends Composite {
/* Delay before filtering takes place */
private static final int FILTER_DELAY = 300;
/* Action Bar */
private static final String BAR_ACTION_ID = "org.rssowl.ui.internal.views.explorer.BarAction"; //$NON-NLS-1$
private Composite fFilterComposite;
private ToolBarManager fFilterToolBar;
private Object fExpandedElements[];
private Text fFilterText;
private String fInitialText = ""; //$NON-NLS-1$
private BookMarkFilter fPatternFilter;
private Job fRefreshJob;
private Object fSelectedElements[];
private TreeViewer fViewer;
private IViewSite fViewSite;
/**
* Create a new instance of the receiver.
*
* @param viewSite A link to the ViewSite this Bar is in.
* @param parent parent <code>Composite</code>
* @param viewer The Viewer this Filter is working for.
* @param patternFilter The PatternFilter to use.
*/
BookMarkSearchbar(IViewSite viewSite, Composite parent, TreeViewer viewer, BookMarkFilter patternFilter) {
super(parent, SWT.NONE);
fViewSite = viewSite;
fViewer = viewer;
fPatternFilter = patternFilter;
createControl(parent);
createRefreshJob();
setFont(parent.getFont());
}
/* Create Toolbar with "Clear" Button */
private void createClearText(Composite parent) {
ToolBar toolBar = new ToolBar(parent, SWT.FLAT | SWT.HORIZONTAL);
fFilterToolBar = new ToolBarManager(toolBar);
fFilterToolBar.getControl().setBackground(parent.getBackground());
fFilterToolBar.getControl().setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, true));
/* Initially Hide */
((GridData) toolBar.getLayoutData()).exclude = true;
toolBar.setVisible(false);
IAction clearTextAction = new Action("", IAction.AS_PUSH_BUTTON) {//$NON-NLS-1$
@Override
public void run() {
setFilterText(""); //$NON-NLS-1$
}
};
clearTextAction.setToolTipText(Messages.BookMarkSearchbar_CLEAR);
clearTextAction.setImageDescriptor(OwlUI.getImageDescriptor("icons/etool16/clear.gif")); //$NON-NLS-1$
fFilterToolBar.add(clearTextAction);
}
/* Creates the Container for the Filter Controls */
private void createControl(Composite parent) {
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
setLayout(layout);
setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
/* Container for Filter Controls */
fFilterComposite = new Composite(this, SWT.NONE);
GridLayout filterLayout = new GridLayout(Application.IS_MAC ? 2 : 3, false);
filterLayout.marginHeight = 0;
filterLayout.marginWidth = 0;
filterLayout.horizontalSpacing = 3;
fFilterComposite.setLayout(filterLayout);
fFilterComposite.setFont(parent.getFont());
fFilterComposite.setBackground(parent.getBackground());
/* Create Filter Controls */
createFilterControls(fFilterComposite);
fFilterComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
fFilterComposite.setBackground(parent.getBackground());
/* Set height as required */
if (fFilterToolBar != null) {
int prefContainerHeight = fFilterComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
int prefToolbarHeight = fFilterToolBar.getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
if (prefToolbarHeight >= prefContainerHeight)
((GridData) fFilterComposite.getLayoutData()).heightHint = prefToolbarHeight;
}
}
/* Create Label, Input Field and ToolBar */
private Composite createFilterControls(Composite parent) {
createFilterTarget(parent);
createFilterText(parent);
if (!Application.IS_MAC)
createClearText(parent);
if (fFilterToolBar != null) {
fFilterToolBar.update(false);
fFilterToolBar.getControl().setVisible(false);
}
return parent;
}
/* ToolBar to control SearchTarget */
private void createFilterTarget(Composite parent) {
final ToolBarManager filterTargetManager = new ToolBarManager(SWT.FLAT);
IAction filterTargetAction = new Action("", IAction.AS_DROP_DOWN_MENU) { //$NON-NLS-1$
@Override
public void run() {
OwlUI.positionDropDownMenu(this, filterTargetManager);
}
@Override
public String getId() {
return BAR_ACTION_ID;
}
};
filterTargetAction.setMenuCreator(new ContextMenuCreator() {
@Override
public Menu createMenu(Control parent) {
Menu menu = new Menu(parent);
/* Search on: Name */
final MenuItem searchName = new MenuItem(menu, SWT.RADIO);
searchName.setText(Messages.BookMarkSearchbar_NAME);
searchName.setSelection(BookMarkFilter.SearchTarget.NAME == fPatternFilter.getSearchTarget());
searchName.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (searchName.getSelection() && fPatternFilter.getSearchTarget() != BookMarkFilter.SearchTarget.NAME)
doSearch(BookMarkFilter.SearchTarget.NAME);
}
});
/* Search on: Link */
final MenuItem searchLink = new MenuItem(menu, SWT.RADIO);
searchLink.setText(Messages.BookMarkSearchbar_LINK);
searchLink.setSelection(BookMarkFilter.SearchTarget.LINK == fPatternFilter.getSearchTarget());
searchLink.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (searchLink.getSelection() && fPatternFilter.getSearchTarget() != BookMarkFilter.SearchTarget.LINK)
doSearch(BookMarkFilter.SearchTarget.LINK);
}
});
return menu;
}
});
filterTargetAction.setImageDescriptor(OwlUI.getImageDescriptor("icons/etool16/find.gif")); //$NON-NLS-1$
filterTargetManager.add(filterTargetAction);
filterTargetManager.createControl(parent);
filterTargetManager.getControl().setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, true));
}
private void doSearch(SearchTarget target) {
fPatternFilter.setSearchTarget(target);
fFilterText.setFocus();
/* Set Message */
if (fPatternFilter.getSearchTarget() == SearchTarget.NAME)
fFilterText.setMessage(Messages.BookMarkSearchbar_NAME);
else
fFilterText.setMessage(Messages.BookMarkSearchbar_LINK);
if (fFilterText.getText().length() > 0)
textChanged();
}
/* Input Field for typing into the Filter */
private void createFilterText(Composite parent) {
fFilterText = new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.CANCEL);
fFilterText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
/* Set Message */
if (fPatternFilter.getSearchTarget() == SearchTarget.NAME)
fFilterText.setMessage(Messages.BookMarkSearchbar_NAME);
else
fFilterText.setMessage(Messages.BookMarkSearchbar_LINK);
/* Register this Input Field to Context Service */
Controller.getDefault().getContextService().registerInputField(fFilterText);
/* Override Accessible */
fFilterText.getAccessible().addAccessibleListener(new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
String filterTextString = fFilterText.getText();
if (filterTextString.length() == 0)
e.result = fInitialText;
else
e.result = filterTextString;
}
});
/* Select All on Focus if input matches Initial Text */
fFilterText.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
JobRunner.runInUIThread(0, true, fFilterText, new Runnable() {
public void run() {
if (fInitialText.equals(fFilterText.getText().trim()))
fFilterText.selectAll();
}
});
/* Enable some Edit Actions */
fViewSite.getActionBars().getGlobalActionHandler(ActionFactory.CUT.getId()).setEnabled(true);
fViewSite.getActionBars().getGlobalActionHandler(ActionFactory.COPY.getId()).setEnabled(true);
fViewSite.getActionBars().getGlobalActionHandler(ActionFactory.PASTE.getId()).setEnabled(true);
}
@Override
public void focusLost(FocusEvent e) {
/* Disable some Edit Actions */
fViewSite.getActionBars().getGlobalActionHandler(ActionFactory.CUT.getId()).setEnabled(false);
fViewSite.getActionBars().getGlobalActionHandler(ActionFactory.COPY.getId()).setEnabled(false);
fViewSite.getActionBars().getGlobalActionHandler(ActionFactory.PASTE.getId()).setEnabled(false);
}
});
/* Focus Tree on Arrow Up or Down */
fFilterText.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
/* Pressed ESC */
if (e.character == SWT.ESC) {
setFilterText(""); //$NON-NLS-1$
fViewer.getTree().setFocus();
return;
}
/* Pressed Arrow Down or Up */
boolean hasItems = fViewer.getTree().getItemCount() > 0;
if (hasItems && (e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.ARROW_UP))
fViewer.getTree().setFocus();
else if (e.character == SWT.CR)
return;
}
});
/* Handle the CR Key Pressed */
fFilterText.addTraverseListener(new TraverseListener() {
public void keyTraversed(TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_RETURN) {
e.doit = false;
/* Results available */
if (fViewer.getTree().getItemCount() > 0) {
boolean hasFocus = fViewer.getTree().setFocus();
boolean textChanged = !fInitialText.equals(fFilterText.getText().trim());
/* Select the first matching Item */
if (hasFocus && textChanged && fFilterText.getText().trim().length() > 0) {
TreeItem item = getFirstMatchingItem(fViewer.getTree().getItems());
if (item != null) {
fViewer.getTree().setSelection(new TreeItem[] { item });
ISelection sel = fViewer.getSelection();
fViewer.setSelection(sel, true);
}
}
}
}
}
});
/* Update Filter on Modified Input */
fFilterText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
textChanged();
}
});
}
/* Create the Job that refreshes the TreeViewer */
private void createRefreshJob() {
fRefreshJob = new WorkbenchJob("") {//$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
/* Tree Disposed */
if (fViewer.getControl().isDisposed())
return Status.CANCEL_STATUS;
/* Get the Filter Pattern */
String text = fFilterText != null ? fFilterText.getText() : null;
if (text == null)
return Status.OK_STATUS;
/* Check if the Initial Text was set */
boolean initial = fInitialText != null && fInitialText.equals(text);
if (initial)
fPatternFilter.setPattern(null);
else
fPatternFilter.setPattern(text);
try {
fViewer.getControl().getParent().setRedraw(false);
/* Remember Expanded Elements if not yet done */
if (fExpandedElements == null)
fExpandedElements = fViewer.getExpandedElements();
/* Remember Selected Elements if present */
IStructuredSelection sel = (IStructuredSelection) fViewer.getSelection();
if (!sel.isEmpty())
fSelectedElements = sel.toArray();
/* Refresh Tree */
BusyIndicator.showWhile(getDisplay(), new Runnable() {
public void run() {
fViewer.refresh(false);
}
});
/* Restore Expanded Elements and Selection when Filter is disabled */
if (text.length() == 0) {
/* Restore Expansion */
fViewer.collapseAll();
for (Object element : fExpandedElements) {
fViewer.setExpandedState(element, true);
}
/* Restore Selection */
if (fSelectedElements != null)
fViewer.setSelection(new StructuredSelection(fSelectedElements), true);
/* Clear Fields */
fExpandedElements = null;
fSelectedElements = null;
}
/*
* Expand elements one at a time. After each is expanded, check to see
* if the filter text has been modified. If it has, then cancel the
* refresh job so the user doesn't have to endure expansion of all the
* nodes.
*/
if (text.length() > 0 && !initial) {
IStructuredContentProvider provider = (IStructuredContentProvider) fViewer.getContentProvider();
Object[] elements = provider.getElements(fViewer.getInput());
for (Object element : elements) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
fViewer.expandToLevel(element, AbstractTreeViewer.ALL_LEVELS);
}
/* Make Sure to show the First Item */
TreeItem[] items = fViewer.getTree().getItems();
if (items.length > 0)
fViewer.getTree().showItem(items[0]);
/* Enable Toolbar to allow resetting the Filter */
setToolBarVisible(true);
}
/* Disable Toolbar - No Filter is currently activated */
else {
setToolBarVisible(false);
}
}
/* Done updating the tree - set redraw back to true */
finally {
fViewer.getControl().getParent().setRedraw(true);
}
return Status.OK_STATUS;
}
};
fRefreshJob.setSystem(true);
/* Cancel the Job once the Tree got disposed */
fViewer.getControl().addDisposeListener(new DisposeListener() {
public void widgetDisposed(org.eclipse.swt.events.DisposeEvent e) {
fRefreshJob.cancel();
};
});
}
Text getControl() {
return fFilterText;
}
TreeItem getFirstMatchingItem(TreeItem[] items) {
for (TreeItem element : items) {
if (fPatternFilter.isLeafMatch(fViewer, element.getData()) && fPatternFilter.isElementSelectable(element.getData()))
return element;
return getFirstMatchingItem(element.getItems());
}
return null;
}
void setFilterText(String string) {
if (fFilterText != null) {
fFilterText.setText(string);
fFilterText.selectAll();
}
}
void textChanged() {
fRefreshJob.cancel();
fRefreshJob.schedule(fFilterText.getText().length() != 0 ? FILTER_DELAY : 0);
}
void setToolBarVisible(boolean visible) {
if (fFilterToolBar != null && ((GridData) fFilterToolBar.getControl().getLayoutData()).exclude == visible) {
((GridData) fFilterToolBar.getControl().getLayoutData()).exclude = !visible;
fFilterToolBar.getControl().setVisible(visible);
fFilterComposite.layout();
}
}
}