/*
* 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;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.math3.analysis.function.Log;
import org.deidentifier.arx.gui.Controller;
import org.deidentifier.arx.gui.resources.Resources;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
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.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import de.linearbits.swt.table.DynamicTable;
/**
* This class provides some utility methods for working with SWT.
*
* @author Fabian Prasser
*/
public class SWTUtil {
/** Constant */
public static final int SLIDER_MAX = 1000;
/** Constant */
private static final double LN2 = new Log().value(2);
/** Constant */
private static final double LN3 = new Log().value(3);
/**
* Centers the shell on the given monitor.
*
* @param shell
* @param monitor
*/
public static void center(Shell shell, Monitor monitor) {
Rectangle shellRect = shell.getBounds();
Rectangle displayRect = monitor.getBounds();
int x = (displayRect.width - shellRect.width) / 2;
int y = (displayRect.height - shellRect.height) / 2;
shell.setLocation(displayRect.x + x, displayRect.y + y);
}
/**
* Centers the given shell.
*
* @param shell
* @param parent
*/
public static void center(final Shell shell, final Shell parent) {
final Rectangle bounds = parent.getBounds();
final Point p = shell.getSize();
final int left = (bounds.width - p.x) / 2;
final int top = (bounds.height - p.y) / 2;
shell.setBounds(left + bounds.x, top + bounds.y, p.x, p.y);
}
/**
* Changes a control's font
* @param control
* @param style
*/
public static void changeFont(Control control, int style) {
FontDescriptor boldDescriptor = FontDescriptor.createFrom(control.getFont()).setStyle(style);
final Font boldFont = boldDescriptor.createFont(control.getDisplay());
control.setFont(boldFont);
control.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent arg0) {
if (boldFont != null && !boldFont.isDisposed()) {
boldFont.dispose();
}
}
});
}
/**
* Adds a bar chart to a column
* @param table
* @param column
*/
public static void createColumnWithBarCharts(final Table table, final TableColumn column) {
int index = -1;
for (int i=0; i< table.getColumnCount(); i++) {
if (table.getColumn(i)==column) {
index = i;
break;
}
}
if (index == -1) {
return;
}
final Display display = table.getDisplay();
final int columnIndex = index;
table.addListener(SWT.PaintItem, new Listener() {
@Override
public void handleEvent(Event event) {
if (event.index == columnIndex) {
GC gc = event.gc;
TableItem item = (TableItem) event.item;
Object object = item.getData(String.valueOf(columnIndex));
if (object == null || !(object instanceof Double)) {
return;
}
// Store
Color foreground = gc.getForeground();
Color background = gc.getBackground();
// Draw NaN
Double percent = (Double)object;
if (percent.isNaN() || percent.isInfinite()) {
String text = percent.isNaN() ? "NaN" : "Infinite";
gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
Point size = event.gc.textExtent(text);
int offset = Math.max(0, (event.height - size.y) / 2);
gc.drawText(text, event.x + 2, event.y + offset, true);
// Draw value
} else {
// Initialize
String text = SWTUtil.getPrettyString((Double)object * 100d) + "%";
percent = percent >= 0d ? percent : 0d;
percent = percent <= 1d ? percent : 1d;
// Draw bar
gc.setBackground(display.getSystemColor(SWT.COLOR_GRAY));
gc.setForeground(GUIHelper.getColor(240, 240, 240));
int width = (int) Math.round((column.getWidth() - 1) * percent);
width = width >= 1 ? width : 1;
gc.fillGradientRectangle(event.x, event.y, width, event.height, true);
// Draw border
Rectangle rect2 = new Rectangle(event.x, event.y, width - 1, event.height - 1);
gc.setForeground(GUIHelper.getColor(150, 150, 150));
gc.drawRectangle(rect2);
// Draw text
gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
Point size = event.gc.textExtent(text);
int offset = Math.max(0, (event.height - size.y) / 2);
gc.drawText(text, event.x + 2, event.y + offset, true);
}
// Reset
gc.setForeground(background);
gc.setBackground(foreground);
}
}
});
}
/**
* Registers an image for a tool item. Generates a version of the image
* that renders well on windows toolbars, when disabled.
*
* @param item
* @param image
*/
public static void createDisabledImage(ToolItem item) {
final Image image = new Image(item.getDisplay(), item.getImage(), SWT.IMAGE_GRAY);
item.setDisabledImage(image);
item.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent arg0) {
if (image != null && !image.isDisposed()) {
image.dispose();
}
}
});
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createFillGridData() {
return createFillGridData(1);
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createFillGridData(int span) {
final GridData data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.grabExcessHorizontalSpace = true;
data.grabExcessVerticalSpace = true;
data.horizontalIndent=0;
data.verticalIndent=0;
data.horizontalSpan = span;
return data;
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createFillHorizontallyGridData() {
return createFillHorizontallyGridData(true);
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createFillHorizontallyGridData(boolean fill) {
return createFillHorizontallyGridData(fill, 1);
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createFillHorizontallyGridData(boolean fill, int span) {
final GridData data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = fill ? SWT.FILL : SWT.CENTER;
data.grabExcessHorizontalSpace = true;
data.grabExcessVerticalSpace = false;
data.horizontalSpan = span;
data.horizontalIndent=0;
data.verticalIndent=0;
return data;
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createFillVerticallyGridData() {
final GridData data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.grabExcessHorizontalSpace = false;
data.grabExcessVerticalSpace = true;
data.horizontalIndent=0;
data.verticalIndent=0;
return data;
}
/**
* Creates a generic tooltip for the table
* @param table
*/
public static void createGenericTooltip(final Table table) {
table.addMouseMoveListener(new MouseMoveListener() {
private TableItem current = null;
@Override
public void mouseMove(MouseEvent arg0) {
TableItem item = table.getItem(new Point(arg0.x, arg0.y));
if (item != null && item != current) {
current = item;
StringBuilder builder = new StringBuilder();
builder.append("("); //$NON-NLS-1$
int columns = item.getParent().getColumnCount();
for (int i = 0; i < columns; i++) {
String value = item.getText(i);
if (value != null && !value.equals("")) { //$NON-NLS-1$
builder.append(value);
if (i < columns - 1) {
builder.append(", "); //$NON-NLS-1$
}
} else if (item.getData(String.valueOf(i)) != null &&
item.getData(String.valueOf(i)) instanceof Double) {
builder.append(getPrettyString(((Double) item.getData(String.valueOf(i))).doubleValue() * 100d) + "%"); //$NON-NLS-1$
if (i < columns - 1) {
builder.append(", "); //$NON-NLS-1$
}
}
}
builder.append(")"); //$NON-NLS-1$
table.setToolTipText(builder.toString());
}
}
});
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createGridData() {
final GridData data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.grabExcessHorizontalSpace = false;
data.grabExcessVerticalSpace = false;
return data;
}
/**
* Creates a grid layout.
*
* @param columns
* @return
*/
public static GridLayout createGridLayout(int columns) {
final GridLayout layout = new GridLayout();
layout.numColumns = columns;
layout.marginBottom = 0;
layout.marginHeight = 0;
layout.marginLeft = 0;
layout.marginRight = 0;
layout.marginTop = 0;
layout.marginWidth = 0;
return layout;
}
/**
* Creates a grid layout.
*
* @param columns
* @param compact
* @return
*/
public static GridLayout createGridLayout(int columns, boolean compact) {
if (compact) return createGridLayout(columns);
final GridLayout layout = new GridLayout();
layout.numColumns = columns;
return layout;
}
/**
* Creates a grid layout with equal-width columns
* @param columns
* @return
*/
public static GridLayout createGridLayoutWithEqualWidth(int columns) {
GridLayout layout = createGridLayout(columns);
layout.makeColumnsEqualWidth = true;
return layout;
}
/**
* Creates a help button in the given folder.
*
* @param controller
* @param folder
* @param id
*/
public static void createHelpButton(final Controller controller, final CTabFolder folder, final String id) {
createHelpButton(controller, folder, id, null);
}
/**
* Creates a help button in the given folder.
*
* @param controller
* @param folder
* @param id
* @param helpids
*/
public static void createHelpButton(final Controller controller,
final CTabFolder folder,
final String id,
final Map<Composite, String> helpids) {
ToolBar toolbar = new ToolBar(folder, SWT.FLAT);
folder.setTopRight( toolbar, SWT.RIGHT );
ToolItem item = new ToolItem( toolbar, SWT.PUSH );
item.setImage(controller.getResources().getManagedImage("help.png")); //$NON-NLS-1$
item.setToolTipText(Resources.getMessage("General.0")); //$NON-NLS-1$
createDisabledImage(item);
int height = toolbar.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
folder.setTabHeight(Math.max(height, folder.getTabHeight()));
item.addSelectionListener(new SelectionAdapter(){
@Override
public void widgetSelected(SelectionEvent arg0) {
if (helpids != null && folder.getSelectionIndex() >= 0 &&
helpids.get(folder.getItem(folder.getSelectionIndex()).getControl()) != null) {
controller.actionShowHelpDialog(helpids.get(folder.getItem(folder.getSelectionIndex()).getControl()));
} else {
controller.actionShowHelpDialog(id);
}
}
});
}
/**
* Creates grid data.
*
* @return
*/
public static GridData createNoFillGridData() {
final GridData d = new GridData();
d.horizontalAlignment = SWT.LEFT;
d.verticalAlignment = SWT.TOP;
d.grabExcessHorizontalSpace = false;
d.grabExcessVerticalSpace = false;
return d;
}
/**
* Creates grid data.
*
* @param i
* @return
*/
public static Object createSpanColumnsGridData(final int i) {
final GridData d = new GridData();
d.grabExcessHorizontalSpace = false;
d.grabExcessVerticalSpace = false;
d.horizontalSpan = i;
return d;
}
/**
* Returns a table. Implements hacks for fixing OSX bugs.
* @param parent
* @param style
* @return
*/
public static Table createTable(Composite parent, int style) {
Table table = new Table(parent, style);
fixOSXTableBug(table);
return table;
}
/**
* Returns a dynamic table. Implements hacks for fixing OSX bugs.
* @param parent
* @param style
* @return
*/
public static DynamicTable createTableDynamic(Composite parent, int style) {
DynamicTable table = new DynamicTable(parent, style);
fixOSXTableBug(table);
return table;
}
/**
* Returns a table viewer. Implements hacks for fixing OSX bugs.
* @param parent
* @param style
* @return
*/
public static TableViewer createTableViewer(Composite container, int style) {
TableViewer viewer = new TableViewer(container, style);
fixOSXTableBug(viewer.getTable());
return viewer;
}
/**
* Returns a checkbox table viewer. Implements hacks for fixing OSX bugs.
* @param parent
* @param style
* @return
*/
public static CheckboxTableViewer createTableViewerCheckbox(Composite container, int style) {
CheckboxTableViewer viewer = CheckboxTableViewer.newCheckList(container, style);
fixOSXTableBug(viewer.getTable());
return viewer;
}
/**
* Disables the composite and its children.
*
* @param elem
*/
public static void disable(final Composite elem) {
setEnabled(elem, false);
}
/**
* Disables the control.
*
* @param elem
*/
public static void disable(final Control elem) {
elem.setEnabled(false);
}
/**
* Converts the double value to a slider selection.
*
* @param min
* @param max
* @param value
* @return
*/
public static int doubleToSlider(final double min,
final double max,
final double value) {
int val = (int)Math.round((value - min) / (max - min) * SLIDER_MAX);
if (val < 0) {
val = 0;
}
if (val > SLIDER_MAX) {
val = SLIDER_MAX;
}
return val;
}
/**
* Enables the composite and its children.
*
* @param elem
*/
public static void enable(final Composite elem) {
setEnabled(elem, true);
}
/**
* Enables the control.
*
* @param elem
*/
public static void enable(final Control elem) {
elem.setEnabled(true);
}
/**
* Tries to fix a bug when resizing sash forms in OSX
* @param sash
*/
public static void fixOSXSashBug(final SashForm sash) {
// Only if on OSX
if (isMac()) {
// Listen for resize event in first composite
for (Control c : sash.getChildren()) {
if (c instanceof Composite) {
// In case of resize, redraw the sash form
c.addControlListener(new ControlAdapter(){
@Override
public void controlResized(ControlEvent arg0) {
sash.redraw();
}
});
return;
}
}
}
}
/**
* Converts a boolean into a pretty string
* @param value
* @return
*/
public static String getPrettyString(boolean value) {
if (value) {
return Resources.getMessage("PropertiesView.159");
} else {
return Resources.getMessage("PropertiesView.170");
}
}
/**
* Returns a pretty string representing the given double
* @param value
* @return
*/
public static String getPrettyString(double value) {
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
if (value == LN2) {
return "ln(2)";
} else if (value == LN3) {
return "ln(3)";
} else if (value == 0) {
return "0";
} else if (Math.abs(value) < 0.00001) {
return new DecimalFormat("#.#####E0", symbols).format(value).replace('E', 'e');
} else if (Math.abs(value) < 1) {
return new DecimalFormat("#.#####", symbols).format(value);
} else if (Math.abs(value) < 100000) {
return new DecimalFormat("######.#####", symbols).format(value);
} else {
return String.valueOf(value).replace('E', 'e');
}
}
/**
* Returns a pretty string representing the given value
* @param value
* @return
*/
public static String getPrettyString(int value) {
return String.valueOf(value);
}
/**
* Returns a pretty string representing the given value
* @param value
* @return
*/
public static String getPrettyString(long value) {
return String.valueOf(value);
}
/**
* Fallback for objects of unknown type
* @param value
* @return
*/
public static String getPrettyString(Object value) {
if (value instanceof Boolean) {
return SWTUtil.getPrettyString(((Boolean)value).booleanValue());
} else if (value instanceof Double) {
return SWTUtil.getPrettyString(((Double)value).doubleValue());
} if (value instanceof Integer) {
return SWTUtil.getPrettyString(((Integer)value).intValue());
} if (value instanceof Long) {
return SWTUtil.getPrettyString(((Long)value).longValue());
}
return String.valueOf(value);
}
/**
* Converts the integer value to a slider selection.
*
* @param min
* @param max
* @param value
* @return
*/
public static int intToSlider(final int min, final int max, final int value) {
return doubleToSlider(min, max, value);
}
/**
* Are we running on an OSX system
* @return
*/
public static boolean isMac() {
return System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Converts the slider value to a double.
*
* @param min
* @param max
* @param value
* @return
*/
public static double sliderToDouble(final double min,
final double max,
final int value) {
double val = ((double) value / (double) SLIDER_MAX) * (max - min) + min;
if (val < min) {
val = min;
}
if (val > max) {
val = max;
}
return val;
}
/**
* Converts the slider value to an integer.
*
* @param min
* @param max
* @param value
* @return
*/
public static int sliderToInt(final int min, final int max, final int value) {
return (int)Math.round(sliderToDouble(min, max, value));
}
/**
* Fixes bugs on OSX when scrolling in tables
* @param table
*/
private static void fixOSXTableBug(final Table table) {
if (isMac()) {
SelectionListener bugFixer = new SelectionListener(){
@Override
public void widgetDefaultSelected(SelectionEvent arg0) {
widgetSelected(arg0);
}
@Override
public void widgetSelected(SelectionEvent arg0) {
table.redraw();
}
};
table.getVerticalBar().addSelectionListener(bugFixer);
table.getHorizontalBar().addSelectionListener(bugFixer);
}
}
/**
* En-/disables the composite and its children.
*
* @param elem
* @param val
*/
private static void setEnabled(final Composite elem, final boolean val) {
elem.setEnabled(val);
for (final Control c : elem.getChildren()) {
if (c instanceof Composite) {
setEnabled((Composite) c, val);
} else {
c.setEnabled(val);
}
}
}
}