Merged the wxPy_newswig branch into the HEAD branch (main trunk)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@24541 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -1 +1,14 @@
|
||||
# Python package
|
||||
#----------------------------------------------------------------------
|
||||
# Name: wxPython.lib.mixins
|
||||
# Purpose: A package for helpful wxPython mix-in classes
|
||||
#
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 15-May-2001
|
||||
# RCS-ID: $Id$
|
||||
# Copyright: (c) 2001 by Total Control Software
|
||||
# Licence: wxWindows license
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
@@ -1,8 +1,43 @@
|
||||
#----------------------------------------------------------------------------
|
||||
# Name: wxPython.lib.mixins.grid
|
||||
# Purpose: Helpful mix-in classes for wxGrid
|
||||
#
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 5-June-2001
|
||||
# RCS-ID: $Id$
|
||||
# Copyright: (c) 2001 by Total Control Software
|
||||
# Licence: wxWindows license
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
|
||||
from wxPython import wx, grid
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class wxGridAutoEditMixin:
|
||||
"""A mix-in class that automatically enables the grid edit control when
|
||||
a cell is selected.
|
||||
|
||||
If your class hooks EVT_GRID_SELECT_CELL be sure to call event.Skip so
|
||||
this handler will be called too.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__enableEdit = 0
|
||||
wx.EVT_IDLE(self, self.__OnIdle)
|
||||
grid.EVT_GRID_SELECT_CELL(self, self.__OnSelectCell)
|
||||
|
||||
|
||||
def __OnIdle(self, evt):
|
||||
if self.__enableEdit:
|
||||
if self.CanEnableCellControl():
|
||||
self.EnableCellEditControl()
|
||||
self.__enableEdit = 0
|
||||
evt.Skip()
|
||||
|
||||
|
||||
def __OnSelectCell(self, evt):
|
||||
self.__enableEdit = 1
|
||||
evt.Skip()
|
||||
|
||||
from wx import _rename
|
||||
from wxPython.lib.mixins import grid
|
||||
_rename(globals(), grid.__dict__, modulename='lib.mixins.grid')
|
||||
del grid
|
||||
del _rename
|
||||
|
@@ -1,8 +1,73 @@
|
||||
#----------------------------------------------------------------------------
|
||||
# Name: wxPython.lib.mixins.listctrl
|
||||
# Purpose: Helpful mix-in classes for using a wxImageList
|
||||
#
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 15-May-2001
|
||||
# RCS-ID: $Id$
|
||||
# Copyright: (c) 2001 by Total Control Software
|
||||
# Licence: wxWindows license
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
from wxPython.wx import *
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
class MagicImageList:
|
||||
'''
|
||||
Mix-in to provide "magic" growing image lists
|
||||
By Mike Fletcher
|
||||
'''
|
||||
|
||||
### LAZYTREE and LISTCONTROL Methods
|
||||
DEFAULTICONSIZE = 16
|
||||
|
||||
def SetupIcons(self, images=(), size=None):
|
||||
self.__size = size or self.DEFAULTICONSIZE
|
||||
self.__magicImageList = wxImageList (self.__size,self.__size)
|
||||
self.__magicImageListMapping = {}
|
||||
self.SetImageList (
|
||||
self.__magicImageList, {
|
||||
16:wxIMAGE_LIST_SMALL,
|
||||
32:wxIMAGE_LIST_NORMAL,
|
||||
}[self.__size]
|
||||
)
|
||||
for image in images:
|
||||
self.AddIcon (image)
|
||||
|
||||
def GetIcons (self, node):
|
||||
'''Get icon indexes for a given node, or None if no associated icon'''
|
||||
icon = self.GetIcon( node )
|
||||
if icon:
|
||||
index = self.AddIcon (icon)
|
||||
return index, index
|
||||
return None
|
||||
|
||||
|
||||
### Local methods...
|
||||
def AddIcon(self, icon, mask = wxNullBitmap):
|
||||
'''Add an icon to the image list, or get the index if already there'''
|
||||
index = self.__magicImageListMapping.get (id (icon))
|
||||
if index is None:
|
||||
if isinstance( icon, wxIconPtr ):
|
||||
index = self.__magicImageList.AddIcon( icon )
|
||||
elif isinstance( icon, wxBitmapPtr ):
|
||||
if isinstance( mask, wxColour ):
|
||||
index = self.__magicImageList.AddWithColourMask( icon, mask )
|
||||
else:
|
||||
index = self.__magicImageList.Add( icon, mask )
|
||||
else:
|
||||
raise ValueError("Unexpected icon object %s, "
|
||||
"expected wxIcon or wxBitmap" % (icon))
|
||||
self.__magicImageListMapping [id (icon)] = index
|
||||
return index
|
||||
|
||||
### Customisation point...
|
||||
def GetIcon( self, node ):
|
||||
'''Get the actual icon object for a node'''
|
||||
if hasattr (node,"DIAGRAMICON"):
|
||||
return node.DIAGRAMICON
|
||||
|
||||
|
||||
"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
|
||||
|
||||
from wx import _rename
|
||||
from wxPython.lib.mixins import imagelist
|
||||
_rename(globals(), imagelist.__dict__, modulename='lib.mixins.imagelist')
|
||||
del imagelist
|
||||
del _rename
|
||||
|
@@ -1,8 +1,322 @@
|
||||
#----------------------------------------------------------------------------
|
||||
# Name: wxPython.lib.mixins.listctrl
|
||||
# Purpose: Helpful mix-in classes for wxListCtrl
|
||||
#
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 15-May-2001
|
||||
# RCS-ID: $Id$
|
||||
# Copyright: (c) 2001 by Total Control Software
|
||||
# Licence: wxWindows license
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
|
||||
from wxPython.wx import *
|
||||
import locale
|
||||
|
||||
from wx import _rename
|
||||
from wxPython.lib.mixins import listctrl
|
||||
_rename(globals(), listctrl.__dict__, modulename='lib.mixins.listctrl')
|
||||
del listctrl
|
||||
del _rename
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
class wxColumnSorterMixin:
|
||||
"""
|
||||
A mixin class that handles sorting of a wxListCtrl in REPORT mode when
|
||||
the column header is clicked on.
|
||||
|
||||
There are a few requirments needed in order for this to work genericly:
|
||||
|
||||
1. The combined class must have a GetListCtrl method that
|
||||
returns the wxListCtrl to be sorted, and the list control
|
||||
must exist at the time the wxColumnSorterMixin.__init__
|
||||
method is called because it uses GetListCtrl.
|
||||
|
||||
2. Items in the list control must have a unique data value set
|
||||
with list.SetItemData.
|
||||
|
||||
3. The combined class must have an attribute named itemDataMap
|
||||
that is a dictionary mapping the data values to a sequence of
|
||||
objects representing the values in each column. These values
|
||||
are compared in the column sorter to determine sort order.
|
||||
|
||||
Interesting methods to override are GetColumnSorter,
|
||||
GetSecondarySortValues, and GetSortImages. See below for details.
|
||||
"""
|
||||
|
||||
def __init__(self, numColumns):
|
||||
self.SetColumnCount(numColumns)
|
||||
list = self.GetListCtrl()
|
||||
if not list:
|
||||
raise ValueError, "No wxListCtrl available"
|
||||
EVT_LIST_COL_CLICK(list, list.GetId(), self.__OnColClick)
|
||||
|
||||
|
||||
def SetColumnCount(self, newNumColumns):
|
||||
self._colSortFlag = [0] * newNumColumns
|
||||
self._col = -1
|
||||
|
||||
|
||||
def SortListItems(self, col=-1, ascending=1):
|
||||
"""Sort the list on demand. Can also be used to set the sort column and order."""
|
||||
oldCol = self._col
|
||||
if col != -1:
|
||||
self._col = col
|
||||
self._colSortFlag[col] = ascending
|
||||
self.GetListCtrl().SortItems(self.GetColumnSorter())
|
||||
self.__updateImages(oldCol)
|
||||
|
||||
|
||||
def GetColumnWidths(self):
|
||||
"""
|
||||
Returns a list of column widths. Can be used to help restore the current
|
||||
view later.
|
||||
"""
|
||||
list = self.GetListCtrl()
|
||||
rv = []
|
||||
for x in range(len(self._colSortFlag)):
|
||||
rv.append(list.GetColumnWidth(x))
|
||||
return rv
|
||||
|
||||
|
||||
def GetSortImages(self):
|
||||
"""
|
||||
Returns a tuple of image list indexesthe indexes in the image list for an image to be put on the column
|
||||
header when sorting in descending order.
|
||||
"""
|
||||
return (-1, -1) # (decending, ascending) image IDs
|
||||
|
||||
|
||||
def GetColumnSorter(self):
|
||||
"""Returns a callable object to be used for comparing column values when sorting."""
|
||||
return self.__ColumnSorter
|
||||
|
||||
|
||||
def GetSecondarySortValues(self, col, key1, key2):
|
||||
"""Returns a tuple of 2 values to use for secondary sort values when the
|
||||
items in the selected column match equal. The default just returns the
|
||||
item data values."""
|
||||
return (key1, key2)
|
||||
|
||||
|
||||
def __OnColClick(self, evt):
|
||||
oldCol = self._col
|
||||
self._col = col = evt.GetColumn()
|
||||
self._colSortFlag[col] = not self._colSortFlag[col]
|
||||
self.GetListCtrl().SortItems(self.GetColumnSorter())
|
||||
self.__updateImages(oldCol)
|
||||
evt.Skip()
|
||||
|
||||
|
||||
def __ColumnSorter(self, key1, key2):
|
||||
col = self._col
|
||||
ascending = self._colSortFlag[col]
|
||||
item1 = self.itemDataMap[key1][col]
|
||||
item2 = self.itemDataMap[key2][col]
|
||||
|
||||
#--- Internationalization of string sorting with locale module
|
||||
if type(item1) == type('') or type(item2) == type(''):
|
||||
cmpVal = locale.strcoll(str(item1), str(item2))
|
||||
else:
|
||||
cmpVal = cmp(item1, item2)
|
||||
#---
|
||||
|
||||
# If the items are equal then pick something else to make the sort value unique
|
||||
if cmpVal == 0:
|
||||
cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
|
||||
|
||||
if ascending:
|
||||
return cmpVal
|
||||
else:
|
||||
return -cmpVal
|
||||
|
||||
|
||||
def __updateImages(self, oldCol):
|
||||
sortImages = self.GetSortImages()
|
||||
if self._col != -1 and sortImages[0] != -1:
|
||||
img = sortImages[self._colSortFlag[self._col]]
|
||||
list = self.GetListCtrl()
|
||||
if oldCol != -1:
|
||||
list.ClearColumnImage(oldCol)
|
||||
list.SetColumnImage(self._col, img)
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
class wxListCtrlAutoWidthMixin:
|
||||
""" A mix-in class that automatically resizes the last column to take up
|
||||
the remaining width of the wxListCtrl.
|
||||
|
||||
This causes the wxListCtrl to automatically take up the full width of
|
||||
the list, without either a horizontal scroll bar (unless absolutely
|
||||
necessary) or empty space to the right of the last column.
|
||||
|
||||
NOTE: This only works for report-style lists.
|
||||
|
||||
WARNING: If you override the EVT_SIZE event in your wxListCtrl, make
|
||||
sure you call event.Skip() to ensure that the mixin's
|
||||
_OnResize method is called.
|
||||
|
||||
This mix-in class was written by Erik Westra <ewestra@wave.co.nz>
|
||||
"""
|
||||
def __init__(self):
|
||||
""" Standard initialiser.
|
||||
"""
|
||||
self._lastColMinWidth = None
|
||||
|
||||
EVT_SIZE(self, self._onResize)
|
||||
EVT_LIST_COL_END_DRAG(self, self.GetId(), self._onResize)
|
||||
|
||||
|
||||
def resizeLastColumn(self, minWidth):
|
||||
""" Resize the last column appropriately.
|
||||
|
||||
If the list's columns are too wide to fit within the window, we use
|
||||
a horizontal scrollbar. Otherwise, we expand the right-most column
|
||||
to take up the remaining free space in the list.
|
||||
|
||||
This method is called automatically when the wxListCtrl is resized;
|
||||
you can also call it yourself whenever you want the last column to
|
||||
be resized appropriately (eg, when adding, removing or resizing
|
||||
columns).
|
||||
|
||||
'minWidth' is the preferred minimum width for the last column.
|
||||
"""
|
||||
self._lastColMinWidth = minWidth
|
||||
self._doResize()
|
||||
|
||||
# =====================
|
||||
# == Private Methods ==
|
||||
# =====================
|
||||
|
||||
def _onResize(self, event):
|
||||
""" Respond to the wxListCtrl being resized.
|
||||
|
||||
We automatically resize the last column in the list.
|
||||
"""
|
||||
wxCallAfter(self._doResize)
|
||||
event.Skip()
|
||||
|
||||
|
||||
def _doResize(self):
|
||||
""" Resize the last column as appropriate.
|
||||
|
||||
If the list's columns are too wide to fit within the window, we use
|
||||
a horizontal scrollbar. Otherwise, we expand the right-most column
|
||||
to take up the remaining free space in the list.
|
||||
|
||||
We remember the current size of the last column, before resizing,
|
||||
as the preferred minimum width if we haven't previously been given
|
||||
or calculated a minimum width. This ensure that repeated calls to
|
||||
_doResize() don't cause the last column to size itself too large.
|
||||
"""
|
||||
numCols = self.GetColumnCount()
|
||||
if numCols == 0: return # Nothing to resize.
|
||||
|
||||
if self._lastColMinWidth == None:
|
||||
self._lastColMinWidth = self.GetColumnWidth(numCols - 1)
|
||||
|
||||
# We're showing the vertical scrollbar -> allow for scrollbar width
|
||||
# NOTE: on GTK, the scrollbar is included in the client size, but on
|
||||
# Windows it is not included
|
||||
listWidth = self.GetClientSize().width
|
||||
if wxPlatform != '__WXMSW__':
|
||||
if self.GetItemCount() > self.GetCountPerPage():
|
||||
scrollWidth = wxSystemSettings_GetMetric(wxSYS_VSCROLL_X)
|
||||
listWidth = listWidth - scrollWidth
|
||||
|
||||
totColWidth = 0 # Width of all columns except last one.
|
||||
for col in range(numCols-1):
|
||||
totColWidth = totColWidth + self.GetColumnWidth(col)
|
||||
|
||||
lastColWidth = self.GetColumnWidth(numCols - 1)
|
||||
|
||||
if totColWidth + self._lastColMinWidth > listWidth:
|
||||
# We haven't got the width to show the last column at its minimum
|
||||
# width -> set it to its minimum width and allow the horizontal
|
||||
# scrollbar to show.
|
||||
self.SetColumnWidth(numCols-1, self._lastColMinWidth)
|
||||
return
|
||||
|
||||
# Resize the last column to take up the remaining available space.
|
||||
|
||||
self.SetColumnWidth(numCols-1, listWidth - totColWidth)
|
||||
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
|
||||
SEL_FOC = wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED
|
||||
def selectBeforePopup(event):
|
||||
"""Ensures the item the mouse is pointing at is selected before a popup.
|
||||
|
||||
Works with both single-select and multi-select lists."""
|
||||
ctrl = event.GetEventObject()
|
||||
if isinstance(ctrl, wxListCtrl):
|
||||
n, flags = ctrl.HitTest(event.GetPosition())
|
||||
if n >= 0:
|
||||
if not ctrl.GetItemState(n, wxLIST_STATE_SELECTED):
|
||||
for i in range(ctrl.GetItemCount()):
|
||||
ctrl.SetItemState(i, 0, SEL_FOC)
|
||||
#for i in getListCtrlSelection(ctrl, SEL_FOC):
|
||||
# ctrl.SetItemState(i, 0, SEL_FOC)
|
||||
ctrl.SetItemState(n, SEL_FOC, SEL_FOC)
|
||||
|
||||
def getListCtrlSelection(listctrl, state=wxLIST_STATE_SELECTED):
|
||||
""" Returns list of item indexes of given state (selected by defaults) """
|
||||
res = []
|
||||
idx = -1
|
||||
while 1:
|
||||
idx = listctrl.GetNextItem(idx, wxLIST_NEXT_ALL, state)
|
||||
if idx == -1:
|
||||
break
|
||||
res.append(idx)
|
||||
return res
|
||||
|
||||
class ListCtrlSelectionManagerMix:
|
||||
"""Mixin that defines a platform independent selection policy
|
||||
|
||||
As selection single and multi-select list return the item index or a
|
||||
list of item indexes respectively.
|
||||
"""
|
||||
wxEVT_DOPOPUPMENU = wxNewId()
|
||||
_menu = None
|
||||
|
||||
def __init__(self):
|
||||
EVT_RIGHT_DOWN(self, self.OnLCSMRightDown)
|
||||
self.Connect(-1, -1, self.wxEVT_DOPOPUPMENU, self.OnLCSMDoPopup)
|
||||
|
||||
def getPopupMenu(self):
|
||||
""" Override to implement dynamic menus (create) """
|
||||
return self._menu
|
||||
|
||||
def setPopupMenu(self, menu):
|
||||
""" Must be set for default behaviour """
|
||||
self._menu = menu
|
||||
|
||||
def afterPopupMenu(self, menu):
|
||||
""" Override to implement dynamic menus (destroy) """
|
||||
pass
|
||||
|
||||
def getSelection(self):
|
||||
res = getListCtrlSelection(self)
|
||||
if self.GetWindowStyleFlag() & wxLC_SINGLE_SEL:
|
||||
if res:
|
||||
return res[0]
|
||||
else:
|
||||
return -1
|
||||
else:
|
||||
return res
|
||||
|
||||
def OnLCSMRightDown(self, event):
|
||||
selectBeforePopup(event)
|
||||
event.Skip()
|
||||
menu = self.getPopupMenu()
|
||||
if menu:
|
||||
evt = wxPyEvent()
|
||||
evt.SetEventType(self.wxEVT_DOPOPUPMENU)
|
||||
evt.menu = menu
|
||||
evt.pos = event.GetPosition()
|
||||
wxPostEvent(self, evt)
|
||||
|
||||
def OnLCSMDoPopup(self, event):
|
||||
self.PopupMenu(event.menu, event.pos)
|
||||
self.afterPopupMenu(event.menu)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
@@ -1,8 +1,396 @@
|
||||
#---------------------------------------------------------------------------
|
||||
# Name: wxPython.lib.mixins.rubberband
|
||||
# Purpose: A mixin class for doing "RubberBand"-ing on a window.
|
||||
#
|
||||
# Author: Robb Shecter and members of wxPython-users
|
||||
#
|
||||
# Created: 11-September-2002
|
||||
# RCS-ID: $Id$
|
||||
# Copyright: (c) 2002 by db-X Corporation
|
||||
# Licence: wxWindows license
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
|
||||
"""
|
||||
A mixin class for doing "RubberBand"-ing on a window.
|
||||
"""
|
||||
|
||||
from wx import _rename
|
||||
from wxPython.lib.mixins import rubberband
|
||||
_rename(globals(), rubberband.__dict__, modulename='lib.mixins.rubberband')
|
||||
del rubberband
|
||||
del _rename
|
||||
from wxPython.wx import *
|
||||
|
||||
|
||||
#
|
||||
# Some miscellaneous mathematical and geometrical functions
|
||||
#
|
||||
|
||||
def isNegative(aNumber):
|
||||
"""
|
||||
x < 0: 1
|
||||
else: 0
|
||||
"""
|
||||
return aNumber < 0
|
||||
|
||||
|
||||
def normalizeBox(box):
|
||||
"""
|
||||
Convert any negative measurements in the current
|
||||
box to positive, and adjust the origin.
|
||||
"""
|
||||
x, y, w, h = box
|
||||
if w < 0:
|
||||
x += (w+1)
|
||||
w *= -1
|
||||
if h < 0:
|
||||
y += (h+1)
|
||||
h *= -1
|
||||
return (x, y, w, h)
|
||||
|
||||
|
||||
def boxToExtent(box):
|
||||
"""
|
||||
Convert a box specification to an extent specification.
|
||||
I put this into a seperate function after I realized that
|
||||
I had been implementing it wrong in several places.
|
||||
"""
|
||||
b = normalizeBox(box)
|
||||
return (b[0], b[1], b[0]+b[2]-1, b[1]+b[3]-1)
|
||||
|
||||
|
||||
def pointInBox(x, y, box):
|
||||
"""
|
||||
Return True if the given point is contained in the box.
|
||||
"""
|
||||
e = boxToExtent(box)
|
||||
return x >= e[0] and x <= e[2] and y >= e[1] and y <= e[3]
|
||||
|
||||
|
||||
def pointOnBox(x, y, box, thickness=1):
|
||||
"""
|
||||
Return True if the point is on the outside edge
|
||||
of the box. The thickness defines how thick the
|
||||
edge should be. This is necessary for HCI reasons:
|
||||
For example, it's normally very difficult for a user
|
||||
to manuever the mouse onto a one pixel border.
|
||||
"""
|
||||
outerBox = box
|
||||
innerBox = (box[0]+thickness, box[1]+thickness, box[2]-(thickness*2), box[3]-(thickness*2))
|
||||
return pointInBox(x, y, outerBox) and not pointInBox(x, y, innerBox)
|
||||
|
||||
|
||||
def getCursorPosition(x, y, box, thickness=1):
|
||||
"""
|
||||
Return a position number in the range 0 .. 7 to indicate
|
||||
where on the box border the point is. The layout is:
|
||||
|
||||
0 1 2
|
||||
7 3
|
||||
6 5 4
|
||||
"""
|
||||
x0, y0, x1, y1 = boxToExtent(box)
|
||||
w, h = box[2], box[3]
|
||||
delta = thickness - 1
|
||||
p = None
|
||||
|
||||
if pointInBox(x, y, (x0, y0, thickness, thickness)):
|
||||
p = 0
|
||||
elif pointInBox(x, y, (x1-delta, y0, thickness, thickness)):
|
||||
p = 2
|
||||
elif pointInBox(x, y, (x1-delta, y1-delta, thickness, thickness)):
|
||||
p = 4
|
||||
elif pointInBox(x, y, (x0, y1-delta, thickness, thickness)):
|
||||
p = 6
|
||||
elif pointInBox(x, y, (x0+thickness, y0, w-(thickness*2), thickness)):
|
||||
p = 1
|
||||
elif pointInBox(x, y, (x1-delta, y0+thickness, thickness, h-(thickness*2))):
|
||||
p = 3
|
||||
elif pointInBox(x, y, (x0+thickness, y1-delta, w-(thickness*2), thickness)):
|
||||
p = 5
|
||||
elif pointInBox(x, y, (x0, y0+thickness, thickness, h-(thickness*2))):
|
||||
p = 7
|
||||
|
||||
return p
|
||||
|
||||
|
||||
|
||||
|
||||
class RubberBand:
|
||||
"""
|
||||
A stretchable border which is drawn on top of an
|
||||
image to define an area.
|
||||
"""
|
||||
def __init__(self, drawingSurface, aspectRatio=None):
|
||||
self.__THICKNESS = 5
|
||||
self.drawingSurface = drawingSurface
|
||||
self.aspectRatio = aspectRatio
|
||||
self.hasLetUp = 0
|
||||
self.currentlyMoving = None
|
||||
self.currentBox = None
|
||||
self.__enabled = 1
|
||||
self.__currentCursor = None
|
||||
EVT_MOUSE_EVENTS(drawingSurface, self.__handleMouseEvents)
|
||||
EVT_PAINT(drawingSurface, self.__handleOnPaint)
|
||||
|
||||
def __setEnabled(self, enabled):
|
||||
self.__enabled = enabled
|
||||
|
||||
def __isEnabled(self):
|
||||
return self.__enabled
|
||||
|
||||
def __handleOnPaint(self, event):
|
||||
#print 'paint'
|
||||
event.Skip()
|
||||
|
||||
def __isMovingCursor(self):
|
||||
"""
|
||||
Return True if the current cursor is one used to
|
||||
mean moving the rubberband.
|
||||
"""
|
||||
return self.__currentCursor == wxCURSOR_HAND
|
||||
|
||||
def __isSizingCursor(self):
|
||||
"""
|
||||
Return True if the current cursor is one of the ones
|
||||
I may use to signify sizing.
|
||||
"""
|
||||
sizingCursors = [wxCURSOR_SIZENESW,
|
||||
wxCURSOR_SIZENS,
|
||||
wxCURSOR_SIZENWSE,
|
||||
wxCURSOR_SIZEWE,
|
||||
wxCURSOR_SIZING,
|
||||
wxCURSOR_CROSS]
|
||||
try:
|
||||
sizingCursors.index(self.__currentCursor)
|
||||
return 1
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
|
||||
def __handleMouseEvents(self, event):
|
||||
"""
|
||||
React according to the new event. This is the main
|
||||
entry point into the class. This method contains the
|
||||
logic for the class's behavior.
|
||||
"""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
x, y = event.GetPosition()
|
||||
|
||||
# First make sure we have started a box.
|
||||
if self.currentBox == None and not event.LeftDown():
|
||||
# No box started yet. Set cursor to the initial kind.
|
||||
self.__setCursor(wxCURSOR_CROSS)
|
||||
return
|
||||
|
||||
if event.LeftDown():
|
||||
if self.currentBox == None:
|
||||
# No RB Box, so start a new one.
|
||||
self.currentBox = (x, y, 0, 0)
|
||||
self.hasLetUp = 0
|
||||
elif self.__isSizingCursor():
|
||||
# Starting a sizing operation. Change the origin.
|
||||
position = getCursorPosition(x, y, self.currentBox, thickness=self.__THICKNESS)
|
||||
self.currentBox = self.__denormalizeBox(position, self.currentBox)
|
||||
|
||||
elif event.Dragging() and event.LeftIsDown():
|
||||
# Use the cursor type to determine operation
|
||||
if self.__isMovingCursor():
|
||||
if self.currentlyMoving or pointInBox(x, y, self.currentBox):
|
||||
if not self.currentlyMoving:
|
||||
self.currentlyMoving = (x - self.currentBox[0], y - self.currentBox[1])
|
||||
self.__moveTo(x - self.currentlyMoving[0], y - self.currentlyMoving[1])
|
||||
elif self.__isSizingCursor():
|
||||
self.__resizeBox(x, y)
|
||||
|
||||
elif event.LeftUp():
|
||||
self.hasLetUp = 1
|
||||
self.currentlyMoving = None
|
||||
self.__normalizeBox()
|
||||
|
||||
elif event.Moving() and not event.Dragging():
|
||||
# Simple mouse movement event
|
||||
self.__mouseMoved(x,y)
|
||||
|
||||
def __denormalizeBox(self, position, box):
|
||||
x, y, w, h = box
|
||||
b = box
|
||||
if position == 2 or position == 3:
|
||||
b = (x, y + (h-1), w, h * -1)
|
||||
elif position == 0 or position == 1 or position == 7:
|
||||
b = (x + (w-1), y + (h-1), w * -1, h * -1)
|
||||
elif position == 6:
|
||||
b = (x + (w-1), y, w * -1, h)
|
||||
return b
|
||||
|
||||
def __resizeBox(self, x, y):
|
||||
"""
|
||||
Resize and repaint the box based on the given mouse
|
||||
coordinates.
|
||||
"""
|
||||
# Implement the correct behavior for dragging a side
|
||||
# of the box: Only change one dimension.
|
||||
if not self.aspectRatio:
|
||||
if self.__currentCursor == wxCURSOR_SIZENS:
|
||||
x = None
|
||||
elif self.__currentCursor == wxCURSOR_SIZEWE:
|
||||
y = None
|
||||
|
||||
x0,y0,w0,h0 = self.currentBox
|
||||
currentExtent = boxToExtent(self.currentBox)
|
||||
if x == None:
|
||||
if w0 < 1:
|
||||
w0 += 1
|
||||
else:
|
||||
w0 -= 1
|
||||
x = x0 + w0
|
||||
if y == None:
|
||||
if h0 < 1:
|
||||
h0 += 1
|
||||
else:
|
||||
h0 -= 1
|
||||
y = y0 + h0
|
||||
x1,y1 = x, y
|
||||
w, h = abs(x1-x0)+1, abs(y1-y0)+1
|
||||
if self.aspectRatio:
|
||||
w = max(w, int(h * self.aspectRatio))
|
||||
h = int(w / self.aspectRatio)
|
||||
w *= [1,-1][isNegative(x1-x0)]
|
||||
h *= [1,-1][isNegative(y1-y0)]
|
||||
newbox = (x0, y0, w, h)
|
||||
self.__drawAndErase(boxToDraw=normalizeBox(newbox), boxToErase=normalizeBox(self.currentBox))
|
||||
self.currentBox = (x0, y0, w, h)
|
||||
|
||||
def __normalizeBox(self):
|
||||
"""
|
||||
Convert any negative measurements in the current
|
||||
box to positive, and adjust the origin.
|
||||
"""
|
||||
self.currentBox = normalizeBox(self.currentBox)
|
||||
|
||||
def __mouseMoved(self, x, y):
|
||||
"""
|
||||
Called when the mouse moved without any buttons pressed
|
||||
or dragging being done.
|
||||
"""
|
||||
# Are we on the bounding box?
|
||||
if pointOnBox(x, y, self.currentBox, thickness=self.__THICKNESS):
|
||||
position = getCursorPosition(x, y, self.currentBox, thickness=self.__THICKNESS)
|
||||
cursor = [
|
||||
wxCURSOR_SIZENWSE,
|
||||
wxCURSOR_SIZENS,
|
||||
wxCURSOR_SIZENESW,
|
||||
wxCURSOR_SIZEWE,
|
||||
wxCURSOR_SIZENWSE,
|
||||
wxCURSOR_SIZENS,
|
||||
wxCURSOR_SIZENESW,
|
||||
wxCURSOR_SIZEWE
|
||||
] [position]
|
||||
self.__setCursor(cursor)
|
||||
elif pointInBox(x, y, self.currentBox):
|
||||
self.__setCursor(wxCURSOR_HAND)
|
||||
else:
|
||||
self.__setCursor()
|
||||
|
||||
def __setCursor(self, id=None):
|
||||
"""
|
||||
Set the mouse cursor to the given id.
|
||||
"""
|
||||
if self.__currentCursor != id: # Avoid redundant calls
|
||||
if id:
|
||||
self.drawingSurface.SetCursor(wxStockCursor(id))
|
||||
else:
|
||||
self.drawingSurface.SetCursor(wxNullCursor)
|
||||
self.__currentCursor = id
|
||||
|
||||
def __moveCenterTo(self, x, y):
|
||||
"""
|
||||
Move the rubber band so that its center is at (x,y).
|
||||
"""
|
||||
x0, y0, w, h = self.currentBox
|
||||
x2, y2 = x - (w/2), y - (h/2)
|
||||
self.__moveTo(x2, y2)
|
||||
|
||||
def __moveTo(self, x, y):
|
||||
"""
|
||||
Move the rubber band so that its origin is at (x,y).
|
||||
"""
|
||||
newbox = (x, y, self.currentBox[2], self.currentBox[3])
|
||||
self.__drawAndErase(boxToDraw=newbox, boxToErase=self.currentBox)
|
||||
self.currentBox = newbox
|
||||
|
||||
def __drawAndErase(self, boxToDraw, boxToErase=None):
|
||||
"""
|
||||
Draw one box shape and possibly erase another.
|
||||
"""
|
||||
dc = wxClientDC(self.drawingSurface)
|
||||
dc.BeginDrawing()
|
||||
dc.SetPen(wxPen(wxWHITE, 1, wxDOT))
|
||||
dc.SetBrush(wxTRANSPARENT_BRUSH)
|
||||
dc.SetLogicalFunction(wxXOR)
|
||||
if boxToErase:
|
||||
dc.DrawRectangle(*boxToErase)
|
||||
dc.DrawRectangle(*boxToDraw)
|
||||
dc.EndDrawing()
|
||||
|
||||
def __dumpMouseEvent(self, event):
|
||||
print 'Moving: ',event.Moving()
|
||||
print 'Dragging: ',event.Dragging()
|
||||
print 'LeftDown: ',event.LeftDown()
|
||||
print 'LeftisDown: ',event.LeftIsDown()
|
||||
print 'LeftUp: ',event.LeftUp()
|
||||
print 'Position: ',event.GetPosition()
|
||||
print 'x,y: ',event.GetX(),event.GetY()
|
||||
print
|
||||
|
||||
|
||||
#
|
||||
# The public API:
|
||||
#
|
||||
|
||||
def reset(self, aspectRatio=None):
|
||||
"""
|
||||
Clear the existing rubberband
|
||||
"""
|
||||
self.currentBox = None
|
||||
self.aspectRatio = aspectRatio
|
||||
self.drawingSurface.Refresh()
|
||||
|
||||
def getCurrentExtent(self):
|
||||
"""
|
||||
Return (x0, y0, x1, y1) or None if
|
||||
no drawing has yet been done.
|
||||
"""
|
||||
if not self.currentBox:
|
||||
extent = None
|
||||
else:
|
||||
extent = boxToExtent(self.currentBox)
|
||||
return extent
|
||||
|
||||
enabled = property(__isEnabled, __setEnabled, None, 'True if I am responding to mouse events')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = wxPySimpleApp()
|
||||
frame = wxFrame(None, -1, title='RubberBand Test', size=(300,300))
|
||||
|
||||
# Add a panel that the rubberband will work on.
|
||||
panel = wxPanel(frame, -1)
|
||||
panel.SetBackgroundColour(wxBLUE)
|
||||
|
||||
# Create the rubberband
|
||||
frame.rubberBand = RubberBand(drawingSurface=panel)
|
||||
frame.rubberBand.reset(aspectRatio=0.5)
|
||||
|
||||
# Add a button that creates a new rubberband
|
||||
def __newRubberBand(event):
|
||||
frame.rubberBand.reset()
|
||||
button = wxButton(frame, 100, 'Reset Rubberband')
|
||||
EVT_BUTTON(frame, 100, __newRubberBand)
|
||||
|
||||
# Layout the frame
|
||||
sizer = wxBoxSizer(wxVERTICAL)
|
||||
sizer.Add(panel, 1, wxEXPAND | wxALL, 5)
|
||||
sizer.Add(button, 0, wxALIGN_CENTER | wxALL, 5)
|
||||
frame.SetAutoLayout(1)
|
||||
frame.SetSizer(sizer)
|
||||
frame.Show(1)
|
||||
app.MainLoop()
|
||||
|
Reference in New Issue
Block a user