Example #1
0
class XFilepathEdit(QWidget):
    """
    The XFilepathEdit class provides a common interface to prompt the user to
    select a filepath from the filesystem.  It can be configured to load
    directories, point to a save file path location, or to an open file path
    location.  It can also be setup to color changed based on the validity
    of the existance of the filepath.
    
    == Example ==
    
    |>>> from projexui.widgets.xfilepathedit import XFilepathEdit
    |>>> import projexui
    |
    |>>> # create the edit
    |>>> edit = projexui.testWidget(XFilepathEdit)
    |
    |>>> # set the filepath
    |>>> edit.setFilepath('/path/to/file')
    |
    |>>> # prompt the user to select the filepath
    |>>> edit.pickFilepath()
    |
    |>>> # enable the coloring validation
    |>>> edit.setValidated(True)
    """
    
    __designer_icon__ = projexui.resources.find('img/file.png')
    
    Mode = enum('OpenFile', 'SaveFile', 'Path', 'OpenFiles')
    
    filepathChanged = Signal(str)
    
    def __init__( self, parent = None ):
        super(XFilepathEdit, self).__init__( parent )
        
        # define custom properties
        self._validated         = False
        self._validForeground   = QColor(0, 120, 0)
        self._validBackground   = QColor(0, 120, 0, 100)
        self._invalidForeground = QColor(255, 0, 0)
        self._invalidBackground = QColor(255, 0, 0, 100)
        self._normalizePath     = False
        
        self._filepathMode      = XFilepathEdit.Mode.OpenFile
        self._filepathEdit      = XLineEdit(self)
        self._filepathButton    = QToolButton(self)
        self._filepathTypes     = 'All Files (*.*)'
        
        # set default properties
        ico = projexui.resources.find('img/folder.png')
        self._filepathEdit.setReadOnly(False)
        self._filepathEdit.setSizePolicy(QSizePolicy.Expanding,
                                         QSizePolicy.Expanding)
        self._filepathButton.setText('...')
        self._filepathButton.setFixedSize(25, 23)
        self._filepathButton.setAutoRaise(True)
        self._filepathButton.setIcon(QIcon(ico))
        self._filepathEdit.setContextMenuPolicy(Qt.CustomContextMenu)
        
        self.setWindowTitle('Load File')
        self.setAcceptDrops(True)
        
        # define the layout
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._filepathEdit)
        layout.addWidget(self._filepathButton)
        self.setLayout(layout)
        
        # create connections
        self._filepathEdit.installEventFilter(self)
        
        self._filepathButton.clicked.connect(   self.pickFilepath )
        self._filepathEdit.textChanged.connect( self.emitFilepathChanged )
        self._filepathEdit.textChanged.connect( self.validateFilepath )
        self._filepathEdit.customContextMenuRequested.connect( self.showMenu )
    
    def autoRaise( self ):
        """
        Returns whether or not the tool button will auto raise.
        
        :return     <bool>
        """
        return self._filepathButton.autoRaise()
    
    @Slot()
    def clearFilepath( self ):
        """
        Clears the filepath contents for this path.
        """
        self.setFilepath('')
    
    @Slot()
    def copyFilepath( self ):
        """
        Copies the current filepath contents to the current clipboard.
        """
        clipboard = QApplication.instance().clipboard()
        clipboard.setText(self.filepath())
        clipboard.setText(self.filepath(), clipboard.Selection)
    
    def dragEnterEvent( self, event ):
        """
        Processes drag enter events.
        
        :param      event | <QDragEvent>
        """
        if ( event.mimeData().hasUrls() ):
            event.acceptProposedAction()
    
    def dragMoveEvent( self, event ):
        """
        Processes drag move events.
        
        :param      event | <QDragEvent>
        """
        if ( event.mimeData().hasUrls() ):
            event.acceptProposedAction()
    
    def dropEvent( self, event ):
        """
        Processes drop event.
        
        :param      event | <QDropEvent>
        """
        if event.mimeData().hasUrls():
            url      = event.mimeData().urls()[0]
            filepath = url.toLocalFile()
            if filepath:
                self.setFilepath(filepath)
    
    def emitFilepathChanged( self ):
        """
        Emits the filepathChanged signal for this widget if the signals are \
        not being blocked.
        """
        if ( not self.signalsBlocked() ):
            self.filepathChanged.emit(self.filepath())
    
    def eventFilter( self, object, event ):
        """
        Overloads the eventFilter to look for click events on the line edit.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        if ( object == self._filepathEdit and \
             self._filepathEdit.isReadOnly() and \
             event.type() == event.MouseButtonPress and \
             event.button() == Qt.LeftButton ):
            self.pickFilepath()
            
        return False
    
    def filepath( self, validated = False ):
        """
        Returns the filepath for this widget.  If the validated flag is set \
        then this method will only return if the file or folder actually \
        exists for this path.  In the case of a SaveFile, only the base folder \
        needs to exist on the system, in other modes the actual filepath must \
        exist.  If not validated, the text will return whatever is currently \
        entered.
        
        :return     <str>
        """
        paths = self.filepaths()
        if not paths:
            return ''
        
        if not validated or self.isValid():
            return paths[0]
        return ''
    
    def filepaths(self):
        """
        Returns a list of the filepaths for this edit.
        
        :return     [<str>, ..]
        """
        return nativestring(self._filepathEdit.text()).split(os.path.pathsep)
    
    def filepathMode( self ):
        """
        Returns the filepath mode for this widget.
        
        :return     <XFilepathEdit.Mode>
        """
        return self._filepathMode
    
    def filepathModeText( self ):
        """
        Returns the text representation for this filepath mode.
        
        :return     <str>
        """
        return XFilepathEdit.Mode[self._filepathMode]
    
    def filepathTypes( self ):
        """
        Returns the filepath types that will be used for this widget.
        
        :return     <str>
        """
        return self._filepathTypes
    
    def hint( self ):
        """
        Returns the hint for this filepath.
        
        :return     <str>
        """
        return self._filepathEdit.hint()
    
    def icon( self ):
        """
        Returns the icon that is used for this filepath widget.
        
        :return     <QIcon>
        """
        return self._filepathButton.icon()
    
    def invalidBackground( self ):
        """
        Returns the invalid background color for this widget.
        
        :return     <QColor>
        """
        return self._invalidBackground
    
    def invalidForeground( self ):
        """
        Returns the invalid foreground color for this widget.
        
        :return     <QColor>
        """
        return self._invalidForeground
    
    def isValid( self ):
        """
        Returns whether or not the filepath exists on the system. \
        In the case of a SaveFile, only the base folder \
        needs to exist on the system, in other modes the actual filepath must \
        exist.
        
        :return     <bool>
        """
        check = nativestring(self._filepathEdit.text()).split(os.path.pathsep)[0]
        if ( self.filepathMode() == XFilepathEdit.Mode.SaveFile ):
            check = os.path.dirname(check)
        
        return os.path.exists(check)
    
    def isValidated( self ):
        """
        Set whether or not to validate the filepath as the user is working \
        with it.
        
        :return     <bool>
        """
        return self._validated
    
    def isReadOnly( self ):
        """
        Returns if the widget is read only for text editing or not.
        
        :return     <bool>
        """
        return self._filepathEdit.isReadOnly()
    
    def normalizePath(self):
        """
        Returns whether or not the path should be normalized for the current
        operating system.  When off, it will be defaulted to forward slashes
        (/).
        
        :return     <bool>
        """
        return self._normalizePath
    
    def pickFilepath( self ):
        """
        Prompts the user to select a filepath from the system based on the \
        current filepath mode.
        """
        mode = self.filepathMode()
        
        filepath = ''
        filepaths = []
        curr_dir = nativestring(self._filepathEdit.text())
        if ( not curr_dir ):
            curr_dir = QDir.currentPath()
        
        if mode == XFilepathEdit.Mode.SaveFile:
            filepath = QFileDialog.getSaveFileName( self,
                                                    self.windowTitle(),
                                                    curr_dir,
                                                    self.filepathTypes() )
                                                    
        elif mode == XFilepathEdit.Mode.OpenFile:
            filepath = QFileDialog.getOpenFileName( self,
                                                    self.windowTitle(),
                                                    curr_dir,
                                                    self.filepathTypes() )
        
        elif mode == XFilepathEdit.Mode.OpenFiles:
            filepaths = QFileDialog.getOpenFileNames( self,
                                                      self.windowTitle(),
                                                      curr_dir,
                                                      self.filepathTypes() )
        
        else:
            filepath = QFileDialog.getExistingDirectory( self,
                                                         self.windowTitle(),
                                                         curr_dir )
        
        if filepath:
            if type(filepath) == tuple:
                filepath = filepath[0]
            self.setFilepath(nativestring(filepath))
        elif filepaths:
            self.setFilepaths(map(str, filepaths))
    
    def setAutoRaise( self, state ):
        """
        Sets whether or not the tool button will auto raise.
        
        :param      state | <bool>
        """
        self._filepathButton.setAutoRaise(state)
    
    @Slot(int)
    def setFilepathMode( self, mode ):
        """
        Sets the filepath mode for this widget to the inputed mode.
        
        :param      mode | <XFilepathEdit.Mode>
        """
        self._filepathMode = mode
    
    @Slot(str)
    def setFilepathModeText( self, text ):
        """
        Sets the filepath mode for this widget based on the inputed text.
        
        :param      text | <str>
        
        :return     <bool> | success
        """
        try:
            self.setFilepathMode(XFilepathEdit.Mode[nativestring(text)])
            return True
        except KeyError:
            return False
    
    @Slot(str)
    def setFilepathTypes( self, filepathTypes ):
        """
        Sets the filepath type string that will be used when looking up \
        filepaths.
        
        :param      filepathTypes | <str>
        """
        self._filepathTypes = filepathTypes
    
    @Slot(str)
    def setFilepath(self, filepath):
        """
        Sets the filepath text for this widget to the inputed path.
        
        :param      filepath | <str>
        """
        if not filepath:
            self._filepathEdit.setText('')
            return
        
        if self.normalizePath():
            filepath = os.path.normpath(nativestring(filepath))
        else:
            filepath = os.path.normpath(nativestring(filepath)).replace('\\', '/')
            
        self._filepathEdit.setText(filepath)
    
    def setFilepaths(self, filepaths):
        """
        Sets the list of the filepaths for this widget to the inputed paths.
        
        :param      filepaths | [<str>, ..]
        """
        self.setFilepath(os.path.pathsep.join(filepaths))
    
    def setHint(self, hint):
        """
        Sets the hint for this filepath.
        
        :param      hint | <str>
        """
        if self.normalizePath():
            filepath = os.path.normpath(nativestring(hint))
        else:
            filepath = os.path.normpath(nativestring(hint)).replace('\\', '/')
            
        self._filepathEdit.setHint(hint)
    
    def setIcon( self, icon ):
        """
        Sets the icon that will be used for this widget's tool button.
        
        :param      icon | <QIcon> || <str>
        """
        self._filepathButton.setIcon(QIcon(icon))
    
    def setInvalidBackground( self, bg ):
        """
        Sets the invalid background color for this widget to the inputed widget.
        
        :param      bg | <QColor>
        """
        self._invalidBackground = QColor(bg)
    
    def setInvalidForeground( self, fg ):
        """
        Sets the invalid foreground color for this widget to the inputed widget.
        
        :param      fg | <QColor>
        """
        self._invalidForeground = QColor(fg)
    
    def setNormalizePath(self, state):
        """
        Sets whether or not the path should be normalized for the current
        operating system.  When off, it will be defaulted to forward slashes
        (/).
        
        :param      state | <bool>
        """
        self._normalizePath = state
    
    @Slot(bool)
    def setReadOnly( self, state ):
        """
        Sets whether or not this filepath widget is readonly in the text edit.
        
        :param      state | <bool>
        """
        self._filepathEdit.setReadOnly(state)
    
    @Slot(bool)
    def setValidated( self, state ):
        """
        Set whether or not to validate the path as the user edits it.
        
        :param      state | <bool>
        """
        self._validated = state
        palette = self.palette()
        
        # reset the palette to default, revalidate
        self._filepathEdit.setPalette(palette)
        self.validate()
    
    def setValidBackground( self, bg ):
        """
        Sets the valid background color for this widget to the inputed color.
        
        :param      bg | <QColor>
        """
        self._validBackground = QColor(bg)
    
    def setValidForeground( self, fg ):
        """
        Sets the valid foreground color for this widget to the inputed color.
        
        :param      fg | <QColor>
        """
        self._validForeground = QColor(fg)
    
    def showMenu( self, pos ):
        """
        Popups a menu for this widget.
        """
        menu = QMenu(self)
        menu.setAttribute(Qt.WA_DeleteOnClose)
        menu.addAction('Clear').triggered.connect(self.clearFilepath)
        menu.addSeparator()
        menu.addAction('Copy Filepath').triggered.connect(self.copyFilepath)
        
        menu.exec_(self.mapToGlobal(pos))
    
    def validBackground( self ):
        """
        Returns the valid background color for this widget.
        
        :return     <QColor>
        """
        return self._validBackground
    
    def validForeground( self ):
        """
        Returns the valid foreground color for this widget.
        
        :return     <QColor>
        """
        return self._validForeground
    
    def validateFilepath( self ):
        """
        Alters the color scheme based on the validation settings.
        """
        if ( not self.isValidated() ):
            return
        
        valid = self.isValid()
        if ( not valid ):
            fg = self.invalidForeground()
            bg = self.invalidBackground()
        else:
            fg = self.validForeground()
            bg = self.validBackground()
        
        palette = self.palette()
        palette.setColor(palette.Base, bg)
        palette.setColor(palette.Text, fg)
        self._filepathEdit.setPalette(palette)
    
    # map Qt properties
    x_autoRaise         = Property(bool, autoRaise,     setAutoRaise)
    x_filepathTypes     = Property(str,  filepathTypes, setFilepathTypes)
    x_filepath          = Property(str,  filepath,      setFilepath)
    x_readOnly          = Property(bool, isReadOnly,    setReadOnly)
    x_validated         = Property(bool, isValidated,   setValidated)
    x_hint              = Property(str,  hint,          setHint)
    x_icon              = Property('QIcon', icon,       setIcon)
    x_normalizePath     = Property(bool, normalizePath, setNormalizePath)
    
    x_invalidForeground = Property('QColor',
                                        invalidForeground,
                                        setInvalidForeground)
    
    x_invalidBackground = Property('QColor',
                                        invalidBackground,
                                        setInvalidBackground)
    
    x_validForeground   = Property('QColor',
                                        validForeground,
                                        setValidForeground)
    
    x_validBackground   = Property('QColor',
                                        validBackground,
                                        setValidBackground)
    
    x_filepathModeText  = Property(str, 
                                       filepathModeText, 
                                       setFilepathModeText)