diff --git a/wxPython/CHANGES.txt b/wxPython/CHANGES.txt index eefa6256aa..01c14d1fb8 100644 --- a/wxPython/CHANGES.txt +++ b/wxPython/CHANGES.txt @@ -174,6 +174,9 @@ Added wxXmlResourceHandler which allows you to create custom handlers for nonstandard class types in XRC resources. See the demo for an example. +Added wxPython.lib.mixins.rubberband module from Robb Shecter. + + diff --git a/wxPython/contrib/stc/_stcextras.py b/wxPython/contrib/stc/_stcextras.py index eefe2c4547..dc8f3456ef 100644 --- a/wxPython/contrib/stc/_stcextras.py +++ b/wxPython/contrib/stc/_stcextras.py @@ -3,5 +3,5 @@ wx.wxStyledTextEventPtr = wxStyledTextEventPtr wx.wxStyledTextCtrlPtr = wxStyledTextCtrlPtr - -wxSTC_CARET_CENTER = wxSTC_CARET_STRICT +# This constant no longer exists in Scintilla, but I'll put it here for a while to avoid disrupting user code... +wxSTC_CARET_CENTER = 0 diff --git a/wxPython/contrib/stc/msw/stc_.py b/wxPython/contrib/stc/msw/stc_.py index 283ac7527d..6a5b12ac93 100644 --- a/wxPython/contrib/stc/msw/stc_.py +++ b/wxPython/contrib/stc/msw/stc_.py @@ -1819,5 +1819,5 @@ wxEVT_STC_ZOOM = stc_c.wxEVT_STC_ZOOM wx.wxStyledTextEventPtr = wxStyledTextEventPtr wx.wxStyledTextCtrlPtr = wxStyledTextCtrlPtr - -wxSTC_CARET_CENTER = wxSTC_CARET_STRICT +# This constant no longer exists in Scintilla, but I'll put it here for a while to avoid disrupting user code... +wxSTC_CARET_CENTER = 0 diff --git a/wxPython/demo/GridCustTable.py b/wxPython/demo/GridCustTable.py index ff78201fcb..66514d2318 100644 --- a/wxPython/demo/GridCustTable.py +++ b/wxPython/demo/GridCustTable.py @@ -45,7 +45,10 @@ class CustomDataTable(wxPyGridTableBase): return len(self.data[0]) def IsEmptyCell(self, row, col): - return not self.data[row][col] + try: + return not self.data[row][col] + except IndexError: + return true # Get/Set values in the table. The Python version of these # methods can handle any data-type, (as long as the Editor and @@ -138,8 +141,22 @@ class CustTableGrid(wxGrid): class TestFrame(wxFrame): def __init__(self, parent, log): wxFrame.__init__(self, parent, -1, "Custom Table, data driven Grid Demo", size=(640,480)) - grid = CustTableGrid(self, log) + p = wxPanel(self, -1, style=0) + grid = CustTableGrid(p, log) + b = wxButton(p, -1, "Another Control...") + b.SetDefault() + EVT_BUTTON(self, b.GetId(), self.OnButton) + EVT_SET_FOCUS(b, self.OnButtonFocus) + bs = wxBoxSizer(wxVERTICAL) + bs.Add(grid, 1, wxGROW|wxALL, 5) + bs.Add(b) + p.SetSizer(bs) + def OnButton(self, evt): + print "button selected" + + def OnButtonFocus(self, evt): + print "button focus" #--------------------------------------------------------------------------- diff --git a/wxPython/demo/wxListCtrl.py b/wxPython/demo/wxListCtrl.py index 0979a5c738..e64a5256bb 100644 --- a/wxPython/demo/wxListCtrl.py +++ b/wxPython/demo/wxListCtrl.py @@ -223,7 +223,8 @@ class TestListCtrlPanel(wxPanel, wxColumnSorterMixin): def OnItemActivated(self, event): self.currentItem = event.m_itemIndex - self.log.WriteText("OnItemActivated: %s\n" % self.list.GetItemText(self.currentItem)) + self.log.WriteText("OnItemActivated: %s\nTopItem: %s" % + (self.list.GetItemText(self.currentItem), self.list.GetTopItem())) def OnItemDelete(self, event): self.log.WriteText("OnItemDelete\n") diff --git a/wxPython/demo/wxListCtrl_virtual.py b/wxPython/demo/wxListCtrl_virtual.py index f7489db1c8..7dffd52fdd 100644 --- a/wxPython/demo/wxListCtrl_virtual.py +++ b/wxPython/demo/wxListCtrl_virtual.py @@ -45,7 +45,8 @@ class TestVirtualList(wxListCtrl): def OnItemActivated(self, event): self.currentItem = event.m_itemIndex - self.log.WriteText("OnItemActivated: %s\n" % self.GetItemText(self.currentItem)) + self.log.WriteText("OnItemActivated: %s\nTopItem: %s\n" % + (self.GetItemText(self.currentItem), self.GetTopItem())) def getColumnText(self, index, col): item = self.GetItem(index, col) diff --git a/wxPython/demo/wxScrolledWindow.py b/wxPython/demo/wxScrolledWindow.py index e0ad207b73..9b2b9b26ba 100644 --- a/wxPython/demo/wxScrolledWindow.py +++ b/wxPython/demo/wxScrolledWindow.py @@ -149,6 +149,7 @@ class MyCanvas(wxScrolledWindow): def OnLeftButtonEvent(self, event): if event.LeftDown(): + self.SetFocus() self.SetXY(event) self.curLine = [] self.CaptureMouse() diff --git a/wxPython/setup.py b/wxPython/setup.py index 7d6a2c6247..a25c4776cc 100755 --- a/wxPython/setup.py +++ b/wxPython/setup.py @@ -13,7 +13,7 @@ from my_distutils import run_swig, contrib_copy_tree # flags and values that affect this script #---------------------------------------------------------------------- -VERSION = "2.3.3pre8" +VERSION = "2.3.3" DESCRIPTION = "Cross platform GUI toolkit for Python" AUTHOR = "Robin Dunn" AUTHOR_EMAIL = "Robin Dunn " diff --git a/wxPython/src/__version__.py b/wxPython/src/__version__.py index b846f0cf7f..2892942400 100644 --- a/wxPython/src/__version__.py +++ b/wxPython/src/__version__.py @@ -1 +1 @@ -ver = '2.3.3pre8' +ver = '2.3.3' diff --git a/wxPython/wxPython/lib/mixins/rubberband.py b/wxPython/wxPython/lib/mixins/rubberband.py new file mode 100644 index 0000000000..e4d9ff7709 --- /dev/null +++ b/wxPython/wxPython/lib/mixins/rubberband.py @@ -0,0 +1,389 @@ +""" +A mixin class for doing "RubberBand"-ing on a window. + +by "Robb Shecter" + +$Id$ + +""" + +from wxPython.wx import * +import Image + +# +# Some miscellaneous mathematical and geometrical functions +# + +def isNegative(aNumber): + """ + x < 0: 1 + else: 0 + """ + return aNumber < 0 + + +def normalizeBox(box): + """ + Convert any negative measurements in the current + box to positive, and adjust the origin. + """ + x, y, w, h = box + if w < 0: + x += (w+1) + w *= -1 + if h < 0: + y += (h+1) + h *= -1 + return (x, y, w, h) + + +def boxToExtent(box): + """ + Convert a box specification to an extent specification. + I put this into a seperate function after I realized that + I had been implementing it wrong in several places. + """ + b = normalizeBox(box) + return (b[0], b[1], b[0]+b[2]-1, b[1]+b[3]-1) + + +def pointInBox(x, y, box): + """ + Return true if the given point is contained in the box. + """ + e = boxToExtent(box) + return x >= e[0] and x <= e[2] and y >= e[1] and y <= e[3] + + +def pointOnBox(x, y, box, thickness=1): + """ + Return true if the point is on the outside edge + of the box. The thickness defines how thick the + edge should be. This is necessary for HCI reasons: + For example, it's normally very difficult for a user + to manuever the mouse onto a one pixel border. + """ + outerBox = box + innerBox = (box[0]+thickness, box[1]+thickness, box[2]-(thickness*2), box[3]-(thickness*2)) + return pointInBox(x, y, outerBox) and not pointInBox(x, y, innerBox) + + +def getCursorPosition(x, y, box, thickness=1): + """ + Return a position number in the range 0 .. 7 to indicate + where on the box border the point is. The layout is: + + 0 1 2 + 7 3 + 6 5 4 + """ + x0, y0, x1, y1 = boxToExtent(box) + w, h = box[2], box[3] + delta = thickness - 1 + p = None + + if pointInBox(x, y, (x0, y0, thickness, thickness)): + p = 0 + elif pointInBox(x, y, (x1-delta, y0, thickness, thickness)): + p = 2 + elif pointInBox(x, y, (x1-delta, y1-delta, thickness, thickness)): + p = 4 + elif pointInBox(x, y, (x0, y1-delta, thickness, thickness)): + p = 6 + elif pointInBox(x, y, (x0+thickness, y0, w-(thickness*2), thickness)): + p = 1 + elif pointInBox(x, y, (x1-delta, y0+thickness, thickness, h-(thickness*2))): + p = 3 + elif pointInBox(x, y, (x0+thickness, y1-delta, w-(thickness*2), thickness)): + p = 5 + elif pointInBox(x, y, (x0, y0+thickness, thickness, h-(thickness*2))): + p = 7 + + return p + + + + +class RubberBand: + """ + A stretchable border which is drawn on top of an + image to define an area. + """ + def __init__(self, drawingSurface, aspectRatio=None): + self.__THICKNESS = 5 + self.drawingSurface = drawingSurface + self.aspectRatio = aspectRatio + self.hasLetUp = 0 + self.currentlyMoving = None + self.currentBox = None + self.__enabled = 1 + self.__currentCursor = None + EVT_MOUSE_EVENTS(drawingSurface, self.__handleMouseEvents) + EVT_PAINT(drawingSurface, self.__handleOnPaint) + + def __setEnabled(self, enabled): + self.__enabled = enabled + + def __isEnabled(self): + return self.__enabled + + def __handleOnPaint(self, event): + #print 'paint' + event.Skip() + + def __isMovingCursor(self): + """ + Return true if the current cursor is one used to + mean moving the rubberband. + """ + return self.__currentCursor == wxCURSOR_HAND + + def __isSizingCursor(self): + """ + Return true if the current cursor is one of the ones + I may use to signify sizing. + """ + sizingCursors = [wxCURSOR_SIZENESW, + wxCURSOR_SIZENS, + wxCURSOR_SIZENWSE, + wxCURSOR_SIZEWE, + wxCURSOR_SIZING, + wxCURSOR_CROSS] + try: + sizingCursors.index(self.__currentCursor) + return 1 + except ValueError: + return 0 + + + def __handleMouseEvents(self, event): + """ + React according to the new event. This is the main + entry point into the class. This method contains the + logic for the class's behavior. + """ + if not self.enabled: + return + + x, y = event.GetPosition() + + # First make sure we have started a box. + if self.currentBox == None and not event.LeftDown(): + # No box started yet. Set cursor to the initial kind. + self.__setCursor(wxCURSOR_CROSS) + return + + if event.LeftDown(): + if self.currentBox == None: + # No RB Box, so start a new one. + self.currentBox = (x, y, 0, 0) + self.hasLetUp = 0 + elif self.__isSizingCursor(): + # Starting a sizing operation. Change the origin. + position = getCursorPosition(x, y, self.currentBox, thickness=self.__THICKNESS) + self.currentBox = self.__denormalizeBox(position, self.currentBox) + + elif event.Dragging() and event.LeftIsDown(): + # Use the cursor type to determine operation + if self.__isMovingCursor(): + if self.currentlyMoving or pointInBox(x, y, self.currentBox): + if not self.currentlyMoving: + self.currentlyMoving = (x - self.currentBox[0], y - self.currentBox[1]) + self.__moveTo(x - self.currentlyMoving[0], y - self.currentlyMoving[1]) + elif self.__isSizingCursor(): + self.__resizeBox(x, y) + + elif event.LeftUp(): + self.hasLetUp = 1 + self.currentlyMoving = None + self.__normalizeBox() + + elif event.Moving() and not event.Dragging(): + # Simple mouse movement event + self.__mouseMoved(x,y) + + def __denormalizeBox(self, position, box): + x, y, w, h = box + b = box + if position == 2 or position == 3: + b = (x, y + (h-1), w, h * -1) + elif position == 0 or position == 1 or position == 7: + b = (x + (w-1), y + (h-1), w * -1, h * -1) + elif position == 6: + b = (x + (w-1), y, w * -1, h) + return b + + def __resizeBox(self, x, y): + """ + Resize and repaint the box based on the given mouse + coordinates. + """ + # Implement the correct behavior for dragging a side + # of the box: Only change one dimension. + if not self.aspectRatio: + if self.__currentCursor == wxCURSOR_SIZENS: + x = None + elif self.__currentCursor == wxCURSOR_SIZEWE: + y = None + + x0,y0,w0,h0 = self.currentBox + currentExtent = boxToExtent(self.currentBox) + if x == None: + if w0 < 1: + w0 += 1 + else: + w0 -= 1 + x = x0 + w0 + if y == None: + if h0 < 1: + h0 += 1 + else: + h0 -= 1 + y = y0 + h0 + x1,y1 = x, y + w, h = abs(x1-x0)+1, abs(y1-y0)+1 + if self.aspectRatio: + w = max(w, int(h * self.aspectRatio)) + h = int(w / self.aspectRatio) + w *= [1,-1][isNegative(x1-x0)] + h *= [1,-1][isNegative(y1-y0)] + newbox = (x0, y0, w, h) + self.__drawAndErase(boxToDraw=normalizeBox(newbox), boxToErase=normalizeBox(self.currentBox)) + self.currentBox = (x0, y0, w, h) + + def __normalizeBox(self): + """ + Convert any negative measurements in the current + box to positive, and adjust the origin. + """ + self.currentBox = normalizeBox(self.currentBox) + + def __mouseMoved(self, x, y): + """ + Called when the mouse moved without any buttons pressed + or dragging being done. + """ + # Are we on the bounding box? + if pointOnBox(x, y, self.currentBox, thickness=self.__THICKNESS): + position = getCursorPosition(x, y, self.currentBox, thickness=self.__THICKNESS) + cursor = [ + wxCURSOR_SIZENWSE, + wxCURSOR_SIZENS, + wxCURSOR_SIZENESW, + wxCURSOR_SIZEWE, + wxCURSOR_SIZENWSE, + wxCURSOR_SIZENS, + wxCURSOR_SIZENESW, + wxCURSOR_SIZEWE + ] [position] + self.__setCursor(cursor) + elif pointInBox(x, y, self.currentBox): + self.__setCursor(wxCURSOR_HAND) + else: + self.__setCursor() + + def __setCursor(self, id=None): + """ + Set the mouse cursor to the given id. + """ + if self.__currentCursor != id: # Avoid redundant calls + if id: + self.drawingSurface.SetCursor(wxStockCursor(id)) + else: + self.drawingSurface.SetCursor(wxNullCursor) + self.__currentCursor = id + + def __moveCenterTo(self, x, y): + """ + Move the rubber band so that its center is at (x,y). + """ + x0, y0, w, h = self.currentBox + x2, y2 = x - (w/2), y - (h/2) + self.__moveTo(x2, y2) + + def __moveTo(self, x, y): + """ + Move the rubber band so that its origin is at (x,y). + """ + newbox = (x, y, self.currentBox[2], self.currentBox[3]) + self.__drawAndErase(boxToDraw=newbox, boxToErase=self.currentBox) + self.currentBox = newbox + + def __drawAndErase(self, boxToDraw, boxToErase=None): + """ + Draw one box shape and possibly erase another. + """ + dc = wxClientDC(self.drawingSurface) + dc.BeginDrawing() + dc.SetPen(wxPen(wxWHITE, 1, wxDOT)) + dc.SetBrush(wxTRANSPARENT_BRUSH) + dc.SetLogicalFunction(wxXOR) + if boxToErase: + dc.DrawRectangle(*boxToErase) + dc.DrawRectangle(*boxToDraw) + dc.EndDrawing() + + def __dumpMouseEvent(self, event): + print 'Moving: ',event.Moving() + print 'Dragging: ',event.Dragging() + print 'LeftDown: ',event.LeftDown() + print 'LeftisDown: ',event.LeftIsDown() + print 'LeftUp: ',event.LeftUp() + print 'Position: ',event.GetPosition() + print 'x,y: ',event.GetX(),event.GetY() + print + + + # + # The public API: + # + + def reset(self, aspectRatio=None): + """ + Clear the existing rubberband + """ + self.currentBox = None + self.aspectRatio = aspectRatio + self.drawingSurface.Refresh() + + def getCurrentExtent(self): + """ + Return (x0, y0, x1, y1) or None if + no drawing has yet been done. + """ + if not self.currentBox: + extent = None + else: + extent = boxToExtent(self.currentBox) + return extent + + enabled = property(__isEnabled, __setEnabled, None, 'True if I am responding to mouse events') + + + +if __name__ == '__main__': + app = wxPySimpleApp() + frame = wxFrame(None, -1, title='RubberBand Test', size=(300,300)) + + # Add a panel that the rubberband will work on. + panel = wxPanel(frame, -1) + panel.SetBackgroundColour(wxBLUE) + + # Create the rubberband + frame.rubberBand = RubberBand(drawingSurface=panel) + frame.rubberBand.reset(aspectRatio=0.5) + + # Add a button that creates a new rubberband + def __newRubberBand(event): + frame.rubberBand.reset() + button = wxButton(frame, 100, 'Reset Rubberband') + EVT_BUTTON(frame, 100, __newRubberBand) + + # Layout the frame + sizer = wxBoxSizer(wxVERTICAL) + sizer.Add(panel, 1, wxEXPAND | wxALL, 5) + sizer.Add(button, 0, wxALIGN_CENTER | wxALL, 5) + frame.SetAutoLayout(1) + frame.SetSizer(sizer) + frame.Show(1) + app.MainLoop()