// 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.usability; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTargetModel; import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel; import de.ugoe.cs.autoquest.eventcore.guimodel.IButton; import de.ugoe.cs.autoquest.eventcore.guimodel.ICheckBox; import de.ugoe.cs.autoquest.eventcore.guimodel.IComboBox; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIView; import de.ugoe.cs.autoquest.eventcore.guimodel.IListBox; import de.ugoe.cs.autoquest.eventcore.guimodel.IMenuButton; import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea; import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField; import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor; import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask; import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel; import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession; /** * TODO comment * * @version $Revision: $ $Date: 16.07.2012$ * @author 2012, last modified by $Author: pharms$ */ public class UnusedGUIElementsRule implements UsabilityEvaluationRule { /* * (non-Javadoc) * * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree) */ @Override public UsabilityEvaluationResult evaluate(ITaskModel taskModel) { UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel); Map>> viewDisplays = getViewDisplays(taskModel.getUserSessions()); Map> allGUIElements = getAllGUIElements(taskModel); for (Map.Entry>> viewDisplay : viewDisplays.entrySet()) { handleUnusedGUIElements (allGUIElements, viewDisplay.getKey(), viewDisplay.getValue(), results); } return results; } /** * @param results * */ private void handleUnusedGUIElements(Map> usedGUIElements, IGUIView view, List> viewUsages, UsabilityEvaluationResult results) { Set allElementsInView = usedGUIElements.get(view); if (allElementsInView == null) { return; } Map> usageCounters = new HashMap<>(); for (IGUIElement relevantElement : allElementsInView) { int usageCounter = 0; for (Set viewUsage : viewUsages) { if (viewUsage.contains(relevantElement)) { usageCounter++; } } List elementsWithSameUsage = usageCounters.get(usageCounter); if (elementsWithSameUsage == null) { elementsWithSameUsage = new LinkedList<>(); usageCounters.put(usageCounter, elementsWithSameUsage); } elementsWithSameUsage.add(relevantElement); } int cumulativeGuiElementUsage = 0; for (Set viewUsage : viewUsages) { cumulativeGuiElementUsage += viewUsage.size(); } List unusedElements = usageCounters.get(0); if (unusedElements != null) { int ratio = 1000 * unusedElements.size() / allElementsInView.size(); UsabilitySmellIntensity severity = UsabilitySmellIntensity.getIntensity (ratio, cumulativeGuiElementUsage, -1); if (severity != null) { Map parameters = new HashMap(); parameters.put("ratio", ratio / 10); parameters.put("allDisplays", viewUsages.size()); parameters.put("view", view); parameters.put("unusedGuiElements", unusedElements); parameters.put("allGuiElements", allElementsInView.size()); results.addSmell (severity, UsabilitySmellDescription.UNUSED_GUI_ELEMENTS, parameters); } } } /** * */ private Map> getAllGUIElements(ITaskModel taskModel) { Map> result = new HashMap<>(); for (ITask task : taskModel.getTasks()) { if (task instanceof IEventTask) { for (ITaskInstance instance : task.getInstances()) { Event event = ((IEventTaskInstance) instance).getEvent(); if ((event.getTarget() instanceof IGUIElement) && (isRelevant((IGUIElement) event.getTarget()))) { IGUIView view = ((IGUIElement) event.getTarget()).getView(); Set elements = result.get(view); if (elements == null) { elements = new HashSet<>(); result.put(view, elements); } elements.add((IGUIElement) event.getTarget()); } } } } if (result.size() <= 0) { return result; } // the problem is, that using the GUI model does not allow to find all in a specific view // as the GUI model may return a merged element instead. But anyway, we can add those, which // are in the GUI model and have the same view. GUIModel model = result.values().iterator().next().iterator().next().getGUIModel(); IHierarchicalEventTargetModel.Traverser traverser = model.getTraverser(); IGUIElement currentGUIElement = null; do { if (traverser.hasFirstChild()) { currentGUIElement = traverser.firstChild(); } else if (traverser.hasNextSibling()) { currentGUIElement = traverser.nextSibling(); } else { while (currentGUIElement != null) { currentGUIElement = traverser.parent(); if (traverser.hasNextSibling()) { currentGUIElement = traverser.nextSibling(); break; } } } if (isRelevant(currentGUIElement)) { IGUIView view = currentGUIElement.getView(); Set elements = result.get(view); if (elements == null) { elements = new HashSet<>(); result.put(view, elements); } elements.add(currentGUIElement); } } while (currentGUIElement != null); return result; } /** * */ private Map>> getViewDisplays(List sessions) { final IGUIView[] currentView = new IGUIView[1]; final List actionInstances = new ArrayList<>(); final Map>> result = new HashMap<>(); for (IUserSession session : sessions) { currentView[0] = null; actionInstances.clear(); for (final ITaskInstance currentRoot : session) { currentRoot.accept(new DefaultTaskInstanceTraversingVisitor() { @Override public void visit(IEventTaskInstance eventTaskInstance) { if (eventTaskInstance.getEvent().getTarget() instanceof IGUIElement) { IGUIView view = ((IGUIElement) eventTaskInstance.getEvent().getTarget()).getView(); if ((currentView[0] == null) && (view != null)) { currentView[0] = view; actionInstances.clear(); } else if ((currentView[0] != null) && (!currentView[0].equals(view))) { addRelevantTargets(currentView[0], actionInstances, result); currentView[0] = view; actionInstances.clear(); } } if (eventTaskInstance.getEvent().getTarget() instanceof IGUIElement) { actionInstances.add(eventTaskInstance); } } }); } // add the used GUI elements of the last shown view in the session if (currentView[0] != null) { addRelevantTargets(currentView[0], actionInstances, result); } } return result; } /** * */ private void addRelevantTargets(IGUIView view, List actionInstances, Map>> result) { List> usedGUIElements = result.get(view); if (usedGUIElements == null) { usedGUIElements = new LinkedList<>(); result.put(view, usedGUIElements); } Set elementsInViewDisplay = new HashSet<>(); for (IEventTaskInstance actionInstance : actionInstances) { IGUIElement element = (IGUIElement) actionInstance.getEvent().getTarget(); while (element != null) { if (isRelevant(element)) { elementsInViewDisplay.add(element); } element = element.getParent(); } } usedGUIElements.add(elementsInViewDisplay); } /** * */ private boolean isRelevant(IGUIElement currentGUIElement) { if (currentGUIElement == null) { return false; } if ((currentGUIElement instanceof IButton) || (currentGUIElement instanceof ICheckBox) || (currentGUIElement instanceof IComboBox) || (currentGUIElement instanceof IListBox) || (currentGUIElement instanceof IMenuButton) || (currentGUIElement instanceof ITextArea) || (currentGUIElement instanceof ITextField)) { return true; } return false; } }