// 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.HashMap; import java.util.LinkedList; import java.util.List; 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.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; 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.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 task on the right bottom */ private Tree involvedTargetsTree; /** the table containing the parents tasks of a displayed task */ private Tree involvedTasks; /** 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; } /** * 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.NONE); treeColumn.setWidth(200); 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(); } } }); SashForm detailsSashForm = new SashForm(mainSashForm, SWT.VERTICAL); detailsSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); description = new StyledText(detailsSashForm, SWT.READ_ONLY | SWT.BORDER | SWT.WRAP); description.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); SashForm detailsBottomSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL); detailsBottomSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); involvedTasks = VisualizationUtils.createTaskDetailsTree (detailsBottomSashForm, "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(detailsBottomSashForm, "involved GUI elements"); VisualizationUtils.addInvolvedTargetsHighlighting(involvedTasks, involvedTargetsTree); detailsBottomSashForm.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(); } /** * 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>> sortedSmells = new LinkedList>>(); for (int i = 0; i < eventCoverageQuantileGroups.length; i++) { sortedSmells.add(new HashMap>()); } for (UsabilitySmell smell : smells) { int eventCoverageQuantile = smell.getIntensity().getEventCoverageQuantile(); for (int i = 0; i < eventCoverageQuantileGroups.length; i++) { if (eventCoverageQuantile >= eventCoverageQuantileGroups[i]) { Map> smellMap = sortedSmells.get(i); List smellList = smellMap.get(smell.getBriefDescription()); if (smellList == null) { smellList = new ArrayList(); smellMap.put(smell.getBriefDescription(), smellList); } 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 (minEventCoverages[i] == 0) { minEventCoverages[i] = smell.getIntensity().getEventCoverage(); maxEventCoverages[i] = smell.getIntensity().getEventCoverage(); } else { minEventCoverages[i] = Math.min (minEventCoverages[i], smell.getIntensity().getEventCoverage()); maxEventCoverages[i] = Math.max (maxEventCoverages[i], smell.getIntensity().getEventCoverage()); } break; } } } 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); } }); } double taskPercentages = 0; double taskPercentagesCoveredByPreceedingGroups = 0; for (int i = 0; i < eventCoverageQuantileGroups.length; i++) { taskPercentages = ((1000 - eventCoverageQuantileGroups[i]) / 10.0) - taskPercentagesCoveredByPreceedingGroups; if (eventCoverageQuantileGroups[i] > -1) { createRootItem("smells for " + taskPercentages + "% of tasks covering " + minEventCoverages[i] + " to " + maxEventCoverages[i] + " recorded events", sortedSmells.get(i)); } else { createRootItem("other smells not related to specific tasks", sortedSmells.get(i)); } taskPercentagesCoveredByPreceedingGroups += taskPercentages; } } /** * */ private void createRootItem(String name, Map> smells) { TreeItem smellItem = new TreeItem(smellList, SWT.NULL); int count = 0; for (Map.Entry> entry : smells.entrySet()) { count += entry.getValue().size(); } smellItem.setText(name + " (" + count + " smells)"); smellItem.setData(smells); if (count > 0) { // simulate a child new TreeItem(smellItem, SWT.NULL); } } /** * */ private void clearSmellDetails() { description.setText(""); involvedTargetsTree.removeAll(); involvedTasks.removeAll(); } /** * */ private void displaySmellDetails(UsabilitySmell smell) { clearSmellDetails(); FontData data = description.getFont().getFontData()[0]; int height = (int) (data.getHeight() * 1.5); Font defaultFont = new Font (description.getDisplay(), data.getName(), height, data.getStyle()); Font boldFont = new Font (description.getDisplay(), data.getName(), height, data.getStyle() | SWT.BOLD); for (Object fragment : smell.getDescriptionFragments()) { int color; Font font; if (fragment instanceof String) { color = SWT.COLOR_BLACK; font = defaultFont; } else { color = SWT.COLOR_DARK_GREEN; font = boldFont; } int initialLength = description.getText().length(); if (fragment instanceof Collection) { int counter = 1; for (Object elem : ((Collection) fragment)) { description.append("\n"); description.append(Integer.toString(counter++)); description.append(".: "); description.append(elem.toString()); } } else { description.append(fragment.toString()); } StyleRange styleRange = new StyleRange (initialLength, description.getText().length() - initialLength, description.getDisplay().getSystemColor(color), null); styleRange.font = font; description.setStyleRange(styleRange); description.append(" "); } description.setLeftMargin(50); description.setRightMargin(50); description.setTopMargin(50); description.setBottomMargin(50); StyleRange styleRange = new StyleRange(); styleRange.font = new Font(description.getDisplay(), "Courier", 12, SWT.NORMAL); description.setStyleRange(styleRange); List involvedTaskList = getInvolvedTasks(smell); for (ITask involvedTask : involvedTaskList) { VisualizationUtils.createTreeItemFor (involvedTask, involvedTasks, usabilityEvalResult.getTaskModel(), true); } List involvedTargets = getInvolvedTargets(smell); if (involvedTargets.size() <= 0) { for (ITask involvedTask : involvedTaskList) { VisualizationUtils.getInvolvedTargets(involvedTask, involvedTargets); } } VisualizationUtils.addInvolvedTargets(involvedTargetsTree, involvedTargets); int weightLeft = involvedTaskList.size() == 0 ? 1 : Math.min(4, Math.max(3, involvedTaskList.size())); int weightRight = involvedTargets.size() == 0 ? 1 : Math.min(3, Math.max(1, involvedTargets.size())); ((SashForm) involvedTasks.getParent()).setWeights(new int[] { weightLeft, weightRight }); VisualizationUtils.expandAll(involvedTasks, true); VisualizationUtils.updateColumnWidths(involvedTasks); } /** * */ private void ensureChildren(TreeItem parent) { if ((parent.getItemCount() == 0) || (parent.getItems()[0].getData() != null)) { return; } for (int i = 0; i < parent.getItems().length; i++) { parent.getItems()[i].dispose(); } if (parent.getData() instanceof Map) { @SuppressWarnings("unchecked") Map> map = (Map>) parent.getData(); for (Map.Entry> entry : map.entrySet()) { TreeItem child = new TreeItem(parent, SWT.NULL); child.setText(entry.getKey() + " (" + entry.getValue().size() + " smells)"); child.setData(entry); if (entry.getValue().size() > 0) { // simulate child new TreeItem(child, SWT.NULL); } } } else if (parent.getData() instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry> entry = (Map.Entry>) parent.getData(); int count = 0; for (UsabilitySmell smell : entry.getValue()) { TreeItem child = new TreeItem(parent, SWT.NULL); child.setData(smell); child.setText(++count + ": ratio = " + smell.getIntensity().getRatio() + ", covered events = " + smell.getIntensity().getEventCoverage()); } } else if (parent.getData() instanceof ITask) { ITask task = (ITask) parent.getData(); if (task instanceof IStructuringTemporalRelationship) { for (ITask subTask : ((IStructuringTemporalRelationship) task).getChildren()) { VisualizationUtils.createTreeItemFor (subTask, parent, usabilityEvalResult.getTaskModel(), true); } } else if (task instanceof IMarkingTemporalRelationship) { VisualizationUtils.createTreeItemFor (((IMarkingTemporalRelationship) task).getMarkedTask(), parent, usabilityEvalResult.getTaskModel(), true); } } } /** * */ private List getInvolvedTasks(UsabilitySmell smell) { List fragments = smell.getDescriptionFragments(); List involvedTasks = new ArrayList(); for (Object fragment : fragments) { if (fragment instanceof ITask) { involvedTasks.add((ITask) fragment); } } return involvedTasks; } /** * */ private List getInvolvedTargets(UsabilitySmell smell) { List fragments = smell.getDescriptionFragments(); List involvedTargets = new ArrayList(); for (Object fragment : fragments) { if (fragment instanceof IEventTarget) { involvedTargets.add((IEventTarget) fragment); } else if (fragment instanceof Collection) { for (Object elem : (Collection) fragment) { if (elem instanceof IEventTarget) { involvedTargets.add((IEventTarget) elem); } } } } return involvedTargets; } }