Passed
Branch master (994cf3)
by Marek
01:46
created

pygameui.Listbox   F

Complexity

Total Complexity 94

Size/Duplication

Total Lines 373
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 294
dl 0
loc 373
rs 1.5789
c 0
b 0
f 0
wmc 94

24 Methods

Rating   Name   Duplication   Size   Complexity  
A Listbox.highlightItem() 0 9 2
F Listbox.layoutWidgets() 0 79 17
F Listbox.selectItem() 0 25 10
A Listbox.unselectAll() 0 7 4
A Listbox.onItemHighlight() 0 3 3
A Listbox.setSelection() 0 3 1
C Listbox._setListIndex() 0 22 8
B Listbox._naturalSort() 0 10 6
A Listbox.delItem() 0 3 1
A Listbox.delItemByIndex() 0 3 1
A Listbox.onSortByColumn() 0 2 1
B Listbox.__init__() 0 41 4
A Listbox.processMWDown() 0 2 1
A Listbox.getSelection() 0 2 1
A Listbox.processMWUp() 0 2 1
F Listbox.itemsChanged() 0 56 14
A Listbox.drawMetaWidget() 0 2 1
A Listbox.onItemSelect() 0 3 2
B Listbox.onNewValue() 0 16 7
A Listbox.onScroll() 0 2 1
A Listbox.setSort() 0 10 3
A Listbox.addItem() 0 3 2
A Listbox.setItems() 0 3 1
A Listbox.onRmbItemSelect() 0 3 2

How to fix   Complexity   

Complexity

Complex classes like pygameui.Listbox often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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