diff --git a/tkprop/properties.py b/tkprop/properties.py index 9470c228a0d729e47d7efd66638b091844981bf9..0365038ee2b3b5b0a0f68325e68d2d7b096be847 100644 --- a/tkprop/properties.py +++ b/tkprop/properties.py @@ -485,11 +485,9 @@ class _ListWrapper(object): A Tk Variable object is created for each item that is added to the list. When a list value is changed, instead of a new Tk Variable being created, the value of the existing variable - is changed. References to every Tk variable in the list are - added to the HasProperties object (the owner, passed to - __init__), with a name of the form 'label_index', where 'label' - is the List property label, and 'index' is the index, in this - list, of the Tk variable. + is changed. References to every Tk variable in the list may + be accessed via the _tkVars attribute of hte _ListWrapper + object. """ def __init__(self, @@ -513,30 +511,27 @@ class _ListWrapper(object): - maxlen: maximum list length """ - self._owner = owner - self._listProp = listProp - self._listType = listType - self._minlen = minlen - self._maxlen = maxlen - self._l = [] - self._tkVars = self._l + self._owner = owner + self._listProp = listProp + self._listType = listType + self._listType.label = self._listProp.label + self._minlen = minlen + self._maxlen = maxlen - self._listType.label = self._listProp.label + # This is the list that the _ListWrapper wraps. + self._tkVars = [] if values is not None: - - self._check_minlen(-len(values)) - self._check_maxlen( len(values)) - - # manually append each item on to the - # list so the values are validated - for v in values: - self.append(v) + self.extend(values) + + elif self._minlen is not None: + for i in range(self._minlen): + self.append(listType.default) - def __len__( self): return self._l.__len__() - def __repr__(self): return list([i.get() for i in self._l]).__repr__() - def __str__( self): return list([i.get() for i in self._l]).__str__() + def __len__( self): return self._tkVars.__len__() + def __repr__(self): return list([i.get() for i in self._tkVars]).__repr__() + def __str__( self): return list([i.get() for i in self._tkVars]).__str__() def _check_maxlen(self, change=1): @@ -544,7 +539,8 @@ class _ListWrapper(object): Test that adding the given number of items to the list would not cause the list to grow beyond its maximum length. """ - if (self._maxlen is not None) and (len(self._l)+change > self._maxlen): + if (self._maxlen is not None) and \ + (len(self._tkVars) + change > self._maxlen): raise IndexError('{} must have a length of at most {}'.format( self._listProp.label, self._maxlen)) @@ -554,24 +550,21 @@ class _ListWrapper(object): Test that removing the given number of items to the list would not cause the list to shrink beyond its minimum length. """ - if (self._minlen is not None) and (len(self._l)-change < self._minlen): + if (self._minlen is not None) and \ + (len(self._tkVars) - change < self._minlen): raise IndexError('{} must have a length of at least {}'.format( self._listProp.label, self._minlen)) - def _makeTkVar(self, value, index): + def _makeTkVar(self, value): """ Encapsulate the given value in a Tkinter variable. A ValueError is raised if the value does not meet the list type/value constraints. """ - tkval = self._listType._tkvartype( - self._listType, - name='{}_{}'.format(self._listProp.label, index)) - - # ValueError here if value is bad - tkval.set(value) + # ValueError will be raised here if value is bad + tkval = self._listType._tkvartype(self._listType, value=value) return tkval @@ -581,8 +574,8 @@ class _ListWrapper(object): value is not present. """ - for i in range(len(self._l)): - if self._l[i].get() == item: + for i in range(len(self._tkVars)): + if self._tkVars[i].get() == item: return i raise ValueError('{} is not present'.format(item)) @@ -593,7 +586,7 @@ class _ListWrapper(object): Return the value(s) at the specified index/slice. """ - items = self._l.__getitem__(key) + items = self._tkVars.__getitem__(key) if isinstance(key,slice): return [i.get() for i in items] @@ -606,7 +599,7 @@ class _ListWrapper(object): Returns an iterator over the values in the list. """ - innerIter = self._l.__iter__() + innerIter = self._tkVars.__iter__() for i in innerIter: yield i.get() @@ -628,7 +621,7 @@ class _ListWrapper(object): c = 0 - for i in self._l: + for i in self._tkVars: if i.get() == item: c = c + 1 @@ -645,11 +638,11 @@ class _ListWrapper(object): """ self._check_maxlen() - index = len(self._l) - tkval = self._makeTkVar(item, index) - self._l.append(tkval) - setattr(self._owner, '{}_{}'.format( - self._listProp.label, index), tkval) + + index = len(self._tkVars) + tkVal = self._makeTkVar(item) + + self._tkVars.append(tkVal) def extend(self, iterable): @@ -659,20 +652,15 @@ class _ListWrapper(object): list type/value constraints. An IndexError is raised if an insertion would causes the list to grow beyond its maximum length. - """ - - while True: - try: - nxt = iterable.next() + """ - # this will raise an IndexError if maxlen is exceeded, - # or a ValueError if the value is invalid - self.append(nxt) - - except StopIteration: - break + toAdd = list(iterable) + self._check_maxlen(len(toAdd)) + for i in toAdd: + self.append(i) + def pop(self): """ Remove and return the last value in the list. An IndexError is @@ -680,27 +668,62 @@ class _ListWrapper(object): below its minimum length. """ - index = len(self._l) - 1 - + index = len(self._tkVars) - 1 self._check_minlen() - - delattr(self._owner, - '{}_{}'.format(self._listProp.label, index)) - return self._l.pop(index).get() + return self._tkVars.pop(index).get() def __setitem__(self, key, value): """ Sets the value(s) of the list at the specified index/slice. A ValueError is raised if any of the values do not meet the - list type/value constraints. + list type/value constraints. """ if isinstance(key, slice): - for i,v in zip(range(key.start, key.stop, key.step), value): - self._l[i].set(v) + if (key.step is not None) and (key.step > 1): + raise ValueError( + '_ListWrapper does not support extended slices') + indices = range(*key.indices(len(self))) + + elif isinstance(key, int): + indices = [key] + value = [value] + + else: + raise ValueError('Invalid key type') + + # if the number of indices specified in the key + # is different from the number of values passed + # in, it means that we are either adding or + # removing items from the list + lenDiff = len(value) - len(indices) + oldLen = len(self) + newLen = oldLen + lenDiff + + if newLen > oldLen: self._check_maxlen( lenDiff) + elif newLen < oldLen: self._check_minlen(-lenDiff) + + value = [self._makeTkVar(v) for v in value] + if len(value) == 1: value = value[0] + self._tkVars.__setitem__(key, value) + + + def __delitem__(self, key): + """ + Remove items at the specified index/slice from the list. An + IndexError is raised if the removal would cause the list to + shrink below its minimum length. + """ + + if isinstance(key, slice): + indices = range(*key.indices(len(self))) else: - self._l[key].set(value) + indices = [key] + + self._check_minlen(len(indices)) + + self._tkVars.__delitem__(key) class List(PropertyBase): @@ -745,10 +768,6 @@ class List(PropertyBase): def __set__(self, instance, value): - instval = _ListWrapper(instance, - self, - values=value, - listType=self.listType, - minlen=self.minlen, - maxlen=self.maxlen) - instance.__dict__[self.label] = instval + + instval = getattr(instance, self.label) + instval[:] = value diff --git a/tkprop/widgets_list.py b/tkprop/widgets_list.py index 5dae70ff7555934f12e57f3de080bcd002680c47..27f06058f15ad2e2abc0e13ae76c1e4803657fd4 100644 --- a/tkprop/widgets_list.py +++ b/tkprop/widgets_list.py @@ -22,29 +22,70 @@ import ttk -def _pasteDataDialog(parent): +def _pasteDataDialog(parent, listProp, propObj): """ A dialog which displays an editable text field, allowing the user to type/paste bulk data which will be used to populate the list (one line per item). """ - listType = listProp.listType - tkVarList = listObj._tkVars + listObj = getattr(propObj, listProp.label) + window = tk.Toplevel() frame = ttk.Frame(window) - frame.pack(fill=tk.BOTH, expand=True) + frame.columnconfigure(0, weight=1) + frame.rowconfigure( 0, weight=1) - # make the list edit dialog modal + # TODO label explaining what to do + text = tk.Text(frame, wrap="none") + vScroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=text.yview) + hScroll = ttk.Scrollbar(frame, orient=tk.HORIZONTAL, command=text.xview) + + initText = '\n'.join([str(l) for l in listObj]) + text.insert('1.0', initText) + + def pasteIntoList(): + """ + Copies whatever the user typed/pasted + into the text area into the list. + """ + + listData = text.get('1.0', 'end') + listData = listData.split('\n') + listData = [s.strip() for s in listData] + listData = filter(len, listData) + + print('Pasting data into list: {}'.format(listData)) + + setattr(propObj, listProp.label, listData) + + window.destroy() + + # ok/cancel buttons + btnFrame = ttk.Frame(frame) + okButton = ttk.Button(btnFrame, text="Ok", command=pasteIntoList) + cancelButton = ttk.Button(btnFrame, text="Cancel", command=window.destroy) + + # lay out the widgets! + text .grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W) + hScroll .grid(row=1, column=0, sticky=tk.E+tk.W) + vScroll .grid(row=0, column=1, sticky=tk.N+tk.S) + btnFrame.grid(row=2, column=0, sticky=tk.E+tk.W, columnspan=2) + + btnFrame.columnconfigure(0, weight=1) + btnFrame.columnconfigure(1, weight=1) + okButton .grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W) + cancelButton.grid(row=0, column=1, sticky=tk.N+tk.S+tk.E+tk.W) + + # make this window modal window.transient(parent) window.grab_set() parent.wait_window(window) - pass -def _editListDialog(parent, listProp, listObj, propObj): +def _editListDialog(parent, listProp, propObj): """ A dialog which displays a widget for every item in the list, and which allows the user to adjust the number of items in @@ -52,11 +93,12 @@ def _editListDialog(parent, listProp, listObj, propObj): Parameters: - parent - listProp - - listObj - propObj """ + listType = listProp.listType + listObj = getattr(propObj, listProp.label) tkVarList = listObj._tkVars # Get a reference to a function which can make @@ -161,21 +203,22 @@ def _List(parent, propObj, tkProp, tkVar): frame = ttk.Frame(parent) - print('Making list widgets for {} !!'.format(tkProp.label)) - - # When the user pushes this button on the parent window, a new window - # is displayed, allowing the user to edit the values in the list + # When the user pushes this button on the parent window, + # a new window is displayed, allowing the user to edit + # the values in the list individually editButton = ttk.Button( frame, text='Edit', - command=lambda: _editListDialog(parent, tkProp, tkVar, propObj)) + command=lambda: _editListDialog(parent, tkProp, propObj)) # When the user pushes this button, a new window is # displayed, allowing the user to type/paste bulk data pasteButton = ttk.Button( frame, text='Paste data', - command=lambda: _pasteDataDialog(parent, tkProp, tkVar, propObj)) + command=lambda: _pasteDataDialog(parent, tkProp, propObj)) - editButton .pack(side=tk.LEFT, fill=tk.X, expand=True) - pasteButton.pack(side=tk.RIGHT, fill=tk.X, expand=True) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + editButton .grid(row=0, column=0, sticky=tk.E+tk.W) + pasteButton.grid(row=0, column=1, sticky=tk.E+tk.W) return frame