| /******************************************************************************* |
| * Copyright (c) 2014 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Matthew Khouzam - Initial API and implementation |
| * Patrick Tasse - Initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.internal.tmf.ui.project.dialogs.offset; |
| |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.viewers.CellEditor; |
| import org.eclipse.jface.viewers.ColumnLabelProvider; |
| import org.eclipse.jface.viewers.ColumnViewer; |
| import org.eclipse.jface.viewers.ColumnViewerEditor; |
| import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; |
| import org.eclipse.jface.viewers.EditingSupport; |
| import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter; |
| import org.eclipse.jface.viewers.TextCellEditor; |
| import org.eclipse.jface.viewers.TreeViewerColumn; |
| import org.eclipse.jface.viewers.TreeViewerEditor; |
| import org.eclipse.jface.viewers.TreeViewerFocusCellManager; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.TreeEditor; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.layout.RowLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Group; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeColumn; |
| import org.eclipse.swt.widgets.TreeItem; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfEventSelectedSignal; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal; |
| import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfOpenTraceHelper; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement; |
| import org.eclipse.tracecompass.tmf.ui.viewers.ArrayTreeContentProvider; |
| import org.eclipse.ui.dialogs.FilteredTree; |
| import org.eclipse.ui.dialogs.PatternFilter; |
| |
| /** |
| * Offset wizard dialog |
| * |
| * @author Matthew Khouzam |
| * |
| */ |
| public class OffsetDialog extends Dialog { |
| |
| private static final int TREE_EDITOR_MIN_WIDTH = 50; |
| private static final String EDITOR_KEY = "$editor$"; //$NON-NLS-1$ |
| private static final String WIDTH_KEY = "$width$"; //$NON-NLS-1$ |
| |
| private static final TmfTimestampFormat TIME_FORMAT = new TmfTimestampFormat("yyyy-MM-dd HH:mm:ss.SSS SSS SSS"); //$NON-NLS-1$ |
| private static final TmfTimestampFormat OFFSET_FORMAT = new TmfTimestampFormat("T.SSS SSS SSS"); //$NON-NLS-1$ |
| |
| private final Map<TmfTraceElement, Long> fOffsetMap; |
| private final Map<TmfTraceElement, ITmfTimestamp> fRefTimeMap; |
| private final Map<TmfTraceElement, ITmfTimestamp> fTargetTimeMap; |
| |
| private Label fBasicMessageLabel; |
| private Group fButtonGroup; |
| private Label fAdvancedMessageLabel; |
| private FilteredTree fViewer; |
| |
| private boolean fAdvancedMode = true; |
| private TreeViewerColumn fButtonViewerColumn; |
| private TreeColumn fRefTimeColumn; |
| private TreeColumn fTargetTimeColumn; |
| |
| private abstract class ColumnEditingSupport extends EditingSupport { |
| private final TextCellEditor textCellEditor; |
| |
| private ColumnEditingSupport(ColumnViewer viewer, TextCellEditor textCellEditor) { |
| super(viewer); |
| this.textCellEditor = textCellEditor; |
| } |
| |
| @Override |
| protected CellEditor getCellEditor(Object element) { |
| return textCellEditor; |
| } |
| |
| @Override |
| protected boolean canEdit(Object element) { |
| return true; |
| } |
| } |
| |
| private class TimeEditingSupport extends ColumnEditingSupport { |
| private Map<TmfTraceElement, ITmfTimestamp> map; |
| |
| private TimeEditingSupport(ColumnViewer viewer, TextCellEditor textCellEditor, Map<TmfTraceElement, ITmfTimestamp> map) { |
| super(viewer, textCellEditor); |
| this.map = map; |
| } |
| |
| @Override |
| protected void setValue(Object element, Object value) { |
| if (value instanceof String) { |
| String string = (String) value; |
| if (string.trim().isEmpty()) { |
| map.remove(element); |
| } else { |
| try { |
| ITmfTimestamp refTime = map.get(element); |
| long ref = refTime == null ? 0 : refTime.toNanos(); |
| Long newVal = TIME_FORMAT.parseValue(string, ref); |
| map.put((TmfTraceElement) element, TmfTimestamp.fromNanos(newVal)); |
| } catch (ParseException e) { |
| /* Ignore and reload previous value */ |
| } |
| } |
| fViewer.getViewer().update(element, null); |
| } |
| } |
| |
| @Override |
| protected Object getValue(Object element) { |
| ITmfTimestamp ts = map.get(element); |
| if (ts == null) { |
| return ""; //$NON-NLS-1$ |
| } |
| return TIME_FORMAT.format(ts.toNanos()); |
| } |
| } |
| |
| private class RefTimeEditingSupport extends TimeEditingSupport { |
| private RefTimeEditingSupport(ColumnViewer viewer, TextCellEditor textCellEditor) { |
| super(viewer, textCellEditor, fRefTimeMap); |
| } |
| } |
| |
| private class TargetTimeEditingSupport extends TimeEditingSupport { |
| private TargetTimeEditingSupport(ColumnViewer viewer, TextCellEditor textCellEditor) { |
| super(viewer, textCellEditor, fTargetTimeMap); |
| } |
| } |
| |
| private class OffsetEditingSupport extends ColumnEditingSupport { |
| private OffsetEditingSupport(ColumnViewer viewer, TextCellEditor textCellEditor) { |
| super(viewer, textCellEditor); |
| } |
| |
| @Override |
| protected void setValue(Object element, Object value) { |
| if (value instanceof String) { |
| String string = (String) value; |
| if (string.trim().isEmpty()) { |
| fOffsetMap.put((TmfTraceElement) element, 0L); |
| } else { |
| try { |
| Long newVal = OFFSET_FORMAT.parseValue(string); |
| fOffsetMap.put((TmfTraceElement) element, newVal); |
| } catch (ParseException e) { |
| /* Ignore and reload previous value */ |
| } |
| } |
| fViewer.getViewer().update(element, null); |
| } |
| } |
| |
| @Override |
| protected Object getValue(Object element) { |
| Long offset = fOffsetMap.get(element); |
| if (offset == null || offset == 0) { |
| return ""; //$NON-NLS-1$ |
| } |
| return OFFSET_FORMAT.format(offset.longValue()); |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param parent |
| * parent shell |
| * @param results |
| * results to put the data into |
| */ |
| public OffsetDialog(Shell parent, Map<TmfTraceElement, Long> results) { |
| super(parent); |
| setShellStyle(getShellStyle() & ~SWT.APPLICATION_MODAL); |
| fOffsetMap = results; |
| fRefTimeMap = new HashMap<>(); |
| fTargetTimeMap = new HashMap<>(); |
| } |
| |
| @Override |
| protected boolean isResizable() { |
| return true; |
| } |
| |
| @Override |
| protected Control createDialogArea(Composite parent) { |
| getShell().setText(Messages.OffsetDialog_Title); |
| Composite area = (Composite) super.createDialogArea(parent); |
| Composite composite = new Composite(area, SWT.NONE); |
| composite.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| GridLayout gl = new GridLayout(); |
| gl.marginHeight = 0; |
| gl.marginWidth = 0; |
| composite.setLayout(new GridLayout()); |
| createBasicMessage(composite); |
| createButtonGroup(composite); |
| createAdvancedMessage(composite); |
| createViewer(composite); |
| |
| /* set label width hint equal to tree width */ |
| int widthHint = fViewer.getViewer().getTree().computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| GridData gd = (GridData) fBasicMessageLabel.getLayoutData(); |
| gd.widthHint = widthHint; |
| gd = (GridData) fAdvancedMessageLabel.getLayoutData(); |
| gd.widthHint = widthHint; |
| gd = (GridData) composite.getLayoutData(); |
| gd.heightHint = composite.computeSize(widthHint, SWT.DEFAULT).y; |
| setBasicMode(); |
| |
| TmfSignalManager.register(this); |
| composite.addDisposeListener(e -> TmfSignalManager.deregister(OffsetDialog.this)); |
| return area; |
| } |
| |
| private void createBasicMessage(final Composite parent) { |
| fBasicMessageLabel = new Label(parent, SWT.WRAP); |
| fBasicMessageLabel.setText(Messages.OffsetDialog_BasicMessage); |
| GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false); |
| gd.widthHint = 0; |
| gd.heightHint = SWT.DEFAULT; |
| fBasicMessageLabel.setLayoutData(gd); |
| } |
| |
| private void createButtonGroup(final Composite parent) { |
| fButtonGroup = new Group(parent, SWT.SHADOW_NONE); |
| fButtonGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); |
| fButtonGroup.setLayout(new RowLayout(SWT.HORIZONTAL)); |
| |
| final Button basicButton = new Button(fButtonGroup, SWT.RADIO); |
| basicButton.setText(Messages.OffsetDialog_BasicButton); |
| basicButton.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| if (!basicButton.getSelection() || !fAdvancedMode) { |
| return; |
| } |
| setBasicMode(); |
| parent.layout(); |
| } |
| }); |
| basicButton.setSelection(true); |
| |
| final Button advancedButton = new Button(fButtonGroup, SWT.RADIO); |
| advancedButton.setText(Messages.OffsetDialog_AdvancedButton); |
| advancedButton.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| if (!advancedButton.getSelection() || fAdvancedMode) { |
| return; |
| } |
| setAdvancedMode(); |
| parent.layout(); |
| } |
| }); |
| } |
| |
| private void createAdvancedMessage(final Composite parent) { |
| fAdvancedMessageLabel = new Label(parent, SWT.WRAP); |
| fAdvancedMessageLabel.setText(Messages.OffsetDialog_AdvancedMessage); |
| GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false); |
| gd.widthHint = 0; |
| gd.heightHint = SWT.DEFAULT; |
| fAdvancedMessageLabel.setLayoutData(gd); |
| } |
| |
| private void createViewer(Composite parent) { |
| |
| // Define the TableViewer |
| fViewer = new FilteredTree(parent, SWT.MULTI | SWT.H_SCROLL |
| | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER, new PatternFilter() { |
| @Override |
| protected boolean isLeafMatch(Viewer viewer, Object element) { |
| return wordMatches(((TmfTraceElement) element).getElementPath()); |
| } |
| }, true); |
| |
| // Make lines and make header visible |
| final Tree tree = fViewer.getViewer().getTree(); |
| tree.setHeaderVisible(true); |
| tree.setLinesVisible(true); |
| |
| TreeViewerFocusCellManager focusCellManager = new TreeViewerFocusCellManager(fViewer.getViewer(), new FocusCellOwnerDrawHighlighter(fViewer.getViewer())); |
| ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(fViewer.getViewer()); |
| TreeViewerEditor.create(fViewer.getViewer(), focusCellManager, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL |
| | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR |
| | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION); |
| |
| final TextCellEditor textCellEditor = new TextCellEditor(fViewer.getViewer().getTree(), SWT.RIGHT); |
| |
| fViewer.getViewer().setColumnProperties(new String[] { Messages.OffsetDialog_TraceName, Messages.OffsetDialog_ReferenceTime, Messages.OffsetDialog_OffsetTime }); |
| |
| TreeViewerColumn column = createTreeViewerColumn(Messages.OffsetDialog_TraceName, SWT.NONE); |
| column.setLabelProvider(new ColumnLabelProvider() { |
| @Override |
| public String getText(Object element) { |
| return ((TmfTraceElement) element).getElementPath(); |
| } |
| }); |
| |
| column = createTreeViewerColumn(Messages.OffsetDialog_OffsetTime, SWT.RIGHT); |
| column.setLabelProvider(new ColumnLabelProvider() { |
| @Override |
| public String getText(Object element) { |
| Long offset = fOffsetMap.get(element); |
| if (offset == null || offset == 0) { |
| return ""; //$NON-NLS-1$ |
| } |
| return super.getText(OFFSET_FORMAT.format(offset.longValue())); |
| } |
| }); |
| column.setEditingSupport(new OffsetEditingSupport(fViewer.getViewer(), textCellEditor)); |
| |
| column = createTreeViewerColumn("", SWT.NONE); //$NON-NLS-1$ |
| column.setLabelProvider(new ColumnLabelProvider() { |
| @Override |
| public String getText(Object element) { |
| return ""; //$NON-NLS-1$ |
| } |
| }); |
| column.getColumn().setWidth(TREE_EDITOR_MIN_WIDTH); |
| column.getColumn().setResizable(false); |
| fButtonViewerColumn = column; |
| |
| column = createTreeViewerColumn(Messages.OffsetDialog_ReferenceTime, SWT.RIGHT); |
| column.setLabelProvider(new ColumnLabelProvider() { |
| @Override |
| public String getText(Object element) { |
| return super.getText(fRefTimeMap.get(element)); |
| } |
| }); |
| column.setEditingSupport(new RefTimeEditingSupport(fViewer.getViewer(), textCellEditor)); |
| fRefTimeColumn = column.getColumn(); |
| |
| column = createTreeViewerColumn(Messages.OffsetDialog_TargetTime, SWT.RIGHT); |
| column.setLabelProvider(new ColumnLabelProvider() { |
| @Override |
| public String getText(Object element) { |
| return super.getText(fTargetTimeMap.get(element)); |
| } |
| }); |
| column.setEditingSupport(new TargetTimeEditingSupport(fViewer.getViewer(), textCellEditor)); |
| fTargetTimeColumn = column.getColumn(); |
| |
| List<TmfTraceElement> traces = new ArrayList<>(fOffsetMap.keySet()); |
| Collections.sort(traces, (o1, o2) -> { |
| IPath folder1 = new Path(o1.getElementPath()).removeLastSegments(1); |
| IPath folder2 = new Path(o2.getElementPath()).removeLastSegments(1); |
| if (folder1.equals(folder2)) { |
| return o1.getName().compareToIgnoreCase(o2.getName()); |
| } |
| if (folder1.isPrefixOf(folder2)) { |
| return 1; |
| } else if (folder2.isPrefixOf(folder1)) { |
| return -1; |
| } |
| return folder1.toString().compareToIgnoreCase(folder2.toString()); |
| }); |
| |
| fViewer.getViewer().setContentProvider(new ArrayTreeContentProvider()); |
| fViewer.getViewer().setInput(traces); |
| |
| /* add button as tree editors to fourth column of every item */ |
| for (TreeItem treeItem : tree.getItems()) { |
| TreeEditor treeEditor = new TreeEditor(tree); |
| Button applyButton = new Button(tree, SWT.PUSH); |
| applyButton.setText("<<"); //$NON-NLS-1$ |
| applyButton.setData(treeItem.getData()); |
| applyButton.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| TmfTraceElement traceElement = (TmfTraceElement) e.widget.getData(); |
| ITmfTimestamp targetTime = fTargetTimeMap.get(traceElement); |
| ITmfTimestamp refTime = fRefTimeMap.get(traceElement); |
| if (targetTime != null && refTime != null) { |
| long offset = targetTime.toNanos() - |
| refTime.toNanos(); |
| fOffsetMap.put(traceElement, offset); |
| fViewer.getViewer().update(traceElement, null); |
| } |
| } |
| }); |
| treeEditor.grabHorizontal = true; |
| treeEditor.minimumWidth = TREE_EDITOR_MIN_WIDTH; |
| treeEditor.setEditor(applyButton, treeItem, 2); |
| treeItem.setData(EDITOR_KEY, applyButton); |
| } |
| |
| /* put temporary values in maps to pack according to time formats */ |
| fRefTimeMap.put(traces.get(0), TmfTimestamp.fromNanos(0)); |
| fTargetTimeMap.put(traces.get(0), TmfTimestamp.fromNanos(0)); |
| fViewer.getViewer().update(traces.get(0), null); |
| for (final TreeColumn treeColumn : tree.getColumns()) { |
| if (treeColumn.getResizable()) { |
| treeColumn.pack(); |
| } |
| } |
| fRefTimeMap.clear(); |
| fTargetTimeMap.clear(); |
| fViewer.getViewer().update(traces.get(0), null); |
| |
| for (TmfTraceElement traceElement : fOffsetMap.keySet()) { |
| for (ITmfTrace parentTrace : TmfTraceManager.getInstance().getOpenedTraces()) { |
| for (ITmfTrace trace : TmfTraceManager.getTraceSet(parentTrace)) { |
| if (traceElement.getResource().equals(trace.getResource())) { |
| fRefTimeMap.put(traceElement, trace.getStartTime()); |
| fViewer.getViewer().update(traceElement, null); |
| break; |
| } |
| } |
| if (fRefTimeMap.get(traceElement) != null) { |
| break; |
| } |
| } |
| } |
| |
| /* open trace when double-clicking a tree item */ |
| tree.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| TmfTraceElement traceElement = (TmfTraceElement) e.item.getData(); |
| TmfOpenTraceHelper.openTraceFromElement(traceElement); |
| } |
| }); |
| |
| tree.setFocus(); |
| } |
| |
| private TreeViewerColumn createTreeViewerColumn(String title, int style) { |
| final TreeViewerColumn viewerColumn = new TreeViewerColumn(fViewer.getViewer(), style); |
| final TreeColumn column = viewerColumn.getColumn(); |
| column.setText(title); |
| column.setResizable(true); |
| return viewerColumn; |
| } |
| |
| private void setBasicMode() { |
| fAdvancedMode = false; |
| fRefTimeColumn.setData(WIDTH_KEY, fRefTimeColumn.getWidth()); |
| fTargetTimeColumn.setData(WIDTH_KEY, fTargetTimeColumn.getWidth()); |
| for (TreeItem treeItem : fViewer.getViewer().getTree().getItems()) { |
| Control editor = (Control) treeItem.getData(EDITOR_KEY); |
| editor.setVisible(false); |
| } |
| fTargetTimeColumn.setWidth(0); |
| fTargetTimeColumn.setResizable(false); |
| fRefTimeColumn.setWidth(0); |
| fRefTimeColumn.setResizable(false); |
| fButtonViewerColumn.getColumn().setWidth(0); |
| fAdvancedMessageLabel.setText(""); //$NON-NLS-1$ |
| } |
| |
| private void setAdvancedMode() { |
| fAdvancedMode = true; |
| fButtonViewerColumn.getColumn().setWidth(TREE_EDITOR_MIN_WIDTH); |
| fRefTimeColumn.setWidth((Integer) fRefTimeColumn.getData(WIDTH_KEY)); |
| fRefTimeColumn.setResizable(true); |
| fTargetTimeColumn.setWidth((Integer) fTargetTimeColumn.getData(WIDTH_KEY)); |
| fTargetTimeColumn.setResizable(true); |
| for (TreeItem treeItem : fViewer.getViewer().getTree().getItems()) { |
| Control editor = (Control) treeItem.getData(EDITOR_KEY); |
| editor.setVisible(true); |
| } |
| fAdvancedMessageLabel.setText(Messages.OffsetDialog_AdvancedMessage); |
| } |
| |
| /** |
| * Handler for the event selected signal |
| * |
| * @param signal |
| * the event selected signal |
| */ |
| @TmfSignalHandler |
| public void eventSelected(final TmfEventSelectedSignal signal) { |
| Display.getDefault().asyncExec(() -> { |
| for (TmfTraceElement traceElement : fOffsetMap.keySet()) { |
| if (traceElement.getResource().equals(signal.getEvent().getTrace().getResource())) { |
| fRefTimeMap.put(traceElement, signal.getEvent().getTimestamp()); |
| fViewer.getViewer().update(traceElement, null); |
| break; |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Handler for the time selected signal |
| * |
| * @param signal |
| * the event selected signal |
| */ |
| @TmfSignalHandler |
| public void timeSelected(final TmfSelectionRangeUpdatedSignal signal) { |
| Display.getDefault().asyncExec(() -> { |
| for (TmfTraceElement traceElement : fOffsetMap.keySet()) { |
| fTargetTimeMap.put(traceElement, signal.getBeginTime()); |
| fViewer.getViewer().update(traceElement, null); |
| } |
| }); |
| } |
| |
| /** |
| * Handler for the trace opened signal |
| * |
| * @param signal |
| * the trace opened signal |
| */ |
| @TmfSignalHandler |
| public void traceOpened(final TmfTraceOpenedSignal signal) { |
| Display.getDefault().asyncExec(() -> { |
| for (ITmfTrace trace : TmfTraceManager.getTraceSet(signal.getTrace())) { |
| for (TmfTraceElement traceElement : fOffsetMap.keySet()) { |
| if (traceElement.getResource().equals(trace.getResource())) { |
| if (fRefTimeMap.get(traceElement) == null) { |
| fRefTimeMap.put(traceElement, trace.getStartTime()); |
| fViewer.getViewer().update(traceElement, null); |
| } |
| break; |
| } |
| } |
| } |
| }); |
| } |
| } |