import Wbase import W import Wkeys import Scrap import string import Evt import Events import Qd import Win import Fm import Numeric import operator import string from types import * # List Manager selection constants... # c.f. http://developer.apple.com/techpubs/mac/MoreToolbox/MoreToolbox-214.html lOnlyOne = 128 lExtendDrag = 64 lNoDisjoint = 32 lNoExtend = 16 lNoRect = 8 lUseSense = 4 lNoNilHilite = 2 # NewHandle function... this is missing from the Python 1.5.2b1 libs, # so we hack it as follows: def NewHandle(size=0): import Res return Res.Resource('.'*size) # little sentry class to make it easy to pick our font cellfont = Fm.GetFNum("Profont") if not cellfont: cellfont = Fm.GetFNum("Monaco") class SetCellFont: def __init__(self): # later: save old font here! # set preferred font Qd.TextFont(cellfont) Qd.TextSize(9) class ArrayEditor: def __init__(self, data): self.window = W.Window((500, 250), "Array Editor", minsize = (250, 150), tabbable=0) self.window.list = self.list = List2D((-1, 20, 1, 1)) self.list.setdata( data ) self.list.ared = self self.window.editfld = self.editfld = EntryFld( (50,-1,200,22), "" ) self.editfld.ared = self self.window.open() def close(self): self.list.ared = None self.list = None self.editfld.ared = None self.editfld = None self.window.close() self.window = None def setvalue(self, val, drow=0, dcol=0): "Set the value in the current cell, and move the selection by drow, dcol." if type(val) == StringType: try: if '.' in val or 'e' in val or 'E' in val: val = string.atof(val) else: val = string.atoi(val) except: val = 0 self.editfld.select(0) self.list.select(1) sel = self.list.getselection() if sel: sel = sel[0] self.list[sel] = val else: sel = (0,0) self.list.setselection( [(sel[0]+drow, sel[1]+dcol)] ) EntryFldParent = W.EditText class EntryFld(EntryFldParent): """A customized EditText field, which connects to an Array Editor.""" def key(self, char, event): # check for special keys... (what, message, when, where, modifiers) = event if modifiers & Events.cmdKey: if char == '.': # cancel entry self.Reset() elif char == Wkeys.returnkey: # accept entry, move down self.ared.setvalue(self.get(), drow=1) elif char == Wkeys.tabkey: print "Got tab key!" # accept entry, move right self.ared.setvalue(self.get(), dcol=1) elif char == Wkeys.enterkey: # accept entry, move right self.ared.setvalue(self.get()) else: EntryFldParent.key(self, char, event) def set(self, data): self.origData = data EntryFldParent.set(self,data) def Reset(self): "Reset to original data." self.set(self.origData) self.selectall() class List2D(Wbase.SelectableWidget): """Two-dimensional list widget, like a spreadsheet.""" LDEF_ID = 0 def __init__(self, possize, data = None, callback = None, flags = lExtendDrag + lNoDisjoint): if data is None: data = Numeric.zeros((0,0)) self.data = data Wbase.SelectableWidget.__init__(self, possize) self._selected = 0 self._enabled = 1 self._list = None self._cols = self.data.shape[1] self._callback = callback self._flags = flags self.lasttyping = "" self.lasttime = Evt.TickCount() self.timelimit = 30 self.setdata(data) self.drawingmode = 0 self.ared = None # ArrayEditor object def open(self): self.setdrawingmode(0) self.createlist() self.setdrawingmode(1) def createlist(self): import List self._calcbounds() self.SetPort() fontsentry = SetCellFont() # create rectangle for drawing the list in -- leave room for borders & scroll bar viewrect = self._bounds viewrect = viewrect[0]+1, viewrect[1]+1, viewrect[2]-16, viewrect[3]-16 # create rect describing the extend of the data datarect = (0,0, self._cols,0) # cell size ascent, descent, width, leading = Qd.GetFontInfo() cSize = (width*11, ascent+descent+leading) # misc. boolean options drawIt = 0 hasGrow = 1 scrollHoriz = 1 scrollVert = 1 self._list = List.LNew(viewrect,datarect, cSize, self.LDEF_ID, self._parentwindow.wid, drawIt, hasGrow, scrollHoriz, scrollVert) if self.drawingmode: self._list.LSetDrawingMode(0) self._list.selFlags = self._flags self.setdata(self.data) if hasattr(self, "_sel"): self.setselection(self._sel) del self._sel def adjust(self, oldbounds): self.SetPort() if self._selected: Win.InvalRect(Qd.InsetRect(oldbounds, -3, -3)) Win.InvalRect(Qd.InsetRect(self._bounds, -3, -3)) else: Win.InvalRect(oldbounds) Win.InvalRect(self._bounds) if oldbounds[:2] == self._bounds[:2]: # set visRgn to empty, to prevent nasty drawing side effect of LSize() Qd.RectRgn(self._parentwindow.wid.GetWindowPort().visRgn, (0, 0, 0, 0)) # adjust viewing rect # list still has the same upper/left coordinates, use LSize l, t, r, b = self._bounds width = r - l - 17 # leave room for scroll bars! height = b - t - 17 self._list.LSize(width, height) ## leave the cell size alone!!! #l, t, r, b = self._list.LRect((0,0)) #cellheight = b - t #self._list.LCellSize((width, cellheight)) # reset visRgn self._parentwindow.wid.CalcVis() else: # oh well, since the list manager doesn't have a LMove call, # we have to make the list all over again... sel = self.getselection() topcell = self.gettopcell() self._list = None self.setdrawingmode(0) self.createlist() self.setselection(sel) self.settopcell(topcell) self.setdrawingmode(1) def close(self): Wbase.SelectableWidget.close(self) self._list = None self._callback = None self.data = None def setdata(self, data): # for now, we'll assume 2D data! rows = data.shape[0] cols = data.shape[1] self.data = data the_list = self._list if not self._parent or not self._list: return # turn off drawing, and remember our scroll position self.setdrawingmode(0) topcell = self.gettopcell() # delete old data, if any the_list.LDelRow(0, 1) # I *think* this deletes all rows, but I'm not sure! the_list.LDelColumn(0, 1) # add new rows and columns the_list.LAddRow(rows, 0) the_list.LAddColumn(cols-1,0) # stuff the data into the cells self_itemrepr = self.itemrepr set_cell = the_list.LSetCell for row in range(rows): for col in range(cols): set_cell(self_itemrepr(data[row,col]), (col,row)) # restore scroll position and drawing mode self.settopcell(topcell) self.setdrawingmode(1) def click(self, point, modifiers): if not self._enabled: return # let the List Manager handle it first isdoubleclick = self._list.LClick(point, modifiers) # then, update our entry field... window = self.ared.window sel = self.getselection() if len(sel) == 1: self.ared.editfld.set( str(self.data[sel[0][0],sel[0][1]]) ) else: self.ared.editfld.set('') # finally, do the callback if any if self._callback: Wbase.CallbackCall(self._callback, 0, isdoubleclick) return 1 def key(self, char, event): (what, message, when, where, modifiers) = event sel = self.getselection() selrows = map(lambda x:x[0],sel) selcols = map(lambda x:x[1],sel) maxrows, maxcols = self.data.shape newselection = [] if char in Wkeys.arrowkeys: if char == Wkeys.uparrowkey: # simulate click above top-left cell target = [min(selrows)-1, min(selcols)] if target[0] < 0: target[0] = 0 elif char == Wkeys.downarrowkey: target = [max(selrows)+1, max(selcols)] if target[0] >= maxrows: target[0] = maxrows elif char == Wkeys.leftarrowkey: target = [min(selrows), min(selcols)-1] if target[1] < 0: target[1] = 0 elif char == Wkeys.rightarrowkey: target = [max(selrows), max(selcols)+1] if target[1] >= maxcols: target[1] = maxcols r = self._list.LRect((target[1],target[0])) self.click( ((r[0]+r[2])/2, (r[1]+r[3])/2), modifiers ) return elif char in (Wkeys.deletekey, Wkeys.backspacekey, '\033'): self.domenu_clear() return elif self.ared: self.ared.editfld.selectall() self.ared.editfld.select(1) self.ared.editfld.key(char,event) return if modifiers & Events.shiftKey: sel = [newselection] + sel else: sel = [newselection] self.setselection(newselection) self._list.LAutoScroll() self.click((-1, -1), 0) #def findmatch(self, tag): # lower = string.lower # items = self.items # taglen = len(tag) # match = '\377' * 100 # match_i = -1 # for i in range(len(items)): # item = lower(str(items[i])) # if tag <= item < match: # match = item # match_i = i # if match_i >= 0: # return match_i # else: # return len(items) - 1 def domenu_copy(self, *args): sel = self.getselection() selitems = [] maxrow = max(map(lambda x:x[0],sel)) minrow = min(map(lambda x:x[0],sel)) maxcol = max(map(lambda x:x[1],sel)) mincol = min(map(lambda x:x[1],sel)) selitems = [] for i in range(minrow,maxrow+1): selitems.append( ['']*(maxcol-mincol+1) ) for i in sel: selitems[i[0]-minrow][i[1]-mincol] = str(self.data[i[0],i[1]]) text = string.join(map(lambda x:string.join(x,'\t'),selitems), '\r') Scrap.ZeroScrap() Scrap.PutScrap('TEXT', text) def domenu_clear(self, *args): sel = self.getselection() for i in sel: self.data[i[0],i[1]] = 0 self._list.LSetCell(self.itemrepr(0), (i[1],i[0])) def can_copy(self, *args): return len(self.getselection()) <> 0 def can_paste(self, *args): length, offset = Scrap.GetScrap(NewHandle(), 'TEXT') return length > 0 def domenu_paste(self, *args): h = NewHandle() length, offset = Scrap.GetScrap(h, 'TEXT') if not length: return sel = self.getselection() if not sel: row,col = (0,0) else: row,col = sel[0] newsel = [] lines = string.split(h.data, '\r') for line in lines: items = string.split(line, '\t') for i in range(len(items)): try: self.data[row,col+i] = eval(items[i]) self._list.LSetCell(self.itemrepr(self.data[row,col+i]), (col+i,row)) newsel.append( (row,col+i) ) except: pass row = row+1 self.setselection(newsel) def domenu_selectall(self, *args): self.selectall() def selectall(self): self.setselection(range(len(self.items))) self._list.LAutoScroll() self.click((-1, -1), 0) def getselection(self): if not self._parent or not self._list: if hasattr(self, "_sel"): return self._sel return [] items = [] # list of selected items rows,cols = self.data.shape point = (0,0) while 1: ok, point = self._list.LGetSelect(1, point) if not ok: break # store selection in (row,col) order -- opposite ListMgr's order! items.append((point[1],point[0])) point = point[0]+1, point[1] if point[0] >= cols: point = 0,point[1]+1 return items def setselection(self, selection): # update the edit field print selection if len(selection) == 1: self.ared.editfld.set( str(self.data[selection[0][0],selection[0][1]]) ) else: self.ared.editfld.set('') # then do usual List handling stuff if not self._parent or not self._list: self._sel = selection return print 2 set_sel = self._list.LSetSelect rows,cols = self.data.shape for row in range(rows): for col in range(cols): if (row,col) in selection: set_sel(1, (col,row)) print "setting:", (row,col) else: set_sel(0, (col,row)) print 3 self._list.LAutoScroll() #def getselectedobjects(self): # sel = self.getselection() # objects = [] # for i in sel: # objects.append(self.items[i]) # return objects # #def setselectedobjects(self, objects): # sel = [] # for o in objects: # try: # sel.append(self.items.index(o)) # except: # pass # self.setselection(sel) def gettopcell(self): l, t, r, b = self._bounds t = t + 1 cl, ct, cr, cb = self._list.LRect((0, 0)) cellheight = cb - ct return (t - ct) / cellheight def settopcell(self, topcell): top = self.gettopcell() diff = topcell - top self._list.LScroll(0, diff) def draw(self, visRgn = None): if not self._list: return if self._visible: if not visRgn: visRgn = self._parentwindow.wid.GetWindowPort().visRgn self._list.LUpdate(visRgn) Qd.FrameRect(self._bounds) if self._selected and self._activated: self.drawselframe(1) def select(self, onoff, isclick = 0): if Wbase.SelectableWidget.select(self, onoff): return self.SetPort() self.drawselframe(onoff) def activate(self, onoff): self._activated = onoff if self._visible: self._list.LActivate(onoff) if self._selected: self.drawselframe(onoff) def get(self): return self.items def itemrepr(self, item): return "%10s" % str(item) def __getitem__(self, pos): return operator.__getitem__(self.data,pos) def __setitem__(self, pos, item): if self._parent and self._list: self._list.LSetCell(self.itemrepr(item), (pos[1],pos[0])) operator.__setitem__(self.data,pos,item) # #def __delitem__(self, index): # if self._parent and self._list: # self._list.LDelRow(1, index) # del self.items[index] # #def __getslice__(self, a, b): # return self.items[a:b] # #def __delslice__(self, a, b): # if b-a: # if self._parent and self._list: # self._list.LDelRow(b-a, a) # del self.items[a:b] # #def __setslice__(self, a, b, items): # if self._parent and self._list: # l = len(items) # the_list = self._list # self.setdrawingmode(0) # if b-a: # if b > len(self.items): # # fix for new 1.5 "feature" where b is sys.maxint instead of len(self)... # # LDelRow doesn't like maxint. # b = len(self.items) # the_list.LDelRow(b-a, a) # the_list.LAddRow(l, a) # self_itemrepr = self.itemrepr # set_cell = the_list.LSetCell # for i in range(len(items)): # set_cell(self_itemrepr(items[i]), (0, i + a)) # self.items[a:b] = items # self.setdrawingmode(1) # else: # self.items[a:b] = items # #def __len__(self): # return len(self.items) # #def append(self, item): # if self._parent and self._list: # index = len(self.items) # self._list.LAddRow(1, index) # self._list.LSetCell(self.itemrepr(item), (0, index)) # self.items.append(item) # #def remove(self, item): # index = self.items.index(item) # self.__delitem__(index) # #def index(self, item): # return self.items.index(item) # #def insert(self, index, item): # if index < 0: # index = 0 # if self._parent and self._list: # self._list.LAddRow(1, index) # self._list.LSetCell(self.itemrepr(item), (0, index)) # self.items.insert(index, item) # def setdrawingmode(self, onoff): if onoff: self.drawingmode = self.drawingmode - 1 if self.drawingmode == 0 and self._list is not None: self._list.LSetDrawingMode(1) if self._visible: bounds = l, t, r, b = Qd.InsetRect(self._bounds, 1, 1) cl, ct, cr, cb = self._list.LRect((self.data.shape[1]-1, self.data.shape[0]-1)) if cb < b: self.SetPort() Qd.EraseRect((l, cb, cr, b)) self._list.LUpdate(self._parentwindow.wid.GetWindowPort().visRgn) Win.ValidRect(bounds) else: if self.drawingmode == 0 and self._list is not None: self._list.LSetDrawingMode(0) self.drawingmode = self.drawingmode + 1 testared = None def test(): global testared try: testared.close() del testared except: pass data = Numeric.reshape(Numeric.arange(200),(20,10)) testared = ArrayEditor(data) #test()