diff options
-rwxr-xr-x | bin/gla11y | 1145 | ||||
-rw-r--r-- | solenv/gbuild/UIConfig.mk | 67 | ||||
-rw-r--r-- | solenv/gbuild/platform/com_GCC_class.mk | 2 | ||||
-rw-r--r-- | solenv/gbuild/platform/com_MSC_class.mk | 2 | ||||
-rw-r--r-- | solenv/sanitizers/ui/cui.suppr | 11 | ||||
-rw-r--r-- | solenv/sanitizers/ui/svt.suppr | 4 | ||||
-rw-r--r-- | solenv/sanitizers/ui/svx.suppr | 18 |
7 files changed, 1100 insertions, 149 deletions
diff --git a/bin/gla11y b/bin/gla11y index fa6e7281a0cd..e19e580d0d4f 100755 --- a/bin/gla11y +++ b/bin/gla11y @@ -36,23 +36,263 @@ try: lxml = True except ImportError: if sys.version_info < (2,7): + print("gla11y needs lxml or python >= 2.7") exit() import xml.etree.ElementTree as ET lxml = False +# Toplevel widgets +widgets_toplevel = [ + 'GtkWindow', + 'GtkOffscreenWindow', + 'GtkApplicationWindow', + 'GtkDialog', + 'GtkAboutDialog', + 'GtkFileChooserDialog', + 'GtkColorChooserDialog', + 'GtkFontChooserDialog', + 'GtkMessageDialog', + 'GtkRecentChooserDialog', + 'GtkAssistant', + 'GtkAppChooserDialog', + 'GtkPrintUnixDialog', + 'GtkShortcutsWindow', +] + +widgets_ignored = widgets_toplevel + [ + # Containers + 'GtkBox', + 'GtkGrid', + 'GtkNotebook', + 'GtkFrame', + 'GtkAspectFrame', + 'GtkListBox', + 'GtkFlowBox', + 'GtkOverlay', + 'GtkMenuBar', + 'GtkToolbar', + 'GtkToolpalette', + 'GtkPaned', + 'GtkHPaned', + 'GtkVPaned', + 'GtkButtonBox', + 'GtkHButtonBox', + 'GtkVButtonBox', + 'GtkLayout', + 'GtkFixed', + 'GtkEventBox', + 'GtkExpander', + 'GtkViewport', + 'GtkScrolledWindow', + 'GtkAlignment', + 'GtkRevealer', + 'GtkSearchBar', + 'GtkHeaderBar', + 'GtkStack', + 'GtkStackSwticher', + 'GtkPopover', + 'GtkPopoverMenu', + 'GtkActionBar', + 'GtkHandleBox', + 'GtkShortcutsSection', + 'GtkShortcutsGroup', + 'GtkTable', + + 'GtkVBox', + 'GtkHBox', + 'GtkToolItem', + 'GtkMenu', + + # Invisible actions + 'GtkSeparator', + 'GtkHSeparator', + 'GtkVSeparator', + 'GtkAction', + 'GtkToggleAction', + 'GtkActionGroup', + 'GtkCellRendererGraph', + 'GtkCellRendererPixbuf', + 'GtkCellRendererProgress', + 'GtkCellRendererSpin', + 'GtkCellRendererText', + 'GtkCellRendererToggle', + 'GtkSeparatorMenuItem', + 'GtkSeparatorToolItem', + + # Storage objects + 'GtkListStore', + 'GtkTreeStore', + 'GtkTreeModelFilter', + 'GtkTreeModelSort', + + 'GtkEntryBuffer', + 'GtkTextBuffer', + 'GtkTextTag', + 'GtkTextTagTable', + + 'GtkSizeGroup', + 'GtkWindowGroup', + 'GtkAccelGroup', + 'GtkAdjustment', + 'GtkEntryCompletion', + 'GtkIconFactory', + 'GtkStatusIcon', + 'GtkFileFilter', + 'GtkRecentFilter', + 'GtkRecentManager', + 'GThemedIcon', + + 'GtkTreeSelection', + + 'GtkListBoxRow', + + # Useless to label + 'GtkScrollbar', + 'GtkHScrollbar', + 'GtkStatusbar', + 'GtkInfoBar', + + # These are actually labels + 'GtkLinkButton', + + # This precisely give a11y information :) + 'AtkObject', +] + +widgets_suffixignored = [ +] + +# These widgets always need a label +widgets_needlabel = [ + 'GtkEntry', + 'GtkSearchEntry', + 'GtkScale', + 'GtkHScale', + 'GtkVScale', + 'GtkSpinButton', + 'GtkSwitch', +] + +# These widgets normally have their own label +widgets_buttons = [ + 'GtkButton', + 'GtkToolButton', + 'GtkToggleButton', + 'GtkToggleToolButton', + 'GtkRadioButton', + 'GtkRadioToolButton', + 'GtkCheckButton', + 'GtkModelButton', + 'GtkLockButton', + 'GtkColorButton', + 'GtkMenuButton', + + 'GtkMenuItem', + 'GtkImageMenuItem', + 'GtkMenuToolButton', + 'GtkRadioMenuItem', + 'GtkCheckMenuItem', +] + +# These widgets are labels that can label other widgets +widgets_labels = [ + 'GtkLabel', + 'GtkAccelLabel', +] + +# The rest should probably be labelled if there are orphan labels + +# GtkSpinner +# GtkProgressBar +# GtkLevelBar + +# GtkComboBox +# GtkComboBoxText +# GtkFileChooserButton +# GtkAppChooserButton +# GtkFontButton +# GtkCalendar +# GtkColorChooserWidget + +# GtkCellView +# GtkTreeView +# GtkTreeViewColumn +# GtkTextView +# GtkIconView + +# GtkImage +# GtkArrow +# GtkDrawingArea + +# GtkScaleButton +# GtkVolumeButton + + +# TODO: +# GtkColorPlane ? +# GtkColorScale ? +# GtkColorSwatch ? +# GtkFileChooserWidget ? +# GtkFishbowl ? +# GtkFontChooserWidget ? +# GtkIcon ? +# GtkInspector* ? +# GtkMagnifier ? +# GtkPathBar ? +# GtkPlacesSidebar ? +# GtkPlacesView ? +# GtkPrinterOptionWidget ? +# GtkStackCombo ? +# GtkStackSidebar ? + progname = os.path.basename(sys.argv[0]) + suppressions = {} +ids = {} +ids_dup = {} +labelled_by_elm = {} +label_for_elm = {} +mnemonic_for_elm = {} + gen_suppr = None gen_supprfile = None suppr_prefix = "" outfile = None + pflag = False -Werror = False -Wnone = False + +warn_orphan_labels = True + errors = 0 errexists = 0 warnings = 0 warnexists = 0 +fatals = 0 +fatalexists = 0 + +enables = [ ] +dofatals = [ ] + +# +# XML browsing and printing functions +# + +def elm_parent(root, elm): + """ + Return the parent of the element. + """ + if lxml: + return elm.getparent() + else: + def find_parent(cur, elm): + for o in cur: + if o == elm: + return cur + parent = find_parent(o, elm) + if parent is not None: + return parent + return None + return find_parent(root, elm) def step_elm(elm): """ @@ -96,18 +336,23 @@ def errpath(filename, tree, elm): oid = elm.attrib.get('id') if oid is not None: oid = oid.encode('ascii','ignore').decode('ascii') - path += "[@id='%s']" % oid - if lxml: - elm = elm.getparent() - while elm is not None: - step = step_elm(elm) - path = step + path - elm = elm.getparent() + path = "//" + path + "[@id='%s']" % oid else: - path = find_elm(tree.getroot(), elm)[:-1] + if lxml: + elm = elm.getparent() + while elm is not None: + step = step_elm(elm) + path = step + path + elm = elm.getparent() + else: + path = find_elm(tree.getroot(), elm)[:-1] path = filename + ':' + path return path +# +# Warning/Error printing functions +# + def elm_prefix(filename, elm): """ Return the display prefix of the element @@ -128,10 +373,49 @@ def elm_name(elm): if 'id' in elm.attrib: id = elm.attrib['id'].encode('ascii','ignore').decode('ascii') name += "'%s' " % id + if not name: + name = "'" + elm.tag + "'" + if lxml: + name += " line " + str(elm.sourceline) return name return "" -def elm_suppr(filename, tree, elm, msgtype): +def elm_name_line(elm): + """ + Return a display name of the element with line number + """ + if elm is not None: + name = elm_name(elm) + if lxml and " line " not in name: + name += "line " + str(elm.sourceline) + " " + return name + return "" + +def elm_line(elm): + """ + Return the line for the given element. + """ + if lxml: + return " line " + str(elm.sourceline) + else: + return "" + +def elms_lines(elms): + """ + Return the list of lines for the given elements. + """ + if lxml: + return " lines " + ', '.join([str(l.sourceline) for l in elms]) + else: + return "" + +def elms_names_lines(elms): + """ + Return the list of names and lines for the given elements. + """ + return ', '.join([elm_name_line(elm) for elm in elms]) + +def elm_suppr(filename, tree, elm, msgtype, dogen): """ Return the prefix to be displayed to the user and the suppression line for the warning type "msgtype" for element "elm" @@ -146,7 +430,7 @@ def elm_suppr(filename, tree, elm, msgtype): if suppressions or gen_suppr is not None: suppr = '%s %s' % (prefix, msgtype) - if gen_suppr is not None and msgtype is not None: + if gen_suppr is not None and msgtype is not None and dogen: if gen_supprfile is None: gen_supprfile = open(gen_suppr, 'w') print(suppr, file=gen_supprfile) @@ -161,90 +445,458 @@ def elm_suppr(filename, tree, elm, msgtype): return (prefix, suppr) -def err(filename, tree, elm, msgtype, msg): +def is_enabled(elm, msgtype, l, default): """ - Emit an error for an element + Test whether warning type msgtype is enabled for elm in l """ - global errors, errexists - - (prefix, suppr) = elm_suppr(filename, tree, elm, msgtype) - - if suppr in suppressions: - # Suppressed - errexists += 1 - return - - errors += 1 - msg = "%s ERROR: %s%s" % (prefix, elm_name(elm), msg) - print(msg) - if outfile is not None: - print(msg, file=outfile) - + enabled = default + for (enable, thetype, klass) in l: + # Match warning type + if thetype is not None: + if thetype != msgtype: + continue + # Match elm class + if klass is not None and elm is not None: + if klass != elm.attrib.get('class'): + continue + enabled = enable + return enabled -def warn(filename, tree, elm, msgtype, msg): +def err(filename, tree, elm, msgtype, msg, error = True): """ - Emit a warning for an element + Emit a warning or error for an element """ - global Werror, Wnone, errors, errexists, warnings, warnexists + global errors, errexists, warnings, warnexists, fatals, fatalexists - if Wnone: + # Let user tune whether a warning or error + fatal = is_enabled(elm, msgtype, dofatals, error) + + # By default warnings and errors are enabled, but let user tune it + if not is_enabled(elm, msgtype, enables, True): return - (prefix, suppr) = elm_suppr(filename, tree, elm, msgtype) + (prefix, suppr) = elm_suppr(filename, tree, elm, msgtype, True) if suppr in suppressions: # Suppressed - if Werror: + suppressions[suppr] = False + if fatal: + fatalexists += 1 + if error: errexists += 1 else: warnexists += 1 return - if Werror: + if error: errors += 1 else: warnings += 1 + if fatal: + fatals += 1 - msg = "%s WARNING: %s%s" % (prefix, elm_name(elm), msg) + msg = "%s %s%s: %s%s" % (prefix, + "FATAL " if fatal else "", + "ERROR" if error else "WARNING", + elm_name(elm), msg) print(msg) if outfile is not None: print(msg, file=outfile) +def warn(filename, tree, elm, msgtype, msg): + """ + Emit a warning for an element + """ + err(filename, tree, elm, msgtype, msg, False) + +# +# Labelling testing functions +# -def check_objects(filename, tree, elm, objects, target): +def find_button_parent(root, elm): """ - Check that objects contains exactly one object + Find a parent which is a button """ - length = len(list(objects)) - if length == 0: - err(filename, tree, elm, "undeclared-target", "uses undeclared target '%s'" % target) - elif length > 1: - err(filename, tree, elm, "multiple-target", "several targets are named '%s'" % target) + if lxml: + parent = elm.getparent() + if parent is not None: + if parent.attrib.get('class') in widgets_buttons: + return parent + return find_button_parent(root, parent) + else: + def find_parent(cur, elm): + for o in cur: + if o == elm: + if cur.attrib.get('class') in widgets_buttons: + # we are the button, immediately above the target + return cur + else: + # we aren't the button, but target is over there + return True + parent = find_parent(o, elm) + if parent == True: + # It is over there, but didn't find a button yet + if cur.attrib.get('class') in widgets_buttons: + # we are the button + return cur + else: + return True + if parent is not None: + # we have the button parent over there + return parent + return None + parent = find_parent(root, elm) + if parent == True: + parent = None + return parent + -def check_props(filename, tree, root, elm, props): +def is_labelled_parent(elm): """ - Check the given list of relation properties + Return whether this element is a labelled parent """ - for prop in props: - objects = root.iterfind(".//object[@id='%s']" % prop.text) - check_objects(filename, tree, elm, objects, prop.text) + klass = elm.attrib.get('class') + if klass in widgets_toplevel: + return True + if klass == 'GtkShortcutsGroup': + children = elm.findall("property[@name='title']") + if len(children) >= 1: + return True + if klass == 'GtkFrame' or klass == 'GtkNotebook': + children = elm.findall("child[@type='tab']") + elm.findall("child[@type='label']") + if len(children) >= 1: + return True + return False -def check_rels(filename, tree, root, elm, rels): +def elm_labelled_parent(root, elm): """ - Check the given list of relations + Return the first labelled parent of the element, which can thus be used as + the root of widgets with common labelled context """ - for rel in rels: + + if lxml: + def find_labelled_parent(elm): + if is_labelled_parent(elm): + return elm + parent = elm.getparent() + if parent is None: + return None + return find_labelled_parent(parent) + parent = elm.getparent() + if parent is None: + return None + return find_labelled_parent(elm.getparent()) + else: + def find_labelled_parent(cur, elm): + if cur == elm: + # the target element is over there + return True + for o in cur: + parent = find_labelled_parent(o, elm) + if parent == True: + # target element is over there, check ourself + if is_labelled_parent(cur): + # yes, and we are the first ancestor of the target element + return cur + else: + # no, but target element is over there. + return True + if parent != None: + # the first ancestor of the target element was over there + return parent + return None + parent = find_labelled_parent(root, elm) + if parent == True: + parent = None + return parent + +def is_orphan_label(filename, tree, root, obj, orphan_root, doprint = False): + """ + Check whether this label has no accessibility relation, or doubtful relation + because another label labels the same target + """ + global label_for_elm, labelled_by_elm, mnemonic_for_elm, warnexists + + # label-for + label_for = obj.findall("accessibility/relation[@type='label-for']") + for rel in label_for: target = rel.attrib['target'] - targets = root.iterfind(".//object[@id='%s']" % target) - check_objects(filename, tree, elm, targets, target) + l = label_for_elm[target] + if len(l) > 1: + return True -def elms_lines(elms): + # mnemonic_widget + mnemonic_for = obj.findall("property[@name='mnemonic_widget']") + for rel in mnemonic_for: + target = rel.text + l = mnemonic_for_elm[target] + if len(l) > 1: + return True + + if len(label_for) > 0: + # At least one label-for, we are not orphan. + return False + + if len(mnemonic_for) > 0: + # At least one mnemonic_widget, we are not orphan. + return False + + labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") + if len(labelled_by) > 0: + # Oh, a labelled label, probably not to be labelling anything + return False + + parent = elm_parent(root, obj) + if parent is not None: + childtype = parent.attrib.get('type') + if childtype is None: + childtype = parent.attrib.get('internal-child') + if parent.tag == 'child' and childtype == 'label' \ + or childtype == 'tab': + # This is a frame or a notebook label, not orphan. + return False + + if find_button_parent(root, obj) is not None: + # This label is part of a button + return False + + oid = obj.attrib.get('oid') + if oid is not None: + if oid in labelled_by_elm: + # Some widget is labelled by us, we are not orphan. + # We should have had a label-for, will warn about it later. + return False + + # No label-for, no mnemonic-for, no labelled-by, we are orphan. + (_, suppr) = elm_suppr(filename, tree, obj, "orphan-label", False) + if suppr in suppressions: + # Warning suppressed for this label + if suppressions[suppr]: + warnexists += 1 + suppressions[suppr] = False + return False + + if doprint: + context = elm_name(orphan_root) + if context: + context = " within " + context + warn(filename, tree, obj, "orphan-label", "does not specify what it labels" + context) + return True + +def is_orphan_widget(filename, tree, root, obj, orphan, orphan_root, doprint = False): """ - Return the list of lines for the given elements. + Check whether this widget has no accessibility relation. """ - if lxml: - return ": lines " + ', '.join([str(l.sourceline) for l in elms]) + global warnexists + if obj.tag != 'object': + return False + + oid = obj.attrib.get('id') + klass = obj.attrib.get('class') + + # "Don't care" special case + if klass in widgets_ignored: + return False + for suffix in widgets_suffixignored: + if klass[-len(suffix):] == suffix: + return False + + # Widgets usual do not strictly require a label, i.e. a labelled parent + # is enough for context, but some do always need one. + requires_label = klass in widgets_needlabel + + labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") + + # Labels special case + if klass in widgets_labels: + return False + + # Case 1: has an explicit <child internal-child="accessible"> sub-element + children = obj.findall("child[@internal-child='accessible']") + if len(children) > 1 and doprint: + err(filename, tree, obj, "multiple-accessible", "has multiple <child internal-child='accessible'>" + "%s" % elms_lines(children)) + if len(children) >= 1: + return False + + # Case 2: has an <accessibility> sub-element with a "labelled-by" + # <relation> pointing to an existing element. + if len(labelled_by) > 0: + return False + + # Case 3: has a label-for + if oid in label_for_elm: + return False + + # Case 4: has a mnemonic + if oid in mnemonic_for_elm: + return False + + # Case 5: Has a <property name="tooltip_text"> + tooltips = obj.findall("property[@name='tooltip_text']") + if len(tooltips) > 1 and doprint: + err(filename, tree, obj, "multiple-tooltip", "has multiple tooltip_text properties") + if len(tooltips) >= 1: + return False + + # Case 6: Has a <property name="placeholder_text"> + placeholders = obj.findall("property[@name='placeholder_text']") + if len(placeholders) > 1 and doprint: + err(filename, tree, obj, "multiple-placeholder", "has multiple placeholder_text properties") + if len(placeholders) >= 1: + return False + + # Buttons usually don't need an external label, their own is enough, (but they do need one) + if klass in widgets_buttons: + + labels = obj.findall("property[@name='label']") + if len(labels) > 1 and doprint: + err(filename, tree, obj, "multiple-label", "has multiple label properties") + if len(labels) >= 1: + # Has a <property name="label"> + return False + + actions = obj.findall("property[@name='action_name']") + if len(actions) > 1 and doprint: + err(filename, tree, obj, "multiple-action_name", "has multiple action_name properties") + if len(actions) >= 1: + # Has a <property name="action_name"> + return False + + gtklabels = obj.findall(".//object[@class='GtkLabel']") + obj.findall(".//object[@class='GtkAccelLabel']") + if len(gtklabels) >= 1: + # Has a custom label + return False + + # no label for a button, warn + if doprint: + warn(filename, tree, obj, "button-no-label", "does not have its own label"); + if not is_enabled(obj, "button-no-label", enables, True): + # Warnings disabled + return False + (_, suppr) = elm_suppr(filename, tree, obj, "button-no-label", False) + if suppr in suppressions: + # Warning suppressed for this widget + if suppressions[suppr]: + warnexists += 1 + suppressions[suppr] = False + return False + return True + + # GtkImages special case + if klass == "GtkImage": + uses = [u for u in tree.iterfind(".//object/property[@name='image']") if u.text == oid] + if len(uses) > 0: + # This image is just used by another element, don't warn + # about the image itself, we probably want the warning on + # the element instead. + return False + + if find_button_parent(root, obj) is not None: + # This image is part of a button, we want the warning on the button + # instead, if any. + return False + + # GtkEntry special case + if klass == 'GtkEntry' or klass == 'GtkSearchEntry': + parent = elm_parent(root, obj) + if parent is not None: + if parent.tag == 'child' and \ + parent.attrib.get('internal-child') == "entry": + # This is an internal entry of another widget. Relations + # will be handled by that widget. + return False + + # GtkShortcutsShortcut special case + if klass == 'GtkShortcutsShortcut': + children = obj.findall("property[@name='title']") + if len(children) >= 1: + return False + + + # Really no label, perhaps emit a warning + if not is_enabled(obj, "no-labelled-by", enables, True): + # Warnings disabled for this class of widgets + return False + (_, suppr) = elm_suppr(filename, tree, obj, "no-labelled-by", False) + if suppr in suppressions: + # Warning suppressed for this widget + if suppressions[suppr]: + warnexists += 1 + suppressions[suppr] = False + return False + + if not orphan: + # No orphan label, so probably the labelled parent provides enough + # context. + if requires_label: + # But these always need a label. + if doprint: + warn(filename, tree, obj, "no-labelled-by", "has no accessibility label") + return True + return False + + if doprint: + context = elm_name(orphan_root) + if context: + context = " within " + context + warn(filename, tree, obj, "no-labelled-by", "has no accessibility label while there are orphan labels" + context) + return True + +def orphan_items(filename, tree, root, elm): + """ + Check whether from some element there exists orphan labels and orphan widgets + """ + orphan_labels = False + orphan_widgets = False + if elm.attrib.get('class') in widgets_labels: + orphan_labels = is_orphan_label(filename, tree, root, elm, None) else: - return "" + orphan_widgets = is_orphan_widget(filename, tree, root, elm, True, None) + for obj in elm: + # We are not interested in orphan labels under another labelled + # parent. This also allows to keep linear complexity. + if not is_labelled_parent(obj): + label, widget = orphan_items(filename, tree, root, obj) + if label: + orphan_labels = True + if widget: + orphan_widgets = True + if orphan_labels and orphan_widgets: + # No need to look up more + break + return orphan_labels, orphan_widgets + +# +# UI accessibility checks +# + +def check_props(filename, tree, root, elm, forward): + """ + Check the given list of relation properties + """ + props = elm.findall("property[@name='mnemonic_widget']") + for prop in props: + if prop.text not in ids: + err(filename, tree, elm, "undeclared-target", forward + " uses undeclared target '%s'" % prop.text) + return props + +def check_rels(filename, tree, root, elm, forward, backward = None): + """ + Check the relations given by forward + """ + oid = elm.attrib.get('id') + rels = elm.findall("accessibility/relation[@type='" + forward + "']") + for rel in rels: + target = rel.attrib['target'] + if target not in ids: + err(filename, tree, elm, "undeclared-target", forward + " uses undeclared target '%s'" % target) + elif backward is not None: + widget = ids[target] + backrels = widget.findall("accessibility/relation[@type='" + backward + "']") + if len([x for x in backrels if x.attrib['target'] == oid]) == 0: + err(filename, tree, elm, "missing-" + backward, "has " + forward + \ + ", but is not " + backward + " by " + elm_name_line(widget)) + return rels def check_a11y_relation(filename, tree): """ @@ -252,111 +904,256 @@ def check_a11y_relation(filename, tree): document represented by `root' doesn't comply with Accessibility rules. """ - root = tree.getroot() + global widgets_ignored, ids, label_for_elm, labelled_by_elm, mnemonic_for_elm - for obj in root.iter('object'): + def check_elm(orphan_root, obj, orphan_labels, orphan_widgets): + """ + Check one element, knowing that orphan_labels/widgets tell whether + there are orphan labels and widgets within orphan_root + """ - label_for = obj.findall("accessibility/relation[@type='label-for']") - check_rels(filename, tree, root, obj, label_for) + oid = obj.attrib.get('id') + klass = obj.attrib.get('class') - labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") - check_rels(filename, tree, root, obj, labelled_by) + # "Don't care" special case + if klass in widgets_ignored: + return + for suffix in widgets_suffixignored: + if klass[-len(suffix):] == suffix: + return - member_of = obj.findall("accessibility/relation[@type='member-of']") - check_rels(filename, tree, root, obj, member_of) + # Widgets usual do not strictly require a label, i.e. a labelled parent + # is enough for context, but some do always need one. + requires_label = klass in widgets_needlabel - if obj.attrib['class'] == 'GtkLabel': - # Case 0: A 'GtkLabel' must contain one or more "label-for" - # pointing to existing elements or... - if len(label_for) > 0: - continue + if oid is not None: + # Check that ids are unique + if oid in ids_dup: + if ids[oid] == obj: + # We are the first, warn + duplicates = tree.findall(".//object[@id='" + oid + "']") + err(filename, tree, obj, "duplicate-id", "has the same id as other elements " + elms_names_lines(duplicates)) + + # Check label-for and their dual labelled-by + label_for = check_rels(filename, tree, root, obj, "label-for", "labelled-by") + + # Check labelled-by and its dual label-for + labelled_by = check_rels(filename, tree, root, obj, "labelled-by", "label-for") - # ...a single "mnemonic_widget" - properties = obj.findall("property[@name='mnemonic_widget']") - check_props(filename, tree, root, obj, properties) + # Should have only one label + if len(labelled_by) >= 1: + if oid in mnemonic_for_elm: + warn(filename, tree, obj, "labelled-by-and-mnemonic", + "has both a mnemonic " + elm_name_line(mnemonic_for_elm[oid][0]) + "and labelled-by relation") + if len(labelled_by) > 1: + warn(filename, tree, obj, "multiple-labelled-by", "has multiple labelled-by relations") + if oid in label_for_elm: + if len(label_for_elm[oid]) > 1: + warn(filename, tree, obj, "duplicate-label-for", "is referenced by multiple label-for " + elms_names_lines(label_for_elm[oid])) + if oid in mnemonic_for_elm: + if len(mnemonic_for_elm[oid]) > 1: + warn(filename, tree, obj, "duplicate-mnemonic", "is referenced by multiple mnemonic_widget " + elms_names_lines(mnemonic_for_elm[oid])) + + # Check member-of + member_of = check_rels(filename, tree, root, obj, "member-of") + + # Labels special case + if klass in widgets_labels: + properties = check_props(filename, tree, root, obj, "mnemonic_widget") if len(properties) > 1: - # It does not make sense for a label to be a mnemonic for - # several actions. - err(filename, tree, obj, "multiple-mnemonic", "has too many sub-elements" - ", expected single <property name='mnemonic_widgets'>" + err(filename, tree, obj, "multiple-mnemonic", "has multiple mnemonic_widgets properties" "%s" % elms_lines(properties)) - continue - if len(properties) == 1: - continue - # TODO: warn that it is a label for nothing - continue - # Case 1: has a <child internal-child="accessible"> sub-element - children = obj.findall("child[@internal-child='accessible']") - if children: - if len(children) > 1: - err(filename, tree, obj, "multiple-accessible", "has too many sub-elements" - ", expected single <child internal-child='accessible'>" - "%s" % elms_lines(children)) - continue + # Emit orphaning warnings + if warn_orphan_labels or orphan_widgets: + is_orphan_label(filename, tree, root, obj, orphan_root, True) - # Case 2: has an <accessibility> sub-element with a "labelled-by" - # <relation> pointing to an existing element. - if len(labelled_by) > 0: - continue + # We are done with the label + return - # Case 3/4: has an ID... + # Not a label, will perhaps need one + + # Emit orphaning warnings + is_orphan_widget(filename, tree, root, obj, orphan_labels, orphan_root, True) + + root = tree.getroot() + + # Flush ids and relations from previous files + ids = {} + ids_dup = {} + labelled_by_elm = {} + label_for_elm = {} + mnemonic_for_elm = {} + + # First pass to get links into hash tables, no warning, just record duplicates + for obj in root.iter('object'): oid = obj.attrib.get('id') if oid is not None: - # ...referenced by a single "label-for" <relation> - rels = root.iterfind(".//relation[@target='%s']" % oid) - labelfor = [r for r in rels if r.attrib.get('type') == 'label-for'] - if len(labelfor) == 1: - continue - if len(labelfor) > 1: - err(filename, tree, obj, "multiple-label-for", "has too many elements" - ", expected single <relation type='label-for' target='%s'>" - "%s" % (oid, elm_lines(labelfor))) - continue + if oid not in ids: + ids[oid] = obj + else: + ids_dup[oid] = True - # ...referenced by a single "mnemonic_widget" - props = root.iterfind(".//property[@name='mnemonic_widget']") - props = [p for p in props if p.text == oid] - # TODO: warn when more than one. - if len(props) >= 1: - continue + labelled_by = obj.findall("accessibility/relation[@type='labelled-by']") + for rel in labelled_by: + target = rel.attrib.get('target') + if target is not None: + if target not in labelled_by_elm: + labelled_by_elm[target] = [ obj ] + else: + labelled_by_elm[target].append(obj) + + label_for = obj.findall("accessibility/relation[@type='label-for']") + for rel in label_for: + target = rel.attrib.get('target') + if target is not None: + if target not in label_for_elm: + label_for_elm[target] = [ obj ] + else: + label_for_elm[target].append(obj) + + mnemonic_for = obj.findall("property[@name='mnemonic_widget']") + for rel in mnemonic_for: + target = rel.text + if target is not None: + if target not in mnemonic_for_elm: + mnemonic_for_elm[target] = [ obj ] + else: + mnemonic_for_elm[target].append(obj) + + # Second pass, recursive depth-first, to be able to efficiently know whether + # there are orphan labels within a part of the tree. + def recurse(orphan_root, obj, orphan_labels, orphan_widgets): + if obj == root or is_labelled_parent(obj): + orphan_root = obj + orphan_labels, orphan_widgets = orphan_items(filename, tree, root, obj) - # TODO: after a few more checks and false-positives filtering, warn - # that this does not have a label - if obj.attrib['class'] == "GtkScale": - # GtkScale definitely needs a context - err(filename, tree, obj, "no-labelled-by", "has no accessibility label") + if obj.tag == 'object': + check_elm(orphan_root, obj, orphan_labels, orphan_widgets) + for o in obj: + recurse(orphan_root, o, orphan_labels, orphan_widgets) + + recurse(root, root, False, False) + +# +# Main +# def usage(): - print("%s [-W error|none] [-p] [-g SUPPR_FILE] [-s SUPPR_FILE] [-P SUPPR_PREFIX] [-o LOG_FILE] [file ... | -L filelist]" % progname, + print("%s [-p] [-g SUPPR_FILE] [-s SUPPR_FILE] [-i WIDGET1,WIDGET2[,...]] [-o LOG_FILE] [file ...]" % progname, file=sys.stderr) - print(" -p print XML class path instead of line number"); - print(" -g Generate suppression file SUPPR_FILE"); - print(" -s Suppress warnings given by file SUPPR_FILE"); - print(" -P Suppress SUPPR_PREFIX from emitted warnings, e.g. absolute source directory"); - print(" -o Also prints errors and warnings to given file"); + print("") + print(" -p Print XML class path instead of line number") + print(" -g Generate suppression file SUPPR_FILE") + print(" -s Suppress warnings given by file SUPPR_FILE") + print(" -i Ignore warnings for widgets of a given class") + print(" -o Also prints errors and warnings to given file") + print("") + print(" --widgets-FOO [+][CLASS1[,CLASS2[,...]]]") + print(" Give or extend one of the lists of widget classes, where FOO can be:") + print(" - toplevel : widgets to be considered toplevel windows") + print(" - ignored : widgets which do not need labelling (e.g. GtkBox)") + print(" - suffixignored : suffixes of widget classes which do not need labelling") + print(" - needlabel : widgets which always need labelling (e.g. GtkEntry)") + print(" - buttons : widgets which need their own label but not more") + print(" (e.g. GtkButton)") + print(" - labels : widgets which provide labels (e.g. GtkLabel)") + print(" --widgets-print print default widgets lists") + print("") + print(" --enable-all enable all warnings/dofatals (default)") + print(" --disable-all disable all warnings/dofatals") + print(" --fatal-all make all warnings dofatals") + print(" --not-fatal-all do not make all warnings dofatals (default)") + print("") + print(" --enable-type=TYPE enable warning/fatal type TYPE") + print(" --disable-type=TYPE disable warning/fatal type TYPE") + print(" --fatal-type=TYPE make warning type TYPE an fatal") + print(" --not-fatal-type=TYPE make warning type TYPE not an fatal") + print("") + print(" --enable-widgets=CLASS enable warning/fatal type CLASS") + print(" --disable-widgets=CLASS disable warning/fatal type CLASS") + print(" --fatal-widgets=CLASS make warning type CLASS an fatal") + print(" --not-fatal-widgets=CLASS make warning type CLASS not an fatal") + print("") + print(" --enable-specific=TYPE.CLASS enable warning/fatal type TYPE for widget") + print(" class CLASS") + print(" --disable-specific=TYPE.CLASS disable warning/fatal type TYPE for widget") + print(" class CLASS") + print(" --fatal-specific=TYPE.CLASS make warning type TYPE an fatal for widget") + print(" class CLASS") + print(" --not-fatal-specific=TYPE.CLASS make warning type TYPE not an fatal for widget") + print(" class CLASS") + print("") + print(" --disable-orphan-labels only warn about orphan labels when there are") + print(" orphan widgets in the same context") sys.exit(2) +def widgets_opt(widgets_list, arg): + """ + Replace or extend `widgets_list' with the list of classes contained in `arg' + """ + append = arg and arg[0] == '+' + if append: + arg = arg[1:] + + if arg: + widgets = arg.split(',') + else: + widgets = [] + + if not append: + del widgets_list[:] + + widgets_list.extend(widgets) + def main(): - global pflag, Werror, Wnone, gen_suppr, gen_supprfile, suppressions, suppr_prefix, errors, outfile + global pflag, gen_suppr, gen_supprfile, suppressions, suppr_prefix, dofatals, enables, dofatals, warn_orphan_labels + global widgets_toplevel, widgets_ignored, widgets_suffixignored, widgets_needlabel, widgets_buttons, widgets_labels + global outfile try: - opts, args = getopt.getopt(sys.argv[1:], "W:pg:s:P:o:L:") + opts, args = getopt.getopt(sys.argv[1:], "piIg:s:P:o:L:", [ + "widgets-toplevel=", + "widgets-ignored=", + "widgets-suffixignored=", + "widgets-needlabel=", + "widgets-buttons=", + "widgets-labels=", + "widgets-print", + + "enable-all", + "disable-all", + "fatal-all", + "not-fatal-all", + + "enable-type=", + "disable-type=", + "fatal-type=", + "not-fatal-type=", + + "enable-widgets=", + "disable-widgets=", + "fatal-widgets=", + "not-fatal-widgets=", + + "enable-specific=", + "disable-specific=", + "fatal-specific=", + "not-fatal-specific=", + + "disable-orphan-labels", + ] ) except getopt.GetoptError: usage() suppr = None out = None filelist = None + for o, a in opts: - if o == "-W": - if a == "error": - Werror = True - elif a == "none": - Wnone = True - elif o == "-p": + if o == "-p": pflag = True elif o == "-g": gen_suppr = a @@ -369,6 +1166,70 @@ def main(): elif o == "-L": filelist = a + elif o == "--widgets-toplevel": + widgets_opt(widgets_toplevel, a) + elif o == "--widgets-ignored": + widgets_opt(widgets_ignored, a) + elif o == "--widgets-suffixignored": + widgets_opt(widgets_suffixignored, a) + elif o == "--widgets-needlabel": + widgets_opt(widgets_needlabel, a) + elif o == "--widgets-buttons": + widgets_opt(widgets_buttons, a) + elif o == "--widgets-labels": + widgets_opt(widgets_labels, a) + elif o == "--widgets-print": + print("--widgets-toplevel '" + ','.join(widgets_toplevel) + "'") + print("--widgets-ignored '" + ','.join(widgets_ignored) + "'") + print("--widgets-suffixignored '" + ','.join(widgets_suffixignored) + "'") + print("--widgets-needlabel '" + ','.join(widgets_needlabel) + "'") + print("--widgets-buttons '" + ','.join(widgets_buttons) + "'") + print("--widgets-labels '" + ','.join(widgets_labels) + "'") + sys.exit(0) + + elif o == '--enable-all': + enables.append( (True, None, None) ) + elif o == '--disable-all': + enables.append( (False, None, None) ) + elif o == '--fatal-all': + dofatals.append( (True, None, None) ) + elif o == '--not-fatal-all': + dofatals.append( (False, None, None) ) + + elif o == '--enable-type': + enables.append( (True, a, None) ) + elif o == '--disable-type': + enables.append( (False, a, None) ) + elif o == '--fatal-type': + dofatals.append( (True, a, None) ) + elif o == '--not-fatal-type': + dofatals.append( (False, a, None) ) + + elif o == '--enable-widgets': + enables.append( (True, None, a) ) + elif o == '--disable-widgets': + enables.append( (False, None, a) ) + elif o == '--fatal-widgets': + dofatals.append( (True, None, a) ) + elif o == '--not-fatal-widgets': + dofatals.append( (False, None, a) ) + + elif o == '--enable-specific': + (thetype, klass) = a.split('.', 1) + enables.append( (True, thetype, klass) ) + elif o == '--disable-specific': + (thetype, klass) = a.split('.', 1) + enables.append( (False, thetype, klass) ) + elif o == '--fatal-specific': + (thetype, klass) = a.split('.', 1) + dofatals.append( (True, thetype, klass) ) + elif o == '--not-fatal-specific': + (thetype, klass) = a.split('.', 1) + dofatals.append( (False, thetype, klass) ) + + elif o == '--disable-orphan-labels': + warn_orphan_labels = False + # Read suppression file before overwriting it if suppr is not None: try: @@ -418,17 +1279,31 @@ def main(): print(estr) if warnings > 0 or warnexists > 0: - wstr = "%s new warning%s" % (warnings, - 's' if warnings > 1 else '') + wstr = "%s new warning%s" % (warnings, 's' if warnings > 1 else '') if warnexists > 0: wstr += " (%s suppressed by %s)" % (warnexists, suppr) print(wstr) + if fatals > 0 or fatalexists > 0: + wstr = "%s new fatal%s" % (fatals, 's' if fatals > 1 else '') + if fatalexists > 0: + wstr += " (%s suppressed by %s)" % (fatalexists, suppr) + print(wstr) + + n = 0 + for (suppr,unused) in suppressions.items(): + if unused: + n += 1 + + if n > 0: + print("%s suppression%s unused" % (n, 's' if n > 1 else '')) + if gen_supprfile is not None: gen_supprfile.close() if outfile is not None: outfile.close() - if errors > 0 and gen_suppr is None: + if fatals > 0 and gen_suppr is None: + print("Explanations are available on https://wiki.documentfoundation.org/Development/Accessibility") sys.exit(1) diff --git a/solenv/gbuild/UIConfig.mk b/solenv/gbuild/UIConfig.mk index 9a88ffd44498..fb3d82d0b912 100644 --- a/solenv/gbuild/UIConfig.mk +++ b/solenv/gbuild/UIConfig.mk @@ -125,11 +125,76 @@ $(call gb_UIConfig_get_clean_target,%) : $(call gb_Output_announce,$*,$(false),UIA,2) rm -f $(call gb_UIConfig_get_a11yerrors_target,$*) +gb_UIConfig_gla11y_PARAMETERS = -P $(SRCDIR)/ + +# Disable this to see suppressed warnings +ifeq (1,1) +gb_UIConfig_gla11y_PARAMETERS += -s $(UI_A11YSUPPRS) +endif # Enable this to regenerate suppression files ifeq (1,0) -GEN_A11Y_SUPPRS = -g $(UI_A11YSUPPRS) +gb_UIConfig_gla11y_PARAMETERS += -g $(UI_A11YSUPPRS) endif +# Tell gla11y about LO-specific widgets +# These are already automatically labelled Shrink/Expand +gb_UIConfig_gla11y_PARAMETERS += --widgets-ignored +foruilo-RefButton +# These, however, do need a label like a GtkEntry +gb_UIConfig_gla11y_PARAMETERS += --widgets-needlabel +foruilo-RefEdit +# These are storage, containers, or preview +gb_UIConfig_gla11y_PARAMETERS += --widgets-suffixignored +ValueSet,HBox,VBox,ToolBox,Preview,PreviewWin,PreviewWindow,PrevWindow +# These are buttons, thus already contain their label (but an image is not enough) +gb_UIConfig_gla11y_PARAMETERS += --widgets-button +vcllo-SmallButton,cuilo-RubyRadioButton,chartcontrollerlo-LightButton,svtlo-ManagedMenuButton + +# All new warnings should be fatal except a few kinds which could be only doubtful +gb_UIConfig_gla11y_PARAMETERS += --fatal-all --not-fatal-type duplicate-mnemonic --not-fatal-type labelled-by-and-mnemonic --not-fatal-type orphan-label + +# Disable all warnings types by default for now, to enable warnings types progressively +gb_UIConfig_gla11y_PARAMETERS += --disable-all + +# For now, ignore orphan labels without an unlabelled widget +gb_UIConfig_gla11y_PARAMETERS += --disable-orphan-labels +# but do not ignore orphan labels with an unlabelled widget +gb_UIConfig_gla11y_PARAMETERS += --enable-type orphan-label + +# The following are to be uncommented progressively + +# These are definite errors +gb_UIConfig_gla11y_PARAMETERS += --enable-type undeclared-target +#gb_UIConfig_gla11y_PARAMETERS += --enable-type missing-label-for +#gb_UIConfig_gla11y_PARAMETERS += --enable-type missing-labelled-by + +# These are often buttons with only an image +#gb_UIConfig_gla11y_PARAMETERS += --enable-type button-no-label +# These are often doubtful +#gb_UIConfig_gla11y_PARAMETERS += --enable-type duplicate-mnemonic --enable-type labelled-by-and-mnemonic + +# For now, disable warning about widgets without a label by default, to enable warnings for classes progressively +# To be uncommented progressively +gb_UIConfig_gla11y_PARAMETERS += --disable-type no-labelled-by +# Clearly need labelling +gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkScale +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkEntry +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkSpinButton +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkSpinner +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkProgressBar +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.svxcorelo-SvxColorListBox +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.svxcorelo-SvxLanguageBox +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.sfxlo-SvxCharView +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.foruilo-RefEdit +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.svxcorelo-PaperSizeListBox +# Probably need labelling +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkComboBox +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkComboBoxText +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkMenuItem +# Possibly need labelling +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkTreeView +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkTreeViewColumn +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkTextView +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkDrawingArea +# Perhaps need labelling +#gb_UIConfig_gla11y_PARAMETERS += --enable-specific no-labelled-by.GtkImage + define gb_UIConfig_a11yerrors__command $(call gb_Output_announce,$(2),$(true),UIA,1) $(call gb_UIConfig__gla11y_command) diff --git a/solenv/gbuild/platform/com_GCC_class.mk b/solenv/gbuild/platform/com_GCC_class.mk index 6d5393d2fad9..4c3531934d5e 100644 --- a/solenv/gbuild/platform/com_GCC_class.mk +++ b/solenv/gbuild/platform/com_GCC_class.mk @@ -159,7 +159,7 @@ define gb_UIConfig__gla11y_command $(call gb_Helper_abbreviate_dirs,\ $(gb_UIConfig_LXML_PATH) $(gb_Helper_set_ld_path) \ $(call gb_ExternalExecutable_get_command,python) \ - $(gb_UIConfig_gla11y_SCRIPT) -s $(UI_A11YSUPPRS) $(GEN_A11Y_SUPPRS) -P $(SRCDIR)/ -o $@ $(UIFILES) + $(gb_UIConfig_gla11y_SCRIPT) $(gb_UIConfig_gla11y_PARAMETERS) -o $@ $(UIFILES) ) endef diff --git a/solenv/gbuild/platform/com_MSC_class.mk b/solenv/gbuild/platform/com_MSC_class.mk index 0a1cdab314ac..8c0a23e9bf03 100644 --- a/solenv/gbuild/platform/com_MSC_class.mk +++ b/solenv/gbuild/platform/com_MSC_class.mk @@ -583,7 +583,7 @@ $(call gb_Helper_abbreviate_dirs,\ FILES=$(call var2file,$(shell $(gb_MKTEMP)),100,$(UIFILES)) && \ $(gb_UIConfig_LXML_PATH) $(gb_Helper_set_ld_path) \ $(call gb_ExternalExecutable_get_command,python) \ - $(gb_UIConfig_gla11y_SCRIPT) -s $(UI_A11YSUPPRS) $(GEN_A11Y_SUPPRS) -P $(SRCDIR)/ -o $@ -L $$FILES + $(gb_UIConfig_gla11y_SCRIPT) $(gb_UIConfig_gla11y_PARAMETERS) -o $@ -L $$FILES ) endef diff --git a/solenv/sanitizers/ui/cui.suppr b/solenv/sanitizers/ui/cui.suppr index d9e0294f464f..598c7a600ae3 100644 --- a/solenv/sanitizers/ui/cui.suppr +++ b/solenv/sanitizers/ui/cui.suppr @@ -1,3 +1,8 @@ -cui/uiconfig/ui/gradientpage.ui:GtkBox[@id='GradientPage']/GtkFrame[@id='frame1']/GtkAlignment[@id='alignment1']/GtkBox[@id='box2']/GtkGrid[@id='grid6']/GtkScale[@id='incrementslider'] no-labelled-by -cui/uiconfig/ui/gradientpage.ui:GtkBox[@id='GradientPage']/GtkFrame[@id='frame1']/GtkAlignment[@id='alignment1']/GtkBox[@id='box2']/GtkGrid[@id='grid3']/GtkScale[@id='borderslider'] no-labelled-by -cui/uiconfig/ui/hatchpage.ui:GtkBox[@id='HatchPage']/GtkFrame[@id='frame1']/GtkAlignment[@id='alignment1']/GtkBox[@id='box3']/GtkBox[@id='box1']/GtkScale[@id='angleslider'] no-labelled-by +cui/uiconfig/ui/gradientpage.ui://GtkScale[@id='incrementslider'] no-labelled-by +cui/uiconfig/ui/gradientpage.ui://GtkLabel[@id='centerft'] orphan-label +cui/uiconfig/ui/gradientpage.ui://GtkScale[@id='borderslider'] no-labelled-by +cui/uiconfig/ui/hatchpage.ui://GtkLabel[@id='distanceft'] orphan-label +cui/uiconfig/ui/hatchpage.ui://GtkLabel[@id='angleft'] orphan-label +cui/uiconfig/ui/hatchpage.ui://GtkScale[@id='angleslider'] no-labelled-by +cui/uiconfig/ui/hatchpage.ui://GtkLabel[@id='linetypeft'] orphan-label +cui/uiconfig/ui/hatchpage.ui://GtkLabel[@id='linecolorft'] orphan-label diff --git a/solenv/sanitizers/ui/svt.suppr b/solenv/sanitizers/ui/svt.suppr index 40ba68b7b93a..64325dcb38dd 100644 --- a/solenv/sanitizers/ui/svt.suppr +++ b/solenv/sanitizers/ui/svt.suppr @@ -1,2 +1,2 @@ -svtools/uiconfig/ui/graphicexport.ui:GtkDialog[@id='GraphicExportDialog']/GtkBox[@id='dialog-vbox1']/GtkBox[@id='box1']/GtkFrame[@id='jpgquality']/GtkAlignment[@id='alignment5']/GtkGrid[@id='grid2']/GtkScale[@id='compressionjpgsb'] no-labelled-by -svtools/uiconfig/ui/graphicexport.ui:GtkDialog[@id='GraphicExportDialog']/GtkBox[@id='dialog-vbox1']/GtkBox[@id='box1']/GtkFrame[@id='pngcompression']/GtkAlignment[@id='alignment13']/GtkGrid[@id='grid9']/GtkScale[@id='compressionpngsb'] no-labelled-by +svtools/uiconfig/ui/graphicexport.ui://GtkScale[@id='compressionjpgsb'] no-labelled-by +svtools/uiconfig/ui/graphicexport.ui://GtkScale[@id='compressionpngsb'] no-labelled-by diff --git a/solenv/sanitizers/ui/svx.suppr b/solenv/sanitizers/ui/svx.suppr index 9179abb2dbd5..b9babb1171ab 100644 --- a/solenv/sanitizers/ui/svx.suppr +++ b/solenv/sanitizers/ui/svx.suppr @@ -1,6 +1,12 @@ -svx/uiconfig/ui/compressgraphicdialog.ui:GtkDialog[@id='CompressGraphicDialog']/GtkBox[@id='dialog-vbox1']/GtkGrid/GtkFrame[@id='frame2']/GtkAlignment[@id='alignment1']/GtkGrid[@id='grid2']/GtkAlignment/GtkGrid/GtkScale[@id='scale-quality'] no-labelled-by -svx/uiconfig/ui/compressgraphicdialog.ui:GtkDialog[@id='CompressGraphicDialog']/GtkBox[@id='dialog-vbox1']/GtkGrid/GtkFrame[@id='frame2']/GtkAlignment[@id='alignment1']/GtkGrid[@id='grid2']/GtkAlignment/GtkGrid/GtkScale[@id='scale-compression'] no-labelled-by -svx/uiconfig/ui/mediaplayback.ui:GtkGrid[@id='MediaPlaybackPanel']/GtkGrid[@id='grid1']/GtkScale[@id='timeslider'] no-labelled-by -svx/uiconfig/ui/mediaplayback.ui:GtkGrid[@id='MediaPlaybackPanel']/GtkGrid[@id='grid1']/GtkScale[@id='volumeslider'] no-labelled-by -svx/uiconfig/ui/sidebararea.ui:GtkGrid[@id='AreaPropertyPanel']/GtkBox[@id='box1']/GtkGrid[@id='grid1']/GtkScale[@id='transparencyslider'] no-labelled-by -svx/uiconfig/ui/sidebarshadow.ui:GtkGrid[@id='ShadowPropertyPanel']/GtkGrid[@id='grid3']/GtkBox[@id='box2']/GtkBox[@id='box1']/GtkGrid[@id='grid2']/GtkScale[@id='transparency_slider'] no-labelled-by +svx/uiconfig/ui/compressgraphicdialog.ui://GtkScale[@id='scale-quality'] no-labelled-by +svx/uiconfig/ui/compressgraphicdialog.ui://GtkScale[@id='scale-compression'] no-labelled-by +svx/uiconfig/ui/mediaplayback.ui://GtkLabel[@id='label1'] orphan-label +svx/uiconfig/ui/mediaplayback.ui://GtkLabel[@id='label2'] orphan-label +svx/uiconfig/ui/mediaplayback.ui://GtkLabel[@id='label3'] orphan-label +svx/uiconfig/ui/mediaplayback.ui://GtkScale[@id='timeslider'] no-labelled-by +svx/uiconfig/ui/mediaplayback.ui://GtkScale[@id='volumeslider'] no-labelled-by +svx/uiconfig/ui/sidebarshadow.ui://GtkLabel[@id='angle'] orphan-label +svx/uiconfig/ui/sidebarshadow.ui://GtkLabel[@id='distance'] orphan-label +svx/uiconfig/ui/sidebarshadow.ui://GtkLabel[@id='transparency_label'] orphan-label +svx/uiconfig/ui/sidebarshadow.ui://GtkScale[@id='transparency_slider'] no-labelled-by +svx/uiconfig/ui/sidebarshadow.ui://GtkLabel[@id='color'] orphan-label |