From c389833c3f47a594d1e70b5d5244d25e3724f7f0 Mon Sep 17 00:00:00 2001 From: Jonathan Rajotte Date: Wed, 26 Oct 2016 18:27:27 -0400 Subject: [PATCH] Introduce "New view" action for views based on TmfView All views based on TmfView now have a new action button in their expandable menu providing an easy way to spawn a view of the same type. Introduce a TmfViewFactory Singleton. This is an initial work toward providing a cloning ability to TmfView based views. Change-Id: Ie8a0c4af305edb6f14968188cfd8feda8983a8cd Signed-off-by: Jonathan Rajotte --- .../doc/User-Guide.mediawiki | 8 + .../ui/views/timegraph/XmlTimeGraphView.java | 3 +- .../xml/ui/views/xychart/XmlXYView.java | 3 +- .../internal/tmf/ui/Messages.java | 2 + .../internal/tmf/ui/messages.properties | 2 + .../tmf/ui/views/NewTmfViewAction.java | 35 ++++ .../tracecompass/tmf/ui/views/TmfView.java | 45 ++++- .../tmf/ui/views/TmfViewFactory.java | 169 ++++++++++++++++++ 8 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/NewTmfViewAction.java create mode 100644 tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfViewFactory.java diff --git a/doc/org.eclipse.tracecompass.doc.user/doc/User-Guide.mediawiki b/doc/org.eclipse.tracecompass.doc.user/doc/User-Guide.mediawiki index f9f51ed37e..17db4ac223 100644 --- a/doc/org.eclipse.tracecompass.doc.user/doc/User-Guide.mediawiki +++ b/doc/org.eclipse.tracecompass.doc.user/doc/User-Guide.mediawiki @@ -2056,6 +2056,10 @@ View Menu {| | +| New Control Flow view +| Spawn a new control flow view. +|- +| | Show Markers | A marker highlights a time interval. A marker can be used for instance to indicate a time range where lost events occurred or to bookmark an interesting interval for future reference. Selecting a category name will toggle the visibility of markers of that category. |- @@ -2186,6 +2190,10 @@ View Menu {| | +| New Resources view +| Spawn a new resources view. +|- +| | Show Markers | A marker highlights a time interval. A marker can be used for instance to indicate a time range where lost events occurred or to bookmark an interesting interval for future reference. Selecting a category name will toggle the visibility of markers of that category. |} diff --git a/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/timegraph/XmlTimeGraphView.java b/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/timegraph/XmlTimeGraphView.java index 1324fbdb2f..cbb10834ed 100644 --- a/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/timegraph/XmlTimeGraphView.java +++ b/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/timegraph/XmlTimeGraphView.java @@ -56,6 +56,7 @@ import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; +import org.eclipse.tracecompass.tmf.ui.views.TmfViewFactory; import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider2; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent; @@ -137,7 +138,7 @@ public class XmlTimeGraphView extends AbstractTimeGraphView { @Override public void createPartControl(Composite parent) { super.createPartControl(parent); - fViewInfo.setName(NonNullUtils.checkNotNull(getViewSite().getSecondaryId())); + fViewInfo.setName(NonNullUtils.checkNotNull(TmfViewFactory.getBaseSecId(getViewSite().getSecondaryId()))); } private void loadNewXmlView() { diff --git a/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/xychart/XmlXYView.java b/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/xychart/XmlXYView.java index 17a805a91b..5553cf4f70 100644 --- a/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/xychart/XmlXYView.java +++ b/tmf/org.eclipse.tracecompass.tmf.analysis.xml.ui/src/org/eclipse/tracecompass/internal/tmf/analysis/xml/ui/views/xychart/XmlXYView.java @@ -22,6 +22,7 @@ import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.TmfXmlUiStrings; import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.views.XmlViewInfo; import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfXYChartViewer; import org.eclipse.tracecompass.tmf.ui.views.TmfChartView; +import org.eclipse.tracecompass.tmf.ui.views.TmfViewFactory; import org.w3c.dom.Element; /** @@ -94,7 +95,7 @@ public class XmlXYView extends TmfChartView { @Override public void createPartControl(@Nullable Composite parent) { super.createPartControl(parent); - fViewInfo.setName(NonNullUtils.checkNotNull(getViewSite().getSecondaryId())); + fViewInfo.setName(NonNullUtils.checkNotNull(TmfViewFactory.getBaseSecId(getViewSite().getSecondaryId()))); setViewTitle(); } diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java index d1ec202d25..44c73aa290 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java @@ -322,6 +322,8 @@ public class Messages extends NLS { public static String TmfView_PinActionToolTipText; public static String TmfView_AlignViewsActionNameText; public static String TmfView_AlignViewsActionToolTipText; + public static String TmfView_NewTmfViewNameText; + public static String TmfView_NewTmfViewToolTipText; public static String CallStackPresentationProvider_Thread; public static String CallStackView_FunctionColumn; diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties index e2e0f058e7..c07e4e4fa2 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties @@ -323,6 +323,8 @@ TmfView_PinActionNameText=Pin View TmfView_PinActionToolTipText=Pin View TmfView_AlignViewsActionNameText=Align Views TmfView_AlignViewsActionToolTipText=Align Views +TmfView_NewTmfViewNameText=&New {0} view +TmfView_NewTmfViewToolTipText=Spawn a new {0} view; # org.eclipse.tracecompass.tmf.ui.views.callstack CallStackPresentationProvider_Thread=Thread diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/NewTmfViewAction.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/NewTmfViewAction.java new file mode 100644 index 0000000000..0376729d69 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/NewTmfViewAction.java @@ -0,0 +1,35 @@ +/********************************************************************** + * Copyright (c) 2016 EfficiOS Inc. + * + * 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.tracecompass.tmf.ui.views; + +import java.text.MessageFormat; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.tracecompass.internal.tmf.ui.Messages; + +/** + * Action to instantiate a new instance of views that support it. + * @author Jonathan Rajotte Julien + * @since 2.2 + */ +public class NewTmfViewAction extends Action { + + /** + * Creates a new NewTmfViewAction. + * + * @param view + * The view for which the action is created + */ + public NewTmfViewAction(TmfView view) { + super(MessageFormat.format(Messages.TmfView_NewTmfViewNameText, view.getTitle().toLowerCase()), IAction.AS_PUSH_BUTTON); + setToolTipText(MessageFormat.format(Messages.TmfView_NewTmfViewToolTipText, view.getTitle())); + } +} diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfView.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfView.java index f073027a66..9c4b451f6d 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfView.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfView.java @@ -14,6 +14,9 @@ package org.eclipse.tracecompass.tmf.ui.views; +import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; + +import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; import org.eclipse.swt.events.ControlAdapter; @@ -51,8 +54,22 @@ public abstract class TmfView extends ViewPart implements ITmfComponent { * Action class for pinning of TmfView. */ protected PinTmfViewAction fPinAction; + + /** + * Action class for spawning a new view based on this view type. + * + * @since 2.2 + */ + private NewTmfViewAction fNewAction; + private static TimeAlignViewsAction fAlignViewsAction; + /** + * The separator used for distinguishing between primary and secondary id of a view id. + * @since 2.2 + */ + public static final String PRIMARY_SECONDARY_ID_SEPARATOR = ":"; //$NON-NLS-1$ + // ------------------------------------------------------------------------ // Constructor // ------------------------------------------------------------------------ @@ -132,9 +149,35 @@ public abstract class TmfView extends ViewPart implements ITmfComponent { } } + /** + * Add the "New view" action to the expandable menu. This action spawn a new + * view of the same type as the caller. + * + * @since 2.2 + */ + private void contributeNewActionToMenu(IMenuManager menuManager) { + if (fNewAction == null) { + fNewAction = new NewTmfViewAction(TmfView.this) { + @Override + public void run() { + TmfViewFactory.newView(checkNotNull(TmfView.this.getViewId()), true); + } + }; + menuManager.add(fNewAction); + } + } + @Override public void createPartControl(final Composite parent) { fParentComposite = parent; + IMenuManager menuManager = getViewSite().getActionBars() + .getMenuManager(); + + /* Add to menu */ + contributeNewActionToMenu(menuManager); + menuManager.add(new Separator()); + + if (this instanceof ITmfTimeAligned) { contributeAlignViewsActionToToolbar(); @@ -215,6 +258,6 @@ public abstract class TmfView extends ViewPart implements ITmfComponent { if (secondaryId == null) { return viewSite.getId(); } - return viewSite.getId() + ':' + secondaryId; + return viewSite.getId() + PRIMARY_SECONDARY_ID_SEPARATOR + secondaryId; } } diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfViewFactory.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfViewFactory.java new file mode 100644 index 0000000000..cc2ee13238 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/TmfViewFactory.java @@ -0,0 +1,169 @@ +/********************************************************************** + * Copyright (c) 2016 EfficiOS Inc. + * + * 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.tracecompass.tmf.ui.views; + +import java.util.UUID; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Factory for TmfView. + * + * @author Jonathan Rajotte Julien + * @since 2.2 + */ +public final class TmfViewFactory { + + /** + * The separator used for secondary id internal use. This allow to have + * multiple level of information inside the secondary id. + */ + @VisibleForTesting + public static final String INTERNAL_SECONDARY_ID_SEPARATOR = "&"; //$NON-NLS-1$ + + /** + * Empty constructor + */ + private TmfViewFactory() {} + + /** + * Create a new view.
+ * + * If a view with the corresponding id already exist and no suffix were + * added the existing view will be given focus. + * + * @param viewId + * The id of the view to be created.
+ * Format: [:secondary_id][&third_id]..[&n_id] + * @param generateSuffix + * Add a generated suffix id (UUID). This allow multiple view of + * the same id to be displayed. + * @return + * NULL if an error occurred. + * The view instance. + */ + @NonNullByDefault + public static @Nullable IViewPart newView(String viewId, boolean generateSuffix) { + IViewPart viewPart = null; + String primaryId = null; + String secondaryId = null; + + /* Parse the view id */ + if (viewId.contains(TmfView.PRIMARY_SECONDARY_ID_SEPARATOR)) { + int index = viewId.indexOf(TmfView.PRIMARY_SECONDARY_ID_SEPARATOR); + + primaryId = viewId.substring(0, index); + secondaryId = getBaseSecId(viewId.substring(index + 1)); + + } else { + primaryId = viewId; + } + + if (generateSuffix) { + if (secondaryId == null) { + secondaryId = UUID.randomUUID().toString(); + } else { + secondaryId += INTERNAL_SECONDARY_ID_SEPARATOR + UUID.randomUUID().toString(); + } + } + + IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow(); + IWorkbenchPage page = workbenchWindow.getActivePage(); + try { + viewPart = page.showView(primaryId, secondaryId, IWorkbenchPage.VIEW_ACTIVATE); + page.activate(viewPart); + } catch (PartInitException e) { + /* Simply return null on error */ + } + + return viewPart; + } + + /** + * Parse a secondary id and return the base secondary id minus any generated + * suffix (UUID). + * + * @param secId + * A view secondary id + * @return NULL when the passed string is a UUID.
+ * The base secondary id + */ + public static String getBaseSecId(String secId) { + if (secId == null) { + return null; + } + + if (!secId.contains(INTERNAL_SECONDARY_ID_SEPARATOR)) { + if (isUUID(secId)) { + return null; + } + return secId; + } + + int lastIndexSepartor = secId.lastIndexOf(INTERNAL_SECONDARY_ID_SEPARATOR); + + /** + * Validate that the right side of the separator is a UUID since the + * separator could be a valid value from the base secondary id. + */ + String potentialUUID = secId.substring(lastIndexSepartor + 1); + + if (!isUUID(potentialUUID)) { + return secId; + } + + return secId.substring(0, lastIndexSepartor); + } + + /** + * Utility method for testing if a string is a valid full length UUID. + *
+ *
+     * e.g:
+     *     9eaf1840-8a87-4314-a8b7-03e3eccf4766 -> true
+     *     1-1-1-1-1 -> false
+     * 
+ * + * @param uuid + * The string to test + * @return If the passed string is a UUID + */ + private static boolean isUUID(String uuid) { + if (uuid == null) { + return false; + } + + try { + /* + * UUID.fromString does not check for length wise valid UUID only the + * UUID form so check if the reverse operation is valid. + */ + UUID fromStringUUID = UUID.fromString(uuid); + String toStringUUID = fromStringUUID.toString(); + return toStringUUID.equals(uuid); + } catch (IllegalArgumentException e) { + /** + * The substring is not a UUID. Assume that the separator come from + * the initial secondaryId. + */ + return false; + } + } +} -- 2.34.1