pygameui.Listbox.Listbox.onItemHighlight()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nop 4
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Pygame.UI.
5
#
6
#  Pygame.UI is free software; you can redistribute it and/or modify
7
#  it under the terms of the Lesser GNU General Public License as published by
8
#  the Free Software Foundation; either version 2.1 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Pygame.UI is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  Lesser GNU General Public License for more details.
15
#
16
#  You should have received a copy of the Lesser GNU General Public License
17
#  along with Pygame.UI; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import re
22
import types
23
24
import pygame
25
26
import Const
27
from Widget import registerWidget
28
from MetaWidget import MetaWidget
29
from Scrollbar import Scrollbar
30
from Entry import Entry
31
from Button import Button
32
33
class Listbox(MetaWidget):
34
35
    def __init__(self, parent, **kwargs):
36
        MetaWidget.__init__(self, parent)
37
        # data
38
        self.__dict__["items"] = []
39
        self.__dict__["labels"] = []
40
        self.__dict__["action"] = None
41
        self.__dict__["rmbAction"] = None
42
        self.__dict__["hoverAction"] = None
43
        self.__dict__["multiselection"] = 0
44
        self.__dict__["selection"] = []
45
        self.__dict__["highlight"] = None
46
        self.__dict__["columns"] = [('Item', 'text', 0, Const.ALIGN_W)]
47
        self.__dict__["columnLabels"] = 0
48
        self.__dict__["scrollBar"] = 1
49
        self.__dict__["sortedBy"] = (None, 1)
50
        self.__dict__["sortable"] = 1
51
        self.__dict__["_labels"] = []
52
        self.__dict__["_buttons"] = []
53
        self.__dict__["_entries"] = []
54
        # flags
55
        self.processKWArguments(kwargs)
56
        parent.registerWidget(self)
57
58
        # create widgets
59
        self.bar = Scrollbar(self, action = 'onScroll')
60
        self.bar.subscribeAction('*', self)
61
        if not self.scrollBar:
62
            self.bar.visible = 0
63
64
        # precreate some objects
65
        # guess number of rows (TODO Enable it)
66
        # rows = self.layout[3] - 1
67
        rows = 0
68
        for item in self.columns:
69
            label = Button(self, action = 'onSortByColumn')
70
            label.subscribeAction('*', self)
71
            self._buttons.append(label)
72
            for i in xrange(0, rows):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
73
                label = Button(self, action = 'onItemSelect', rmbAction = "onRmbItemSelect", hoverAction = "onItemHighlight", style = "listitem", toggle = 1)
74
                label.subscribeAction('*', self)
75
                self._labels.append(label)
76
77
    def layoutWidgets(self):
78
        gx, gy = self.theme.getGridParams()
79
        r = self.rect
80
        self.bar.rect = pygame.Rect(r.width - gx, 0, gx, r.height)
81
82
        self.labels = []
83
        rows = r.height / gy
84
        startRow = 0
85
        bIndex = 0
86
        lIndex = 0
87
        eIndex = 0
88
        if self.columnLabels:
89
            rowLabels = []
90
            y = 0
91
            x = 0
92
            remains = (r.width - gx) / gx
93
            for title, name, width, flags in self.columns:
94
                if len(self._buttons) <= bIndex:
95
                    label = Button(self, action = 'onSortByColumn')
96
                    label.subscribeAction('*', self)
97
                    self._buttons.append(label)
98
                label = self._buttons[bIndex]
99
                bIndex += 1
100
                label.set(text = title, align = flags & Const.ALIGN_MASK,
101
                    data = name, visible = 1)
102
                if width == 0 or width > remains: width = remains
103
                label.rect = pygame.Rect(x, y, width * gx, gy)
104
                x += width * gx
105
                remains -= width
106
            startRow = 1
107
        for row in xrange(startRow, rows):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
108
            rowLabels = []
109
            y = row * gy
110
            x = 0
111
            remains = (r.width - gx) / gx
112
            for title, name, width, flags in self.columns:
113
                if flags & Const.F_EDITABLE:
114
                    if len(self._entries) <= eIndex:
115
                        label = Entry(self, align = Const.ALIGN_E, action = 'onNewValue')
116
                        label.subscribeAction('*', self)
117
                        self._entries.append(label)
118
                    label = self._entries[eIndex]
119
                    eIndex += 1
120
                    label._listboxColumn = name
121
                    label.visible = 1
122
                    label.redraw()
123
                else:
124
                    if len(self._labels) <= lIndex:
125
                        label = Button(self, action = 'onItemSelect', rmbAction = "onRmbItemSelect", hoverAction = "onItemHighlight", style = "listitem", toggle = 1)
126
                        label.subscribeAction('*', self)
127
                        self._labels.append(label)
128
                    label = self._labels[lIndex]
129
                    lIndex += 1
130
                    label.set(align = flags & Const.ALIGN_MASK, visible = 1)
131
                    label.redraw()
132
                if width == 0 or width > remains: width = remains
133
                label.rect = pygame.Rect(x, y, width * gx, gy)
134
                x += width * gx
135
                remains -= width
136
                rowLabels.append(label)
137
            self.labels.append(rowLabels)
138
        while lIndex < len(self._labels):
139
            self._labels[lIndex].visible = 0
140
            lIndex += 1
141
        while bIndex < len(self._buttons):
142
            self._buttons[bIndex].visible = 0
143
            bIndex += 1
144
        while eIndex < len(self._entries):
145
            self._entries[eIndex].visible = 0
146
            eIndex += 1
147
148
        self.bar.slider.position = 0
149
        self.bar.slider.min = 0
150
        if self.columnLabels:
151
            self.bar.slider.shown = rows - 1
152
        else:
153
            self.bar.slider.shown = rows
154
155
        self.itemsChanged()
156
157
    def onScroll(self, widget, action, data):
158
        self.itemsChanged()
159
160
    def selectItem(self, item):
161
        if item:
162
            if self.multiselection:
163
                if item in self.selection:
164
                    item.selected = 0
165
                    self.selection.remove(item)
166
                    if item.index != None:
167
                        self._setListIndex(item.index, item)
168
                else:
169
                    item.selected = 1
170
                    self.selection.append(item)
171
                    if item.index != None:
172
                        self._setListIndex(item.index, item)
173
            else:
174
                if self.selection:
175
                    for tmp in self.selection:
176
                        tmp.selected = 0
177
                        if tmp.index != None:
178
                            self._setListIndex(tmp.index, tmp)
179
                item.selected = 1
180
                if item.index != None:
181
                    self._setListIndex(item.index, item)
182
                self.selection = [item]
183
            return 1
184
        return 0
185
186
    def highlightItem(self, item, highlighted):
187
        if item is not None:
188
            self.highlight = item
189
            self.highlight.highlighted = highlighted
190
            self._setListIndex(item.index, item)
191
            return 1
192
        else:
193
            self.highlight = None
194
            return 0
195
196
    def onItemSelect(self, widget, action, data):
197
        if self.selectItem(widget.data):
198
            self.processAction(self.action, widget.data)
199
200
    def onRmbItemSelect(self, widget, action, data):
201
        if self.selectItem(widget.data):
202
            self.processAction(self.rmbAction, widget.data)
203
204
    def onItemHighlight(self, widget, action, data):
205
        if self.highlightItem(widget.data, data):
206
            self.processAction(self.hoverAction, widget.data if data else None)
207
208
    def onNewValue(self, widget, action, data):
209
        value = widget.text
210
        t = type(getattr(widget.data, widget._listboxColumn))
211
        try:
212
            if t == types.IntType: value = int(value)
213
            elif t == types.FloatType: value = float(value)
214
            elif t == types.StringType: value = str(value)
215
            elif t == types.UnicodeType: pass
216
            elif t == types.LongType: value = long(value)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable long does not seem to be defined.
Loading history...
217
            else:
218
                self._setListIndex(widget.data.index, widget.data)
219
                return
220
        except ValueError:
221
            self._setListIndex(widget.data.index, widget.data)
222
            return
223
        setattr(widget.data, widget._listboxColumn, value)
224
225
    def setSort(self, column):
226
        if not self.sortable:
227
            return
228
229
        if self.sortedBy[0] == column:
230
            self.sortedBy = (self.sortedBy[0], not self.sortedBy[1])
231
        else:
232
            self.sortedBy = (column, 1)
233
234
        self.itemsChanged()
235
236
    def onSortByColumn(self, widget, action, data):
237
        self.setSort(widget.data)
238
239
    def _setListIndex(self, rowIndex, item):
240
        if rowIndex < len(self.labels):
241
            if item.selected and item not in self.selection:
242
                self.selection.append(item)
243
            if not item.selected and item in self.selection:
244
                self.selection.remove(item)
245
            columnIndex = 0
246
            for title, name, width, flags in self.columns:
247
                label = self.labels[rowIndex][columnIndex]
248
                label.text = item.getAsString(name)
249
                label.tooltip = item.tooltip
250
                label.tooltipTitle = item.tooltipTitle
251
                label.statustip = item.statustip
252
                label.font = item.font
253
                label.foreground = item.foreground
254
                label.enabled = 1
255
                if name == 'text':
256
                    label.icons = item.icons
257
                label.data = item
258
                label.pressed = item.selected
259
                label.highlighted = item.highlighted
260
                columnIndex += 1
261
262
    def _naturalSort(self, items, attr, reverse):
263
        """ Code inspired by https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
264
        Converts strings to lists of strings and numbers, always in STR, NUM, STR, NUM order (first STR
265
        might be empty).
266
        """
267
268
        convert = lambda text: int(text) if text.isdigit() else text.lower()
269
        alphaNum = lambda key: [ convert(c) for c in re.split('([0-9]+)', unicode(key)) ]
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
270
        keyF = lambda a: alphaNum(getattr(a, attr))
271
        items.sort(key=keyF, reverse = reverse)
272
273
    def itemsChanged(self):
274
        if self.items and self.sortable and self.sortedBy[0] != None:
275
            if hasattr(self.items[0], self.sortedBy[0] + "_raw"):
276
                sortAttr = self.sortedBy[0] + "_raw"
277
            else:
278
                sortAttr = self.sortedBy[0]
279
            self._naturalSort(self.items, sortAttr, not self.sortedBy[1])
280
281
        self.bar.slider.max = max(1, len(self.items))
282
        index = 0
283
        pos = int(self.bar.slider.position)
284
        if pos >= len(self.items) - len(self.labels): pos = max(0, len(self.items) - len(self.labels))
285
        # clear selection without triggering widget update
286
        self.__dict__['selection'] = []
287
        for item in self.items:
288
            item.index = None
289
            # reconstruct selection
290
            if item.selected:
291
                self.selection.append(item)
292
        for item in self.items[pos:pos + len(self.labels)]:
293
            item.index = index
294
            index2 = 0
295
            if index < len(self.labels):
296
                for title, name, width, flags in self.columns:
297
                    label = self.labels[index][index2]
298
                    label.text = item.getAsString(name)
299
                    label.tooltip = item.tooltip
300
                    label.tooltipTitle = item.tooltipTitle
301
                    label.statustip = item.statustip
302
                    label.font = item.font
303
                    label.enabled = 1
304
                    label.foreground = item.foreground
305
                    if name == 'text':
306
                        label.icons = item.icons
307
                    label.data = item
308
                    label.pressed = item.selected
309
                    index2 += 1
310
            else:
311
                break
312
            index += 1
313
        while index < len(self.labels):
314
            index2 = 0
315
            for title, name, width, flags in self.columns:
316
                label = self.labels[index][index2]
317
                label.text = None
318
                label.icons = None
319
                label.data = None
320
                label.tooltip = None
321
                label.tooltipTitle = None
322
                label.statustip = None
323
                label.foreground = None
324
                label.background = None
325
                label.enabled = 0
326
                label.pressed = 0
327
                index2 += 1
328
            index += 1
329
330
        #self.parent.redraw(self)
331
332
    def getSelection(self):
333
        return self.selection
334
335
    def setSelection(self, selection):
336
        self.selection = selection
337
        self.itemsChanged()
338
339
    def unselectAll(self):
340
        if self.selection:
341
            for item in self.selection:
342
                item.selected = 0
343
                item.highlighted = 0
344
                if item.index != None:
345
                    self._setListIndex(item.index, item)
346
347
    def addItem(self, item):
348
        if item not in self.items:
349
            self.items.append(item)
350
351
    def delItem(self, item):
352
        self.items.remove(item)
353
        self.itemsChanged()
354
355
    def delItemByIndex(self, index):
356
        del self.items[index]
357
        self.itemsChanged()
358
359
    def setItems(self, items):
360
        self.items = items
361
        self.itemsChanged()
362
363
    # redirect mouse wheel events to the scollbar
364
    def processMWUp(self, evt):
365
        return self.bar.processMWUp(evt)
366
367
    def processMWDown(self, evt):
368
        return self.bar.processMWDown(evt)
369
370
    def drawMetaWidget(self, surface):
371
        return self.theme.drawListbox(surface, self)
372
373
registerWidget(Listbox, 'listbox')
374