/******************************************************************************* * Copyright (c) 2014 Ericsson * * 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: * 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.Comparator; 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.DisposeEvent; import org.eclipse.swt.events.DisposeListener; 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(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent 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, new Comparator<TmfTraceElement>() { @Override public int compare(TmfTraceElement o1, TmfTraceElement 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(new Runnable() { @Override public void run() { 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(new Runnable() { @Override public void run() { 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(new Runnable() { @Override public void run() { 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; } } } } }); } }