I would like to add this mixin to objectlistview which is after all listctrl in fancy clothing.
Can anyone please assist? Thanks.
#-----------------------------------------------------------------------------
Name: dropscroller.py
Purpose: auto scrolling for a list that’s being used as a drop target
Author: Rob McMullen
Created: 2007
RCS-ID: $Id: $
Copyright: (c) 2007 Rob McMullen
License: wxPython
#-----------------------------------------------------------------------------
“”"
Automatic scrolling mixin for a list control, including an indicator
showing where the items will be dropped.It would be nice to have somethin similar for a tree control as well,
but I haven’t tackled that yet.
“”"
import sysimport wx
class ListDropScrollerMixin(object):
“”"Automatic scrolling for ListCtrls for use when using drag and drop.This mixin is used to automatically scroll a list control when approaching the top or bottom edge of a list. Currently, this only works for lists in report mode. Add this as a mixin in your list, and then call processListScroll in your DropTarget's OnDragOver method. When the drop ends, call finishListScroll to clean up the resources (i.e. the wx.Timer) that the dropscroller uses and make sure that the insertion indicator is erased. The parameter interval is the delay time in milliseconds between list scroll steps. If indicator_width is negative, then the indicator will be the width of the list. If positive, the width will be that number of pixels, and zero means to display no indicator. """ def __init__(self, interval=200, width=-1): """Don't forget to call this mixin's init method in your List. Interval is in milliseconds. """ self._auto_scroll_timer = None self._auto_scroll_interval = interval self._auto_scroll = 0 self._auto_scroll_save_y = -1 self._auto_scroll_save_width = width self.Bind(wx.EVT_TIMER, self.OnAutoScrollTimer) def _startAutoScrollTimer(self, direction = 0): """Set the direction of the next scroll, and start the interval timer if it's not already running. """ if self._auto_scroll_timer == None: self._auto_scroll_timer = wx.Timer(self, wx.TIMER_ONE_SHOT) self._auto_scroll_timer.Start(self._auto_scroll_interval) self._auto_scroll = direction def _stopAutoScrollTimer(self): """Clean up the timer resources. """ self._auto_scroll_timer = None self._auto_scroll = 0 def _getAutoScrollDirection(self, index): """Determine the scroll step direction that the list should move, based on the index reported by HitTest. """ first_displayed = self.GetTopItem() if first_displayed == index: # If the mouse is over the first index... if index > 0: # scroll the list up unless... return -1 else: # we're already at the top. return 0 elif index >= first_displayed + self.GetCountPerPage() - 1: # If the mouse is over the last visible item, but we're # not at the last physical item, scroll down. return 1 # we're somewhere in the middle of the list. Don't scroll return 0 def getDropIndex(self, x, y, index=None, flags=None): """Find the index to insert the new item, which could be before or after the index passed in. """ if index is None: index, flags = self.HitTest((x, y)) if index == wx.NOT_FOUND: # not clicked on an item if (flags & (wx.LIST_HITTEST_NOWHERE|wx.LIST_HITTEST_ABOVE|wx.LIST_HITTEST_BELOW)): # empty list or below last item index = sys.maxsize # append to end of list #print "getDropIndex: append to end of list: index=%d" % index elif (self.GetItemCount() > 0): if y <= self.GetItemRect(0).y: # clicked just above first item index = 0 # append to top of list #print "getDropIndex: before first item: index=%d, y=%d, rect.y=%d" % (index, y, self.GetItemRect(0).y) else: index = self.GetItemCount() + 1 # append to end of list #print "getDropIndex: after last item: index=%d" % index else: # clicked on an item # Get bounding rectangle for the item the user is dropping over. rect = self.GetItemRect(index) #print "getDropIndex: landed on %d, y=%d, rect=%s" % (index, y, rect) # NOTE: On all platforms, the y coordinate used by HitTest # is relative to the scrolled window. There are platform # differences, however, because on GTK the top of the # vertical scrollbar stops below the header, while on MSW # the top of the vertical scrollbar is equal to the top of # the header. The result is the y used in HitTest and the # y returned by GetItemRect are offset by a certain amount # on GTK. The HitTest's y=0 in GTK corresponds to the top # of the first item, while y=0 on MSW is in the header. # From Robin Dunn: use GetMainWindow on the list to find # the actual window on which to draw if self != self.GetMainWindow(): y += self.GetMainWindow().GetPositionTuple()[1] # If the user is dropping into the lower half of the rect, # we want to insert _after_ this item. if y >= (rect.y + rect.height/2): index = index + 1 return index def processListScroll(self, x, y): """Main handler: call this with the x and y coordinates of the mouse cursor as determined from the OnDragOver callback. This method will determine which direction the list should be scrolled, and start the interval timer if necessary. """ index, flags = self.HitTest((x, y)) direction = self._getAutoScrollDirection(index) if direction == 0: self._stopAutoScrollTimer() else: self._startAutoScrollTimer(direction) drop_index = self.getDropIndex(x, y, index=index, flags=flags) count = self.GetItemCount() if drop_index >= count: rect = self.GetItemRect(count - 1) y = rect.y + rect.height + 1 else: rect = self.GetItemRect(drop_index) y = rect.y # From Robin Dunn: on GTK & MAC the list is implemented as # a subwindow, so have to use GetMainWindow on the list to # find the actual window on which to draw if self != self.GetMainWindow(): y -= self.GetMainWindow().GetPositionTuple()[1] if self._auto_scroll_save_y == -1 or self._auto_scroll_save_y != y: #print "main window=%s, self=%s, pos=%s" % (self, self.GetMainWindow(), self.GetMainWindow().GetPositionTuple()) if self._auto_scroll_save_width < 0: self._auto_scroll_save_width = rect.width dc = self._getIndicatorDC() self._eraseIndicator(dc) dc.DrawLine(0, y, self._auto_scroll_save_width, y) self._auto_scroll_save_y = y def finishListScroll(self): """Clean up timer resource and erase indicator. """ self._stopAutoScrollTimer() self._eraseIndicator() def OnAutoScrollTimer(self, evt): """Timer event handler to scroll the list in the requested direction. """ #print "_auto_scroll = %d, timer = %s" % (self._auto_scroll, self._auto_scroll_timer is not None) if self._auto_scroll == 0: # clean up timer resource self._auto_scroll_timer = None else: dc = self._getIndicatorDC() self._eraseIndicator(dc) if self._auto_scroll < 0: self.EnsureVisible(self.GetTopItem() + self._auto_scroll) self._auto_scroll_timer.Start() else: self.EnsureVisible(self.GetTopItem() + self.GetCountPerPage()) self._auto_scroll_timer.Start() evt.Skip() def _getIndicatorDC(self): dc = wx.ClientDC(self.GetMainWindow()) dc.SetPen(wx.Pen(wx.WHITE, 3)) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetLogicalFunction(wx.XOR) return dc def _eraseIndicator(self, dc=None): if dc is None: dc = self._getIndicatorDC() if self._auto_scroll_save_y >= 0: # erase the old line dc.DrawLine(0, self._auto_scroll_save_y, self._auto_scroll_save_width, self._auto_scroll_save_y) self._auto_scroll_save_y = -1
if name == ‘main’:
import pickle as pickleclass TestDataObject(wx.CustomDataObject): """Sample custom data object""" def __init__(self): wx.CustomDataObject.__init__(self, "TestData") class TestDropTarget(wx.PyDropTarget): """Custom drop target modified from the wxPython demo.""" def __init__(self, window): wx.DropTarget.__init__(self) self.dv = window # specify the type of data we will accept self.data = TestDataObject() self.SetDataObject(self.data) def cleanup(self): self.dv.finishListScroll() # some virtual methods that track the progress of the drag def OnEnter(self, x, y, d): print ("OnEnter: %d, %d, %d\n" % (x, y, d)) return d def OnLeave(self): print ("OnLeave\n") self.cleanup() def OnDrop(self, x, y): print ("OnDrop: %d %d\n" % (x, y)) self.cleanup() return True def OnDragOver(self, x, y, d): top = self.dv.GetTopItem() print ("OnDragOver: %d, %d, %d, top=%s" % (x, y, d, top)) self.dv.processListScroll(x, y) # The value returned here tells the source what kind of visual # feedback to give. For example, if wxDragCopy is returned then # only the copy cursor will be shown, even if the source allows # moves. You can use the passed in (x,y) to determine what kind # of feedback to give. In this case we return the suggested value # which is based on whether the Ctrl key is pressed. return d # Called when OnDrop returns True. We need to get the data and # do something with it. def OnData(self, x, y, d): print ("OnData: %d, %d, %d\n" % (x, y, d)) self.cleanup() # copy the data from the drag source to our data object if self.GetData(): # convert it back to a list of lines and give it to the viewer items = pickle.loads(self.data.GetData()) self.dv.AddDroppedItems(x, y, items) # what is returned signals the source what to do # with the original data (move, copy, etc.) In this # case we just return the suggested value given to us. return d class TestList(wx.ListCtrl, ListDropScrollerMixin): """Simple list control that provides a drop target and uses the new mixin for automatic scrolling. """ def __init__(self, parent, name, count=100): wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT) # The mixin needs to be initialized ListDropScrollerMixin.__init__(self, interval=200) self.dropTarget=TestDropTarget(self) self.SetDropTarget(self.dropTarget) self.create(name, count) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnStartDrag) def create(self, name, count): """Set up some test data.""" self.InsertColumn(0, "#") self.InsertColumn(1, "Title") for i in range(count): self.InsertItem(i, str(i)) self.SetItem(i, 1, "%s-%d" % (name, i)) def OnStartDrag(self, evt): index = evt.GetIndex() print ("beginning drag of item %d" % index) # Create the data object containing all currently selected # items data = TestDataObject() items = [] index = self.GetFirstSelected() while index != -1: items.append((self.GetItem(index, 0).GetText(), self.GetItem(index, 1).GetText())) index = self.GetNextSelected(index) data.SetData(pickle.dumps(items,-1)) # And finally, create the drop source and begin the drag # and drop opperation dropSource = wx.DropSource(self) dropSource.SetData(data) print ("Begining DragDrop\n") result = dropSource.DoDragDrop(wx.Drag_AllowMove) print ("DragDrop completed: %d\n" % result) def AddDroppedItems(self, x, y, items): index = self.getDropIndex(x, y) print ("At (%d,%d), index=%d, adding %s" % (x, y, index, items)) list_count = self.GetItemCount() for item in items: index = self.InsertItem(index, item[0]) self.SetItem(index, 1, item[1]) index += 1 class ListPanel(wx.SplitterWindow): def __init__(self, parent): wx.SplitterWindow.__init__(self, parent) self.list1 = TestList(self, "left", 100) self.list2 = TestList(self, "right", 10) self.SplitVertically(self.list1, self.list2) self.Layout() app = wx.App() frame = wx.Frame(None, -1, title='List Drag Test', size=(400,500)) frame.CreateStatusBar() panel = ListPanel(frame) label = wx.StaticText(frame, -1, "Drag items from a list to either list.\nThe lists will scroll when the cursor\nis near the first and last visible items") sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(panel, 1, wx.EXPAND | wx.ALL, 5) frame.SetAutoLayout(1) frame.SetSizer(sizer) frame.Show(1) app.MainLoop()