def __init__(self,master,width=600,height=400,startlayer=True,type=WindowTypes.singleuser,maxundo=40): BeeSessionState.__init__(self,master,width,height,type) qtgui.QWidget.__init__(self) self.localcommandstack=CommandStack(self,CommandStackTypes.singleuser,maxundo=maxundo) self.layerfinisher=LayerFinisher(qtcore.QRectF(0,0,width,height)) # initialize values self.zoom=1.0 self.ui=Ui_DrawingWindowMdi() self.ui.setupUi(self) self.activated=False self.backdrop=None self.filename="" self.cursoroverlay=None self.remotedrawingthread=None self.tooloverlay=None self.selection=None self.selectionlock=qtcore.QReadWriteLock() self.clippath=None self.clippathlock=qtcore.QReadWriteLock() self.localcommandqueue=Queue(0) # replace widget with my custom class widget self.scene=BeeCanvasScene(self) self.ui.PictureViewWidget=BeeCanvasView(self,self.ui.PictureViewWidget,self.scene) self.view=self.ui.PictureViewWidget #self.resizeViewToWindow() self.view.setCursor(master.getCurToolDesc().getCursor()) self.selectiondisplay=SelectedAreaDisplay(None,self.scene,self.view) self.scene.addItem(self.layerfinisher) # initiate drawing thread if type==WindowTypes.standaloneserver: self.localdrawingthread=DrawingThread(self.remotecommandqueue,self.id,type=ThreadTypes.server,master=master) else: self.localdrawingthread=DrawingThread(self.localcommandqueue,self.id,master=self.master) self.localdrawingthread.start() # for sending events to server so they don't slow us down locally self.sendtoserverqueue=None self.sendtoserverthread=None self.serverreadingthread=None # create a backdrop to be put at the bottom of all the layers self.recreateBackdrop() # put in starting blank layer if needed # don't go through the queue for this layer add because we need it to # be done before the next step if startlayer: self.insertLayer(self.nextLayerKey(),0,history=False) # have window get destroyed when it gets a close event self.setAttribute(qtcore.Qt.WA_DeleteOnClose) self.changeWindowTitle("Bee Canvas %d" % self.id) self.setupMenu()
class BeeDrawingWindow(qtgui.QWidget,BeeSessionState): """ Represents a window that the user can draw in """ def __init__(self,master,width=600,height=400,startlayer=True,type=WindowTypes.singleuser,maxundo=40): BeeSessionState.__init__(self,master,width,height,type) qtgui.QWidget.__init__(self) self.localcommandstack=CommandStack(self,CommandStackTypes.singleuser,maxundo=maxundo) self.layerfinisher=LayerFinisher(qtcore.QRectF(0,0,width,height)) # initialize values self.zoom=1.0 self.ui=Ui_DrawingWindowMdi() self.ui.setupUi(self) self.activated=False self.backdrop=None self.filename="" self.cursoroverlay=None self.remotedrawingthread=None self.tooloverlay=None self.selection=None self.selectionlock=qtcore.QReadWriteLock() self.clippath=None self.clippathlock=qtcore.QReadWriteLock() self.localcommandqueue=Queue(0) # replace widget with my custom class widget self.scene=BeeCanvasScene(self) self.ui.PictureViewWidget=BeeCanvasView(self,self.ui.PictureViewWidget,self.scene) self.view=self.ui.PictureViewWidget #self.resizeViewToWindow() self.view.setCursor(master.getCurToolDesc().getCursor()) self.selectiondisplay=SelectedAreaDisplay(None,self.scene,self.view) self.scene.addItem(self.layerfinisher) # initiate drawing thread if type==WindowTypes.standaloneserver: self.localdrawingthread=DrawingThread(self.remotecommandqueue,self.id,type=ThreadTypes.server,master=master) else: self.localdrawingthread=DrawingThread(self.localcommandqueue,self.id,master=self.master) self.localdrawingthread.start() # for sending events to server so they don't slow us down locally self.sendtoserverqueue=None self.sendtoserverthread=None self.serverreadingthread=None # create a backdrop to be put at the bottom of all the layers self.recreateBackdrop() # put in starting blank layer if needed # don't go through the queue for this layer add because we need it to # be done before the next step if startlayer: self.insertLayer(self.nextLayerKey(),0,history=False) # have window get destroyed when it gets a close event self.setAttribute(qtcore.Qt.WA_DeleteOnClose) self.changeWindowTitle("Bee Canvas %d" % self.id) self.setupMenu() # this is for debugging memory cleanup #def __del__(self): # print "DESTRUCTOR: bee drawing window" def setupMenu(self): menubar=qtgui.QMenuBar(self) replaceWidget(self.ui.menu_widget,menubar) self.ui.menu_widget=menubar # File Menu filemenu=menubar.addMenu("File") curaction=filemenu.addAction("New",self.on_action_File_New_triggered) curaction=filemenu.addAction("Open",self.on_action_File_Open_triggered) curaction=filemenu.addAction("Play",self.on_action_File_Play_triggered) curaction=filemenu.addAction("Connect",self.on_action_File_Connect_triggered) filemenu.addSeparator() curaction=filemenu.addAction("Save (Ctrl+S)",self.on_action_File_Save_triggered) curaction=filemenu.addAction("Save As (Ctrl+A)",self.on_action_File_Save_As_triggered) curaction=filemenu.addAction("Log (Ctrl+L)") curaction.setCheckable(True) qtcore.QObject.connect(curaction,qtcore.SIGNAL("toggled(bool)"),self.on_action_File_Log_toggled) self.logaction=curaction curaction=filemenu.addAction("Close",self.on_action_File_Close_triggered) # Edit menu editmenu=menubar.addMenu("Edit") curaction=editmenu.addAction("Undo (Ctrl+Z)",self.on_action_Edit_Undo_triggered) curaction=editmenu.addAction("Redo (Ctrl+R)",self.on_action_Edit_Redo_triggered) editmenu.addSeparator() curaction=editmenu.addAction("Cut (Ctrl+X)",self.on_action_Edit_Cut_triggered) curaction=editmenu.addAction("Copy (Ctrl+C)",self.on_action_Edit_Copy_triggered) curaction=editmenu.addAction("Paste (Ctrl+V)",self.on_action_Edit_Paste_triggered) #View menu viewmenu=menubar.addMenu("View") curaction=viewmenu.addAction("Zoom In (+)",self.on_action_View_Zoom_In_triggered) curaction=viewmenu.addAction("Zoom Out (-)",self.on_action_View_Zoom_Out_triggered) curaction=viewmenu.addAction("Zoom 1:1 (1)",self.on_action_View_Zoom_1_1_triggered) #Image menu imagemenu=menubar.addMenu("Image") self.imagemenu=imagemenu curaction=imagemenu.addAction("Canvas Size",self.on_action_Image_Canvas_Size_triggered) curaction=imagemenu.addAction("Scale Image",self.on_action_Image_Scale_Image_triggered) curaction=imagemenu.addAction("Flatten Image",self.on_action_Image_Flatten_Image_triggered) #Select menu selectmenu=menubar.addMenu("Select") curaction=selectmenu.addAction("Select None",self.on_action_Select_None_triggered) curaction=selectmenu.addAction("Invert Selection",self.on_action_Select_Invert_Selection_triggered) curaction=selectmenu.addAction("Grow Selection",self.on_action_Select_Grow_Selection_triggered) curaction=selectmenu.addAction("Shrink Selection",self.on_action_Select_Shrink_Selection_triggered) #Network menu #networkmenu=menubar.addMenu("Network") self.menubar=menubar def keyPressEvent(self,event): if event.modifiers()==qtcore.Qt.ControlModifier: if event.key()==qtcore.Qt.Key_Z: self.on_action_Edit_Undo_triggered() elif event.key()==qtcore.Qt.Key_R: self.on_action_Edit_Redo_triggered() elif event.key()==qtcore.Qt.Key_X: self.on_action_Edit_Cut_triggered() elif event.key()==qtcore.Qt.Key_C: self.on_action_Edit_Copy_triggered() elif event.key()==qtcore.Qt.Key_P: self.on_action_Edit_Paste_triggered() elif event.key()==qtcore.Qt.Key_S: self.on_action_File_Save_triggered() elif event.key()==qtcore.Qt.Key_A: self.on_action_File_Save_As_triggered() elif event.key()==qtcore.Qt.Key_L: self.logaction.toggle() else: if event.key()==qtcore.Qt.Key_Plus: self.on_action_View_Zoom_In_triggered() elif event.key()==qtcore.Qt.Key_Minus: self.on_action_View_Zoom_Out_triggered() elif event.key()==qtcore.Qt.Key_1: self.on_action_View_Zoom_1_1_triggered() def changeWindowTitle(self,name): self.setWindowTitle(name) self.menufocusaction.setText(name) def setFileName(self,filename): self.filename=filename self.changeWindowTitle(os.path.basename(str(filename))) def resetLayerZValues(self,lock=None): i=0 if not lock: lock=qtcore.QReadLocker(self.layerslistlock) for layer in self.layers: layer.setZValue(i) i+=1 self.layerfinisher.setZValue(i) i+=1 if self.selectiondisplay: self.selectiondisplay.setZValue(i) i+=1 if self.tooloverlay: self.tooloverlay.setZValue(i) i+=1 self.scene.update() def displayMessage(self,boxtype,title,message): if boxtype==BeeDisplayMessageTypes.warning: qtgui.QMessageBox.warning(self,title,message) elif boxtype==BeeDisplayMessageTypes.error: qtgui.QMessageBox.critical(self,title,message) def changeToolOverlay(self,overlay=None): lock=qtcore.QWriteLocker(self.layerslistlock) if self.tooloverlay: self.scene.removeItem(self.tooloverlay) self.tooloverlay=None if overlay: self.scene.addItem(overlay) self.tooloverlay=overlay self.resetLayerZValues(lock) def saveFile(self,filename): """ save current state of session to file """ # if we are saving my custom format if filename.endsWith(".bee"): self.startLog(filename,True) # my custom format is a pickled list of tuples containing: # a compressed qbytearray with PNG data, opacity, visibility, blend mode #l=[] # first item in list is file format version and size of image #l.append((BEE_FILE_FORMAT_VERSION,self.docwidth,self.docheight)) #for layer in self.layers: # bytearray=qtcore.QByteArray() # buf=qtcore.QBuffer(bytearray) # buf.open(qtcore.QIODevice.WriteOnly) # layer.image.save(buf,"PNG") # add gzip compression to byte array # bytearray=qtcore.qCompress(bytearray) # l.append((bytearray,layer.opacity,layer.visible,layer.compmode)) #f=open(filename,"w") #pickle.dump(l,f) # for all other formats just use the standard qt image writer else: writer=qtgui.QImageWriter(filename) writer.write(self.scene.getImageCopy()) def scaleCanvas(self,newwidth,newheight,history=True): sizelock=qtcore.QWriteLocker(self.docsizelock) BeeSessionState.scaleCanvas(self,newwidth,newheight,sizelock,history) self.scene.setCanvasSize(newwidth,newheight) def adjustCanvasSize(self,leftadj,topadj,rightadj,bottomadj,sizelock=None,history=True): # lock the image so no updates can happen in the middle of this if not sizelock: sizelock=qtcore.QWriteLocker(self.docsizelock) if history: historyevent=AdjustCanvasSizeCommand(self.docwidth,self.docheight,leftadj,topadj,rightadj,bottomadj,self.layers) self.addCommandToHistory(historyevent) self.docwidth=self.docwidth+leftadj+rightadj self.docheight=self.docheight+topadj+bottomadj # adjust size of all the layers for layer in self.layers: layer.adjustCanvasSize(leftadj,topadj,rightadj,bottomadj) # finally resize the widget and update image self.scene.adjustCanvasSize(leftadj,topadj,rightadj,bottomadj) self.reCompositeImage() # update all layer preview thumbnails self.master.refreshLayerThumb(self.id) def getClipPathCopy(self): cliplock=qtcore.QReadLocker(self.clippathlock) if self.clippath: return qtgui.QPainterPath(self.clippath) return None # update the clipping path to match the current selection def updateClipPath(self,slock=None): """ updates the clip path to match current selections, should be called every time selections are updated """ if not slock: slock=qtcore.QReadLocker(self.selectionlock) cliplock=qtcore.QWriteLocker(self.clippathlock) if not self.selection: self.clippath=None return self.clippath=qtgui.QPainterPath(self.selection) def penDown(self,x,y,pressure,modkeys,tool=None,source=ThreadTypes.user): if not tool: tool=self.master.getCurToolInst(self) self.curtool=tool self.curtool.guiLevelPenDown(x,y,pressure,modkeys) if not self.curtool.layerkey: return layer=self.getLayerForKey(self.curtool.layerkey) if not layer: return if layer.type==LayerTypes.user or ( layer.type==LayerTypes.floating and self.curtool.allowedonfloating): self.addPenDownToQueue(x,y,pressure,tool.layerkey,tool,source,modkeys=modkeys) def penMotion(self,x,y,pressure,modkeys,source=ThreadTypes.user): #print "window pen motion: (x,y,pressure):", x,y,pressure if self.curtool: self.curtool.guiLevelPenMotion(x,y,pressure,modkeys) layer=self.getLayerForKey(self.curtool.layerkey) if not layer: return if layer.type==LayerTypes.user or ( layer.type==LayerTypes.floating and self.curtool.allowedonfloating): self.addPenMotionToQueue(x,y,pressure,self.curtool.layerkey,source,modkeys=modkeys) def penUp(self,x,y,modkeys,source=ThreadTypes.user): if self.curtool: self.curtool.guiLevelPenUp(x,y,modkeys) layer=self.getLayerForKey(self.curtool.layerkey) if not layer: return if layer.type==LayerTypes.user or ( layer.type==LayerTypes.floating and self.curtool.allowedonfloating): self.addPenUpToQueue(x,y,self.curtool.layerkey,source,modkeys=modkeys) self.curtool=None def requestUpdateSelectionDisplayPath(self,path=None): event=SelectionDisplayUpdateEvent(path) BeeApp().app.postEvent(self,event) # change the current selection path, and update to screen to show it def changeSelection(self,type,newarea=None,slock=None,history=True): if not slock: slock=qtcore.QWriteLocker(self.selectionlock) if self.selection: oldpath=qtgui.QPainterPath(self.selection) else: oldpath=None defaultreturn=oldpath,oldpath dirtyregion=qtgui.QRegion() # if we get a clear operation clear the seleciton and outline then return if type==SelectionModTypes.clear: # in this case there already is no selection so just ignore it if not self.selection: return defaultreturn if self.selection: dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) self.selection=None self.updateClipPath(slock=slock) self.requestUpdateSelectionDisplayPath() elif type==SelectionModTypes.setlist: if self.selection: dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) self.selection=newarea if self.selection: dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) self.updateClipPath(slock=slock) self.requestUpdateSelectionDisplayPath(self.clippath) elif type==SelectionModTypes.invert: sizelock=qtcore.QReadLocker(self.docsizelock) width,height=self.getDocSize(sizelock) rect=qtcore.QRectF(0,0,width,height) newpath=qtgui.QPainterPath() newpath.addRect(rect) if self.selection: newpath=newpath.subtracted(self.selection) self.selection=newpath self.updateClipPath(slock=slock) self.requestUpdateSelectionDisplayPath(self.clippath) elif type==SelectionModTypes.shrink or type==SelectionModTypes.grow: if self.selection: stroker=qtgui.QPainterPathStroker() stroker.setWidth(2*newarea) stroker.setJoinStyle(qtcore.Qt.MiterJoin) growpath=stroker.createStroke(self.selection) if type==SelectionModTypes.grow: self.selection=self.selection.united(growpath) # make sure the selection hasn't grown larger than the full image width,height=self.getDocSize() rect=qtcore.QRectF(0,0,width,height) fulldocpath=qtgui.QPainterPath() fulldocpath.addRect(rect) self.selection=self.selection.intersected(fulldocpath) dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) elif type==SelectionModTypes.shrink: dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) self.selection=self.selection.subtracted(growpath) if self.selection.isEmpty(): self.selection=None self.updateClipPath(slock=slock) self.requestUpdateSelectionDisplayPath(self.clippath) else: # in all thses cases the new area argument can be implied to be the cursor overlay, but we need one or the other if not self.cursoroverlay and not newarea: return defaultreturn else: if not newarea: newarea=qtgui.QPainterPath(self.cursoroverlay.path) if type==SelectionModTypes.new or not self.selection: dirtyregion=dirtyregion.united(qtgui.QRegion(newarea.boundingRect().toAlignedRect())) if self.selection: dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) self.selection=newarea elif type==SelectionModTypes.add: dirtyregion=dirtyregion.united(qtgui.QRegion(newarea.boundingRect().toAlignedRect())) # the new area completely contains this path so just go with the new one if newarea.contains(self.selection): self.selection=newarea # the new area is inside the old one so no change elif self.selection.contains(newarea): pass # if they intersect union the areas elif newarea.intersects(self.selection): self.selection=newarea.united(self.selection) # otherwise they are completely disjoint so just add it separately else: self.selection.addPath(newarea) elif type==SelectionModTypes.subtract: dirtyregion=dirtyregion.united(qtgui.QRegion(newarea.boundingRect().toAlignedRect())) # the new area completely contains the new path then deselect everything if newarea.contains(self.selection): self.selection=None # if they intersect subtract the areas elif newarea.intersects(self.selection) or self.selection.contains(newarea): self.selection=self.selection.subtracted(newarea) elif type==SelectionModTypes.intersect: dirtyregion=dirtyregion.united(qtgui.QRegion(newarea.boundingRect().toAlignedRect())) dirtyregion=dirtyregion.united(qtgui.QRegion(self.selection.boundingRect().toAlignedRect())) if newarea.contains(self.selection): pass elif newarea.intersects(self.selection) or self.selection.contains(newarea): self.selection=self.selection.intersected(newarea) else: self.selection=None else: print_debug("unrecognized selection modification type: %d" % type) self.updateClipPath(slock=slock) self.requestUpdateSelectionDisplayPath(self.clippath) # now update screen as needed if not dirtyregion.isEmpty(): dirtyrect=dirtyregion.boundingRect() dirtyrect.adjust(-1,-1,2,2) self.view.updateView(dirtyrect) if history: command=ChangeSelectionCommand(oldpath,self.selection) self.addCommandToHistory(command) return oldpath,self.selection def queueCommand(self,command,source=ThreadTypes.user,owner=0): if source==ThreadTypes.user: #print "putting command in local queue" self.localcommandqueue.put(command) elif source==ThreadTypes.server: #print "putting command in routing queue" #self.master.routinginput.put((command,owner)) self.remotecommandqueue.put(command) else: #print "putting command in remote queue:", command, self.remotecommandqueue self.remotecommandqueue.put(command) # send event to GUI to update the list of current layers def requestLayerListRefresh(self,lock=None): self.resetLayerZValues(lock) event=qtcore.QEvent(BeeCustomEventTypes.refreshlayerslist) BeeApp().app.postEvent(self.master,event) def layerDownPushed(self): layer=self.getCurLayer() if layer: if layer.type==LayerTypes.floating: parent=layer.parentItem() lock=qtcore.QReadLocker(self.layerslistlock) if parent in self.layers: index=self.layers.index(parent) while index>0: index-=1 if self.ownedByMe(self.layers[index].owner): layer.setParentItem(self.layers[index]) self.scene.update() self.master.refreshLayersList(layerslock=lock) self.addFloatingLayerMoveToQueue(layer.key,parent.key,self.layers[index].key) break else: self.addLayerDownToQueue(layer.key) def addFloatingLayerMoveToQueue(self,layerkey,oldparentkey,newparentkey): self.queueCommand((DrawingCommandTypes.localonly,LocalOnlyCommandTypes.floatingmove,layerkey,oldparentkey,newparentkey),ThreadTypes.user) def layerUpPushed(self): layer=self.getCurLayer() if layer: if layer.type==LayerTypes.floating: parent=layer.parentItem() lock=qtcore.QReadLocker(self.layerslistlock) if parent in self.layers: index=self.layers.index(parent) index+=1 while index<len(self.layers): if self.ownedByMe(self.layers[index].owner): layer.setParentItem(self.layers[index]) self.scene.update() self.master.refreshLayersList(layerslock=lock) self.addFloatingLayerMoveToQueue(layer.key,parent.key,self.layers[index].key) break index+=1 else: self.addLayerUpToQueue(layer.key) def removeLayer(self,layer,history=True,listlock=None): index=None if not listlock: listlock=qtcore.QWriteLocker(self.layerslistlock) if layer.type==LayerTypes.floating: index=-1 self.scene.removeItem(layer) self.scene.update() if checkvalid: self.setValidActiveLayer(True,listlock=listlock) self.requestLayerListRefresh(listlock) else: (layer,index)=BeeSessionState.removeLayer(self,layer,history=history,listlock=listlock) if layer: self.scene.removeItem(layer) self.scene.update() self.setValidActiveLayer(True,listlock=listlock) return layer,index def insertRawLayer(self,layer,index,listlock=None): if not listlock: listlock=qtcore.QWriteLocker(self.layerslistlock) try: self.layers.insert(index,layer) except: self.layers.append(layer) self.scene.addItem(layer) listlock.unlock() self.setValidActiveLayer() self.requestLayerListRefresh() self.reCompositeImage() def insertLayer(self,key,index,type=LayerTypes.user,image=None,opacity=None,visible=None,compmode=None,owner=0,history=True,lock=None): if not lock: lock=qtcore.QWriteLocker(self.layerslistlock) # make sure layer doesn't exist already oldlayer=self.getLayerForKey(key,lock=lock) if oldlayer: print_debug("ERROR: tried to create layer with same key as existing layer") return layer=BeeGuiLayer(self.id,type,key,image,opacity=opacity,visible=visible,compmode=compmode,owner=owner) try: self.layers.insert(index,layer) except: self.layers.append(layer) # only add command to history if we are in a local session if self.type==WindowTypes.singleuser and history: self.addCommandToHistory(AddLayerCommand(layer.key)) self.scene.addItem(layer) lock.unlock() self.setValidActiveLayer() self.requestLayerListRefresh() self.reCompositeImage() # recomposite all layers together into the displayed image # when a thread calls this method it shouldn't have a lock on any layers def reCompositeImage(self,dirtyrect=None): if dirtyrect: self.view.updateView(qtcore.QRectF(dirtyrect)) else: self.view.updateView() return def getImagePixelColor(self,x,y,size=1): return self.scene.getPixelColor(x,y,size) def getCurLayerPixelColor(self,x,y,size=1): key=self.getCurLayerKey() curlayer=self.getLayerForKey(key) if curlayer: return curlayer.getPixelColor(x,y,size) else: return qtgui.QColor() def startRemoteDrawingThreads(self): pass def mdiWinStateChange(self,oldstate,newstate): if newstate & qtcore.Qt.WindowActive: self.master.takeFocus(self) # handle a few events that don't have easy function over loading front ends def event(self,event): # do the last part of setup when the window is done being created, this is so nothing starts drawing on the screen before it is ready if event.type()==qtcore.QEvent.WindowActivate: if self.activated==False: self.activated=True self.reCompositeImage() self.startRemoteDrawingThreads() elif event.type()==qtcore.QEvent.Show: if self.activated==False: self.activated=True self.reCompositeImage() self.startRemoteDrawingThreads() elif event.type()==BeeCustomEventTypes.displaymessage: self.displayMessage(event.boxtype,event.title,event.message) elif event.type()==BeeCustomEventTypes.updateselectiondisplay: self.selectiondisplay.updatePath(event.path) # once the window has received a deferred delete it needs to have all it's references removed so memory can be freed up elif event.type()==qtcore.QEvent.DeferredDelete: self.cleanUp() return qtgui.QWidget.event(self,event) # get the current layer key def getCurLayerKey(self,curlayerlock=None): if not curlayerlock: curlayerlock=qtcore.QMutexLocker(self.curlayerkeymutex) return self.curlayerkey def findValidLayer(self,layerslock=None): if not layerslock: layerslock=qtcore.QReadLocker(self.layerslistlock) for layer in self.layers: if self.ownedByMe(self.layer.owner): return layer return None def getCurLayer(self): if self.layers: if self.getLayerForKey(self.curlayerkey): return self.getLayerForKey(self.curlayerkey) return None # not sure how useful these will be, but just in case a tool wants to do something special when it leaves the drawable area they are here def penEnter(self): if self.curtool: self.curtool.penEnter() def penLeave(self): if self.curtool: self.curtool.penLeave() def resizeViewToWindow(self): cw=self.ui.centralwidget geo=cw.geometry() mbgeo=self.ui.menubar.geometry() x=geo.x() y=geo.y() width=geo.width() height=geo.height()-mbgeo.height() self.view.setGeometry(x,y,width,height) # respond to menu item events in the drawing window def on_action_Edit_Cut_triggered(self,accept=True): if accept: self.addCutToQueue() def on_action_Edit_Copy_triggered(self,accept=True): if accept: self.addCopyToQueue() def on_action_Edit_Paste_triggered(self,accept=True): if accept: x,y=self.view.snapPointToView(0,0) self.addPasteToQueue(x,y) def on_action_Edit_Undo_triggered(self,accept=True): if accept: self.addUndoToQueue() def on_action_Edit_Redo_triggered(self,accept=True): if accept: self.addRedoToQueue() def on_action_Select_None_triggered(self,accept=True): if accept: self.addSelectionChangeToQueue(SelectionModTypes.clear,None) def on_action_Select_Invert_Selection_triggered(self,accept=True): if accept: self.addSelectionChangeToQueue(SelectionModTypes.invert,None) def on_action_Select_Grow_Selection_triggered(self,accept=True): if accept: dialog=qtgui.QDialog(self) dialog.ui=Ui_Grow_Selection_Dialog() dialog.ui.setupUi(dialog) dialog.exec_() if dialog.result(): pixels=dialog.ui.SpinBox_grow.value() self.addSelectionChangeToQueue(SelectionModTypes.grow,pixels) def on_action_Select_Shrink_Selection_triggered(self,accept=True): if accept: dialog=qtgui.QDialog(self) dialog.ui=Ui_Shrink_Selection_Dialog() dialog.ui.setupUi(dialog) dialog.exec_() if dialog.result(): pixels=dialog.ui.SpinBox_shrink.value() self.addSelectionChangeToQueue(SelectionModTypes.shrink,pixels) def on_action_View_Zoom_In_triggered(self,accept=True): if accept: #self.zoom*=1.25 self.zoom*=2 self.view.newZoom(self.zoom) def on_action_View_Zoom_Out_triggered(self,accept=True): if accept: #self.zoom/=1.25 self.zoom/=2 self.view.newZoom(self.zoom) def on_action_View_Zoom_1_1_triggered(self,accept=True): if accept: self.zoom=1.0 self.view.newZoom(self.zoom) def on_action_Image_Scale_Image_triggered(self,accept=True): if accept: width,height=self.getDocSize() dialog=BeeScaleImageDialog(self,width,height) dialog.exec_() if dialog.result(): newwidth=dialog.ui.width_spin_box.value() newheight=dialog.ui.height_spin_box.value() self.addScaleCanvasToQueue(newwidth,newheight) def on_action_Image_Canvas_Size_triggered(self,accept=True): if accept: dialog=CanvasAdjustDialog(self) # if the canvas is in any way shared don't allow changing the top or left # so no other lines in queue will be messed up if self.type!=WindowTypes.singleuser: dialog.ui.Left_Adjust_Box.setDisabled(True) dialog.ui.Top_Adjust_Box.setDisabled(True) dialog.exec_() if dialog.result(): leftadj=dialog.leftadj topadj=dialog.topadj rightadj=dialog.rightadj bottomadj=dialog.bottomadj self.addAdjustCanvasSizeRequestToQueue(leftadj,topadj,rightadj,bottomadj) def on_action_Image_Flatten_Image_triggered(self,accept=True): if accept: self.addFlattenImageToQueue() def flattenImage(self,listlock=None,history=True): # lock the list of layers and all the layer images if not listlock: listlock=qtcore.QWriteLocker(self.layerslistlock) # we need at least two layers and the layer finisher layer to have this operation actually do something if len(self.scene.items())<3: return layerlocks=[] for l in self.layers: layerlocks.append(qtcore.QReadLocker(l.imagelock)) sceneimage=self.scene.getImageCopy() newkey=self.nextLayerKey() newlayer=BeeGuiLayer(self.id,LayerTypes.user,newkey,sceneimage) oldlayers=[] layers=self.layers[:] for l in layers: layer,index=self.removeLayer(l,history=False,listlock=listlock) oldlayers.append(layer) newlayer.image=sceneimage self.scene.update() self.insertLayer(self.nextLayerKey(),0,history=False,image=sceneimage,lock=listlock) if history: historyevent=FlattenImageCommand(oldlayers) self.addCommandToHistory(historyevent) def addSelectionChangeToQueue(self,selectionop,path): self.queueCommand((DrawingCommandTypes.localonly,LocalOnlyCommandTypes.selection,selectionop,path),ThreadTypes.user) def addPasteToQueue(self,x=0,y=0): # It is only possible for this to happen from a local source so it's defined here instead of in the base state class. layerkey=self.getCurLayerKey() # don't do anything if there is no current layer if layerkey: # make sure layer is owned locally so it can be altered if self.localLayer(layerkey): self.queueCommand((DrawingCommandTypes.layer,LayerCommandTypes.paste,layerkey,x,y),ThreadTypes.user) def addCopyToQueue(self): # It is only possible for this to happen from a local source so it's defined here instead of in the base state class. layerkey=self.getCurLayerKey() # don't do anything if there is no current layer if layerkey: path=self.getClipPathCopy() if path and not path.isEmpty(): self.queueCommand((DrawingCommandTypes.layer,LayerCommandTypes.copy,layerkey,path),ThreadTypes.user) def addCutToQueue(self): # It is only possible for this to happen from a local source so it's defined here instead of in the base state class. layerkey=self.getCurLayerKey() # make sure current layer is valid if layerkey: # make sure layer is owned locally so it can be altered if self.localLayer(layerkey): clippath=self.getClipPathCopy() if clippath and not clippath.isEmpty(): self.queueCommand((DrawingCommandTypes.layer,LayerCommandTypes.cut,layerkey,clippath),ThreadTypes.user) # deselect everything when we do this self.changeSelection(SelectionModTypes.clear,history=False) def addAnchorToQueue(self,parentkey,floating): pos=floating.pos() x=pos.x() y=pos.y() image=floating.getImageCopy() clippath=None compmode=floating.getCompmode() alphachannel=qtgui.QImage(image.size(),qtgui.QImage.Format_ARGB32_Premultiplied) # fade image if the opacity is less than full alphaammount=int(255*floating.getOpacity()) if alphaammount < 255: alphachannel.fill(qtgui.QColor(0,0,0,alphaammount).rgba()) #image.setAlphaChannel(alphachannel) painter=qtgui.QPainter() painter.begin(image) painter.setCompositionMode(qtgui.QPainter.CompositionMode_DestinationIn) painter.drawImage(0,0,alphachannel) painter.end() self.queueCommand((DrawingCommandTypes.layer,LayerCommandTypes.anchor,parentkey,x,y,image,clippath,compmode,floating),ThreadTypes.user) # create backdrop for bottom of all layers, eventually I'd like this to be configurable, but for now it just fills in all white def recreateBackdrop(self): self.backdrop=qtgui.QImage(self.docwidth,self.docheight,qtgui.QImage.Format_ARGB32_Premultiplied) self.backdrop.fill(self.backdropcolor) def on_action_File_Log_toggled(self,state): """If log box is now checked ask user to provide log file name and start a log file for the current session from this point If log box is now unchecked end the current log file """ if state: filename=qtgui.QFileDialog.getSaveFileName(self.master,"Choose File Name",".","Logfiles (*.slg)") if not filename: return self.startLog(filename) else: self.endLog() def on_action_File_New_triggered(self,accept=True): if not accept: return self.master.on_action_File_New_triggered() def on_action_File_Open_triggered(self,accept=True): if not accept: return self.master.on_action_File_Open_triggered() def on_action_File_Play_triggered(self,accept=True): if not accept: return self.master.on_action_File_Play_triggered() def on_action_File_Connect_triggered(self,accept=True): if not accept: return self.master.on_action_File_Connect_triggered() def on_action_File_Save_triggered(self,accept=True): if not self.filename: self.on_action_File_Save_As_triggered() return self.saveFile(self.filename) def on_action_File_Save_As_triggered(self,accept=True): if not accept: return filterstring=qtcore.QString("Images (") formats=getSupportedWriteFileFormats() for f in formats: filterstring.append(" *.") filterstring.append(f) # add in extension for custom file format filterstring.append(" *.bee)") filename=qtgui.QFileDialog.getSaveFileName(self.master,"Choose File Name",".",filterstring) if filename: self.saveFile(filename) self.setFileName(filename) def on_action_File_Close_triggered(self,accept=True): if accept: self.closeDrawingWindow() def closeDrawingWindow(self): parent=self.parentWidget() if parent: parent.close() # this is here because the window doesn't seem to get deleted when it's closed # the cleanUp function attempts to clean up as much memory as possible def cleanUp(self): #print "ref counts at beginning of cleanup:", sys.getrefcount(self) # end the log if there is one self.endLog() self.selectiondisplay.cleanUp() self.localdrawingthread.addExitEventToQueue() if not self.localdrawingthread.wait(10000): print_debug("WARNING: drawing thread did not terminate on time") # if we started a remote drawing thread kill it if self.remotedrawingthread: self.remotedrawingthread.addExitEventToQueue() if not self.remotedrawingthread.wait(20000): print_debug("WARNING: remote drawing thread did not terminate on time") self.scene.removeItem(self.selectiondisplay) self.selectiondisplay=None self.scene.removeItem(self.layerfinisher) if self.tooloverlay: self.scene.removeItem(self.tooloverlay) # this should be the last referece to the window self.master.unregisterWindow(self) self.localcommandstack=None #print "ref counts after cleanup:", sys.getrefcount(self) # just in case someone lets up on the cursor when outside the drawing area this will make sure it's caught def tabletEvent(self,event): if event.type()==qtcore.QEvent.TabletRelease: self.view.cursorReleaseEvent(event.x(),event.y(),event.modifiers()) return qtgui.QWidget.tabletEvent(self,event) def getLayerForKey(self,key,lock=None): if key==None: return None if not lock: lock=qtcore.QReadLocker(self.layerslistlock) for layer in self.layers: if layer.key==key: return layer for child in layer.childItems(): if child.key==key: return child print_debug("WARNING: could not find layer for key %d" % key ) return None def findValidLayer(self,listlock=None): if not listlock: listlock=qtcore.QReadLocker(self.layerslistlock) for curlayer in self.layers: if self.ownedByMe(curlayer.getOwner()): return curlayer return None def setValidActiveLayer(self,curlayerkeylock=None,listlock=None): needchange=False if not curlayerkeylock: curlayerkeylock=qtcore.QMutexLocker(self.curlayerkeymutex) curlayer=self.getLayerForKey(self.curlayerkey,listlock) if not curlayer: needchange=True elif self.type==WindowTypes.networkclient: if not self.ownedByMe(curlayer.getOwner()): needchange=True if needchange: if not listlock: listlock=qtcore.QReadLocker(self.layerslistlock) for layer in self.layers: if self.ownedByMe(layer.getOwner()): self.setActiveLayer(layer.key,curlayerkeylock) return layer.key self.setActiveLayer(None,curlayerkeylock) return None return self.curlayerkey def setActiveLayer(self,newkey,lock=None): if not lock: lock=qtcore.QMutexLocker(self.curlayerkeymutex) oldkey=self.curlayerkey oldkey=self.getCurLayerKey(lock) self.curlayerkey=newkey self.master.updateLayerHighlight(self,newkey,lock) self.master.updateLayerHighlight(self,oldkey,lock) def switchAllLayersToLocal(self): lock=qtcore.QReadLocker(self.layerslistlock) for layer in self.layers: layer.type=LayerTypes.user layer.changeName("Layer: %d" % layer.key) # delete all layers def clearAllLayers(self): # lock all layers and the layers list lock=qtcore.QWriteLocker(self.layerslistlock) for layer in self.layers[:]: self.removeLayer(layer,history=False,lock=lock) self.layers=[] lock.unlock() self.requestLayerListRefresh() self.reCompositeImage()