/******************************************************************************* * Copyright (c) 2012-2015 INRIA. * 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: * Generoso Pagano - initial API and implementation ******************************************************************************/ package fr.inria.soctrace.framesoc.ui.views; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.draw2d.ColorConstants; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.ui.IActionBars; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import org.eclipse.wb.swt.ResourceManager; import fr.inria.soctrace.framesoc.core.FramesocManager; import fr.inria.soctrace.framesoc.core.bus.FramesocBus; import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopic; import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopicList; import fr.inria.soctrace.framesoc.core.bus.FramesocBusVariable; import fr.inria.soctrace.framesoc.core.bus.IFramesocBusListener; import fr.inria.soctrace.framesoc.ui.Activator; import fr.inria.soctrace.framesoc.ui.dialogs.NewParamDialog; import fr.inria.soctrace.framesoc.ui.loaders.TraceDetailsLoader; import fr.inria.soctrace.framesoc.ui.loaders.TraceLoader.TraceChange; import fr.inria.soctrace.framesoc.ui.model.DetailsTableRow; import fr.inria.soctrace.framesoc.ui.model.TraceNode; import fr.inria.soctrace.framesoc.ui.perspective.FramesocViews; import fr.inria.soctrace.framesoc.ui.utils.TraceSelection; import fr.inria.soctrace.lib.model.Trace; import fr.inria.soctrace.lib.model.utils.ModelConstants.TimeUnit; import fr.inria.soctrace.lib.model.utils.SoCTraceException; import fr.inria.soctrace.lib.storage.utils.DBModelConstants.TraceTableModel; /** * View displaying trace metadata. * * @author "Generoso Pagano <generoso.pagano@inria.fr>" */ public class TraceDetailsView extends ViewPart implements IFramesocBusListener { /** * The ID of the view as specified by the extension. */ public static final String ID = FramesocViews.TRACE_DETAILS_VIEW_ID; /** * Table Columns */ private final static String COL_PROPERTY = "Property"; private final static String COL_VALUE = "Value"; /** * View header for multi trace selection */ private final static String MULTI_TRACE_SELECTION = "Multi-trace selection"; /** * Followed topics */ private FramesocBusTopicList topics; /** * Viewer */ private TableViewer viewer; /** * Data loader */ private TraceDetailsLoader traceDetailsLoader = new TraceDetailsLoader(); /** * Current traces */ private List<Trace> currentTraces = null; /** * Editing support */ private ValueEditingSupport editingSupport; /** * Delete param action */ private Action delParamsAction; /** * The listener we register with the selection service */ private ISelectionListener listener = new ISelectionListener() { public void selectionChanged(IWorkbenchPart sourcepart, ISelection selection) { // we ignore our own selections if (sourcepart != TraceDetailsView.this) { storeSelection(selection); showSelection(); } } }; private Action addParamAction; /** * Constructor. Register to Framesoc Notification Bus. */ public TraceDetailsView() { topics = new FramesocBusTopicList(this); topics.addTopic(FramesocBusTopic.TOPIC_UI_FOCUSED_TRACE); topics.addTopic(FramesocBusTopic.TOPIC_UI_SYSTEM_INITIALIZED); topics.addTopic(FramesocBusTopic.TOPIC_UI_TRACES_SYNCHRONIZED); topics.registerAll(); } @Override public void createPartControl(Composite parent) { // create viewer and editing support viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); editingSupport = new ValueEditingSupport(viewer); createColumns(); final Table table = viewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(true); viewer.setContentProvider(ArrayContentProvider.getInstance()); viewer.setInput(traceDetailsLoader.getProperties()); // viewer selection listener viewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); Object[] rows = selection.toArray(); boolean enable = (rows.length > 0); for (Object o : rows) { DetailsTableRow row = (DetailsTableRow) o; if (!row.isCustomParam()) { enable = false; break; } } delParamsAction.setEnabled(enable); } }); // build toolbar IActionBars actionBars = getViewSite().getActionBars(); IToolBarManager toolBar = actionBars.getToolBarManager(); toolBar.add(editingSupport.createResetAction()); toolBar.add(editingSupport.createSaveAction()); toolBar.add(new Separator()); toolBar.add(createAddParamAction()); toolBar.add(createDelParamsAction()); // register the selection listener getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(listener); // provide our selection for other viewers getSite().setSelectionProvider(viewer); } private boolean editingClean() { // clean editing before if (currentTraces.size() > 0 && editingSupport.isModified()) { MessageDialog .openWarning( getSite().getShell(), "Warning", "There are unsaved changes in trace details. Save or rollback them before adding or removing properties."); return false; } return true; } private IAction createDelParamsAction() { delParamsAction = new Action("Delete property") { @Override public void run() { if (!editingClean()) return; // get the selection IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); Object[] rows = selection.toArray(); List<String> params = new ArrayList<>(rows.length); StringBuilder sb = new StringBuilder(); sb.append("Remove the following properties from the selected traces?\n\n"); sb.append("Properties to remove:\n"); for (Object row : rows) { String name = (String) ((DetailsTableRow) row).getName(); params.add(name); sb.append("* "); sb.append(name); sb.append("\n"); } if (!MessageDialog.openQuestion(getSite().getShell(), "Remove properties", sb.toString())) { return; } // delete corresponding params in all selected traces List<Trace> traces = getSelectedTraces(); try { FramesocManager.getInstance().deleteParams(traces, params); showSelection(); } catch (SoCTraceException e) { MessageDialog.openError(getSite().getShell(), "Error", "An error occurred removing the properties.\n" + e.getMessage()); e.printStackTrace(); } } }; delParamsAction.setImageDescriptor(ResourceManager.getPluginImageDescriptor( Activator.PLUGIN_ID, "icons/minus.png")); delParamsAction.setEnabled(false); return delParamsAction; } private IAction createAddParamAction() { addParamAction = new Action("Add property") { @Override public void run() { if (!editingClean()) return; NewParamDialog dlg = new NewParamDialog(getSite().getShell()); if (dlg.open() == Dialog.OK) { // add the new param in all selected traces List<Trace> traces = getSelectedTraces(); try { FramesocManager.getInstance().saveParam(traces, dlg.getName(), dlg.getType(), dlg.getValue()); showSelection(); } catch (SoCTraceException e) { MessageDialog.openError(getSite().getShell(), "Error", "An error occurred saving the properties.\n" + e.getMessage()); e.printStackTrace(); } } } }; addParamAction.setImageDescriptor(ResourceManager.getPluginImageDescriptor( Activator.PLUGIN_ID, "icons/plus.png")); addParamAction.setEnabled(false); return addParamAction; } private List<Trace> getSelectedTraces() { IStructuredSelection selection = (IStructuredSelection) FramesocBus.getInstance() .getVariable(FramesocBusVariable.TRACE_VIEW_CURRENT_TRACE_SELECTION); Object[] traceNodes = selection.toArray(); List<Trace> traces = new ArrayList<>(traceNodes.length); for (Object node : traceNodes) { traces.add(((TraceNode) node).getTrace()); } return traces; } @Override public void setFocus() { viewer.getControl().setFocus(); } @Override public void dispose() { // unregister listeners topics.unregisterAll(); getSite().getWorkbenchWindow().getSelectionService().removeSelectionListener(listener); super.dispose(); } private void showSelection(Trace selection) { currentTraces = new LinkedList<Trace>(); currentTraces.add(selection); showSelection(); } private void storeSelection(ISelection selection) { if (!TraceSelection.isSelectionValid(selection)) return; currentTraces = TraceSelection.getTracesFromSelection(selection); } private void showSelection() { if (currentTraces == null) return; // manage old trace and clean editing support if (currentTraces.size() > 0 && editingSupport.isModified()) { if (MessageDialog.openQuestion(getSite().getShell(), "Save trace details", "There are unsaved changes in trace details, do you want to save them?")) editingSupport.save(); editingSupport.clean(); } // display current selection if (currentTraces.size() == 1) { setContentDescription("Trace: " + currentTraces.iterator().next().getAlias()); traceDetailsLoader.load(currentTraces.iterator().next()); } else { setContentDescription("Multi-trace selection"); traceDetailsLoader.load(currentTraces); } List<DetailsTableRow> prop = traceDetailsLoader.getProperties(); if (prop != null && prop.size() > 0) { viewer.setInput(prop); addParamAction.setEnabled(true); } } // Utilities private void createColumns() { TableViewerColumn col = createTableViewerColumn(COL_PROPERTY, 200, 0); col.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return ((DetailsTableRow) element).getName(); } @Override public Image getImage(Object element) { if (((DetailsTableRow) element).isCustomParam()) return PlatformUI.getWorkbench().getSharedImages() .getImage(ISharedImages.IMG_DEF_VIEW); else return PlatformUI.getWorkbench().getSharedImages() .getImage(ISharedImages.IMG_OBJS_INFO_TSK); } }); col = createTableViewerColumn(COL_VALUE, 100, 1); col.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return ((DetailsTableRow) element).getValue(); } @Override public org.eclipse.swt.graphics.Color getForeground(Object element) { if (((DetailsTableRow) element).isReadOnly()) return ColorConstants.gray; return ColorConstants.black; } }); col.setEditingSupport(editingSupport); } private TableViewerColumn createTableViewerColumn(String title, int bound, final int colNumber) { final TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.NONE); final TableColumn column = viewerColumn.getColumn(); column.setText(title); column.setWidth(bound); column.setResizable(true); column.setMoveable(true); return viewerColumn; } @Override public void handle(FramesocBusTopic topic, Object data) { if (topic.equals(FramesocBusTopic.TOPIC_UI_FOCUSED_TRACE) && data != null) { showSelection((Trace) data); } else if (topic.equals(FramesocBusTopic.TOPIC_UI_SYSTEM_INITIALIZED)) { // clean the viewer content currentTraces = null; viewer.setInput(null); viewer.refresh(); } else if (topic.equals(FramesocBusTopic.TOPIC_UI_TRACES_SYNCHRONIZED)) { if (currentTraces != null) { @SuppressWarnings("unchecked") Map<TraceChange, List<Trace>> traceChangeMap = ((Map<TraceChange, List<Trace>>) data); boolean reload = false; Iterator<Trace> it = currentTraces.iterator(); while (it.hasNext()) { Trace trace = it.next(); if (traceChangeMap.get(TraceChange.REMOVE).contains(trace)) { it.remove(); reload = true; } else if (traceChangeMap.get(TraceChange.UPDATE).contains(trace)) { reload = true; } } // all selected traces have been deleted if (currentTraces.size() == 0) { viewer.setInput(null); addParamAction.setEnabled(false); } // some trace has been removed or updated, and there are still selected traces else if (reload) { traceDetailsLoader.load(currentTraces); viewer.setInput(traceDetailsLoader.getProperties()); } } } } /** * Editing support class */ private class ValueEditingSupport extends EditingSupport { private final ImageDescriptor imgSave; private final ImageDescriptor imgReset; private Action saveAction; private Action resetAction; private boolean modified; private boolean editable; private final TableViewer viewer; private final TextCellEditor textEditor; private final ComboBoxCellEditor timeUnitEditor; private final String[] UNITS; public ValueEditingSupport(TableViewer viewer) { super(viewer); this.editable = true; this.modified = false; this.viewer = viewer; this.textEditor = new TextCellEditor(viewer.getTable()); // time unit TimeUnit[] units = TimeUnit.values(); UNITS = new String[units.length]; for (int i = 0; i < units.length; i++) { UNITS[i] = units[i].getLabel(); } this.timeUnitEditor = new ComboBoxCellEditor(viewer.getTable(), UNITS, SWT.READ_ONLY); // images imgSave = ResourceManager.getPluginImageDescriptor(Activator.PLUGIN_ID, "icons/save.png"); imgReset = ResourceManager.getPluginImageDescriptor(Activator.PLUGIN_ID, "icons/load.png"); } @Override protected CellEditor getCellEditor(Object element) { if (isTimeUnit(element)) { return timeUnitEditor; } else { return textEditor; } } @Override protected boolean canEdit(Object element) { return editable && !((DetailsTableRow) element).isReadOnly(); } @Override protected Object getValue(Object element) { String value = ((DetailsTableRow) element).getValue(); if (isTimeUnit(element)) { return getIndex(value); } else { return value; } } @Override protected void setValue(Object element, Object value) { DetailsTableRow row = ((DetailsTableRow) element); if (String.valueOf(value).equals(row.getValue())) return; if (isTimeUnit(element)) { row.setValue(UNITS[(Integer) value]); } else { row.setValue(String.valueOf(value)); } viewer.update(element, null); modified = true; saveAction.setEnabled(true); resetAction.setEnabled(true); if (currentTraces.size() == 1) { setContentDescription("*Trace: " + currentTraces.iterator().next().getAlias()); } else { setContentDescription("*" + MULTI_TRACE_SELECTION); } } public boolean isModified() { return modified; } public void save() { try { printCurrentTraces(); traceDetailsLoader.store(currentTraces); } catch (SoCTraceException e) { MessageDialog.openError(getSite().getShell(), "Exception", e.getMessage()); } if (currentTraces.size() == 1) { traceDetailsLoader.load(currentTraces.iterator().next()); setContentDescription("Trace: " + currentTraces.iterator().next().getAlias()); } else { traceDetailsLoader.load(currentTraces); setContentDescription(MULTI_TRACE_SELECTION); } viewer.setInput(traceDetailsLoader.getProperties()); } public void clean() { if (currentTraces.size() == 1) { setContentDescription("Trace: " + currentTraces.iterator().next().getAlias()); } else { setContentDescription(MULTI_TRACE_SELECTION); } modified = false; editable = true; saveAction.setEnabled(false); resetAction.setEnabled(false); } // utilities private int getIndex(String s) { for (int i = 0; i < UNITS.length; i++) { if (s.equals(UNITS[i])) return i; } return -1; } private boolean isTimeUnit(Object element) { DetailsTableRow row = (DetailsTableRow) element; return row.getName().equals(TraceTableModel.TIMEUNIT.getDescription()); } public Action createSaveAction() { saveAction = new Action("Save changes") { @Override public void run() { if (!modified) return; // just to be robust viewer.getTable().forceFocus(); // store the last edited field too save(); clean(); } }; saveAction.setImageDescriptor(imgSave); saveAction.setEnabled(false); return saveAction; } public Action createResetAction() { resetAction = new Action("Reset changes") { @Override public void run() { if (!modified) return; // just to be robust clean(); traceDetailsLoader.load(currentTraces); viewer.setInput(traceDetailsLoader.getProperties()); viewer.getTable().forceFocus(); // avoid editing during saving } }; resetAction.setImageDescriptor(imgReset); //resetAction.setEnabled(false); return resetAction; } } private void printCurrentTraces() { for (Trace t : currentTraces) { t.print(true); } } }