Forward port recent changes on the 2.8 branch to HEAD
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@46083 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -1766,7 +1766,7 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
TR_NO_LINES # don't draw lines at all
|
||||
TR_LINES_AT_ROOT # connect top-level nodes
|
||||
TR_TWIST_BUTTONS # draw mac-like twist buttons
|
||||
TR_SINGLE # single selection mode
|
||||
TR_SINGLE # single selection mode
|
||||
TR_MULTIPLE # can select multiple items
|
||||
TR_EXTENDED # todo: allow extended selection
|
||||
TR_HAS_VARIABLE_ROW_HEIGHT # allows rows to have variable height
|
||||
@@ -1870,12 +1870,13 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
self._vistaselection = False
|
||||
|
||||
# Connection lines style
|
||||
grey = (160,160,160)
|
||||
if wx.Platform != "__WXMAC__":
|
||||
self._dottedPen = wx.Pen("grey", 1, wx.USER_DASH)
|
||||
self._dottedPen = wx.Pen(grey, 1, wx.USER_DASH)
|
||||
self._dottedPen.SetDashes([1,1])
|
||||
self._dottedPen.SetCap(wx.CAP_BUTT)
|
||||
else:
|
||||
self._dottedPen = wx.Pen("grey", 1)
|
||||
self._dottedPen = wx.Pen(grey, 1)
|
||||
|
||||
# Pen Used To Draw The Border Around Selected Items
|
||||
self._borderPen = wx.BLACK_PEN
|
||||
@@ -1893,8 +1894,6 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
if major < 10:
|
||||
style |= TR_ROW_LINES
|
||||
|
||||
self._windowStyle = style
|
||||
|
||||
# Create the default check image list
|
||||
self.SetImageListCheck(13, 13)
|
||||
|
||||
@@ -2169,13 +2168,13 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
dc = wx.ClientDC(self)
|
||||
self.RefreshLine(item)
|
||||
|
||||
if self._windowStyle & TR_AUTO_CHECK_CHILD:
|
||||
if self.HasFlag(TR_AUTO_CHECK_CHILD):
|
||||
ischeck = self.IsItemChecked(item)
|
||||
self.AutoCheckChild(item, ischeck)
|
||||
if self._windowStyle & TR_AUTO_CHECK_PARENT:
|
||||
if self.HasFlag(TR_AUTO_CHECK_PARENT):
|
||||
ischeck = self.IsItemChecked(item)
|
||||
self.AutoCheckParent(item, ischeck)
|
||||
elif self._windowStyle & TR_AUTO_TOGGLE_CHILD:
|
||||
elif self.HasFlag(TR_AUTO_TOGGLE_CHILD):
|
||||
self.AutoToggleChild(item)
|
||||
|
||||
e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId())
|
||||
@@ -2310,12 +2309,6 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
self._dirty = True
|
||||
|
||||
|
||||
def HasFlag(self, flag):
|
||||
"""Returns whether CustomTreeCtrl has a flag."""
|
||||
|
||||
return self._windowStyle & flag
|
||||
|
||||
|
||||
def HasChildren(self, item):
|
||||
"""Returns whether an item has children or not."""
|
||||
|
||||
@@ -2349,19 +2342,19 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
# want to update the inherited styles, but right now
|
||||
# none of the parents has updatable styles
|
||||
|
||||
if self._windowStyle & TR_MULTIPLE and not (styles & TR_MULTIPLE):
|
||||
if self.HasFlag(TR_MULTIPLE) and not (styles & TR_MULTIPLE):
|
||||
selections = self.GetSelections()
|
||||
for select in selections[0:-1]:
|
||||
self.SelectItem(select, False)
|
||||
|
||||
self._windowStyle = styles
|
||||
self.SetWindowStyle(styles)
|
||||
self._dirty = True
|
||||
|
||||
|
||||
def GetTreeStyle(self):
|
||||
"""Returns the CustomTreeCtrl style."""
|
||||
|
||||
return self._windowStyle
|
||||
return self.GetWindowStyle()
|
||||
|
||||
|
||||
def HasButtons(self):
|
||||
@@ -3155,10 +3148,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
def DoInsertItem(self, parentId, previous, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
|
||||
"""Actually inserts an item in the tree."""
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if ct_type < 0 or ct_type > 2:
|
||||
@@ -3190,10 +3183,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
if self._anchor:
|
||||
raise Exception("\nERROR: Tree Can Have Only One Root")
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if ct_type < 0 or ct_type > 2:
|
||||
@@ -3226,10 +3219,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
def PrependItem(self, parent, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
|
||||
"""Appends an item as a first child of parent."""
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
return self.DoInsertItem(parent, 0, text, ct_type, wnd, image, selImage, data)
|
||||
@@ -3238,10 +3231,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
def InsertItemByItem(self, parentId, idPrevious, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
|
||||
"""Auxiliary function to cope with the C++ hideous multifunction."""
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
parent = parentId
|
||||
@@ -3264,10 +3257,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
def InsertItemByIndex(self, parentId, before, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
|
||||
"""Auxiliary function to cope with the C++ hideous multifunction."""
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
parent = parentId
|
||||
@@ -3282,10 +3275,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
def InsertItem(self, parentId, input, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
|
||||
"""Inserts an item after the given previous."""
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if type(input) == type(1):
|
||||
@@ -3297,10 +3290,10 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
def AppendItem(self, parentId, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
|
||||
"""Appends an item as a last child of its parent."""
|
||||
|
||||
if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if wnd is not None and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
if text.find("\n") >= 0 and not self.HasFlag(TR_HAS_VARIABLE_ROW_HEIGHT):
|
||||
raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
|
||||
|
||||
parent = parentId
|
||||
@@ -3562,7 +3555,8 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
for child in self._itemWithWindow:
|
||||
if not self.IsVisible(child):
|
||||
wnd = child.GetWindow()
|
||||
wnd.Hide()
|
||||
if wnd:
|
||||
wnd.Hide()
|
||||
|
||||
|
||||
def Unselect(self):
|
||||
@@ -4565,7 +4559,7 @@ class CustomTreeCtrl(wx.PyScrolledWindow):
|
||||
|
||||
else: # no custom buttons
|
||||
|
||||
if self._windowStyle & TR_TWIST_BUTTONS:
|
||||
if self.HasFlag(TR_TWIST_BUTTONS):
|
||||
# We draw something like the Mac twist buttons
|
||||
|
||||
dc.SetPen(wx.BLACK_PEN)
|
||||
|
File diff suppressed because it is too large
Load Diff
347
wxPython/wx/lib/floatcanvas/GUIMode.py
Normal file
347
wxPython/wx/lib/floatcanvas/GUIMode.py
Normal file
@@ -0,0 +1,347 @@
|
||||
"""
|
||||
|
||||
Module that holds the GUI modes used by FloatCanvas
|
||||
|
||||
|
||||
Note that this can only be imported after a wx.App() has been created.
|
||||
|
||||
This approach was inpired by Christian Blouin, who also wrote the initial
|
||||
version of the code.
|
||||
|
||||
"""
|
||||
|
||||
import wx
|
||||
## fixme: events should live in their own module, so all of FloatCanvas
|
||||
## wouldn't have to be imported here.
|
||||
import FloatCanvas, Resources
|
||||
import numpy as N
|
||||
|
||||
## create all the Cursors, so they don't need to be created each time.
|
||||
if "wxMac" in wx.PlatformInfo: # use 16X16 cursors for wxMac
|
||||
HandCursor = wx.CursorFromImage(Resources.getHand16Image())
|
||||
GrabHandCursor = wx.CursorFromImage(Resources.getGrabHand16Image())
|
||||
|
||||
img = Resources.getMagPlus16Image()
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6)
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6)
|
||||
MagPlusCursor = wx.CursorFromImage(img)
|
||||
|
||||
img = Resources.getMagMinus16Image()
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6)
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6)
|
||||
MagMinusCursor = wx.CursorFromImage(img)
|
||||
else: # use 24X24 cursors for GTK and Windows
|
||||
HandCursor = wx.CursorFromImage(Resources.getHandImage())
|
||||
GrabHandCursor = wx.CursorFromImage(Resources.getGrabHandImage())
|
||||
|
||||
img = Resources.getMagPlusImage()
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9)
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9)
|
||||
MagPlusCursor = wx.CursorFromImage(img)
|
||||
|
||||
img = Resources.getMagMinusImage()
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9)
|
||||
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9)
|
||||
MagMinusCursor = wx.CursorFromImage(img)
|
||||
|
||||
|
||||
class GUIBase:
|
||||
"""
|
||||
Basic Mouse mode and baseclass for other GUImode.
|
||||
|
||||
This one does nothing with any event
|
||||
|
||||
"""
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
Cursor = wx.NullCursor
|
||||
|
||||
# Handlers
|
||||
def OnLeftDown(self, event):
|
||||
pass
|
||||
def OnLeftUp(self, event):
|
||||
pass
|
||||
def OnLeftDouble(self, event):
|
||||
pass
|
||||
def OnRightDown(self, event):
|
||||
pass
|
||||
def OnRightUp(self, event):
|
||||
pass
|
||||
def OnRightDouble(self, event):
|
||||
pass
|
||||
def OnMiddleDown(self, event):
|
||||
pass
|
||||
def OnMiddleUp(self, event):
|
||||
pass
|
||||
def OnMiddleDouble(self, event):
|
||||
pass
|
||||
def OnWheel(self, event):
|
||||
pass
|
||||
def OnMove(self, event):
|
||||
pass
|
||||
|
||||
def UpdateScreen(self):
|
||||
"""
|
||||
Update gets called if the screen has been repainted in the middle of a zoom in
|
||||
so the Rubber Band Box can get updated
|
||||
"""
|
||||
pass
|
||||
|
||||
class GUIMouse(GUIBase):
|
||||
"""
|
||||
|
||||
Mouse mode checks for a hit test, and if nothing is hit,
|
||||
raises a FloatCanvas mouse event for each event.
|
||||
|
||||
"""
|
||||
|
||||
Cursor = wx.NullCursor
|
||||
|
||||
# Handlers
|
||||
def OnLeftDown(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_LEFT_DOWN
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnLeftUp(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_LEFT_UP
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnLeftDouble(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_LEFT_DCLICK
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnMiddleDown(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_MIDDLE_DOWN
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnMiddleUp(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_MIDDLE_UP
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnMiddleDouble(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_MIDDLE_DCLICK
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnRightDown(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_RIGHT_DOWN
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnRightUp(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_RIGHT_UP
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnRightDouble(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_RIGHT_DCLICK
|
||||
if not self.parent.HitTest(event, EventType):
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnWheel(self, event):
|
||||
EventType = FloatCanvas.EVT_FC_MOUSEWHEEL
|
||||
self.parent._RaiseMouseEvent(event, EventType)
|
||||
|
||||
def OnMove(self, event):
|
||||
## The Move event always gets raised, even if there is a hit-test
|
||||
self.parent.MouseOverTest(event)
|
||||
self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)
|
||||
|
||||
|
||||
class GUIMove(GUIBase):
|
||||
|
||||
Cursor = HandCursor
|
||||
GrabCursor = GrabHandCursor
|
||||
def __init__(self, parent):
|
||||
GUIBase.__init__(self, parent)
|
||||
self.StartMove = None
|
||||
self.PrevMoveXY = None
|
||||
|
||||
def OnLeftDown(self, event):
|
||||
self.parent.SetCursor(self.GrabCursor)
|
||||
self.parent.CaptureMouse()
|
||||
self.StartMove = N.array( event.GetPosition() )
|
||||
self.PrevMoveXY = (0,0)
|
||||
|
||||
def OnLeftUp(self, event):
|
||||
if self.StartMove is not None:
|
||||
StartMove = self.StartMove
|
||||
EndMove = N.array(event.GetPosition())
|
||||
DiffMove = StartMove-EndMove
|
||||
if N.sum(DiffMove**2) > 16:
|
||||
self.parent.MoveImage(DiffMove, 'Pixel')
|
||||
self.StartMove = None
|
||||
self.parent.SetCursor(self.Cursor)
|
||||
|
||||
def OnMove(self, event):
|
||||
# Allways raise the Move event.
|
||||
self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)
|
||||
if event.Dragging() and event.LeftIsDown() and not self.StartMove is None:
|
||||
xy1 = N.array( event.GetPosition() )
|
||||
wh = self.parent.PanelSize
|
||||
xy_tl = xy1 - self.StartMove
|
||||
dc = wx.ClientDC(self.parent)
|
||||
dc.BeginDrawing()
|
||||
x1,y1 = self.PrevMoveXY
|
||||
x2,y2 = xy_tl
|
||||
w,h = self.parent.PanelSize
|
||||
##fixme: This sure could be cleaner!
|
||||
if x2 > x1 and y2 > y1:
|
||||
xa = xb = x1
|
||||
ya = yb = y1
|
||||
wa = w
|
||||
ha = y2 - y1
|
||||
wb = x2- x1
|
||||
hb = h
|
||||
elif x2 > x1 and y2 <= y1:
|
||||
xa = x1
|
||||
ya = y1
|
||||
wa = x2 - x1
|
||||
ha = h
|
||||
xb = x1
|
||||
yb = y2 + h
|
||||
wb = w
|
||||
hb = y1 - y2
|
||||
elif x2 <= x1 and y2 > y1:
|
||||
xa = x1
|
||||
ya = y1
|
||||
wa = w
|
||||
ha = y2 - y1
|
||||
xb = x2 + w
|
||||
yb = y1
|
||||
wb = x1 - x2
|
||||
hb = h - y2 + y1
|
||||
elif x2 <= x1 and y2 <= y1:
|
||||
xa = x2 + w
|
||||
ya = y1
|
||||
wa = x1 - x2
|
||||
ha = h
|
||||
xb = x1
|
||||
yb = y2 + h
|
||||
wb = w
|
||||
hb = y1 - y2
|
||||
|
||||
dc.SetPen(wx.TRANSPARENT_PEN)
|
||||
dc.SetBrush(self.parent.BackgroundBrush)
|
||||
dc.DrawRectangle(xa, ya, wa, ha)
|
||||
dc.DrawRectangle(xb, yb, wb, hb)
|
||||
self.PrevMoveXY = xy_tl
|
||||
if self.parent._ForeDrawList:
|
||||
dc.DrawBitmapPoint(self.parent._ForegroundBuffer,xy_tl)
|
||||
else:
|
||||
dc.DrawBitmapPoint(self.parent._Buffer,xy_tl)
|
||||
dc.EndDrawing()
|
||||
|
||||
def OnWheel(self, event):
|
||||
"""
|
||||
By default, zoom in/out by a 0.1 factor per Wheel event.
|
||||
"""
|
||||
if event.GetWheelRotation() < 0:
|
||||
self.parent.Zoom(0.9)
|
||||
else:
|
||||
self.parent.Zoom(1.1)
|
||||
|
||||
class GUIZoomIn(GUIBase):
|
||||
|
||||
Cursor = MagPlusCursor
|
||||
|
||||
def __init__(self, parent):
|
||||
GUIBase.__init__(self, parent)
|
||||
self.StartRBBox = None
|
||||
self.PrevRBBox = None
|
||||
|
||||
def OnLeftDown(self, event):
|
||||
self.StartRBBox = N.array( event.GetPosition() )
|
||||
self.PrevRBBox = None
|
||||
self.parent.CaptureMouse()
|
||||
|
||||
def OnLeftUp(self, event):
|
||||
#if self.parent.HasCapture():
|
||||
# self.parent.ReleaseMouse()
|
||||
if event.LeftUp() and not self.StartRBBox is None:
|
||||
self.PrevRBBox = None
|
||||
EndRBBox = event.GetPosition()
|
||||
StartRBBox = self.StartRBBox
|
||||
# if mouse has moved less that ten pixels, don't use the box.
|
||||
if ( abs(StartRBBox[0] - EndRBBox[0]) > 10
|
||||
and abs(StartRBBox[1] - EndRBBox[1]) > 10 ):
|
||||
EndRBBox = self.parent.PixelToWorld(EndRBBox)
|
||||
StartRBBox = self.parent.PixelToWorld(StartRBBox)
|
||||
BB = N.array(((min(EndRBBox[0],StartRBBox[0]),
|
||||
min(EndRBBox[1],StartRBBox[1])),
|
||||
(max(EndRBBox[0],StartRBBox[0]),
|
||||
max(EndRBBox[1],StartRBBox[1]))),N.float_)
|
||||
self.parent.ZoomToBB(BB)
|
||||
else:
|
||||
Center = self.parent.PixelToWorld(StartRBBox)
|
||||
self.parent.Zoom(1.5,Center)
|
||||
self.StartRBBox = None
|
||||
|
||||
def OnMove(self, event):
|
||||
# Allways raise the Move event.
|
||||
self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)
|
||||
if event.Dragging() and event.LeftIsDown() and not (self.StartRBBox is None):
|
||||
xy0 = self.StartRBBox
|
||||
xy1 = N.array( event.GetPosition() )
|
||||
wh = abs(xy1 - xy0)
|
||||
wh[0] = max(wh[0], int(wh[1]*self.parent.AspectRatio))
|
||||
wh[1] = int(wh[0] / self.parent.AspectRatio)
|
||||
xy_c = (xy0 + xy1) / 2
|
||||
dc = wx.ClientDC(self.parent)
|
||||
dc.BeginDrawing()
|
||||
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
|
||||
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
||||
dc.SetLogicalFunction(wx.XOR)
|
||||
if self.PrevRBBox:
|
||||
dc.DrawRectanglePointSize(*self.PrevRBBox)
|
||||
self.PrevRBBox = ( xy_c - wh/2, wh )
|
||||
dc.DrawRectanglePointSize( *self.PrevRBBox )
|
||||
dc.EndDrawing()
|
||||
|
||||
def UpdateScreen(self):
|
||||
"""
|
||||
Update gets called if the screen has been repainted in the middle of a zoom in
|
||||
so the Rubber Band Box can get updated
|
||||
"""
|
||||
if self.PrevRBBox is not None:
|
||||
dc = wx.ClientDC(self.parent)
|
||||
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
|
||||
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
||||
dc.SetLogicalFunction(wx.XOR)
|
||||
dc.DrawRectanglePointSize(*self.PrevRBBox)
|
||||
|
||||
def OnRightDown(self, event):
|
||||
self.parent.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel")
|
||||
|
||||
def OnWheel(self, event):
|
||||
if event.GetWheelRotation() < 0:
|
||||
self.parent.Zoom(0.9)
|
||||
else:
|
||||
self.parent.Zoom(1.1)
|
||||
|
||||
class GUIZoomOut(GUIBase):
|
||||
|
||||
Cursor = MagMinusCursor
|
||||
|
||||
def OnLeftDown(self, event):
|
||||
self.parent.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel")
|
||||
|
||||
def OnRightDown(self, event):
|
||||
self.parent.Zoom(1.5, event.GetPosition(), centerCoords="pixel")
|
||||
|
||||
def OnWheel(self, event):
|
||||
if event.GetWheelRotation() < 0:
|
||||
self.parent.Zoom(0.9)
|
||||
else:
|
||||
self.parent.Zoom(1.1)
|
||||
|
||||
def OnMove(self, event):
|
||||
# Allways raise the Move event.
|
||||
self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)
|
||||
|
@@ -4,18 +4,9 @@ A Panel that includes the FloatCanvas and Navigation controls
|
||||
"""
|
||||
|
||||
import wx
|
||||
|
||||
import FloatCanvas, Resources
|
||||
|
||||
ID_ZOOM_IN_BUTTON = wx.NewId()
|
||||
ID_ZOOM_OUT_BUTTON = wx.NewId()
|
||||
ID_ZOOM_TO_FIT_BUTTON = wx.NewId()
|
||||
ID_MOVE_MODE_BUTTON = wx.NewId()
|
||||
ID_POINTER_BUTTON = wx.NewId()
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
class NavCanvas(wx.Panel):
|
||||
"""
|
||||
NavCanvas.py
|
||||
@@ -23,96 +14,80 @@ class NavCanvas(wx.Panel):
|
||||
This is a high level window that encloses the FloatCanvas in a panel
|
||||
and adds a Navigation toolbar.
|
||||
|
||||
Copyright: Christopher Barker)
|
||||
"""
|
||||
|
||||
License: Same as the version of wxPython you are using it with
|
||||
|
||||
Please let me know if you're using this!!!
|
||||
|
||||
Contact me at:
|
||||
|
||||
Chris.Barker@noaa.gov
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, parent, id = -1,
|
||||
size = wx.DefaultSize,
|
||||
**kwargs): # The rest just get passed into FloatCanvas
|
||||
|
||||
wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size)
|
||||
def __init__(self,
|
||||
parent,
|
||||
id = wx.ID_ANY,
|
||||
size = wx.DefaultSize,
|
||||
**kwargs): # The rest just get passed into FloatCanvas
|
||||
wx.Panel.__init__(self, parent, id, size=size)
|
||||
|
||||
self.BuildToolbar()
|
||||
## Create the vertical sizer for the toolbar and Panel
|
||||
box = wx.BoxSizer(wx.VERTICAL)
|
||||
box.Add(self.BuildToolbar(), 0, wx.ALL | wx.ALIGN_LEFT | wx.GROW, 4)
|
||||
|
||||
self.Canvas = FloatCanvas.FloatCanvas( self, wx.NewId(),
|
||||
size = wx.DefaultSize,
|
||||
**kwargs)
|
||||
box.Add(self.Canvas,1,wx.GROW)
|
||||
box.Add(self.ToolBar, 0, wx.ALL | wx.ALIGN_LEFT | wx.GROW, 4)
|
||||
|
||||
box.Fit(self)
|
||||
self.SetSizer(box)
|
||||
self.Canvas = FloatCanvas.FloatCanvas(self, **kwargs)
|
||||
box.Add(self.Canvas, 1, wx.GROW)
|
||||
|
||||
self.SetSizerAndFit(box)
|
||||
|
||||
|
||||
import GUIMode # here so that it doesn't get imported before wx.App()
|
||||
self.GUIZoomIn = GUIMode.GUIZoomIn(self.Canvas)
|
||||
self.GUIZoomOut = GUIMode.GUIZoomOut(self.Canvas)
|
||||
self.GUIMove = GUIMode.GUIMove(self.Canvas)
|
||||
self.GUIMouse = GUIMode.GUIMouse(self.Canvas)
|
||||
|
||||
# default to Mouse mode
|
||||
self.ToolBar.ToggleTool(ID_POINTER_BUTTON,1)
|
||||
self.Canvas.SetMode("Mouse")
|
||||
|
||||
self.ToolBar.ToggleTool(self.PointerTool.GetId(), True)
|
||||
self.Canvas.SetMode(self.GUIMouse)
|
||||
|
||||
return None
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Delegate all extra methods to the Canvas
|
||||
"""
|
||||
attrib = getattr(self.Canvas, name)
|
||||
## add the attribute to this module's dict for future calls
|
||||
self.__dict__[name] = attrib
|
||||
return attrib
|
||||
|
||||
def BuildToolbar(self):
|
||||
tb = wx.ToolBar(self,-1)
|
||||
tb = wx.ToolBar(self)
|
||||
self.ToolBar = tb
|
||||
|
||||
tb.SetToolBitmapSize((24,24))
|
||||
|
||||
tb.AddTool(ID_POINTER_BUTTON, Resources.getPointerBitmap(), isToggle=True, shortHelpString = "Pointer")
|
||||
wx.EVT_TOOL(self, ID_POINTER_BUTTON, self.SetToolMode)
|
||||
|
||||
tb.AddTool(ID_ZOOM_IN_BUTTON, Resources.getMagPlusBitmap(), isToggle=True, shortHelpString = "Zoom In")
|
||||
wx.EVT_TOOL(self, ID_ZOOM_IN_BUTTON, self.SetToolMode)
|
||||
|
||||
tb.AddTool(ID_ZOOM_OUT_BUTTON, Resources.getMagMinusBitmap(), isToggle=True, shortHelpString = "Zoom Out")
|
||||
wx.EVT_TOOL(self, ID_ZOOM_OUT_BUTTON, self.SetToolMode)
|
||||
|
||||
tb.AddTool(ID_MOVE_MODE_BUTTON, Resources.getHandBitmap(), isToggle=True, shortHelpString = "Move")
|
||||
wx.EVT_TOOL(self, ID_MOVE_MODE_BUTTON, self.SetToolMode)
|
||||
|
||||
self.PointerTool = tb.AddRadioTool(wx.ID_ANY, bitmap=Resources.getPointerBitmap(), shortHelp = "Pointer")
|
||||
self.Bind(wx.EVT_TOOL, lambda evt : self.SetMode(Mode=self.GUIMouse), self.PointerTool)
|
||||
|
||||
self.ZoomInTool = tb.AddRadioTool(wx.ID_ANY, bitmap=Resources.getMagPlusBitmap(), shortHelp = "Zoom In")
|
||||
self.Bind(wx.EVT_TOOL, lambda evt : self.SetMode(Mode=self.GUIZoomIn), self.ZoomInTool)
|
||||
|
||||
self.ZoomOutTool = tb.AddRadioTool(wx.ID_ANY, bitmap=Resources.getMagMinusBitmap(), shortHelp = "Zoom Out")
|
||||
self.Bind(wx.EVT_TOOL, lambda evt : self.SetMode(Mode=self.GUIZoomOut), self.ZoomOutTool)
|
||||
|
||||
self.MoveTool = tb.AddRadioTool(wx.ID_ANY, bitmap=Resources.getHandBitmap(), shortHelp = "Move")
|
||||
self.Bind(wx.EVT_TOOL, lambda evt : self.SetMode(Mode=self.GUIMove), self.MoveTool)
|
||||
|
||||
tb.AddSeparator()
|
||||
|
||||
tb.AddControl(wx.Button(tb, ID_ZOOM_TO_FIT_BUTTON, "Zoom To Fit",wx.DefaultPosition, wx.DefaultSize))
|
||||
wx.EVT_BUTTON(self, ID_ZOOM_TO_FIT_BUTTON, self.ZoomToFit)
|
||||
|
||||
self.ZoomButton = wx.Button(tb, label="Zoom To Fit")
|
||||
tb.AddControl(self.ZoomButton)
|
||||
self.ZoomButton.Bind(wx.EVT_BUTTON, self.ZoomToFit)
|
||||
|
||||
tb.Realize()
|
||||
S = tb.GetSize()
|
||||
tb.SetSizeHints(S[0],S[1])
|
||||
## fixme: remove this when the bug is fixed!
|
||||
wx.CallAfter(self.HideShowHack) # this required on wxPython 2.8.3 on OS-X
|
||||
|
||||
return tb
|
||||
|
||||
def SetToolMode(self,event):
|
||||
for id in [ID_ZOOM_IN_BUTTON,
|
||||
ID_ZOOM_OUT_BUTTON,
|
||||
ID_MOVE_MODE_BUTTON,
|
||||
ID_POINTER_BUTTON]:
|
||||
self.ToolBar.ToggleTool(id,0)
|
||||
self.ToolBar.ToggleTool(event.GetId(),1)
|
||||
if event.GetId() == ID_ZOOM_IN_BUTTON:
|
||||
self.Canvas.SetMode("ZoomIn")
|
||||
elif event.GetId() == ID_ZOOM_OUT_BUTTON:
|
||||
self.Canvas.SetMode("ZoomOut")
|
||||
elif event.GetId() == ID_MOVE_MODE_BUTTON:
|
||||
self.Canvas.SetMode("Move")
|
||||
elif event.GetId() == ID_POINTER_BUTTON:
|
||||
self.Canvas.SetMode("Mouse")
|
||||
def HideShowHack(self):
|
||||
##fixme: remove this when the bug is fixed!
|
||||
"""
|
||||
Hack to hide and show button on toolbar to get around OS-X bug on
|
||||
wxPython2.8 on OS-X
|
||||
"""
|
||||
self.ZoomButton.Hide()
|
||||
self.ZoomButton.Show()
|
||||
|
||||
def SetMode(self, Mode):
|
||||
self.Canvas.SetMode(Mode)
|
||||
|
||||
def ZoomToFit(self,Event):
|
||||
self.Canvas.ZoomToBB()
|
||||
self.Canvas.SetFocus() # Otherwise the focus stays on the Button, and wheel events are lost.
|
||||
|
||||
|
170
wxPython/wx/lib/floatcanvas/Utilities/BBox.py
Normal file
170
wxPython/wx/lib/floatcanvas/Utilities/BBox.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
A Bounding Box object and assorted utilities , subclassed from a numpy array
|
||||
|
||||
"""
|
||||
|
||||
import numpy as N
|
||||
|
||||
class BBox(N.ndarray):
|
||||
"""
|
||||
A Bounding Box object:
|
||||
|
||||
Takes Data as an array. Data is any python sequence that can be turned into a
|
||||
2x2 numpy array of floats:
|
||||
|
||||
[[MinX, MinY ],
|
||||
[MaxX, MaxY ]]
|
||||
|
||||
It is a subclass of numpy.ndarray, so for the most part it can be used as
|
||||
an array, and arrays that fit the above description can be used in its place.
|
||||
|
||||
Usually created by the factory functions:
|
||||
|
||||
asBBox
|
||||
|
||||
and
|
||||
|
||||
fromPoints
|
||||
|
||||
"""
|
||||
def __new__(subtype, data):
|
||||
"""
|
||||
Takes Data as an array. Data is any python sequence that can be turned into a
|
||||
2x2 numpy array of floats:
|
||||
|
||||
[[MinX, MinY ],
|
||||
[MaxX, MaxY ]]
|
||||
|
||||
You don't usually call this directly. BBox objects are created with the factory functions:
|
||||
|
||||
asBBox
|
||||
|
||||
and
|
||||
|
||||
fromPoints
|
||||
|
||||
"""
|
||||
arr = N.array(data, N.float)
|
||||
arr.shape = (2,2)
|
||||
if arr[0,0] > arr[1,0] or arr[0,1] > arr[1,1]:
|
||||
# note: zero sized BB OK.
|
||||
raise ValueError("BBox values not aligned: \n minimum values must be less that maximum values")
|
||||
return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
|
||||
|
||||
def Overlaps(self, BB):
|
||||
"""
|
||||
Overlap(BB):
|
||||
|
||||
Tests if the given Bounding Box overlaps with this one.
|
||||
Returns True is the Bounding boxes overlap, False otherwise
|
||||
If they are just touching, returns True
|
||||
"""
|
||||
|
||||
if ( (self[1,0] >= BB[0,0]) and (self[0,0] <= BB[1,0]) and
|
||||
(self[1,1] >= BB[0,1]) and (self[0,1] <= BB[1,1]) ):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def Inside(self, BB):
|
||||
"""
|
||||
Inside(BB):
|
||||
|
||||
Tests if the given Bounding Box is entirely inside this one.
|
||||
|
||||
Returns True if it is entirely inside, or touching the
|
||||
border.
|
||||
|
||||
Returns False otherwise
|
||||
"""
|
||||
if ( (BB[0,0] >= self[0,0]) and (BB[1,0] <= self[1,0]) and
|
||||
(BB[0,1] >= self[0,1]) and (BB[1,1] <= self[1,1]) ):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def Merge(self, BB):
|
||||
"""
|
||||
Joins this bounding box with the one passed in, maybe making this one bigger
|
||||
|
||||
"""
|
||||
|
||||
if BB[0,0] < self[0,0]: self[0,0] = BB[0,0]
|
||||
if BB[0,1] < self[0,1]: self[0,1] = BB[0,1]
|
||||
if BB[1,0] > self[1,0]: self[1,0] = BB[1,0]
|
||||
if BB[1,1] > self[1,1]: self[1,1] = BB[1,1]
|
||||
|
||||
### This could be used for a make BB from a bunch of BBs
|
||||
|
||||
#~ def _getboundingbox(bboxarray): # lrk: added this
|
||||
#~ # returns the bounding box of a bunch of bounding boxes
|
||||
#~ upperleft = N.minimum.reduce(bboxarray[:,0])
|
||||
#~ lowerright = N.maximum.reduce(bboxarray[:,1])
|
||||
#~ return N.array((upperleft, lowerright), N.float)
|
||||
#~ _getboundingbox = staticmethod(_getboundingbox)
|
||||
|
||||
|
||||
## Save the ndarray __eq__ for internal use.
|
||||
Array__eq__ = N.ndarray.__eq__
|
||||
def __eq__(self, BB):
|
||||
"""
|
||||
__eq__(BB) The equality operator
|
||||
|
||||
A == B if and only if all the entries are the same
|
||||
|
||||
"""
|
||||
return N.all(self.Array__eq__(BB))
|
||||
|
||||
|
||||
def asBBox(data):
|
||||
"""
|
||||
returns a BBox object.
|
||||
|
||||
If object is a BBox, it is returned unaltered
|
||||
|
||||
If object is a numpy array, a BBox object is returned that shares a
|
||||
view of the data with that array
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(data, BBox):
|
||||
return data
|
||||
arr = N.asarray(data, N.float)
|
||||
return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
|
||||
|
||||
def fromPoints(Points):
|
||||
"""
|
||||
fromPoints (Points).
|
||||
|
||||
reruns the bounding box of the set of points in Points. Points can
|
||||
be any python object that can be turned into a numpy NX2 array of Floats.
|
||||
|
||||
If a single point is passed in, a zero-size Bounding Box is returned.
|
||||
|
||||
"""
|
||||
Points = N.asarray(Points, N.float).reshape(-1,2)
|
||||
|
||||
arr = N.vstack( (Points.min(0), Points.max(0)) )
|
||||
return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
|
||||
|
||||
def fromBBArray(BBarray):
|
||||
"""
|
||||
Builds a BBox object from an array of Bounding Boxes.
|
||||
The resulting Bounding Box encompases all the included BBs.
|
||||
|
||||
The BBarray is in the shape: (Nx2x2) where BBarray[n] is a 2x2 array that represents a BBox
|
||||
"""
|
||||
|
||||
#upperleft = N.minimum.reduce(BBarray[:,0])
|
||||
#lowerright = N.maximum.reduce(BBarray[:,1])
|
||||
|
||||
# BBarray = N.asarray(BBarray, N.float).reshape(-1,2)
|
||||
# arr = N.vstack( (BBarray.min(0), BBarray.max(0)) )
|
||||
BBarray = N.asarray(BBarray, N.float).reshape(-1,2,2)
|
||||
arr = N.vstack( (BBarray[:,0,:].min(0), BBarray[:,1,:].max(0)) )
|
||||
return asBBox(arr)
|
||||
#return asBBox( (upperleft, lowerright) ) * 2
|
||||
|
||||
|
||||
|
||||
|
354
wxPython/wx/lib/floatcanvas/Utilities/BBoxTest.py
Normal file
354
wxPython/wx/lib/floatcanvas/Utilities/BBoxTest.py
Normal file
@@ -0,0 +1,354 @@
|
||||
|
||||
"""
|
||||
Test code for the BBox Object
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from BBox import *
|
||||
|
||||
class testCreator(unittest.TestCase):
|
||||
def testCreates(self):
|
||||
B = BBox(((0,0),(5,5)))
|
||||
self.failUnless(isinstance(B, BBox))
|
||||
|
||||
def testType(self):
|
||||
B = N.array(((0,0),(5,5)))
|
||||
self.failIf(isinstance(B, BBox))
|
||||
|
||||
def testDataType(self):
|
||||
B = BBox(((0,0),(5,5)))
|
||||
self.failUnless(B.dtype == N.float)
|
||||
|
||||
def testShape(self):
|
||||
B = BBox((0,0,5,5))
|
||||
self.failUnless(B.shape == (2,2))
|
||||
|
||||
def testShape2(self):
|
||||
self.failUnlessRaises(ValueError, BBox, (0,0,5) )
|
||||
|
||||
def testShape3(self):
|
||||
self.failUnlessRaises(ValueError, BBox, (0,0,5,6,7) )
|
||||
|
||||
def testArrayConstruction(self):
|
||||
A = N.array(((4,5),(10,12)), N.float_)
|
||||
B = BBox(A)
|
||||
self.failUnless(isinstance(B, BBox))
|
||||
|
||||
def testMinMax(self):
|
||||
self.failUnlessRaises(ValueError, BBox, (0,0,-1,6) )
|
||||
|
||||
def testMinMax2(self):
|
||||
self.failUnlessRaises(ValueError, BBox, (0,0,1,-6) )
|
||||
|
||||
def testMinMax(self):
|
||||
# OK to have a zero-sized BB
|
||||
B = BBox(((0,0),(0,5)))
|
||||
self.failUnless(isinstance(B, BBox))
|
||||
|
||||
def testMinMax2(self):
|
||||
# OK to have a zero-sized BB
|
||||
B = BBox(((10.0,-34),(10.0,-34.0)))
|
||||
self.failUnless(isinstance(B, BBox))
|
||||
|
||||
def testMinMax3(self):
|
||||
# OK to have a tiny BB
|
||||
B = BBox(((0,0),(1e-20,5)))
|
||||
self.failUnless(isinstance(B, BBox))
|
||||
|
||||
def testMinMax4(self):
|
||||
# Should catch tiny difference
|
||||
self.failUnlessRaises(ValueError, BBox, ((0,0), (-1e-20,5)) )
|
||||
|
||||
class testAsBBox(unittest.TestCase):
|
||||
|
||||
def testPassThrough(self):
|
||||
B = BBox(((0,0),(5,5)))
|
||||
C = asBBox(B)
|
||||
self.failUnless(B is C)
|
||||
|
||||
def testPassThrough2(self):
|
||||
B = (((0,0),(5,5)))
|
||||
C = asBBox(B)
|
||||
self.failIf(B is C)
|
||||
|
||||
def testPassArray(self):
|
||||
# Different data type
|
||||
A = N.array( (((0,0),(5,5))) )
|
||||
C = asBBox(A)
|
||||
self.failIf(A is C)
|
||||
|
||||
def testPassArray2(self):
|
||||
# same data type -- should be a view
|
||||
A = N.array( (((0,0),(5,5))), N.float_ )
|
||||
C = asBBox(A)
|
||||
A[0,0] = -10
|
||||
self.failUnless(C[0,0] == A[0,0])
|
||||
|
||||
class testIntersect(unittest.TestCase):
|
||||
|
||||
def testSame(self):
|
||||
B = BBox(((-23.5, 456),(56, 532.0)))
|
||||
C = BBox(((-23.5, 456),(56, 532.0)))
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testUpperLeft(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (0, 12),(10, 32.0) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testUpperRight(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (12, 12),(25, 32.0) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testLowerRight(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (12, 5),(25, 15) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testLowerLeft(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (-10, 5),(8.5, 15) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testBelow(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (-10, 5),(8.5, 9.2) ) )
|
||||
self.failIf(B.Overlaps(C) )
|
||||
|
||||
def testAbove(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (-10, 25.001),(8.5, 32) ) )
|
||||
self.failIf(B.Overlaps(C) )
|
||||
|
||||
def testLeft(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (4, 8),(4.95, 32) ) )
|
||||
self.failIf(B.Overlaps(C) )
|
||||
|
||||
def testRight(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (17.1, 8),(17.95, 32) ) )
|
||||
self.failIf(B.Overlaps(C) )
|
||||
|
||||
def testInside(self):
|
||||
B = BBox( ( (-15, -25),(-5, -10) ) )
|
||||
C = BBox( ( (-12, -22), (-6, -8) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testOutside(self):
|
||||
B = BBox( ( (-15, -25),(-5, -10) ) )
|
||||
C = BBox( ( (-17, -26), (3, 0) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testTouch(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (15, 8),(17.95, 32) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testCorner(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (15, 25),(17.95, 32) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testZeroSize(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (15, 25),(15, 25) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testZeroSize2(self):
|
||||
B = BBox( ( (5, 10),(5, 10) ) )
|
||||
C = BBox( ( (15, 25),(15, 25) ) )
|
||||
self.failIf(B.Overlaps(C) )
|
||||
|
||||
def testZeroSize3(self):
|
||||
B = BBox( ( (5, 10),(5, 10) ) )
|
||||
C = BBox( ( (0, 8),(10, 12) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
def testZeroSize4(self):
|
||||
B = BBox( ( (5, 1),(10, 25) ) )
|
||||
C = BBox( ( (8, 8),(8, 8) ) )
|
||||
self.failUnless(B.Overlaps(C) )
|
||||
|
||||
|
||||
|
||||
class testEquality(unittest.TestCase):
|
||||
def testSame(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
self.failUnless(B == C)
|
||||
|
||||
def testIdentical(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
self.failUnless(B == B)
|
||||
|
||||
def testNotSame(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = BBox( ( (1.0, 2.0), (5.0, 10.1) ) )
|
||||
self.failIf(B == C)
|
||||
|
||||
def testWithArray(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = N.array( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
self.failUnless(B == C)
|
||||
|
||||
def testWithArray2(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = N.array( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
self.failUnless(C == B)
|
||||
|
||||
def testWithArray2(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = N.array( ( (1.01, 2.0), (5.0, 10.0) ) )
|
||||
self.failIf(C == B)
|
||||
|
||||
class testInside(unittest.TestCase):
|
||||
def testSame(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
self.failUnless(B.Inside(C))
|
||||
|
||||
def testPoint(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = BBox( ( (3.0, 4.0), (3.0, 4.0) ) )
|
||||
self.failUnless(B.Inside(C))
|
||||
|
||||
def testPointOutside(self):
|
||||
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
C = BBox( ( (-3.0, 4.0), (0.10, 4.0) ) )
|
||||
self.failIf(B.Inside(C))
|
||||
|
||||
def testUpperLeft(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (0, 12),(10, 32.0) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testUpperRight(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (12, 12),(25, 32.0) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testLowerRight(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (12, 5),(25, 15) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testLowerLeft(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (-10, 5),(8.5, 15) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testBelow(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (-10, 5),(8.5, 9.2) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testAbove(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (-10, 25.001),(8.5, 32) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testLeft(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (4, 8),(4.95, 32) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
def testRight(self):
|
||||
B = BBox( ( (5, 10),(15, 25) ) )
|
||||
C = BBox( ( (17.1, 8),(17.95, 32) ) )
|
||||
self.failIf(B.Inside(C) )
|
||||
|
||||
class testFromPoints(unittest.TestCase):
|
||||
|
||||
def testCreate(self):
|
||||
Pts = N.array( ((5,2),
|
||||
(3,4),
|
||||
(1,6),
|
||||
), N.float_ )
|
||||
B = fromPoints(Pts)
|
||||
#B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
|
||||
self.failUnless(B[0,0] == 1.0 and
|
||||
B[0,1] == 2.0 and
|
||||
B[1,0] == 5.0 and
|
||||
B[1,1] == 6.0
|
||||
)
|
||||
def testCreateInts(self):
|
||||
Pts = N.array( ((5,2),
|
||||
(3,4),
|
||||
(1,6),
|
||||
) )
|
||||
B = fromPoints(Pts)
|
||||
self.failUnless(B[0,0] == 1.0 and
|
||||
B[0,1] == 2.0 and
|
||||
B[1,0] == 5.0 and
|
||||
B[1,1] == 6.0
|
||||
)
|
||||
|
||||
def testSinglePoint(self):
|
||||
Pts = N.array( (5,2), N.float_ )
|
||||
B = fromPoints(Pts)
|
||||
self.failUnless(B[0,0] == 5.0 and
|
||||
B[0,1] == 2.0 and
|
||||
B[1,0] == 5.0 and
|
||||
B[1,1] == 2.0
|
||||
)
|
||||
|
||||
def testListTuples(self):
|
||||
Pts = [ (3, 6.5),
|
||||
(13, 43.2),
|
||||
(-4.32, -4),
|
||||
(65, -23),
|
||||
(-0.0001, 23.432),
|
||||
]
|
||||
B = fromPoints(Pts)
|
||||
self.failUnless(B[0,0] == -4.32 and
|
||||
B[0,1] == -23.0 and
|
||||
B[1,0] == 65.0 and
|
||||
B[1,1] == 43.2
|
||||
)
|
||||
class testMerge(unittest.TestCase):
|
||||
A = BBox( ((-23.5, 456), (56, 532.0)) )
|
||||
B = BBox( ((-20.3, 460), (54, 465 )) )# B should be completely inside A
|
||||
C = BBox( ((-23.5, 456), (58, 540.0)) )# up and to the right or A
|
||||
D = BBox( ((-26.5, 12), (56, 532.0)) )
|
||||
|
||||
def testInside(self):
|
||||
C = self.A.copy()
|
||||
C.Merge(self.B)
|
||||
self.failUnless(C == self.A)
|
||||
|
||||
def testFullOutside(self):
|
||||
C = self.B.copy()
|
||||
C.Merge(self.A)
|
||||
self.failUnless(C == self.A)
|
||||
|
||||
def testUpRight(self):
|
||||
A = self.A.copy()
|
||||
A.Merge(self.C)
|
||||
self.failUnless(A[0] == self.A[0] and A[1] == self.C[1])
|
||||
|
||||
def testDownLeft(self):
|
||||
A = self.A.copy()
|
||||
A.Merge(self.D)
|
||||
self.failUnless(A[0] == self.D[0] and A[1] == self.A[1])
|
||||
|
||||
class testBBarray(unittest.TestCase):
|
||||
BBarray = N.array( ( ((-23.5, 456), (56, 532.0)),
|
||||
((-20.3, 460), (54, 465 )),
|
||||
((-23.5, 456), (58, 540.0)),
|
||||
((-26.5, 12), (56, 532.0)),
|
||||
),
|
||||
dtype=N.float)
|
||||
print BBarray
|
||||
BB = asBBox( ((-26.5, 12.), ( 58. , 540.)) )
|
||||
|
||||
def testJoin(self):
|
||||
BB = fromBBArray(self.BBarray)
|
||||
self.failUnless(BB == self.BB, "Wrong BB was created. It was:\n%s \nit should have been:\n%s"%(BB, self.BB))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
115
wxPython/wx/lib/floatcanvas/Utilities/GUI.py
Normal file
115
wxPython/wx/lib/floatcanvas/Utilities/GUI.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
|
||||
Part of the floatcanvas.Utilities package.
|
||||
|
||||
This module contains assorted GUI-related utilities that can be used
|
||||
with FloatCanvas
|
||||
|
||||
So far, they are:
|
||||
|
||||
RubberBandBox: used to draw a RubberBand Box on the screen
|
||||
|
||||
"""
|
||||
import wx
|
||||
from floatcanvas import FloatCanvas
|
||||
|
||||
class RubberBandBox:
|
||||
"""
|
||||
Class to provide a rubber band box that can be drawn on a Window
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, Canvas, CallBack, Tol=5):
|
||||
|
||||
"""
|
||||
To initialize:
|
||||
|
||||
RubberBandBox(Canvas, CallBack)
|
||||
|
||||
Canvas: the FloatCanvas you want the Rubber band box to be used on
|
||||
|
||||
CallBack: is the method you want called when the mouse is
|
||||
released. That method will be called, passing in a rect
|
||||
parameter, where rect is: (Point, WH) of the rect in
|
||||
world coords.
|
||||
|
||||
Tol: The tolerance for the smallest rectangle allowed. defaults
|
||||
to 5. In pixels
|
||||
|
||||
Methods:
|
||||
|
||||
Enable() : Enables the Rubber Band Box (Binds the events)
|
||||
|
||||
Disable() : Enables the Rubber Band Box (Unbinds the events)
|
||||
|
||||
Attributes:
|
||||
|
||||
CallBack: The callback function, if it's replaced you need to
|
||||
call Enable() again.
|
||||
|
||||
"""
|
||||
|
||||
self.Canvas = Canvas
|
||||
self.CallBack = CallBack
|
||||
self.Tol = Tol
|
||||
|
||||
self.Drawing = False
|
||||
self.RBRect = None
|
||||
self.StartPointWorld = None
|
||||
|
||||
return None
|
||||
|
||||
def Enable(self):
|
||||
"""
|
||||
Called when you want the rubber band box to be enabled
|
||||
|
||||
"""
|
||||
|
||||
# bind events:
|
||||
self.Canvas.Bind(FloatCanvas.EVT_MOTION, self.OnMove )
|
||||
self.Canvas.Bind(FloatCanvas.EVT_LEFT_DOWN, self.OnLeftDown)
|
||||
self.Canvas.Bind(FloatCanvas.EVT_LEFT_UP, self.OnLeftUp )
|
||||
|
||||
def Disable(self):
|
||||
"""
|
||||
Called when you don't want the rubber band box to be enabled
|
||||
|
||||
"""
|
||||
|
||||
# unbind events:
|
||||
self.Canvas.Unbind(FloatCanvas.EVT_MOTION)
|
||||
self.Canvas.Unbind(FloatCanvas.EVT_LEFT_DOWN)
|
||||
self.Canvas.Unbind(FloatCanvas.EVT_LEFT_UP)
|
||||
|
||||
def OnMove(self, event):
|
||||
if self.Drawing:
|
||||
x, y = self.StartPoint
|
||||
Cornerx, Cornery = event.GetPosition()
|
||||
w, h = ( Cornerx - x, Cornery - y)
|
||||
if abs(w) > self.Tol and abs(h) > self.Tol:
|
||||
# draw the RB box
|
||||
dc = wx.ClientDC(self.Canvas)
|
||||
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
|
||||
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
||||
dc.SetLogicalFunction(wx.XOR)
|
||||
if self.RBRect:
|
||||
dc.DrawRectangle(*self.RBRect)
|
||||
self.RBRect = (x, y, w, h )
|
||||
dc.DrawRectangle(*self.RBRect)
|
||||
event.Skip() # skip so that other events can catch these
|
||||
|
||||
def OnLeftDown(self, event):
|
||||
# Start drawing
|
||||
self.Drawing = True
|
||||
self.StartPoint = event.GetPosition()
|
||||
self.StartPointWorld = event.Coords
|
||||
|
||||
def OnLeftUp(self, event):
|
||||
# Stop Drawing
|
||||
if self.Drawing:
|
||||
self.Drawing = False
|
||||
if self.RBRect:
|
||||
WH = event.Coords - self.StartPointWorld
|
||||
wx.CallAfter(self.CallBack, (self.StartPointWorld, WH))
|
||||
self.RBRect = None
|
||||
self.StartPointWorld = None
|
7
wxPython/wx/lib/floatcanvas/Utilities/__init__.py
Normal file
7
wxPython/wx/lib/floatcanvas/Utilities/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
__init__ for the floatcanvas Utilities package
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@@ -29,8 +29,7 @@ It is double buffered, so re-draws after the window is uncovered by
|
||||
something else are very quick.
|
||||
|
||||
It relies on NumPy, which is needed for speed (maybe, I haven't profiled
|
||||
it). It will also use numarray, if you don't have Numeric, but it is
|
||||
slower.
|
||||
properly) and convenience.
|
||||
|
||||
Bugs and Limitations: Lots: patches, fixes welcome
|
||||
|
||||
@@ -60,12 +59,6 @@ If you are zoomed in, it checks the Bounding box of an object before
|
||||
drawing it. This makes it a great deal faster when there are a lot of
|
||||
objects and you are zoomed in so that only a few are shown.
|
||||
|
||||
One solution is to be able to pass some sort of object set to the DC
|
||||
directly. I've used DC.DrawPointList(Points), and it helped a lot with
|
||||
drawing lots of points. However, when zoomed in, the Bounding boxes need
|
||||
to be checked, so I may some day write C++ code that does the loop and
|
||||
checks the BBs.
|
||||
|
||||
Mouse Events:
|
||||
|
||||
There are a full set of custom mouse events. They are just like the
|
||||
@@ -80,19 +73,25 @@ clicked, mouse-over'd, etc.
|
||||
See the Demo for what it can do, and how to use it.
|
||||
|
||||
Copyright: Christopher Barker
|
||||
|
||||
License: Same as the version of wxPython you are using it with.
|
||||
|
||||
TRAC site for some docs and updates:
|
||||
http://morticia.cs.dal.ca/FloatCanvas/
|
||||
|
||||
SVN for latest code:
|
||||
svn://morticia.cs.dal.ca/FloatCanvas
|
||||
|
||||
Mailing List:
|
||||
http://mail.mithis.com/cgi-bin/mailman/listinfo/floatcanvas
|
||||
|
||||
Check for updates or answers to questions, send me an email.
|
||||
|
||||
Please let me know if you're using this!!!
|
||||
|
||||
Contact me at:
|
||||
|
||||
Chris.Barker@noaa.gov
|
||||
|
||||
"""
|
||||
|
||||
__version__ = "0.9.10"
|
||||
__version__ = "0.9.18"
|
||||
|
||||
|
||||
|
@@ -95,7 +95,10 @@ class ImageView(wx.Window):
|
||||
if image is None:
|
||||
return
|
||||
|
||||
bmp = image.ConvertToBitmap()
|
||||
try:
|
||||
bmp = image.ConvertToBitmap()
|
||||
except:
|
||||
return
|
||||
|
||||
iwidth = bmp.GetWidth() # dimensions of image file
|
||||
iheight = bmp.GetHeight()
|
||||
|
@@ -221,6 +221,8 @@ class InspectionFrame(wx.Frame):
|
||||
|
||||
def OnClose(self, evt):
|
||||
self.SaveSettings(self.config)
|
||||
self.mgr.UnInit()
|
||||
del self.mgr
|
||||
evt.Skip()
|
||||
|
||||
|
||||
@@ -533,6 +535,9 @@ class InspectionInfoPanel(wx.stc.StyledTextCtrl):
|
||||
|
||||
|
||||
def FmtSizerItem(self, obj):
|
||||
if obj is None:
|
||||
return ['SizerItem: None']
|
||||
|
||||
st = ['SizerItem:']
|
||||
st.append(self.Fmt('proportion', obj.GetProportion()))
|
||||
st.append(self.Fmt('flag',
|
||||
|
@@ -47,6 +47,30 @@ class MaskedComboBoxSelectEvent(wx.PyCommandEvent):
|
||||
this event was generated."""
|
||||
return self.__selection
|
||||
|
||||
class MaskedComboBoxEventHandler(wx.EvtHandler):
|
||||
"""
|
||||
This handler ensures that the derived control can react to events
|
||||
from the base control before any external handlers run, to ensure
|
||||
proper behavior.
|
||||
"""
|
||||
def __init__(self, combobox):
|
||||
wx.EvtHandler.__init__(self)
|
||||
self.combobox = combobox
|
||||
combobox.PushEventHandler(self)
|
||||
self.Bind(wx.EVT_SET_FOCUS, self.combobox._OnFocus ) ## defeat automatic full selection
|
||||
self.Bind(wx.EVT_KILL_FOCUS, self.combobox._OnKillFocus ) ## run internal validator
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.combobox._OnDoubleClick) ## select field under cursor on dclick
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.combobox._OnContextMenu ) ## bring up an appropriate context menu
|
||||
self.Bind(wx.EVT_CHAR, self.combobox._OnChar ) ## handle each keypress
|
||||
self.Bind(wx.EVT_KEY_DOWN, self.combobox._OnKeyDownInComboBox ) ## for special processing of up/down keys
|
||||
self.Bind(wx.EVT_KEY_DOWN, self.combobox._OnKeyDown ) ## for processing the rest of the control keys
|
||||
## (next in evt chain)
|
||||
self.Bind(wx.EVT_COMBOBOX, self.combobox._OnDropdownSelect ) ## to bring otherwise completely independent base
|
||||
## ctrl selection into maskededit framework
|
||||
self.Bind(wx.EVT_TEXT, self.combobox._OnTextChange ) ## color control appropriately & keep
|
||||
## track of previous value for undo
|
||||
|
||||
|
||||
|
||||
class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
"""
|
||||
@@ -152,18 +176,15 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
self._SetKeycodeHandler(wx.WXK_UP, self._OnSelectChoice)
|
||||
self._SetKeycodeHandler(wx.WXK_DOWN, self._OnSelectChoice)
|
||||
|
||||
self.replace_next_combobox_event = False
|
||||
self.correct_selection = -1
|
||||
|
||||
if setupEventHandling:
|
||||
## Setup event handlers
|
||||
self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
|
||||
self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick) ## select field under cursor on dclick
|
||||
self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu ) ## bring up an appropriate context menu
|
||||
self.Bind(wx.EVT_CHAR, self._OnChar ) ## handle each keypress
|
||||
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDownInComboBox ) ## for special processing of up/down keys
|
||||
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## for processing the rest of the control keys
|
||||
## (next in evt chain)
|
||||
self.Bind(wx.EVT_TEXT, self._OnTextChange ) ## color control appropriately & keep
|
||||
## track of previous value for undo
|
||||
## Setup event handling functions through event handler object,
|
||||
## to guarantee processing prior to giving event callbacks from
|
||||
## outside the class:
|
||||
self.evt_handler = MaskedComboBoxEventHandler(self)
|
||||
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnWindowDestroy )
|
||||
|
||||
|
||||
|
||||
@@ -171,6 +192,13 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
return "<MaskedComboBox: %s>" % self.GetValue()
|
||||
|
||||
|
||||
def OnWindowDestroy(self, event):
|
||||
# clean up associated event handler object:
|
||||
if self.RemoveEventHandler(self.evt_handler):
|
||||
self.evt_handler.Destroy()
|
||||
event.Skip()
|
||||
|
||||
|
||||
def _CalcSize(self, size=None):
|
||||
"""
|
||||
Calculate automatic size if allowed; augment base mixin function
|
||||
@@ -252,7 +280,9 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
# Record current selection and insertion point, for undo
|
||||
self._prevSelection = self._GetSelection()
|
||||
self._prevInsertionPoint = self._GetInsertionPoint()
|
||||
## dbg('MaskedComboBox::_SetValue(%s), selection beforehand: %d' % (value, self.GetSelection()))
|
||||
wx.ComboBox.SetValue(self, value)
|
||||
## dbg('MaskedComboBox::_SetValue(%s), selection now: %d' % (value, self.GetSelection()))
|
||||
# text change events don't always fire, so we check validity here
|
||||
# to make certain formatting is applied:
|
||||
self._CheckValid()
|
||||
@@ -264,11 +294,14 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
masked control. NOTE: this must be done in the class derived
|
||||
from the base wx control.
|
||||
"""
|
||||
## dbg('MaskedComboBox::SetValue(%s)' % value, indent=1)
|
||||
if not self._mask:
|
||||
wx.ComboBox.SetValue(value) # revert to base control behavior
|
||||
## dbg('no mask; deferring to base class', indent=0)
|
||||
return
|
||||
# else...
|
||||
# empty previous contents, replacing entire value:
|
||||
## dbg('MaskedComboBox::SetValue: selection beforehand: %d' % (self.GetSelection()))
|
||||
self._SetInsertionPoint(0)
|
||||
self._SetSelection(0, self._masklength)
|
||||
|
||||
@@ -306,15 +339,26 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
dateparts = value.split(' ')
|
||||
dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
|
||||
value = string.join(dateparts, ' ')
|
||||
## dbg('adjusted value: "%s"' % value)
|
||||
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
|
||||
else:
|
||||
raise
|
||||
## dbg('adjusted value: "%s"' % value)
|
||||
|
||||
self._SetValue(value)
|
||||
#### dbg('queuing insertion after .SetValue', replace_to)
|
||||
wx.CallAfter(self._SetInsertionPoint, replace_to)
|
||||
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
||||
# Attempt to compensate for fact that calling .SetInsertionPoint() makes the
|
||||
# selection index -1, even if the resulting set value is in the list.
|
||||
# So, if we are setting a value that's in the list, use index selection instead.
|
||||
if value in self._choices:
|
||||
index = self._choices.index(value)
|
||||
self._prevValue = self._curValue
|
||||
self._curValue = self._choices[index]
|
||||
self._ctrl_constraints._autoCompleteIndex = index
|
||||
self.SetSelection(index)
|
||||
else:
|
||||
self._SetValue(value)
|
||||
#### dbg('queuing insertion after .SetValue', replace_to)
|
||||
wx.CallAfter(self._SetInsertionPoint, replace_to)
|
||||
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
||||
## dbg(indent=0)
|
||||
|
||||
|
||||
def _Refresh(self):
|
||||
@@ -509,26 +553,60 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
Necessary override for bookkeeping on choice selection, to keep current value
|
||||
current.
|
||||
"""
|
||||
## dbg('MaskedComboBox::SetSelection(%d)' % index)
|
||||
## dbg('MaskedComboBox::SetSelection(%d)' % index, indent=1)
|
||||
if self._mask:
|
||||
self._prevValue = self._curValue
|
||||
self._curValue = self._choices[index]
|
||||
self._ctrl_constraints._autoCompleteIndex = index
|
||||
if index != -1:
|
||||
self._curValue = self._choices[index]
|
||||
else:
|
||||
self._curValue = None
|
||||
wx.ComboBox.SetSelection(self, index)
|
||||
## dbg('selection now: %d' % self.GetCurrentSelection(), indent=0)
|
||||
|
||||
|
||||
def _OnKeyDownInComboBox(self, event):
|
||||
"""
|
||||
This function is necessary because navigation and control key
|
||||
events do not seem to normally be seen by the wxComboBox's
|
||||
EVT_CHAR routine. (Tabs don't seem to be visible no matter
|
||||
what... {:-( )
|
||||
This function is necessary because navigation and control key events
|
||||
do not seem to normally be seen by the wxComboBox's EVT_CHAR routine.
|
||||
(Tabs don't seem to be visible no matter what, except for CB_READONLY
|
||||
controls, for some bizarre reason... {:-( )
|
||||
"""
|
||||
key = event.GetKeyCode()
|
||||
## dbg('MaskedComboBox::OnKeyDownInComboBox(%d)' % key)
|
||||
if event.GetKeyCode() in self._nav + self._control:
|
||||
self._OnChar(event)
|
||||
return
|
||||
if not self._IsEditable():
|
||||
# WANTS_CHARS with CB_READONLY apparently prevents navigation on WXK_TAB;
|
||||
# ensure we can still navigate properly, as maskededit mixin::OnChar assumes
|
||||
# that event.Skip() will just work, but it doesn't:
|
||||
if self._keyhandlers.has_key(key):
|
||||
self._keyhandlers[key](event)
|
||||
# else pass
|
||||
else:
|
||||
## dbg('calling OnChar()')
|
||||
self._OnChar(event)
|
||||
else:
|
||||
event.Skip() # let mixin default KeyDown behavior occur
|
||||
## dbg(indent=0)
|
||||
|
||||
|
||||
def _OnDropdownSelect(self, event):
|
||||
"""
|
||||
This function appears to be necessary because dropdown selection seems to
|
||||
manipulate the contents of the control in an inconsistent way, properly
|
||||
changing the selection index, but *not* the value. (!) Calling SetSelection()
|
||||
on a selection event for the same selection would seem like a nop, but it seems to
|
||||
fix the problem.
|
||||
"""
|
||||
## dbg('MaskedComboBox::OnDropdownSelect(%d)' % event.GetSelection(), indent=1)
|
||||
if self.replace_next_combobox_event:
|
||||
## dbg('replacing EVT_COMBOBOX')
|
||||
self.replace_next_combobox_event = False
|
||||
self._OnAutoSelect(self._ctrl_constraints, self.correct_selection)
|
||||
else:
|
||||
## dbg('skipping EVT_COMBOBOX')
|
||||
event.Skip()
|
||||
## dbg(indent=0)
|
||||
|
||||
|
||||
def _OnSelectChoice(self, event):
|
||||
@@ -585,7 +663,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
Override mixin (empty) autocomplete handler, so that autocompletion causes
|
||||
combobox to update appropriately.
|
||||
"""
|
||||
## dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1)
|
||||
## dbg('MaskedComboBox::OnAutoSelect(%d, %d)' % (field._index, match_index), indent=1)
|
||||
## field._autoCompleteIndex = match_index
|
||||
if field == self._ctrl_constraints:
|
||||
self.SetSelection(match_index)
|
||||
@@ -594,7 +672,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
|
||||
self._CheckValid()
|
||||
## dbg('field._autoCompleteIndex:', match_index)
|
||||
## dbg('self.GetSelection():', self.GetSelection())
|
||||
## dbg('self.GetCurrentSelection():', self.GetCurrentSelection())
|
||||
end = self._goEnd(getPosOnly=True)
|
||||
## dbg('scheduling set of end position to:', end)
|
||||
# work around bug in wx 2.5
|
||||
@@ -614,15 +692,24 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||
item in the list. (and then does the usual OnReturn bit.)
|
||||
"""
|
||||
## dbg('MaskedComboBox::OnReturn', indent=1)
|
||||
## dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection())
|
||||
if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
|
||||
wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
|
||||
|
||||
## dbg('current value: "%s"' % self.GetValue(), 'current selection:', self.GetCurrentSelection())
|
||||
if self.GetCurrentSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
|
||||
## dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
|
||||
## wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
|
||||
self.replace_next_combobox_event = True
|
||||
self.correct_selection = self._ctrl_constraints._autoCompleteIndex
|
||||
event.m_keyCode = wx.WXK_TAB
|
||||
event.Skip()
|
||||
## dbg(indent=0)
|
||||
|
||||
|
||||
def _LostFocus(self):
|
||||
## dbg('MaskedComboBox::LostFocus; Selection=%d, value="%s"' % (self.GetSelection(), self.GetValue()))
|
||||
if self.GetCurrentSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
|
||||
## dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
|
||||
wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
|
||||
|
||||
|
||||
class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
|
||||
"""
|
||||
The "user-visible" masked combobox control, this class is
|
||||
@@ -659,6 +746,19 @@ class PreMaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
|
||||
__i = 0
|
||||
## CHANGELOG:
|
||||
## ====================
|
||||
## Version 1.4
|
||||
## 1. Added handler for EVT_COMBOBOX to address apparently inconsistent behavior
|
||||
## of control when the dropdown control is used to do a selection.
|
||||
## NOTE: due to misbehavior of wx.ComboBox re: losing all concept of the
|
||||
## current selection index if SetInsertionPoint() is called, which is required
|
||||
## to support masked .SetValue(), this control is flaky about retaining selection
|
||||
## information. I can't truly fix this without major changes to the base control,
|
||||
## but I've tried to compensate as best I can.
|
||||
## TODO: investigate replacing base control with ComboCtrl instead...
|
||||
## 2. Fixed navigation in readonly masked combobox, which was not working because
|
||||
## the base control doesn't do navigation if style=CB_READONLY|WANTS_CHARS.
|
||||
##
|
||||
##
|
||||
## Version 1.3
|
||||
## 1. Made definition of "hack" GetMark conditional on base class not
|
||||
## implementing it properly, to allow for migration in wx code base
|
||||
|
@@ -1,12 +1,12 @@
|
||||
#----------------------------------------------------------------------------
|
||||
# Name: maskededit.py
|
||||
# Authors: Jeff Childers, Will Sadkin
|
||||
# Email: jchilders_98@yahoo.com, wsadkin@parlancecorp.com
|
||||
# Authors: Will Sadkin, Jeff Childers
|
||||
# Email: wsadkin@parlancecorp.com, jchilders_98@yahoo.com
|
||||
# Created: 02/11/2003
|
||||
# Copyright: (c) 2003 by Jeff Childers, Will Sadkin, 2003
|
||||
# Portions: (c) 2002 by Will Sadkin, 2002-2006
|
||||
# Portions: (c) 2002 by Will Sadkin, 2002-2007
|
||||
# RCS-ID: $Id$
|
||||
# License: wxWindows license
|
||||
# License: wxWidgets license
|
||||
#----------------------------------------------------------------------------
|
||||
# NOTE:
|
||||
# MaskedEdit controls are based on a suggestion made on [wxPython-Users] by
|
||||
@@ -27,7 +27,7 @@
|
||||
#
|
||||
#----------------------------------------------------------------------------
|
||||
#
|
||||
# 03/30/2004 - Will Sadkin (wsadkin@nameconnector.com)
|
||||
# 03/30/2004 - Will Sadkin (wsadkin@parlancecorp.com)
|
||||
#
|
||||
# o Split out TextCtrl, ComboBox and IpAddrCtrl into their own files,
|
||||
# o Reorganized code into masked package
|
||||
@@ -337,7 +337,18 @@ to individual fields:
|
||||
raiseOnInvalidPaste False by default; normally a bad paste simply is ignored with a bell;
|
||||
if True, this will cause a ValueError exception to be thrown,
|
||||
with the .value attribute of the exception containing the bad value.
|
||||
===================== ==================================================================
|
||||
|
||||
stopFieldChangeIfInvalid
|
||||
False by default; tries to prevent navigation out of a field if its
|
||||
current value is invalid. Can be used to create a hybrid of validation
|
||||
settings, allowing intermediate invalid values in a field without
|
||||
sacrificing ability to limit values as with validRequired.
|
||||
NOTE: It is possible to end up with an invalid value when using
|
||||
this option if focus is switched to some other control via mousing.
|
||||
To avoid this, consider deriving a class that defines _LostFocus()
|
||||
function that returns the control to a valid value when the focus
|
||||
shifts. (AFAICT, The change in focus is unpreventable.)
|
||||
===================== =================================================================
|
||||
|
||||
|
||||
Coloring Behavior
|
||||
@@ -1327,12 +1338,14 @@ class Field:
|
||||
'emptyInvalid': False, ## Set to True to make EMPTY = INVALID
|
||||
'description': "", ## primarily for autoformats, but could be useful elsewhere
|
||||
'raiseOnInvalidPaste': False, ## if True, paste into field will cause ValueError
|
||||
'stopFieldChangeIfInvalid': False,## if True, disallow field navigation out of invalid field
|
||||
}
|
||||
|
||||
# This list contains all parameters that when set at the control level should
|
||||
# propagate down to each field:
|
||||
propagating_params = ('fillChar', 'groupChar', 'decimalChar','useParensForNegatives',
|
||||
'compareNoCase', 'emptyInvalid', 'validRequired', 'raiseOnInvalidPaste')
|
||||
'compareNoCase', 'emptyInvalid', 'validRequired', 'raiseOnInvalidPaste',
|
||||
'stopFieldChangeIfInvalid')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
@@ -3021,7 +3034,7 @@ class MaskedEditMixin:
|
||||
char = char.decode(self._defaultEncoding)
|
||||
else:
|
||||
char = unichr(event.GetUnicodeKey())
|
||||
dbg('unicode char:', char)
|
||||
## dbg('unicode char:', char)
|
||||
excludes = u''
|
||||
if type(field._excludeChars) != types.UnicodeType:
|
||||
excludes += field._excludeChars.decode(self._defaultEncoding)
|
||||
@@ -3767,6 +3780,21 @@ class MaskedEditMixin:
|
||||
## dbg(indent=0)
|
||||
return False
|
||||
|
||||
field = self._FindField(sel_to)
|
||||
index = field._index
|
||||
field_start, field_end = field._extent
|
||||
slice = self._GetValue()[field_start:field_end]
|
||||
|
||||
## dbg('field._stopFieldChangeIfInvalid?', field._stopFieldChangeIfInvalid)
|
||||
## dbg('field.IsValid(slice)?', field.IsValid(slice))
|
||||
|
||||
if field._stopFieldChangeIfInvalid and not field.IsValid(slice):
|
||||
## dbg('field invalid; field change disallowed')
|
||||
if not wx.Validator_IsSilent():
|
||||
wx.Bell()
|
||||
## dbg(indent=0)
|
||||
return False
|
||||
|
||||
|
||||
if event.ShiftDown():
|
||||
|
||||
@@ -3775,13 +3803,12 @@ class MaskedEditMixin:
|
||||
# NOTE: doesn't yet work with SHIFT-tab under wx; the control
|
||||
# never sees this event! (But I've coded for it should it ever work,
|
||||
# and it *does* work for '.' in IpAddrCtrl.)
|
||||
field = self._FindField(pos)
|
||||
index = field._index
|
||||
field_start = field._extent[0]
|
||||
|
||||
if pos < field_start:
|
||||
## dbg('cursor before 1st field; cannot change to a previous field')
|
||||
if not wx.Validator_IsSilent():
|
||||
wx.Bell()
|
||||
## dbg(indent=0)
|
||||
return False
|
||||
|
||||
if event.ControlDown():
|
||||
@@ -3821,8 +3848,6 @@ class MaskedEditMixin:
|
||||
|
||||
else:
|
||||
# "Go forward"
|
||||
field = self._FindField(sel_to)
|
||||
field_start, field_end = field._extent
|
||||
if event.ControlDown():
|
||||
## dbg('queuing select to end of field:', pos, field_end)
|
||||
wx.CallAfter(self._SetInsertionPoint, pos)
|
||||
@@ -3888,10 +3913,19 @@ class MaskedEditMixin:
|
||||
wx.CallAfter(self._SetInsertionPoint, next_pos)
|
||||
## dbg(indent=0)
|
||||
return False
|
||||
## dbg(indent=0)
|
||||
|
||||
|
||||
def _OnDecimalPoint(self, event):
|
||||
## dbg('MaskedEditMixin::_OnDecimalPoint', indent=1)
|
||||
field = self._FindField(self._GetInsertionPoint())
|
||||
start, end = field._extent
|
||||
slice = self._GetValue()[start:end]
|
||||
|
||||
if field._stopFieldChangeIfInvalid and not field.IsValid(slice):
|
||||
if not wx.Validator_IsSilent():
|
||||
wx.Bell()
|
||||
return False
|
||||
|
||||
pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode())
|
||||
|
||||
@@ -4021,7 +4055,7 @@ class MaskedEditMixin:
|
||||
|
||||
def _findNextEntry(self,pos, adjustInsert=True):
|
||||
""" Find the insertion point for the next valid entry character position."""
|
||||
## dbg('MaskedEditMixin::_findNextEntry', indent=1)
|
||||
## dbg('MaskedEditMixin::_findNextEntry', indent=1)
|
||||
if self._isTemplateChar(pos) or pos in self._explicit_field_boundaries: # if changing fields, pay attn to flag
|
||||
adjustInsert = adjustInsert
|
||||
else: # else within a field; flag not relevant
|
||||
@@ -4280,7 +4314,9 @@ class MaskedEditMixin:
|
||||
#### dbg('field_len?', field_len)
|
||||
#### dbg('pos==end; len (slice) < field_len?', len(slice) < field_len)
|
||||
#### dbg('not field._moveOnFieldFull?', not field._moveOnFieldFull)
|
||||
if len(slice) == field_len and field._moveOnFieldFull:
|
||||
if( len(slice) == field_len and field._moveOnFieldFull
|
||||
and (not field._stopFieldChangeIfInvalid or
|
||||
field._stopFieldChangeIfInvalid and field.IsValid(slice))):
|
||||
# move cursor to next field:
|
||||
pos = self._findNextEntry(pos)
|
||||
self._SetInsertionPoint(pos)
|
||||
@@ -4317,11 +4353,14 @@ class MaskedEditMixin:
|
||||
# else make sure the user is not trying to type over a template character
|
||||
# If they are, move them to the next valid entry position
|
||||
elif self._isTemplateChar(pos):
|
||||
if( not field._moveOnFieldFull
|
||||
and (not self._signOk
|
||||
or (self._signOk
|
||||
and field._index == 0
|
||||
and pos > 0) ) ): # don't move to next field without explicit cursor movement
|
||||
if( (not field._moveOnFieldFull
|
||||
and (not self._signOk
|
||||
or (self._signOk and field._index == 0 and pos > 0) ) )
|
||||
|
||||
or (field._stopFieldChangeIfInvalid
|
||||
and not field.IsValid(self._GetValue()[start:end]) ) ):
|
||||
|
||||
# don't move to next field without explicit cursor movement
|
||||
pass
|
||||
else:
|
||||
# find next valid position
|
||||
@@ -5092,7 +5131,11 @@ class MaskedEditMixin:
|
||||
#### dbg('field._moveOnFieldFull?', field._moveOnFieldFull)
|
||||
#### dbg('len(fstr.lstrip()) == end-start?', len(fstr.lstrip()) == end-start)
|
||||
if( field._moveOnFieldFull and pos == end
|
||||
and len(fstr.lstrip()) == end-start): # if field now full
|
||||
and len(fstr.lstrip()) == end-start # if field now full
|
||||
and (not field._stopFieldChangeIfInvalid # and we either don't care about valid
|
||||
or (field._stopFieldChangeIfInvalid # or we do and the current field value is valid
|
||||
and field.IsValid(fstr)))):
|
||||
|
||||
newpos = self._findNextEntry(end) # go to next field
|
||||
else:
|
||||
newpos = pos # else keep cursor at current position
|
||||
@@ -5165,7 +5208,11 @@ class MaskedEditMixin:
|
||||
|
||||
if( field._insertRight # if insert-right field (but we didn't start at right edge)
|
||||
and field._moveOnFieldFull # and should move cursor when full
|
||||
and len(newtext[start:end].strip()) == end-start): # and field now full
|
||||
and len(newtext[start:end].strip()) == end-start # and field now full
|
||||
and (not field._stopFieldChangeIfInvalid # and we either don't care about valid
|
||||
or (field._stopFieldChangeIfInvalid # or we do and the current field value is valid
|
||||
and field.IsValid(newtext[start:end].strip())))):
|
||||
|
||||
newpos = self._findNextEntry(end) # go to next field
|
||||
## dbg('newpos = nextentry =', newpos)
|
||||
else:
|
||||
@@ -6723,6 +6770,12 @@ __i=0
|
||||
|
||||
## CHANGELOG:
|
||||
## ====================
|
||||
## Version 1.13
|
||||
## 1. Added parameter option stopFieldChangeIfInvalid, which can be used to relax the
|
||||
## validation rules for a control, but make best efforts to stop navigation out of
|
||||
## that field should its current value be invalid. Note: this does not prevent the
|
||||
## value from remaining invalid if focus for the control is lost, via mousing etc.
|
||||
##
|
||||
## Version 1.12
|
||||
## 1. Added proper support for NUMPAD keypad keycodes for navigation and control.
|
||||
##
|
||||
|
@@ -2,7 +2,7 @@
|
||||
# Name: wxPython.lib.masked.numctrl.py
|
||||
# Author: Will Sadkin
|
||||
# Created: 09/06/2003
|
||||
# Copyright: (c) 2003 by Will Sadkin
|
||||
# Copyright: (c) 2003-2007 by Will Sadkin
|
||||
# RCS-ID: $Id$
|
||||
# License: wxWidgets license
|
||||
#----------------------------------------------------------------------------
|
||||
@@ -81,6 +81,7 @@ masked.NumCtrl:
|
||||
min = None,
|
||||
max = None,
|
||||
limited = False,
|
||||
limitOnFieldChange = False,
|
||||
selectOnEntry = True,
|
||||
foregroundColour = "Black",
|
||||
signedForegroundColour = "Red",
|
||||
@@ -155,6 +156,12 @@ masked.NumCtrl:
|
||||
If False and bounds are set, out-of-bounds values will
|
||||
result in a background colored with the current invalidBackgroundColour.
|
||||
|
||||
limitOnFieldChange
|
||||
An alternative to limited, this boolean indicates whether or not a
|
||||
field change should be allowed if the value in the control
|
||||
is out of bounds. If True, and control focus is lost, this will also
|
||||
cause the control to take on the nearest bound value.
|
||||
|
||||
selectOnEntry
|
||||
Boolean indicating whether or not the value in each field of the
|
||||
control should be automatically selected (for replacement) when
|
||||
@@ -312,6 +319,18 @@ IsLimited()
|
||||
Returns <I>True</I> if the control is currently limiting the
|
||||
value to fall within the current bounds.
|
||||
|
||||
SetLimitOnFieldChange()
|
||||
If called with a value of True, will cause the control to allow
|
||||
out-of-bounds values, but will prevent field change if attempted
|
||||
via navigation, and if the control loses focus, it will change
|
||||
the value to the nearest bound.
|
||||
|
||||
GetLimitOnFieldChange()
|
||||
|
||||
IsLimitedOnFieldChange()
|
||||
Returns <I>True</I> if the control is currently limiting the
|
||||
value on field change.
|
||||
|
||||
|
||||
SetAllowNone(bool)
|
||||
If called with a value of True, this function will cause the control
|
||||
@@ -390,7 +409,7 @@ MININT = -maxint-1
|
||||
|
||||
from wx.tools.dbg import Logger
|
||||
from wx.lib.masked import MaskedEditMixin, Field, BaseMaskedTextCtrl
|
||||
dbg = Logger()
|
||||
##dbg = Logger()
|
||||
##dbg(enable=1)
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
@@ -442,6 +461,7 @@ class NumCtrlAccessorsMixin:
|
||||
'emptyInvalid',
|
||||
'validFunc',
|
||||
'validRequired',
|
||||
'stopFieldChangeIfInvalid',
|
||||
)
|
||||
for param in exposed_basectrl_params:
|
||||
propname = param[0].upper() + param[1:]
|
||||
@@ -478,6 +498,7 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
||||
'min': None, # by default, no bounds set
|
||||
'max': None,
|
||||
'limited': False, # by default, no limiting even if bounds set
|
||||
'limitOnFieldChange': False, # by default, don't limit if changing fields, even if bounds set
|
||||
'allowNone': False, # by default, don't allow empty value
|
||||
'selectOnEntry': True, # by default, select the value of each field on entry
|
||||
'foregroundColour': "Black",
|
||||
@@ -759,6 +780,12 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
||||
maskededit_kwargs['validRequired'] = False
|
||||
self._limited = kwargs['limited']
|
||||
|
||||
if kwargs.has_key('limitOnFieldChange'):
|
||||
if kwargs['limitOnFieldChange'] and not self._limitOnFieldChange:
|
||||
maskededit_kwargs['stopFieldChangeIfInvalid'] = True
|
||||
elif kwargs['limitOnFieldChange'] and self._limitOnFieldChange:
|
||||
maskededit_kwargs['stopFieldChangeIfInvalid'] = False
|
||||
|
||||
## dbg('maskededit_kwargs:', maskededit_kwargs)
|
||||
if maskededit_kwargs.keys():
|
||||
self.SetCtrlParameters(**maskededit_kwargs)
|
||||
@@ -923,6 +950,43 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
||||
wx.CallAfter(self.SetInsertionPoint, sel_start) # preserve current selection/position
|
||||
wx.CallAfter(self.SetSelection, sel_start, sel_to)
|
||||
|
||||
|
||||
def _OnChangeField(self, event):
|
||||
"""
|
||||
This routine enhances the base masked control _OnFieldChange(). It's job
|
||||
is to ensure limits are imposed if limitOnFieldChange is enabled.
|
||||
"""
|
||||
## dbg('NumCtrl::_OnFieldChange', indent=1)
|
||||
if self._limitOnFieldChange and not (self._min <= self.GetValue() <= self._max):
|
||||
self._disallowValue()
|
||||
## dbg('oob - field change disallowed',indent=0)
|
||||
return False
|
||||
else:
|
||||
## dbg(indent=0)
|
||||
return MaskedEditMixin._OnChangeField(self, event) # call the baseclass function
|
||||
|
||||
|
||||
def _LostFocus(self):
|
||||
"""
|
||||
On loss of focus, if limitOnFieldChange is set, ensure value conforms to limits.
|
||||
"""
|
||||
## dbg('NumCtrl::_LostFocus', indent=1)
|
||||
if self._limitOnFieldChange:
|
||||
## dbg("limiting on loss of focus")
|
||||
value = self.GetValue()
|
||||
if self._min is not None and value < self._min:
|
||||
## dbg('Set to min value:', self._min)
|
||||
self._SetValue(self._toGUI(self._min))
|
||||
|
||||
elif self._max is not None and value > self._max:
|
||||
## dbg('Setting to max value:', self._max)
|
||||
self._SetValue(self._toGUI(self._max))
|
||||
# (else do nothing.)
|
||||
# (else do nothing.)
|
||||
## dbg(indent=0)
|
||||
return True
|
||||
|
||||
|
||||
def _SetValue(self, value):
|
||||
"""
|
||||
This routine supersedes the base masked control _SetValue(). It is
|
||||
@@ -1346,7 +1410,32 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
|
||||
|
||||
def GetLimited(self):
|
||||
""" (For regularization of property accessors) """
|
||||
return self.IsLimited
|
||||
return self.IsLimited()
|
||||
|
||||
def SetLimitOnFieldChange(self, limit):
|
||||
"""
|
||||
If called with a value of True, this function will cause the control
|
||||
to prevent navigation out of the current field if its value is out-of-bounds,
|
||||
and limit the value to fall within the bounds currently specified if the
|
||||
control loses focus.
|
||||
|
||||
If called with a value of False, this function will disable value
|
||||
limiting, but coloring of out-of-bounds values will still take
|
||||
place if bounds have been set for the control.
|
||||
"""
|
||||
self.SetParameters(limitOnFieldChange = limit)
|
||||
|
||||
|
||||
def IsLimitedOnFieldChange(self):
|
||||
"""
|
||||
Returns True if the control is currently limiting the
|
||||
value to fall within the current bounds.
|
||||
"""
|
||||
return self._limitOnFieldChange
|
||||
|
||||
def GetLimitOnFieldChange(self):
|
||||
""" (For regularization of property accessors) """
|
||||
return self.IsLimitedOnFieldChange()
|
||||
|
||||
|
||||
def IsInBounds(self, value=None):
|
||||
@@ -1794,6 +1883,13 @@ __i=0
|
||||
## 1. Add support for printf-style format specification.
|
||||
## 2. Add option for repositioning on 'illegal' insertion point.
|
||||
##
|
||||
## Version 1.4
|
||||
## 1. In response to user request, added limitOnFieldChange feature, so that
|
||||
## out-of-bounds values can be temporarily added to the control, but should
|
||||
## navigation be attempted out of an invalid field, it will not navigate,
|
||||
## and if focus is lost on a control so limited with an invalid value, it
|
||||
## will change the value to the nearest bound.
|
||||
##
|
||||
## Version 1.3
|
||||
## 1. fixed to allow space for a group char.
|
||||
##
|
||||
|
@@ -157,13 +157,25 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
||||
Allow mixin to set the raw value of the control with this function.
|
||||
REQUIRED by any class derived from MaskedEditMixin.
|
||||
"""
|
||||
## dbg('MaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent=1)
|
||||
## dbg('MaskedTextCtrl::_SetValue("%(value)s", use_change_value=%(use_change_value)d)' % locals(), indent=1)
|
||||
# Record current selection and insertion point, for undo
|
||||
self._prevSelection = self._GetSelection()
|
||||
self._prevInsertionPoint = self._GetInsertionPoint()
|
||||
wx.TextCtrl.SetValue(self, value)
|
||||
## dbg(indent=0)
|
||||
|
||||
def _ChangeValue(self, value):
|
||||
"""
|
||||
Allow mixin to set the raw value of the control with this function without
|
||||
generating an event as a result. (New for masked.TextCtrl as of 2.8.4)
|
||||
"""
|
||||
## dbg('MaskedTextCtrl::_ChangeValue("%(value)s", use_change_value=%(use_change_value)d)' % locals(), indent=1)
|
||||
# Record current selection and insertion point, for undo
|
||||
self._prevSelection = self._GetSelection()
|
||||
self._prevInsertionPoint = self._GetInsertionPoint()
|
||||
wx.TextCtrl.ChangeValue(self, value)
|
||||
## dbg(indent=0)
|
||||
|
||||
def SetValue(self, value):
|
||||
"""
|
||||
This function redefines the externally accessible .SetValue() to be
|
||||
@@ -171,10 +183,27 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
||||
masked control. NOTE: this must be done in the class derived
|
||||
from the base wx control.
|
||||
"""
|
||||
## dbg('MaskedTextCtrl::SetValue = "%s"' % value, indent=1)
|
||||
self.ModifyValue(value, use_change_value=False)
|
||||
|
||||
def ChangeValue(self, value):
|
||||
"""
|
||||
Provided to accomodate similar functionality added to base control in wxPython 2.7.1.1.
|
||||
"""
|
||||
self.ModifyValue(value, use_change_value=True)
|
||||
|
||||
|
||||
def ModifyValue(self, value, use_change_value=False):
|
||||
"""
|
||||
This factored function of common code does the bulk of the work for SetValue
|
||||
and ChangeValue.
|
||||
"""
|
||||
## dbg('MaskedTextCtrl::ModifyValue("%(value)s", use_change_value=%(use_change_value)d)' % locals(), indent=1)
|
||||
|
||||
if not self._mask:
|
||||
wx.TextCtrl.SetValue(self, value) # revert to base control behavior
|
||||
if use_change_value:
|
||||
wx.TextCtrl.ChangeValue(self, value) # revert to base control behavior
|
||||
else:
|
||||
wx.TextCtrl.SetValue(self, value) # revert to base control behavior
|
||||
return
|
||||
|
||||
# empty previous contents, replacing entire value:
|
||||
@@ -198,7 +227,7 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
||||
value = value[1:]
|
||||
## dbg('padded value = "%s"' % value)
|
||||
|
||||
# make SetValue behave the same as if you had typed the value in:
|
||||
# make Set/ChangeValue behave the same as if you had typed the value in:
|
||||
try:
|
||||
value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
|
||||
if self._isFloat:
|
||||
@@ -220,10 +249,12 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
||||
else:
|
||||
## dbg('exception thrown', indent=0)
|
||||
raise
|
||||
|
||||
self._SetValue(value) # note: to preserve similar capability, .SetValue()
|
||||
# does not change IsModified()
|
||||
#### dbg('queuing insertion after .SetValue', replace_to)
|
||||
if use_change_value:
|
||||
self._ChangeValue(value)
|
||||
else:
|
||||
self._SetValue(value) # note: to preserve similar capability, .SetValue()
|
||||
# does not change IsModified()
|
||||
#### dbg('queuing insertion after ._Set/ChangeValue', replace_to)
|
||||
# set selection to last char replaced by paste
|
||||
wx.CallAfter(self._SetInsertionPoint, replace_to)
|
||||
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
||||
@@ -372,6 +403,10 @@ class PreMaskedTextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
|
||||
__i=0
|
||||
## CHANGELOG:
|
||||
## ====================
|
||||
## Version 1.3
|
||||
## - Added support for ChangeValue() function, similar to that of the base
|
||||
## control, added in wxPython 2.7.1.1.
|
||||
##
|
||||
## Version 1.2
|
||||
## - Converted docstrings to reST format, added doc for ePyDoc.
|
||||
## removed debugging override functions.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
'''
|
||||
"""
|
||||
treemixin.py
|
||||
|
||||
This module provides three mixin classes that can be used with tree
|
||||
@@ -18,7 +18,7 @@ controls:
|
||||
all items in the tree to restore it later.
|
||||
|
||||
All mixin classes work with wx.TreeCtrl, wx.gizmos.TreeListCtrl,
|
||||
and wx.lib.customtree.CustomTreeCtrl. They can be used together or
|
||||
and wx.lib.customtreectrl.CustomTreeCtrl. They can be used together or
|
||||
separately.
|
||||
|
||||
The VirtualTree and DragAndDrop mixins force the wx.TR_HIDE_ROOT style.
|
||||
@@ -30,15 +30,15 @@ Date: 15 April 2007
|
||||
|
||||
ExpansionState is based on code and ideas from Karsten Hilbert.
|
||||
Andrea Gavana provided help with the CustomTreeCtrl integration.
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
import wx, wx.lib.customtreectrl
|
||||
import wx
|
||||
|
||||
|
||||
class TreeAPIHarmonizer(object):
|
||||
''' This class attempts to hide the differences in API between the
|
||||
different tree controls that are part of wxPython. '''
|
||||
""" This class attempts to hide the differences in API between the
|
||||
different tree controls that are part of wxPython. """
|
||||
|
||||
def __callSuper(self, methodName, default, *args, **kwargs):
|
||||
# If our super class has a method called methodName, call it,
|
||||
@@ -165,12 +165,12 @@ class TreeAPIHarmonizer(object):
|
||||
**kwargs)
|
||||
|
||||
def HitTest(self, *args, **kwargs):
|
||||
''' HitTest returns a two-tuple (item, flags) for tree controls
|
||||
""" HitTest returns a two-tuple (item, flags) for tree controls
|
||||
without columns and a three-tuple (item, flags, column) for tree
|
||||
controls with columns. Our caller can indicate this method to
|
||||
always return a three-tuple no matter what tree control we're mixed
|
||||
in with by specifying the optional argument 'alwaysReturnColumn'
|
||||
to be True. '''
|
||||
to be True. """
|
||||
alwaysReturnColumn = kwargs.pop('alwaysReturnColumn', False)
|
||||
hitTestResult = super(TreeAPIHarmonizer, self).HitTest(*args, **kwargs)
|
||||
if len(hitTestResult) == 2 and alwaysReturnColumn:
|
||||
@@ -209,11 +209,11 @@ class TreeAPIHarmonizer(object):
|
||||
|
||||
|
||||
class TreeHelper(object):
|
||||
''' This class provides methods that are not part of the API of any
|
||||
tree control, but are convenient to have available. '''
|
||||
""" This class provides methods that are not part of the API of any
|
||||
tree control, but are convenient to have available. """
|
||||
|
||||
def GetItemChildren(self, item=None, recursively=False):
|
||||
''' Return the children of item as a list. '''
|
||||
""" Return the children of item as a list. """
|
||||
if not item:
|
||||
item = self.GetRootItem()
|
||||
if not item:
|
||||
@@ -228,7 +228,7 @@ class TreeHelper(object):
|
||||
return children
|
||||
|
||||
def GetIndexOfItem(self, item):
|
||||
''' Return the index of item. '''
|
||||
""" Return the index of item. """
|
||||
parent = self.GetItemParent(item)
|
||||
if parent:
|
||||
parentIndices = self.GetIndexOfItem(parent)
|
||||
@@ -238,7 +238,7 @@ class TreeHelper(object):
|
||||
return ()
|
||||
|
||||
def GetItemByIndex(self, index):
|
||||
''' Return the item specified by index. '''
|
||||
""" Return the item specified by index. """
|
||||
item = self.GetRootItem()
|
||||
for i in index:
|
||||
children = self.GetItemChildren(item)
|
||||
@@ -247,7 +247,7 @@ class TreeHelper(object):
|
||||
|
||||
|
||||
class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
''' This is a mixin class that can be used to allow for virtual tree
|
||||
""" This is a mixin class that can be used to allow for virtual tree
|
||||
controls. It can be mixed in with wx.TreeCtrl, wx.gizmos.TreeListCtrl,
|
||||
wx.lib.customtree.CustomTreeCtrl.
|
||||
|
||||
@@ -279,7 +279,7 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
the fourth one. A tuple with two integers, e.g. (3,0), represents a
|
||||
child of a visible root item, in this case the first child of the
|
||||
fourth root item.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['style'] = kwargs.get('style', wx.TR_DEFAULT_STYLE) | \
|
||||
@@ -289,69 +289,69 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed)
|
||||
|
||||
def OnGetChildrenCount(self, index):
|
||||
''' This function *must* be overloaded in the derived class.
|
||||
""" This function *must* be overloaded in the derived class.
|
||||
It should return the number of child items of the item with the
|
||||
provided index. If index == () it should return the number of
|
||||
root items. '''
|
||||
root items. """
|
||||
raise NotImplementedError
|
||||
|
||||
def OnGetItemText(self, index, column=0):
|
||||
''' This function *must* be overloaded in the derived class. It
|
||||
""" This function *must* be overloaded in the derived class. It
|
||||
should return the string containing the text of the specified
|
||||
item. '''
|
||||
item. """
|
||||
raise NotImplementedError
|
||||
|
||||
def OnGetItemFont(self, index):
|
||||
''' This function may be overloaded in the derived class. It
|
||||
should return the wx.Font to be used for the specified item. '''
|
||||
""" This function may be overloaded in the derived class. It
|
||||
should return the wx.Font to be used for the specified item. """
|
||||
return wx.NullFont
|
||||
|
||||
def OnGetItemTextColour(self, index):
|
||||
''' This function may be overloaded in the derived class. It
|
||||
""" This function may be overloaded in the derived class. It
|
||||
should return the wx.Colour to be used as text colour for the
|
||||
specified item. '''
|
||||
specified item. """
|
||||
return wx.NullColour
|
||||
|
||||
def OnGetItemBackgroundColour(self, index):
|
||||
''' This function may be overloaded in the derived class. It
|
||||
""" This function may be overloaded in the derived class. It
|
||||
should return the wx.Colour to be used as background colour for
|
||||
the specified item. '''
|
||||
the specified item. """
|
||||
return wx.NullColour
|
||||
|
||||
def OnGetItemImage(self, index, which=wx.TreeItemIcon_Normal, column=0):
|
||||
''' This function may be overloaded in the derived class. It
|
||||
""" This function may be overloaded in the derived class. It
|
||||
should return the index of the image to be used. Don't forget
|
||||
to associate an ImageList with the tree control. '''
|
||||
to associate an ImageList with the tree control. """
|
||||
return -1
|
||||
|
||||
def OnGetItemType(self, index):
|
||||
''' This function may be overloaded in the derived class, but
|
||||
""" This function may be overloaded in the derived class, but
|
||||
that only makes sense when this class is mixed in with a tree
|
||||
control that supports checkable items, i.e. CustomTreeCtrl.
|
||||
This method should return whether the item is to be normal (0,
|
||||
the default), a checkbox (1) or a radiobutton (2).
|
||||
Note that OnGetItemChecked needs to be implemented as well; it
|
||||
should return whether the item is actually checked. '''
|
||||
should return whether the item is actually checked. """
|
||||
return 0
|
||||
|
||||
def OnGetItemChecked(self, index):
|
||||
''' This function may be overloaded in the derived class, but
|
||||
""" This function may be overloaded in the derived class, but
|
||||
that only makes sense when this class is mixed in with a tree
|
||||
control that supports checkable items, i.e. CustomTreeCtrl.
|
||||
This method should return whether the item is to be checked.
|
||||
Note that OnGetItemType should return 1 (checkbox) or 2
|
||||
(radiobutton) for this item. '''
|
||||
(radiobutton) for this item. """
|
||||
return False
|
||||
|
||||
def RefreshItems(self):
|
||||
''' Redraws all visible items. '''
|
||||
""" Redraws all visible items. """
|
||||
rootItem = self.GetRootItem()
|
||||
if not rootItem:
|
||||
rootItem = self.AddRoot('Hidden root')
|
||||
self.RefreshChildrenRecursively(rootItem)
|
||||
|
||||
def RefreshItem(self, index):
|
||||
''' Redraws the item with the specified index. '''
|
||||
""" Redraws the item with the specified index. """
|
||||
try:
|
||||
item = self.GetItemByIndex(index)
|
||||
except IndexError:
|
||||
@@ -362,8 +362,8 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
self.DoRefreshItem(item, index, hasChildren)
|
||||
|
||||
def RefreshChildrenRecursively(self, item, itemIndex=None):
|
||||
''' Refresh the children of item, reusing as much of the
|
||||
existing items in the tree as possible. '''
|
||||
""" Refresh the children of item, reusing as much of the
|
||||
existing items in the tree as possible. """
|
||||
if itemIndex is None:
|
||||
itemIndex = self.GetIndexOfItem(item)
|
||||
reusableChildren = self.GetItemChildren(item)
|
||||
@@ -377,7 +377,7 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
self.Delete(child)
|
||||
|
||||
def RefreshItemRecursively(self, item, itemIndex):
|
||||
''' Refresh the item and its children recursively. '''
|
||||
""" Refresh the item and its children recursively. """
|
||||
hasChildren = bool(self.OnGetChildrenCount(itemIndex))
|
||||
item = self.DoRefreshItem(item, itemIndex, hasChildren)
|
||||
# We need to refresh the children when the item is expanded and
|
||||
@@ -388,7 +388,7 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
self.SetItemHasChildren(item, hasChildren)
|
||||
|
||||
def DoRefreshItem(self, item, index, hasChildren):
|
||||
''' Refresh one item. '''
|
||||
""" Refresh one item. """
|
||||
item = self.RefreshItemType(item, index)
|
||||
self.RefreshItemText(item, index)
|
||||
self.RefreshColumns(item, index)
|
||||
@@ -458,7 +458,7 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
event.Skip()
|
||||
|
||||
def __refreshAttribute(self, item, index, attribute, *args):
|
||||
''' Refresh the specified attribute if necessary. '''
|
||||
""" Refresh the specified attribute if necessary. """
|
||||
value = getattr(self, 'OnGet%s'%attribute)(index, *args)
|
||||
if getattr(self, 'Get%s'%attribute)(item, *args) != value:
|
||||
return getattr(self, 'Set%s'%attribute)(item, value, *args)
|
||||
@@ -467,7 +467,7 @@ class VirtualTree(TreeAPIHarmonizer, TreeHelper):
|
||||
|
||||
|
||||
class DragAndDrop(TreeAPIHarmonizer, TreeHelper):
|
||||
''' This is a mixin class that can be used to easily implement
|
||||
""" This is a mixin class that can be used to easily implement
|
||||
dragging and dropping of tree items. It can be mixed in with
|
||||
wx.TreeCtrl, wx.gizmos.TreeListCtrl, or wx.lib.customtree.CustomTreeCtrl.
|
||||
|
||||
@@ -480,7 +480,7 @@ class DragAndDrop(TreeAPIHarmonizer, TreeHelper):
|
||||
dropped an item on top of another item. It's up to you to decide how
|
||||
to handle the drop. If you are using this mixin together with the
|
||||
VirtualTree mixin, it makes sense to rearrange your underlying data
|
||||
and then call RefreshItems to let the virtual tree refresh itself. '''
|
||||
and then call RefreshItems to let the virtual tree refresh itself. """
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['style'] = kwargs.get('style', wx.TR_DEFAULT_STYLE) | \
|
||||
@@ -489,11 +489,11 @@ class DragAndDrop(TreeAPIHarmonizer, TreeHelper):
|
||||
self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnBeginDrag)
|
||||
|
||||
def OnDrop(self, dropItem, dragItem):
|
||||
''' This function must be overloaded in the derived class.
|
||||
""" This function must be overloaded in the derived class.
|
||||
dragItem is the item being dragged by the user. dropItem is the
|
||||
item dragItem is dropped upon. If the user doesn't drop dragItem
|
||||
on another item, dropItem equals the (hidden) root item of the
|
||||
tree control. '''
|
||||
tree control. """
|
||||
raise NotImplementedError
|
||||
|
||||
def OnBeginDrag(self, event):
|
||||
@@ -567,7 +567,7 @@ class DragAndDrop(TreeAPIHarmonizer, TreeHelper):
|
||||
|
||||
|
||||
class ExpansionState(TreeAPIHarmonizer, TreeHelper):
|
||||
''' This is a mixin class that can be used to save and restore
|
||||
""" This is a mixin class that can be used to save and restore
|
||||
the expansion state (i.e. which items are expanded and which items
|
||||
are collapsed) of a tree. It can be mixed in with wx.TreeCtrl,
|
||||
wx.gizmos.TreeListCtrl, or wx.lib.customtree.CustomTreeCtrl.
|
||||
@@ -590,20 +590,20 @@ class ExpansionState(TreeAPIHarmonizer, TreeHelper):
|
||||
expansion doesn't depend on the position of items in the tree, but
|
||||
rather on some more stable characteristic of the underlying domain
|
||||
object, e.g. a social security number in case of persons or an isbn
|
||||
number in case of books. '''
|
||||
number in case of books. """
|
||||
|
||||
def GetItemIdentity(self, item):
|
||||
''' Return a hashable object that represents the identity of the
|
||||
""" Return a hashable object that represents the identity of the
|
||||
item. By default this returns the position of the item in the
|
||||
tree. You may want to override this to return the item label
|
||||
(if you know that labels are unique and don't change), or return
|
||||
something that represents the underlying domain object, e.g.
|
||||
a database key. '''
|
||||
a database key. """
|
||||
return self.GetIndexOfItem(item)
|
||||
|
||||
def GetExpansionState(self):
|
||||
''' GetExpansionState() -> list of expanded items. Expanded items
|
||||
are coded as determined by the result of GetItemIdentity(item). '''
|
||||
""" GetExpansionState() -> list of expanded items. Expanded items
|
||||
are coded as determined by the result of GetItemIdentity(item). """
|
||||
root = self.GetRootItem()
|
||||
if not root:
|
||||
return []
|
||||
@@ -613,9 +613,9 @@ class ExpansionState(TreeAPIHarmonizer, TreeHelper):
|
||||
return self.GetExpansionStateOfItem(root)
|
||||
|
||||
def SetExpansionState(self, listOfExpandedItems):
|
||||
''' SetExpansionState(listOfExpandedItems). Expands all tree items
|
||||
""" SetExpansionState(listOfExpandedItems). Expands all tree items
|
||||
whose identity, as determined by GetItemIdentity(item), is present
|
||||
in the list and collapses all other tree items. '''
|
||||
in the list and collapses all other tree items. """
|
||||
root = self.GetRootItem()
|
||||
if not root:
|
||||
return
|
||||
|
Reference in New Issue
Block a user