| /*=============================================================================# |
| # Copyright (c) 2007, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ltk.ui.sourceediting; |
| |
| import static org.eclipse.statet.ecommons.ui.actions.UIActions.ADDITIONS_GROUP_ID; |
| |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.content.IContentType; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.viewers.IPostSelectionProvider; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IActionBars; |
| import org.eclipse.ui.IPageLayout; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.editors.text.IEncodingSupport; |
| import org.eclipse.ui.part.IPageSite; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTarget; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.services.IServiceLocator; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; |
| import org.eclipse.ui.views.contentoutline.IContentOutlinePage; |
| |
| import org.eclipse.statet.ecommons.ui.SharedUIResources; |
| import org.eclipse.statet.ecommons.ui.actions.HandlerCollection; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.ui.workbench.AbstractEditorOutlinePage; |
| |
| import org.eclipse.statet.internal.ltk.ui.EditingMessages; |
| import org.eclipse.statet.ltk.ast.core.AstInfo; |
| import org.eclipse.statet.ltk.core.ISourceModelStamp; |
| import org.eclipse.statet.ltk.model.core.IModelElementDelta; |
| import org.eclipse.statet.ltk.model.core.elements.IModelElement; |
| import org.eclipse.statet.ltk.model.core.elements.IModelElement.Filter; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceStructElement; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnitModelInfo; |
| import org.eclipse.statet.ltk.ui.IModelElementInputListener; |
| import org.eclipse.statet.ltk.ui.ISelectionWithElementInfoListener; |
| import org.eclipse.statet.ltk.ui.LTKInputData; |
| |
| |
| /** |
| * Abstract content outline page for a {@link SourceEditor1} with model info. |
| */ |
| public abstract class SourceEditor1OutlinePage extends AbstractEditorOutlinePage |
| implements IContentOutlinePage, IAdaptable, ISourceEditorAssociated, |
| IShowInSource, IShowInTargetList, IShowInTarget, |
| IPostSelectionProvider, IModelElementInputListener { |
| |
| |
| protected class OutlineContent implements OutlineContentProvider.IOutlineContent { |
| |
| |
| public OutlineContent() { |
| } |
| |
| |
| @Override |
| public ISourceUnitModelInfo getModelInfo(final Object input) { |
| return SourceEditor1OutlinePage.this.getModelInfo(input); |
| } |
| @Override |
| public Filter getContentFilter() { |
| return SourceEditor1OutlinePage.this.getContentFilter(); |
| } |
| |
| } |
| |
| public class AstContentProvider extends OutlineContentProvider { |
| |
| |
| public AstContentProvider() { |
| super(new OutlineContent()); |
| } |
| |
| |
| @Override |
| public ISourceModelStamp getStamp(final Object inputElement) { |
| if (inputElement instanceof ISourceUnit) { |
| final AstInfo ast= ((ISourceUnit) inputElement).getAstInfo(SourceEditor1OutlinePage.this.mainType, false, null); |
| if (ast != null) { |
| return ast.getStamp(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Object[] getElements(final Object inputElement) { |
| if (inputElement instanceof ISourceUnit) { |
| final AstInfo ast= ((ISourceUnit) inputElement).getAstInfo(SourceEditor1OutlinePage.this.mainType, false, null); |
| if (ast != null) { |
| SourceEditor1OutlinePage.this.currentModelStamp= ast.getStamp(); |
| return new Object[] { ast.getRoot() }; |
| } |
| } |
| return new Object[0]; |
| } |
| |
| } |
| |
| |
| /** |
| * @deprecated use {@link AbstractToggleHandler} |
| */ |
| @Deprecated |
| protected abstract class ToggleAction extends Action { |
| |
| private final String settingsKey; |
| private final int time; |
| |
| public ToggleAction(final String checkSettingsKey, final boolean checkSettingsDefault, |
| final int expensive) { |
| assert (checkSettingsKey != null); |
| |
| this.settingsKey= checkSettingsKey; |
| this.time= expensive; |
| |
| final IDialogSettings settings= getDialogSettings(); |
| final boolean on= (settings.get(this.settingsKey) == null) ? |
| checkSettingsDefault : getDialogSettings().getBoolean(this.settingsKey); |
| setChecked(on); |
| configure(on); |
| } |
| |
| protected void init() { |
| } |
| |
| @Override |
| public void run() { |
| final Runnable runnable= new Runnable() { |
| @Override |
| public void run() { |
| final boolean on= isChecked(); |
| configure(on); |
| getDialogSettings().put(ToggleAction.this.settingsKey, on); |
| } |
| }; |
| if (this.time == 0) { |
| runnable.run(); |
| } |
| else { |
| BusyIndicator.showWhile(Display.getCurrent(), runnable); |
| } |
| } |
| |
| protected abstract void configure(boolean on); |
| |
| } |
| |
| private class SyncWithEditorAction extends ToggleAction implements ISelectionWithElementInfoListener { |
| |
| public SyncWithEditorAction() { |
| super("sync.editor", true, 0); //$NON-NLS-1$ |
| setText(EditingMessages.SyncWithEditor_label); |
| setImageDescriptor(SharedUIResources.getImages().getDescriptor(SharedUIResources.LOCTOOL_SYNCHRONIZED_IMAGE_ID)); |
| } |
| |
| @Override |
| protected void configure(final boolean on) { |
| if (on) { |
| SourceEditor1OutlinePage.this.editor.addPostSelectionWithElementInfoListener(this); |
| } |
| else { |
| SourceEditor1OutlinePage.this.editor.removePostSelectionWithElementInfoListener(this); |
| } |
| } |
| |
| @Override |
| public void inputChanged() { |
| } |
| |
| @Override |
| public void stateChanged(final LTKInputData state) { |
| if (!state.isStillValid()) { |
| return; |
| } |
| if (!isUpToDate(state.getInputInfo().getStamp())) { |
| elementUpdatedInfo(state.getInputElement(), null); |
| } |
| UIAccess.getDisplay().syncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (state.isStillValid() && isChecked()) { |
| select(state.getModelSelection()); |
| } |
| } |
| }); |
| } |
| |
| } |
| |
| |
| private final SourceEditor1 editor; |
| private final String mainType; |
| private OutlineContentProvider contentProvider; |
| |
| private ISourceModelStamp currentModelStamp; |
| |
| private IModelElement inputUnit; |
| |
| private SyncWithEditorAction syncWithEditorAction; |
| |
| |
| public SourceEditor1OutlinePage(final SourceEditor1 editor, final String mainType, final String contextMenuId) { |
| super(contextMenuId); |
| if (editor == null) { |
| throw new NullPointerException(); |
| } |
| if (mainType == null) { |
| throw new NullPointerException(); |
| } |
| this.editor= editor; |
| this.mainType= mainType; |
| } |
| |
| |
| @Override |
| public void init(final IPageSite pageSite) { |
| super.init(pageSite); |
| pageSite.setSelectionProvider(this); |
| } |
| |
| protected boolean isUpToDate(final ISourceModelStamp stamp) { |
| final ISourceModelStamp current= this.currentModelStamp; |
| return (current != null && current.equals(stamp)); |
| } |
| |
| protected IModelElement.Filter getContentFilter() { |
| return null; |
| } |
| |
| @Override |
| public void createControl(final Composite parent) { |
| super.createControl(parent); |
| |
| this.editor.getModelInputProvider().addListener(this); |
| getViewer().setInput(this.inputUnit); |
| } |
| |
| protected OutlineContentProvider createContentProvider() { |
| return new OutlineContentProvider(new OutlineContent()); |
| } |
| |
| @Override |
| protected void configureViewer(final TreeViewer viewer) { |
| this.contentProvider= createContentProvider(); |
| viewer.setContentProvider(this.contentProvider); |
| } |
| |
| |
| @Override |
| protected void initActions(final IServiceLocator serviceLocator, final HandlerCollection handlers) { |
| super.initActions(serviceLocator, handlers); |
| |
| this.syncWithEditorAction= new SyncWithEditorAction(); |
| } |
| |
| @Override |
| protected void contributeToActionBars(final IServiceLocator serviceLocator, |
| final IActionBars actionBars, final HandlerCollection handlers) { |
| super.contributeToActionBars(serviceLocator, actionBars, handlers); |
| |
| actionBars.setGlobalActionHandler(ITextEditorActionConstants.UNDO, this.editor.getAction(ITextEditorActionConstants.UNDO)); |
| actionBars.setGlobalActionHandler(ITextEditorActionConstants.REDO, this.editor.getAction(ITextEditorActionConstants.REDO)); |
| |
| // actionBars.setGlobalActionHandler(ITextEditorActionConstants.NEXT, this.editor.getAction(ITextEditorActionConstants.NEXT)); |
| actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION, this.editor.getAction(ITextEditorActionConstants.NEXT)); |
| // actionBars.setGlobalActionHandler(ITextEditorActionConstants.PREVIOUS, this.editor.getAction(ITextEditorActionConstants.PREVIOUS)); |
| actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION, this.editor.getAction(ITextEditorActionConstants.PREVIOUS)); |
| |
| final IMenuManager menuManager= actionBars.getMenuManager(); |
| |
| menuManager.add(this.syncWithEditorAction); |
| |
| final IContextService service= serviceLocator.getService(IContextService.class); |
| service.activateContext("org.eclipse.statet.ltk.contexts.EditSource1MenuSet"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected void contextMenuAboutToShow(final IMenuManager m) { |
| final Separator additions= new Separator(ADDITIONS_GROUP_ID); |
| m.add(additions); |
| } |
| |
| |
| @Override |
| public void elementChanged(final IModelElement element) { |
| this.inputUnit= element; |
| this.currentModelStamp= null; |
| final TreeViewer viewer= getViewer(); |
| if (UIAccess.isOkToUse(viewer)) { |
| viewer.setInput(this.inputUnit); |
| } |
| } |
| |
| @Override |
| public void elementInitialInfo(final IModelElement element) { |
| elementUpdatedInfo(element, null); |
| } |
| |
| @Override |
| public void elementUpdatedInfo(final IModelElement element, final IModelElementDelta delta) { |
| if (element != this.inputUnit || (element == null && this.inputUnit == null)) { |
| return; |
| } |
| final Display display= UIAccess.getDisplay(); |
| display.syncExec(new Runnable() { |
| @Override |
| public void run() { |
| final TreeViewer viewer= getViewer(); |
| |
| if (element != SourceEditor1OutlinePage.this.inputUnit |
| || !UIAccess.isOkToUse(viewer) |
| || isUpToDate(SourceEditor1OutlinePage.this.contentProvider.getStamp(element)) ) { |
| return; |
| } |
| beginIgnoreSelection(); |
| try { |
| viewer.refresh(true); |
| } |
| finally { |
| endIgnoreSelection(false); |
| } |
| } |
| }); |
| } |
| |
| protected ISourceUnitModelInfo getModelInfo(final Object input) { |
| if (input instanceof ISourceUnit) { |
| return ((ISourceUnit) input).getModelInfo(this.mainType, 0, null); |
| } |
| return null; |
| } |
| |
| |
| @Override |
| public void dispose() { |
| this.editor.getModelInputProvider().removeListener(this); |
| this.editor.handleOutlinePageClosed(); |
| |
| super.dispose(); |
| } |
| |
| |
| @Override |
| protected void selectInEditor(final ISelection selection) { |
| this.editor.setSelection(selection, this.syncWithEditorAction); |
| } |
| |
| protected void select(ISourceStructElement element) { |
| final TreeViewer viewer= getViewer(); |
| if (UIAccess.isOkToUse(viewer)) { |
| beginIgnoreSelection(); |
| try { |
| final Filter filter= getContentFilter(); |
| Object selectedElement= null; |
| final IStructuredSelection currentSelection= ((IStructuredSelection) viewer.getSelection()); |
| if (currentSelection.size() == 1) { |
| selectedElement= currentSelection.getFirstElement(); |
| } |
| while (element != null |
| && (element.getElementType() & IModelElement.MASK_C2) != IModelElement.C2_SOURCE_FILE) { |
| if (selectedElement != null && element.equals(selectedElement)) { |
| return; |
| } |
| if (filter == null || filter.include(element)) { |
| selectedElement= null; |
| viewer.setSelection(new StructuredSelection(element), true); |
| if (!viewer.getSelection().isEmpty()) { |
| return; |
| } |
| } |
| final IModelElement parent= element.getSourceParent(); |
| if (parent instanceof ISourceStructElement) { |
| element= (ISourceStructElement) parent; |
| continue; |
| } |
| else { |
| break; |
| } |
| } |
| if (!viewer.getSelection().isEmpty()) { |
| viewer.setSelection(StructuredSelection.EMPTY); |
| } |
| } |
| finally { |
| endIgnoreSelection(true); |
| } |
| } |
| } |
| |
| |
| @Override |
| public ShowInContext getShowInContext() { |
| return new ShowInContext(this.editor.getEditorInput(), null); |
| } |
| |
| @Override |
| public String[] getShowInTargetIds() { |
| return new String[] { IPageLayout.ID_PROJECT_EXPLORER }; |
| } |
| |
| @Override |
| public boolean show(final ShowInContext context) { |
| final IModelElement inputUnit= this.inputUnit; |
| final ISelection selection= context.getSelection(); |
| if (selection instanceof LTKInputData) { |
| final LTKInputData data= (LTKInputData) selection; |
| data.update(); |
| if (inputUnit.equals(data.getInputElement())) { |
| select(data.getModelSelection()); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| @Override |
| public ISourceEditor getSourceEditor() { |
| return this.editor; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> T getAdapter(final Class<T> adapterType) { |
| if (adapterType == ISourceEditorAssociated.class) { |
| return (T) this; |
| } |
| if (adapterType == IEncodingSupport.class) { |
| return this.editor.getAdapter((Class<T>) IEncodingSupport.class); |
| } |
| if (adapterType == IContentType.class) { |
| return (T) this.editor.getContentType(); |
| } |
| return null; |
| } |
| |
| } |