/**
*
*/
package fr.inria.soctrace.framesoc.ui.dialogs;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ColorUtil;
import fr.inria.linuxtools.tmf.ui.widgets.virtualtable.ColumnData;
import fr.inria.linuxtools.tmf.ui.widgets.virtualtable.TmfVirtualTable;
import fr.inria.soctrace.framesoc.ui.model.ITableColumn;
import fr.inria.soctrace.framesoc.ui.tracetable.TraceTableCache;
import fr.inria.soctrace.framesoc.ui.tracetable.TraceTableColumn;
import fr.inria.soctrace.framesoc.ui.tracetable.TraceTableColumnEnum;
import fr.inria.soctrace.framesoc.ui.tracetable.TraceTableRow;
import fr.inria.soctrace.lib.model.Trace;
import fr.inria.soctrace.lib.model.utils.SoCTraceException;
/**
* Trace filter dialog.
*
* <pre>
* TODO:
* - allow to hide columns, and store in local settings the hidden columns
* - allow to restore hidden columns
* </pre>
*
* @author "Generoso Pagano <generoso.pagano@inria.fr>"
*/
public class TraceFilterDialog extends Dialog {
private static final String TRACE_FILTER_DIALOG_TITLE = "Trace Search";
private static final String FILTER_HINT = "<filter>";
private static final String MESSAGE = "Check the traces to highlight them in the Traces view.";
private static final int DEFAULT_WIDTH = 1200;
private static final int DEFAULT_HEIGHT = 500;
private Label fStatusText;
private TmfVirtualTable fTable;
private TraceTableCache fCache;
private Set<Trace> fChecked;
private TableItem fFilterItem;
private int fNumTraces;
private List<TraceTableColumn> sortedColumns;
// SWT resources
private LocalResourceManager resourceManager = new LocalResourceManager(
JFaceResources.getResources());
private Color grayColor;
private Color blackColor;
private Font boldFont;
/**
* Keys for table data
*/
public interface Key {
/** Column object, set on a column */
String COLUMN_OBJ = "$field_id"; //$NON-NLS-1$
/** Filter text, set on a column */
String FILTER_TXT = "$fltr_txt"; //$NON-NLS-1$
/** Filter flag, set on the table */
String FILTER_FLAG = "$fltr_flag"; //$NON-NLS-1$
}
public TraceFilterDialog(Shell parentShell) {
super(parentShell);
}
public void init(List<Trace> traces, Set<Trace> checked) {
if (traces == null)
throw new NullPointerException();
if (checked == null)
throw new NullPointerException();
fCache = new TraceTableCache();
fCache.init(traces);
fChecked = checked;
fNumTraces = traces.size();
}
public Set<Trace> getChecked() {
return fChecked;
}
@Override
protected boolean isResizable() {
return true;
}
@Override
protected Control createDialogArea(Composite parent) {
if (fCache == null)
throw new NullPointerException("Dialog not initialized");
getShell().setText(TRACE_FILTER_DIALOG_TITLE);
Composite composite = (Composite) super.createDialogArea(parent);
GridLayout gridLayout = (GridLayout) composite.getLayout();
gridLayout.verticalSpacing = 2;
gridLayout.marginWidth = 2;
gridLayout.marginHeight = 2;
Label messageLabel = createMessage(composite);
TmfVirtualTable table = createTable(composite);
fStatusText = new Label(composite, SWT.NONE);
fStatusText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
fStatusText.setText(getStatus(fNumTraces, fCache.getItemCount()));
if (fCache.getItemCount() == 0) {
messageLabel.setEnabled(false);
table.setEnabled(false);
}
return composite;
}
private TmfVirtualTable createTable(Composite composite) {
// Create the virtual table
final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.CHECK;
fTable = new TmfVirtualTable(composite, style);
// Set the table layout
final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
layoutData.widthHint = DEFAULT_WIDTH;
layoutData.heightHint = DEFAULT_HEIGHT;
fTable.setLayoutData(layoutData);
// Create resources
createResources();
// Some cosmetic enhancements
fTable.setHeaderVisible(true);
fTable.setLinesVisible(true);
// Set the columns
setColumnHeaders();
// Set the sorter
setColumnSorter();
// Set the frozen row for header row
fTable.setFrozenRowCount(1);
// Create the header row cell editor
createHeaderEditor();
// Handle the table item requests
fTable.addListener(SWT.SetData, new Listener() {
@Override
public void handleEvent(final Event event) {
final TableItem item = (TableItem) event.item;
int index = event.index - 1; // -1 for the header row
if (event.index == 0) {
setHeaderRowItemData(item);
return;
}
TraceTableRow row = fCache.get(index);
item.setText(getItemStrings(row));
if (fChecked.contains(row.getTrace())) {
item.setChecked(true);
updateFilterCheck();
} else {
// we have to uncheck the item to clean any check during scroll
// e.g., if we scroll and the line that before was in our position
// was checked, the checked status of the item would remain.
item.setChecked(false);
updateFilterCheck();
}
}
});
fTable.setItemCount(1 + fCache.getItemCount()); // +1 for the header
fTable.setSelection(0);
fTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent e) {
e.doit = false;
if (e.character == SWT.ESC) {
fTable.refresh();
} else if (e.character == SWT.DEL) {
cleanFilter();
}
}
});
fTable.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
if (event.detail != SWT.CHECK)
return;
TableItem item = (TableItem) event.item;
int index = fTable.indexOf(item);
boolean checked = item.getChecked();
if (index == 0) {
for (TableItem it : fTable.getItems()) {
it.setChecked(checked);
}
for (int i = 0; i < fCache.getItemCount(); i++) {
if (checked) {
fChecked.add(fCache.get(i).getTrace());
} else {
fChecked.remove(fCache.get(i).getTrace());
}
}
} else {
index = index - 1; // exclude header
if (checked) {
fChecked.add(fCache.get(index).getTrace());
} else {
fChecked.remove(fCache.get(index).getTrace());
}
updateFilterCheck();
}
}
});
return fTable;
}
private void updateFilterCheck() {
if (fFilterItem == null) {
return;
}
for (int i = 0; i < fCache.getItemCount(); i++) {
if (!fChecked.contains(fCache.get(i).getTrace())) {
fFilterItem.setChecked(false);
return;
}
}
fFilterItem.setChecked(true);
}
private void cleanFilter() {
if (fTable.getData(Key.FILTER_FLAG) == null) {
return;
}
fTable.clearAll();
for (final TableColumn column : fTable.getColumns()) {
column.setData(Key.FILTER_TXT, null);
}
fTable.setData(Key.FILTER_FLAG, null);
fCache.cleanFilter();
fCache.applyFilter();
fTable.setItemCount(1 + fCache.getItemCount());
fTable.setSelection(0);
fTable.refresh();
fStatusText.setText(getStatus(fNumTraces, fCache.getItemCount()));
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
// overridden to remove focus from OK button (last parameter false)
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, false);
}
private String[] getItemStrings(TraceTableRow row) {
String values[] = new String[fCache.getTableColumns().values().size()];
int i = 0;
for (TraceTableColumn col : sortedColumns) {
try {
values[i] = row.get(col);
} catch (SoCTraceException e) {
e.printStackTrace();
} finally {
i++;
}
}
return values;
}
private void setHeaderRowItemData(final TableItem item) {
fFilterItem = item;
item.setForeground(grayColor);
for (int i = 0; i < fTable.getColumns().length; i++) {
final TableColumn column = fTable.getColumns()[i];
final String filter = (String) column.getData(Key.FILTER_TXT);
if (filter == null) {
item.setText(i, FILTER_HINT);
item.setForeground(i, grayColor);
item.setFont(i, fTable.getFont());
} else {
item.setText(i, filter);
item.setForeground(i, blackColor);
item.setFont(i, boldFont);
}
}
}
private void createResources() {
grayColor = resourceManager.createColor(ColorUtil.blend(fTable.getBackground().getRGB(),
fTable.getForeground().getRGB()));
blackColor = fTable.getDisplay().getSystemColor(SWT.COLOR_BLACK);
boldFont = resourceManager.createFont(FontDescriptor.createFrom(fTable.getFont()).setStyle(
SWT.BOLD));
}
private void setColumnHeaders() {
ColumnData columnData[] = new ColumnData[fCache.getTableColumns().values().size()];
// Keep the same column order as enum and put custom param at the end
sortedColumns = new ArrayList<TraceTableColumn>();
for (TraceTableColumnEnum col : TraceTableColumnEnum.values()) {
sortedColumns.add(fCache.getTableColumns().get(col.getHeader()));
}
for (TraceTableColumn col : fCache.getTableColumns().values()) {
if (!sortedColumns.contains(col))
sortedColumns
.add(fCache.getTableColumns().get(col.getHeader()));
}
int i = 0;
for (TraceTableColumn col : sortedColumns) {
columnData[i++] = new ColumnData(col.getShortName(), col.getWidth(), SWT.LEFT);
}
fTable.setColumnHeaders(columnData);
i = 0;
for (TraceTableColumn col : sortedColumns) {
fTable.getColumns()[i++].setData(Key.COLUMN_OBJ, col);
}
}
private void setColumnSorter() {
Listener sortListener = new Listener() {
@Override
public void handleEvent(Event e) {
// determine new sort column and direction
TableColumn sortColumn = fTable.getSortColumn();
TableColumn currentColumn = (TableColumn) e.widget;
int dir = fTable.getSortDirection();
if (sortColumn == currentColumn) {
dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
} else {
fTable.setSortColumn(currentColumn);
dir = SWT.UP;
}
// sort the data based on column and direction
fCache.sort((ITableColumn)currentColumn.getData(Key.COLUMN_OBJ), dir);
// update data displayed in table
fTable.setSortDirection(dir);
fTable.clearAll();
fTable.setItemCount(1+fCache.getItemCount()); // +1 for header
fTable.refresh();
}
};
for (TableColumn tc : fTable.getColumns()) {
tc.addListener(SWT.Selection, sortListener);
}
}
private void createHeaderEditor() {
final TableEditor tableEditor = fTable.createTableEditor();
tableEditor.horizontalAlignment = SWT.LEFT;
tableEditor.verticalAlignment = SWT.CENTER;
tableEditor.grabHorizontal = true;
tableEditor.minimumWidth = 50;
// Handle the header row selection
fTable.addMouseListener(new MouseAdapter() {
int columnIndex;
TableColumn column;
TableItem item;
@Override
public void mouseDown(final MouseEvent event) {
if (event.button != 1) {
return;
}
// Identify the selected row
final Point point = new Point(event.x, event.y);
item = fTable.getItem(point);
// Header row selected
if ((item != null) && (fTable.indexOf(item) == 0)) {
// Identify the selected column
columnIndex = -1;
for (int i = 0; i < fTable.getColumns().length; i++) {
final Rectangle rect = item.getBounds(i);
if (rect.contains(point)) {
columnIndex = i;
break;
}
}
if (columnIndex == -1) {
return;
}
column = fTable.getColumns()[columnIndex];
// The control that will be the editor must be a child of
// the Table
final Text newEditor = (Text) fTable.createTableEditorControl(Text.class);
final String headerString = (String) column.getData(Key.FILTER_TXT);
if (headerString != null) {
newEditor.setText(headerString);
}
newEditor.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(final FocusEvent e) {
final boolean changed = updateHeader(newEditor.getText());
if (changed) {
applyHeader();
}
}
});
newEditor.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent e) {
if (e.character == SWT.CR) {
updateHeader(newEditor.getText());
applyHeader();
// Set focus on the table so that the next
// carriage return goes to the next result
TraceFilterDialog.this.fTable.setFocus();
} else if (e.character == SWT.ESC) {
tableEditor.getEditor().dispose();
}
}
});
newEditor.selectAll();
newEditor.setFocus();
tableEditor.setEditor(newEditor, item, columnIndex);
}
}
/*
* returns true if the value was changed
*/
private boolean updateHeader(final String text) {
if (text.trim().length() > 0) {
try {
final String regex = regexFix(text);
Pattern.compile(regex);
if (regex.equals(column.getData(Key.FILTER_TXT))) {
tableEditor.getEditor().dispose();
return false;
}
ITableColumn col = (ITableColumn) column.getData(Key.COLUMN_OBJ);
fCache.setFilterText(col, regex);
column.setData(Key.FILTER_TXT, regex);
} catch (final PatternSyntaxException ex) {
tableEditor.getEditor().dispose();
MessageDialog.openError(PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getShell(), ex.getDescription(),
ex.getMessage());
return false;
}
} else {
if (column.getData(Key.FILTER_TXT) == null) {
tableEditor.getEditor().dispose();
return false;
}
ITableColumn col = (ITableColumn) column.getData(Key.COLUMN_OBJ);
fCache.setFilterText(col, "");
column.setData(Key.FILTER_TXT, null);
}
return true;
}
public String regexFix(String pattern) {
String ret = pattern;
// if the pattern does not contain one of the expressions .* !^
// (at the beginning) $ (at the end), then a .* is added at the
// beginning and at the end of the pattern
if (!(ret.indexOf(".*") >= 0 || ret.charAt(0) == '^' || ret.charAt(ret.length() - 1) == '$')) { //$NON-NLS-1$
ret = ".*" + ret + ".*"; //$NON-NLS-1$ //$NON-NLS-2$
}
return ret;
}
private void applyHeader() {
fTable.clearAll();
fCache.applyFilter();
tableEditor.getEditor().dispose();
fTable.setData(Key.FILTER_FLAG, true);
fTable.setItemCount(1 + fCache.getItemCount()); // +1 for header
fTable.refresh();
fStatusText.setText(getStatus(fNumTraces, fCache.getItemCount()));
}
});
}
private Label createMessage(Composite composite) {
Label messageLabel = new Label(composite, SWT.NONE);
messageLabel.setText(MESSAGE);
messageLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
return messageLabel;
}
private String getStatus(int traces, int matched) {
StringBuilder sb = new StringBuilder();
sb.append("Filter matched ");
sb.append(matched);
sb.append(" of ");
sb.append(traces);
sb.append(" traces");
return sb.toString();
}
}