File: keymap.py

package info (click to toggle)
python-panwid 0.3.0.dev15-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 308 kB
  • sloc: python: 3,702; makefile: 3
file content (157 lines) | stat: -rw-r--r-- 5,294 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# Mixin class for mapping keyboard input to widget methods.

import logging
logger = logging.getLogger(__name__)
# import os
# if os.environ.get("DEBUG"):
#     logger.setLevel(logging.DEBUG)
#     formatter = logging.Formatter("%(asctime)s [%(levelname)8s] %(message)s",
#                                   datefmt='%Y-%m-%d %H:%M:%S')
#     fh = logging.FileHandler("keymap.log")
#     fh.setFormatter(formatter)
#     logger.addHandler(fh)
# else:
#     logger.addHandler(logging.NullHandler())

import six
import urwid
import re

_camel_snake_re_1 = re.compile(r'(.)([A-Z][a-z]+)')
_camel_snake_re_2 = re.compile('([a-z0-9])([A-Z])')

def camel_to_snake(s):
    s = _camel_snake_re_1.sub(r'\1_\2', s)
    return _camel_snake_re_2.sub(r'\1_\2', s).lower()


def optional_arg_decorator(fn):
    def wrapped_decorator(*args, **kwargs):
        if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
            return fn(args[0])
        else:
            def real_decorator(decoratee):
                return fn(decoratee, *args, **kwargs)
            return real_decorator
    return wrapped_decorator


@optional_arg_decorator
def keymap_command(f, command=None, *args, **kwargs):
    f._keymap = True
    f._keymap_command = command
    f._keymap_args = args
    f._keymap_kwargs = kwargs
    return f


def keymapped():

    def wrapper(cls):

        def keypress_decorator(func):

            def keypress(self, size, key):
                key = super(cls, self).keypress(size, key)
                if not key:
                    return
                logger.debug("%s wrapped keypress: %s" %(self.__class__.__name__, key))
                # logger.debug("%s scope: %s, keymap: %s" %(self.__class__.__name__, self.KEYMAP_SCOPE, getattr(self, "KEYMAP", None)))
                for scope in [cls.KEYMAP_SCOPE, "any"]:
                    # logger.debug("key: %s, scope: %s, %s, %s" %(key, scope, self.KEYMAP_SCOPE, self.KEYMAP))
                    if not scope in self.KEYMAP.keys():
                        continue
                    if key in self.KEYMAP[scope]:
                        val = self.KEYMAP[scope][key]
                        if not isinstance(val, list):
                            val = [val]
                        for cmd in val:
                            args = []
                            kwargs = {}
                            if isinstance(cmd, tuple):
                                if len(cmd) == 3:
                                    (cmd, args, kwargs) = cmd
                                elif len(cmd) == 2:
                                    (cmd, args) = cmd
                                else:
                                    raise Exception
                            command = cmd.replace(" ", "_")
                            if not command in self.KEYMAP_MAPPING:
                                logger.debug("%s: %s not in mapping %s" %(cls, key, self.KEYMAP_MAPPING))
                            if hasattr(self, command):
                                fn_name = command
                            else:
                                fn_name = self.KEYMAP_MAPPING[command]
                            f = getattr(self, fn_name)
                            f(*args, **kwargs)
                    return key
                else:
                    return key

                return key

            return keypress

        def default_keypress(self, size, key):
            logger.debug("default keypress: %s" %(key))
            key = super(cls, self).keypress(size, key)
            return key

        if not hasattr(cls, "KEYMAP"):
            cls.KEYMAP = {}
        scope = camel_to_snake(cls.__name__)
        cls.KEYMAP_SCOPE = scope
        func = getattr(cls, "keypress", None)
        logger.debug("func class: %s" %(cls.__name__))
        if not func:
            logger.debug("setting default keypress for %s" %(cls.__name__))
            cls.keypress = default_keypress
        else:
            cls.keypress = keypress_decorator(func)
        if not hasattr(cls, "KEYMAP_MAPPING"):
            cls.KEYMAP_MAPPING = {}
        cls.KEYMAP_MAPPING.update({
            (getattr(getattr(cls, k), "_keymap_command", k) or k).replace(" ", "_"): k
            for k in cls.__dict__.keys()
            if hasattr(getattr(cls, k), '_keymap')
        })
        return cls
    return wrapper


@keymapped()
class KeymapMovementMixin(object):

    def cycle_position(self, n):

        if len(self):
            pos = self.focus_position + n
            if pos > len(self) - 1:
                pos = len(self) - 1
            elif pos < 0:
                pos = 0
            self.focus_position = pos

    @keymap_command("up")
    def keymap_up(self): self.cycle_position(-1)

    @keymap_command("down")
    def keymap_down(self): self.cycle_position(1)

    @keymap_command("page up")
    def keymap_page_up(self): self.cycle_position(-self.page_size)

    @keymap_command("page down")
    def keymap_page_down(self): self.cycle_position(self.page_size)

    @keymap_command("home")
    def keymap_home(self): self.focus_position = 0

    @keymap_command("end")
    def keymap_end(self): self.focus_position = len(self)-1

__all__ = [
    "keymapped",
    "keymap_command",
    "KeymapMovementMixin"
]