/* * 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; import java.util.ArrayList; import java.util.List; import org.deidentifier.arx.ARXLattice; import org.deidentifier.arx.ARXLattice.ARXNode; import org.deidentifier.arx.ARXLattice.Anonymity; import org.deidentifier.arx.ARXResult; import org.deidentifier.arx.gui.Controller; import org.deidentifier.arx.gui.model.Model; import org.deidentifier.arx.gui.model.ModelEvent; import org.deidentifier.arx.gui.model.ModelEvent.ModelPart; import org.deidentifier.arx.gui.resources.Resources; import org.deidentifier.arx.gui.view.SWTUtil; import org.deidentifier.arx.metric.v2.QualityMetadata; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import cern.colt.Arrays; /** * This class implements the global application tool bar. * * @author Fabian Prasser * @author Florian Kohlmayer */ public class MainToolBar extends AbstractMenu { /** * Helper class including some statistics. */ private static class SearchSpaceStatistics { /** Count. */ private final long numTransformationsInSearchSpace; /** Count. */ private final int numMaterializedTransformations; /** Count. */ private final int numTransformationsPruned; /** Count. */ private final int numTransformationsAnonymous; /** Count. */ private final int numTransformationsNotAnonymous; /** Count. */ private final int numTransformationsProbablyAnonymous; /** Count. */ private final int numTransformationsProbablyNotAnonymous; /** Count. */ private final int numTransformationsAnonymityUnknown; /** Count. */ private final int numTransformationsInfolossAvailable; /** Time in seconds. */ private final double executionTime; /** Optimal transformation. */ private final ARXNode optimum; /** Heuristic */ private final boolean heuristic; /** * Creates the statistics. * * @param result */ private SearchSpaceStatistics(ARXResult result){ // Prepare int pruned = 0; int anonymous = 0; int notAnonymous = 0; int probablyAnonymous = 0; int probablyNotAnonymous = 0; int anonymityUnknown = 0; int infolossAvailable = 0; ARXLattice lattice = result.getLattice(); // Compute statistics for (final ARXNode[] level : lattice.getLevels()) { for (final ARXNode node : level) { if (!node.isChecked()) { pruned++; } if (node.getAnonymity() == Anonymity.ANONYMOUS) { anonymous++; } else if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS) { notAnonymous++; } else if (node.getAnonymity() == Anonymity.PROBABLY_ANONYMOUS) { probablyAnonymous++; } else if (node.getAnonymity() == Anonymity.PROBABLY_NOT_ANONYMOUS) { probablyNotAnonymous++; } else if (node.getAnonymity() == Anonymity.UNKNOWN) { anonymityUnknown++; } if (node.getHighestScore().compareTo(node.getLowestScore()) == 0) { infolossAvailable++; } } } // Store this.executionTime = (double)result.getTime() / 1000d; this.numTransformationsInSearchSpace = lattice.getVirtualSize(); this.numMaterializedTransformations = lattice.getSize(); this.numTransformationsPruned = pruned; this.numTransformationsAnonymous = anonymous; this.numTransformationsNotAnonymous = notAnonymous; this.numTransformationsAnonymityUnknown = anonymityUnknown; this.numTransformationsProbablyAnonymous = probablyAnonymous; this.numTransformationsProbablyNotAnonymous = probablyNotAnonymous; this.numTransformationsInfolossAvailable = infolossAvailable; this.optimum = result.getGlobalOptimum(); this.heuristic = !result.getLattice().isComplete(); } @Override public String toString() { // Prepare double prunedPercentage = (double) this.numTransformationsPruned / (double) this.numMaterializedTransformations * 100d; double materializedPercentage = (double) this.numMaterializedTransformations / (double) this.numTransformationsInSearchSpace * 100d; // Render statistics about the solution space StringBuilder sb = new StringBuilder(); sb.append(Resources.getMessage("MainToolBar.1")); //$NON-NLS-1$ sb.append(Resources.getMessage("MainToolBar.2")) //$NON-NLS-1$ .append(this.numTransformationsInSearchSpace) .append("\n"); //$NON-NLS-1$ sb.append(Resources.getMessage("MainToolBar.41")) //$NON-NLS-1$ .append(this.numMaterializedTransformations); sb.append(" [") //$NON-NLS-1$ .append(SWTUtil.getPrettyString(materializedPercentage)) .append("%]\n"); //$NON-NLS-1$ sb.append(Resources.getMessage("MainToolBar.12")) //$NON-NLS-1$ .append(this.numTransformationsPruned); sb.append(" [") //$NON-NLS-1$ .append(SWTUtil.getPrettyString(prunedPercentage)) .append("%]\n"); //$NON-NLS-1$ sb.append(Resources.getMessage("MainToolBar.18")) //$NON-NLS-1$ .append(SWTUtil.getPrettyString(this.executionTime)) .append("s\n"); //$NON-NLS-1$ if (this.numTransformationsAnonymous != 0 || this.numTransformationsNotAnonymous != 0 || this.numTransformationsProbablyAnonymous != 0 || this.numTransformationsProbablyNotAnonymous != 0 || this.numTransformationsAnonymityUnknown != 0 || this.numTransformationsInfolossAvailable != 0) { // Render the classification result sb.append(Resources.getMessage("MainToolBar.22")); //$NON-NLS-1$ if (this.numTransformationsAnonymous != 0) { sb.append(Resources.getMessage("MainToolBar.24")) //$NON-NLS-1$ .append(this.numTransformationsAnonymous); } if (this.numTransformationsNotAnonymous != 0) { sb.append(Resources.getMessage("MainToolBar.26")) //$NON-NLS-1$ .append(this.numTransformationsNotAnonymous); } if (this.numTransformationsProbablyAnonymous != 0) { sb.append(Resources.getMessage("MainToolBar.28")) //$NON-NLS-1$ .append(this.numTransformationsProbablyAnonymous); } if (this.numTransformationsProbablyNotAnonymous != 0) { sb.append(Resources.getMessage("MainToolBar.30")) //$NON-NLS-1$ .append(this.numTransformationsProbablyNotAnonymous); } if (this.numTransformationsAnonymityUnknown != 0) { sb.append(Resources.getMessage("MainToolBar.34")) //$NON-NLS-1$ .append(this.numTransformationsAnonymityUnknown); } if (this.numTransformationsInfolossAvailable != 0) { sb.append(Resources.getMessage("MainToolBar.35")) //$NON-NLS-1$ .append(this.numTransformationsInfolossAvailable); } sb.append("\n"); } // Render information about the optimum if (this.optimum != null) { sb.append(Resources.getMessage("MainToolBar.36")) //$NON-NLS-1$ .append(Resources.getMessage("MainToolBar.39")) //$NON-NLS-1$ .append(SWTUtil.getPrettyString(!this.heuristic)) .append(Resources.getMessage("MainToolBar.37")) //$NON-NLS-1$ .append(Arrays.toString(this.optimum.getTransformation())); sb.append(Resources.getMessage("MainToolBar.38")) //$NON-NLS-1$ .append(this.optimum.getHighestScore().toString()); for (QualityMetadata<?> metadata : this.optimum.getHighestScore().getMetadata()) { sb.append("\n - ") //$NON-NLS-1$ .append(metadata.getParameter()).append(": ") //$NON-NLS-1$ .append(SWTUtil.getPrettyString(metadata.getValue())); } } // Return return sb.toString(); } } /** Static offset. */ private static final int OFFSET = 10; /** Text. */ private String tooltip; /** State. */ private Model model; /** State. */ private final List<ToolItem> toolitems = new ArrayList<ToolItem>(); /** Widget. */ private ToolBar toolbar; /** Widget. */ private Label labelAttribute; /** Widget. */ private Label labelTransformations; /** Widget. */ private Label labelApplied; /** Widget. */ private Label labelSelected; /** Widget. */ private Composite infoComposite; /** Widget. */ private ToolItem infoItem; /** * Creates a new instance. * * @param parent * @param controller */ public MainToolBar(final Shell parent, final Controller controller, List<MainMenuItem> items) { super(controller); toolbar = new ToolBar(parent, SWT.FLAT); toolbar.setLayoutData(SWTUtil.createFillHorizontallyGridData()); // Create items this.createItems(toolbar, items, ""); //$NON-NLS-1$ this.createLabels(); // Pack toolbar.pack(); // Initialize this.update(new ModelEvent(this, ModelPart.MODEL, null)); } @Override public void dispose() { super.dispose(); } @Override public void reset() { if (labelSelected != null) { toolbar.setRedraw(false); labelSelected.setText(""); //$NON-NLS-1$ labelSelected.pack(); layout(); toolbar.setRedraw(true); } if (labelApplied != null) { toolbar.setRedraw(false); labelApplied.setText(""); //$NON-NLS-1$ labelApplied.pack(); layout(); toolbar.setRedraw(true); } if (labelTransformations != null) { toolbar.setRedraw(false); labelTransformations.setText(""); //$NON-NLS-1$ labelTransformations.pack(); layout(); toolbar.setRedraw(true); } if (labelAttribute != null) { toolbar.setRedraw(false); labelAttribute.setText(""); //$NON-NLS-1$ labelAttribute.pack(); layout(); toolbar.setRedraw(true); } } @Override public void update(final ModelEvent event) { super.update(event); if (event.part == ModelPart.SELECTED_NODE) { if (model.getSelectedNode() != null) { toolbar.setRedraw(false); labelSelected.setText(Resources.getMessage("MainToolBar.3") + //$NON-NLS-1$ Arrays.toString(model.getSelectedNode().getTransformation())); labelSelected.pack(); layout(); toolbar.setRedraw(true); } } else if (event.part == ModelPart.OUTPUT) { if (model.getOutputNode() != null) { // Update tool tip SearchSpaceStatistics stats = new SearchSpaceStatistics(model.getResult()); setToolTip(stats); // Update labels toolbar.setRedraw(false); labelTransformations.setText(Resources.getMessage("MainToolBar.6") + //$NON-NLS-1$ SWTUtil.getPrettyString(stats.numTransformationsInSearchSpace)); labelTransformations.pack(); labelApplied.setText(Resources.getMessage("MainToolBar.4") + //$NON-NLS-1$ Arrays.toString(model.getOutputNode().getTransformation())); labelApplied.pack(); layout(); toolbar.setRedraw(true); } else { reset(); } } else if (event.part == ModelPart.RESULT) { if (model.getResult() != null) { // Update tool tip SearchSpaceStatistics stats = new SearchSpaceStatistics(model.getResult()); setToolTip(stats); // Update labels toolbar.setRedraw(false); labelTransformations.setText(Resources.getMessage("MainToolBar.6") + //$NON-NLS-1$ SWTUtil.getPrettyString(stats.numTransformationsInSearchSpace)); labelTransformations.pack(); labelSelected.setText(Resources.getMessage("MainToolBar.7")); //$NON-NLS-1$ labelSelected.pack(); labelApplied.setText(Resources.getMessage("MainToolBar.8")); //$NON-NLS-1$ labelApplied.pack(); layout(); toolbar.setRedraw(true); } } else if (event.part == ModelPart.SELECTED_ATTRIBUTE) { String attribute = (String)event.data; // Update label toolbar.setRedraw(false); labelAttribute.setText(Resources.getMessage("MainToolBar.50") + trim(attribute)); //$NON-NLS-1$ labelAttribute.pack(); layout(); toolbar.setRedraw(true); } else if (event.part == ModelPart.MODEL) { model = (Model) event.data; } } /** * Creates all items * @param toolbar * @param items * @param label */ private void createItems(ToolBar toolbar, List<MainMenuItem> items, String label) { // For each item for (final MainMenuItem item : items) { // Skip items that are not buttons if (!item.isButton()) { continue; } // Create group if (item instanceof MainMenuGroup) { MainMenuGroup group = (MainMenuGroup) item; if (!this.toolitems.isEmpty()) { ToolItem menuItem = new ToolItem(toolbar, SWT.SEPARATOR); menuItem.setEnabled(false); menuItem.setData(item); this.toolitems.add(menuItem); } createItems(toolbar, group.getItems(), label.length() != 0 ? label + " -> " + group.getLabel() : group.getLabel()); //$NON-NLS-1$ // Create separator } else if (item instanceof MainMenuSeparator) { if (!this.toolitems.isEmpty()) { ToolItem menuItem = new ToolItem(toolbar, SWT.SEPARATOR); this.toolitems.add(menuItem); } // Create item } else { ToolItem menuItem = new ToolItem(toolbar, SWT.PUSH); menuItem.setToolTipText(label.length() != 0 ? label + " -> " + item.getLabel() : item.getLabel()); //$NON-NLS-1$ if (item.getImage() != null) { menuItem.setImage(item.getImage()); SWTUtil.createDisabledImage(menuItem); } menuItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { item.action(getController()); } }); menuItem.setData(item); menuItem.setEnabled(false); this.toolitems.add(menuItem); } } } private void createLabels() { // Add status labels infoItem = new ToolItem(toolbar, SWT.SEPARATOR); infoComposite = new Composite(toolbar, SWT.NONE); infoItem.setControl(infoComposite); infoComposite.setLayout(null); labelAttribute = new Label(infoComposite, SWT.SINGLE | SWT.READ_ONLY); labelAttribute.setText(Resources.getMessage("MainToolBar.33")); //$NON-NLS-1$ labelAttribute.pack(); labelTransformations = new Label(infoComposite, SWT.SINGLE | SWT.READ_ONLY); labelTransformations.setText(Resources.getMessage("MainToolBar.33")); //$NON-NLS-1$ labelTransformations.pack(); labelSelected = new Label(infoComposite, SWT.SINGLE | SWT.READ_ONLY); labelSelected.setText(Resources.getMessage("MainToolBar.31")); //$NON-NLS-1$ labelSelected.pack(); labelApplied = new Label(infoComposite, SWT.SINGLE | SWT.READ_ONLY); labelApplied.setText(Resources.getMessage("MainToolBar.32")); //$NON-NLS-1$ labelApplied.pack(); // Copy info to clip board on right-click Menu menu = new Menu(toolbar); MenuItem itemCopy = new MenuItem(menu, SWT.NONE); itemCopy.setText(Resources.getMessage("MainToolBar.42")); //$NON-NLS-1$ itemCopy.addSelectionListener(new SelectionAdapter(){ public void widgetSelected(SelectionEvent arg0) { if (tooltip != null) { Clipboard clipboard = new Clipboard(toolbar.getDisplay()); TextTransfer textTransfer = TextTransfer.getInstance(); clipboard.setContents(new String[]{tooltip}, new Transfer[]{textTransfer}); clipboard.dispose(); } } }); labelSelected.setMenu(menu); labelApplied.setMenu(menu); labelTransformations.setMenu(menu); // Add listener for layout toolbar.addControlListener(new ControlAdapter() { @Override public void controlResized(final ControlEvent arg0) { layout(); } }); } /** * Performs layouting. */ private void layout() { // Disable redrawing toolbar.setRedraw(false); // Adjust size of items and composite Rectangle bounds = toolbar.getBounds(); int remaining = toolbar.getBounds().width; for (final ToolItem item : toolitems) { remaining -= item.getBounds().width; } remaining -= OFFSET; infoComposite.setSize(remaining, bounds.height); infoItem.setWidth(remaining); int locationY = (infoComposite.getBounds().height - labelSelected.getBounds().height)/2; // Layout label int locationX = remaining - labelApplied.getSize().x; labelApplied.setLocation(locationX, locationY); if (locationX < 0) labelApplied.setVisible(false); else labelApplied.setVisible(true); // Layout label locationX -= labelSelected.getSize().x + OFFSET; labelSelected.setLocation(locationX, locationY); if (locationX < 0) labelSelected.setVisible(false); else labelSelected.setVisible(true); // Layout label locationX -= labelTransformations.getSize().x + OFFSET; labelTransformations.setLocation(locationX, locationY); if (locationX < 0) labelTransformations.setVisible(false); else labelTransformations.setVisible(true); // Layout label locationX -= labelAttribute.getSize().x + OFFSET; labelAttribute.setLocation(locationX, locationY); if (locationX < 0) labelAttribute.setVisible(false); else labelAttribute.setVisible(true); // Redraw toolbar.setRedraw(true); } /** * Sets the tooltip. * * @param stats */ private void setToolTip(SearchSpaceStatistics stats) { this.tooltip = stats.toString(); this.labelSelected.setToolTipText(tooltip); this.labelApplied.setToolTipText(tooltip); this.labelTransformations.setToolTipText(tooltip); } /** * Trims the given string to 20 characters * @param attribute * @return */ private String trim(String attribute) { if (attribute.length() > 20) { return attribute.substring(0, 20) + "..."; } else { return attribute; } } @Override protected void update(Model model) { // Check if (toolbar == null) return; // For each item for (final ToolItem item : toolbar.getItems()) { // Check group if (!(item.getData() instanceof MainMenuGroup)) { MainMenuItem mItem = (MainMenuItem) item.getData(); item.setEnabled(mItem == null || mItem.isEnabled(model)); } } } }