~marmuta/onboard/emoji-palette

« back to all changes in this revision

Viewing changes to Onboard/LayoutLoaderSVG.py

  • Committer: marmuta
  • Date: 2017-02-08 11:21:42 UTC
  • Revision ID: marmvta@gmail.com-20170208112142-wzgloxmraxhi84f8
Improve PEP8 compliance of LayoutLoaderSVG.py. 
  
Fixes a few old issues with rarely used code paths, in particular 
leftover long() calls from Python 2 times.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
from __future__ import division, print_function, unicode_literals
23
23
 
24
 
### Logging ###
25
24
import logging
26
25
_logger = logging.getLogger("LayoutLoaderSVG")
27
 
###############
28
26
 
29
27
import os
30
28
import re
34
32
 
35
33
from Onboard                 import Exceptions
36
34
from Onboard                 import KeyCommon
37
 
from Onboard.KeyCommon       import StickyBehavior, ImageSlot, \
38
 
                                    KeyPath, KeyGeometry
 
35
from Onboard.KeyCommon       import (StickyBehavior, ImageSlot,
 
36
                                     KeyPath, KeyGeometry)
39
37
from Onboard.Layout          import LayoutRoot, LayoutBox, LayoutPanel
40
 
from Onboard.utils           import modifiers, Rect, \
41
 
                                    toprettyxml, Version, open_utf8, \
42
 
                                    permute_mask, LABEL_MODIFIERS, \
43
 
                                    unicode_str, XDGDirs
 
38
from Onboard.utils           import (modifiers, Rect,
 
39
                                     toprettyxml, Version, open_utf8,
 
40
                                     permute_mask, LABEL_MODIFIERS,
 
41
                                     unicode_str, XDGDirs)
44
42
 
45
43
# Layout items that can be created dynamically via the 'class' XML attribute.
46
 
from Onboard.WordSuggestions import WordListPanel
47
 
from Onboard.KeyGtk          import RectKey, WordlistKey, BarKey, \
48
 
                                    WordKey, InputlineKey
 
44
from Onboard.WordSuggestions import WordListPanel  # noqa: flake8
 
45
from Onboard.KeyGtk          import (RectKey, WordlistKey, BarKey, # noqa: flake8
 
46
                                     WordKey, InputlineKey)
49
47
 
50
 
### Config Singleton ###
51
48
from Onboard.Config import Config
52
49
config = Config()
53
 
########################
54
50
 
55
51
 
56
52
class LayoutLoaderSVG:
101
97
    def load(self, vk, layout_filename, color_scheme):
102
98
        """ Load layout root file. """
103
99
        self._system_layout, self._system_variant = \
104
 
                                      self._get_system_keyboard_layout(vk)
105
 
        _logger.info("current system keyboard layout(variant): '{}'" \
 
100
            self._get_system_keyboard_layout(vk)
 
101
        _logger.info("current system keyboard layout(variant): '{}'"
106
102
                     .format(self._get_system_layout_string()))
107
103
 
108
104
        layout = self._load(vk, layout_filename, color_scheme,
110
106
        if layout:
111
107
            # purge attributes only used during loading
112
108
            for item in layout.iter_items():
113
 
                if not item.templates is None:
 
109
                if item.templates is not None:
114
110
                    item.templates = None
115
 
                if not item.keysym_rules is None:
 
111
                if item.keysym_rules is not None:
116
112
                    item.keysym_rules = None
117
113
 
118
114
            # enable caching
120
116
 
121
117
        return layout
122
118
 
123
 
 
124
 
    def _load(self, vk, layout_filename, color_scheme, root_layout_dir, parent_item = None):
 
119
    def _load(self, vk, layout_filename, color_scheme,
 
120
              root_layout_dir, parent_item=None):
125
121
        """ Load or include layout file at any depth level. """
126
122
        self._vk = vk
127
123
        self._layout_filename = layout_filename
129
125
        self._root_layout_dir = root_layout_dir
130
126
        return self._load_layout(layout_filename, parent_item)
131
127
 
132
 
    def _load_layout(self, layout_filename, parent_item = None):
 
128
    def _load_layout(self, layout_filename, parent_item=None):
133
129
        self._svg_cache = {}
134
130
        layout = None
135
131
 
146
142
            # check layout format, no format version means legacy layout
147
143
            format = self.LAYOUT_FORMAT_LEGACY
148
144
            if dom.hasAttribute("format"):
149
 
               format = Version.from_string(dom.attributes["format"].value)
 
145
                format = Version.from_string(dom.attributes["format"].value)
150
146
            self._format = format
151
147
 
152
 
            root = LayoutPanel() # root, representing the 'keyboard' tag
153
 
            root.set_id("__root__") # id for debug prints
 
148
            root = LayoutPanel()  # root, representing the 'keyboard' tag
 
149
            root.set_id("__root__")  # id for debug prints
154
150
 
155
151
            # Init included root with the parent item's svg filename.
156
152
            # -> Allows to skip specifying svg filenames in includes.
161
157
                self._parse_dom_node(dom, root)
162
158
                layout = root
163
159
            else:
164
 
                _logger.warning(_format("Loading legacy layout, format '{}'. "
 
160
                _logger.warning(
 
161
                    _format("Loading legacy layout, format '{}'. "
165
162
                            "Please consider upgrading to current format '{}'",
166
163
                            format, self.LAYOUT_FORMAT))
167
164
                items = self._parse_legacy_layout(dom)
171
168
 
172
169
        f.close()
173
170
 
174
 
        self._svg_cache = {} # Free the memory
 
171
        self._svg_cache = {}  # Free the memory
175
172
        return layout
176
173
 
177
174
    def _parse_dom_node(self, dom_node, parent_item):
192
189
                    can_load = True
193
190
                else:
194
191
                    id = child.attributes["id"].value
195
 
                    if not id in loaded_ids:
 
192
                    if id not in loaded_ids:
196
193
                        if child.hasAttribute("layout"):
197
194
                            layout = child.attributes["layout"].value
198
195
                            can_load = self._has_matching_layout(layout)
203
200
                        else:
204
201
                            can_load = True
205
202
 
206
 
 
207
203
                if can_load:
208
204
                    tag = child.tagName
209
205
 
247
243
                parent.append_items(incl_root.items)
248
244
                parent.update_keysym_rules(incl_root.keysym_rules)
249
245
                parent.update_templates(incl_root.templates)
250
 
                incl_root.items = None # help garbage collector
 
246
                incl_root.items = None  # help garbage collector
251
247
                incl_root.keysym_rules = None
252
248
                incl_root.templates = None
253
249
 
260
256
        id = attributes.get("id")
261
257
        if not id:
262
258
            raise Exceptions.LayoutFileError(
263
 
                "'id' attribute required for template '{} {}' "
264
 
                "in layout '{}'" \
265
 
                .format(tag,
266
 
                        str(list(attributes.values())),
 
259
                "'id' attribute required for template '{}' "
 
260
                "in layout '{}'"
 
261
                .format(str(list(attributes.values())),
267
262
                        self._layout_filename))
268
263
 
269
264
        parent.update_templates({(id, RectKey) : attributes})
301
296
        item = item_class()
302
297
 
303
298
        value = attributes.get("id")
304
 
        if not value is None:
 
299
        if value is not None:
305
300
            item.id = value
306
301
 
307
302
        value = attributes.get("group")
308
 
        if not value is None:
 
303
        if value is not None:
309
304
            item.group = value
310
305
 
311
306
        value = attributes.get("layer")
312
 
        if not value is None:
 
307
        if value is not None:
313
308
            item.layer_id = value
314
309
 
315
310
        value = attributes.get("filename")
316
 
        if not value is None:
 
311
        if value is not None:
317
312
            item.filename = value
318
313
 
319
314
        value = attributes.get("visible")
320
 
        if not value is None:
 
315
        if value is not None:
321
316
            item.visible = value == "true"
322
317
 
323
318
        value = attributes.get("sensitive")
324
 
        if not value is None:
 
319
        if value is not None:
325
320
            item.sensitive = value == "true"
326
321
 
327
322
        value = attributes.get("border")
328
 
        if not value is None:
 
323
        if value is not None:
329
324
            item.border = float(value)
330
325
 
331
326
        value = attributes.get("expand")
332
 
        if not value is None:
 
327
        if value is not None:
333
328
            item.expand = value == "true"
334
329
 
335
330
        value = attributes.get("unlatch_layer")
336
 
        if not value is None:
 
331
        if value is not None:
337
332
            item.unlatch_layer = value == "true"
338
333
 
339
334
        value = attributes.get("scannable")
341
336
            item.scannable = False
342
337
 
343
338
        value = attributes.get("scan_priority")
344
 
        if not value is None:
 
339
        if value is not None:
345
340
            item.scan_priority = int(value)
346
341
 
347
342
        return item
349
344
    def _parse_sublayout(self, node, parent):
350
345
        attributes = dict(node.attributes.items())
351
346
        item = self._init_item(attributes, LayoutPanel)
352
 
        item.sublayout_parent = parent # make templates accessible in the subl.
 
347
 
 
348
        # make templates accessible in the sublayout
 
349
        item.sublayout_parent = parent
353
350
        return item
354
351
 
355
352
    def _parse_box(self, node):
421
418
                    result = key
422
419
                else:
423
420
                    _logger.info("Ignoring key '{}'."
424
 
                                 " No svg object found for '{}'." \
 
421
                                 " No svg object found for '{}'."
425
422
                                 .format(key.id, key.svg_id))
426
423
 
427
424
        return result  # ignore keys not found in an svg file
435
432
        key.set_id(full_id, theme_id, svg_id)
436
433
 
437
434
        if "_" in key.get_id():
438
 
            _logger.warning("underscore in key id '{}', please use dashes" \
 
435
            _logger.warning("underscore in key id '{}', please use dashes"
439
436
                            .format(key.get_id()))
440
437
 
441
438
        value = attributes.get("modifier")
444
441
                key.modifier = modifiers[value]
445
442
            except KeyError as ex:
446
443
                (strerror) = ex
447
 
                raise Exceptions.LayoutFileError("Unrecognized modifier %s in" \
 
444
                raise Exceptions.LayoutFileError(
 
445
                    "Unrecognized modifier %s in"
448
446
                    "definition of %s" (strerror, full_id))
449
447
 
450
448
        value = attributes.get("action")
453
451
                key.action = KeyCommon.actions[value]
454
452
            except KeyError as ex:
455
453
                (strerror) = ex
456
 
                raise Exceptions.LayoutFileError("Unrecognized key action {} in" \
 
454
                raise Exceptions.LayoutFileError(
 
455
                    "Unrecognized key action {} in"
457
456
                    "definition of {}".format(strerror, full_id))
458
457
 
459
458
        if "char" in attributes:
462
461
        elif "keysym" in attributes:
463
462
            value = attributes["keysym"]
464
463
            key.type = KeyCommon.KEYSYM_TYPE
465
 
            if value[1] == "x":#Deals for when keysym is hex
466
 
                key.code = int(value,16)
 
464
            if value[1] == "x":  # for hex keysym
 
465
                key.code = int(value, 16)
467
466
            else:
468
 
                key.code = int(value,10)
 
467
                key.code = int(value, 10)
469
468
        elif "keypress_name" in attributes:
470
469
            key.code = attributes["keypress_name"]
471
470
            key.type = KeyCommon.KEYPRESS_NAME_TYPE
497
496
 
498
497
        # get the optional image filename
499
498
        if "image" in attributes:
500
 
            if not key.image_filenames: key.image_filenames = {}
501
 
            key.image_filenames[ImageSlot.NORMAL] = attributes["image"].split(";")[0]
 
499
            if not key.image_filenames:
 
500
                key.image_filenames = {}
 
501
            key.image_filenames[ImageSlot.NORMAL] = \
 
502
                attributes["image"].split(";")[0]
502
503
        if "image_active" in attributes:
503
 
            if not key.image_filenames: key.image_filenames = {}
 
504
            if not key.image_filenames:
 
505
                key.image_filenames = {}
504
506
            key.image_filenames[ImageSlot.ACTIVE] = attributes["image_active"]
505
507
 
506
508
        # get labels
552
554
            key.label_y_align = float(attributes["label_y_align"])
553
555
 
554
556
        if "label_margin" in attributes:
555
 
            values = attributes["label_margin"].replace(" ","").split(",")
556
 
            margin = [float(x) if x else key.label_margin[i] \
 
557
            values = attributes["label_margin"].replace(" ", "").split(",")
 
558
            margin = [float(x) if x else key.label_margin[i]
557
559
                      for i, x in enumerate(values[:2])]
558
 
            margin += margin[:1]*(2 - len(margin))
 
560
            margin += margin[:1] * (2 - len(margin))
559
561
            if margin:
560
562
                key.label_margin = margin
561
563
 
567
569
                key.sticky = False
568
570
            else:
569
571
                raise Exceptions.LayoutFileError(
570
 
                    "Invalid value '{}' for 'sticky' attribute of key '{}'" \
 
572
                    "Invalid value '{}' for 'sticky' attribute of key '{}'"
571
573
                    .format(sticky, key.id))
572
574
        else:
573
575
            key.sticky = False
583
585
                key.sticky_behavior = StickyBehavior.from_string(value)
584
586
            except KeyError as ex:
585
587
                (strerror) = ex
586
 
                raise Exceptions.LayoutFileError("Unrecognized sticky behavior {} in" \
 
588
                raise Exceptions.LayoutFileError(
 
589
                    "Unrecognized sticky behavior {} in"
587
590
                    "definition of {}".format(strerror, full_id))
588
591
 
589
592
        if "tooltip" in attributes:
602
605
 
603
606
        # Get labels from keyboard mapping first.
604
607
        if key.type == KeyCommon.KEYCODE_TYPE and \
605
 
           not key.id in ["BKSP"]:
606
 
            if self._vk: # xkb keyboard found?
 
608
           key.id not in ["BKSP"]:
 
609
            if self._vk:  # xkb keyboard found?
607
610
                vkmodmasks = self._label_modifier_masks
608
611
                if sys.version_info.major == 2:
609
 
                    vkmodmasks = [long(m) for m in vkmodmasks]
 
612
                    vkmodmasks = [int(m) for m in vkmodmasks]
610
613
                vklabels = self._vk.labels_from_keycode(key.code, vkmodmasks)
611
614
                if sys.version_info.major == 2:
612
615
                    vklabels = [x.decode("UTF-8") for x in vklabels]
619
622
 
620
623
        # If key is a macro (snippet) generate label from its number.
621
624
        elif key.type == KeyCommon.MACRO_TYPE:
622
 
            label, text = config.snippets.get(int(key.code), \
623
 
                                                       (None, None))
 
625
            label, text = config.snippets.get(int(key.code), (None, None))
624
626
            tooltip = _format("Snippet {}", key.code)
625
627
            if not label:
626
628
                labels[0] = "     --     "
638
640
        # override with per-keysym labels
639
641
        keysym_rules = self._get_keysym_rules(key)
640
642
        if key.type == KeyCommon.KEYCODE_TYPE:
641
 
            if self._vk: # xkb keyboard found?
 
643
            if self._vk:  # xkb keyboard found?
642
644
                vkmodmasks = self._label_modifier_masks
643
645
                try:
644
646
                    if sys.version_info.major == 2:
645
 
                        vkmodmasks = [long(m) for m in vkmodmasks]
 
647
                        vkmodmasks = [int(m) for m in vkmodmasks]
646
648
                    vkkeysyms  = self._vk.keysyms_from_keycode(key.code,
647
649
                                                               vkmodmasks)
648
650
                except AttributeError:
654
656
                    attributes = keysym_rules.get(keysym)
655
657
                    if attributes:
656
658
                        label = attributes.get("label")
657
 
                        if not label is None:
 
659
                        if label is not None:
658
660
                            mask = vkmodmasks[i]
659
661
                            labels[mask] = label
660
662
 
661
663
        # Translate labels - Gettext behaves oddly when translating
662
664
        # empty strings
663
 
        return { mask : lab and _(lab) or None
664
 
                 for mask, lab in labels.items()}
 
665
        return {mask : lab and _(lab) or None
 
666
                for mask, lab in labels.items()}
665
667
 
666
668
    def _parse_layout_labels(self, attributes):
667
669
        """ Deprecated label definitions up to v0.98.x """
699
701
        except Exceptions.LayoutFileError as ex:
700
702
            raise Exceptions.LayoutFileError(
701
703
                "error loading '{}'".format(filename),
702
 
                chained_exception = (ex))
 
704
                chained_exception=(ex))
703
705
        return svg_nodes
704
706
 
705
707
    def _parse_svg(self, node):
714
716
 
715
717
                    if tag == "rect":
716
718
                        svg_node.bounds = \
717
 
                                   Rect(float(child.attributes['x'].value),
718
 
                                        float(child.attributes['y'].value),
719
 
                                        float(child.attributes['width'].value),
720
 
                                        float(child.attributes['height'].value))
 
719
                            Rect(float(child.attributes['x'].value),
 
720
                                 float(child.attributes['y'].value),
 
721
                                 float(child.attributes['width'].value),
 
722
                                 float(child.attributes['height'].value))
721
723
 
722
724
                    elif tag == "path":
723
725
                        data = child.attributes['d'].value
726
728
                            svg_node.path = KeyPath.from_svg_path(data)
727
729
                        except ValueError as ex:
728
730
                            raise Exceptions.LayoutFileError(
729
 
                                  "while reading geometry with id '{}'".format(id),
730
 
                                  chained_exception = (ex))
 
731
                                "while reading geometry with id '{}'"
 
732
                                .format(id),
 
733
                                chained_exception=(ex))
731
734
 
732
735
                        svg_node.bounds = svg_node.path.get_bounds()
733
736
 
749
752
            if templates:
750
753
                for id in ids:
751
754
                    match = templates.get((id, classinfo))
752
 
                    if not match is None:
 
755
                    if match is not None:
753
756
                        return match
754
757
        return {}
755
758
 
760
763
        """
761
764
        keysym_rules = {}
762
765
        for item in reversed(list(scope_item.iter_to_root())):
763
 
            if not item.keysym_rules is None:
 
766
            if item.keysym_rules is not None:
764
767
                keysym_rules.update(item.keysym_rules)
765
768
 
766
769
        return keysym_rules
768
771
    def _get_system_keyboard_layout(self, vk):
769
772
        """ get names of the currently active layout group and variant """
770
773
 
771
 
        if vk: # xkb keyboard found?
 
774
        if vk:  # xkb keyboard found?
772
775
            group = vk.get_current_group()
773
776
            names = vk.get_rules_names()
774
777
        else:
826
829
                return True
827
830
        return False
828
831
 
829
 
 
830
832
    # --------------------------------------------------------------------------
831
833
    # Legacy pane layout support
832
834
    # --------------------------------------------------------------------------
874
876
        for key in layer_area.iter_keys():
875
877
            w = key.get_border_rect().w
876
878
            histogram[w] = histogram.get(w, 0) + 1
877
 
        most_frequent_width = max(list(zip(list(histogram.values()), list(histogram.keys()))))[1] \
878
 
                              if histogram else 18
 
879
        most_frequent_width = max(list(zip(list(histogram.values()),
 
880
                                           list(histogram.keys()))))[1] \
 
881
            if histogram else 18
879
882
 
880
883
        # Legacy onboard had automatic tab-keys for pane switching.
881
884
        # Simulate this by generating layer buttons from scratch.
949
952
                # check layout format
950
953
                format = LayoutLoaderSVG.LAYOUT_FORMAT_LEGACY
951
954
                if keyboard_node.hasAttribute("format"):
952
 
                   format = Version.from_string(keyboard_node.attributes["format"].value)
 
955
                    format = Version.from_string(
 
956
                        keyboard_node.attributes["format"].value)
953
957
                keyboard_node.attributes["id"] = dst_basename
954
958
 
955
959
                if format < LayoutLoaderSVG.LAYOUT_FORMAT_LAYOUT_TREE:
956
 
                    raise Exceptions.LayoutFileError( \
957
 
                        _format("copy_layouts failed, unsupported layout format '{}'.",
 
960
                    raise Exceptions.LayoutFileError(
 
961
                        _format("copy_layouts failed, "
 
962
                                "unsupported layout format '{}'.",
958
963
                                format))
959
964
                else:
960
965
                    # replace the basename of all svg filenames
963
968
                            if node.hasAttribute("filename"):
964
969
                                filename = node.attributes["filename"].value
965
970
 
966
 
                                # Create a replacement layer name for the unlikely
967
 
                                # case  that the svg-filename doesn't contain a
968
 
                                # layer section (as in path/basename-layer.ext).
969
 
                                fallback_layer_name = fallback_layers.get(filename,
970
 
                                             "Layer" + str(len(fallback_layers)))
 
971
                                # Create a replacement layer name for the
 
972
                                # unlikely case  that the svg-filename doesn't
 
973
                                # contain a layer section
 
974
                                # (as in path/basename-layer.ext).
 
975
                                fallback_layer_name = fallback_layers.get(
 
976
                                    filename,
 
977
                                    "Layer" + str(len(fallback_layers)))
971
978
                                fallback_layers[filename] = fallback_layer_name
972
979
 
973
980
                                # replace the basename of this filename
974
 
                                new_filename = LayoutLoaderSVG._replace_basename( \
975
 
                                     filename, dst_basename, fallback_layer_name)
 
981
                                new_filename = \
 
982
                                    LayoutLoaderSVG._replace_basename(
 
983
                                        filename, dst_basename,
 
984
                                        fallback_layer_name)
976
985
 
977
 
                                node.attributes["filename"].value = new_filename
 
986
                                node.attributes["filename"].value = \
 
987
                                    new_filename
978
988
                                svg_filenames[filename] = new_filename
979
989
 
980
990
            if domdoc:
997
1007
                        if not dir:
998
1008
                            dst = os.path.join(dst_dir, name)
999
1009
 
1000
 
                        _logger.info(_format("copying svg file '{}' to '{}'", \
 
1010
                        _logger.info(_format("copying svg file '{}' to '{}'",
1001
1011
                                     src, dst))
1002
1012
                        shutil.copyfile(src, dst)
1003
1013
        except OSError as ex:
1004
 
            _logger.error("copy_layout failed: " + \
 
1014
            _logger.error("copy_layout failed: " +
1005
1015
                          unicode_str(ex))
1006
1016
        except Exceptions.LayoutFileError as ex:
1007
1017
            _logger.error(unicode_str(ex))
1008
1018
 
1009
 
 
1010
1019
    @staticmethod
1011
1020
    def remove_layout(filename):
1012
1021
        for fn in LayoutLoaderSVG.get_layout_svg_filenames(filename):
1056
1065
        if name:
1057
1066
            index = name.rfind("-")
1058
1067
            if index >= 0:
1059
 
                layer = name[index+1:]
 
1068
                layer = name[index + 1:]
1060
1069
            else:
1061
1070
                layer = fallback_layer_name
1062
1071
            return "{}-{}{}".format(new_basename, layer, ext)