Changeset 2042 for trunk/autoquest-core-usability
- Timestamp:
- 10/20/15 10:11:04 (9 years ago)
- Location:
- trunk/autoquest-core-usability/src/main
- Files:
-
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/CheckBoxMultipleSelectionRule.java
r1918 r2042 15 15 package de.ugoe.cs.autoquest.usability; 16 16 17 import java.util.ArrayList; 17 18 import java.util.Collection; 18 19 import java.util.HashMap; 19 20 import java.util.HashSet; 21 import java.util.LinkedList; 20 22 import java.util.List; 21 23 import java.util.Map; 22 24 import java.util.Set; 23 25 26 import de.ugoe.cs.autoquest.eventcore.IEventTarget; 27 import de.ugoe.cs.autoquest.eventcore.gui.MouseClick; 28 import de.ugoe.cs.autoquest.eventcore.gui.ValueSelection; 24 29 import de.ugoe.cs.autoquest.eventcore.guimodel.ICheckBox; 25 30 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; 26 import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskTraversingVisitor; 31 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIView; 32 import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor; 27 33 import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask; 28 34 import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; 29 import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;30 import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;31 35 import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask; 32 36 import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance; 33 37 import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel; 34 import de.ugoe.cs.autoquest.tasktrees.treeifc. TaskMetric;38 import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession; 35 39 36 40 /** … … 68 72 UsabilityEvaluationResult results) 69 73 { 74 System.out.println("\n\n########################################\n"); 75 final List<List<IEventTaskInstance>> actionInstancesInSameView = new LinkedList<>(); 76 77 for (IUserSession session : taskModel.getUserSessions()) { 78 for (ITaskInstance instance : session) { 79 final LinkedList<IEventTaskInstance> currentList = new LinkedList<>(); 80 81 instance.accept(new DefaultTaskInstanceTraversingVisitor() { 82 @Override 83 public void visit(IEventTaskInstance eventTaskInstance) { 84 if (eventTaskInstance.getEvent().getTarget() instanceof IGUIElement) { 85 IEventTarget target = eventTaskInstance.getEvent().getTarget(); 86 IGUIView currentView = ((IGUIElement) target).getView(); 87 88 IGUIView previousView = null; 89 if (currentList.size() > 0) { 90 target = currentList.getLast().getEvent().getTarget(); 91 previousView = ((IGUIElement) target).getView(); 92 } 93 94 if ((previousView == currentView) || 95 ((previousView != null) && (previousView.equals(currentView)))) 96 { 97 currentList.add(eventTaskInstance); 98 } 99 else { 100 if (currentList.size() > 0) { 101 actionInstancesInSameView.add(new ArrayList<>(currentList)); 102 } 103 currentList.clear(); 104 currentList.add(eventTaskInstance); 105 } 106 } 107 } 108 }); 109 110 if (currentList.size() > 0) { 111 actionInstancesInSameView.add(new ArrayList<>(currentList)); 112 } 113 } 114 } 115 116 70 117 Map<IGUIElement, List<IGUIElement>> checkBoxGroups = statistics.getCheckBoxGroups(); 71 118 72 CHECK_NEXT_GROUP: 73 for (List<IGUIElement> group : checkBoxGroups.values()) { 74 Set<ITask> tasksUsingGroup = new HashSet<>(); 75 76 for (IGUIElement checkBox : group) { 77 Set<ITask> tasksUsingCheckBox = getTasksUsingCheckBox(checkBox, taskModel); 119 for (Map.Entry<IGUIElement, List<IGUIElement>> group : checkBoxGroups.entrySet()) { 120 IGUIView currentView = group.getKey().getView(); 121 int noOfEvents = 0; 122 int noOfGroupUsages = 0; 123 int noOfSingleCheckBoxUsages = 0; 124 125 for (List<IEventTaskInstance> actionInstanceList : actionInstancesInSameView) { 126 IEventTarget target = actionInstanceList.get(0).getEvent().getTarget(); 127 IGUIView viewOfList = ((IGUIElement) target).getView(); 78 128 79 for (ITask taskUsingCheckBox : tasksUsingCheckBox) { 80 if (tasksUsingGroup.contains(taskUsingCheckBox)) { 81 continue CHECK_NEXT_GROUP; 82 } 83 else { 84 tasksUsingGroup.add(taskUsingCheckBox); 129 if ((viewOfList == currentView) || 130 ((viewOfList != null) && (viewOfList.equals(currentView)))) 131 { 132 noOfGroupUsages++; 133 boolean[] checkBoxUsages = new boolean[group.getValue().size()]; 134 135 for (IEventTaskInstance actionInstance : actionInstanceList) { 136 int index = 0; 137 for (IGUIElement checkBox : group.getValue()) { 138 if ((("JFC".equals(actionInstance.getEvent().getTarget().getPlatform())) && 139 (actionInstance.getEvent().getType() instanceof MouseClick) && 140 (actionInstance.getEvent().getTarget().equals(checkBox))) || 141 ((actionInstance.getEvent().getType() instanceof ValueSelection<?>) && 142 (actionInstance.getEvent().getTarget().equals(checkBox)))) 143 { 144 checkBoxUsages[index] = !checkBoxUsages[index]; 145 noOfEvents++; 146 } 147 index++; 148 } 149 } 150 151 int noOfCheckedBoxes = 0; 152 153 for (int i = 0; i < checkBoxUsages.length; i++) { 154 if (checkBoxUsages[i]) { 155 noOfCheckedBoxes++; 156 } 157 } 158 159 if (noOfCheckedBoxes == 1) { 160 noOfSingleCheckBoxUsages++; 85 161 } 86 162 } 87 163 } 88 164 89 if (tasksUsingGroup.size() > 0) { 90 int eventCoverage = 0; 91 int allRecordedEvents = 0; 92 93 for (ITask task : tasksUsingGroup) { 94 if (task instanceof IEventTask) { 95 eventCoverage += 96 taskModel.getTaskInfo(task).getMeasureValue(TaskMetric.EVENT_COVERAGE); 97 } 98 } 99 100 for (ITask task : taskModel.getTasks()) { 101 if (task instanceof IEventTask) { 102 allRecordedEvents += 103 taskModel.getTaskInfo(task).getMeasureValue(TaskMetric.EVENT_COVERAGE); 104 } 105 } 106 107 108 UsabilitySmellIntensity intensity = UsabilitySmellIntensity.getIntensity 109 ((int) (1000 * eventCoverage / allRecordedEvents), eventCoverage, -1); 110 111 if (intensity != null) { 112 Map<String, Object> parameters = new HashMap<String, Object>(); 113 parameters.put("radioButtons", group); 114 115 results.addSmell 116 (intensity, UsabilitySmellDescription.CHECK_BOX_SINGLE_SELECTION, 117 parameters); 118 } 119 } 120 } 121 } 122 123 /** 124 * 125 */ 126 private Set<ITask> getTasksUsingCheckBox(final IGUIElement checkBox, ITaskModel taskModel) { 127 final Set<ITask> tasksUsingCheckBox = new HashSet<ITask>(); 128 129 for (ITask candidate : taskModel.getTasks()) { 130 candidate.accept(new DefaultTaskTraversingVisitor() { 131 @Override 132 public void visit(IEventTask eventTask) { 133 if (!eventTask.getInstances().isEmpty()) { 134 IEventTaskInstance instance = 135 (IEventTaskInstance) eventTask.getInstances().iterator().next(); 136 137 if (checkBox.equals(instance.getEvent().getTarget())) { 138 tasksUsingCheckBox.add(eventTask); 139 } 140 } 141 } 142 143 @Override 144 public void visit(IStructuringTemporalRelationship relationship) { 145 if (tasksUsingCheckBox.contains(relationship)) { 146 return; 147 } 148 else { 149 for (ITask child : relationship.getChildren()) { 150 if (tasksUsingCheckBox.contains(child)) { 151 tasksUsingCheckBox.add(relationship); 152 return; 153 } 154 } 155 156 super.visit(relationship); 157 158 for (ITask child : relationship.getChildren()) { 159 if (tasksUsingCheckBox.contains(child)) { 160 tasksUsingCheckBox.add(relationship); 161 break; 162 } 163 } 164 } 165 } 166 167 @Override 168 public void visit(IMarkingTemporalRelationship relationship) { 169 if (tasksUsingCheckBox.contains(relationship)) { 170 return; 171 } 172 else { 173 if (tasksUsingCheckBox.contains(relationship.getMarkedTask())) { 174 tasksUsingCheckBox.add(relationship); 175 return; 176 } 177 178 super.visit(relationship); 179 180 if (tasksUsingCheckBox.contains(relationship.getMarkedTask())) { 181 tasksUsingCheckBox.add(relationship); 182 return; 183 } 184 } 185 } 186 }); 187 } 188 189 return tasksUsingCheckBox; 165 // get a value that is 1 if for one group usage there is on average one check box usage 166 // get a value of 0 if for one group usage there is on average two check box usages 167 168 int ratio = noOfGroupUsages > 0 ? 1000 * noOfSingleCheckBoxUsages / noOfGroupUsages : 0; 169 170 System.out.println(currentView + " " + ratio + " " + noOfGroupUsages + " " + 171 noOfSingleCheckBoxUsages); 172 173 UsabilitySmellIntensity intensity = UsabilitySmellIntensity.getIntensity 174 (ratio, noOfEvents, -1); 175 176 if (intensity != null) { 177 Map<String, Object> parameters = new HashMap<String, Object>(); 178 parameters.put("allUsages", noOfGroupUsages); 179 parameters.put("singleUsages", noOfSingleCheckBoxUsages); 180 parameters.put("ratio", 100 * noOfSingleCheckBoxUsages / noOfGroupUsages); 181 parameters.put("radioButtons", group.getValue()); 182 183 results.addSmell 184 (intensity, UsabilitySmellDescription.CHECK_BOX_SINGLE_SELECTION, parameters); 185 } 186 } 190 187 } 191 188 -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/DataEntryMethodChangeRule.java
r1960 r2042 29 29 import de.ugoe.cs.autoquest.eventcore.gui.ValueSelection; 30 30 import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor; 31 import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskTraversingVisitor; 32 import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask; 31 33 import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; 32 34 import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence; … … 98 100 int ratio = getAverageDataEntryMethodChangeRatio((ISequence) task); 99 101 100 if ( ratio > 0) {102 if ((ratio > 0) && (getLeafNodes(task) > 2)) { 101 103 methodChangeRatios.put(task, ratio); 102 104 } … … 105 107 106 108 return methodChangeRatios; 109 } 110 111 /** 112 * 113 */ 114 private int getLeafNodes(ITask task) { 115 final int[] counter = new int[1]; 116 117 task.accept(new DefaultTaskTraversingVisitor() { 118 @Override 119 public void visit(IEventTask eventTask) { 120 counter[0]++; 121 } 122 }); 123 124 return counter[0]; 107 125 } 108 126 -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/DefaultValueRule.java
r1941 r2042 29 29 30 30 import de.ugoe.cs.autoquest.eventcore.Event; 31 import de.ugoe.cs.autoquest.eventcore.gui.MouseClick; 31 32 import de.ugoe.cs.autoquest.eventcore.gui.TextInput; 32 33 import de.ugoe.cs.autoquest.eventcore.gui.ValueSelection; 33 34 import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel; 34 35 import de.ugoe.cs.autoquest.eventcore.guimodel.ICheckBox; 36 import de.ugoe.cs.autoquest.eventcore.guimodel.IComboBox; 35 37 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; 36 38 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec; … … 136 138 maxObserved = Math.max(maxObserved, selectedValue.getValue()); 137 139 138 if (mostOftenSelected.size() > 0) { 139 ListIterator<Object> iterator = mostOftenSelected.listIterator(); 140 while (iterator.hasNext()) { 141 if (selectedValues.get(iterator.next()) < selectedValue.getValue()) { 142 iterator.previous(); 143 iterator.add(selectedValue.getKey()); 144 145 while (mostOftenSelected.size() > 5) { 146 mostOftenSelected.removeLast(); 147 } 148 149 break; 150 } 151 } 152 } 153 else { 140 boolean added = false; 141 ListIterator<Object> iterator = mostOftenSelected.listIterator(); 142 while (iterator.hasNext()) { 143 if (selectedValues.get(iterator.next()) < selectedValue.getValue()) { 144 iterator.previous(); 145 iterator.add(selectedValue.getKey()); 146 added = true; 147 break; 148 } 149 } 150 151 if (!added) { 154 152 mostOftenSelected.add(selectedValue.getKey()); 153 } 154 155 while (mostOftenSelected.size() > 5) { 156 mostOftenSelected.removeLast(); 155 157 } 156 158 } … … 163 165 } 164 166 165 if ((expected.length == 1) ||167 if ((expected.length > 1) && 166 168 (new ChiSquareTest().chiSquareTest(expected, observed, 0.05))) 167 169 { … … 218 220 */ 219 221 private boolean isValueSelection(ITaskInstance instance) { 220 return (instance instanceof IEventTaskInstance) && 221 ((((IEventTaskInstance) instance).getEvent().getType() instanceof TextInput) || 222 (((IEventTaskInstance) instance).getEvent().getType() instanceof ValueSelection)); 222 if (instance instanceof IEventTaskInstance) { 223 Event event = ((IEventTaskInstance) instance).getEvent(); 224 225 if ((event.getType() instanceof TextInput) || 226 (event.getType() instanceof ValueSelection)) 227 { 228 return true; 229 } 230 231 if (("JFC".equals(event.getTarget().getPlatform())) && 232 (event.getTarget() instanceof ICheckBox) && 233 (event.getType() instanceof MouseClick)) 234 { 235 return true; 236 } 237 } 238 239 return false; 223 240 } 224 241 … … 439 456 ((ValueSelection<?>) valueChange.getEvent().getType()).getSelectedValue(); 440 457 441 if ((target instanceof IRadioButton) || (target instanceof ICheckBox)) { 458 if ((target.target instanceof IRadioButton) || 459 (target.target instanceof ICheckBox)) 460 { 442 461 selectedValue = selectedValue + " (" + target + ")"; 462 } 463 else if (target.target instanceof IComboBox) { 464 if (selectedValue == null) { 465 // this may have happened due to the recording issue that selected 466 // values of combo boxes are not logged correctly. In this case, 467 // pretend to have the a random value selected 468 selectedValue = "randomValueDueToRecordingBug_" + Math.random(); 469 } 470 } 471 } 472 else if (valueChange.getEvent().getType() instanceof MouseClick) { 473 if ((target.target instanceof IRadioButton) || 474 (target.target instanceof ICheckBox)) 475 { 476 selectedValue = target.toString(); 477 } 478 else { 479 throw new IllegalStateException("the implementation needs to be extended " + 480 "to fully support clicks as value changes"); 443 481 } 444 482 } -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/MisleadingClickCueRule.java
r1918 r2042 24 24 import de.ugoe.cs.autoquest.eventcore.gui.MouseClick; 25 25 import de.ugoe.cs.autoquest.eventcore.gui.MouseDoubleClick; 26 import de.ugoe.cs.autoquest.eventcore.guimodel.IButton; 26 27 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; 27 28 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIView; … … 65 66 UsabilityEvaluationResult results) 66 67 { 67 for (Map.Entry<IGUI View, Map<IGUIElement, Integer>> uselessClickCounter :68 for (Map.Entry<IGUIElement, Map<IGUIView, Integer>> uselessClickCounter : 68 69 statistics.getUselessClickCounters().entrySet()) 69 70 { 70 for (Map.Entry<IGUIElement, Integer> counter : uselessClickCounter.getValue().entrySet()) 71 { 72 int uselessClicks = counter.getValue(); 73 int noOfViewDisplays = statistics.getViewOpenedCount(uselessClickCounter.getKey()); 74 75 int ratio = Math.min(1000, 1000 * uselessClicks / noOfViewDisplays); 76 77 UsabilitySmellIntensity intensity = 78 UsabilitySmellIntensity.getIntensity(ratio, uselessClicks, -1); 79 80 if (intensity != null) { 81 Map<String, Object> parameters = new HashMap<String, Object>(); 82 parameters.put("noOfViewDisplays", noOfViewDisplays); 83 parameters.put("uselessClicks", uselessClicks); 84 parameters.put("element", counter.getKey()); 85 parameters.put("view", uselessClickCounter.getKey()); 86 87 results.addSmell 88 (intensity, UsabilitySmellDescription.MISLEADING_CLICK_CUE, parameters); 89 } 71 int uselessClicks = 0; 72 int noOfViewDisplays = 0; 73 74 for (Map.Entry<IGUIView, Integer> counter : uselessClickCounter.getValue().entrySet()) { 75 uselessClicks += counter.getValue(); 76 noOfViewDisplays += statistics.getViewOpenedCount(counter.getKey()); 77 } 78 79 int ratio = Math.min(1000, 1000 * uselessClicks / noOfViewDisplays); 80 81 UsabilitySmellIntensity intensity = 82 UsabilitySmellIntensity.getIntensity(ratio, uselessClicks, -1); 83 84 if (intensity != null) { 85 Map<String, Object> parameters = new HashMap<String, Object>(); 86 parameters.put("noOfViewDisplays", noOfViewDisplays); 87 parameters.put("uselessClicks", uselessClicks); 88 parameters.put("element", uselessClickCounter.getKey()); 89 90 results.addSmell 91 (intensity, UsabilitySmellDescription.MISLEADING_CLICK_CUE, parameters); 90 92 } 91 93 } … … 155 157 (target instanceof IText)) 156 158 { 157 return true; 159 // check if the parent is a button 160 IGUIElement parent = target; 161 while ((parent != null) && !(parent instanceof IButton)) { 162 parent = parent.getParent(); 163 } 164 165 return !(parent instanceof IButton); 158 166 } 159 167 else { … … 171 179 172 180 /** */ 173 private Map<IGUI View, Map<IGUIElement, Integer>> uselessClickCounters = new HashMap<>();181 private Map<IGUIElement, Map<IGUIView, Integer>> uselessClickCounters = new HashMap<>(); 174 182 175 183 /** … … 199 207 */ 200 208 private void addUselessClick(IEventTaskInstance eventTaskInstance) { 201 Map<IGUIElement, Integer> counterMap = uselessClickCounters.get 202 (((IGUIElement) eventTaskInstance.getEvent().getTarget()).getView()); 209 IGUIElement target = (IGUIElement) eventTaskInstance.getEvent().getTarget(); 210 211 Map<IGUIView, Integer> counterMap = uselessClickCounters.get(target); 203 212 204 213 if (counterMap == null) { 205 214 counterMap = new HashMap<>(); 206 uselessClickCounters.put 207 (((IGUIElement) eventTaskInstance.getEvent().getTarget()).getView(), counterMap); 208 } 209 210 Integer counter = counterMap.get(eventTaskInstance.getEvent().getTarget()); 215 uselessClickCounters.put(target, counterMap); 216 } 217 218 Integer counter = counterMap.get(target.getView()); 211 219 212 220 if (counter == null) { 213 counterMap.put( (IGUIElement) eventTaskInstance.getEvent().getTarget(), 1);221 counterMap.put(target.getView(), 1); 214 222 } 215 223 else { 216 counterMap.put( (IGUIElement) eventTaskInstance.getEvent().getTarget(), counter + 1);217 } 218 } 219 220 /** 221 * 222 */ 223 private Map<IGUI View, Map<IGUIElement, Integer>> getUselessClickCounters() {224 counterMap.put(target.getView(), counter + 1); 225 } 226 } 227 228 /** 229 * 230 */ 231 private Map<IGUIElement, Map<IGUIView, Integer>> getUselessClickCounters() { 224 232 return uselessClickCounters; 225 233 } -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/MissingFeedbackRule.java
r1918 r2042 195 195 196 196 if (clicksOnIdenticalButton != null) { 197 if (clicksOnIdenticalButton.size() > 1) { 198 //throw new IllegalStateException("not described in dissertation"); 199 } 200 197 201 long cummulativeImpatience = 0; 198 202 -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/TextInputStatisticsRule.java
r1918 r2042 107 107 int noOfUsagesOfTextField1 = statistics.getUsageCount(entry.textField1); 108 108 int noOfUsagesOfTextField2 = statistics.getUsageCount(entry.textField2); 109 int noOfUsagesOfTextField1WithSameTextInTextField2 = entry.enteredTexts.size(); 110 111 int ratioTextField1 = 112 1000 * noOfUsagesOfTextField1WithSameTextInTextField2 / noOfUsagesOfTextField1; 113 114 int ratioTextField2 = 115 1000 * noOfUsagesOfTextField1WithSameTextInTextField2 / noOfUsagesOfTextField2; 116 117 createTextFieldEntryRepetitionSmell(ratioTextField1, entry.textField1, 118 entry.textField2, results); 119 120 createTextFieldEntryRepetitionSmell(ratioTextField2, entry.textField2, 121 entry.textField1, results); 122 123 } 124 } 125 126 /** 127 * 128 */ 129 private void createTextFieldEntryRepetitionSmell(int ratioOfEqualEntries, 130 ITextField textField1, 131 ITextField textField2, 132 UsabilityEvaluationResult results) 133 { 109 int numberOfEqualEntries = entry.enteredTexts.size(); 110 111 createTextFieldEntryRepetitionSmell(noOfUsagesOfTextField1, numberOfEqualEntries, 112 entry.textField1, entry.textField2, results); 113 114 createTextFieldEntryRepetitionSmell(noOfUsagesOfTextField2, numberOfEqualEntries, 115 entry.textField2, entry.textField1, results); 116 117 } 118 } 119 120 /** 121 * 122 */ 123 private void createTextFieldEntryRepetitionSmell(int allEntries, 124 int numberOfEqualEntries, 125 ITextField textField1, 126 ITextField textField2, 127 UsabilityEvaluationResult results) 128 { 129 int ratio = 1000 * numberOfEqualEntries / allEntries; 130 134 131 UsabilitySmellIntensity severity = 135 UsabilitySmellIntensity.getIntensity(ratio OfEqualEntries);132 UsabilitySmellIntensity.getIntensity(ratio, allEntries, -1); 136 133 137 134 if (severity != null) { 138 135 Map<String, Object> parameters = new HashMap<String, Object>(); 139 parameters.put("textRepetitionRatio", (ratioOfEqualEntries / 10)); 136 parameters.put("numberOfEqualEntries", numberOfEqualEntries); 137 parameters.put("numberOfAllEntries", allEntries); 138 parameters.put("textRepetitionRatio", (ratio / 10)); 140 139 parameters.put("textField1", textField1); 141 140 parameters.put("textField2", textField2); … … 169 168 int ratio = 1000 * noLetterOrDigitCount / allCharactersCount; 170 169 171 UsabilitySmellIntensity severity = UsabilitySmellIntensity.getIntensity(ratio); 170 UsabilitySmellIntensity severity = UsabilitySmellIntensity.getIntensity 171 (ratio, statistics.getAllInputsInto(textField).size(), -1); 172 172 173 173 if (severity != null) { -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/UnusedGUIElementsRule.java
r1918 r2042 15 15 package de.ugoe.cs.autoquest.usability; 16 16 17 import java.util.ArrayList; 17 18 import java.util.HashMap; 18 19 import java.util.HashSet; … … 28 29 import de.ugoe.cs.autoquest.eventcore.guimodel.IComboBox; 29 30 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; 31 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIView; 30 32 import de.ugoe.cs.autoquest.eventcore.guimodel.IListBox; 31 33 import de.ugoe.cs.autoquest.eventcore.guimodel.IMenuButton; 32 34 import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea; 33 35 import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField; 36 import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor; 34 37 import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask; 35 38 import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; … … 37 40 import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance; 38 41 import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel; 42 import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession; 39 43 40 44 /** … … 54 58 public UsabilityEvaluationResult evaluate(ITaskModel taskModel) { 55 59 UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel); 56 57 GUIModel guiModel = getGUIModel(taskModel); 58 Set<IGUIElement> allGUIElements = getAllGUIElements(guiModel); 59 Set<IGUIElement> usedGUIElements = getUsedGUIElements(taskModel); 60 List<IGUIElement> unusedGUIElements = getUnusedGUIElements(usedGUIElements, allGUIElements); 61 handleUnusedGUIElements(unusedGUIElements, allGUIElements, results); 60 61 Map<IGUIView, List<Set<IGUIElement>>> viewDisplays = 62 getViewDisplays(taskModel.getUserSessions()); 63 64 Map<IGUIView, Set<IGUIElement>> allGUIElements = getAllGUIElements(taskModel); 65 66 for (Map.Entry<IGUIView, List<Set<IGUIElement>>> viewDisplay : viewDisplays.entrySet()) { 67 handleUnusedGUIElements 68 (allGUIElements, viewDisplay.getKey(), viewDisplay.getValue(), results); 69 } 62 70 63 71 return results; … … 65 73 66 74 /** 67 * <p> 68 * TODO: comment 69 * </p> 75 * @param results 70 76 * 71 * @param unusedGUIElements 72 * @param results 73 */ 74 private void handleUnusedGUIElements(List<IGUIElement> unusedGUIElements, 75 Set<IGUIElement> allGUIElements, 76 UsabilityEvaluationResult results) 77 */ 78 private void handleUnusedGUIElements(Map<IGUIView, Set<IGUIElement>> usedGUIElements, 79 IGUIView view, 80 List<Set<IGUIElement>> viewUsages, 81 UsabilityEvaluationResult results) 77 82 { 78 int ratio = 1000 * unusedGUIElements.size() / allGUIElements.size(); 79 80 UsabilitySmellIntensity severity = UsabilitySmellIntensity.getIntensity(ratio); 81 82 if (severity != null) { 83 Map<String, Object> parameters = new HashMap<String, Object>(); 84 85 parameters.put("ratio", ratio / 10); 86 parameters.put("noOfUnused", unusedGUIElements.size()); 87 parameters.put("noOfAll", allGUIElements.size()); 88 parameters.put("unusedGuiElements", unusedGUIElements); 89 90 results.addSmell 91 (severity, UsabilitySmellDescription.UNUSED_GUI_ELEMENTS, parameters); 92 } 93 } 94 95 /** 96 * <p> 97 * TODO: comment 98 * </p> 83 Set<IGUIElement> allElementsInView = usedGUIElements.get(view); 84 85 if (allElementsInView == null) { 86 return; 87 } 88 89 Map<Integer, List<IGUIElement>> usageCounters = new HashMap<>(); 90 91 for (IGUIElement relevantElement : allElementsInView) { 92 int usageCounter = 0; 93 94 for (Set<IGUIElement> viewUsage : viewUsages) { 95 if (viewUsage.contains(relevantElement)) { 96 usageCounter++; 97 } 98 } 99 100 List<IGUIElement> elementsWithSameUsage = usageCounters.get(usageCounter); 101 102 if (elementsWithSameUsage == null) { 103 elementsWithSameUsage = new LinkedList<>(); 104 usageCounters.put(usageCounter, elementsWithSameUsage); 105 } 106 107 elementsWithSameUsage.add(relevantElement); 108 } 109 110 int cumulativeGuiElementUsage = 0; 111 for (Set<IGUIElement> viewUsage : viewUsages) { 112 cumulativeGuiElementUsage += viewUsage.size(); 113 } 114 115 List<IGUIElement> unusedElements = usageCounters.get(0); 116 117 if (unusedElements != null) { 118 int ratio = 1000 * unusedElements.size() / allElementsInView.size(); 119 120 UsabilitySmellIntensity severity = UsabilitySmellIntensity.getIntensity 121 (ratio, cumulativeGuiElementUsage, -1); 122 123 if (severity != null) { 124 Map<String, Object> parameters = new HashMap<String, Object>(); 125 126 parameters.put("ratio", ratio / 10); 127 parameters.put("allDisplays", viewUsages.size()); 128 parameters.put("view", view); 129 parameters.put("unusedGuiElements", unusedElements); 130 parameters.put("allGuiElements", allElementsInView.size()); 131 132 results.addSmell 133 (severity, UsabilitySmellDescription.UNUSED_GUI_ELEMENTS, parameters); 134 } 135 } 136 } 137 138 /** 99 139 * 100 * @param taskModel101 * @return102 */103 private GUIModel getGUIModel(ITaskModel taskModel) {140 */ 141 private Map<IGUIView, Set<IGUIElement>> getAllGUIElements(ITaskModel taskModel) { 142 Map<IGUIView, Set<IGUIElement>> result = new HashMap<>(); 143 104 144 for (ITask task : taskModel.getTasks()) { 105 145 if (task instanceof IEventTask) { … … 107 147 Event event = ((IEventTaskInstance) instance).getEvent(); 108 148 109 if (event.getTarget() instanceof IGUIElement) { 110 return ((IGUIElement) event.getTarget()).getGUIModel(); 149 if ((event.getTarget() instanceof IGUIElement) && 150 (isRelevant((IGUIElement) event.getTarget()))) 151 { 152 IGUIView view = ((IGUIElement) event.getTarget()).getView(); 153 154 Set<IGUIElement> elements = result.get(view); 155 156 if (elements == null) { 157 elements = new HashSet<>(); 158 result.put(view, elements); 159 } 160 161 elements.add((IGUIElement) event.getTarget()); 111 162 } 112 163 } … … 114 165 } 115 166 116 return null; 117 } 118 119 /** 120 * <p> 121 * TODO: comment 122 * </p> 123 * 124 * @param taskModel 125 */ 126 private Set<IGUIElement> getUsedGUIElements(ITaskModel taskModel) { 127 Set<IGUIElement> usedGUIElements = new HashSet<IGUIElement>(); 128 129 for (ITask task : taskModel.getTasks()) { 130 if (task instanceof IEventTask) { 131 for (ITaskInstance instance : task.getInstances()) { 132 Event event = ((IEventTaskInstance) instance).getEvent(); 133 134 if (event.getTarget() instanceof IGUIElement) { 135 usedGUIElements.add((IGUIElement) event.getTarget()); 167 // the problem is, that using the GUI model does not allow to find all in a specific view 168 // as the GUI model may return a merged element instead. But anyway, we can add those, which 169 // are in the GUI model and have the same view. 170 171 GUIModel model = result.values().iterator().next().iterator().next().getGUIModel(); 172 173 GUIModel.Traverser traverser = model.getTraverser(); 174 175 IGUIElement currentGUIElement = null; 176 do { 177 if (traverser.hasFirstChild()) { 178 currentGUIElement = traverser.firstChild(); 179 } 180 else if (traverser.hasNextSibling()) { 181 currentGUIElement = traverser.nextSibling(); 182 } 183 else { 184 while (currentGUIElement != null) { 185 currentGUIElement = traverser.parent(); 186 if (traverser.hasNextSibling()) { 187 currentGUIElement = traverser.nextSibling(); 188 break; 136 189 } 137 190 } 138 191 } 139 } 140 141 return usedGUIElements; 142 } 143 144 /** 145 * <p> 146 * TODO: comment 147 * </p> 148 * 149 * @param taskModel 150 */ 151 private Set<IGUIElement> getAllGUIElements(GUIModel guiModel) { 152 Set<IGUIElement> allGUIElements = new HashSet<IGUIElement>(); 153 154 if (guiModel != null) { 155 GUIModel.Traverser traverser = guiModel.getTraverser(); 156 157 IGUIElement currentGUIElement = null; 158 do { 159 if (traverser.hasFirstChild()) { 160 currentGUIElement = traverser.firstChild(); 161 } 162 else if (traverser.hasNextSibling()) { 163 currentGUIElement = traverser.nextSibling(); 164 } 165 else { 166 while (currentGUIElement != null) { 167 currentGUIElement = traverser.parent(); 168 if (traverser.hasNextSibling()) { 169 currentGUIElement = traverser.nextSibling(); 170 break; 192 193 if (isRelevant(currentGUIElement)) { 194 IGUIView view = currentGUIElement.getView(); 195 196 Set<IGUIElement> elements = result.get(view); 197 198 if (elements == null) { 199 elements = new HashSet<>(); 200 result.put(view, elements); 201 } 202 203 elements.add(currentGUIElement); 204 } 205 } 206 while (currentGUIElement != null); 207 208 return result; 209 } 210 211 /** 212 * 213 */ 214 private Map<IGUIView, List<Set<IGUIElement>>> getViewDisplays(List<IUserSession> sessions) { 215 final IGUIView[] currentView = new IGUIView[1]; 216 final List<IEventTaskInstance> actionInstances = new ArrayList<>(); 217 final Map<IGUIView, List<Set<IGUIElement>>> result = new HashMap<>(); 218 219 for (IUserSession session : sessions) { 220 currentView[0] = null; 221 actionInstances.clear(); 222 223 for (final ITaskInstance currentRoot : session) { 224 currentRoot.accept(new DefaultTaskInstanceTraversingVisitor() { 225 @Override 226 public void visit(IEventTaskInstance eventTaskInstance) { 227 if (eventTaskInstance.getEvent().getTarget() instanceof IGUIElement) { 228 IGUIView view = 229 ((IGUIElement) eventTaskInstance.getEvent().getTarget()).getView(); 230 231 if ((currentView[0] == null) && (view != null)) { 232 currentView[0] = view; 233 actionInstances.clear(); 234 } 235 else if ((currentView[0] != null) && (!currentView[0].equals(view))) { 236 addRelevantTargets(currentView[0], actionInstances, result); 237 238 currentView[0] = view; 239 actionInstances.clear(); 240 } 241 } 242 243 if (eventTaskInstance.getEvent().getTarget() instanceof IGUIElement) { 244 actionInstances.add(eventTaskInstance); 171 245 } 172 246 } 173 } 174 175 if (isRelevant(currentGUIElement)) { 176 allGUIElements.add(currentGUIElement); 177 } 178 } 179 while (currentGUIElement != null); 180 } 181 182 return allGUIElements; 183 } 184 185 /** 186 * <p> 187 * TODO: comment 188 * </p> 247 }); 248 } 249 250 // add the used GUI elements of the last shown view in the session 251 if (currentView[0] != null) { 252 addRelevantTargets(currentView[0], actionInstances, result); 253 } 254 } 255 256 return result; 257 } 258 259 /** 189 260 * 190 * @param taskModel191 */192 private List<IGUIElement> getUnusedGUIElements(Set<IGUIElement> usedGUIElements,193 Set<IGUIElement> allGUIElements)261 */ 262 private void addRelevantTargets(IGUIView view, 263 List<IEventTaskInstance> actionInstances, 264 Map<IGUIView, List<Set<IGUIElement>>> result) 194 265 { 195 List<IGUIElement> unusedGUIElements = new LinkedList<IGUIElement>(); 196 for (IGUIElement currentGUIElement : allGUIElements) { 197 if (isRelevant(currentGUIElement) && 198 !belongsToUsedGUIElements(currentGUIElement, usedGUIElements)) 199 { 200 unusedGUIElements.add(currentGUIElement); 201 } 202 } 203 204 return unusedGUIElements; 205 } 206 207 /** 208 * <p> 209 * TODO: comment 210 * </p> 266 List<Set<IGUIElement>> usedGUIElements = result.get(view); 267 268 if (usedGUIElements == null) { 269 usedGUIElements = new LinkedList<>(); 270 result.put(view, usedGUIElements); 271 } 272 273 Set<IGUIElement> elementsInViewDisplay = new HashSet<>(); 274 275 for (IEventTaskInstance actionInstance : actionInstances) { 276 IGUIElement element = (IGUIElement) actionInstance.getEvent().getTarget(); 277 278 while (element != null) { 279 if (isRelevant(element)) { 280 elementsInViewDisplay.add(element); 281 } 282 283 element = element.getParent(); 284 } 285 } 286 287 usedGUIElements.add(elementsInViewDisplay); 288 } 289 290 /** 211 291 * 212 * @param currentGUIElement213 * @param usedGUIElements214 * @return215 */216 private boolean belongsToUsedGUIElements(IGUIElement relevantGUIElement,217 Set<IGUIElement> usedGUIElements)218 {219 if (usedGUIElements.contains(relevantGUIElement)) {220 return true;221 }222 else {223 // in some cases, the events are recorded for the children of the relevant GUI elements224 // therefore, check the children, as well.225 List<IGUIElement> children =226 relevantGUIElement.getGUIModel().getChildren(relevantGUIElement);227 228 if (children != null) {229 for (IGUIElement child : children) {230 if (belongsToUsedGUIElements(child, usedGUIElements)) {231 return true;232 }233 }234 }235 }236 237 return false;238 }239 240 /**241 * <p>242 * TODO: comment243 * </p>244 *245 * @param currentGUIElement246 * @return247 292 */ 248 293 private boolean isRelevant(IGUIElement currentGUIElement) { -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/UsabilityEvaluationManager.java
r1918 r2042 16 16 17 17 import java.util.ArrayList; 18 import java.util.HashMap; 19 import java.util.LinkedList; 18 20 import java.util.List; 21 import java.util.ListIterator; 22 import java.util.Map; 19 23 import java.util.logging.Level; 20 24 … … 48 52 */ 49 53 private void init() { 50 rules.add(new TaskTreeTestRule()); 51 // rules.add(new TextInputStatisticsRule()); 52 // rules.add(new MissingFeedbackRule()); 53 // rules.add(new EventCoverageRatioRule()); 54 // rules.add(new TargetDistanceRule()); 55 // rules.add(new RequiredInefficientActionsRule()); 56 // rules.add(new DataEntryMethodChangeRule()); 54 // rules.add(new TaskTreeTestRule()); 55 56 rules.add(new EventCoverageRatioRule()); 57 rules.add(new RequiredInefficientActionsRule()); 58 rules.add(new TargetDistanceRule()); 59 rules.add(new MissingFeedbackRule()); 60 rules.add(new DataEntryMethodChangeRule()); 61 rules.add(new CommonTaskRateRule()); 62 rules.add(new TextInputStatisticsRule()); 63 rules.add(new CheckBoxMultipleSelectionRule()); 64 rules.add(new MisleadingClickCueRule()); 65 rules.add(new DefaultCursorPositioningRule()); 57 66 rules.add(new DefaultValueRule()); 58 // rules.add(new CheckBoxMultipleSelectionRule()); 59 // rules.add(new CommonTaskRateRule()); 60 // rules.add(new MisleadingClickCueRule()); 61 // rules.add(new DefaultCursorPositioningRule()); 62 // rules.add(new UnusedGUIElementsRule()); 67 rules.add(new UnusedGUIElementsRule()); 68 63 69 // rules.add(new TaskCooccurrenceRule()); 64 70 } … … 67 73 * 68 74 */ 69 public UsabilityEvaluationResult evaluateUsability(ITaskModel taskModel ) {75 public UsabilityEvaluationResult evaluateUsability(ITaskModel taskModel, int maxCount) { 70 76 Console.traceln(Level.INFO, "evaluating usability of task model " + taskModel); 71 77 … … 73 79 74 80 for (UsabilityEvaluationRule rule : rules) { 75 Console.traceln(Level.INFO, " applying rule " + rule.getClass().getSimpleName());81 Console.traceln(Level.INFO, "\napplying rule " + rule.getClass().getSimpleName()); 76 82 UsabilityEvaluationResult result = rule.evaluate(taskModel); 77 interimResults.add(result); 78 Console.traceln(Level.INFO, "the rule found " + result.getAllSmells().size() + 79 " usability smells."); 80 81 List<ITask> referredTasks = new ArrayList<ITask>(); 82 83 84 Map<String, List<UsabilitySmell>> smellGroups = new HashMap<>(); 85 83 86 for (UsabilitySmell smell : result.getAllSmells()) { 84 if (smell.getSmellingTask() != null) { 85 referredTasks.add(smell.getSmellingTask()); 86 } 87 } 88 89 int counter = 0; 90 for (int i = 0; i < referredTasks.size(); i++) { 91 for (int j = 0; j < referredTasks.size(); j++) { 92 if (isChildOf(referredTasks.get(i), referredTasks.get(j))) { 93 counter++; 94 break; 87 List<UsabilitySmell> smellGroup = smellGroups.get(smell.getBriefDescription()); 88 89 if (smellGroup == null) { 90 smellGroup = new LinkedList<>(); 91 smellGroups.put(smell.getBriefDescription(), smellGroup); 92 } 93 94 smellGroup.add(smell); 95 } 96 97 for (Map.Entry<String, List<UsabilitySmell>> smellGroup : smellGroups.entrySet()) { 98 Console.traceln(Level.INFO, "the rule found " + smellGroup.getValue().size() + 99 " usability smells of type \"" + smellGroup.getKey() + "\""); 100 101 result = new UsabilityEvaluationResult(taskModel, smellGroup.getValue()); 102 103 checkDuplicates(result); 104 105 if (maxCount < result.getAllSmells().size()) { 106 Console.traceln(Level.INFO, "filtering for " + maxCount + 107 " smells of same type with highest event coverage."); 108 109 LinkedList<UsabilitySmell> sortedSmells = new LinkedList<>(); 110 111 for (UsabilitySmell smell : result.getAllSmells()) { 112 ListIterator<UsabilitySmell> iterator = sortedSmells.listIterator(); 113 114 boolean added = false; 115 116 while (iterator.hasNext()) { 117 if (iterator.next().getIntensity().getEventCoverage() < 118 smell.getIntensity().getEventCoverage()) 119 { 120 iterator.previous(); 121 iterator.add(smell); 122 added = true; 123 break; 124 } 125 } 126 127 if (!added) { 128 sortedSmells.add(smell); 129 } 130 131 while (sortedSmells.size() > maxCount) { 132 sortedSmells.removeLast(); 133 } 95 134 } 96 } 97 } 98 99 if (counter > 0) { 100 Console.traceln(Level.INFO, counter + " of the findings are duplicates in " + 101 "that they refer to tasks whose parent tasks are also " + 102 "referred by the findings"); 135 136 result = new UsabilityEvaluationResult(taskModel, sortedSmells); 137 checkDuplicates(result); 138 } 139 140 interimResults.add(result); 103 141 } 104 142 } … … 112 150 113 151 /** 114 * <p>115 * TODO: comment116 * </p>117 152 * 118 * @param iTask 119 * @param iTask2 120 * @return 153 */ 154 private void checkDuplicates(UsabilityEvaluationResult result) { 155 List<ITask> referredTasks = new ArrayList<ITask>(); 156 157 for (UsabilitySmell smell : result.getAllSmells()) { 158 if (smell.getSmellingTask() != null) { 159 referredTasks.add(smell.getSmellingTask()); 160 } 161 } 162 163 int counter = 0; 164 for (int i = 0; i < referredTasks.size(); i++) { 165 for (int j = 0; j < referredTasks.size(); j++) { 166 if (isChildOf(referredTasks.get(i), referredTasks.get(j))) { 167 counter++; 168 break; 169 } 170 } 171 } 172 173 if (counter > 0) { 174 Console.traceln(Level.INFO, counter + " of the findings are duplicates in " + 175 "that they refer to tasks whose parent tasks are also " + 176 "referred by the findings"); 177 } 178 } 179 180 /** 181 * 121 182 */ 122 183 private boolean isChildOf(final ITask potChild, ITask potParent) { -
trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/UsabilityEvaluationResult.java
r1918 r2042 16 16 17 17 import java.util.ArrayList; 18 import java.util.Collection; 18 19 import java.util.List; 19 20 import java.util.Map; … … 41 42 public UsabilityEvaluationResult(ITaskModel taskModel) { 42 43 this.taskModel = taskModel; 44 } 45 46 /** 47 * 48 */ 49 public UsabilityEvaluationResult(ITaskModel taskModel, 50 Collection<UsabilitySmell> smells) 51 { 52 this.taskModel = taskModel; 53 for (UsabilitySmell smell : smells) { 54 this.smells.add(smell); 55 } 43 56 } 44 57 -
trunk/autoquest-core-usability/src/main/resources/smellDescriptions_en.xml
r1950 r2042 21 21 In 22 22 </textFragment> 23 <parameterFragment parameterName="numberOfEqualEntries" /> 24 <textFragment> 25 of 26 </textFragment> 27 <parameterFragment parameterName="numberOfAllEntries" /> 28 <textFragment> 29 times ( 30 </textFragment> 23 31 <parameterFragment parameterName="textRepetitionRatio" /> 24 32 <textFragment> 25 % of entering text into text field33 %) of entering text into text field 26 34 </textFragment> 27 35 <parameterFragment parameterName="textField1" /> … … 173 181 174 182 <smellDescription smellId="UNUSED_GUI_ELEMENTS" briefText="unused GUI elements"> 175 <parameterFragment parameterName="ratio" /> 176 <textFragment> 177 % of the GUI elements ( 178 </textFragment> 179 <parameterFragment parameterName="noOfUnused" /> 180 <textFragment> 181 of 182 </textFragment> 183 <parameterFragment parameterName="noOfAll" /> 184 <textFragment> 185 ) are not used. This can have several reasons. Either they are not 186 visible to the user or they are not required. If they are important, they may not be 187 recognized by the user. This can be caused by visual design or because they are too hidden. 188 If they are less important, they can either be removed from the GUI or relocated to a less 189 prominent position. The unused GUI elements are: 183 <textFragment> 184 In 185 </textFragment> 186 <parameterFragment parameterName="allDisplays" /> 187 <textFragment> 188 times the view 189 </textFragment> 190 <parameterFragment parameterName="view" /> 191 <textFragment> 192 is displayed, the following GUI elements belonging to the view were never used. They make up 193 </textFragment> 194 <parameterFragment parameterName="ratio" /> 195 <textFragment> 196 % of all 197 </textFragment> 198 <parameterFragment parameterName="allGuiElements" /> 199 <textFragment> 200 interaction elements in that view. This indicates, that they are not required and 201 can, therefore, be removed. The unused GUI elements are: 190 202 </textFragment> 191 203 <parameterFragment parameterName="unusedGuiElements" /> … … 227 239 <smellDescription smellId="CHECK_BOX_SINGLE_SELECTION" briefText="check box single selection"> 228 240 <textFragment> 229 Found a group of check boxes of which only one is usually selected. In this case, radio 230 buttons should be used instead, if the alternatives are mutually exclusive. The check boxes 241 Found a group of check boxes of which only one is usually selected. In 242 </textFragment> 243 <parameterFragment parameterName="allUsages" /> 244 <textFragment> 245 usages of the check box group, 246 </textFragment> 247 <parameterFragment parameterName="singleUsages" /> 248 <textFragment> 249 times ( 250 </textFragment> 251 <parameterFragment parameterName="ratio" /> 252 <textFragment> 253 %) only one of all check boxes was selected finally by the user (considering, that all 254 check boxes are always unchecked when they are displayed). If the alternatives represented by 255 the check boxes are mutually exclusive, radio buttons should be used instead. The check boxes 231 256 belonging to the group are: 232 257 </textFragment> … … 255 280 <parameterFragment parameterName="noOfViewDisplays" /> 256 281 <textFragment> 257 times the view 258 </textFragment> 259 <parameterFragment parameterName="view" /> 260 <textFragment> 261 has been displayed, the element 282 times it has been displayed to users, the element 262 283 </textFragment> 263 284 <parameterFragment parameterName="element" />
Note: See TracChangeset
for help on using the changeset viewer.