/*
* ARX: Powerful Data Anonymization
* Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.deidentifier.arx.gui.view.impl.explore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.deidentifier.arx.ARXLattice;
import org.deidentifier.arx.ARXLattice.ARXNode;
import org.deidentifier.arx.ARXResult;
import org.deidentifier.arx.gui.Controller;
import org.deidentifier.arx.gui.model.ModelEvent;
import org.deidentifier.arx.gui.model.ModelEvent.ModelPart;
import org.deidentifier.arx.gui.model.ModelNodeFilter;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.SWTUtil;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.TableItem;
import de.linearbits.swt.table.DynamicTable;
import de.linearbits.swt.table.DynamicTableColumn;
/**
* This class implements a list view on selected nodes.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class ViewList extends ViewSolutionSpace {
/** Are we on linux*/
private static final boolean IS_LINUX = isLinux();
/**
* Are we on linux?
* @return
*/
private static final boolean isLinux() {
String os = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
return !(os.indexOf("win") >= 0 || os.indexOf("mac") >= 0); //$NON-NLS-1$ //$NON-NLS-2$
}
/** The table. */
private final DynamicTable table;
/** The list. */
private final List<ARXNode> list = new ArrayList<ARXNode>();
/** The listener. */
private Listener listener;
/** Color */
private Color background = null;
/** Map */
private Map<Color, Image> symbols = new HashMap<Color, Image>();
/**
* Contructor
*
* @param parent
* @param controller
*/
public ViewList(final Composite parent, final Controller controller) {
super(parent, controller);
table = SWTUtil.createTableDynamic(super.getPrimaryComposite(), SWT.SINGLE | SWT.VIRTUAL | SWT.BORDER | SWT.V_SCROLL | SWT.FULL_SELECTION);
table.setLayoutData(SWTUtil.createFillGridData());
table.setHeaderVisible(true);
table.addSelectionListener(new SelectionAdapter(){
@Override
public void widgetSelected(SelectionEvent arg0) {
ARXNode node = list.get(table.getSelectionIndex());
ViewList.this.actionSelectNode(node);
}
});
table.addMouseListener(new MouseAdapter(){
@Override
public void mouseUp(MouseEvent arg0) {
if (arg0.button == 3) {
if (getSelectedNode() != null) {
Point display = table.toDisplay(arg0.x, arg0.y);
getModel().setSelectedNode(getSelectedNode());
controller.update(new ModelEvent(ViewList.this,
ModelPart.SELECTED_NODE, getSelectedNode()));
actionShowMenu(display.x, display.y);
}
}
}
});
final DynamicTableColumn column1 = new DynamicTableColumn(table, SWT.LEFT);
column1.setText(Resources.getMessage("ListView.1")); //$NON-NLS-1$
column1.setWidth("20%", "100px"); //$NON-NLS-1$ //$NON-NLS-2$
final DynamicTableColumn column0 = new DynamicTableColumn(table, SWT.LEFT);
column0.setText(" "); //$NON-NLS-1$
column0.setWidth("30px"); //$NON-NLS-1$
final DynamicTableColumn column4 = new DynamicTableColumn(table, SWT.LEFT);
column4.setText(Resources.getMessage("ListView.2")); //$NON-NLS-1$
column4.setWidth("10%", "100px"); //$NON-NLS-1$ //$NON-NLS-2$
final DynamicTableColumn column5 = new DynamicTableColumn(table, SWT.LEFT);
column5.setText(" "); //$NON-NLS-1$
column5.setWidth("30px"); //$NON-NLS-1$
final DynamicTableColumn column2 = new DynamicTableColumn(table, SWT.LEFT);
column2.setText(Resources.getMessage("ListView.3")); //$NON-NLS-1$
column2.setWidth("35%", "100px"); //$NON-NLS-1$ //$NON-NLS-2$
final DynamicTableColumn column3 = new DynamicTableColumn(table, SWT.LEFT);
column3.setText(Resources.getMessage("ListView.4")); //$NON-NLS-1$
column3.setWidth("35%", "100px"); //$NON-NLS-1$ //$NON-NLS-2$
table.setItemCount(0);
column0.pack();
column1.pack();
column2.pack();
column3.pack();
column4.pack();
column5.pack();
// Create tooltip listener
// TODO: Does not work on Windows
Listener tableListener = new Listener() {
private TableItem previousHighlighted = null;
public void handleEvent(Event event) {
if (previousHighlighted != null) {
if (!previousHighlighted.isDisposed()) {
previousHighlighted.setBackground(background);
}
}
TableItem item = table.getItem(new Point(event.x, event.y));
if (item != null) {
item.setBackground(GUIHelper.COLOR_GRAY);
previousHighlighted = item;
ARXNode node = (ARXNode) item.getData();
if (node != null) {
table.redraw();
table.setToolTipText(getTooltipDecorator().decorate(node));
}
}
}
};
table.addListener(SWT.MouseMove, tableListener);
table.addListener(SWT.MouseExit, tableListener);
}
@Override
public void dispose() {
super.dispose();
for (Entry<Color, Image> entry : symbols.entrySet()) {
entry.getValue().dispose();
}
symbols.clear();
}
/**
* Resets the view.
*/
@Override
public void reset() {
super.reset();
table.setRedraw(false);
for (final TableItem i : table.getItems()) {
i.dispose();
}
list.clear();
table.setRedraw(true);
if (listener != null) {
table.removeListener(SWT.SetData, listener);
}
SWTUtil.disable(table);
}
/**
* Creates an item in the list.
*
* @param item
* @param index
*/
private void createItem(final TableItem item, final int index) {
final ARXNode node = list.get(index);
final String transformation = Arrays.toString(node.getTransformation());
item.setText(0, transformation);
final String anonymity = node.getAnonymity().toString();
item.setText(2, anonymity);
String min = null;
if (node.getLowestScore() != null) {
min = node.getLowestScore().toString() +
" [" + SWTUtil.getPrettyString(asRelativeValue(node.getLowestScore())) + "%]"; //$NON-NLS-1$ //$NON-NLS-2$
} else {
min = Resources.getMessage("ListView.7"); //$NON-NLS-1$
}
item.setText(4, min);
String max = null;
if (node.getHighestScore() != null) {
max = node.getHighestScore().toString() +
" [" + SWTUtil.getPrettyString(asRelativeValue(node.getHighestScore())) + "%]"; //$NON-NLS-1$ //$NON-NLS-2$
} else {
max = Resources.getMessage("ListView.10"); //$NON-NLS-1$
}
item.setText(5, max);
item.setData(node);
item.setImage(1, getSymbol(super.getInnerColor(node)));
item.setImage(3, getSymbol(super.getUtilityColor(node)));
this.background = this.background != null ? this.background : item.getBackground();
}
/**
* Dynamically creates an image with the given color
* @param color
* @return
*/
private Image getSymbol(Color color) {
// Check cache
if (symbols.containsKey(color)) {
return symbols.get(color);
}
// Define
final int WIDTH = 16;
final int HEIGHT = 16;
// "Fix" for Bug #50163
Image image = IS_LINUX ? getTransparentImage(table.getDisplay(), WIDTH, HEIGHT) :
new Image(table.getDisplay(), WIDTH, HEIGHT);
// Prepare
GC gc = new GC(image);
gc.setBackground(color);
// Render
if (!IS_LINUX) {
gc.fillRectangle(0, 0, WIDTH, HEIGHT);
} else {
gc.setAntialias(SWT.ON);
gc.fillOval(0, 0, WIDTH, HEIGHT);
gc.setAntialias(SWT.OFF);
}
// Cleanup
gc.dispose();
// Store in cache and return
symbols.put(color, image);
return image;
}
/**
* Creates a transparent image
* @param display
* @param width
* @param height
* @return
*/
private Image getTransparentImage(Display display, int width, int height) {
ImageData imData = new ImageData(width,
height,
24,
new PaletteData(0xff0000,
0x00ff00,
0x0000ff));
imData.setAlpha(0, 0, 0);
Arrays.fill(imData.alphaData, (byte) 0);
return new Image(display, imData);
}
/**
* Updates the list.
*
* @param result
* @param filter
*/
private void update(final ARXResult result, final ModelNodeFilter filter) {
if (result == null || result.getLattice() == null) return;
if (filter == null) return;
getController().getResources().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!table.isEnabled()) {
SWTUtil.enable(table);
}
table.setRedraw(false);
for (final TableItem i : table.getItems()) {
i.dispose();
}
list.clear();
final ARXLattice l = result.getLattice();
for (final ARXNode[] level : l.getLevels()) {
for (final ARXNode node : level) {
if (filter.isAllowed(result.getLattice(), node)) {
list.add(node);
}
}
}
Collections.sort(list, new Comparator<ARXNode>() {
@Override
public int compare(final ARXNode arg0,
final ARXNode arg1) {
return arg0.getHighestScore()
.compareTo(arg1.getHighestScore());
}
});
// Check
if (list.size() > getModel().getMaxNodesInViewer()) {
list.clear();
}
if (listener != null) {
table.removeListener(SWT.SetData, listener);
}
listener = new Listener() {
@Override
public void handleEvent(final Event event) {
final TableItem item = (TableItem) event.item;
final int index = table.indexOf(item);
createItem(item, index);
}
};
table.addListener(SWT.SetData, listener);
table.setItemCount(list.size());
table.setRedraw(true);
}
});
}
@Override
protected void actionRedraw() {
this.table.redraw();
}
@Override
protected void eventFilterChanged(ARXResult result, ModelNodeFilter filter) {
update(result, filter);
}
@Override
protected void eventModelChanged() {
update(getModel().getResult(), getModel().getNodeFilter());
}
@Override
protected void eventNodeSelected() {
int index = -1;
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals(getSelectedNode())) {
index = i;
break;
}
}
if (index == -1) return;
this.table.select(index);
}
@Override
protected void eventResultChanged(ARXResult result) {
if (result == null) reset();
}
}