blob: 50175cac093b81ed6a6a75bd1510cb1ca33bb984 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}
});
}
}