File: columns.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 (220 lines) | stat: -rw-r--r-- 6,461 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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import logging
logger = logging.getLogger("panwid.datatable")

from datetime import datetime, date as datetype

from .common import *

class NoSuchColumnException(Exception):
    pass

def make_value_function(template):

    def inner(table, row):
        pos = table.index_to_position(row.get(table.index))
        return template.format(
            data=row,
            row=pos+1,
            rows_loaded = len(table),
            rows_total = table.query_result_count() if table.limit else "?"
        )

    return inner

class DataTableBaseColumn(object):

    _width = ("weight", 1)

    def __init__(
            self,
            padding = DEFAULT_CELL_PADDING,
            hide=False,
            width=None,
            min_width=None,
            attr = None

    ):
        self.hide = hide
        self.padding = padding

        if isinstance(self.padding, tuple):
            self.padding_left, self.padding_right = self.padding
        else:
            self.padding_left = self.padding_right = self.padding

        if width is not None:  self._width = width
        self.min_width = min_width
        self.attr = attr

        if isinstance(self._width, tuple):
            if self._width[0] != "weight":
                raise Exception(
                    "Column width %s not supported" %(self._width[0])
                )
            self.initial_sizing, self.initial_width = self._width
            self.min_width = 3 # FIXME
        elif isinstance(self._width, int):
            self.initial_sizing = "given"
            self.initial_width = self._width
            self.min_width = self.initial_width = self._width # assume starting width is minimum

        else:
            raise Exception(self._width)

        self.sizing = self.initial_sizing
        self.width = self.initial_width

    def __repr__(self):
        return f"<{self.__class__.__name__}: {self.name}>"

    def width_with_padding(self, table_padding=None):
        padding = 0
        if self.padding is None and table_padding is not None:
            padding = table_padding
        return self.width + self.padding_left + self.padding_right

    @property
    def index(self):
        return self.table.visible_columns.index(self)

    @property
    def header(self):
        try:
            return self.table.header.cells[self.index]
        except ValueError:
            return None



class DataTableColumn(DataTableBaseColumn):

    def __init__(self, name,
                 label=None,
                 value=None,
                 align="left", wrap="space",
                 pack=False,
                 no_clip_header = False,
                 truncate=False,
                 format_fn=None,
                 decoration_fn=None,
                 format_record = None, # format_fn is passed full row data
                 sort_key = None, sort_reverse=False,
                 sort_icon = None,
                 footer_fn = None, footer_arg = "values", **kwargs):

        super().__init__(**kwargs)
        self.name = name
        self.label = label if label is not None else name
        if value:
            if isinstance(value, str):
                self.value_fn = make_value_function(value)
            elif callable(value):
                self.value_fn = value
        else:
            self.value_fn = None
        self.align = align
        self.pack = pack
        self.wrap = wrap
        self.no_clip_header = no_clip_header
        self.truncate = truncate
        self.format_fn = format_fn
        self.decoration_fn = decoration_fn
        self.format_record = format_record
        self.sort_key = sort_key
        self.sort_reverse = sort_reverse
        self.sort_icon = sort_icon
        self.footer_fn = footer_fn
        self.footer_arg = footer_arg
        logger.debug(f"column {self.name}, width: {self.sizing}, {self.width}")


    @property
    def contents_width(self):
        try:
            index = next(i for i, c
                         in enumerate(self.table.visible_columns)
                         if getattr(c, "name", None) == self.name)
        except StopIteration:
            raise Exception(self.name, [ c.name for c in self.table.visible_columns])
        # logger.info(f"len: {len(self.table.body)}")

        l = [
            (
             getattr(r.cells[index].value, "min_width", None)
             or
             len(str(r.cells[index].formatted_value))
            ) + self.padding*2
            for r in (self.table.body)
        ] + [self.table.header.cells[index].min_width or 0] + [self.min_width or 0]
        return max(l)

    @property
    def minimum_width(self):
        # if self.sizing == "pack":
        if self.pack:
            # logger.info(f"min: {self.name}, {self.contents_width}")
            return self.contents_width
        else:
            return self.min_width or len(self.label) + self.padding_left + self.padding_right + (1 if self.sort_icon else 0)


    def _format(self, v):

        # First, call the format function for the column, if there is one
        if self.format_fn:
            try:
                v = self.format_fn(v)
            except Exception as e:
                logger.error("%s format exception: %s" %(self.name, v))
                logger.exception(e)
                raise e
        return self.format(v)


    def format(self, v):

        # Do our best to make the value into something presentable
        if v is None:
            v = " "
        elif isinstance(v, int):
            v = "%d" %(v)
        elif isinstance(v, float):
            v = "%.03f" %(v)
        elif isinstance(v, datetime):
            v = v.strftime("%Y-%m-%d %H:%M:%S")
        elif isinstance(v, datetype):
            v = v.strftime("%Y-%m-%d")
        return v


class DataTableDivider(DataTableBaseColumn):

    _width = 1

    def __init__(self, char=" ", in_header=False, in_footer=False, **kwargs):
        super().__init__(**kwargs)
        self.char = char
        self.in_header = in_header
        self.in_footer = in_footer

    @property
    def name(self):
        return "divider"

    @property
    def value(self):
        # FIXME: should use SolidFill for rows that span multiple screen rows
        w = urwid.Divider(self.char)
        return w

    @property
    def contents_width(self):
        return len(self.char)

    @property
    def pack(self):
        return False

    @property
    def align(self):
        return "left"