diff options
author | Thomas Wolf | 2016-12-06 18:05:43 +0000 |
---|---|---|
committer | Matthias Sohn | 2016-12-12 22:29:07 +0000 |
commit | d7a674e071bd8dfdcfa7110ea54d520ef43ac8be (patch) | |
tree | 7c5bad0914ce2409a2de2772c3075a46b136df24 | |
parent | 63485929cf9affdf82df44addd31212f4854c6cb (diff) | |
download | egit-d7a674e071bd8dfdcfa7110ea54d520ef43ac8be.tar.gz egit-d7a674e071bd8dfdcfa7110ea54d520ef43ac8be.tar.xz egit-d7a674e071bd8dfdcfa7110ea54d520ef43ac8be.zip |
Add an outline page for DiffEditorPage
Introduce a MultiPageEditorContentOutlinePage for the CommitEditor
that manages outline pages for nested editors. Give the nested
DiffEditorPage its own fully functional outline page. The outline
page is linked to the editor page: its selection updates when the
caret's position in the editor moves between file diffs. It also
has a simple context menu giving access to actions for opening the
files in an editor, or for showing a file diff in a compare editor.
Change-Id: Iddd72105b86c3a8c5d31e6fa874bb470ad1f964c
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
8 files changed, 762 insertions, 5 deletions
diff --git a/org.eclipse.egit.ui/META-INF/MANIFEST.MF b/org.eclipse.egit.ui/META-INF/MANIFEST.MF index 07ad37a5cb..6b9207fd1a 100644 --- a/org.eclipse.egit.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.egit.ui/META-INF/MANIFEST.MF @@ -28,7 +28,8 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.7.0,4.0.0)", org.eclipse.debug.ui;bundle-version="[3.4.0,4.0.0)";resolution:=optional, org.eclipse.e4.ui.css.swt.theme;resolution:=optional, org.eclipse.jdt.core;bundle-version="[3.4.0,4.0.0)";resolution:=optional, - org.eclipse.jdt.ui;bundle-version="[3.4.0,4.0.0)";resolution:=optional + org.eclipse.jdt.ui;bundle-version="[3.4.0,4.0.0)";resolution:=optional, + org.eclipse.ui.views;bundle-version="[3.4.0,4.0.0)" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.eclipse.egit.core;version="[4.6.0,4.7.0)", diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java index 6b3d59d757..8b1cd16fe4 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java @@ -4325,6 +4325,9 @@ public class UIText extends NLS { public static String StashEditorPage_LabelParent2; /** */ + public static String MultiPageEditorContentOutlinePage_NoOutline; + + /** */ public static String Header_contextMenu_copy; /** */ diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java index 8970773b0e..540dfabba0 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java @@ -73,6 +73,7 @@ import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.part.MultiPageEditorSite; import org.eclipse.ui.part.ShowInContext; import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; /** * Editor class to view a commit in a form editor. @@ -142,6 +143,8 @@ public class CommitEditor extends SharedHeaderFormEditor implements return openQuiet(commit, true); } + private IContentOutlinePage outlinePage; + private CommitEditorPage commitPage; private DiffEditorPage diffPage; @@ -364,9 +367,10 @@ public class CommitEditor extends SharedHeaderFormEditor implements @Override public Object getAdapter(Class adapter) { if (RepositoryCommit.class == adapter) { - return AdapterUtils.adapt(getEditorInput(), adapter); + return AdapterUtils.adapt(getEditorInput(), RepositoryCommit.class); + } else if (IContentOutlinePage.class == adapter) { + return getOutlinePage(); } - return super.getAdapter(adapter); } @@ -432,6 +436,13 @@ public class CommitEditor extends SharedHeaderFormEditor implements } } + private IContentOutlinePage getOutlinePage() { + if (outlinePage == null) { + outlinePage = new MultiPageEditorContentOutlinePage(this); + } + return outlinePage; + } + @Override public ShowInContext getShowInContext() { IFormPage currentPage = getActivePageInstance(); diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java new file mode 100644 index 0000000000..e27a125e26 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java @@ -0,0 +1,355 @@ +/******************************************************************************* + * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch> + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.commit; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.Path; +import org.eclipse.egit.ui.internal.UIText; +import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion; +import org.eclipse.egit.ui.internal.history.FileDiff; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.util.SafeRunnable; +import org.eclipse.jface.viewers.IOpenListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.OpenEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +/** + * A {@link NestedContentOutlinePage} for the {DiffEditorPage}, displaying an + * outline for {@link DiffDocument}s. + */ +public class DiffEditorOutlinePage extends NestedContentOutlinePage { + + private IDocument input; + + private CopyOnWriteArrayList<IOpenListener> openListeners = new CopyOnWriteArrayList<>(); + + private ISelection selection; + + @Override + public void createControl(Composite parent) { + super.createControl(parent); + TreeViewer viewer = getTreeViewer(); + viewer.setAutoExpandLevel(2); + viewer.setContentProvider(new DiffContentProvider()); + viewer.setLabelProvider(new DiffLabelProvider()); + viewer.addOpenListener(event -> fireOpenEvent(event)); + if (input != null) { + viewer.setInput(input); + } + createContextMenu(viewer); + if (selection != null) { + viewer.setSelection(selection); + } + } + + /** + * Sets the input of the page to the given {@link IDocument}. + * + * @param input + * to set for the page + */ + public void setInput(IDocument input) { + this.input = input; + TreeViewer viewer = getTreeViewerChecked(); + if (viewer != null) { + viewer.setInput(input); + } + } + + @Override + public void setSelection(ISelection selection) { + this.selection = selection; + TreeViewer viewer = getTreeViewerChecked(); + if (viewer != null) { + super.setSelection(selection); + } + } + + private TreeViewer getTreeViewerChecked() { + TreeViewer viewer = getTreeViewer(); + if (viewer == null || viewer.getControl() == null + || viewer.getControl().isDisposed()) { + return null; + } + return viewer; + } + + /** + * Adds a listener for selection-open in this page's viewer. Has no effect + * if an identical listener is already registered. + * + * @param listener + * to add to the page'sviewer + */ + public void addOpenListener(IOpenListener listener) { + openListeners.addIfAbsent(listener); + } + + /** + * Removes the given open listener from this page's viewer. Has no effect if + * the listener is not registered. + * + * @param listener + * to remove from this page's viewer. + */ + public void removeOpenListener(IOpenListener listener) { + openListeners.remove(listener); + } + + private void fireOpenEvent(OpenEvent event) { + for (IOpenListener listener : openListeners) { + SafeRunnable.run(new SafeRunnable() { + + @Override + public void run() { + listener.open(event); + } + }); + } + } + + private void createContextMenu(TreeViewer viewer) { + MenuManager contextMenu = new MenuManager(); + contextMenu.setRemoveAllWhenShown(true); + contextMenu.addMenuListener(menuManager -> { + setFocus(); + Collection<FileDiffRegion> selected = getSelectedFileDiffs(); + if (selected.isEmpty()) { + return; + } + Collection<FileDiffRegion> haveNew = selected.stream() + .filter(diff -> !diff.getDiff().getChange() + .equals(DiffEntry.ChangeType.DELETE)) + .collect(Collectors.toList()); + Collection<FileDiffRegion> haveOld = selected.stream() + .filter(diff -> !diff.getDiff().getChange() + .equals(DiffEntry.ChangeType.ADD)) + .collect(Collectors.toList()); + Collection<FileDiffRegion> existing = haveNew.stream() + .filter(diff -> new Path(diff.getRepository().getWorkTree() + .getAbsolutePath()) + .append(diff.getDiff().getNewPath()) + .toFile().exists()) + .collect(Collectors.toList()); + if (!existing.isEmpty()) { + menuManager.add(new Action( + UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) { + + @Override + public void run() { + for (FileDiffRegion fileDiff : existing) { + File file = new Path(fileDiff.getRepository() + .getWorkTree().getAbsolutePath()).append( + fileDiff.getDiff().getNewPath()) + .toFile(); + DiffViewer.openFileInEditor(file, -1); + } + } + }); + } + if (!haveNew.isEmpty()) { + menuManager.add(new Action( + UIText.CommitFileDiffViewer_OpenInEditorMenuLabel) { + + @Override + public void run() { + for (FileDiffRegion fileDiff : haveNew) { + DiffViewer.openInEditor(fileDiff.getRepository(), + fileDiff.getDiff(), DiffEntry.Side.NEW, -1); + } + } + }); + } + if (!haveOld.isEmpty()) { + menuManager.add(new Action( + UIText.CommitFileDiffViewer_OpenPreviousInEditorMenuLabel) { + + @Override + public void run() { + for (FileDiffRegion fileDiff : haveOld) { + DiffViewer.openInEditor(fileDiff.getRepository(), + fileDiff.getDiff(), DiffEntry.Side.OLD, -1); + } + } + }); + } + if (selected.size() == 1) { + menuManager.add(new Separator()); + menuManager.add(new Action( + UIText.CommitFileDiffViewer_CompareMenuLabel) { + + @Override + public void run() { + FileDiffRegion fileDiff = selected.iterator().next(); + DiffViewer.showTwoWayFileDiff(fileDiff.getRepository(), + fileDiff.getDiff()); + } + }); + } + }); + Menu menu = contextMenu.createContextMenu(viewer.getTree()); + viewer.getTree().setMenu(menu); + } + + private Collection<FileDiffRegion> getSelectedFileDiffs() { + ISelection currentSelection = getSelection(); + List<FileDiffRegion> result = new ArrayList<>(); + if (!currentSelection.isEmpty() && currentSelection instanceof StructuredSelection) { + for (Object selected : ((StructuredSelection) currentSelection).toList()) { + if (selected instanceof FileDiffRegion + && !((FileDiffRegion) selected).getDiff() + .isSubmodule()) { + result.add((FileDiffRegion) selected); + } + } + } + return result; + } + + private static class DiffContentProvider implements ITreeContentProvider { + + private static final Object[] NOTHING = new Object[0]; + + public static class Folder { + public String name; + + public List<FileDiffRegion> files; + } + + private HashMap<String, Folder> folders = new LinkedHashMap<>(); + + private Map<FileDiffRegion, Folder> parents = new HashMap<>(); + + @Override + public Object[] getElements(Object inputElement) { + folders.clear(); + parents.clear(); + if (inputElement instanceof DiffDocument) { + computeFolders(((DiffDocument) inputElement).getFileRegions()); + return folders.values().toArray(); + } + return NOTHING; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof Folder) { + return ((Folder) parentElement).files.toArray(); + } + return NOTHING; + } + + @Override + public Object getParent(Object element) { + if (element instanceof FileDiffRegion) { + return parents.get(element); + } + return null; + } + + @Override + public boolean hasChildren(Object element) { + return (element instanceof Folder); + } + + private void computeFolders(FileDiffRegion[] ranges) { + for (FileDiffRegion range : ranges) { + String path = range.getDiff().getPath(); + int i = path.lastIndexOf('/'); + if (i > 0) { + path = path.substring(0, i); + } else { + path = "/"; //$NON-NLS-1$ + } + Folder folder = folders.get(path); + if (folder == null) { + folder = new Folder(); + folder.name = path; + folder.files = new ArrayList<>(); + folders.put(path, folder); + } + folder.files.add(range); + parents.put(range, folder); + } + } + } + + private static class DiffLabelProvider extends LabelProvider { + + private final Image FOLDER = PlatformUI.getWorkbench().getSharedImages() + .getImage(ISharedImages.IMG_OBJ_FOLDER); + + private final ResourceManager resourceManager = new LocalResourceManager( + JFaceResources.getResources()); + + public DiffLabelProvider() { + super(); + } + + @Override + public Image getImage(Object element) { + if (element instanceof DiffContentProvider.Folder) { + return FOLDER; + } + if (element instanceof FileDiffRegion) { + FileDiff diff = ((FileDiffRegion) element).getDiff(); + return (Image) resourceManager + .get(diff.getImageDescriptor(diff)); + } + return super.getImage(element); + } + + @Override + public String getText(Object element) { + if (element instanceof DiffContentProvider.Folder) { + return ((DiffContentProvider.Folder) element).name; + } + if (element instanceof FileDiffRegion) { + FileDiff diff = ((FileDiffRegion) element).getDiff(); + String path = diff.getPath(); + int i = path.lastIndexOf('/'); + return path.substring(i + 1); + } + return super.getText(element); + } + + @Override + public void dispose() { + resourceManager.dispose(); + super.dispose(); + } + } + +} + diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java index f6ac171936..589c9c9616 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java @@ -46,6 +46,7 @@ import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; import org.eclipse.jface.text.source.projection.ProjectionSupport; import org.eclipse.jface.text.source.projection.ProjectionViewer; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -64,10 +65,12 @@ import org.eclipse.ui.part.ShowInContext; import org.eclipse.ui.progress.UIJob; import org.eclipse.ui.texteditor.AbstractDocumentProvider; import org.eclipse.ui.texteditor.ITextEditorActionConstants; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; /** * A {@link TextEditor} wrapped as an {@link IFormPage} and specialized to - * showing a unified diff of a whole commit. + * showing a unified diff of a whole commit. The editor has an associated + * {@link DiffEditorOutlinePage}. */ public class DiffEditorPage extends TextEditor implements IFormPage, IShowInSource, IShowInTargetList { @@ -85,9 +88,12 @@ public class DiffEditorPage extends TextEditor private Control textControl; + private DiffEditorOutlinePage outlinePage; private Annotation[] currentFoldingAnnotations; + private FileDiffRegion currentFileDiffRange; + /** * Creates a new {@link DiffEditorPage} with the given id and title, which * is shown on the page's tab. @@ -116,6 +122,50 @@ public class DiffEditorPage extends TextEditor this(editor, "diffPage", UIText.DiffEditorPage_Title); //$NON-NLS-1$ } + @SuppressWarnings("unchecked") + @Override + public Object getAdapter(Class adapter) { + // TODO Switch to generified signature once EGit's base dependency is + // Eclipse 4.6 + if (IContentOutlinePage.class.equals(adapter)) { + if (outlinePage == null) { + outlinePage = createOutlinePage(); + outlinePage.setInput( + getDocumentProvider().getDocument(getEditorInput())); + if (currentFileDiffRange != null) { + outlinePage.setSelection( + new StructuredSelection(currentFileDiffRange)); + } + } + return outlinePage; + } + return super.getAdapter(adapter); + } + + private DiffEditorOutlinePage createOutlinePage() { + DiffEditorOutlinePage page = new DiffEditorOutlinePage(); + page.addSelectionChangedListener( + event -> doSetSelection(event.getSelection())); + page.addOpenListener(event -> { + FormEditor editor = getEditor(); + editor.getSite().getPage().activate(editor); + editor.setActivePage(getId()); + doSetSelection(event.getSelection()); + }); + return page; + } + + @Override + public void dispose() { + // Nested editors are responsible for disposing their outline pages + // themselves. + if (outlinePage != null) { + outlinePage.dispose(); + outlinePage = null; + } + super.dispose(); + } + // TextEditor specifics: @Override @@ -127,6 +177,17 @@ public class DiffEditorPage extends TextEditor ProjectionSupport projector = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors()); projector.install(); + viewer.getTextWidget().addCaretListener((event) -> { + if (outlinePage != null) { + FileDiffRegion region = getFileDiffRange(event.caretOffset); + if (region != null && !region.equals(currentFileDiffRange)) { + currentFileDiffRange = region; + outlinePage.setSelection(new StructuredSelection(region)); + } else { + currentFileDiffRange = region; + } + } + }); return viewer; } @@ -150,9 +211,36 @@ public class DiffEditorPage extends TextEditor super.doSetInput(input); if (input instanceof DiffEditorInput) { setFolding(); + FileDiffRegion region = getFileDiffRange(0); + currentFileDiffRange = region; } else if (input instanceof CommitEditorInput) { formatDiff(); + currentFileDiffRange = null; + } + if (outlinePage != null) { + outlinePage.setInput(getDocumentProvider().getDocument(input)); + if (currentFileDiffRange != null) { + outlinePage.setSelection( + new StructuredSelection(currentFileDiffRange)); + } + } + } + + @Override + protected void doSetSelection(ISelection selection) { + if (!selection.isEmpty() && selection instanceof StructuredSelection) { + Object selected = ((StructuredSelection) selection) + .getFirstElement(); + if (selected instanceof FileDiffRegion) { + FileDiffRegion newRange = (FileDiffRegion) selected; + if (!newRange.equals(currentFileDiffRange)) { + currentFileDiffRange = newRange; + selectAndReveal(newRange.getOffset(), 0); + } + return; + } } + super.doSetSelection(selection); } @Override @@ -184,7 +272,9 @@ public class DiffEditorPage extends TextEditor @Override public void setActive(boolean active) { - // Nothing to do. + if (active) { + setFocus(); + } } @Override @@ -227,6 +317,9 @@ public class DiffEditorPage extends TextEditor if (object instanceof IMarker) { IDE.gotoMarker(this, (IMarker) object); return true; + } else if (object instanceof ISelection) { + doSetSelection((ISelection) object); + return true; } return false; } @@ -290,6 +383,17 @@ public class DiffEditorPage extends TextEditor } } + private FileDiffRegion getFileDiffRange(int widgetOffset) { + DiffViewer viewer = (DiffViewer) getSourceViewer(); + int offset = viewer.widgetOffset2ModelOffset(widgetOffset); + IDocument document = getDocumentProvider() + .getDocument(getEditorInput()); + if (document instanceof DiffDocument) { + return ((DiffDocument) document).findFileRegion(offset); + } + return null; + } + /** * Gets the full unified diff of a {@link RepositoryCommit}. * @@ -432,4 +536,5 @@ public class DiffEditorPage extends TextEditor } } + } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java new file mode 100644 index 0000000000..732cd09259 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch> + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.commit; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.egit.core.AdapterUtils; +import org.eclipse.egit.ui.internal.UIText; +import org.eclipse.jface.dialogs.IPageChangedListener; +import org.eclipse.jface.util.SafeRunnable; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.SubActionBars; +import org.eclipse.ui.part.IPage; +import org.eclipse.ui.part.MessagePage; +import org.eclipse.ui.part.MultiPageEditorPart; +import org.eclipse.ui.part.Page; +import org.eclipse.ui.part.PageBook; +import org.eclipse.ui.views.contentoutline.ContentOutlinePage; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + * A {@link ContentOutlinePage} for {@link MultiPageEditorPart}s. The + * {@link org.eclipse.ui.views.contentoutline.ContentOutline ContentOutline} + * view only automatically handles outline pages of top-level editors, but not + * of nested editors. (It reacts only to part events, not to page change events, + * and moreover the MultiPageEditorPart framework does not send events when + * pages are added or removed.) + * <p> + * This class manages content outline pages for nested editors in its own + * {@link PageBook}. Nested editors can provide their outline pages as usual by + * adapting to {@link IContentOutlinePage}. + * </p> + */ +public class MultiPageEditorContentOutlinePage extends ContentOutlinePage { + + private final MultiPageEditorPart editorPart; + + private final ISelectionChangedListener globalSelectionListener = // + event -> fireSelectionChangedEvent(event); + + private final CopyOnWriteArrayList<ISelectionChangedListener> selectionListeners = new CopyOnWriteArrayList<>(); + + private PageBook book; + + private MessagePage emptyPage; + + private IPage currentPage; + + private IPageChangedListener pageListener; + + private final Map<IEditorPart, IPage> pages = new HashMap<>(); + + private final Map<IPage, SubActionBars> bars = new HashMap<>(); + + /** + * Creates a new {@link MultiPageEditorContentOutlinePage} for the given + * top-level editor part. It will track page changes and create and manage + * outline pages for any nested {@link IEditorPart}s of that top-level + * editor part. + * + * @param editorPart + * the outline page belongs to + */ + public MultiPageEditorContentOutlinePage(MultiPageEditorPart editorPart) { + super(); + this.editorPart = editorPart; + } + + @Override + public void createControl(Composite parent) { + book = new PageBook(parent, SWT.NONE); + emptyPage = new MessagePage(); + emptyPage.createControl(book); + emptyPage + .setMessage(UIText.MultiPageEditorContentOutlinePage_NoOutline); + Object activePage = editorPart.getSelectedPage(); + if (activePage instanceof IEditorPart) { + showPage(createOutlinePage((IEditorPart) activePage)); + } else { + currentPage = emptyPage; + book.showPage(emptyPage.getControl()); + } + pageListener = (event) -> { + Object newPage = event.getSelectedPage(); + if (!(newPage instanceof IEditorPart)) { + showPage(emptyPage); + return; + } + IPage newOutlinePage = pages.get(newPage); + if (newOutlinePage == null) { + newOutlinePage = createOutlinePage((IEditorPart) newPage); + } + showPage(newOutlinePage); + }; + editorPart.addPageChangedListener(pageListener); + } + + @Override + public void dispose() { + if (pageListener != null) { + editorPart.removePageChangedListener(pageListener); + pageListener = null; + } + pages.clear(); + selectionListeners.clear(); + for (SubActionBars bar : bars.values()) { + bar.dispose(); + } + bars.clear(); + if (currentPage instanceof ISelectionProvider) { + ((ISelectionProvider) currentPage) + .removeSelectionChangedListener(globalSelectionListener); + } + currentPage = null; + if (book != null) { + book.dispose(); + book = null; + } + if (emptyPage != null) { + emptyPage.dispose(); + emptyPage = null; + } + } + + @Override + public Control getControl() { + return book; + } + + @Override + public void setFocus() { + // See org.eclipse.ui.part.PageBookView + book.setFocus(); + currentPage.setFocus(); + } + + @Override + public void addSelectionChangedListener( + ISelectionChangedListener listener) { + selectionListeners.addIfAbsent(listener); + } + + @Override + public ISelection getSelection() { + if (currentPage instanceof ISelectionProvider) { + return ((ISelectionProvider) currentPage).getSelection(); + } + return StructuredSelection.EMPTY; + } + + @Override + public void removeSelectionChangedListener( + ISelectionChangedListener listener) { + selectionListeners.remove(listener); + } + + @Override + public void setSelection(ISelection selection) { + if (currentPage instanceof ISelectionProvider) { + ((ISelectionProvider) currentPage).setSelection(selection); + } + } + + private void showPage(IPage page) { + if (page == null) { + page = emptyPage; + } + if (currentPage == page) { + return; + } + if (currentPage instanceof ISelectionProvider) { + ((ISelectionProvider) currentPage) + .removeSelectionChangedListener(globalSelectionListener); + } + SubActionBars localBars = bars.get(currentPage); + if (localBars != null) { + localBars.deactivate(); + } + currentPage = page; + if (currentPage instanceof ISelectionProvider) { + ((ISelectionProvider) currentPage) + .addSelectionChangedListener(globalSelectionListener); + } + localBars = bars.get(currentPage); + Control control = page.getControl(); + if (control == null || control.isDisposed()) { + page.createControl(book); + page.setActionBars(localBars); + control = page.getControl(); + } + if (localBars != null) { + localBars.activate(); + } + getSite().getActionBars().updateActionBars(); + book.showPage(control); + if (currentPage instanceof ISelectionProvider) { + ISelection selection = ((ISelectionProvider) currentPage) + .getSelection(); + fireSelectionChangedEvent(new SelectionChangedEvent( + (ISelectionProvider) currentPage, selection)); + } else { + fireSelectionChangedEvent( + new SelectionChangedEvent(this, StructuredSelection.EMPTY)); + } + } + + private IPage createOutlinePage(IEditorPart editor) { + IContentOutlinePage outlinePage = AdapterUtils.adapt(editor, + IContentOutlinePage.class); + if (outlinePage == null) { + pages.put(editor, emptyPage); + return emptyPage; + } + pages.put(editor, outlinePage); + if (outlinePage instanceof NestedContentOutlinePage) { + ((Page) outlinePage).init(getSite()); + } + SubActionBars pageBars = new SubActionBars(getSite().getActionBars()); + bars.put(outlinePage, pageBars); + return outlinePage; + } + + private void fireSelectionChangedEvent(SelectionChangedEvent event) { + for (ISelectionChangedListener listener : selectionListeners) { + SafeRunnable.run(new SafeRunnable() { + + @Override + public void run() { + listener.selectionChanged(event); + } + }); + } + } +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java new file mode 100644 index 0000000000..87bcc78708 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch> + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.commit; + +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ui.part.IPageSite; +import org.eclipse.ui.views.contentoutline.ContentOutlinePage; + +/** + * A {@link ContentOutlinePage} that is to be nested in a + * {@link MultiPageEditorContentOutlinePage}. + */ +public class NestedContentOutlinePage extends ContentOutlinePage { + + @Override + public void init(IPageSite pageSite) { + // ContentOutlinePage insists on setting itself as selection provider. + // For pages nested inside a MultiPageEditorContentOutlinePage this is + // wrong. Save and restore the selection provider. + ISelectionProvider provider = pageSite.getSelectionProvider(); + super.init(pageSite); + pageSite.setSelectionProvider(provider); + } +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties index 014a6f4b20..639acbd1d2 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties @@ -1494,6 +1494,8 @@ StashEditorPage_LabelParent0=HEAD: StashEditorPage_LabelParent1=Index: StashEditorPage_LabelParent2=Untracked: +MultiPageEditorContentOutlinePage_NoOutline=An outline is not available. + Header_contextMenu_copy=&Copy Header_contextMenu_copy_SHA1=Copy &SHA-1 Header_copy_SHA1_error_title= Problem Copying SHA-1 to Clipboard |