示例#1
0
    def __init__(self, parent=None, subfolders=False, status=None):
        QTreeView.__init__(self, parent)

        self._load = False  # If True a loadFiles signal is emitted when
        # an index is clicked. See selectionChanged.

        dirmodel = QDirModel()
        dirmodel.setSorting(QDir.IgnoreCase)
        dirmodel.setFilter(QDir.Dirs | QDir.NoDotAndDotDot)
        dirmodel.setReadOnly(False)
        dirmodel.setLazyChildCount(False)
        dirmodel.setResolveSymlinks(False)
        header = PuddleHeader(Qt.Horizontal, self)
        self.setHeader(header)
        self.header().setSectionResizeMode(QHeaderView.ResizeToContents)

        self.setModel(dirmodel)
        [self.hideColumn(column) for column in range(1, 4)]

        self.header().hide()
        self.subfolders = subfolders
        self.setSelectionMode(self.ExtendedSelection)
        self._lastselection = 0  # If > 0 appends files. See selectionChanged
        self._load = True
        self.setDragEnabled(False)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)
        self._dropaction = Qt.MoveAction
        self._threadRunning = False

        self._select = True

        self.expanded.connect(lambda discarded: self.resizeColumnToContents(0))
示例#2
0
    def __init__(self, mainWindow, parent = None):
        super().__init__(parent)
        self.mDirectoryEdit = QLineEdit()
        self.mMapsView = MapsView(mainWindow)

        self.setObjectName("MapsDock")
        widget = QWidget(self)
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(5, 5, 5, 5)
        dirLayout = QHBoxLayout()
        # QDirModel is obsolete, but I could not get QFileSystemModel to work here
        model = QDirModel(self)
        model.setFilter(QDir.AllDirs | QDir.Dirs | QDir.Drives | QDir.NoDotAndDotDot)
        completer = QCompleter(model, self)
        self.mDirectoryEdit.setCompleter(completer)
        button = QPushButton(self.tr("Browse..."))
        dirLayout.addWidget(self.mDirectoryEdit)
        dirLayout.addWidget(button)
        layout.addWidget(self.mMapsView)
        layout.addLayout(dirLayout)
        self.setWidget(widget)
        self.retranslateUi()
        button.clicked.connect(self.browse)
        prefs = preferences.Preferences.instance()
        prefs.mapsDirectoryChanged.connect(self.onMapsDirectoryChanged)
        self.mDirectoryEdit.setText(prefs.mapsDirectory())
        self.mDirectoryEdit.returnPressed.connect(self.editedMapsDirectory)
示例#3
0
 def show_dir(self):
     model = QDirModel()
     model.setFilter(QDir.AllDirs | QDir.Files | QDir.NoDotAndDotDot)
     model.setNameFilters(["*.pickle", '*.pickle.gz'])
     _dir = self.lineEdit.text()
     if not _dir:
         _dir = '.'
     self.treeView.setModel(model)
     self.treeView.setRootIndex(model.index(_dir))
示例#4
0
class SearchWidget(QFrame):
    """Widget, appeared, when Ctrl+F pressed.
    Has different forms for different search modes
    """

    Normal = 'normal'
    Good = 'good'
    Bad = 'bad'
    Incorrect = 'incorrect'

    visibilityChanged = pyqtSignal(bool)
    """
    visibilityChanged(visible)

    **Signal** emitted, when widget has been shown or hidden
    """  # pylint: disable=W0105

    searchInDirectoryStartPressed = pyqtSignal(type(re.compile('')), list, str)
    """
    searchInDirectoryStartPressed(regEx, mask, path)

    **Signal** emitted, when 'search in directory' button had been pressed
    """  # pylint: disable=W0105

    searchInDirectoryStopPressed = pyqtSignal()
    """
    searchInDirectoryStopPressed()

    **Signal** emitted, when 'stop search in directory' button had been pressed
    """  # pylint: disable=W0105

    replaceCheckedStartPressed = pyqtSignal(str)
    """
    replaceCheckedStartPressed(replText)

    **Signal** emitted, when 'replace checked' button had been pressed
    """  # pylint: disable=W0105

    replaceCheckedStopPressed = pyqtSignal()
    """
    replaceCheckedStartPressed()

    **Signal** emitted, when 'stop replacing checked' button had been pressed
    """  # pylint: disable=W0105

    searchRegExpChanged = pyqtSignal(type(re.compile('')))
    """
    searchRegExpValidStateChanged(regEx)

    **Signal** emitted, when search regexp has been changed.
    If reg exp is invalid - regEx object contains empty pattern
    """  # pylint: disable=W0105

    searchNext = pyqtSignal()
    """
    searchNext()

    **Signal** emitted, when 'Search Next' had been pressed
    """  # pylint: disable=W0105

    searchPrevious = pyqtSignal()
    """
    searchPrevious()

    **Signal** emitted, when 'Search Previous' had been pressed
    """  # pylint: disable=W0105

    replaceFileOne = pyqtSignal(str)
    """
    replaceFileOne(replText)

    **Signal** emitted, when 'Replace' had been pressed
    """  # pylint: disable=W0105

    replaceFileAll = pyqtSignal(str)
    """
    replaceFileAll(replText)

    **Signal** emitted, when 'Replace All' had been pressed
    """  # pylint: disable=W0105

    def __init__(self, plugin):
        QFrame.__init__(self, core.workspace())
        self._mode = None
        self.plugin = plugin
        uic.loadUi(os.path.join(os.path.dirname(__file__), 'SearchWidget.ui'), self)

        self.cbSearch.setCompleter(None)
        self.cbReplace.setCompleter(None)
        self.cbMask.setCompleter(None)

        self.fsModel = QDirModel(self.cbPath.lineEdit())
        self.fsModel.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)

        self.cbPath.lineEdit().setCompleter(QCompleter(self.fsModel,
                                                       self.cbPath.lineEdit()))
        self._pathBackspaceShortcut = QShortcut(QKeySequence("Ctrl+Backspace"), self.cbPath, self._onPathBackspace)
        self._pathBackspaceShortcut.setContext(Qt.WidgetWithChildrenShortcut)

        # TODO QDirModel is deprecated but QCompleter does not yet handle
        # QFileSystemodel - please update when possible."""
        self.cbSearch.setCompleter(None)
        self.pbSearchStop.setVisible(False)
        self.pbReplaceCheckedStop.setVisible(False)

        self._progress = QProgressBar(self)
        self._progress.setAlignment(Qt.AlignCenter)
        self._progress.setToolTip(self.tr("Search in progress..."))
        self._progress.setMaximumSize(QSize(80, 16))
        core.mainWindow().statusBar().insertPermanentWidget(1, self._progress)
        self._progress.setVisible(False)

        # cd up action
        self.tbCdUp = QToolButton(self.cbPath.lineEdit())
        self.tbCdUp.setIcon(QIcon(":/enkiicons/go-up.png"))
        self.tbCdUp.setCursor(Qt.ArrowCursor)
        self.tbCdUp.installEventFilter(self)  # for drawing button

        self.cbSearch.installEventFilter(self)  # for catching Tab and Shift+Tab
        self.cbReplace.installEventFilter(self)  # for catching Tab and Shift+Tab
        self.cbPath.installEventFilter(self)  # for catching Tab and Shift+Tab
        self.cbMask.installEventFilter(self)  # for catching Tab and Shift+Tab

        self._closeShortcut = QShortcut(QKeySequence("Esc"), self)
        self._closeShortcut.setContext(Qt.WidgetWithChildrenShortcut)
        self._closeShortcut.activated.connect(self.hide)

        # connections
        self.cbSearch.lineEdit().textChanged.connect(self._onSearchRegExpChanged)

        self.cbSearch.lineEdit().returnPressed.connect(self._onReturnPressed)
        self.cbReplace.lineEdit().returnPressed.connect(self._onReturnPressed)
        self.cbPath.lineEdit().returnPressed.connect(self._onReturnPressed)
        self.cbMask.lineEdit().returnPressed.connect(self._onReturnPressed)

        self.cbRegularExpression.stateChanged.connect(self._onSearchRegExpChanged)
        self.cbCaseSensitive.stateChanged.connect(self._onSearchRegExpChanged)
        self.cbWholeWord.stateChanged.connect(self._onSearchRegExpChanged)

        self.tbCdUp.clicked.connect(self._onCdUpPressed)

        self.pbNext.pressed.connect(self.searchNext)
        self.pbPrevious.pressed.connect(self.searchPrevious)
        self.pbSearchStop.pressed.connect(self.searchInDirectoryStopPressed)
        self.pbReplaceCheckedStop.pressed.connect(self.replaceCheckedStopPressed)

        core.mainWindow().hideAllWindows.connect(self.hide)
        core.workspace().escPressed.connect(self.hide)

        core.workspace().currentDocumentChanged.connect(
            lambda old, new: self.setVisible(self.isVisible() and new is not None))

    def show(self):
        """Reimplemented function. Sends signal
        """
        super(SearchWidget, self).show()
        self.visibilityChanged.emit(self.isVisible())

    def hide(self):
        """Reimplemented function.
        Sends signal, returns focus to workspace
        """
        super(SearchWidget, self).hide()
        core.workspace().focusCurrentDocument()
        self.visibilityChanged.emit(self.isVisible())

    def setVisible(self, visible):
        """Reimplemented function. Sends signal
        """
        super(SearchWidget, self).setVisible(visible)
        self.visibilityChanged.emit(self.isVisible())

    def _regExEscape(self, text):
        """Improved version of re.escape()
        Doesn't escape space, comma, underscore.
        Escapes \n and \t
        """
        text = re.escape(text)
        # re.escape escapes space, comma, underscore, but, it is not necessary and makes text not readable
        for symbol in (' ,_=\'"/:@#%&'):
            text = text.replace('\\' + symbol, symbol)

        text = text.replace('\\\n', '\\n')
        text = text.replace('\\\t', '\\t')

        return text

    def _makeEscapeSeqsVisible(self, text):
        """Replace invisible \n and \t with escape sequences
        """
        text = text.replace('\\', '\\\\')
        text = text.replace('\t', '\\t')
        text = text.replace('\n', '\\n')
        return text

    def setMode(self, mode):
        """Change search mode.
        i.e. from "Search file" to "Replace directory"
        """
        if self._mode == mode and self.isVisible():
            if core.workspace().currentDocument() is not None and \
               not core.workspace().currentDocument().hasFocus():
                self.cbSearch.lineEdit().selectAll()
                self.cbSearch.setFocus()

        self._mode = mode

        # Set Search and Replace text
        document = core.workspace().currentDocument()
        if document is not None and \
           document.hasFocus() and \
           document.qutepart.selectedText:
            searchText = document.qutepart.selectedText

            self.cbReplace.setEditText(self._makeEscapeSeqsVisible(searchText))

            if self.cbRegularExpression.checkState() == Qt.Checked:
                searchText = self._regExEscape(searchText)
            self.cbSearch.setEditText(searchText)

        if not self.cbReplace.lineEdit().text() and \
                self.cbSearch.lineEdit().text() and \
                not self.cbRegularExpression.checkState() == Qt.Checked:
            replaceText = self.cbSearch.lineEdit().text().replace('\\', '\\\\')
            self.cbReplace.setEditText(replaceText)

        # Move focus to Search edit
        self.cbSearch.setFocus()
        self.cbSearch.lineEdit().selectAll()

        # Set search path
        if mode & MODE_FLAG_DIRECTORY and \
           not (self.isVisible() and self.cbPath.isVisible()):
            try:
                searchPath = os.path.abspath(str(os.path.curdir))
            except OSError:  # current directory might have been deleted
                pass
            else:
                self.cbPath.setEditText(searchPath)

        # Set widgets visibility flag according to state
        widgets = (self.wSearch, self.pbPrevious, self.pbNext, self.pbSearch, self.wReplace, self.wPath,
                   self.pbReplace, self.pbReplaceAll, self.pbReplaceChecked, self.wOptions, self.wMask)
        #                         wSear  pbPrev pbNext pbSear wRepl  wPath  pbRep  pbRAll pbRCHK wOpti wMask
        visible = \
            {MODE_SEARCH:               (1,     1,     1,     0,     0,     0,     0,     1,     1,    1,    0,),
             MODE_REPLACE:               (1,     1,     1,     0,     1,     0,     1,     1,     0,    1,    0,),
             MODE_SEARCH_DIRECTORY:      (1,     0,     0,     1,     0,     1,     0,     0,     0,    1,    1,),
             MODE_REPLACE_DIRECTORY:     (1,     0,     0,     1,     1,     1,     0,     0,     1,    1,    1,),
             MODE_SEARCH_OPENED_FILES:   (1,     0,     0,     1,     0,     0,     0,     0,     0,    1,    1,),
             MODE_REPLACE_OPENED_FILES:  (1,     0,     0,     1,     1,     0,     0,     0,     1,    1,    1,)}

        for i, widget in enumerate(widgets):
            widget.setVisible(visible[mode][i])

        # Search next button text
        if mode == MODE_REPLACE:
            self.pbNext.setText('Next')
        else:
            self.pbNext.setText('Next↵')

        # Finaly show all with valid size
        self.show()  # show before updating widgets and labels
        self._updateLabels()
        self._updateWidgets()

    def eventFilter(self, object_, event):
        """ Event filter for mode switch tool button
        Draws icons in the search and path lineEdits
        """
        if event.type() == QEvent.Paint and object_ is self.tbCdUp:  # draw CdUp button in search path QLineEdit
            toolButton = object_
            lineEdit = self.cbPath.lineEdit()
            lineEdit.setContentsMargins(lineEdit.height(), 0, 0, 0)

            height = lineEdit.height()
            availableRect = QRect(0, 0, height, height)

            if toolButton.rect() != availableRect:
                toolButton.setGeometry(availableRect)

            painter = QPainter(toolButton)
            toolButton.icon().paint(painter, availableRect)

            return True

        elif event.type() == QEvent.KeyPress:  # Tab and Shift+Tab in QLineEdits

            if event.key() == Qt.Key_Tab:
                self._moveFocus(1)
                return True
            elif event.key() == Qt.Key_Backtab:
                self._moveFocus(-1)
                return True

        return QFrame.eventFilter(self, object_, event)

    def _onReturnPressed(self):
        """Return or Enter pressed on widget.
        Search next or Replace next
        """
        if self.pbReplace.isVisible():
            self.pbReplace.click()
        elif self.pbNext.isVisible():
            self.pbNext.click()
        elif self.pbSearch.isVisible():
            self.pbSearch.click()
        elif self.pbSearchStop.isVisible():
            self.pbSearchStop.click()

    def _onPathBackspace(self):
        """Ctrl+Backspace pressed on path.
        Remove 1 path level.
        Default behavior would be to remove one word on Linux or all on Windows
        """
        path = self.cbPath.currentText()
        if path.endswith('/') or \
           path.endswith('\\'):
            path = path[:-1]

        head, tail = os.path.split(path)
        if head and \
           head != path:
            if not head.endswith(os.sep):
                head += os.sep
            self.cbPath.lineEdit().setText(head)

    def _moveFocus(self, step):
        """Move focus forward or backward according to step.
        Standard Qt Keyboard focus algorithm doesn't allow circular navigation
        """
        allFocusableWidgets = (self.cbSearch, self.cbReplace, self.cbPath, self.cbMask)
        visibleWidgets = [widget for widget in allFocusableWidgets
                          if widget.isVisible()]

        try:
            focusedIndex = visibleWidgets.index(QApplication.focusWidget())
        except ValueError:
            print('Invalid focused widget in Search Widget', file=sys.stderr)
            return

        nextFocusedIndex = (focusedIndex + step) % len(visibleWidgets)

        visibleWidgets[nextFocusedIndex].setFocus()
        visibleWidgets[nextFocusedIndex].lineEdit().selectAll()

    def _updateLabels(self):
        """Update 'Search' 'Replace' 'Path' labels geometry
        """
        width = 0

        if self.lSearch.isVisible():
            width = max(width, self.lSearch.minimumSizeHint().width())

        if self.lReplace.isVisible():
            width = max(width, self.lReplace.minimumSizeHint().width())

        if self.lPath.isVisible():
            width = max(width, self.lPath.minimumSizeHint().width())

        self.lSearch.setMinimumWidth(width)
        self.lReplace.setMinimumWidth(width)
        self.lPath.setMinimumWidth(width)

    def _updateWidgets(self):
        """Update geometry of widgets with buttons
        """
        width = 0

        if self.wSearchRight.isVisible():
            width = max(width, self.wSearchRight.minimumSizeHint().width())

        if self.wReplaceRight.isVisible():
            width = max(width, self.wReplaceRight.minimumSizeHint().width())

        if self.wPathRight.isVisible():
            width = max(width, self.wPathRight.minimumSizeHint().width())

        self.wSearchRight.setMinimumWidth(width)
        self.wReplaceRight.setMinimumWidth(width)
        self.wPathRight.setMinimumWidth(width)

    def updateComboBoxes(self):
        """Update comboboxes with last used texts
        """
        searchText = self.cbSearch.currentText()
        replaceText = self.cbReplace.currentText()
        maskText = self.cbMask.currentText()

        # search
        if searchText:
            index = self.cbSearch.findText(searchText)

            if index == -1:
                self.cbSearch.addItem(searchText)

        # replace
        if replaceText:
            index = self.cbReplace.findText(replaceText)

            if index == -1:
                self.cbReplace.addItem(replaceText)

        # mask
        if maskText:
            index = self.cbMask.findText(maskText)

            if index == -1:
                self.cbMask.addItem(maskText)

    def _searchPatternTextAndFlags(self):
        """Get search pattern and flags
        """
        pattern = self.cbSearch.currentText()

        pattern = pattern.replace('\u2029', '\n')  # replace unicode paragraph separator with habitual \n

        if not self.cbRegularExpression.checkState() == Qt.Checked:
            pattern = re.escape(pattern)

        if self.cbWholeWord.checkState() == Qt.Checked:
            pattern = r'\b' + pattern + r'\b'

        flags = 0
        if not self.cbCaseSensitive.checkState() == Qt.Checked:
            flags = re.IGNORECASE
        return pattern, flags

    def getRegExp(self):
        """Read search parameters from controls and present it as a regular expression
        """
        pattern, flags = self._searchPatternTextAndFlags()
        return re.compile(pattern, flags)

    def isSearchRegExpValid(self):
        """Try to compile search pattern to check if it is valid
        Returns bool result and text error
        """
        pattern, flags = self._searchPatternTextAndFlags()
        try:
            re.compile(pattern, flags)
        except re.error as ex:
            return False, str(ex)

        return True, None

    def _getSearchMask(self):
        """Get search mask as list of patterns
        """
        mask = [s.strip() for s in self.cbMask.currentText().split(' ')]
        # remove empty
        mask = [_f for _f in mask if _f]
        return mask

    def setState(self, state):
        """Change line edit color according to search result
        """
        widget = self.cbSearch.lineEdit()

        color = {SearchWidget.Normal: QApplication.instance().palette().color(QPalette.Base),
                 SearchWidget.Good: QColor(Qt.green),
                 SearchWidget.Bad: QColor(Qt.red),
                 SearchWidget.Incorrect: QColor(Qt.darkYellow)}

        stateColor = color[state]
        if state != SearchWidget.Normal:
            stateColor.setAlpha(100)

        pal = widget.palette()
        pal.setColor(widget.backgroundRole(), stateColor)
        widget.setPalette(pal)

    def setSearchInProgress(self, inProgress):
        """Search thread started or stopped
        """
        self.pbSearchStop.setVisible(inProgress)
        self.pbSearch.setVisible(not inProgress)
        self._updateWidgets()
        self._progress.setVisible(inProgress)

    def onSearchProgressChanged(self, value, total):
        """Signal from the thread, progress changed
        """
        self._progress.setValue(value)
        self._progress.setMaximum(total)

    def setReplaceInProgress(self, inProgress):
        """Replace thread started or stopped
        """
        self.pbReplaceCheckedStop.setVisible(inProgress)
        self.pbReplaceChecked.setVisible(not inProgress)
        self._updateWidgets()

    def setSearchInFileActionsEnabled(self, enabled):
        """Set enabled state for Next, Prev, Replace, ReplaceAll
        """
        for button in (self.pbNext, self.pbPrevious, self.pbReplace, self.pbReplaceAll):
            button.setEnabled(enabled)

    def _onSearchRegExpChanged(self):
        """User edited search text or checked/unchecked checkboxes
        """
        valid, error = self.isSearchRegExpValid()
        if valid:
            self.setState(self.Normal)
            core.mainWindow().statusBar().clearMessage()
            self.pbSearch.setEnabled(len(self.getRegExp().pattern) > 0)
        else:
            core.mainWindow().statusBar().showMessage(error, 3000)
            self.setState(self.Incorrect)
            self.pbSearch.setEnabled(False)
            self.searchRegExpChanged.emit(re.compile(''))
            return

        self.searchRegExpChanged.emit(self.getRegExp())

    def _onCdUpPressed(self):
        """User pressed "Up" button, need to remove one level from search path
        """
        text = self.cbPath.currentText()
        if not os.path.exists(text):
            return

        editText = os.path.normpath(os.path.join(text, os.path.pardir))
        self.cbPath.setEditText(editText)

    def on_pbSearch_pressed(self):
        """Handler of click on "Search" button (for search in directory)
        """
        self.setState(SearchWidget.Normal)

        self.searchInDirectoryStartPressed.emit(self.getRegExp(),
                                                self._getSearchMask(),
                                                self.cbPath.currentText())

    def on_pbReplace_pressed(self):
        """Handler of click on "Replace" (in file) button
        """
        self.replaceFileOne.emit(self.cbReplace.currentText())

    def on_pbReplaceAll_pressed(self):
        """Handler of click on "Replace all" (in file) button
        """
        self.replaceFileAll.emit(self.cbReplace.currentText())

    def on_pbReplaceChecked_pressed(self):
        """Handler of click on "Replace checked" (in directory) button
        """
        self.replaceCheckedStartPressed.emit(self.cbReplace.currentText())

    def on_pbBrowse_pressed(self):
        """Handler of click on "Browse" button. Explores FS for search directory path
        """
        path = QFileDialog.getExistingDirectory(self, self.tr("Search path"), self.cbPath.currentText())

        if path:
            self.cbPath.setEditText(path)
示例#5
0
class ComboBox(QComboBox):
    """File browser combo box.
    Widget and functionality
    """

    def __init__(self, fileBrowser):
        QComboBox.__init__(self, fileBrowser)

        self._fileBrowser = fileBrowser

        self.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.setAttribute(Qt.WA_MacSmallSize)
        self.setEditable(True)
        self.setMinimumContentsLength(1)
        self.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.lineEdit().setReadOnly(False)
        self._completionModel = QDirModel(self.lineEdit())
        self._completionModel.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        self.lineEdit().setCompleter(QCompleter(self._completionModel,
                                                self.lineEdit()))
        # TODO QDirModel is deprecated but QCompleter does not yet handle
        # QFileSystemModel - please update when possible.
        self._count = 0

        # Show popup action
        self._showPopupAction = QAction(QIcon(':enkiicons/filtered.png'), "File browser history", self)
        self._showPopupAction.setShortcut('Ctrl+H')
        core.actionManager().addAction("mNavigation/mFileBrowser/aMenuShow", self._showPopupAction)
        self._showPopupAction.triggered.connect(self._onShowPopup)

        # reconnected in self.updateComboItems()
        self.currentIndexChanged[int].connect(self._onItemSelected)

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction(self._showPopupAction)

    def _onShowPopup(self, triggered):
        """Handler for self._showPopupAction
        """
        self._fileBrowser.show()
        self.showPopup()

    def _onItemSelected(self, index):
        """Handler of item selection in the combo box
        """
        if self.count() > self._count:  # It is user input
            path = self.itemText(index)
            if os.path.isdir(path):
                self._fileBrowser.setCurrentPath(path)
        else:
            path = self.itemData(index)
            self._fileBrowser.setCurrentPath(path)

    def updateItems(self, items):
        """Update items in the combo box according to current history
        """
        self.currentIndexChanged[int].disconnect()
        self.clear()
        # Current text
        self.addItem(os.path.normpath(self._fileBrowser.currentPath()))
        self.setItemData(0, self._fileBrowser.currentPath())
        self.insertSeparator(self.count())

        for index, path in enumerate(items):
            self.addItem(os.path.normpath(path))
            self.setItemData(index + 2, path)
        self._count = self.count()
        self.currentIndexChanged[int].connect(self._onItemSelected)
示例#6
0
class MyMainWindow(QWidget, Ui_Form):
    def __init__(self, proPath, parent=None):
        super(MyMainWindow, self).__init__(parent)
        # self.setCentralWidget(self.widget)
        self.proPath = proPath
        #************** 初始化窗口
        self.setupUi(self)
        # 设置窗口的标题
        self.setWindowTitle('ftpFilesys')
        # 设置窗口的图标,引用当前目录下的web.png图片
        self.setWindowIcon(QIcon(self.proPath + '/Icon/LOGO.jpg'))
        self.setSignal()
        self.downing = True
        #************** 初始化按键
        # self.Bt_down.setEnabled(False)
        self.Bt_up.setEnabled(False)
        # 创建SSH对象
        self.ssh = paramiko.SSHClient()
        # 允许连接不在know_hosts文件中的主机,否则可能报错:paramiko.ssh_exception.SSHException: Server '192.168.43.140' not found in known_hosts
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.ftp_root = "/home/sd/ftp/biaodingCloud"

        #************** 初始化变量
        """FTP窗口"""
        self.ftp = myFTP()  # 实例化FTP
        self.ftp.encoding = "utf-8"
        self.select_file = ""  # listView中选择的文件名
        self.file_list = []  # 存放查询FTP返回的当前目录所有文件列表
        self.ftp_tip = []  #存储当前登陆信息
        """本地窗口"""
        # self.sysfile = QFileSystemModel() # 获取本地文件系统
        # self.sysfile.setRootPath('')
        # self.treeView_local.setModel(self.sysfile)
        self.model = QDirModel()
        self.model.setFilter(QtCore.QDir.Dirs | QtCore.QDir.NoDotAndDotDot)
        self.treeView_local.header().hide()  # 隐藏表头
        self.treeView_local.setModel(self.model)
        for col in range(1, 4):
            self.treeView_local.setColumnHidden(col, True)

        self.save_path = ""
        # self.treeView_local.setRootIndex(self.model.index(self.save_path))
        self.save_name = ""

        self.lineEdit_ip.setText("192.168.200.11")
        self.lineEdit_port.setText("21")
        self.lineEdit_user.setText("admin")
        self.lineEdit_pwd.setText("123")

    # 信号绑定设置
    def setSignal(self):
        self.Bt_link.clicked.connect(self.ftp_connect)
        self.Bt_down.clicked.connect(self.Bt_down_run)
        # self.Bt_up.clicked.connect(self.ftp_connect)
        # 任意输入框改变就可以重新使能连接按键
        self.lineEdit_ip.editingFinished.connect(self.BtEnabled)
        self.lineEdit_port.editingFinished.connect(self.BtEnabled)
        self.lineEdit_user.editingFinished.connect(self.BtEnabled)
        self.lineEdit_pwd.editingFinished.connect(self.BtEnabled)
        self.treeWidget_master.itemClicked.connect(self.select_item_ftp)
        # self.treeView_local.doubleClicked.connect(self.select_item_local)
        self.treeView_local.expanded.connect(self.select_item_local)
        self.treeView_local.clicked.connect(self.select_item_local)
        # self.treeWidget_master.doubleClicked.connect(self.cd_button)

    # 按键使能
    def BtEnabled(self):
        self.Bt_link.setEnabled(True)
        self.Bt_down.setEnabled(False)
        self.Bt_up.setEnabled(False)

    # ftp 连接登录
    def ftp_connect(self):
        self.Text_log.append("linking...")
        self.treeWidget_master.clear()
        host = self.lineEdit_ip.text()  # 获取IP地址框内容
        port = int(self.lineEdit_port.text())  # 获取端口号,注意要转换为int
        usr = self.lineEdit_user.text()  # 获取用户名
        pwd = self.lineEdit_pwd.text()  # 获取密码
        self.ftp_tip.append(host)
        self.ftp_tip.append(port)
        self.ftp_tip.append(usr)
        self.ftp_tip.append(pwd)

        try:
            self.ftp.connect(host, port, timeout=10)  # 连接FTP
        except:
            logging.warning('network connect time out')  # 打印日志信息
            self.Text_log.append("network connect time out")
        try:
            self.ftp.login(usr, pwd)  # 登录FTP
        except:
            logging.warning("username or password error")  # 打印日志信息
            self.Text_log.append("username or password error")

        self.file_list = self.ftp.nlst()  # 查询当前目录的所有文件列表
        self.Bt_link.setEnabled(False)

        self.root = QTreeWidgetItem(self.treeWidget_master)
        self.root.setText(0, '/')
        # print(self.setItemIcon('/'))
        self.root.setIcon(0, QIcon(self.setItem_Icon('/')))
        self.root.setToolTip(0, '/')
        # self.dirItem(self.file_list,self.root)
        self.dirItem(self.root)
        self.cursor = self.Text_log.textCursor()
        self.Text_log.moveCursor(self.cursor.End)
        self.Text_log.append("link success!")
        # 连接服务器
        result = self.ssh.connect(hostname=b'192.168.200.11',
                                  port=22,
                                  username=b'fs_001',
                                  password=b'123')
        self.Text_log.append(result)
        print("link success!")
        self.treeWidget_master.itemExpanded.connect(self.dirItem_new)
        # for col in range(len(self.file_list)):
        #     print(col)
        #     self.treeWidget_master.setColumnHidden(col, True)
        # print(self.ftp.dir('/DetectMask.zip'))

    # 判断数据类型,设置图标
    def setItem_Icon(self, obj):
        if "." in obj:  # 是文件则不能进入
            icon = self.proPath + "/Icon/file.png"
        else:  # 是文件夹则可以进入
            icon = self.proPath + "/Icon/folder.png"

        if ".jpg" in obj or ".jpeg" in obj or ".png" in obj:  # 是文件则不能进入
            icon = self.proPath + "/Icon/file_img.png"
        elif ".zip" in obj or ".rar" in obj or ".7z" in obj:
            icon = self.proPath + "/Icon/file_zip.png"

        elif ".xls" in obj or ".xlsx" in obj:
            icon = self.proPath + "/Icon/file_excel.png"
        elif ".ppt" in obj or "pptx" in obj:
            icon = self.proPath + "/Icon/file_ppt.png"
        elif ".doc" in obj or ".docx" in obj or ".7z" in obj:
            icon = self.proPath + "/Icon/file_word.png"
        elif ".pdf" in obj:
            icon = self.proPath + "/Icon/file_pdf.png"
        elif ".py" in obj:
            icon = self.proPath + "/Icon/file_python.png"
        return icon

    # 递归操作,遍历ftp服务器所有文件
    def dirItem(self, item):
        # print(item.toolTip((0)))
        list = self.ftp.nlst(str(item.toolTip(0)))
        # for name1 in list:
        #     print("      ",name1)
        #self.showMessage("加载... ", QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom, QtCore.Qt.black)
        for i, path in enumerate(list):
            name = path.split('/')[-1]
            child = QTreeWidgetItem(item)
            item.addChild(child)
            child.setIcon(0, QIcon(self.setItem_Icon(name)))
            child.setText(0, name)
            # print(self.ftp.pwd())
            child.setToolTip(0, path)
            # print(child.toolTip(0))
            # print(this,name)

    # 动态加载dirItem
    def dirItem_new(self, item):
        # print(item.toolTip((0)))
        # list=self.ftp.nlst(str(item.toolTip(0)))
        child_num = item.childCount()
        for i in range(child_num):
            cc_num = item.child(i).childCount()
            item.child(i).setExpanded(False)
            if cc_num >= 0:
                for j in range(cc_num):
                    item.child(i).removeChild(item.child(i).child(j))
            if self.checkFileDir(self.ftp, item.child(i).toolTip(0)) == "dir":
                self.dirItem(item.child(i))

        # time.sleep(10)

    """递归操作,耗费资源+加载时间久,放弃"""

    # 递归操作,遍历ftp服务器所有文件
    # def dirItem(self,list,item):
    #     # list=self.ftp.nlst(str(item.toolTip(0)))
    #     # self.showMessage("加载... ", QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom, QtCore.Qt.black)
    #     for i,name in enumerate(list):
    #         child = QTreeWidgetItem(item)
    #         item.addChild(child)
    #         child.setIcon(0,QIcon(self.setItem_Icon(name)))
    #         child.setText(0,name)
    #         child.setToolTip(0,self.ftp.pwd())
    #         # print(child.toolTip(0))
    #         # print(this,name)
    #         this = self.checkFileDir(self.ftp, name)
    #         if this == "dir":
    #             # print(self.ftp.pwd(), name)
    #             self.ftp.cwd(name)
    #             filelist=self.ftp.nlst()
    #             # for name1 in filelist:
    #             #     print("      ",name1)
    #             self.dirItem(filelist,child)
    #             self.ftp.cwd("..")

    # 判断是否为文件

    def checkFileDir(self, ftp, file_name):
        """
        判断当前目录下的文件与文件夹
        :param ftp: 实例化的FTP对象
        :param file_name: 文件名/文件夹名
        :return:返回字符串“File”为文件,“Dir”问文件夹,“Unknow”为无法识别
        """
        rec = ""
        try:
            rec = ftp.cwd(file_name)  # 需要判断的元素

            ftp.cwd("..")  # 如果能通过路径打开必为文件夹,在此返回上一级
        except ftplib.error_perm as fe:
            rec = fe  # 不能通过路劲打开必为文件,抓取其错误信息

        finally:
            # print(file_name,rec)
            if "550" in str(rec)[:3]:
                return "file"
            elif "250" in str(rec)[:3]:
                return "dir"
            else:
                return "unknow"

    # 单击选中,使能下载按键
    def select_item_ftp(self, item):
        # print(item.text(0),item.columnCount())
        self.select_item = item
        self.select_file = item.toolTip(0)
        # print(self.select_file)
        if '.' in self.select_file:  # 如果是文件,则可下载
            self.Bt_down.setEnabled(True)
        else:  # 否则是文件夹,不能下载
            self.Bt_down.setEnabled(False)

    # 选择文件保存目录
    def select_item_local(self, obj):
        self.save_path = self.model.filePath(obj)

    # 更新主窗口显示
    def handleDisplay(self, data):
        if "ERROR" in data:
            self.Text_log.append(data)
            self.Bt_link.setEnabled(True)

        else:
            self.cursor = self.Text_log.textCursor()
            self.cursor.select(QTextCursor.LineUnderCursor)
            self.cursor.removeSelectedText()
            # self.Text_log.moveCursor(QtGui.QTextCursor.StartOfLine, QtGui.QTextCursor.MoveAnchor)
            self.Text_log.insertPlainText(data)
            if data == "100%":
                self.downing = True
                self.Text_log.append("Download Success!")
                self.thread.quit()
                # 执行命令
                # stdin:标准输入(就是你输入的命令);stdout:标准输出(就是命令执行结果);stderr:标准错误(命令执行过程中如果出错了就把错误打到这里),stdout和stderr仅会输出一个
                self.mv_file()
                self.Bt_down.setEnabled(False)

    # 移动文件
    def mv_file(self):
        old_file = False
        path, file = os.path.split(self.select_file)
        print(self.ftp_root + path + "/old_file/")
        # s = self.ftp.mkd(path + "/old_file/")
        stdin, stdout, stderr = self.ssh.exec_command(
            "mkdir  %s" % (self.ftp_root + path + "/old_file/"))

        stdin, stdout, stderr = self.ssh.exec_command(
            "mv %s  %s" % (self.ftp_root + self.select_file,
                           self.ftp_root + path + "/old_file/" + file))
        #
        # self.Text_log.append(str(stdout))
        parent = self.select_item.parent()

        print(parent.toolTip(0))
        parent.setExpanded(False)
        parent.removeChild(self.select_item)
        child_num = parent.childCount()
        for i in range(child_num):
            if self.checkFileDir(self.ftp,
                                 parent.child(i).toolTip(0)) == "dir":
                if parent.child(i).text(0) == "old_file":

                    old_file = True
        if old_file == False:

            item_name = "old_file"
            child = QTreeWidgetItem(parent)
            parent.addChild(child)
            child.setIcon(0, QIcon(self.setItem_Icon(item_name)))
            child.setText(0, item_name)
            # print(self.ftp.pwd())
            child.setToolTip(0, path + "/old_file")
        # else:
        # child_num = parent.childCount()
        # for i in range(child_num):
        #     parent.removeChild(parent.child(i))
        # self.dirItem(parent)

        parent.setExpanded(True)
        self.treeWidget_master.update()
        # self.select_item.setExpanded(True)

    # 下载晚间操作
    def Bt_down_run(self):

        if self.save_path == "":
            self.Text_log.append("未选择保存路径")
            self.downing = True
            reply = QMessageBox.warning(self, "警告", "未选择保存路径", QMessageBox.Ok)
            return

        if self.select_file == "":
            self.Text_log.append("未选择下载文件")
            self.downing = True
            reply = QMessageBox.warning(self, "警告", "未选择下载文件", QMessageBox.Ok)
            return

        if self.downing == False:
            reply = QMessageBox.warning(self, "警告", "正在下载,请等待。。。",
                                        QMessageBox.Ok)
            return
        self.downing = False
        self.save_name = self.save_path + "/%s" % self.select_file.split(
            '/')[-1]
        # print(self.select_file, self.save_name)
        self.Text_log.append("开始下载,\"%s\"将文件下载到\"%s\"" %
                             (self.select_file, self.save_name))
        if self.checkFileDir(self.ftp, self.select_file) == "dir":
            self.Text_log.append("这是文件夹,不能下载")
            return

        print(self.save_name)

        if os.path.exists(self.save_name):
            reply = QMessageBox.warning(self, "文件已存在", "是否覆盖?",
                                        QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.Yes:
                os.remove(self.save_name)
            else:
                self.downing = True
                return

        # 创建线程
        self.down = down_Thread(self.ftp_tip, self.select_file, self.save_name)
        # self.down = down_Thread(self)
        # thread = MyThread(target=self.tcp_run, args=(self.ftp_tip,self.select_file,self.save_name))
        # target = self.ftp.retrbinary, args = ("RETR %s" % self.select_file, open(self.save_name, 'wb').write)
        # 连接信号
        self.down.update_date.connect(self.handleDisplay)
        self.thread = QThread()
        self.Text_log.append("0%")
        self.down.moveToThread(self.thread)
        # 开始线程
        self.thread.started.connect(self.down.run)
        self.thread.start()
示例#7
0
class SearchWidget(QFrame):
    """Widget, appeared, when Ctrl+F pressed.
    Has different forms for different search modes
    """

    Normal = 'normal'
    Good = 'good'
    Bad = 'bad'
    Incorrect = 'incorrect'

    visibilityChanged = pyqtSignal(bool)
    """
    visibilityChanged(visible)

    **Signal** emitted, when widget has been shown or hidden
    """  # pylint: disable=W0105

    searchInDirectoryStartPressed = pyqtSignal(type(re.compile('')), list, str)
    """
    searchInDirectoryStartPressed(regEx, mask, path)

    **Signal** emitted, when 'search in directory' button had been pressed
    """  # pylint: disable=W0105

    searchInDirectoryStopPressed = pyqtSignal()
    """
    searchInDirectoryStopPressed()

    **Signal** emitted, when 'stop search in directory' button had been pressed
    """  # pylint: disable=W0105

    replaceCheckedStartPressed = pyqtSignal(str)
    """
    replaceCheckedStartPressed(replText)

    **Signal** emitted, when 'replace checked' button had been pressed
    """  # pylint: disable=W0105

    replaceCheckedStopPressed = pyqtSignal()
    """
    replaceCheckedStartPressed()

    **Signal** emitted, when 'stop replacing checked' button had been pressed
    """  # pylint: disable=W0105

    searchRegExpChanged = pyqtSignal(type(re.compile('')))
    """
    searchRegExpValidStateChanged(regEx)

    **Signal** emitted, when search regexp has been changed.
    If reg exp is invalid - regEx object contains empty pattern
    """  # pylint: disable=W0105

    searchNext = pyqtSignal()
    """
    searchNext()

    **Signal** emitted, when 'Search Next' had been pressed
    """  # pylint: disable=W0105

    searchPrevious = pyqtSignal()
    """
    searchPrevious()

    **Signal** emitted, when 'Search Previous' had been pressed
    """  # pylint: disable=W0105

    replaceFileOne = pyqtSignal(str)
    """
    replaceFileOne(replText)

    **Signal** emitted, when 'Replace' had been pressed
    """  # pylint: disable=W0105

    replaceFileAll = pyqtSignal(str)
    """
    replaceFileAll(replText)

    **Signal** emitted, when 'Replace All' had been pressed
    """  # pylint: disable=W0105

    def __init__(self, plugin):
        QFrame.__init__(self, core.workspace())
        self._mode = None
        self.plugin = plugin
        uic.loadUi(os.path.join(os.path.dirname(__file__), 'SearchWidget.ui'),
                   self)

        self.cbSearch.setCompleter(None)
        self.cbReplace.setCompleter(None)
        self.cbMask.setCompleter(None)

        self.fsModel = QDirModel(self.cbPath.lineEdit())
        self.fsModel.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)

        self.cbPath.lineEdit().setCompleter(
            QCompleter(self.fsModel, self.cbPath.lineEdit()))
        self._pathBackspaceShortcut = QShortcut(QKeySequence("Ctrl+Backspace"),
                                                self.cbPath,
                                                self._onPathBackspace)
        self._pathBackspaceShortcut.setContext(Qt.WidgetWithChildrenShortcut)

        # TODO QDirModel is deprecated but QCompleter does not yet handle
        # QFileSystemodel - please update when possible."""
        self.cbSearch.setCompleter(None)
        self.pbSearchStop.setVisible(False)
        self.pbReplaceCheckedStop.setVisible(False)

        self._progress = QProgressBar(self)
        self._progress.setAlignment(Qt.AlignCenter)
        self._progress.setToolTip(self.tr("Search in progress..."))
        self._progress.setMaximumSize(QSize(80, 16))
        core.mainWindow().statusBar().insertPermanentWidget(1, self._progress)
        self._progress.setVisible(False)

        # cd up action
        self.tbCdUp = QToolButton(self.cbPath.lineEdit())
        self.tbCdUp.setIcon(QIcon(":/enkiicons/go-up.png"))
        self.tbCdUp.setCursor(Qt.ArrowCursor)
        self.tbCdUp.installEventFilter(self)  # for drawing button

        self.cbSearch.installEventFilter(
            self)  # for catching Tab and Shift+Tab
        self.cbReplace.installEventFilter(
            self)  # for catching Tab and Shift+Tab
        self.cbPath.installEventFilter(self)  # for catching Tab and Shift+Tab
        self.cbMask.installEventFilter(self)  # for catching Tab and Shift+Tab

        self._closeShortcut = QShortcut(QKeySequence("Esc"), self)
        self._closeShortcut.setContext(Qt.WidgetWithChildrenShortcut)
        self._closeShortcut.activated.connect(self.hide)

        # connections
        self.cbSearch.lineEdit().textChanged.connect(
            self._onSearchRegExpChanged)

        self.cbSearch.lineEdit().returnPressed.connect(self._onReturnPressed)
        self.cbReplace.lineEdit().returnPressed.connect(self._onReturnPressed)
        self.cbPath.lineEdit().returnPressed.connect(self._onReturnPressed)
        self.cbMask.lineEdit().returnPressed.connect(self._onReturnPressed)

        self.cbRegularExpression.stateChanged.connect(
            self._onSearchRegExpChanged)
        self.cbCaseSensitive.stateChanged.connect(self._onSearchRegExpChanged)
        self.cbWholeWord.stateChanged.connect(self._onSearchRegExpChanged)

        self.tbCdUp.clicked.connect(self._onCdUpPressed)

        self.pbNext.pressed.connect(self.searchNext)
        self.pbPrevious.pressed.connect(self.searchPrevious)
        self.pbSearchStop.pressed.connect(self.searchInDirectoryStopPressed)
        self.pbReplaceCheckedStop.pressed.connect(
            self.replaceCheckedStopPressed)

        core.mainWindow().hideAllWindows.connect(self.hide)
        core.workspace().escPressed.connect(self.hide)

        core.workspace().currentDocumentChanged.connect(
            lambda old, new: self.setVisible(self.isVisible() and new is
                                             not None))

    def show(self):
        """Reimplemented function. Sends signal
        """
        super(SearchWidget, self).show()
        self.visibilityChanged.emit(self.isVisible())

    def hide(self):
        """Reimplemented function.
        Sends signal, returns focus to workspace
        """
        super(SearchWidget, self).hide()
        core.workspace().focusCurrentDocument()
        self.visibilityChanged.emit(self.isVisible())

    def setVisible(self, visible):
        """Reimplemented function. Sends signal
        """
        super(SearchWidget, self).setVisible(visible)
        self.visibilityChanged.emit(self.isVisible())

    def _regExEscape(self, text):
        """Improved version of re.escape()
        Doesn't escape space, comma, underscore.
        Escapes \n and \t
        """
        text = re.escape(text)
        # re.escape escapes space, comma, underscore, but, it is not necessary and makes text not readable
        for symbol in (' ,_=\'"/:@#%&'):
            text = text.replace('\\' + symbol, symbol)

        text = text.replace('\\\n', '\\n')
        text = text.replace('\\\t', '\\t')

        return text

    def _makeEscapeSeqsVisible(self, text):
        """Replace invisible \n and \t with escape sequences
        """
        text = text.replace('\\', '\\\\')
        text = text.replace('\t', '\\t')
        text = text.replace('\n', '\\n')
        return text

    def setMode(self, mode):
        """Change search mode.
        i.e. from "Search file" to "Replace directory"
        """
        if self._mode == mode and self.isVisible():
            if core.workspace().currentDocument() is not None and \
               not core.workspace().currentDocument().hasFocus():
                self.cbSearch.lineEdit().selectAll()
                self.cbSearch.setFocus()

        self._mode = mode

        # Set Search and Replace text
        document = core.workspace().currentDocument()
        if document is not None and \
           document.hasFocus() and \
           document.qutepart.selectedText:
            searchText = document.qutepart.selectedText

            self.cbReplace.setEditText(self._makeEscapeSeqsVisible(searchText))

            if self.cbRegularExpression.checkState() == Qt.Checked:
                searchText = self._regExEscape(searchText)
            self.cbSearch.setEditText(searchText)

        if not self.cbReplace.lineEdit().text() and \
                self.cbSearch.lineEdit().text() and \
                not self.cbRegularExpression.checkState() == Qt.Checked:
            replaceText = self.cbSearch.lineEdit().text().replace('\\', '\\\\')
            self.cbReplace.setEditText(replaceText)

        # Move focus to Search edit
        self.cbSearch.setFocus()
        self.cbSearch.lineEdit().selectAll()

        # Set search path
        if mode & MODE_FLAG_DIRECTORY and \
           not (self.isVisible() and self.cbPath.isVisible()):
            try:
                searchPath = os.path.abspath(str(os.path.curdir))
            except OSError:  # current directory might have been deleted
                pass
            else:
                self.cbPath.setEditText(searchPath)

        # Set widgets visibility flag according to state
        widgets = (self.wSearch, self.pbPrevious, self.pbNext, self.pbSearch,
                   self.wReplace, self.wPath, self.pbReplace,
                   self.pbReplaceAll, self.pbReplaceChecked, self.wOptions,
                   self.wMask)
        #                         wSear  pbPrev pbNext pbSear wRepl  wPath  pbRep  pbRAll pbRCHK wOpti wMask
        visible = \
            {MODE_SEARCH:               (1,     1,     1,     0,     0,     0,     0,     1,     1,    1,    0,),
             MODE_REPLACE:               (1,     1,     1,     0,     1,     0,     1,     1,     0,    1,    0,),
             MODE_SEARCH_DIRECTORY:      (1,     0,     0,     1,     0,     1,     0,     0,     0,    1,    1,),
             MODE_REPLACE_DIRECTORY:     (1,     0,     0,     1,     1,     1,     0,     0,     1,    1,    1,),
             MODE_SEARCH_OPENED_FILES:   (1,     0,     0,     1,     0,     0,     0,     0,     0,    1,    1,),
             MODE_REPLACE_OPENED_FILES:  (1,     0,     0,     1,     1,     0,     0,     0,     1,    1,    1,)}

        for i, widget in enumerate(widgets):
            widget.setVisible(visible[mode][i])

        # Search next button text
        if mode == MODE_REPLACE:
            self.pbNext.setText('Next')
        else:
            self.pbNext.setText('Next↵')

        # Finaly show all with valid size
        self.show()  # show before updating widgets and labels
        self._updateLabels()
        self._updateWidgets()

    def eventFilter(self, object_, event):
        """ Event filter for mode switch tool button
        Draws icons in the search and path lineEdits
        """
        if event.type(
        ) == QEvent.Paint and object_ is self.tbCdUp:  # draw CdUp button in search path QLineEdit
            toolButton = object_
            lineEdit = self.cbPath.lineEdit()
            lineEdit.setContentsMargins(lineEdit.height(), 0, 0, 0)

            height = lineEdit.height()
            availableRect = QRect(0, 0, height, height)

            if toolButton.rect() != availableRect:
                toolButton.setGeometry(availableRect)

            painter = QPainter(toolButton)
            toolButton.icon().paint(painter, availableRect)

            return True

        elif event.type(
        ) == QEvent.KeyPress:  # Tab and Shift+Tab in QLineEdits

            if event.key() == Qt.Key_Tab:
                self._moveFocus(1)
                return True
            elif event.key() == Qt.Key_Backtab:
                self._moveFocus(-1)
                return True

        return QFrame.eventFilter(self, object_, event)

    def _onReturnPressed(self):
        """Return or Enter pressed on widget.
        Search next or Replace next
        """
        if self.pbReplace.isVisible():
            self.pbReplace.click()
        elif self.pbNext.isVisible():
            self.pbNext.click()
        elif self.pbSearch.isVisible():
            self.pbSearch.click()
        elif self.pbSearchStop.isVisible():
            self.pbSearchStop.click()

    def _onPathBackspace(self):
        """Ctrl+Backspace pressed on path.
        Remove 1 path level.
        Default behavior would be to remove one word on Linux or all on Windows
        """
        path = self.cbPath.currentText()
        if path.endswith('/') or \
           path.endswith('\\'):
            path = path[:-1]

        head, tail = os.path.split(path)
        if head and \
           head != path:
            if not head.endswith(os.sep):
                head += os.sep
            self.cbPath.lineEdit().setText(head)

    def _moveFocus(self, step):
        """Move focus forward or backward according to step.
        Standard Qt Keyboard focus algorithm doesn't allow circular navigation
        """
        allFocusableWidgets = (self.cbSearch, self.cbReplace, self.cbPath,
                               self.cbMask)
        visibleWidgets = [
            widget for widget in allFocusableWidgets if widget.isVisible()
        ]

        try:
            focusedIndex = visibleWidgets.index(QApplication.focusWidget())
        except ValueError:
            print('Invalid focused widget in Search Widget', file=sys.stderr)
            return

        nextFocusedIndex = (focusedIndex + step) % len(visibleWidgets)

        visibleWidgets[nextFocusedIndex].setFocus()
        visibleWidgets[nextFocusedIndex].lineEdit().selectAll()

    def _updateLabels(self):
        """Update 'Search' 'Replace' 'Path' labels geometry
        """
        width = 0

        if self.lSearch.isVisible():
            width = max(width, self.lSearch.minimumSizeHint().width())

        if self.lReplace.isVisible():
            width = max(width, self.lReplace.minimumSizeHint().width())

        if self.lPath.isVisible():
            width = max(width, self.lPath.minimumSizeHint().width())

        self.lSearch.setMinimumWidth(width)
        self.lReplace.setMinimumWidth(width)
        self.lPath.setMinimumWidth(width)

    def _updateWidgets(self):
        """Update geometry of widgets with buttons
        """
        width = 0

        if self.wSearchRight.isVisible():
            width = max(width, self.wSearchRight.minimumSizeHint().width())

        if self.wReplaceRight.isVisible():
            width = max(width, self.wReplaceRight.minimumSizeHint().width())

        if self.wPathRight.isVisible():
            width = max(width, self.wPathRight.minimumSizeHint().width())

        self.wSearchRight.setMinimumWidth(width)
        self.wReplaceRight.setMinimumWidth(width)
        self.wPathRight.setMinimumWidth(width)

    def updateComboBoxes(self):
        """Update comboboxes with last used texts
        """
        searchText = self.cbSearch.currentText()
        replaceText = self.cbReplace.currentText()
        maskText = self.cbMask.currentText()

        # search
        if searchText:
            index = self.cbSearch.findText(searchText)

            if index == -1:
                self.cbSearch.addItem(searchText)

        # replace
        if replaceText:
            index = self.cbReplace.findText(replaceText)

            if index == -1:
                self.cbReplace.addItem(replaceText)

        # mask
        if maskText:
            index = self.cbMask.findText(maskText)

            if index == -1:
                self.cbMask.addItem(maskText)

    def _searchPatternTextAndFlags(self):
        """Get search pattern and flags
        """
        pattern = self.cbSearch.currentText()

        pattern = pattern.replace(
            '\u2029',
            '\n')  # replace unicode paragraph separator with habitual \n

        if not self.cbRegularExpression.checkState() == Qt.Checked:
            pattern = re.escape(pattern)

        if self.cbWholeWord.checkState() == Qt.Checked:
            pattern = r'\b' + pattern + r'\b'

        flags = 0
        if not self.cbCaseSensitive.checkState() == Qt.Checked:
            flags = re.IGNORECASE
        return pattern, flags

    def getRegExp(self):
        """Read search parameters from controls and present it as a regular expression
        """
        pattern, flags = self._searchPatternTextAndFlags()
        return re.compile(pattern, flags)

    def isSearchRegExpValid(self):
        """Try to compile search pattern to check if it is valid
        Returns bool result and text error
        """
        pattern, flags = self._searchPatternTextAndFlags()
        try:
            re.compile(pattern, flags)
        except re.error as ex:
            return False, str(ex)

        return True, None

    def _getSearchMask(self):
        """Get search mask as list of patterns
        """
        mask = [s.strip() for s in self.cbMask.currentText().split(' ')]
        # remove empty
        mask = [_f for _f in mask if _f]
        return mask

    def setState(self, state):
        """Change line edit color according to search result
        """
        widget = self.cbSearch.lineEdit()

        color = {
            SearchWidget.Normal:
            QApplication.instance().palette().color(QPalette.Base),
            SearchWidget.Good:
            QColor(Qt.green),
            SearchWidget.Bad:
            QColor(Qt.red),
            SearchWidget.Incorrect:
            QColor(Qt.darkYellow)
        }

        stateColor = color[state]
        if state != SearchWidget.Normal:
            stateColor.setAlpha(100)

        pal = widget.palette()
        pal.setColor(widget.backgroundRole(), stateColor)
        widget.setPalette(pal)

    def setSearchInProgress(self, inProgress):
        """Search thread started or stopped
        """
        self.pbSearchStop.setVisible(inProgress)
        self.pbSearch.setVisible(not inProgress)
        self._updateWidgets()
        self._progress.setVisible(inProgress)

    def onSearchProgressChanged(self, value, total):
        """Signal from the thread, progress changed
        """
        self._progress.setValue(value)
        self._progress.setMaximum(total)

    def setReplaceInProgress(self, inProgress):
        """Replace thread started or stopped
        """
        self.pbReplaceCheckedStop.setVisible(inProgress)
        self.pbReplaceChecked.setVisible(not inProgress)
        self._updateWidgets()

    def setSearchInFileActionsEnabled(self, enabled):
        """Set enabled state for Next, Prev, Replace, ReplaceAll
        """
        for button in (self.pbNext, self.pbPrevious, self.pbReplace,
                       self.pbReplaceAll):
            button.setEnabled(enabled)

    def _onSearchRegExpChanged(self):
        """User edited search text or checked/unchecked checkboxes
        """
        valid, error = self.isSearchRegExpValid()
        if valid:
            self.setState(self.Normal)
            core.mainWindow().statusBar().clearMessage()
            self.pbSearch.setEnabled(len(self.getRegExp().pattern) > 0)
        else:
            core.mainWindow().statusBar().showMessage(error, 3000)
            self.setState(self.Incorrect)
            self.pbSearch.setEnabled(False)
            self.searchRegExpChanged.emit(re.compile(''))
            return

        self.searchRegExpChanged.emit(self.getRegExp())

    def _onCdUpPressed(self):
        """User pressed "Up" button, need to remove one level from search path
        """
        text = self.cbPath.currentText()
        if not os.path.exists(text):
            return

        editText = os.path.normpath(os.path.join(text, os.path.pardir))
        self.cbPath.setEditText(editText)

    def on_pbSearch_pressed(self):
        """Handler of click on "Search" button (for search in directory)
        """
        self.setState(SearchWidget.Normal)

        self.searchInDirectoryStartPressed.emit(self.getRegExp(),
                                                self._getSearchMask(),
                                                self.cbPath.currentText())

    def on_pbReplace_pressed(self):
        """Handler of click on "Replace" (in file) button
        """
        self.replaceFileOne.emit(self.cbReplace.currentText())

    def on_pbReplaceAll_pressed(self):
        """Handler of click on "Replace all" (in file) button
        """
        self.replaceFileAll.emit(self.cbReplace.currentText())

    def on_pbReplaceChecked_pressed(self):
        """Handler of click on "Replace checked" (in directory) button
        """
        self.replaceCheckedStartPressed.emit(self.cbReplace.currentText())

    def on_pbBrowse_pressed(self):
        """Handler of click on "Browse" button. Explores FS for search directory path
        """
        path = QFileDialog.getExistingDirectory(self, self.tr("Search path"),
                                                self.cbPath.currentText())

        if path:
            self.cbPath.setEditText(path)
示例#8
0
    def __init__(self):
        super(GeneralPreferences, self).__init__()
        grid = QGridLayout(self)
        grid.setSpacing(20)
        grid.setColumnStretch(1, 10)

        # directory auto completer
        completer = QCompleter(self)
        dirs = QDirModel(self)
        dirs.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        completer.setModel(dirs)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        completer.setCompletionMode(QCompleter.PopupCompletion)

        label = QLabel(
            "<b>Ingresá el directorio donde descargar los videos...</b>")
        label.setTextFormat(Qt.RichText)
        grid.addWidget(label, 0, 0, 1, 2)

        grid.addWidget(QLabel("Descargar en:"), 1, 0, 2, 1)
        prv = config.get('downloaddir', '')
        self.downloaddir_entry = QLineEdit(prv)
        self.downloaddir_entry.setCompleter(completer)
        self.downloaddir_entry.setPlaceholderText('Ingresá un directorio')
        grid.addWidget(self.downloaddir_entry, 1, 1, 2, 2)

        self.downloaddir_buttn = QPushButton("Elegir un directorio")
        self.downloaddir_buttn.clicked.connect(self._choose_dir)
        grid.addWidget(self.downloaddir_buttn, 2, 1, 3, 2)

        self.autoreload_checkbox = QCheckBox(
            "Recargar automáticamente la lista de episodios al iniciar")
        self.autoreload_checkbox.setToolTip(
            "Cada vez que arranca el programa refrescar la lista de episodios."
        )
        prv = config.get('autorefresh', False)
        self.autoreload_checkbox.setChecked(prv)
        grid.addWidget(self.autoreload_checkbox, 3, 0, 4, 2)

        self.shownotifs_checkbox = QCheckBox(
            "Mostrar una notificación cuando termina cada descarga")
        self.shownotifs_checkbox.setToolTip(
            "Hacer que el escritorio muestre una notificación cada vez que una descarga "
            "se complete.")
        prv = config.get('notification', True)
        self.shownotifs_checkbox.setChecked(prv)
        grid.addWidget(self.shownotifs_checkbox, 4, 0, 5, 2)

        self.cleanfnames_checkbox = QCheckBox(
            "Limpiar nombres para que se pueda guardar en cualquier lado")
        self.cleanfnames_checkbox.setToolTip(
            "Convertir caracteres extraños en títulos para que el archivo se pueda grabar en "
            "cualquier disco o pendrive.")
        prv = config.get('clean-filenames', False)
        self.cleanfnames_checkbox.setChecked(prv)
        grid.addWidget(self.cleanfnames_checkbox, 5, 0, 6, 2)

        lq = QLabel(
            "<b>Ingrese la Calidad de Video Preferida para las Descargas:</b>")
        lq.setTextFormat(Qt.RichText)
        grid.addWidget(lq, 8, 0, 7, 2)

        lqd = QLabel("* En caso de no existir se eligirá la más conveniente.")
        lqd.setTextFormat(Qt.RichText)
        grid.addWidget(lqd, 9, 0, 8, 2)

        self.select_quality = QComboBox()
        self.select_quality.setGeometry(QRect())
        self.select_quality.setObjectName("Calidad de Video Preferida")
        self.select_quality.addItem("1080p")  # HD
        self.select_quality.addItem("720p")  # ALTA
        self.select_quality.addItem("480p")  # MEDIA
        self.select_quality.addItem("360p")  # BAJA
        self.select_quality.addItem("240p")  # MALA
        self.select_quality.activated[str].connect(self.selected)

        prv = config.get('quality', '720p')
        self.select_quality.setCurrentText(prv)
        grid.addWidget(self.select_quality, 10, 0, 9, 2)