/*******************************************************************************
* Copyright (c) 2006 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.erlide.ui.editors.erl.outline;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlExtension;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.erlide.engine.model.IErlElement;
import org.erlide.ui.ErlideUIMessages;
import org.erlide.ui.actions.SortAction;
import org.erlide.ui.editors.erl.AbstractErlangEditor;
import org.erlide.ui.editors.erl.outline.filters.FilterDescriptor;
import org.erlide.ui.editors.util.EditorUtility;
import org.erlide.ui.navigator.ErlElementSorter;
import org.erlide.ui.util.StringMatcher;
import org.erlide.util.ErlLogger;
/**
* AbstractInfoPopupDialog
*
*/
public class QuickOutlinePopupDialog extends PopupDialog implements IInformationControl,
IInformationControlExtension, IInformationControlExtension2, DisposeListener {
TreeViewer fTreeViewer;
private final IOutlineContentCreator fOutlineContentCreator;
private final IOutlineSelectionHandler fOutlineSelectionHandler;
private Text fFilterText;
private StringMatcher fStringMatcher;
private QuickOutlineNamePatternFilter fNamePatternFilter;
private SortAction fSortAction;
private ITreeContentProvider fTreeContentProvider;
private ILabelProvider fTreeLabelProvider;
private final AbstractErlangEditor fEditor;
// private ViewerComparator fTreeViewerComparator;
// private ViewerComparator fTreeViewerDefaultComparator;
public QuickOutlinePopupDialog(final Shell parent, final int shellStyle,
final AbstractErlangEditor editor, final IOutlineContentCreator creator,
final IOutlineSelectionHandler handler) {
super(parent, shellStyle, true, true, true, true, true, null, null);
// Set outline creator
fOutlineContentCreator = creator;
// Set outline handler
fOutlineSelectionHandler = handler;
// Initialize the other fields
fEditor = editor;
initialize();
// Create all controls early to preserve the life cycle of the original
// implementation.
create();
}
/**
*
*/
private void initialize() {
setInfoText(ErlideUIMessages.QuickOutlinePopupDialog_infoTextPressEscToExit);
fFilterText = null;
fTreeViewer = null;
fStringMatcher = null;
fNamePatternFilter = null;
fSortAction = null;
fTreeContentProvider = null;
fTreeLabelProvider = null;
// fTreeViewerComparator = null;
// fTreeViewerDefaultComparator = null;
}
@Override
protected Control createDialogArea(final Composite parent) {
// Applies only to dialog body - not title. See createTitleControl
// Create an empty dialog area, if the source page is not defined
if (fOutlineContentCreator == null || fOutlineSelectionHandler == null) {
return super.createDialogArea(parent);
}
// Create the tree viewer
createUIWidgetTreeViewer(parent);
// Add listeners to the tree viewer
createUIListenersTreeViewer();
// Create the actions
createUIActions();
// Add a dispose listner
addDisposeListener(this);
// Return the tree
return fTreeViewer.getControl();
}
/**
*
*/
private void createUIActions() {
// Add sort action to dialog menu
fSortAction = new SortAction(fTreeViewer,
ErlideUIMessages.PDEMultiPageContentOutline_SortingAction_tooltip,
new ErlElementSorter(ErlElementSorter.SORT_ON_NAME),
new ErlElementSorter(ErlElementSorter.SORT_ON_EXPORT), null, false, null);
}
@Override
protected void fillDialogMenu(final IMenuManager dialogMenu) {
// Add the sort action
dialogMenu.add(fSortAction);
// Separator
dialogMenu.add(new Separator());
// Add the default actions
super.fillDialogMenu(dialogMenu);
}
/**
* @param parent
*/
private void createUIWidgetTreeViewer(final Composite parent) {
final int style = SWT.H_SCROLL | SWT.V_SCROLL;
// Create the tree
final Tree widget = new Tree(parent, style);
// Configure the layout
final GridData data = new GridData(GridData.FILL_BOTH);
data.heightHint = widget.getItemHeight() * 12;
widget.setLayoutData(data);
// Create the tree viewer
fTreeViewer = new TreeViewer(widget);
// Add member filter, don't show attributes
final FilterDescriptor filterDescriptor = FilterDescriptor
.getFilterDescriptor("attributesFilter");
fTreeViewer.addFilter(filterDescriptor.getViewerFilter());
// Add the name pattern filter
fNamePatternFilter = new QuickOutlineNamePatternFilter();
fTreeViewer.addFilter(fNamePatternFilter);
// Set the content provider
fTreeContentProvider = fOutlineContentCreator.createOutlineContentProvider();
fTreeViewer.setContentProvider(fTreeContentProvider);
// Set the label provider
fTreeLabelProvider = fOutlineContentCreator.createOutlineLabelProvider();
fTreeViewer.setLabelProvider(fTreeLabelProvider);
// Create the outline sorter (to be set on the sort action)
// fTreeViewerComparator = fOutlineContentCreator
// .createOutlineComparator();
// Set the comparator to null (sort action will be disabled initially
// because of this)
// Create the default outline sorter (Most like this will just return
// null to indicate sorting disabled
// fTreeViewerDefaultComparator = fOutlineContentCreator
// .createDefaultOutlineComparator();
// fTreeViewer.setComparator(fTreeViewerDefaultComparator);
fTreeViewer.setAutoExpandLevel(1);
fTreeViewer.setUseHashlookup(true);
fTreeViewer.setInput(fOutlineContentCreator.getOutlineInput());
}
/**
*
*/
private void createUIListenersTreeViewer() {
// Get the underlying tree widget
final Tree tree = fTreeViewer.getTree();
// Handle key events
tree.addKeyListener(new KeyListener() {
@Override
public void keyPressed(final KeyEvent e) {
if (e.character == 0x1B) {
// Dispose on ESC key press
dispose();
}
}
@Override
public void keyReleased(final KeyEvent e) {
// NO-OP
}
});
// Handle mouse clicks
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(final MouseEvent e) {
handleTreeViewerMouseUp(tree, e);
}
});
// Handle mouse move events
tree.addMouseMoveListener(new QuickOutlineMouseMoveListener(fTreeViewer));
// Handle widget selection events
tree.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(final SelectionEvent e) {
// NO-OP
}
@Override
public void widgetDefaultSelected(final SelectionEvent e) {
gotoSelectedElement();
}
});
}
/**
* @param tree
* @param e
*/
void handleTreeViewerMouseUp(final Tree tree, final MouseEvent e) {
// Ensure a selection was made, the first mouse button was
// used and the event happened in the tree
if (tree.getSelectionCount() < 1 || e.button != 1
|| !tree.equals(e.getSource())) {
return;
}
// Selection is made in the selection changed listener
final Object object = tree.getItem(new Point(e.x, e.y));
final TreeItem selection = tree.getSelection()[0];
if (selection.equals(object)) {
gotoSelectedElement();
}
}
/**
* @return
*/
private Object getSelectedElement() {
if (fTreeViewer == null) {
return null;
}
return ((IStructuredSelection) fTreeViewer.getSelection()).getFirstElement();
}
@Override
public void addDisposeListener(final DisposeListener listener) {
getShell().addDisposeListener(listener);
}
@Override
public void addFocusListener(final FocusListener listener) {
getShell().addFocusListener(listener);
}
@Override
public Point computeSizeHint() {
// Return the shell's size
// Note that it already has the persisted size if persisting is enabled.
return getShell().getSize();
}
@Override
public void dispose() {
close();
}
@Override
public boolean isFocusControl() {
if (fTreeViewer.getControl().isFocusControl() || fFilterText.isFocusControl()) {
return true;
}
return false;
}
@Override
public void removeDisposeListener(final DisposeListener listener) {
getShell().removeDisposeListener(listener);
}
@Override
public void removeFocusListener(final FocusListener listener) {
getShell().removeFocusListener(listener);
}
@Override
public void setBackgroundColor(final Color background) {
applyBackgroundColor(background, getContents());
}
@Override
public void setFocus() {
getShell().forceFocus();
fFilterText.setFocus();
}
@Override
protected Control getFocusControl() {
return fFilterText;
}
@Override
public void setForegroundColor(final Color foreground) {
applyForegroundColor(foreground, getContents());
}
@Override
public void setInformation(final String information) {
// Ignore
// See IInformationControlExtension2
}
@Override
public void setLocation(final Point location) {
/*
* If the location is persisted, it gets managed by PopupDialog - fine.
* Otherwise, the location is computed in Window#getInitialLocation,
* which will center it in the parent shell / main monitor, which is
* wrong for two reasons: - we want to center over the editor / subject
* control, not the parent shell - the center is computed via the
* initalSize, which may be also wrong since the size may have been
* updated since via min/max sizing of
* AbstractInformationControlManager. In that case, override the
* location with the one computed by the manager. Note that the call to
* constrainShellSize in PopupDialog.open will still ensure that the
* shell is entirely visible.
*/
if (!getPersistLocation() || !getPersistSize() || getDialogSettings() == null) {
getShell().setLocation(location);
}
}
@Override
public void setSize(final int width, final int height) {
getShell().setSize(width, height);
}
@Override
public void setSizeConstraints(final int maxWidth, final int maxHeight) {
// Ignore
}
@Override
public void setVisible(final boolean visible) {
if (visible) {
open();
} else {
saveDialogBounds(getShell());
getShell().setVisible(false);
}
}
@Override
public boolean hasContents() {
if (fTreeViewer == null || fTreeViewer.getInput() == null) {
return false;
}
return true;
}
@Override
public void setInput(final Object input) {
// Input comes from ErlangSourceInfoProvider.getInformation2()
// The input should be a model object of some sort
// Turn it into a structured selection and set the selection in the tree
if (input != null) {
fTreeViewer.setSelection(new StructuredSelection(input));
}
}
@Override
public void widgetDisposed(final DisposeEvent e) {
// Note: We do not reuse the dialog
fTreeViewer = null;
fFilterText = null;
}
@Override
protected Control createTitleControl(final Composite parent) {
// Applies only to dialog title - not body. See createDialogArea
// Create the text widget
createUIWidgetFilterText(parent);
// Add listeners to the text widget
createUIListenersFilterText();
// Return the text widget
return fFilterText;
}
/**
* @param parent
* @return
*/
private void createUIWidgetFilterText(final Composite parent) {
// Create the widget
fFilterText = new Text(parent, SWT.NONE);
// Set the font
final GC gc = new GC(parent);
gc.setFont(parent.getFont());
final FontMetrics fontMetrics = gc.getFontMetrics();
gc.dispose();
// Create the layout
final GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.heightHint = Dialog.convertHeightInCharsToPixels(fontMetrics, 1);
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.CENTER;
fFilterText.setLayoutData(data);
}
/**
*
*/
protected void gotoSelectedElement() {
final Object selectedElement = getSelectedElement();
ErlLogger.debug("&&>> " + selectedElement);
if (selectedElement == null) {
return;
}
dispose();
if (fEditor != null && selectedElement instanceof IErlElement) {
EditorUtility.revealInEditor(fEditor, (IErlElement) selectedElement);
}
}
/**
*
*/
private void createUIListenersFilterText() {
// Handle key events
fFilterText.addKeyListener(new KeyListener() {
@Override
public void keyPressed(final KeyEvent e) {
if (e.keyCode == 0x0D) {
// Return key was pressed
gotoSelectedElement();
} else if (e.keyCode == SWT.ARROW_DOWN) {
// Down key was pressed
fTreeViewer.getTree().setFocus();
} else if (e.keyCode == SWT.ARROW_UP) {
// Up key was pressed
fTreeViewer.getTree().setFocus();
} else if (e.character == 0x1B) {
// Escape key was pressed
dispose();
}
}
@Override
public void keyReleased(final KeyEvent e) {
// NO-OP
}
});
// Handle text modify events
fFilterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(final ModifyEvent e) {
String text = ((Text) e.widget).getText();
final int length = text.length();
if (length > 0) {
// Append a '*' pattern to the end of the text value if it
// does not have one already
if (text.charAt(length - 1) != '*') {
text = text + '*';
}
// Prepend a '*' pattern to the beginning of the text value
// if it does not have one already
if (text.charAt(0) != '*') {
text = '*' + text;
}
}
// Set and update the pattern
setMatcherString(text, true);
}
});
}
/**
* Sets the patterns to filter out for the receiver.
* <p>
* The following characters have special meaning: ? => any character * =>
* any string
* </p>
*
* @param pattern
* the pattern
* @param update
* <code>true</code> if the viewer should be updated
*/
void setMatcherString(final String pattern, final boolean update) {
if (pattern.length() == 0) {
fStringMatcher = null;
} else {
fStringMatcher = new StringMatcher(pattern, true, false);
}
// Update the name pattern filter on the tree viewer
fNamePatternFilter.setStringMatcher(fStringMatcher);
// Update the tree viewer according to the pattern
if (update) {
stringMatcherUpdated();
}
}
/**
* The string matcher has been modified. The default implementation
* refreshes the view and selects the first matched element
*/
private void stringMatcherUpdated() {
// Refresh the tree viewer to re-filter
fTreeViewer.getControl().setRedraw(false);
fTreeViewer.refresh();
fTreeViewer.expandAll();
selectFirstMatch();
fTreeViewer.getControl().setRedraw(true);
}
/**
* Selects the first element in the tree which matches the current filter
* pattern.
*/
private void selectFirstMatch() {
final Tree tree = fTreeViewer.getTree();
final Object element = findFirstMatchToPattern(tree.getItems());
if (element != null) {
fTreeViewer.setSelection(new StructuredSelection(element), true);
} else {
fTreeViewer.setSelection(StructuredSelection.EMPTY);
}
}
/**
* @param items
* @return
*/
private Object findFirstMatchToPattern(final TreeItem[] items) {
// Match the string pattern against labels
final ILabelProvider labelProvider = (ILabelProvider) fTreeViewer
.getLabelProvider();
// Process each item in the tree
for (int i = 0; i < items.length; i++) {
Object element = items[i].getData();
// Return the first element if no pattern is set
if (fStringMatcher == null) {
return element;
}
// Return the element if it matches the pattern
if (element != null) {
final String label = labelProvider.getText(element);
if (fStringMatcher.match(label)) {
return element;
}
}
// Recursively check the elements children for a match
element = findFirstMatchToPattern(items[i].getItems());
// Return the child element match if found
if (element != null) {
return element;
}
}
// No match found
return null;
}
@Override
protected Point getInitialSize() {
return new Point(400, 250);
}
}