Exemple #1
0
 def __init__(self, path, manager):
     QtCore.QObject.__init__(self)
     self.manager = manager
     self.delayedChanges = []
     self.path = os.path.abspath(path)
     self.parentDir = None
     self.lock = Mutex(QtCore.QMutex.Recursive)
     self.sigproxy = SignalProxy(self.sigChanged, slot=self.delayedChange)
Exemple #2
0
 def __init__(self, path, manager):
     QtCore.QObject.__init__(self)
     self.manager = manager
     self.delayedChanges = []
     self.path = os.path.abspath(path)
     self.parentDir = None
     #self.lock = threading.RLock()
     self.lock = Mutex(QtCore.QMutex.Recursive)
     self.sigproxy = SignalProxy(self.sigChanged, slot=self.delayedChange)
Exemple #3
0
    def __init__(self, camera, module):
        CameraModuleInterface.__init__(self, camera, module)

        self.module = module
        self.view = module.getView()
        self.hasQuit = False
        self.boundaryItems = {}

        ## setup UI
        self.ui = CameraInterfaceTemplate()
        self.widget = dockarea.DockArea()
        w = QtGui.QWidget()
        self.ui.setupUi(w)

        # takes care of displaying image data,
        # contrast & background subtraction user interfaces
        self.imagingCtrl = ImagingCtrl()
        self.frameDisplay = self.imagingCtrl.frameDisplay

        ## Move control panels into docks
        recDock = dockarea.Dock(name="Recording",
                                widget=self.imagingCtrl,
                                size=(100, 10),
                                autoOrientation=False)
        devDock = dockarea.Dock(name="Device Control",
                                widget=self.ui.devCtrlWidget,
                                size=(100, 10),
                                autoOrientation=False)
        dispDock = dockarea.Dock(name="Display Control",
                                 widget=self.frameDisplay.contrastWidget(),
                                 size=(100, 600),
                                 autoOrientation=False)
        bgDock = dockarea.Dock(name="Background Subtraction",
                               widget=self.frameDisplay.backgroundWidget(),
                               size=(100, 10),
                               autoOrientation=False)
        self.widget.addDock(recDock)
        self.widget.addDock(devDock, 'bottom', recDock)
        self.widget.addDock(dispDock, 'bottom', devDock)
        self.widget.addDock(bgDock, 'bottom', dispDock)

        ## Camera state variables
        self.cam = camera
        self.roi = None
        self.exposure = 0.001
        self.binning = 1
        self.region = None

        ## set up item groups
        self.cameraItemGroup = pg.ItemGroup(
        )  ## translated with scope, scaled with camera objective
        self.imageItemGroup = pg.ItemGroup(
        )  ## translated and scaled as each frame arrives
        self.view.addItem(self.imageItemGroup)
        self.view.addItem(self.cameraItemGroup)
        self.cameraItemGroup.setZValue(0)
        self.imageItemGroup.setZValue(-2)

        ## video image item
        self.imageItem = self.frameDisplay.imageItem()
        self.view.addItem(self.imageItem)
        self.imageItem.setParentItem(self.imageItemGroup)
        self.imageItem.setZValue(-10)

        ## open camera, determine bit depth and sensor area
        self.openCamera()

        ## Initialize values
        self.lastCameraPosition = Point(self.camSize[0] * 0.5,
                                        self.camSize[1] * 0.5)
        self.lastCameraScale = Point(1.0, 1.0)
        self.scopeCenter = [self.camSize[0] * 0.5, self.camSize[1] * 0.5]
        self.cameraScale = [1, 1]

        ## Camera region-of-interest control
        self.roi = CamROI(self.camSize, parent=self.cameraItemGroup)
        self.roi.sigRegionChangeFinished.connect(self.regionWidgetChanged)
        self.roi.setZValue(-1)
        self.setRegion()

        ## Set up microscope objective borders
        self.borders = CameraItemGroup(self.cam)
        self.module.addItem(self.borders)
        self.borders.setZValue(-1)

        self.cam.sigGlobalTransformChanged.connect(self.globalTransformChanged)

        self.globalTransformChanged()

        # initially set binning and exposure from camera state
        self.exposure = self.cam.getParam('exposure')
        self.binning = self.cam.getParam('binning')[0]
        ## Initialize values/connections in Camera Dock
        self.setUiBinning(self.binning)
        self.ui.spinExposure.setValue(self.exposure)
        self.ui.spinExposure.setOpts(dec=True,
                                     step=1,
                                     minStep=100e-6,
                                     siPrefix=True,
                                     suffix='s',
                                     bounds=[0, 10])

        #Signals from self.ui.btnSnap and self.ui.recordStackBtn are caught by the RecordThread
        self.ui.btnFullFrame.clicked.connect(lambda: self.setRegion())
        self.proxy1 = SignalProxy(self.ui.binningCombo.currentIndexChanged,
                                  slot=self.binningComboChanged)
        self.ui.spinExposure.valueChanged.connect(
            self.setExposure
        )  ## note that this signal (from acq4.util.SpinBox) is delayed.

        ## Signals from Camera device
        self.cam.sigNewFrame.connect(self.newFrame)
        self.cam.sigCameraStopped.connect(self.cameraStopped)
        self.cam.sigCameraStarted.connect(self.cameraStarted)
        self.cam.sigShowMessage.connect(self.showMessage)

        self.frameDisplay.imageUpdated.connect(self.imageUpdated)
        self.imagingCtrl.sigStartVideoClicked.connect(self.startAcquireClicked)
        self.imagingCtrl.sigStopVideoClicked.connect(self.stopAcquireClicked)
        self.imagingCtrl.ui.acquireFrameBtn.setEnabled(False)
Exemple #4
0
class CameraInterface(CameraModuleInterface):
    """
    This class provides all the functionality necessary for a camera to display 
    images and controls within the camera module's main window. Each camera that 
    connects to a camera module must implement an instance of this interface.
    
    The interface provides a control GUI via the controlWidget() method and 
    directly manages its own GraphicsItems within the camera module's view box.
    """

    sigNewFrame = Qt.Signal(object, object)  # self, frame

    def __init__(self, camera, module):
        CameraModuleInterface.__init__(self, camera, module)

        self.module = module
        self.view = module.getView()
        self.hasQuit = False
        self.boundaryItems = {}

        ## setup UI
        self.ui = CameraInterfaceTemplate()
        self.widget = dockarea.DockArea()
        w = Qt.QWidget()
        self.ui.setupUi(w)

        # takes care of displaying image data,
        # contrast & background subtraction user interfaces
        self.imagingCtrl = ImagingCtrl()
        self.frameDisplay = self.imagingCtrl.frameDisplay

        ## Move control panels into docks
        recDock = dockarea.Dock(name="Recording",
                                widget=self.imagingCtrl,
                                size=(100, 10),
                                autoOrientation=False)
        devDock = dockarea.Dock(name="Device Control",
                                widget=self.ui.devCtrlWidget,
                                size=(100, 10),
                                autoOrientation=False)
        dispDock = dockarea.Dock(name="Display Control",
                                 widget=self.frameDisplay.contrastWidget(),
                                 size=(100, 600),
                                 autoOrientation=False)
        bgDock = dockarea.Dock(name="Background Subtraction",
                               widget=self.frameDisplay.backgroundWidget(),
                               size=(100, 10),
                               autoOrientation=False)
        self.widget.addDock(recDock)
        self.widget.addDock(devDock, 'bottom', recDock)
        self.widget.addDock(dispDock, 'bottom', devDock)
        self.widget.addDock(bgDock, 'bottom', dispDock)

        ## Camera state variables
        self.cam = camera
        self.roi = None
        self.exposure = 0.001
        self.binning = 1
        self.region = None

        ## set up item groups
        self.cameraItemGroup = pg.ItemGroup(
        )  ## translated with scope, scaled with camera objective
        self.imageItemGroup = pg.ItemGroup(
        )  ## translated and scaled as each frame arrives
        self.view.addItem(self.imageItemGroup)
        self.view.addItem(self.cameraItemGroup)
        self.cameraItemGroup.setZValue(0)
        self.imageItemGroup.setZValue(-2)

        ## video image item
        self.imageItem = self.frameDisplay.imageItem()
        self.view.addItem(self.imageItem)
        self.imageItem.setParentItem(self.imageItemGroup)
        self.imageItem.setZValue(-10)

        ## open camera, determine bit depth and sensor area
        self.openCamera()

        ## Initialize values
        self.lastCameraPosition = Point(self.camSize[0] * 0.5,
                                        self.camSize[1] * 0.5)
        self.lastCameraScale = Point(1.0, 1.0)
        self.scopeCenter = [self.camSize[0] * 0.5, self.camSize[1] * 0.5]
        self.cameraScale = [1, 1]

        ## Camera region-of-interest control
        self.roi = CamROI(self.camSize, parent=self.cameraItemGroup)
        self.roi.sigRegionChangeFinished.connect(self.regionWidgetChanged)
        self.roi.setZValue(-1)
        self.setRegion()

        ## Set up microscope objective borders
        self.borders = CameraItemGroup(self.cam)
        self.module.addItem(self.borders)
        self.borders.setZValue(-1)

        self.cam.sigGlobalTransformChanged.connect(self.globalTransformChanged)

        self.globalTransformChanged()

        # initially set binning and exposure from camera state
        self.exposure = self.cam.getParam('exposure')
        self.binning = self.cam.getParam('binning')[0]
        ## Initialize values/connections in Camera Dock
        self.setUiBinning(self.binning)
        self.ui.spinExposure.setValue(self.exposure)
        self.ui.spinExposure.setOpts(dec=True,
                                     step=1,
                                     minStep=100e-6,
                                     siPrefix=True,
                                     suffix='s',
                                     bounds=[0, 10])

        #Signals from self.ui.btnSnap and self.ui.recordStackBtn are caught by the RecordThread
        self.ui.btnFullFrame.clicked.connect(lambda: self.setRegion())
        self.binningComboProxy = SignalProxy(
            self.ui.binningCombo.currentIndexChanged,
            slot=self.binningComboChanged)
        self.ui.spinExposure.valueChanged.connect(
            self.setExposure
        )  ## note that this signal (from acq4.util.SpinBox) is delayed.

        ## Signals from Camera device
        self.cam.sigNewFrame.connect(self.newFrame)
        self.cam.sigCameraStopped.connect(self.cameraStopped)
        self.cam.sigCameraStarted.connect(self.cameraStarted)
        self.cam.sigShowMessage.connect(self.showMessage)
        self.cam.sigParamsChanged.connect(self.cameraParamsChanged)

        self.frameDisplay.imageUpdated.connect(self.imageUpdated)
        self.imagingCtrl.sigStartVideoClicked.connect(self.startAcquireClicked)
        self.imagingCtrl.sigStopVideoClicked.connect(self.stopAcquireClicked)
        self.imagingCtrl.ui.acquireFrameBtn.setEnabled(False)

        # Hotkey signals
        for action, key in self.cam.camConfig.get('hotkeys', {}).items():
            if action not in ['snap', 'start']:
                raise ValueError("Unknown hotkey action %r" % action)

            dev = Manager.getManager().getDevice(key['device'])
            dev.addKeyCallback(key['key'], self.hotkeyPressed, (action, ))

    def newFrame(self, frame):
        self.imagingCtrl.newFrame(frame)
        self.sigNewFrame.emit(self, frame)

    def controlWidget(self):
        return self.widget

    def openCamera(self, ind=0):
        try:
            self.camSize = self.cam.getParam('sensorSize')
            self.showMessage("Opened camera %s" % self.cam, 5000)
            self.scope = self.cam.getScopeDevice()

            try:
                bins = self.cam.listParams('binning')[0][0]
            except:
                bins = self.cam.listParams('binningX')[0]
            bins.sort()
            bins.reverse()
            for b in bins:
                self.ui.binningCombo.addItem(str(b))

        except:
            self.showMessage("Error opening camera")
            raise

    def globalTransformChanged(self,
                               emitter=None,
                               changedDev=None,
                               transform=None):
        ## scope has moved; update viewport and camera outlines.
        ## This is only used when the camera is not running--
        ## if the camera is running, then this is taken care of in drawFrame to
        ## ensure that the image remains stationary on screen.
        if not self.cam.isRunning():
            tr = pg.SRTTransform(self.cam.globalTransform())
            self.updateTransform(tr)

    def imageUpdated(self, frame):
        ## New image is displayed; update image transform
        self.imageItem.setTransform(frame.frameTransform().as2D())

        ## Update viewport to correct for scope movement/scaling
        tr = pg.SRTTransform(frame.deviceTransform())
        self.updateTransform(tr)

        self.imageItemGroup.setTransform(tr)

    def updateTransform(self, tr):
        ## update view for new transform such that sensor bounds remain stationary on screen.
        pos = tr.getTranslation()

        scale = tr.getScale()
        if scale != self.lastCameraScale:
            anchor = self.view.mapViewToDevice(self.lastCameraPosition)
            self.view.scaleBy(scale / self.lastCameraScale)
            Qt.QApplication.processEvents()
            anchor2 = self.view.mapDeviceToView(anchor)
            diff = pos - anchor2
            self.lastCameraScale = scale
        else:
            diff = pos - self.lastCameraPosition

        self.view.translateBy(diff)
        self.lastCameraPosition = pos
        self.cameraItemGroup.setTransform(tr)

    def regionWidgetChanged(self, *args):
        self.updateRegion()

    def updateRegion(self, autoRestart=True):
        #self.clearFrameBuffer()
        r = self.roi.parentBounds()
        newRegion = [
            int(r.left()),
            int(r.top()),
            int(r.width()),
            int(r.height())
        ]
        if self.region != newRegion:
            self.region = newRegion
            self.cam.setParam('region', self.region, autoRestart=autoRestart)

    def quit(self):
        self.imagingCtrl.quit()

        if self.hasQuit:
            return

        try:
            self.cam.sigNewFrame.disconnect(self.newFrame)
            self.cam.sigCameraStopped.disconnect(self.cameraStopped)
            self.cam.sigCameraStarted.disconnect(self.cameraStarted)
            self.cam.sigShowMessage.disconnect(self.showMessage)
        except TypeError:
            pass

        self.hasQuit = True
        if self.cam.isRunning():
            self.cam.stop()
            if not self.cam.wait(10000):
                printExc("Timed out while waiting for acq thread exit!")

    def hotkeyPressed(self, device, keys, action):
        callback = {
            'snap': self.imagingCtrl.saveFrameClicked,
            'start': self.imagingCtrl.acquireVideoClicked,
        }.get(action)

        callback()

    def cameraStopped(self):
        self.imagingCtrl.acquisitionStopped()

    def cameraStarted(self):
        self.imagingCtrl.acquisitionStarted()

    def binningComboChanged(self, args):
        self.setBinning(*args)

    def setBinning(self, ind=None, autoRestart=True):
        """Set camera's binning value. If ind is specified, it is the index from binningCombo from which to grab the new binning value."""
        #self.backgroundFrame = None
        if ind is not None:
            self.binning = int(self.ui.binningCombo.itemText(ind))
        self.cam.setParam('binning', (self.binning, self.binning),
                          autoRestart=autoRestart)
        #self.clearFrameBuffer()
        ###self.updateRgnLabel()

    def setUiBinning(self, b, updateCamera=True):
        ind = self.ui.binningCombo.findText(str(b))
        if ind == -1:
            raise Exception("Binning mode %s not in list." % str(b))

        if updateCamera:
            self.ui.binningCombo.setCurrentIndex(ind)
        else:
            with self.binningComboProxy.block():
                self.ui.binningCombo.setCurrentIndex(ind)

    def setExposure(self, e=None, autoRestart=True):
        if e is not None:
            self.exposure = e
        self.cam.setParam('exposure', self.exposure, autoRestart=autoRestart)

    def updateCameraDecorations(self):
        ps = self.cameraScale
        pos = self.lastCameraPosition
        cs = self.camSize
        if ps is None:
            return

        m = self.cam.globalTransform()
        self.cameraItemGroup.setTransform(pg.SRTTransform(m))

    def setRegion(self, rgn=None):
        if rgn is None:
            rgn = [0, 0, self.camSize[0] - 1, self.camSize[1] - 1]
        self.roi.setPos([rgn[0], rgn[1]])
        self.roi.setSize([self.camSize[0], self.camSize[1]])

    def cameraParamsChanged(self, changes):
        # camera parameters changed; update ui to match
        if 'exposure' in changes:
            with pg.SignalBlock(self.ui.spinExposure.valueChanged,
                                self.setExposure):
                self.ui.spinExposure.setValue(changes['exposure'])

        if 'binningX' in changes:
            self.setUiBinning(changes['binningX'], updateCamera=False)
        elif 'binning' in changes:
            self.setUiBinning(changes['binning'][0], updateCamera=False)

        if 'region' in changes:
            with pg.SignalBlock(self.roi.sigRegionChangeFinished,
                                self.regionWidgetChanged):
                rgn = changes['region']
                self.roi.setPos([rgn[0], rgn[1]])
                self.roi.setSize([rgn[2], rgn[3]])
        if 'regionX' in changes or 'regionY' in changes:
            with pg.SignalBlock(self.roi.sigRegionChangeFinished,
                                self.regionWidgetChanged):
                x = changes.get('regionX', self.roi.pos().x())
                y = changes.get('regionY', self.roi.pos().y())
                self.roi.setPos([x, y])
        if 'regionW' in changes or 'regionH' in changes:
            with pg.SignalBlock(self.roi.sigRegionChangeFinished,
                                self.regionWidgetChanged):
                w = changes.get('regionW', self.roi.state['size'][0])
                h = changes.get('regionH', self.roi.state['size'][1])
                self.roi.setSize([w, h])

    def startAcquireClicked(self, mode):
        """User clicked the acquire video button.
        """
        try:
            self.cam.setParam('triggerMode', 'Normal', autoRestart=False)
            self.setBinning(autoRestart=False)
            self.setExposure(autoRestart=False)
            self.updateRegion(autoRestart=False)
            self.cam.start()
            Manager.logMsg("Camera started aquisition.", importance=0)
        except:
            self.imagingCtrl.acquisitionStopped()
            printExc("Error starting camera:")

    def stopAcquireClicked(self):
        self.cam.stop()
        Manager.logMsg("Camera stopped acquisition.", importance=0)

    def showMessage(self, msg, delay=2000):
        self.module.showMessage(msg, delay)

    def getImageItem(self):
        return self.imageItem

    def boundingRect(self):
        """
        Return bounding rect of this imaging device in global coordinates
        """
        return self.cam.getBoundary().boundingRect()

    def takeImage(self, closeShutter=None):
        # closeShutter is used for laser scanning devices; we can ignore it here.
        return self.getDevice().acquireFrames(1, stack=False)
Exemple #5
0
class FileHandle(QtCore.QObject):
    
    sigChanged = QtCore.Signal(object, object, object)  # (self, change, (args))
    sigDelayedChange = QtCore.Signal(object, object)  # (self, changes)
    
    def __init__(self, path, manager):
        QtCore.QObject.__init__(self)
        self.manager = manager
        self.delayedChanges = []
        self.path = os.path.abspath(path)
        self.parentDir = None
        #self.lock = threading.RLock()
        self.lock = Mutex(QtCore.QMutex.Recursive)
        self.sigproxy = SignalProxy(self.sigChanged, slot=self.delayedChange)
        
    def getFile(self, fn):
        return getFileHandle(os.path.join(self.name(), fn))
        

    def __repr__(self):
        return "<%s '%s' (0x%x)>" % (self.__class__.__name__, self.name(), self.__hash__())

    def __reduce__(self):
        return (getHandle, (self.name(),))

    def name(self, relativeTo=None):
        """Return the full name of this file with its absolute path"""
        #self.checkExists()
        with self.lock:
            path = self.path
            if relativeTo == self:
                path = ''
            elif relativeTo is not None:
                rpath = relativeTo.name()
                if not self.isGrandchildOf(relativeTo):
                    raise Exception("Path %s is not child of %s" % (path, rpath))
                return path[len(os.path.join(rpath, '')):]
            return path
        
    def shortName(self):
        """Return the name of this file without its path"""
        #self.checkExists()
        return os.path.split(self.name())[1]

    def ext(self):
        """Return file's extension"""
        return os.path.splitext(self.name())[1]

    def parent(self):
        self.checkExists()
        with self.lock:
            if self.parentDir is None:
                dirName = os.path.split(self.name())[0]
                self.parentDir = self.manager.getDirHandle(dirName)
            return self.parentDir
        
    def info(self):
        self.checkExists()
        info = self.parent()._fileInfo(self.shortName())
        return advancedTypes.ProtectedDict(info)
        
    def setInfo(self, info=None, **args):
        """Set meta-information for this file. Updates all keys specified in info, leaving others unchanged."""
        if info is None:
            info = args
        self.checkExists()
        self.emitChanged('meta')
        return self.parent()._setFileInfo(self.shortName(), info)
        
    def isManaged(self):
        self.checkExists()
        return self.parent().isManaged(self.shortName())
        
    def move(self, newDir):
        self.checkExists()
        with self.lock:
            oldDir = self.parent()
            fn1 = self.name()
            name = self.shortName()
            fn2 = os.path.join(newDir.name(), name)
            if os.path.exists(fn2):
                raise Exception("Destination file %s already exists." % fn2)
#            print "<> DataManager.move: about to move %s => %s" % (fn1, fn2)

            if oldDir.isManaged() and not newDir.isManaged():
                raise Exception("Not moving managed file to unmanaged location--this would cause loss of meta info.")
            

            os.rename(fn1, fn2)
#            print "<> DataManager.move: moved."
            self.path = fn2
            self.parentDir = None
#            print "<> DataManager.move: inform DM of change.."
            self.manager._handleChanged(self, 'moved', fn1, fn2)
            
            if oldDir.isManaged() and newDir.isManaged():
#                print "<> DataManager.move: new parent index old info.."
                newDir.indexFile(name, info=oldDir._fileInfo(name))
            elif newDir.isManaged():
#                print "<> DataManager.move: new parent index (no info)"
                newDir.indexFile(name)
                
            if oldDir.isManaged() and oldDir.isManaged(name):
#                print "<> DataManager.move: old parent forget child.."
                oldDir.forget(name)
                
#            print "<> DataManager.move: emit 'moved'.."
            self.emitChanged('moved', fn1, fn2)
                
#            print "<> DataManager.move: oldDir emit 'children'"
            oldDir._childChanged()
#            print "<> DataManager.move: newDir emit 'children'"
            newDir._childChanged()
        
    def rename(self, newName):
        #print "Rename %s -> %s" % (self.name(), newName)
        self.checkExists()
        with self.lock:
            parent = self.parent()
            fn1 = self.name()
            oldName = self.shortName()
            fn2 = os.path.join(parent.name(), newName)
            if os.path.exists(fn2):
                raise Exception("Destination file %s already exists." % fn2)
            #print "rename", fn1, fn2
            info = {}
            if parent.isManaged(oldName):
                info = parent._fileInfo(oldName)
                parent.forget(oldName)
            os.rename(fn1, fn2)
            self.path = fn2
            self.manager._handleChanged(self, 'renamed', fn1, fn2)
            if parent.isManaged(oldName):
                parent.indexFile(newName, info=info)
                
            self.emitChanged('renamed', fn1, fn2)
            self.parent()._childChanged()
        
    def delete(self):
        self.checkExists()
        with self.lock:
            parent = self.parent()
            fn1 = self.name()
            oldName = self.shortName()
            if self.isFile():
                os.remove(fn1)
            else:
                shutil.rmtree(fn1)
            self.manager._handleChanged(self, 'deleted', fn1)
            self.path = None
            if parent.isManaged():
                parent.forget(oldName)
            self.emitChanged('deleted', fn1)
            parent._childChanged()
        
    def read(self, *args, **kargs):
        self.checkExists()
        with self.lock:
            typ = self.fileType()
            
            if typ is None:
                fd = open(self.name(), 'r')
                data = fd.read()
                fd.close()
            else:
                cls = filetypes.getFileType(typ)
                data = cls.read(self, *args, **kargs)
                #mod = __import__('acq4.filetypes.%s' % typ, fromlist=['*'])
                #func = getattr(mod, 'fromFile')
                #data = func(fileName=self.name())
            
            return data
        
    def fileType(self):
        with self.lock:
            info = self.info()
            
            ## Use the recorded object_type to read the file if possible.
            ## Otherwise, ask the filetypes to choose the type for us.
            if '__object_type__' not in info:
                typ = filetypes.suggestReadType(self)
            else:
                typ = info['__object_type__']
            return typ



    def emitChanged(self, change, *args):
        self.delayedChanges.append(change)
        self.sigChanged.emit(self, change, args)

    def delayedChange(self, args):
        changes = list(set(self.delayedChanges))
        self.delayedChanges = []
        #self.emit(QtCore.SIGNAL('delayedChange'), self, changes)
        self.sigDelayedChange.emit(self, changes)
    
    def hasChildren(self):
        self.checkExists()
        return False
    
    def _parentMoved(self, oldDir, newDir):
        """Inform this object that it has been moved as a result of its (grand)parent having moved."""
        prefix = os.path.join(oldDir, '')
        if self.path[:len(prefix)] != prefix:
            raise Exception("File %s is not in moved tree %s, should not update!" % (self.path, oldDir))
        subName = self.path[len(prefix):]
        #while subName[0] == os.path.sep:
            #subName = subName[1:]
        newName = os.path.join(newDir, subName)
        #print "===", oldDir, newDir, subName, newName
        if not os.path.exists(newName):
            raise Exception("File %s does not exist." % newName)
        self.path = newName
        self.parentDir = None
        #print "parent of %s changed" % self.name()
        self.emitChanged('parent')
        
    def exists(self, name=None):
        if self.path is None:
            return False
        if name is not None:
            raise Exception("Cannot check for subpath existence on FileHandle.")
        return os.path.exists(self.path)

    def checkExists(self):
        if not self.exists():
            raise Exception("File '%s' does not exist." % self.path)

    def checkDeleted(self):
        if self.path is None:
            raise Exception("File has been deleted.")

    def isDir(self, path=None):
        return False
        
    def isFile(self):
        return True
        
    def _deleted(self):
        self.path = None
    
    def isGrandchildOf(self, grandparent):
        """Return true if this files is anywhere in the tree beneath grandparent."""
        gname = os.path.join(grandparent.name(), '')
        return self.name()[:len(gname)] == gname
    
    def write(self, data, **kwargs):
        self.parent().writeFile(data, self.shortName(), **kwargs)
        

    def flushSignals(self):
        """If any delayed signals are pending, send them now."""
        self.sigproxy.flush()
Exemple #6
0
class FileHandle(QtCore.QObject):
    
    sigChanged = QtCore.Signal(object, object, object)  # (self, change, (args))
    sigDelayedChange = QtCore.Signal(object, object)  # (self, changes)
    
    def __init__(self, path, manager):
        QtCore.QObject.__init__(self)
        self.manager = manager
        self.delayedChanges = []
        self.path = os.path.abspath(path)
        self.parentDir = None
        self.lock = Mutex(QtCore.QMutex.Recursive)
        self.sigproxy = SignalProxy(self.sigChanged, slot=self.delayedChange)
        
    def getFile(self, fn):
        return getFileHandle(os.path.join(self.name(), fn))
        

    def __repr__(self):
        return "<%s '%s' (0x%x)>" % (self.__class__.__name__, self.name(), self.__hash__())

    def __reduce__(self):
        return (getHandle, (self.name(),))

    def name(self, relativeTo=None):
        """Return the full name of this file with its absolute path"""
        #self.checkExists()
        with self.lock:
            path = self.path
            if relativeTo == self:
                path = ''
            elif relativeTo is not None:
                commonParent = relativeTo
                pcount = 0
                while True:
                    if self is commonParent or self.isGrandchildOf(commonParent):
                        break
                    else:
                        pcount += 1
                        commonParent = commonParent.parent()
                        if commonParent is None:
                            raise Exception("No relative path found from %s to %s." % (relativeTo.name(), self.name()))
                rpath = path[len(os.path.join(commonParent.name(), '')):]
                if pcount == 0:
                    return rpath
                else:
                    ppath = os.path.join(*(['..'] * pcount))
                    if rpath != '':
                        return os.path.join(ppath, rpath)
                    else:
                        return ppath
            return path
        
    def shortName(self):
        """Return the name of this file without its path"""
        #self.checkExists()
        return os.path.split(self.name())[1]

    def ext(self):
        """Return file's extension"""
        return os.path.splitext(self.name())[1]

    def parent(self):
        self.checkExists()
        with self.lock:
            if self.parentDir is None:
                dirName = os.path.split(self.name())[0]
                self.parentDir = self.manager.getDirHandle(dirName)
            return self.parentDir
        
    def info(self):
        self.checkExists()
        info = self.parent()._fileInfo(self.shortName())
        return advancedTypes.ProtectedDict(info)
        
    def setInfo(self, info=None, **args):
        """Set meta-information for this file. Updates all keys specified in info, leaving others unchanged."""
        if info is None:
            info = args
        self.checkExists()
        self.emitChanged('meta')
        return self.parent()._setFileInfo(self.shortName(), info)
        
    def isManaged(self):
        self.checkExists()
        return self.parent().isManaged(self.shortName())
        
    def move(self, newDir):
        self.checkExists()
        with self.lock:
            oldDir = self.parent()
            fn1 = self.name()
            name = self.shortName()
            fn2 = os.path.join(newDir.name(), name)
            if os.path.exists(fn2):
                raise Exception("Destination file %s already exists." % fn2)

            if oldDir.isManaged() and not newDir.isManaged():
                raise Exception("Not moving managed file to unmanaged location--this would cause loss of meta info.")
            

            os.rename(fn1, fn2)
            self.path = fn2
            self.parentDir = None
            self.manager._handleChanged(self, 'moved', fn1, fn2)
            
            if oldDir.isManaged() and newDir.isManaged():
                newDir.indexFile(name, info=oldDir._fileInfo(name))
            elif newDir.isManaged():
                newDir.indexFile(name)
                
            if oldDir.isManaged() and oldDir.isManaged(name):
                oldDir.forget(name)
                
            self.emitChanged('moved', fn1, fn2)
                
            oldDir._childChanged()
            newDir._childChanged()
        
    def rename(self, newName):
        """Rename this file.

        *newName* should be the new name of the file *excluding* its path.
        """
        self.checkExists()
        with self.lock:
            parent = self.parent()
            fn1 = self.name()
            oldName = self.shortName()
            fn2 = os.path.join(parent.name(), newName)
            if os.path.exists(fn2):
                raise Exception("Destination file %s already exists." % fn2)
            info = {}
            managed = parent.isManaged(oldName)
            if managed:
                info = parent._fileInfo(oldName)
                parent.forget(oldName)
            os.rename(fn1, fn2)
            self.path = fn2
            self.manager._handleChanged(self, 'renamed', fn1, fn2)
            if managed:
                parent.indexFile(newName, info=info)
                
            self.emitChanged('renamed', fn1, fn2)
            self.parent()._childChanged()
        
    def delete(self):
        self.checkExists()
        with self.lock:
            parent = self.parent()
            fn1 = self.name()
            oldName = self.shortName()
            if parent.isManaged():
                parent.forget(oldName)
            if self.isFile():
                os.remove(fn1)
            else:
                shutil.rmtree(fn1)
            self.manager._handleChanged(self, 'deleted', fn1)
            self.path = None
            self.emitChanged('deleted', fn1)
            parent._childChanged()
        
    def read(self, *args, **kargs):
        self.checkExists()
        with self.lock:
            typ = self.fileType()
            
            if typ is None:
                fd = open(self.name(), 'r')
                data = fd.read()
                fd.close()
            else:
                cls = filetypes.getFileType(typ)
                data = cls.read(self, *args, **kargs)
            
            return data
        
    def fileType(self):
        with self.lock:
            info = self.info()
            
            ## Use the recorded object_type to read the file if possible.
            ## Otherwise, ask the filetypes to choose the type for us.
            if '__object_type__' not in info:
                typ = filetypes.suggestReadType(self)
            else:
                typ = info['__object_type__']
            return typ

    def emitChanged(self, change, *args):
        self.delayedChanges.append(change)
        self.sigChanged.emit(self, change, args)

    def delayedChange(self, args):
        changes = list(set(self.delayedChanges))
        self.delayedChanges = []
        self.sigDelayedChange.emit(self, changes)
    
    def hasChildren(self):
        # self.checkExists()
        return False
    
    def _parentMoved(self, oldDir, newDir):
        """Inform this object that it has been moved as a result of its (grand)parent having moved."""
        prefix = os.path.join(oldDir, '')
        if self.path[:len(prefix)] != prefix:
            raise Exception("File %s is not in moved tree %s, should not update!" % (self.path, oldDir))
        subName = self.path[len(prefix):]
        newName = os.path.join(newDir, subName)
        if not os.path.exists(newName):
            raise Exception("File %s does not exist." % newName)
        self.path = newName
        self.parentDir = None
        self.emitChanged('parent')
        
    def exists(self, name=None):
        if self.path is None:
            return False
        if name is not None:
            raise Exception("Cannot check for subpath existence on FileHandle.")
        return os.path.exists(self.path)

    def checkExists(self):
        if not self.exists():
            raise Exception("File '%s' does not exist." % self.path)

    def checkDeleted(self):
        if self.path is None:
            raise Exception("File has been deleted.")

    def isDir(self, path=None):
        return False
        
    def isFile(self):
        return True
        
    def _deleted(self):
        self.path = None
    
    def isGrandchildOf(self, grandparent):
        """Return true if this files is anywhere in the tree beneath grandparent."""
        gname = os.path.join(abspath(grandparent.name()), '')
        return abspath(self.name())[:len(gname)] == gname

    def write(self, data, **kwargs):
        self.parent().writeFile(data, self.shortName(), **kwargs)
        
    def flushSignals(self):
        """If any delayed signals are pending, send them now."""
        self.sigproxy.flush()