/******************************************************************************* * Copyright (c) 2010, 2016 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.eclipse.wst.sse.ui.internal.quickoutline; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; 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.ISelection; 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.MouseMoveListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; 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.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.ui.IContentSelectionProvider; import org.eclipse.wst.sse.ui.internal.SSEUIPlugin; import org.eclipse.wst.sse.ui.internal.filter.StringMatcher; import org.eclipse.wst.sse.ui.quickoutline.AbstractQuickOutlineConfiguration; /** * Popup dialog that contains the filtering input and the outline * view of the editor's input. * * <p> * Based on {@link org.eclipse.jdt.internal.ui.text.AbstractInformationControl} * </p> * */ public class QuickOutlinePopupDialog extends PopupDialog implements IInformationControl, IInformationControlExtension, IInformationControlExtension2, DisposeListener { /** Section used for storing the dialog's size and position settings */ private static final String DIALOG_SECTION = "org.eclipse.wst.sse.ui.quick_outline"; //$NON-NLS-1$ /** Text field for entering filter patterns */ private Text fFilterText; /** Tree for presenting the information outline */ private TreeViewer fTreeViewer; /** The model to be outlined */ private IStructuredModel fModel; private ILabelProvider fLabelProvider; private ITreeContentProvider fContentProvider; private IContentSelectionProvider fSelectionProvider; private StringPatternFilter fFilter; public QuickOutlinePopupDialog(Shell parent, int shellStyle, IStructuredModel model, AbstractQuickOutlineConfiguration configuration) { super(parent, shellStyle, true, true, true, true, true, null, null); fContentProvider = configuration.getContentProvider(); fLabelProvider = configuration.getLabelProvider(); fSelectionProvider = configuration.getContentSelectionProvider(); fModel = model; create(); } protected Control createDialogArea(Composite parent) { createTreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL); addListeners(fTreeViewer.getTree()); installFilter(); return fTreeViewer.getControl(); } protected Control createTitleControl(Composite parent) { createFilterText(parent); return fFilterText; } protected void createTreeViewer(Composite parent, int style) { Tree tree = new Tree(parent, style); GridData gd = new GridData(GridData.FILL_BOTH); gd.heightHint = tree.getItemHeight() * 12; tree.setLayoutData(gd); fTreeViewer = new TreeViewer(tree); fTreeViewer.setContentProvider(fContentProvider); fTreeViewer.setLabelProvider(fLabelProvider); fTreeViewer.setAutoExpandLevel(2); fTreeViewer.setUseHashlookup(true); fTreeViewer.setInput(fModel); } protected void createFilterText(Composite parent) { fFilterText = new Text(parent, SWT.NONE); Dialog.applyDialogFont(fFilterText); GridData data= new GridData(GridData.FILL_HORIZONTAL); data.horizontalAlignment= GridData.FILL; data.verticalAlignment= GridData.CENTER; fFilterText.setLayoutData(data); fFilterText.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { if (e.keyCode == 0x0D) // return gotoSelectedElement(); if (e.keyCode == SWT.ARROW_DOWN) fTreeViewer.getTree().setFocus(); if (e.keyCode == SWT.ARROW_UP) fTreeViewer.getTree().setFocus(); if (e.character == 0x1B) // ESC dispose(); } public void keyReleased(KeyEvent e) { // do nothing } }); } protected void installFilter() { fFilter = new StringPatternFilter(); fTreeViewer.addFilter(fFilter); fFilterText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { String text = ((Text) e.widget).getText(); int length = text.length(); if (length > 0 && text.charAt(length -1 ) != '*') { text = text + '*'; } setMatcherString(text, true); } }); } protected void setMatcherString(String pattern, boolean update) { fFilter.updatePattern(pattern); if (update) stringMatcherUpdated(); } /** * The string matcher has been modified. The default implementation * refreshes the view and selects the first matched element */ protected void stringMatcherUpdated() { // refresh viewer to re-filter fTreeViewer.getControl().setRedraw(false); fTreeViewer.refresh(); fTreeViewer.expandAll(); selectFirstMatch(); fTreeViewer.getControl().setRedraw(true); } private void selectFirstMatch() { TreeItem item = findItem(fTreeViewer.getTree().getItems(), fFilter.getStringMatcher()); if (item != null) { fTreeViewer.getTree().setSelection(item); } else { fTreeViewer.setSelection(StructuredSelection.EMPTY); } } private TreeItem findItem(TreeItem[] items, StringMatcher matcher) { if (matcher == null) return items.length > 0 ? items[0] : null; int length = items.length; TreeItem result = null; for (int i = 0; i < length; i++) { if (matcher.match(items[i].getText())) return items[i]; if (items[i].getItemCount() > 0) { result = findItem(items[i].getItems(), matcher); if (result != null) return result; } } return result; } protected IDialogSettings getDialogSettings() { IDialogSettings settings= SSEUIPlugin.getDefault().getDialogSettings().getSection(DIALOG_SECTION); if (settings == null) settings = SSEUIPlugin.getDefault().getDialogSettings().addNewSection(DIALOG_SECTION); return settings; } private void gotoSelectedElement() { Object element = getSelectedElement(); dispose(); if (element != null) { ITextEditor editor = getActiveTextEditor(); if (editor != null) { editor.selectAndReveal(((IndexedRegion) element).getStartOffset(), ((IndexedRegion) element).getEndOffset() - ((IndexedRegion) element).getStartOffset()); } } } private ITextEditor getActiveTextEditor() { IWorkbench wb = PlatformUI.getWorkbench(); ITextEditor editor = null; if (wb != null) { IWorkbenchWindow ww = wb.getActiveWorkbenchWindow(); IWorkbenchPage page = ww.getActivePage(); if (page != null) { IEditorPart part = page.getActiveEditor(); if (part instanceof ITextEditor) editor = (ITextEditor) part; else editor = part != null ? (ITextEditor) part.getAdapter(ITextEditor.class) : null; } } return editor; } private Object getSelectedElement() { if (fTreeViewer == null) return null; return ((IStructuredSelection) fTreeViewer.getSelection()).getFirstElement(); } private void addListeners(final Tree tree) { tree.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { if (e.character == 0x1B) // ESC dispose(); } public void keyReleased(KeyEvent e) { // do nothing } }); tree.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { // do nothing } public void widgetDefaultSelected(SelectionEvent e) { gotoSelectedElement(); } }); /* Mouse hover */ tree.addMouseMoveListener(new MouseMoveListener() { TreeItem fLastItem= null; public void mouseMove(MouseEvent e) { if (tree.equals(e.getSource())) { Object o= tree.getItem(new Point(e.x, e.y)); if (o instanceof TreeItem) { Rectangle clientArea = tree.getClientArea(); if (!o.equals(fLastItem)) { fLastItem= (TreeItem)o; tree.setSelection(new TreeItem[] { fLastItem }); } else if (e.y - clientArea.y < tree.getItemHeight() / 4) { // Scroll up Point p= tree.toDisplay(e.x, e.y); Item item= fTreeViewer.scrollUp(p.x, p.y); if (item instanceof TreeItem) { fLastItem= (TreeItem)item; tree.setSelection(new TreeItem[] { fLastItem }); } } else if (clientArea.y + clientArea.height - e.y < tree.getItemHeight() / 4) { // Scroll down Point p= tree.toDisplay(e.x, e.y); Item item= fTreeViewer.scrollDown(p.x, p.y); if (item instanceof TreeItem) { fLastItem= (TreeItem)item; tree.setSelection(new TreeItem[] { fLastItem }); } } } } } }); tree.addMouseListener(new MouseAdapter() { public void mouseUp(MouseEvent e) { if (tree.getSelectionCount() < 1) return; if (e.button != 1) return; if (tree.equals(e.getSource())) { Object o= tree.getItem(new Point(e.x, e.y)); TreeItem selection= tree.getSelection()[0]; if (selection.equals(o)) gotoSelectedElement(); } } }); } public void addDisposeListener(DisposeListener listener) { getShell().addDisposeListener(listener); } public void addFocusListener(FocusListener listener) { getShell().addFocusListener(listener); } public Point computeSizeHint() { return getShell().getSize(); } public void dispose() { close(); } public boolean isFocusControl() { return getShell().getDisplay().getActiveShell() == getShell(); } public void removeDisposeListener(DisposeListener listener) { getShell().removeDisposeListener(listener); } public void removeFocusListener(FocusListener listener) { getShell().removeFocusListener(listener); } public void setBackgroundColor(Color background) { applyBackgroundColor(background, getContents()); } public void setFocus() { getShell().forceFocus(); fFilterText.setFocus(); } public void setForegroundColor(Color foreground) { applyForegroundColor(foreground, getContents()); } public void setInformation(String information) { // nothing to do } public void setLocation(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() || getDialogSettings() == null) getShell().setLocation(location); } public void setSize(int width, int height) { getShell().setSize(width, height); } public void setSizeConstraints(int maxWidth, int maxHeight) { } public void setVisible(boolean visible) { if (visible) { open(); } else { saveDialogBounds(getShell()); getShell().setVisible(false); } } public boolean hasContents() { return fTreeViewer != null && fTreeViewer.getInput() != null; } public void setInput(Object input) { if (!(input instanceof ISelection)) { fTreeViewer.setSelection(new StructuredSelection(input)); } else { if (fSelectionProvider != null) { ISelection selection = fSelectionProvider.getSelection(fTreeViewer, (ISelection) input); fTreeViewer.setSelection(selection); } else { fTreeViewer.setSelection((ISelection) input); } } } public void widgetDisposed(DisposeEvent e) { fTreeViewer = null; fFilterText = null; fModel = null; } protected void fillDialogMenu(IMenuManager dialogMenu) { // Add custom actions super.fillDialogMenu(dialogMenu); } }