eric7/EricWidgets/EricPathPicker.py

branch
eric7
changeset 8358
144a6b854f70
parent 8356
68ec9c3d4de5
child 8366
2a9f5153c438
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/EricWidgets/EricPathPicker.py	Sat May 22 19:58:24 2021 +0200
@@ -0,0 +1,718 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a path picker widget.
+"""
+
+import enum
+import os
+
+from PyQt6.QtCore import pyqtSignal, Qt, QFileInfo, QCoreApplication, QDir
+from PyQt6.QtWidgets import (
+    QWidget, QHBoxLayout, QToolButton, QSizePolicy, QLineEdit, QComboBox
+)
+
+from . import EricFileDialog
+from .EricCompleters import EricFileCompleter, EricDirCompleter
+
+import UI.PixmapCache
+
+
+class EricPathPickerModes(enum.Enum):
+    """
+    Class implementing the path picker modes.
+    """
+    OPEN_FILE_MODE = 0
+    OPEN_FILES_MODE = 1
+    SAVE_FILE_MODE = 2
+    SAVE_FILE_ENSURE_EXTENSION_MODE = 3
+    SAVE_FILE_OVERWRITE_MODE = 4
+    DIRECTORY_MODE = 5
+    DIRECTORY_SHOW_FILES_MODE = 6
+    CUSTOM_MODE = 99
+    NO_MODE = 100
+
+
+class EricPathPickerBase(QWidget):
+    """
+    Class implementing the base of a path picker widget consisting of a
+    line edit or combo box and a tool button to open a file dialog.
+    
+    @signal textChanged(path) emitted when the entered path has changed
+        (line edit based widget)
+    @signal editTextChanged(path) emitted when the entered path has changed
+        (combo box based widget)
+    @signal pathSelected(path) emitted after a path has been selected via the
+        file dialog
+    @signal aboutToShowPathPickerDialog emitted before the file dialog is shown
+    @signal pickerButtonClicked emitted when the picker button was pressed and
+        the widget mode is custom
+    """
+    DefaultMode = EricPathPickerModes.NO_MODE
+    
+    textChanged = pyqtSignal(str)
+    editTextChanged = pyqtSignal(str)
+    pathSelected = pyqtSignal(str)
+    aboutToShowPathPickerDialog = pyqtSignal()
+    pickerButtonClicked = pyqtSignal()
+    
+    def __init__(self, parent=None, useLineEdit=True):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        @param useLineEdit flag indicating the use of a line edit
+        @type bool
+        """
+        super().__init__(parent)
+        
+        self.__lineEditKind = useLineEdit
+        
+        self.__mode = EricPathPicker.DefaultMode
+        self.__editorEnabled = True
+        
+        self._completer = None
+        self.__filters = ""
+        self.__defaultDirectory = ""
+        self.__windowTitle = ""
+        
+        self.__layout = QHBoxLayout(self)
+        self.__layout.setSpacing(0)
+        self.__layout.setContentsMargins(0, 0, 0, 0)
+        self.setLayout(self.__layout)
+        
+        if useLineEdit:
+            self._editor = QLineEdit(self)
+            self._editor.setPlaceholderText(QCoreApplication.translate(
+                    "EricPathPickerBase", "Enter Path Name"))
+            self._editor.setClearButtonEnabled(True)
+        else:
+            self._editor = QComboBox(self)
+            self._editor.setEditable(True)
+            self._editor.lineEdit().setPlaceholderText(
+                QCoreApplication.translate(
+                    "EricPathPickerBase", "Enter Path Name"))
+            self._editor.lineEdit().setClearButtonEnabled(True)
+        
+        self.__button = QToolButton(self)
+        self.__button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
+        self.__button.setIcon(UI.PixmapCache.getIcon("open"))
+        
+        self.__layout.addWidget(self._editor)
+        self.__layout.addWidget(self.__button)
+        
+        self.__button.clicked.connect(self.__showPathPickerDialog)
+        if useLineEdit:
+            self._editor.textEdited.connect(self.__pathEdited)
+            self._editor.textChanged.connect(self.textChanged)
+        else:
+            self._editor.editTextChanged.connect(self.editTextChanged)
+        
+        self.setFocusProxy(self._editor)
+        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        self.setSizePolicy(QSizePolicy.Policy.Expanding,
+                           QSizePolicy.Policy.Preferred)
+        
+        self.__button.setEnabled(self.__mode != EricPathPickerModes.NO_MODE)
+    
+    def __pathEdited(self, path):
+        """
+        Private slot handling editing of the path.
+        
+        @param path current text of the path line edit
+        @type str
+        """
+        if self._completer and not self._completer.popup().isVisible():
+            self._completer.setRootPath(QDir.toNativeSeparators(path))
+    
+    def setMode(self, mode):
+        """
+        Public method to set the path picker mode.
+        
+        @param mode picker mode
+        @type EricPathPickerModes
+        @exception ValueError raised to indicate a bad parameter value
+        """
+        if mode not in EricPathPickerModes:
+            raise ValueError("Bad value for 'mode' parameter.")
+        
+        oldMode = self.__mode
+        self.__mode = mode
+        
+        if mode != oldMode or (self.__lineEditKind and not self._completer):
+            if self.__lineEditKind and self._completer:
+                # Remove current completer
+                self._editor.setCompleter(None)
+                self._completer = None
+            
+            if mode != EricPathPickerModes.NO_MODE:
+                if self.__lineEditKind:
+                    # Set a new completer
+                    if mode == EricPathPickerModes.DIRECTORY_MODE:
+                        self._completer = EricDirCompleter(self._editor)
+                    else:
+                        self._completer = EricFileCompleter(self._editor)
+                
+                # set inactive text
+                if mode == EricPathPickerModes.OPEN_FILES_MODE:
+                    self._editor.setPlaceholderText(
+                        self.tr("Enter Path Names separated by ';'"))
+                else:
+                    self._editor.setPlaceholderText(
+                        self.tr("Enter Path Name"))
+        self.__button.setEnabled(self.__mode != EricPathPickerModes.NO_MODE)
+    
+    def mode(self):
+        """
+        Public method to get the path picker mode.
+        
+        @return path picker mode
+        @rtype EricPathPickerModes
+        """
+        return self.__mode
+    
+    def setPickerEnabled(self, enable):
+        """
+        Public method to set the enabled state of the file dialog button.
+        
+        @param enable flag indicating the enabled state
+        @type bool
+        """
+        self.__button.setEnabled(enable)
+    
+    def isPickerEnabled(self):
+        """
+        Public method to get the file dialog button enabled state.
+        
+        @return flag indicating the enabled state
+        @rtype bool
+        """
+        return self.__button.isEnabled()
+    
+    def clear(self):
+        """
+        Public method to clear the current path or list of paths.
+        """
+        self._editor.clear()
+    
+    def clearEditText(self):
+        """
+        Public method to clear the current path.
+        """
+        if not self.__lineEditKind:
+            self._editor.clearEditText()
+    
+    def _setEditorText(self, text):
+        """
+        Protected method to set the text of the editor.
+        
+        @param text text to set
+        @type str
+        """
+        if self.__lineEditKind:
+            self._editor.setText(text)
+        else:
+            self._editor.setEditText(text)
+            if text and self._editor.findText(text) == -1:
+                self._editor.insertItem(0, text)
+    
+    def _editorText(self):
+        """
+        Protected method to get the text of the editor.
+        
+        @return text of the editor
+        @rtype str
+        """
+        if self.__lineEditKind:
+            return self._editor.text()
+        else:
+            return self._editor.currentText()
+    
+    def setText(self, path, toNative=True):
+        """
+        Public method to set the current path.
+        
+        @param path path to be set
+        @type str
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        """
+        if self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
+            self._setEditorText(path)
+        else:
+            if toNative:
+                path = QDir.toNativeSeparators(path)
+            self._setEditorText(path)
+            if self._completer:
+                self._completer.setRootPath(path)
+    
+    def text(self, toNative=True):
+        """
+        Public method to get the current path.
+        
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        @return current path
+        @rtype str
+        """
+        if self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
+            if toNative:
+                return ";".join(
+                    [QDir.toNativeSeparators(path)
+                     for path in self._editorText().split(";")])
+            else:
+                return self._editorText()
+        else:
+            if toNative:
+                return os.path.expanduser(
+                    QDir.toNativeSeparators(self._editorText()))
+            else:
+                return os.path.expanduser(self._editorText())
+    
+    def setEditText(self, path, toNative=True):
+        """
+        Public method to set the current path.
+        
+        @param path path to be set
+        @type str
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        """
+        self.setText(path, toNative=toNative)
+    
+    def currentText(self, toNative=True):
+        """
+        Public method to get the current path.
+        
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        @return current path
+        @rtype str
+        """
+        return self.text(toNative=toNative)
+    
+    def setPath(self, path, toNative=True):
+        """
+        Public method to set the current path.
+        
+        @param path path to be set
+        @type str
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        """
+        self.setText(path, toNative=toNative)
+    
+    def path(self, toNative=True):
+        """
+        Public method to get the current path.
+        
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        @return current path
+        @rtype str
+        """
+        return self.text(toNative=toNative)
+    
+    def paths(self, toNative=True):
+        """
+        Public method to get the list of entered paths.
+        
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        @return entered paths
+        @rtype list of str
+        """
+        if self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
+            return self.path(toNative=toNative).split(";")
+        else:
+            return [self.path(toNative=toNative)]
+    
+    def firstPath(self, toNative=True):
+        """
+        Public method to get the first path of a list of entered paths.
+        
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        @return first path
+        @rtype str
+        """
+        return self.paths(toNative=toNative)[0]
+    
+    def lastPath(self, toNative=True):
+        """
+        Public method to get the last path of a list of entered paths.
+        
+        @param toNative flag indicating to convert the path into
+            a native format
+        @type bool
+        @return last path
+        @rtype str
+        """
+        return self.paths(toNative=toNative)[-1]
+    
+    def setEditorEnabled(self, enable):
+        """
+        Public method to set the path editor's enabled state.
+        
+        @param enable flag indicating the enable state
+        @type bool
+        """
+        if enable != self._editorEnabled:
+            self._editorEnabled = enable
+            self._editor.setEnabled(enable)
+    
+    def editorEnabled(self):
+        """
+        Public method to get the path editor's enabled state.
+        
+        @return flag indicating the enabled state
+        @rtype bool
+        """
+        return self._editorEnabled
+    
+    def setDefaultDirectory(self, directory):
+        """
+        Public method to set the default directory.
+        
+        @param directory default directory
+        @type str
+        """
+        self.__defaultDirectory = directory
+    
+    def defaultDirectory(self):
+        """
+        Public method to get the default directory.
+        
+        @return default directory
+        @rtype str
+        """
+        return self.__defaultDirectory
+    
+    def setWindowTitle(self, title):
+        """
+        Public method to set the path picker dialog window title.
+        
+        @param title window title
+        @type str
+        """
+        self.__windowTitle = title
+    
+    def windowTitle(self):
+        """
+        Public method to get the path picker dialog's window title.
+        
+        @return window title
+        @rtype str
+        """
+        return self.__windowTitle
+    
+    def setFilters(self, filters):
+        """
+        Public method to set the filters for the path picker dialog.
+        
+        Note: Multiple filters must be separated by ';;'.
+        
+        @param filters string containing the file filters
+        @type str
+        """
+        self.__filters = filters
+    
+    def filters(self):
+        """
+        Public methods to get the filter string.
+        
+        @return filter string
+        @rtype str
+        """
+        return self.__filters
+    
+    def setNameFilters(self, filters):
+        """
+        Public method to set the name filters for the completer.
+        
+        @param filters list of file name filters
+        @type list of str
+        """
+        if self._completer:
+            self._completer.model().setNameFilters(filters)
+    
+    def setButtonToolTip(self, tooltip):
+        """
+        Public method to set the tool button tool tip.
+        
+        @param tooltip text to be set as a tool tip
+        @type str
+        """
+        self.__button.setToolTip(tooltip)
+    
+    def buttonToolTip(self):
+        """
+        Public method to get the tool button tool tip.
+        
+        @return tool tip text
+        @rtype str
+        """
+        return self.__button.toolTip()
+    
+    def setEditorToolTip(self, tooltip):
+        """
+        Public method to set the editor tool tip.
+        
+        @param tooltip text to be set as a tool tip
+        @type str
+        """
+        self._editor.setToolTip(tooltip)
+    
+    def editorToolTip(self):
+        """
+        Public method to get the editor tool tip.
+        
+        @return tool tip text
+        @rtype str
+        """
+        return self._editor.toolTip()
+    
+    def __showPathPickerDialog(self):
+        """
+        Private slot to show the path picker dialog.
+        """
+        if self.__mode == EricPathPickerModes.NO_MODE:
+            return
+        
+        if self.__mode == EricPathPickerModes.CUSTOM_MODE:
+            self.pickerButtonClicked.emit()
+            return
+        
+        self.aboutToShowPathPickerDialog.emit()
+        
+        windowTitle = self.__windowTitle
+        if not windowTitle:
+            if self.__mode == EricPathPickerModes.OPEN_FILE_MODE:
+                windowTitle = self.tr("Choose a file to open")
+            elif self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
+                windowTitle = self.tr("Choose files to open")
+            elif self.__mode in [
+                EricPathPickerModes.SAVE_FILE_MODE,
+                    EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE,
+                    EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE]:
+                windowTitle = self.tr("Choose a file to save")
+            elif self.__mode == EricPathPickerModes.DIRECTORY_MODE:
+                windowTitle = self.tr("Choose a directory")
+        
+        directory = self._editorText()
+        if not directory and self.__defaultDirectory:
+            directory = self.__defaultDirectory
+        directory = (
+            os.path.expanduser(directory.split(";")[0])
+            if self.__mode == EricPathPickerModes.OPEN_FILES_MODE else
+            os.path.expanduser(directory)
+        )
+        if not os.path.isabs(directory) and self.__defaultDirectory:
+            directory = os.path.join(self.__defaultDirectory, directory)
+        directory = QDir.fromNativeSeparators(directory)
+        
+        if self.__mode == EricPathPickerModes.OPEN_FILE_MODE:
+            path = EricFileDialog.getOpenFileName(
+                self,
+                windowTitle,
+                directory,
+                self.__filters)
+            path = QDir.toNativeSeparators(path)
+        elif self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
+            paths = EricFileDialog.getOpenFileNames(
+                self,
+                windowTitle,
+                directory,
+                self.__filters)
+            path = ";".join([QDir.toNativeSeparators(path)
+                             for path in paths])
+        elif self.__mode == EricPathPickerModes.SAVE_FILE_MODE:
+            path = EricFileDialog.getSaveFileName(
+                self,
+                windowTitle,
+                directory,
+                self.__filters,
+                EricFileDialog.DontConfirmOverwrite)
+            path = QDir.toNativeSeparators(path)
+        elif self.__mode == EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE:
+            path, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                self,
+                windowTitle,
+                directory,
+                self.__filters,
+                None,
+                EricFileDialog.DontConfirmOverwrite)
+            path = QDir.toNativeSeparators(path)
+            if path:
+                ext = QFileInfo(path).suffix()
+                if not ext:
+                    ex = selectedFilter.split("(*")[1].split(")")[0]
+                    if ex:
+                        path += ex
+        elif self.__mode == EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE:
+            path = EricFileDialog.getSaveFileName(
+                self,
+                windowTitle,
+                directory,
+                self.__filters)
+            path = QDir.toNativeSeparators(path)
+        elif self.__mode == EricPathPickerModes.DIRECTORY_MODE:
+            path = EricFileDialog.getExistingDirectory(
+                self,
+                windowTitle,
+                directory,
+                EricFileDialog.ShowDirsOnly)
+            path = QDir.toNativeSeparators(path)
+            while path.endswith(os.sep):
+                path = path[:-1]
+        elif self.__mode == EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE:
+            path = EricFileDialog.getExistingDirectory(
+                self,
+                windowTitle,
+                directory,
+                EricFileDialog.DontUseNativeDialog)
+            path = QDir.toNativeSeparators(path)
+            while path.endswith(os.sep):
+                path = path[:-1]
+        
+        if path:
+            self._setEditorText(path)
+            self.pathSelected.emit(path)
+    
+    def setReadOnly(self, readOnly):
+        """
+        Public method to set the path picker to read only mode.
+        
+        @param readOnly flag indicating read only mode
+        @type bool
+        """
+        try:
+            self._editor.setReadOnly(readOnly)
+        except AttributeError:
+            self._editor.setEditable(not readOnly)
+        self.setPickerEnabled(not readOnly)
+    
+    def isReadOnly(self):
+        """
+        Public method to check the path picker for read only mode.
+        
+        @return flg indicating read only mode
+        @rtype bool
+        """
+        try:
+            return self._editor.isReadOnly()
+        except AttributeError:
+            return not self._editor.isEditable()
+    
+    ##################################################################
+    ## Methods below emulate some of the QComboBox API
+    ##################################################################
+    
+    def addItems(self, pathsList):
+        """
+        Public method to add paths to the current list.
+        
+        @param pathsList list of paths to add
+        @type list of str
+        """
+        self._editor.addItems(pathsList)
+    
+    def addItem(self, path):
+        """
+        Public method to add a paths to the current list.
+        
+        @param path path to add
+        @type str
+        """
+        self._editor.addItem(path)
+    
+    def setPathsList(self, pathsList):
+        """
+        Public method to set the paths list.
+        
+        @param pathsList list of paths
+        @type list of str
+        """
+        self.clear()
+        self.addItems(pathsList)
+    
+    def setCurrentIndex(self, index):
+        """
+        Public slot to set the current index.
+        
+        @param index index of the item to set current
+        @type int
+        """
+        self._editor.setCurrentIndex(index)
+    
+    def setInsertPolicy(self, policy):
+        """
+        Public method to set the insertion policy of the combo box.
+        
+        @param policy insertion policy
+        @type QComboBox.InsertPolicy
+        """
+        self._editor.setInsertPolicy(policy)
+    
+    def setSizeAdjustPolicy(self, policy):
+        """
+        Public method to set the size adjust policy of the combo box.
+        
+        @param policy size adjust policy
+        @type QComboBox.SizeAdjustPolicy
+        """
+        self._editor.setSizeAdjustPolicy(policy)
+
+
+class EricPathPicker(EricPathPickerBase):
+    """
+    Class implementing a path picker widget consisting of a line edit and a
+    tool button to open a file dialog.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent, useLineEdit=True)
+
+
+class EricComboPathPicker(EricPathPickerBase):
+    """
+    Class implementing a path picker widget consisting of a combobox and a
+    tool button to open a file dialog.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent, useLineEdit=False)
+    
+    def getPathItems(self):
+        """
+        Public method to get the list of remembered paths.
+        
+        @return list od remembered paths
+        @rtype list of str
+        """
+        paths = []
+        for index in range(self._editor.count()):
+            paths.append(self._editor.itemText(index))
+        return paths

eric ide

mercurial