/******************************************************************************* * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal * * 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: * Patrick Tasse - Initial API and implementation * Geneviève Bastien - Experiment instantiated with experiment type *******************************************************************************/ package org.eclipse.tracecompass.tmf.ui.editors; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.InvalidRegistryObjectException; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.action.IStatusLineManager; 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.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.tracecompass.internal.tmf.ui.Activator; import org.eclipse.tracecompass.internal.tmf.ui.editors.ITmfEventsEditorConstants; import org.eclipse.tracecompass.internal.tmf.ui.editors.TmfTableColumnUtils; import org.eclipse.tracecompass.tmf.core.TmfCommonConstants; import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect; import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects; import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; import org.eclipse.tracecompass.tmf.core.signal.TmfTimestampFormatUpdateSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal; import org.eclipse.tracecompass.tmf.core.trace.ITmfContext; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; import org.eclipse.tracecompass.tmf.ui.project.model.Messages; import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentElement; import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentFolder; import org.eclipse.tracecompass.tmf.ui.project.model.TmfOpenTraceHelper; import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectElement; import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectRegistry; import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement; import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceFolder; import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceTypeUIUtils; import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsTable; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.IReusableEditor; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.views.properties.IPropertySheetPage; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; /** * Editor for TMF events * * @author Patrick Tasse */ public class TmfEventsEditor extends TmfEditor implements ITmfTraceEditor, IReusableEditor, IPropertyListener, IResourceChangeListener, ISelectionProvider, ISelectionChangedListener, IPartListener, IGotoMarker { /** ID for this class */ public static final String ID = "org.eclipse.linuxtools.tmf.ui.editors.events"; //$NON-NLS-1$ private TmfEventsTable fEventsTable; private IFile fFile; private ITmfTrace fTrace; private Composite fParent; private ListenerList fSelectionChangedListeners = new ListenerList(); private boolean fTraceSelected; private IMarker fPendingGotoMarker; @Override public void doSave(final IProgressMonitor monitor) { } @Override public void doSaveAs() { } @Override public void init(final IEditorSite site, IEditorInput input) throws PartInitException { IFileEditorInput fileEditorInput; if (input instanceof TmfEditorInput) { fFile = ((TmfEditorInput) input).getFile(); fTrace = ((TmfEditorInput) input).getTrace(); /* change the input to a FileEditorInput to allow open handlers to find this editor */ fileEditorInput = new FileEditorInput(fFile); } else if (input instanceof IFileEditorInput) { fileEditorInput = (IFileEditorInput) input; fFile = fileEditorInput.getFile(); if (fFile == null) { throw new PartInitException("Invalid IFileEditorInput: " + fileEditorInput); //$NON-NLS-1$ } try { final String traceTypeId = fFile.getPersistentProperty(TmfCommonConstants.TRACETYPE); if (traceTypeId == null) { throw new PartInitException(Messages.TmfOpenTraceHelper_NoTraceType); } if (ITmfEventsEditorConstants.EXPERIMENT_INPUT_TYPE_CONSTANTS.contains(traceTypeId)) { // Special case: experiment bookmark resource final TmfProjectElement project = TmfProjectRegistry.getProject(fFile.getProject(), true); if (project == null) { throw new PartInitException(Messages.TmfOpenTraceHelper_NoTraceType); } TmfExperimentFolder experimentFolder = project.getExperimentsFolder(); if (experimentFolder != null) { for (final TmfExperimentElement experimentElement : experimentFolder.getExperiments()) { if (experimentElement.getResource().equals(fFile.getParent())) { setPartName(experimentElement.getName()); super.setSite(site); super.setInput(fileEditorInput); TmfOpenTraceHelper.reopenTraceFromElement(experimentElement, this); return; } } } } else if (ITmfEventsEditorConstants.TRACE_INPUT_TYPE_CONSTANTS.contains(traceTypeId)) { // Special case: trace bookmark resource final TmfProjectElement project = TmfProjectRegistry.getProject(fFile.getProject(), true); final TmfTraceFolder tracesFolder = project.getTracesFolder(); if (tracesFolder != null) { for (final TmfTraceElement traceElement : tracesFolder.getTraces()) { if (traceElement.getResource().equals(fFile.getParent())) { setPartName(traceElement.getElementPath()); super.setSite(site); super.setInput(fileEditorInput); TmfOpenTraceHelper.reopenTraceFromElement(traceElement, this); return; } } } } else { final TmfProjectElement project = TmfProjectRegistry.getProject(fFile.getProject(), true); final TmfTraceFolder tracesFolder = project.getTracesFolder(); if (tracesFolder != null) { for (final TmfTraceElement traceElement : tracesFolder.getTraces()) { if (traceElement.getResource().equals(fFile)) { setPartName(traceElement.getElementPath()); super.setSite(site); super.setInput(fileEditorInput); TmfOpenTraceHelper.reopenTraceFromElement(traceElement, this); return; } } } } } catch (final PartInitException e) { throw e; } catch (final InvalidRegistryObjectException e) { Activator.getDefault().logError("Error initializing TmfEventsEditor", e); //$NON-NLS-1$ } catch (final CoreException e) { Activator.getDefault().logError("Error initializing TmfEventsEditor", e); //$NON-NLS-1$ } } else { throw new PartInitException("Invalid IEditorInput: " + input.getClass()); //$NON-NLS-1$ } if (fTrace == null) { throw new PartInitException("Invalid IEditorInput: " + fFile.getName()); //$NON-NLS-1$ } super.setSite(site); super.setInput(fileEditorInput); } @Override public boolean isDirty() { return false; } @Override public boolean isSaveAsAllowed() { return false; } @Override public void setInput(final IEditorInput input) { super.setInput(input); firePropertyChange(IEditorPart.PROP_INPUT); } @Override public void propertyChanged(final Object source, final int propId) { if (propId == IEditorPart.PROP_INPUT && getEditorInput() instanceof TmfEditorInput) { if (fTrace != null) { broadcast(new TmfTraceClosedSignal(this, fTrace)); saveState(); } fEventsTable.dispose(); fFile = ((TmfEditorInput) getEditorInput()).getFile(); fTrace = ((TmfEditorInput) getEditorInput()).getTrace(); /* change the input to a FileEditorInput to allow open handlers to find this editor */ super.setInput(new FileEditorInput(fFile)); createAndInitializeTable(); // The table was swapped for a new one, make sure it gets focus if // the editor is active. Otherwise, the new table will not get focus // because the editor already had focus. if (!PlatformUI.getWorkbench().isClosing() && PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart() == getSite().getPart()) { fEventsTable.setFocus(); } fParent.layout(); } } private void loadState() { final @Nullable String traceTypeId = fTrace.getTraceTypeId(); fEventsTable.setColumnOrder(TmfTableColumnUtils.loadColumnOrder(traceTypeId)); fEventsTable.setColumnWidth(TmfTableColumnUtils.loadColumnWidth(traceTypeId), TmfTableColumnUtils.loadColumnResizable(traceTypeId)); } private void saveState() { final @Nullable String traceTypeId = fTrace.getTraceTypeId(); TmfTableColumnUtils.saveColumnOrder(traceTypeId, fEventsTable.getColumnOrder()); TmfTableColumnUtils.saveColumnWidth(traceTypeId, fEventsTable.getColumnWidth()); TmfTableColumnUtils.saveColumnResizability(traceTypeId, fEventsTable.getColumnResizable()); } @Override public void createPartControl(final Composite parent) { fParent = parent; createAndInitializeTable(); addPropertyListener(this); ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); // we need to wrap the ISelectionProvider interface in the editor because // the events table can be replaced later while the selection changed listener // is only added once by the platform to the selection provider set here getSite().setSelectionProvider(this); getSite().getPage().addPartListener(this); } private void createAndInitializeTable() { if (fTrace != null) { setPartName(fTrace.getName()); fEventsTable = createEventsTable(fParent, fTrace.getCacheSize()); fEventsTable.registerContextMenus(getSite()); fEventsTable.addSelectionChangedListener(this); fEventsTable.setTrace(fTrace, true); fEventsTable.refreshBookmarks(fFile); loadState(); /* ensure start time is set */ final ITmfContext context = fTrace.seekEvent(0); fTrace.getNext(context); context.dispose(); broadcast(new TmfTraceOpenedSignal(this, fTrace, fFile)); if (fTraceSelected) { broadcast(new TmfTraceSelectedSignal(this, fTrace)); } /* go to marker after trace opened */ if (fPendingGotoMarker != null) { fEventsTable.gotoMarker(fPendingGotoMarker); fPendingGotoMarker = null; } } else { fEventsTable = new TmfEventsTable(fParent, 0); fEventsTable.addSelectionChangedListener(this); } IStatusLineManager statusLineManager = getEditorSite().getActionBars().getStatusLineManager(); fEventsTable.setStatusLineManager(statusLineManager); } @Override public void dispose() { if (getSite() != null) { getSite().getPage().removePartListener(this); } ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); removePropertyListener(this); if (fTrace != null) { broadcast(new TmfTraceClosedSignal(this, fTrace)); if (fEventsTable != null) { saveState(); } } super.dispose(); } /** * Create the event table * * @param parent * The parent composite * @param cacheSize * The cache size * @return The event table instance */ protected @NonNull TmfEventsTable createEventsTable(final Composite parent, final int cacheSize) { ITmfTrace trace = fTrace; /* * Check if the trace (or experiment type) defines a specific event * table in its extension point. */ TmfEventsTable table = TmfTraceTypeUIUtils.getEventTable(trace, parent, cacheSize); if (table != null) { return table; } /* * Use the aspects defined by the trace type (or each trace type in an * experiment) to build a table consisting of these. */ Iterable<ITmfEventAspect<?>> aspects = getTraceAspects(trace); if (Iterables.isEmpty(aspects)) { /* Couldn't find any event aspects, use a default table */ return new TmfEventsTable(parent, cacheSize); } return new TmfEventsTable(parent, cacheSize, aspects); } /** * Get the event table for the given trace. It will be of the type defined * by the extension point if applicable, else it will be a default table * with the extension-point-defined columns (if any). * * @param trace * The event table is for this trace * @param parent * The parent composite of the table * @param cacheSize * The cache size to use * @return The event table for the trace */ private static @NonNull Iterable<ITmfEventAspect<?>> getTraceAspects(ITmfTrace trace) { if (trace instanceof TmfExperiment) { return getExperimentAspects((TmfExperiment) trace); } return trace.getEventAspects(); } /** * Get the events table for an experiment. If all traces in the experiment * are of the same type, use the same behavior as if it was one trace of * that type. * * @param experiment * the experiment * @param parent * the parent Composite * @param cacheSize * the event table cache size * @return An event table of the appropriate type */ private static @NonNull Iterable<ITmfEventAspect<?>> getExperimentAspects( final TmfExperiment experiment) { List<ITmfTrace> traces = experiment.getTraces(); ImmutableSet.Builder<ITmfEventAspect<?>> builder = new ImmutableSet.Builder<>(); /* For experiments, we'll add a "trace name" aspect/column */ builder.add(TmfBaseAspects.getTraceNameAspect()); String commonTraceType = getCommonTraceType(experiment); if (commonTraceType != null) { /* * All the traces in this experiment are of the same type, let's * just use the normal table for that type. */ builder.addAll(traces.get(0).getEventAspects()); } else { /* * There are different trace types in the experiment, so we are * definitely using a TmfEventsTable. Aggregate the columns from all * trace types. */ for (ITmfTrace trace : traces) { Iterable<ITmfEventAspect<?>> traceAspects = trace.getEventAspects(); builder.addAll(traceAspects); } } return builder.build(); } /** * Check if an experiment contains traces of all the same type. If so, * returns this type as a String. If not, returns null. * * @param experiment * The experiment * @return The common trace type if there is one, or 'null' if there are * different types. */ private static @Nullable String getCommonTraceType(TmfExperiment experiment) { String commonTraceType = null; try { for (final ITmfTrace trace : experiment.getTraces()) { final IResource resource = trace.getResource(); if (resource == null) { return null; } final String traceType = resource.getPersistentProperty(TmfCommonConstants.TRACETYPE); if ((commonTraceType != null) && !commonTraceType.equals(traceType)) { return null; } commonTraceType = traceType; } } catch (CoreException e) { /* * One of the traces didn't advertise its type, we can't infer * anything. */ return null; } return commonTraceType; } @Override public ITmfTrace getTrace() { return fTrace; } @Override public void setFocus() { fEventsTable.setFocus(); } @Override public <T> T getAdapter(final Class<T> adapter) { if (IGotoMarker.class.equals(adapter)) { if (fTrace == null || fEventsTable == null) { return adapter.cast(this); } return adapter.cast(fEventsTable); } else if (IPropertySheetPage.class.equals(adapter)) { return adapter.cast(new UnsortedPropertySheetPage()); } return super.getAdapter(adapter); } @Override public void gotoMarker(IMarker marker) { if (fTrace == null || fEventsTable == null) { fPendingGotoMarker = marker; } else { fEventsTable.gotoMarker(marker); } } @Override public void resourceChanged(final IResourceChangeEvent event) { final Set<@NonNull IMarker> added = new HashSet<>(); final Set<@NonNull IMarker> removed = new HashSet<>(); boolean deltaFound = false; for (final IMarkerDelta delta : event.findMarkerDeltas(IMarker.BOOKMARK, false)) { if (delta.getResource().equals(fFile)) { if (delta.getKind() == IResourceDelta.REMOVED) { removed.add(delta.getMarker()); } else if (delta.getKind() == IResourceDelta.ADDED) { added.add(delta.getMarker()); } /* this also covers IResourceDelta.CHANGED */ deltaFound = true; } } if (!deltaFound) { return; } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (removed.isEmpty() && added.isEmpty()) { fEventsTable.getTable().refresh(); } else { if (!removed.isEmpty()) { fEventsTable.removeBookmark(Iterables.toArray(removed, IMarker.class)); } if (!added.isEmpty()) { fEventsTable.addBookmark(Iterables.toArray(added, IMarker.class)); } } } }); } // ------------------------------------------------------------------------ // ISelectionProvider // ------------------------------------------------------------------------ @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { fSelectionChangedListeners.add(listener); } @Override public ISelection getSelection() { if (fEventsTable == null) { return StructuredSelection.EMPTY; } return fEventsTable.getSelection(); } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { fSelectionChangedListeners.remove(listener); } @Override public void setSelection(ISelection selection) { // not implemented } /** * Notifies any selection changed listeners that the viewer's selection has changed. * Only listeners registered at the time this method is called are notified. * * @param event a selection changed event * * @see ISelectionChangedListener#selectionChanged */ protected void fireSelectionChanged(final SelectionChangedEvent event) { Object[] listeners = fSelectionChangedListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.selectionChanged(event); } }); } } // ------------------------------------------------------------------------ // ISelectionChangedListener // ------------------------------------------------------------------------ @Override public void selectionChanged(SelectionChangedEvent event) { fireSelectionChanged(event); } // ------------------------------------------------------------------------ // IPartListener // ------------------------------------------------------------------------ @Override public void partActivated(IWorkbenchPart part) { if (part == this && !fTraceSelected) { fTraceSelected = true; if (fTrace == null) { return; } broadcast(new TmfTraceSelectedSignal(this, fTrace)); } } @Override public void partBroughtToTop(IWorkbenchPart part) { if (part == this && !fTraceSelected) { fTraceSelected = true; if (fTrace == null) { return; } broadcast(new TmfTraceSelectedSignal(this, fTrace)); } } @Override public void partClosed(IWorkbenchPart part) { } @Override public void partDeactivated(IWorkbenchPart part) { } @Override public void partOpened(IWorkbenchPart part) { } // ------------------------------------------------------------------------ // Global commands // ------------------------------------------------------------------------ /** * Add a bookmark */ public void addBookmark() { fEventsTable.addBookmark(fFile); } // ------------------------------------------------------------------------ // Signal handlers // ------------------------------------------------------------------------ /** * Handler for the Trace Selected signal * * @param signal The incoming signal */ @TmfSignalHandler public void traceSelected(final TmfTraceSelectedSignal signal) { if ((signal.getSource() != this)) { if (signal.getTrace().equals(fTrace)) { getSite().getPage().bringToTop(this); } else { fTraceSelected = false; } } } /** * Update the display to use the updated timestamp format * * @param signal the incoming signal */ @TmfSignalHandler public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) { if (fEventsTable != null) { fEventsTable.refresh(); } } }