// Copyright 2012 Georg-August-Universität Göttingen, Germany
//
// 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 de.ugoe.cs.autoquest.ui.swt;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import de.ugoe.cs.autoquest.eventcore.IEventTarget;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.usability.UsabilitySmell;
import de.ugoe.cs.autoquest.usability.UsabilitySmell.ManualLabel;
import de.ugoe.cs.autoquest.usability.UsabilitySmellIntensity;
import de.ugoe.cs.autoquest.usability.UsabilityEvaluationResult;
/**
*
* a dialog to inspect the results of a usability evaluation
* TODO update comments
*
*
* @author Patrick Harms
*/
public class ShowUsabilityEvaluationResultDialog extends Dialog {
/** the main shell */
protected Shell shell;
/** the table containing all smells */
private Tree smellList;
/** the description label of a selected smell */
private StyledText description;
/** the tree of involved GUI elements of a specific smell on the right */
private Tree involvedTargetsTree;
/** the table containing the involved tasks of a displayed smell */
private Tree involvedTasks;
/** the radio button group for assessing if a smell is true positive or not */
private Group assessmentRadioButtons;
/** the list of all different tags given for smells */
private List existingTags = new LinkedList<>();
/** the parent of all check boxes for tag representation */
private Composite tagCheckBoxParent;
/** the displayed usability evaluation result */
private UsabilityEvaluationResult usabilityEvalResult;
/** task tree dialog to show task details */
private ShowTaskTreeDialog showTaskTreeDialog;
/**
* creates the dialog
*/
public ShowUsabilityEvaluationResultDialog(Shell parent,
int style,
UsabilityEvaluationResult usabilityEvalResult,
String dataSetName)
{
super(parent, style);
setText("Usability Evaluation Result " + dataSetName);
this.usabilityEvalResult = usabilityEvalResult;
for (UsabilitySmell smell : usabilityEvalResult.getAllSmells()) {
for (String tag : smell.getTags()) {
if (!existingTags.contains(tag)) {
existingTags.add(tag);
}
}
}
Collections.sort(existingTags);
}
/**
* displays the dialog
*/
public void open() {
showTaskTreeDialog = new ShowTaskTreeDialog
(super.getParent(), SWT.NONE, usabilityEvalResult.getTaskModel(),
"task details of usability smells");
createContents();
shell.open();
shell.layout();
shell.addShellListener(new ShellAdapter() {
@Override
public void shellClosed(ShellEvent e) {
showTaskTreeDialog.dispose();
}
});
Display display = getParent().getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* creates the two views, one on the task instances on the left, on on the task models on the
* right. Also adds a selection adapter to the task instances so that for a selected task
* instance always the respective model is presented.
*/
private void createContents() {
shell = new Shell(getParent(), SWT.SHELL_TRIM | SWT.BORDER);
GraphicsDevice gd =
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
shell.setSize(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight());
shell.setText(getText());
shell.setLayout(new GridLayout(1, false));
SashForm mainSashForm = new SashForm(shell, SWT.HORIZONTAL);
mainSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
smellList = new Tree(mainSashForm, SWT.BORDER | SWT.SINGLE | SWT.VIRTUAL);
smellList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
smellList.setHeaderVisible(true);
//smellList.setLinesVisible(true);
TreeColumn treeColumn = new TreeColumn(smellList, SWT.HORIZONTAL);
treeColumn.setWidth(300);
treeColumn.setText("smells");
buildSmellTree();
smellList.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
TreeItem[] selectedItems = smellList.getSelection();
if ((selectedItems.length == 1) &&
(selectedItems[0].getData() instanceof UsabilitySmell))
{
displaySmellDetails((UsabilitySmell) selectedItems[0].getData());
}
else {
clearSmellDetails();
}
VisualizationUtils.updateColumnWidths(smellList);
}
});
SashForm detailsSashForm = new SashForm(mainSashForm, SWT.VERTICAL);
detailsSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
Composite descriptionComposite = new Composite(detailsSashForm, SWT.NONE);
descriptionComposite.setLayout(new GridLayout());
description = new StyledText(descriptionComposite, SWT.READ_ONLY | SWT.BORDER | SWT.WRAP);
description.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
SashForm detailsTreesSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL);
detailsTreesSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
involvedTasks = VisualizationUtils.createTaskDetailsTree
(detailsTreesSashForm, "involved tasks", usabilityEvalResult.getTaskModel());
VisualizationUtils.addItemSpecificContextMenu
(involvedTasks, ITask.class, "show details", new SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e) {
showTaskTreeDialog.open((ITask) involvedTasks.getSelection()[0].getData());
}
});
VisualizationUtils.addExpansionListener(involvedTasks, new Listener() {
public void handleEvent(final Event event) {
ensureChildren((TreeItem) event.item);
((TreeItem) event.item).setExpanded(true);
}
});
involvedTargetsTree =
VisualizationUtils.createTargetsTree(detailsTreesSashForm, "involved GUI elements");
VisualizationUtils.addInvolvedTargetsHighlighting(involvedTasks, involvedTargetsTree);
final Composite detailsAssessmentComposite = new Composite(descriptionComposite, SWT.NONE);
detailsAssessmentComposite.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false, 1, 1));
detailsAssessmentComposite.setLayout(new GridLayout(5, false));
assessmentRadioButtons = new Group(detailsAssessmentComposite, SWT.NONE);
assessmentRadioButtons.setLayout(new RowLayout(SWT.HORIZONTAL));
final Button unassessedRadioButton = new Button(assessmentRadioButtons, SWT.RADIO);
unassessedRadioButton.setText("unassessed");
unassessedRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
TreeItem[] selectedItems = smellList.getSelection();
if ((selectedItems.length == 1) &&
(selectedItems[0].getData() instanceof UsabilitySmell))
{
UsabilitySmell smell = (UsabilitySmell) selectedItems[0].getData();
if (unassessedRadioButton.getSelection()) {
smell.setManualLabel(ManualLabel.UNCHECKED);
}
}
}
});
final Button falsePositiveRadioButton = new Button(assessmentRadioButtons, SWT.RADIO);
falsePositiveRadioButton.setText("false positive");
falsePositiveRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
TreeItem[] selectedItems = smellList.getSelection();
if ((selectedItems.length == 1) &&
(selectedItems[0].getData() instanceof UsabilitySmell))
{
UsabilitySmell smell = (UsabilitySmell) selectedItems[0].getData();
if (falsePositiveRadioButton.getSelection()) {
smell.setManualLabel(ManualLabel.FALSE_POSITIVE);
}
}
}
});
final Button truePositiveRadioButton = new Button(assessmentRadioButtons, SWT.RADIO);
truePositiveRadioButton.setText("true positive");
truePositiveRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
TreeItem[] selectedItems = smellList.getSelection();
if ((selectedItems.length == 1) &&
(selectedItems[0].getData() instanceof UsabilitySmell))
{
UsabilitySmell smell = (UsabilitySmell) selectedItems[0].getData();
if (truePositiveRadioButton.getSelection()) {
smell.setManualLabel(ManualLabel.TRUE_POSITIVE);
}
}
}
});
new Label(detailsAssessmentComposite, SWT.NONE).setText("Tags:");
tagCheckBoxParent = new Composite(detailsAssessmentComposite, SWT.NONE);
tagCheckBoxParent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
tagCheckBoxParent.setLayout(new RowLayout());
recreateTagsSashForm();
final Text addTagField = new Text(detailsAssessmentComposite, SWT.BORDER);
addTagField.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
addTagField.setSize(addTagField.computeSize(100, 50));
final Button addTagButton = new Button(detailsAssessmentComposite, SWT.NONE);
addTagButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
addTagButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
String tagName = addTagField.getText();
if ((tagName != null) && (!"".equals(tagName)) && (!existingTags.contains(tagName))) {
existingTags.add(tagName);
recreateTagsSashForm();
}
}
});
addTagButton.setText("add tag");
detailsTreesSashForm.setWeights(new int[] { 1, 1 });
detailsSashForm.setWeights(new int[] { 1, 3 });
mainSashForm.setWeights(new int[] { 1, 3 });
//indexColumn.pack();
//severityColumn.pack();
//descriptionColumn.pack();
//smellList.pack();
}
/**
*
* TODO: comment
*
*
*/
private void recreateTagsSashForm() {
for (Control child : tagCheckBoxParent.getChildren()) {
child.dispose();
}
if (existingTags.size() > 0) {
TreeItem[] selectedItems = smellList.getSelection();
boolean enabled = (selectedItems.length == 1) &&
(selectedItems[0].getData() instanceof UsabilitySmell);
for (String existingTag : existingTags) {
Button checkBox = new Button(tagCheckBoxParent, SWT.CHECK);
checkBox.setText(existingTag);
checkBox.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
Button btn = (Button) event.getSource();
TreeItem[] selectedItems = smellList.getSelection();
if ((selectedItems.length == 1) &&
(selectedItems[0].getData() instanceof UsabilitySmell))
{
UsabilitySmell smell = (UsabilitySmell) selectedItems[0].getData();
if (btn.getSelection()) {
smell.addTag(btn.getText());
}
else {
smell.removeTag(btn.getText());
}
}
}
});
checkBox.setEnabled(enabled);
if (enabled) {
checkBox.setSelection
(((UsabilitySmell) selectedItems[0].getData()).getTags().contains(existingTag));
}
}
}
else {
new Label(tagCheckBoxParent, SWT.NONE).setText
("no tags added yet --> add using the text field and the button on the right");
}
tagCheckBoxParent.layout();
tagCheckBoxParent.redraw();
}
/**
* convenience method for creating the display of the instances
*/
private void buildSmellTree() {
int groupCount = 5;
int groupSize = 30;
List smells = usabilityEvalResult.getAllSmells();
final Map> sortedSmells = new HashMap<>();
for (UsabilitySmell smell : smells) {
List smellList = sortedSmells.get(smell.getBriefDescription());
if (smellList == null) {
smellList = new ArrayList();
sortedSmells.put(smell.getBriefDescription(), smellList);
}
ListIterator it = smellList.listIterator();
boolean added = false;
while (it.hasNext()) {
if (smell.getIntensity().getEventCoverage() >
it.next().getIntensity().getEventCoverage())
{
it.previous();
it.add(smell);
added = true;
break;
}
}
if (!added) {
smellList.add(smell);
}
// extract the intial list of tags of the smells
for (String tag : smell.getTags()) {
if (!existingTags.contains(tag)) {
existingTags.add(tag);
}
}
}
final Map>> allSortedSmells =
new HashMap>>();
for (Map.Entry> entry : sortedSmells.entrySet()) {
// we create groupCount groups of size groupSize
int overallIndex = 0;
for (int i = 0; i < groupCount; i++) {
List smellList = new LinkedList<>();
for (int j = 0; overallIndex < entry.getValue().size() &&
((j < groupSize) || (i == (groupCount - 1))); j++)
{
UsabilitySmell smell = entry.getValue().get(overallIndex++);
int ratio = smell.getIntensity().getRatio();
int eventCoverage = smell.getIntensity().getEventCoverage();
int index = 0;
for (index = 0; index < smellList.size(); index++) {
UsabilitySmellIntensity candidate = smellList.get(index).getIntensity();
if ((ratio == candidate.getRatio()) &&
(eventCoverage > candidate.getEventCoverage()))
{
break;
}
else if (ratio > candidate.getRatio()) {
break;
}
}
smellList.add(index, smell);
}
if (smellList.size() > 0) {
Map> smellGroups = allSortedSmells.get(i);
if (smellGroups == null) {
smellGroups = new HashMap<>();
allSortedSmells.put(i, smellGroups);
}
smellGroups.put(entry.getKey(), smellList);
}
}
}
if (smellList.getListeners(SWT.Expand).length == 0) {
smellList.addListener(SWT.Expand, new Listener() {
public void handleEvent(final Event event) {
ensureChildren((TreeItem) event.item);
((TreeItem) event.item).setExpanded(true);
VisualizationUtils.updateColumnWidths(smellList);
}
});
}
for (int i = 0; i < allSortedSmells.size(); i++) {
createRootItem("smells group " + (i + 1), allSortedSmells.get(i));
}
}
/**
* convenience method for creating the display of the instances
*/
/*private void buildSmellTree() {
List smells = usabilityEvalResult.getAllSmells();
int[] eventCoverageQuantileGroups = { 990, 975, 950, 0, -1 };
int[] minEventCoverages = new int[eventCoverageQuantileGroups.length];
int[] maxEventCoverages = new int[eventCoverageQuantileGroups.length];
final List