''' Copyright (C) 2004 - 2008 Simon Forman This file is part of Xerblin. Xerblin is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ''' from future import standard_library standard_library.install_aliases() from builtins import map, object, str from tkinter import Listbox, SINGLE from tkinter.dnd import dnd_start from joy.utils.stack import iter_stack, list_to_stack, expression_to_string class SourceWrapper(object): ''' Helper object for drag and drop. ''' def __init__(self, source, widget, index=None): ''' source is the object being dragged, widget is the container that's initialing the drag operation, and index s thu index of the item in the widget's model object (which presumably is a ListModel containing the source object.) ''' self.source = source self.widget = widget self.index = index def dnd_end(self, target, event): try: self.widget.clear() except AttributeError: pass class DraggyListbox(Listbox): def __init__(self, master=None, **kw): # Get our stack. self.stack = kw.pop('items') # Override any passed in selectmode. kw['selectmode'] = SINGLE Listbox.__init__(self, master, **kw) self.bind('', self.startDrag) self.bind('', self.clear) def clear(self, event=None): i = self.curselection() if i: i = int(i[0]) self.selection_clear(i) def startDrag(self, event): i = self.nearest(event.y) if i >= 0: self.selection_set(i) source = self.stack[i] source = SourceWrapper(source, self, i) event.num = 1 # Don't ask. (See Tkdnd.py) dnd_start(source, event) return "break" class ControllerListbox(DraggyListbox): def __init__(self, master=None, **kw): DraggyListbox.__init__(self, master, **kw) self._dragIndex = -1 def dnd_accept(self, source, event): self.focus_force() return self def dnd_enter(self, source, event): pass def dnd_motion(self, source, event): I = self.nearest(event.y_root - self.winfo_rooty()) if self._dragIndex >= 0: self.delete(self._dragIndex) self._dragIndex = I self.insert(I, '---') def dnd_leave(self, source, event): if self._dragIndex >= 0: self.delete(self._dragIndex) self._dragIndex = -1 def dnd_commit(self, source, event): i = self._dragIndex if i >= 0: self.delete(i) self._dragIndex = -1 try: if self is source.widget: # Don't duplicate something by dropping it on itself. if i == source.index: return # Instead, move it by removing it before the pending append. del self.stack[source.index] if i > source.index: i -= 1 self.stack.insert(i, source.source) finally: self.clear() class StackListbox(ControllerListbox): def __init__(self, world, master=None, **kw): ControllerListbox.__init__(self, master, **kw) self.world = world def _update(self): self.delete(0, 'end') self.insert(0, *map(self.format, self.stack)) def update_stack(self, stack): self.stack = list(iter_stack(stack)) self._update() def dnd_commit(self, source, event): ControllerListbox.dnd_commit(self, source, event) self._update() self.world.stack = list_to_stack(self.stack) @staticmethod def format(item): if isinstance(item, tuple): return '[%s]' % expression_to_string(item) return str(item)