Пример #1
0
class MainGlyphWindow(QMainWindow):
    def __init__(self, glyph, parent=None):
        super().__init__(parent)

        menuBar = self.menuBar()
        fileMenu = QMenu("&File", self)
        fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
        menuBar.addMenu(fileMenu)
        editMenu = QMenu("&Edit", self)
        self._undoAction = editMenu.addAction("&Undo", self.undo,
                                              QKeySequence.Undo)
        self._redoAction = editMenu.addAction("&Redo", self.redo,
                                              QKeySequence.Redo)
        editMenu.addSeparator()
        # XXX
        action = editMenu.addAction("C&ut", self.cutOutlines, QKeySequence.Cut)
        action.setEnabled(False)
        self._copyAction = editMenu.addAction("&Copy", self.copyOutlines,
                                              QKeySequence.Copy)
        editMenu.addAction("&Paste", self.pasteOutlines, QKeySequence.Paste)
        editMenu.addAction("Select &All", self.selectAll,
                           QKeySequence.SelectAll)
        editMenu.addAction("&Deselect", self.deselect, "Ctrl+D")
        menuBar.addMenu(editMenu)
        glyphMenu = QMenu("&Glyph", self)
        glyphMenu.addAction("&Next Glyph", lambda: self.glyphOffset(1), "End")
        glyphMenu.addAction("&Previous Glyph", lambda: self.glyphOffset(-1),
                            "Home")
        glyphMenu.addAction("&Go To…", self.changeGlyph, "G")
        glyphMenu.addSeparator()
        self._layerAction = glyphMenu.addAction("&Layer Actions…",
                                                self.layerActions, "L")
        menuBar.addMenu(glyphMenu)

        # create tools and buttons toolBars
        self._tools = []
        self._toolsActionGroup = QActionGroup(self)
        self._toolsToolBar = QToolBar("Tools", self)
        self._toolsToolBar.setMovable(False)
        self._buttons = []
        self._buttonsToolBar = QToolBar("Buttons", self)
        self._buttonsToolBar.setMovable(False)
        self.addToolBar(self._toolsToolBar)
        self.addToolBar(self._buttonsToolBar)

        # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/
        self._layersToolBar = QToolBar("Layers", self)
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self._currentLayerBox = QComboBox(self)
        self._currentLayerBox.currentIndexChanged.connect(self._layerChanged)
        self._layersToolBar.addWidget(spacer)
        self._layersToolBar.addWidget(self._currentLayerBox)
        self._layersToolBar.setContentsMargins(0, 0, 2, 0)
        self._layersToolBar.setMovable(False)
        self.addToolBar(self._layersToolBar)

        viewMenu = self.createPopupMenu()
        viewMenu.setTitle("View")
        viewMenu.addSeparator()
        action = viewMenu.addAction("Lock Toolbars", self.lockToolBars)
        action.setCheckable(True)
        action.setChecked(True)
        menuBar.addMenu(viewMenu)

        self.view = GlyphView(self)
        self.setGlyph(glyph)
        selectionTool = self.installTool(SelectionTool)
        selectionTool.trigger()
        self.installTool(PenTool)
        self.installTool(RulerTool)
        self.installTool(KnifeTool)
        self.installButton(RemoveOverlapButton)

        self.setCentralWidget(self.view.scrollArea())
        self.resize(900, 700)
        self.view.setFocus(True)

    # ----------
    # Menu items
    # ----------

    def glyphOffset(self, offset):
        currentGlyph = self.view.glyph()
        font = currentGlyph.font
        glyphOrder = font.glyphOrder
        # should be enforced in fontView already
        if not (glyphOrder and len(glyphOrder)):
            return
        index = glyphOrder.index(currentGlyph.name)
        newIndex = (index + offset) % len(glyphOrder)
        glyph = font[glyphOrder[newIndex]]
        self.setGlyph(glyph)

    def changeGlyph(self):
        glyph = self.view.glyph()
        newGlyph, ok = GotoDialog.getNewGlyph(self, glyph)
        if ok and newGlyph is not None:
            self.setGlyph(newGlyph)

    def layerActions(self):
        glyph = self.view.glyph()
        newLayer, action, ok = LayerActionsDialog.getLayerAndAction(
            self, glyph)
        if ok and newLayer is not None:
            # TODO: whole glyph for now, but consider selection too
            if not glyph.name in newLayer:
                newLayer.newGlyph(glyph.name)
            otherGlyph = newLayer[glyph.name]
            otherGlyph.disableNotifications()
            if action == "Swap":
                tempGlyph = TGlyph()
                otherGlyph.drawPoints(tempGlyph.getPointPen())
                tempGlyph.width = otherGlyph.width
                otherGlyph.clearContours()
            glyph.drawPoints(otherGlyph.getPointPen())
            otherGlyph.width = glyph.width
            if action != "Copy":
                glyph.disableNotifications()
                glyph.clearContours()
                if action == "Swap":
                    tempGlyph.drawPoints(glyph.getPointPen())
                    glyph.width = tempGlyph.width
                glyph.enableNotifications()
            otherGlyph.enableNotifications()

    def undo(self):
        glyph = self.view.glyph()
        glyph.undo()

    def redo(self):
        glyph = self.view.glyph()
        glyph.redo()

    def cutOutlines(self):
        pass

    def copyOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = QMimeData()
        copyGlyph = glyph.getRepresentation("defconQt.FilterSelection")
        mimeData.setData(
            "application/x-defconQt-glyph-data",
            pickle.dumps([copyGlyph.serialize(blacklist=("name", "unicode"))]))
        clipboard.setMimeData(mimeData)

    def pasteOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = clipboard.mimeData()
        if mimeData.hasFormat("application/x-defconQt-glyph-data"):
            data = pickle.loads(
                mimeData.data("application/x-defconQt-glyph-data"))
            if len(data) == 1:
                pen = glyph.getPointPen()
                pasteGlyph = TGlyph()
                pasteGlyph.deserialize(data[0])
                # TODO: if we serialize selected state, we don't need to do
                # this
                pasteGlyph.selected = True
                if len(pasteGlyph) or len(pasteGlyph.components) or \
                        len(pasteGlyph.anchors):
                    glyph.prepareUndo()
                    pasteGlyph.drawPoints(pen)

    def selectAll(self):
        glyph = self.view.glyph()
        glyph.selected = True
        if not len(glyph):
            for component in glyph.components:
                component.selected = True

    def deselect(self):
        glyph = self.view.glyph()
        for anchor in glyph.anchors:
            anchor.selected = False
        for component in glyph.components:
            component.selected = False
        glyph.selected = False

    def lockToolBars(self):
        action = self.sender()
        movable = not action.isChecked()
        for toolBar in (self._toolsToolBar, self._buttonsToolBar,
                        self._layersToolBar):
            toolBar.setMovable(movable)

    # --------------------------
    # Tools & buttons management
    # --------------------------

    def installTool(self, tool):
        # TODO: add shortcut with number
        action = self._toolsToolBar.addAction(QIcon(tool.iconPath), tool.name,
                                              self._setViewTool)
        action.setCheckable(True)
        action.setData(len(self._tools))
        self._toolsActionGroup.addAction(action)
        self._tools.append(tool(parent=self.view))
        return action

    def uninstallTool(self, tool):
        pass  # XXX

    def _setViewTool(self):
        action = self.sender()
        action.setChecked(True)
        index = action.data()
        self.view.currentTool = self._tools[index]

    def installButton(self, button):
        action = self._buttonsToolBar.addAction(QIcon(button.iconPath),
                                                button.name,
                                                self._buttonAction)
        action.setData(len(self._buttons))
        self._buttons.append(button(parent=self.view))
        return action

    def uninstallButton(self, button):
        pass  # XXX

    def _buttonAction(self):
        action = self.sender()
        index = action.data()
        button = self._buttons[index]
        button.clicked()

    # --------------------
    # Notification support
    # --------------------

    # glyph

    def _subscribeToGlyph(self, glyph):
        if glyph is not None:
            glyph.addObserver(self, "_glyphChanged", "Glyph.Changed")
            glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged")
            glyph.addObserver(self, "_glyphSelectionChanged",
                              "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.connect(self._undoAction.setEnabled)
            undoManager.canRedoChanged.connect(self._redoAction.setEnabled)
            self._subscribeToFontAndLayerSet(glyph.font)

    def _unsubscribeFromGlyph(self, glyph):
        if glyph is not None:
            glyph.removeObserver(self, "Glyph.Changed")
            glyph.removeObserver(self, "Glyph.NameChanged")
            glyph.removeObserver(self, "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.disconnect(self._undoAction.setEnabled)
            undoManager.canRedoChanged.disconnect(self._redoAction.setEnabled)
            self._unsubscribeFromFontAndLayerSet(glyph.font)

    def _glyphChanged(self, notification):
        self.view.glyphChanged()

    def _glyphNameChanged(self, notification):
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    def _glyphSelectionChanged(self, notification):
        self._updateSelection()
        self.view.glyphChanged()

    def _fontInfoChanged(self, notification):
        self.view.fontInfoChanged()
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    # layers & font

    def _subscribeToFontAndLayerSet(self, font):
        """Note: called by _subscribeToGlyph."""
        if font is None:
            return
        font.info.addObserver(self, "_fontInfoChanged", "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        layerSet.addObserver(self, '_layerSetLayerDeleted',
                             'LayerSet.LayerDeleted')
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged'):
            layerSet.addObserver(self, '_layerSetEvents', event)

    def _unsubscribeFromFontAndLayerSet(self, font):
        """Note: called by _unsubscribeFromGlyph."""
        if font is None:
            return
        font.info.removeObserver(self, "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'):
            layerSet.removeObserver(self, event)

    def _layerSetEvents(self, notification):
        self._updateLayerControls()

    def _layerSetLayerDeleted(self, notification):
        self._layerSetEvents(notification)
        self._currentLayerBox.setCurrentIndex(0)

    # other updaters

    def _updateUndoRedo(self):
        glyph = self.view.glyph()
        self._undoAction.setEnabled(glyph.canUndo())
        self._redoAction.setEnabled(glyph.canRedo())

    def _updateSelection(self):
        def hasSelection():
            glyph = self.view.glyph()
            for contour in glyph:
                if len(contour.selection):
                    return True
            for anchor in glyph.anchors:
                if anchor.selected:
                    return True
            for component in glyph.components:
                if component.selected:
                    return True
            return False

        self._copyAction.setEnabled(hasSelection())

    # --------------
    # Public Methods
    # --------------

    def setGlyph(self, glyph):
        currentGlyph = self.view.glyph()
        self._unsubscribeFromGlyph(currentGlyph)
        self._subscribeToGlyph(glyph)
        self.view.setGlyph(glyph)
        self._updateLayerControls()
        self._updateUndoRedo()
        self._updateSelection()
        self.setWindowTitle(glyph.name, glyph.font)

    def setDrawingAttribute(self, attr, value, layerName=None):
        self.view.setDrawingAttribute(attr, value, layerName)

    def drawingAttribute(self, attr, layerName=None):
        return self.view.drawingAttribute(attr, layerName)

    # -----------------
    # Layers management
    # -----------------

    def _layerChanged(self, newLayerIndex):
        glyph = self.view.glyph()
        layer = self._currentLayerBox.itemData(newLayerIndex)
        if layer is None:
            layer = self._makeLayer()
            if layer is None:
                # restore comboBox to active index
                layerSet = glyph.layerSet
                index = layerSet.layerOrder.index(glyph.layer.name)
                self._setLayerBoxIndex(index)
                return

        if glyph.name in layer:
            newGlyph = layer[glyph.name]
        else:
            # TODO: make sure we mimic defcon ufo3 APIs for that
            newGlyph = self._makeLayerGlyph(layer, glyph)
        self.setGlyph(newGlyph)

        # setting the layer-glyph here
        app = QApplication.instance()
        app.setCurrentGlyph(newGlyph)

    def _makeLayer(self):
        # TODO: what with duplicate names?
        glyph = self.view.glyph()
        newLayerName, ok = AddLayerDialog.getNewLayerName(self)
        if ok:
            layerSet = glyph.layerSet
            # TODO: this should return the layer
            layerSet.newLayer(newLayerName)
            return layerSet[newLayerName]
        else:
            return None

    def _makeLayerGlyph(self, layer, currentGlyph):
        glyph = layer.newGlyph(currentGlyph.name)
        glyph.width = currentGlyph.width
        glyph.template = True
        return glyph

    def _updateLayerControls(self):
        comboBox = self._currentLayerBox
        glyph = self.view.glyph()
        comboBox.blockSignals(True)
        comboBox.clear()
        for layer in glyph.layerSet:
            comboBox.addItem(layer.name, layer)
        comboBox.setCurrentText(glyph.layer.name)
        comboBox.addItem("New layer…", None)
        comboBox.blockSignals(False)
        self._layerAction.setEnabled(len(glyph.layerSet) > 1)

    def _setLayerBoxIndex(self, index):
        comboBox = self._currentLayerBox
        comboBox.blockSignals(True)
        comboBox.setCurrentIndex(index)
        comboBox.blockSignals(False)

    # ---------------------
    # QMainWindow functions
    # ---------------------

    def event(self, event):
        if event.type() == QEvent.WindowActivate:
            app = QApplication.instance()
            app.setCurrentGlyph(self.view.glyph())
        return super().event(event)

    def closeEvent(self, event):
        glyph = self.view.glyph()
        self._unsubscribeFromGlyph(glyph)
        event.accept()

    def setWindowTitle(self, title, font=None):
        if font is not None:
            title = "%s – %s %s" % (title, font.info.familyName,
                                    font.info.styleName)
        super().setWindowTitle(title)
Пример #2
0
class MainGlyphWindow(QMainWindow):

    def __init__(self, glyph, parent=None):
        super().__init__(parent)

        menuBar = self.menuBar()
        fileMenu = QMenu("&File", self)
        fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
        menuBar.addMenu(fileMenu)
        editMenu = QMenu("&Edit", self)
        self._undoAction = editMenu.addAction(
            "&Undo", self.undo, QKeySequence.Undo)
        self._redoAction = editMenu.addAction(
            "&Redo", self.redo, QKeySequence.Redo)
        editMenu.addSeparator()
        # XXX
        action = editMenu.addAction("C&ut", self.cutOutlines, QKeySequence.Cut)
        action.setEnabled(False)
        self._copyAction = editMenu.addAction(
            "&Copy", self.copyOutlines, QKeySequence.Copy)
        editMenu.addAction("&Paste", self.pasteOutlines, QKeySequence.Paste)
        editMenu.addAction(
            "Select &All", self.selectAll, QKeySequence.SelectAll)
        editMenu.addAction("&Deselect", self.deselect, "Ctrl+D")
        menuBar.addMenu(editMenu)
        glyphMenu = QMenu("&Glyph", self)
        glyphMenu.addAction("&Next Glyph", lambda: self.glyphOffset(1), "End")
        glyphMenu.addAction(
            "&Previous Glyph", lambda: self.glyphOffset(-1), "Home")
        glyphMenu.addAction("&Go To…", self.changeGlyph, "G")
        glyphMenu.addSeparator()
        self._layerAction = glyphMenu.addAction(
            "&Layer Actions…", self.layerActions, "L")
        menuBar.addMenu(glyphMenu)

        # create tools and buttons toolBars
        self._tools = []
        self._toolsToolBar = QToolBar("Tools", self)
        self._toolsToolBar.setMovable(False)
        self._buttons = []
        self._buttonsToolBar = QToolBar("Buttons", self)
        self._buttonsToolBar.setMovable(False)
        self.addToolBar(self._toolsToolBar)
        self.addToolBar(self._buttonsToolBar)

        # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/
        self._layersToolBar = QToolBar("Layers", self)
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self._currentLayerBox = QComboBox(self)
        self._currentLayerBox.currentIndexChanged.connect(
            self._layerChanged)
        self._layersToolBar.addWidget(spacer)
        self._layersToolBar.addWidget(self._currentLayerBox)
        self._layersToolBar.setContentsMargins(0, 0, 2, 0)
        self._layersToolBar.setMovable(False)
        self.addToolBar(self._layersToolBar)

        viewMenu = self.createPopupMenu()
        viewMenu.setTitle("View")
        viewMenu.addSeparator()
        action = viewMenu.addAction("Lock Toolbars", self.lockToolBars)
        action.setCheckable(True)
        action.setChecked(True)
        menuBar.addMenu(viewMenu)

        self.view = GlyphView(self)
        self.setGlyph(glyph)
        selectionTool = self.installTool(SelectionTool)
        selectionTool.trigger()
        self.installTool(PenTool)
        self.installTool(RulerTool)
        self.installTool(KnifeTool)
        self.installButton(RemoveOverlapButton)

        self.setCentralWidget(self.view.scrollArea())
        self.resize(900, 700)
        self.view.setFocus(True)

    # ----------
    # Menu items
    # ----------

    def glyphOffset(self, offset):
        currentGlyph = self.view.glyph()
        font = currentGlyph.font
        glyphOrder = font.glyphOrder
        # should be enforced in fontView already
        if not (glyphOrder and len(glyphOrder)):
            return
        index = glyphOrder.index(currentGlyph.name)
        newIndex = (index + offset) % len(glyphOrder)
        glyph = font[glyphOrder[newIndex]]
        self.setGlyph(glyph)

    def changeGlyph(self):
        glyph = self.view.glyph()
        newGlyph, ok = GotoDialog.getNewGlyph(self, glyph)
        if ok and newGlyph is not None:
            self.setGlyph(newGlyph)

    def layerActions(self):
        glyph = self.view.glyph()
        newLayer, action, ok = LayerActionsDialog.getLayerAndAction(
            self, glyph)
        if ok and newLayer is not None:
            # TODO: whole glyph for now, but consider selection too
            if not glyph.name in newLayer:
                newLayer.newGlyph(glyph.name)
            otherGlyph = newLayer[glyph.name]
            otherGlyph.disableNotifications()
            if action == "Swap":
                tempGlyph = TGlyph()
                otherGlyph.drawPoints(tempGlyph.getPointPen())
                tempGlyph.width = otherGlyph.width
                otherGlyph.clearContours()
            glyph.drawPoints(otherGlyph.getPointPen())
            otherGlyph.width = glyph.width
            if action != "Copy":
                glyph.disableNotifications()
                glyph.clearContours()
                if action == "Swap":
                    tempGlyph.drawPoints(glyph.getPointPen())
                    glyph.width = tempGlyph.width
                glyph.enableNotifications()
            otherGlyph.enableNotifications()

    def undo(self):
        glyph = self.view.glyph()
        glyph.undo()

    def redo(self):
        glyph = self.view.glyph()
        glyph.redo()

    def cutOutlines(self):
        pass

    def copyOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = QMimeData()
        copyGlyph = glyph.getRepresentation("defconQt.FilterSelection")
        mimeData.setData("application/x-defconQt-glyph-data",
                         pickle.dumps([copyGlyph.serialize(
                             blacklist=("name", "unicode")
                         )]))
        clipboard.setMimeData(mimeData)

    def pasteOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = clipboard.mimeData()
        if mimeData.hasFormat("application/x-defconQt-glyph-data"):
            data = pickle.loads(mimeData.data(
                "application/x-defconQt-glyph-data"))
            if len(data) == 1:
                pen = glyph.getPointPen()
                pasteGlyph = TGlyph()
                pasteGlyph.deserialize(data[0])
                # TODO: if we serialize selected state, we don't need to do
                # this
                pasteGlyph.selected = True
                if len(pasteGlyph) or len(pasteGlyph.components) or \
                        len(pasteGlyph.anchors):
                    glyph.prepareUndo()
                    pasteGlyph.drawPoints(pen)

    def selectAll(self):
        glyph = self.view.glyph()
        glyph.selected = True
        if not len(glyph):
            for component in glyph.components:
                component.selected = True

    def deselect(self):
        glyph = self.view.glyph()
        for anchor in glyph.anchors:
            anchor.selected = False
        for component in glyph.components:
            component.selected = False
        glyph.selected = False

    def lockToolBars(self):
        action = self.sender()
        movable = not action.isChecked()
        for toolBar in (
                self._toolsToolBar, self._buttonsToolBar, self._layersToolBar):
            toolBar.setMovable(movable)

    # --------------------------
    # Tools & buttons management
    # --------------------------

    def installTool(self, tool):
        action = self._toolsToolBar.addAction(
            QIcon(tool.iconPath), tool.name, self._setViewTool)
        action.setCheckable(True)
        num = len(self._tools)
        action.setData(num)
        action.setShortcut(QKeySequence(str(num + 1)))
        self._tools.append(tool(parent=self.view))
        return action

    def uninstallTool(self, tool):
        pass  # XXX

    def _setViewTool(self):
        action = self.sender()
        index = action.data()
        newTool = self._tools[index]
        if newTool == self.view.currentTool():
            action.setChecked(True)
            return
        ok = self.view.setCurrentTool(newTool)
        # if view did change tool, disable them all and enable the one we want
        # otherwise, just disable the tool that was clicked.
        # previously we used QActionGroup to have exclusive buttons, but doing
        # it manually allows us to NAK a button change.
        if ok:
            for act in self._toolsToolBar.actions():
                act.setChecked(False)
        action.setChecked(ok)

    def installButton(self, button):
        action = self._buttonsToolBar.addAction(
            QIcon(button.iconPath), button.name, self._buttonAction)
        action.setData(len(self._buttons))
        self._buttons.append(button(parent=self.view))
        return action

    def uninstallButton(self, button):
        pass  # XXX

    def _buttonAction(self):
        action = self.sender()
        index = action.data()
        button = self._buttons[index]
        button.clicked()

    # --------------------
    # Notification support
    # --------------------

    # glyph

    def _subscribeToGlyph(self, glyph):
        if glyph is not None:
            glyph.addObserver(self, "_glyphChanged", "Glyph.Changed")
            glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged")
            glyph.addObserver(
                self, "_glyphSelectionChanged", "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.connect(self._undoAction.setEnabled)
            undoManager.canRedoChanged.connect(self._redoAction.setEnabled)
            self._subscribeToFontAndLayerSet(glyph.font)

    def _unsubscribeFromGlyph(self, glyph):
        if glyph is not None:
            glyph.removeObserver(self, "Glyph.Changed")
            glyph.removeObserver(self, "Glyph.NameChanged")
            glyph.removeObserver(self, "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.disconnect(self._undoAction.setEnabled)
            undoManager.canRedoChanged.disconnect(self._redoAction.setEnabled)
            self._unsubscribeFromFontAndLayerSet(glyph.font)

    def _glyphChanged(self, notification):
        self.view.glyphChanged()

    def _glyphNameChanged(self, notification):
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    def _glyphSelectionChanged(self, notification):
        self._updateSelection()
        self.view.glyphChanged()

    def _fontInfoChanged(self, notification):
        self.view.fontInfoChanged()
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    # layers & font

    def _subscribeToFontAndLayerSet(self, font):
        """Note: called by _subscribeToGlyph."""
        if font is None:
            return
        font.info.addObserver(self, "_fontInfoChanged", "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        layerSet.addObserver(self, '_layerSetLayerDeleted',
                             'LayerSet.LayerDeleted')
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged'):
            layerSet.addObserver(self, '_layerSetEvents', event)

    def _unsubscribeFromFontAndLayerSet(self, font):
        """Note: called by _unsubscribeFromGlyph."""
        if font is None:
            return
        font.info.removeObserver(self, "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'):
            layerSet.removeObserver(self, event)

    def _layerSetEvents(self, notification):
        self._updateLayerControls()

    def _layerSetLayerDeleted(self, notification):
        self._layerSetEvents(notification)
        self._currentLayerBox.setCurrentIndex(0)

    # other updaters

    def _updateUndoRedo(self):
        glyph = self.view.glyph()
        self._undoAction.setEnabled(glyph.canUndo())
        self._redoAction.setEnabled(glyph.canRedo())

    def _updateSelection(self):
        def hasSelection():
            glyph = self.view.glyph()
            for contour in glyph:
                if len(contour.selection):
                    return True
            for anchor in glyph.anchors:
                if anchor.selected:
                    return True
            for component in glyph.components:
                if component.selected:
                    return True
            return False
        self._copyAction.setEnabled(hasSelection())

    # --------------
    # Public Methods
    # --------------

    def setGlyph(self, glyph):
        currentGlyph = self.view.glyph()
        self._unsubscribeFromGlyph(currentGlyph)
        self._subscribeToGlyph(glyph)
        self.view.setGlyph(glyph)
        self._updateLayerControls()
        self._updateUndoRedo()
        self._updateSelection()
        self.setWindowTitle(glyph.name, glyph.font)

    def setDrawingAttribute(self, attr, value, layerName=None):
        self.view.setDrawingAttribute(attr, value, layerName)

    def drawingAttribute(self, attr, layerName=None):
        return self.view.drawingAttribute(attr, layerName)

    # -----------------
    # Layers management
    # -----------------

    def _layerChanged(self, newLayerIndex):
        glyph = self.view.glyph()
        layer = self._currentLayerBox.itemData(newLayerIndex)
        if layer is None:
            layer = self._makeLayer()
            if layer is None:
                # restore comboBox to active index
                layerSet = glyph.layerSet
                index = layerSet.layerOrder.index(glyph.layer.name)
                self._setLayerBoxIndex(index)
                return

        if glyph.name in layer:
            newGlyph = layer[glyph.name]
        else:
            # TODO: make sure we mimic defcon ufo3 APIs for that
            newGlyph = self._makeLayerGlyph(layer, glyph)
        self.setGlyph(newGlyph)

        # setting the layer-glyph here
        app = QApplication.instance()
        app.setCurrentGlyph(newGlyph)

    def _makeLayer(self):
        # TODO: what with duplicate names?
        glyph = self.view.glyph()
        newLayerName, ok = AddLayerDialog.getNewLayerName(self)
        if ok:
            layerSet = glyph.layerSet
            # TODO: this should return the layer
            layerSet.newLayer(newLayerName)
            return layerSet[newLayerName]
        else:
            return None

    def _makeLayerGlyph(self, layer, currentGlyph):
        glyph = layer.newGlyph(currentGlyph.name)
        glyph.width = currentGlyph.width
        glyph.template = True
        return glyph

    def _updateLayerControls(self):
        comboBox = self._currentLayerBox
        glyph = self.view.glyph()
        comboBox.blockSignals(True)
        comboBox.clear()
        for layer in glyph.layerSet:
            comboBox.addItem(layer.name, layer)
        comboBox.setCurrentText(glyph.layer.name)
        comboBox.addItem("New layer…", None)
        comboBox.blockSignals(False)
        self._layerAction.setEnabled(len(glyph.layerSet) > 1)

    def _setLayerBoxIndex(self, index):
        comboBox = self._currentLayerBox
        comboBox.blockSignals(True)
        comboBox.setCurrentIndex(index)
        comboBox.blockSignals(False)

    # ---------------------
    # QMainWindow functions
    # ---------------------

    def event(self, event):
        if event.type() == QEvent.WindowActivate:
            app = QApplication.instance()
            app.setCurrentGlyph(self.view.glyph())
        return super().event(event)

    def closeEvent(self, event):
        glyph = self.view.glyph()
        self._unsubscribeFromGlyph(glyph)
        event.accept()

    def setWindowTitle(self, title, font=None):
        if font is not None:
            title = "%s – %s %s" % (
                title, font.info.familyName, font.info.styleName)
        super().setWindowTitle(title)
Пример #3
0
class TransCodaEditor(QWidget):
    encoder_changed = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject')

    def __init__(self, caption="Trans:Coda Editor"):
        super().__init__()
        self.encoder = EncoderSelector()
        self.encoder_view = EncoderView()
        self.actions = QToolBar()
        self.caption = caption
        self.add_action = CommonUtils.create_action(
            tooltip="Add Encoder",
            icon=TransCoda.theme.ico_add_item,
            func=self.action_add,
            name="Add",
            parent=self)
        self.del_action = CommonUtils.create_action(
            tooltip="Delete Encoder",
            icon=TransCoda.theme.ico_clear,
            func=self.action_del,
            name="Delete",
            parent=self)
        self.mod_action = CommonUtils.create_action(
            tooltip="Edit Encoder",
            icon=TransCoda.theme.ico_edit,
            func=self.action_mod,
            name="Edit",
            parent=self)
        self.ref_action = CommonUtils.create_action(
            tooltip="Refresh Encoder",
            icon=TransCoda.theme.ico_refresh,
            func=self.action_ref,
            name="Refresh",
            parent=self)
        self._init_ui()

    def _init_ui(self):
        self.encoder.encoder_changed.connect(self._configure_toolbar)
        self.actions.setOrientation(Qt.Horizontal)
        self.actions.addActions([
            self.add_action, self.del_action, self.mod_action, self.ref_action
        ])
        self.actions.setContentsMargins(0, 0, 0, 0)
        q_size = QSize(16, 16)
        self.actions.setIconSize(q_size)
        self._configure_toolbar(None, None)

        h_layout = QHBoxLayout()
        h_layout.setContentsMargins(0, 0, 0, 0)
        h_layout.addWidget(QLabel(f"<u>{self.caption}</u>"), 1)
        h_layout.addWidget(self.actions)
        v_layout = QVBoxLayout()
        v_layout.setContentsMargins(0, 0, 0, 0)
        v_layout.addLayout(h_layout)
        v_layout.addWidget(self.encoder)
        self.setMinimumWidth(400)

        self.setLayout(v_layout)

    def action_add(self, _):
        self.encoder_view.update_view("Add Encoder")
        if self.encoder_view.exec() == QDialog.Accepted:
            media_type, encoder_group, name, command, executable, extension = self.encoder_view.get_details(
            )
            self.encoder.add_encoder(media_type, encoder_group, name, command,
                                     executable, extension)

    def action_del(self, _):
        path, _ = self.encoder.get_encoder()
        tokens = path.split(EncoderSelector.__PATH_SEPARATOR__)
        self.encoder.del_encoder(tokens[0], tokens[1], tokens[2])

    def action_mod(self, _):
        path, encoder = self.encoder.get_encoder()
        self.encoder_view.update_view("Modify Encoder",
                                      encoder_path=path,
                                      encoder=encoder)
        if self.encoder_view.exec() == QDialog.Accepted:
            media_type, encoder_group, name, command, executable, extension = self.encoder_view.get_details(
            )
            self.encoder.update_encoder(media_type, encoder_group, name,
                                        command, executable, extension)

    def action_ref(self, _):
        self.encoder.refresh_encoders()

    def select_encoder(self, encoder_name):
        return self.encoder.select_encoder(encoder_name)

    def _configure_toolbar(self, path, encoder):
        self.mod_action.setEnabled(True if encoder else False)
        self.del_action.setEnabled(True if encoder else False)
        if encoder:
            self.encoder_changed.emit(path, encoder)
Пример #4
0
class JavaTracePanel(QWidget):
    def __init__(self, app, *__args):
        super().__init__(app)
        self.app = app

        self.app.dwarf.onJavaTraceEvent.connect(self.on_event)
        self.app.dwarf.onEnumerateJavaClassesStart.connect(
            self.on_enumeration_start)
        self.app.dwarf.onEnumerateJavaClassesMatch.connect(
            self.on_enumeration_match)
        self.app.dwarf.onEnumerateJavaClassesComplete.connect(
            self.on_enumeration_complete)

        self.tracing = False
        self.trace_classes = []
        self.trace_depth = 0

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        self._record_icon = QIcon(
            utils.resource_path('assets/icons/record.png'))
        self._pause_icon = QIcon(utils.resource_path('assets/icons/pause.png'))
        self._stop_icon = QIcon(utils.resource_path('assets/icons/stop.png'))

        self._tool_bar = QToolBar()
        self._tool_bar.setFixedHeight(16)
        self._tool_bar.setContentsMargins(1, 1, 1, 1)
        self._tool_bar.addAction(self._record_icon, 'Record', self.start_trace)
        self._tool_bar.addAction(self._pause_icon, 'Pause', self.pause_trace)
        self._tool_bar.addAction(self._stop_icon, 'Stop', self.stop_trace)
        self._tool_bar.addSeparator()
        self._entries_lbl = QLabel('Entries: 0')
        self._entries_lbl.setAlignment(Qt.AlignRight)
        self._entries_lbl.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Preferred)
        self._tool_bar.addWidget(self._entries_lbl)

        layout.addWidget(self._tool_bar)

        self.setup_splitter = QSplitter()
        self.events_list = JavaTraceView(self)
        self.events_list.setVisible(False)

        self.trace_list = QListWidget()
        self.class_list = QListWidget()

        self.trace_list.itemDoubleClicked.connect(self.trace_list_double_click)

        self.class_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.class_list.customContextMenuRequested.connect(
            self.show_class_list_menu)
        self.class_list.itemDoubleClicked.connect(self.class_list_double_click)

        self.current_class_search = ''

        bar = QScrollBar()
        bar.setFixedWidth(0)
        bar.setFixedHeight(0)
        self.trace_list.setHorizontalScrollBar(bar)
        bar = QScrollBar()
        bar.setFixedWidth(0)
        bar.setFixedHeight(0)
        self.class_list.setHorizontalScrollBar(bar)

        self.setup_splitter.addWidget(self.trace_list)
        self.setup_splitter.addWidget(self.class_list)
        self.setup_splitter.setHandleWidth(1)

        layout.addWidget(self.setup_splitter)
        layout.addWidget(self.events_list)

        self.setLayout(layout)

    def class_list_double_click(self, item):
        try:
            if self.trace_classes.index(item.text()) >= 0:
                return
        except:
            pass
        self.trace_classes.append(item.text())
        q = NotEditableListWidgetItem(item.text())
        self.trace_list.addItem(q)
        self.trace_list.sortItems()

    def on_enumeration_start(self):
        self.class_list.clear()

    def on_enumeration_match(self, java_class):
        try:
            if PREFIXED_CLASS.index(java_class) >= 0:
                try:
                    if self.trace_classes.index(java_class) >= 0:
                        return
                except:
                    pass
                q = NotEditableListWidgetItem(java_class)
                self.trace_list.addItem(q)
                self.trace_classes.append(java_class)
        except:
            pass

        q = NotEditableListWidgetItem(java_class)
        self.class_list.addItem(q)

    def on_enumeration_complete(self):
        self.class_list.sortItems()
        self.trace_list.sortItems()

    def on_event(self, data):
        trace, event, clazz, data = data
        if trace == 'java_trace':
            self.events_list.add_event({
                'event': event,
                'class': clazz,
                'data': data.replace(',', ', ')
            })
            self._entries_lbl.setText('Events: %d' %
                                      len(self.events_list.data))

    def pause_trace(self):
        self.app.dwarf.dwarf_api('stopJavaTracer')
        self.tracing = False

    def search(self):
        accept, input = InputDialog.input(
            self.app,
            hint='Search',
            input_content=self.current_class_search,
            placeholder='Search something...')
        if accept:
            self.current_class_search = input.lower()
            for i in range(0, self.class_list.count()):
                try:
                    if self.class_list.item(i).text().lower().index(
                            self.current_class_search.lower()) >= 0:
                        self.class_list.setRowHidden(i, False)
                except:
                    self.class_list.setRowHidden(i, True)

    def show_class_list_menu(self, pos):
        menu = QMenu()
        search = menu.addAction('Search')
        action = menu.exec_(self.class_list.mapToGlobal(pos))
        if action:
            if action == search:
                self.search()

    def start_trace(self):
        self.app.dwarf.dwarf_api('startJavaTracer', [self.trace_classes])
        self.trace_depth = 0
        self.tracing = True
        self.setup_splitter.setVisible(False)
        self.events_list.setVisible(True)

    def stop_trace(self):
        self.app.dwarf.dwarf_api('stopJavaTracer')
        self.tracing = False
        self.setup_splitter.setVisible(True)
        self.events_list.setVisible(False)
        self.events_list.clear()

    def trace_list_double_click(self, item):
        try:
            index = self.trace_classes.index(item.text())
        except:
            return
        if index < 0:
            return
        self.trace_classes.pop(index)
        self.trace_list.takeItem(self.trace_list.row(item))

    def keyPressEvent(self, event):
        if event.modifiers() & Qt.ControlModifier:
            if event.key() == Qt.Key_F:
                self.search()
        super(JavaTracePanel, self).keyPressEvent(event)
Пример #5
0
class GlyphWindow(BaseMainWindow):

    def __init__(self, glyph, parent=None):
        super().__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose, False)
        self.setUnifiedTitleAndToolBarOnMac(True)

        self.view = GlyphCanvasView(self)
        # create tools and buttons toolBars
        self._tools = []
        self._toolsToolBar = QToolBar(self.tr("Tools"), self)
        self._toolsToolBar.setMovable(False)
        self._buttons = []
        self._buttonsToolBar = QToolBar(self.tr("Buttons"), self)
        self._buttonsToolBar.setMovable(False)
        self.addToolBar(self._toolsToolBar)
        self.addToolBar(self._buttonsToolBar)

        # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/
        self._layersToolBar = QToolBar(self.tr("Layers"), self)
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self._currentLayerBox = QComboBox(self)
        self._currentLayerBox.currentIndexChanged.connect(
            self._layerChanged)
        self._layersToolBar.addWidget(spacer)
        self._layersToolBar.addWidget(self._currentLayerBox)
        self._layersToolBar.setContentsMargins(0, 0, 2, 0)
        self._layersToolBar.setMovable(False)
        self.addToolBar(self._layersToolBar)

        self.setGlyph(glyph)
        app = QApplication.instance()
        tools = app.drawingTools()
        for index, tool in enumerate(tools):
            action = self.installTool(tool)
            if not index:
                action.trigger()
        app.dispatcher.addObserver(
            self, "_drawingToolRegistered", "drawingToolRegistered")
        # TODO: drawingToolUnregistered
        self.installButton(RemoveOverlapButton)

        self.setCentralWidget(self.view)
        self.view.setFocus(True)

        self.readSettings()

    def readSettings(self):
        geometry = settings.glyphWindowGeometry()
        if geometry:
            self.restoreGeometry(geometry)

    def writeSettings(self):
        # TODO: save current tool?
        settings.setGlyphWindowGeometry(self.saveGeometry())

    def setupMenu(self, menuBar):
        fileMenu = menuBar.fetchMenu(Entries.File)
        fontWindow = self.parent()
        if fontWindow is not None:
            fileMenu.fetchAction(Entries.File_Save, fontWindow.saveFile)
            fileMenu.fetchAction(Entries.File_Save_As, fontWindow.saveFileAs)
        fileMenu.fetchAction(Entries.File_Close, self.close)
        if fontWindow is not None:
            fileMenu.fetchAction(Entries.File_Reload, fontWindow.reloadFile)

        editMenu = menuBar.fetchMenu(Entries.Edit)
        self._undoAction = editMenu.fetchAction(Entries.Edit_Undo, self.undo)
        self._redoAction = editMenu.fetchAction(Entries.Edit_Redo, self.redo)
        editMenu.addSeparator()
        cutAction = editMenu.fetchAction(Entries.Edit_Cut, self.cutOutlines)
        copyAction = editMenu.fetchAction(Entries.Edit_Copy, self.copyOutlines)
        self._selectActions = (cutAction, copyAction)
        editMenu.fetchAction(Entries.Edit_Paste, self.pasteOutlines)
        editMenu.fetchAction(Entries.Edit_Select_All, self.selectAll)
        editMenu.fetchAction(Entries.Edit_Find, self.changeGlyph)
        # TODO: sort this out
        # editMenu.fetchAction(self.tr("&Deselect"), self.deselect, "Ctrl+D")

        # glyphMenu = menuBar.fetchMenu(self.tr("&Glyph"))
        # self._layerAction = glyphMenu.fetchAction(
        #     self.tr("&Layer Actions…"), self.layerActions, "L")

        viewMenu = menuBar.fetchMenu(Entries.View)
        viewMenu.fetchAction(Entries.View_Zoom_In, lambda: self.view.zoom(1))
        viewMenu.fetchAction(Entries.View_Zoom_Out, lambda: self.view.zoom(-1))
        viewMenu.fetchAction(Entries.View_Reset_Zoom, self.view.fitScaleBBox)
        viewMenu.addSeparator()
        viewMenu.fetchAction(
            Entries.View_Next_Glyph, lambda: self.glyphOffset(1))
        viewMenu.fetchAction(
            Entries.View_Previous_Glyph, lambda: self.glyphOffset(-1))

        self._updateUndoRedo()

    # ----------
    # Menu items
    # ----------

    def saveFile(self):
        glyph = self.view.glyph()
        font = glyph.font
        if None not in (font, font.path):
            font.save()

    def glyphOffset(self, offset):
        currentGlyph = self.view.glyph()
        font = currentGlyph.font
        glyphOrder = font.glyphOrder
        # should be enforced in fontView already
        if not (glyphOrder and len(glyphOrder)):
            return
        index = glyphOrder.index(currentGlyph.name)
        newIndex = (index + offset) % len(glyphOrder)
        glyph = font[glyphOrder[newIndex]]
        self.setGlyph(glyph)

    def changeGlyph(self):
        glyph = self.view.glyph()
        newGlyph, ok = FindDialog.getNewGlyph(self, glyph)
        if ok and newGlyph is not None:
            self.setGlyph(newGlyph)

    def layerActions(self):
        glyph = self.view.glyph()
        newLayer, action, ok = LayerActionsDialog.getLayerAndAction(
            self, glyph)
        if ok and newLayer is not None:
            # TODO: whole glyph for now, but consider selection too
            if glyph.name not in newLayer:
                newLayer.newGlyph(glyph.name)
            otherGlyph = newLayer[glyph.name]
            otherGlyph.holdNotifications()
            if action == "Swap":
                tempGlyph = glyph.__class__()
                otherGlyph.drawPoints(tempGlyph.getPointPen())
                tempGlyph.width = otherGlyph.width
                otherGlyph.clearContours()
            glyph.drawPoints(otherGlyph.getPointPen())
            otherGlyph.width = glyph.width
            if action != "Copy":
                glyph.holdNotifications()
                glyph.clearContours()
                if action == "Swap":
                    tempGlyph.drawPoints(glyph.getPointPen())
                    glyph.width = tempGlyph.width
                glyph.releaseHeldNotifications()
            otherGlyph.releaseHeldNotifications()

    def undo(self):
        glyph = self.view.glyph()
        glyph.undo()

    def redo(self):
        glyph = self.view.glyph()
        glyph.redo()

    def cutOutlines(self):
        glyph = self.view.glyph()
        self.copyOutlines()
        deleteUISelection(glyph)

    def copyOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = QMimeData()
        copyGlyph = glyph.getRepresentation("TruFont.FilterSelection")
        mimeData.setData("application/x-trufont-glyph-data",
                         pickle.dumps([copyGlyph.serialize(
                             blacklist=("name", "unicode")
                         )]))
        clipboard.setMimeData(mimeData)

    def pasteOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = clipboard.mimeData()
        if mimeData.hasFormat("application/x-trufont-glyph-data"):
            data = pickle.loads(mimeData.data(
                "application/x-trufont-glyph-data"))
            if len(data) == 1:
                pen = glyph.getPointPen()
                pasteGlyph = glyph.__class__()
                pasteGlyph.deserialize(data[0])
                # TODO: if we serialize selected state, we don't need to do
                # this
                pasteGlyph.selected = True
                if len(pasteGlyph) or len(pasteGlyph.components) or \
                        len(pasteGlyph.anchors):
                    glyph.prepareUndo()
                    pasteGlyph.drawPoints(pen)

    def selectAll(self):
        glyph = self.view.glyph()
        if glyph.selected:
            for anchor in glyph.anchors:
                anchor.selected = True
            for component in glyph.components:
                component.selected = True
        else:
            glyph.selected = True

    def deselect(self):
        glyph = self.view.glyph()
        for anchor in glyph.anchors:
            anchor.selected = False
        for component in glyph.components:
            component.selected = False
        glyph.selected = False

    def lockToolBars(self):
        action = self.sender()
        movable = not action.isChecked()
        for toolBar in (
                self._toolsToolBar, self._buttonsToolBar, self._layersToolBar):
            toolBar.setMovable(movable)

    # --------------------------
    # Tools & buttons management
    # --------------------------

    def installTool(self, tool):
        action = self._toolsToolBar.addAction(
            QIcon(tool.iconPath), tool.name, self._setViewTool)
        action.setCheckable(True)
        num = len(self._tools)
        action.setData(num)
        action.setShortcut(QKeySequence(str(num + 1)))
        self._tools.append(tool(parent=self.view.widget()))
        return action

    def uninstallTool(self, tool):
        pass  # XXX

    def _setViewTool(self):
        action = self.sender()
        index = action.data()
        newTool = self._tools[index]
        if newTool == self.view.currentTool():
            action.setChecked(True)
            return
        ok = self.view.setCurrentTool(newTool)
        # if view did change tool, disable them all and enable the one we want
        # otherwise, just disable the tool that was clicked.
        # previously we used QActionGroup to have exclusive buttons, but doing
        # it manually allows us to NAK a button change.
        if ok:
            for act in self._toolsToolBar.actions():
                act.setChecked(False)
        action.setChecked(ok)

    def installButton(self, button):
        action = self._buttonsToolBar.addAction(
            QIcon(button.iconPath), button.name, self._buttonAction)
        action.setData(len(self._buttons))
        self._buttons.append(button(parent=self.view))
        return action

    def uninstallButton(self, button):
        pass  # XXX

    def _buttonAction(self):
        action = self.sender()
        index = action.data()
        button = self._buttons[index]
        button.clicked()

    # -------------
    # Notifications
    # -------------

    # app

    def _drawingToolRegistered(self, notification):
        tool = notification.data["tool"]
        self.installTool(tool)

    # glyph

    def _subscribeToGlyph(self, glyph):
        if glyph is not None:
            glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged")
            glyph.addObserver(
                self, "_glyphSelectionChanged", "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.connect(self._setUndoEnabled)
            undoManager.canRedoChanged.connect(self._setRedoEnabled)
            self._subscribeToFontAndLayerSet(glyph.font)

    def _unsubscribeFromGlyph(self, glyph):
        if glyph is not None:
            glyph.removeObserver(self, "Glyph.NameChanged")
            glyph.removeObserver(self, "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.disconnect(self._setUndoEnabled)
            undoManager.canRedoChanged.disconnect(self._setRedoEnabled)
            self._unsubscribeFromFontAndLayerSet(glyph.font)

    def _glyphNameChanged(self, notification):
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    def _glyphSelectionChanged(self, notification):
        self._updateSelection()

    # layers & font

    def _subscribeToFontAndLayerSet(self, font):
        """Note: called by _subscribeToGlyph."""
        if font is None:
            return
        font.info.addObserver(self, "_fontInfoChanged", "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        layerSet.addObserver(
            self, '_layerSetLayerDeleted', 'LayerSet.LayerDeleted')
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged'):
            layerSet.addObserver(self, '_layerSetEvents', event)

    def _unsubscribeFromFontAndLayerSet(self, font):
        """Note: called by _unsubscribeFromGlyph."""
        if font is None:
            return
        font.info.removeObserver(self, "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'):
            layerSet.removeObserver(self, event)

    def _fontInfoChanged(self, notification):
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    def _layerSetEvents(self, notification):
        self._updateLayerControls()

    def _layerSetLayerDeleted(self, notification):
        self._layerSetEvents(notification)
        self._currentLayerBox.setCurrentIndex(0)

    # other updaters

    def _updateSelection(self):
        def hasSelection():
            glyph = self.view.glyph()
            for contour in glyph:
                if len(contour.selection):
                    return True
            for anchor in glyph.anchors:
                if anchor.selected:
                    return True
            for component in glyph.components:
                if component.selected:
                    return True
            return False

        if not hasattr(self, "_selectActions"):
            return
        hasSelection = hasSelection()
        for action in self._selectActions:
            action.setEnabled(hasSelection)

    def _updateUndoRedo(self):
        glyph = self.view.glyph()
        self._setUndoEnabled(glyph.canUndo())
        self._setRedoEnabled(glyph.canRedo())

    def _setUndoEnabled(self, value):
        if not hasattr(self, "_undoAction"):
            return
        self._undoAction.setEnabled(value)

    def _setRedoEnabled(self, value):
        if not hasattr(self, "_redoAction"):
            return
        self._redoAction.setEnabled(value)

    # --------------
    # Public Methods
    # --------------

    def setGlyph(self, glyph):
        currentGlyph = self.view.glyph()
        self._unsubscribeFromGlyph(currentGlyph)
        self.view.setGlyph(glyph)
        self._subscribeToGlyph(glyph)
        self._updateLayerControls()
        self._updateUndoRedo()
        self._updateSelection()
        self.setWindowTitle(glyph.name, glyph.font)
        # setting the layer-glyph here
        app = QApplication.instance()
        app.setCurrentGlyph(glyph)

    # -----------------
    # Layers management
    # -----------------

    def _layerChanged(self, newLayerIndex):
        glyph = self.view.glyph()
        layer = self._currentLayerBox.itemData(newLayerIndex)
        if layer is None:
            layer = self._makeLayer()
            if layer is None:
                # restore comboBox to active index
                layerSet = glyph.layerSet
                index = layerSet.layerOrder.index(glyph.layer.name)
                self._setLayerBoxIndex(index)
                return

        if glyph.name in layer:
            newGlyph = layer[glyph.name]
        else:
            # TODO: make sure we mimic defcon ufo3 APIs for that
            newGlyph = self._makeLayerGlyph(layer, glyph)
        self.setGlyph(newGlyph)

    def _makeLayer(self):
        # TODO: what with duplicate names?
        glyph = self.view.glyph()
        newLayerName, color, ok = AddLayerDialog.getNewLayerNameAndColor(self)
        if ok:
            layerSet = glyph.layerSet
            layer = layerSet.newLayer(newLayerName)
            layer.color = color
            return layer
        return None

    def _makeLayerGlyph(self, layer, currentGlyph):
        glyph = layer.newGlyph(currentGlyph.name)
        glyph.width = currentGlyph.width
        glyph.template = True
        return glyph

    def _updateLayerControls(self):
        comboBox = self._currentLayerBox
        glyph = self.view.glyph()
        comboBox.blockSignals(True)
        comboBox.clear()
        for layer in glyph.layerSet:
            comboBox.addItem(layer.name, layer)
        comboBox.setCurrentText(glyph.layer.name)
        comboBox.addItem(self.tr("New layer…"), None)
        comboBox.blockSignals(False)
        if not hasattr(self, "_layerAction"):
            return
        self._layerAction.setEnabled(len(glyph.layerSet) > 1)

    def _setLayerBoxIndex(self, index):
        comboBox = self._currentLayerBox
        comboBox.blockSignals(True)
        comboBox.setCurrentIndex(index)
        comboBox.blockSignals(False)

    # ---------------------
    # QMainWindow functions
    # ---------------------

    def sizeHint(self):
        return QSize(1100, 750)

    def moveEvent(self, event):
        self.writeSettings()

    resizeEvent = moveEvent

    def event(self, event):
        if event.type() == QEvent.WindowActivate:
            app = QApplication.instance()
            app.setCurrentGlyph(self.view.glyph())
        return super().event(event)

    def showEvent(self, event):
        app = QApplication.instance()
        data = dict(window=self)
        app.postNotification("glyphWindowWillOpen", data)
        super().showEvent(event)
        app.postNotification("glyphWindowOpened", data)

    def closeEvent(self, event):
        super().closeEvent(event)
        if event.isAccepted():
            app = QApplication.instance()
            app.dispatcher.removeObserver(self, "drawingToolRegistered")
            data = dict(window=self)
            app.postNotification("glyphWindowWillClose", data)
            glyph = self.view.glyph()
            self._unsubscribeFromGlyph(glyph)
            self.view.closeEvent(event)

    def setWindowTitle(self, title, font=None):
        if font is not None:
            title = "%s – %s %s" % (
                title, font.info.familyName, font.info.styleName)
        super().setWindowTitle(title)
Пример #6
0
class ManageWindow(QWidget):
    __BASE_HEIGHT__ = 400

    signal_user_res = pyqtSignal(bool)
    signal_table_update = pyqtSignal()

    def __init__(self,
                 i18n: dict,
                 icon_cache: MemoryCache,
                 manager: SoftwareManager,
                 disk_cache: bool,
                 download_icons: bool,
                 screen_size,
                 suggestions: bool,
                 display_limit: int,
                 config: Configuration,
                 context: ApplicationContext,
                 notifications: bool,
                 tray_icon=None):
        super(ManageWindow, self).__init__()
        self.i18n = i18n
        self.manager = manager
        self.tray_icon = tray_icon
        self.working = False  # restrict the number of threaded actions
        self.pkgs = []  # packages current loaded in the table
        self.pkgs_available = []  # all packages loaded in memory
        self.pkgs_installed = []  # cached installed packages
        self.display_limit = display_limit
        self.icon_cache = icon_cache
        self.disk_cache = disk_cache
        self.download_icons = download_icons
        self.screen_size = screen_size
        self.config = config
        self.context = context
        self.notifications = notifications

        self.icon_app = QIcon(resource.get_path('img/logo.svg'))
        self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
        self.setWindowIcon(self.icon_app)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.toolbar_top = QToolBar()
        self.toolbar_top.addWidget(new_spacer())

        self.label_status = QLabel()
        self.label_status.setText('')
        self.label_status.setStyleSheet("font-weight: bold")
        self.toolbar_top.addWidget(self.label_status)

        self.toolbar_search = QToolBar()
        self.toolbar_search.setStyleSheet("spacing: 0px;")
        self.toolbar_search.setContentsMargins(0, 0, 0, 0)

        label_pre_search = QLabel()
        label_pre_search.setStyleSheet(
            "background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pre_search)

        self.input_search = QLineEdit()
        self.input_search.setMaxLength(20)
        self.input_search.setFrame(False)
        self.input_search.setPlaceholderText(
            self.i18n['window_manage.input_search.placeholder'] + "...")
        self.input_search.setToolTip(
            self.i18n['window_manage.input_search.tooltip'])
        self.input_search.setStyleSheet(
            "QLineEdit { background-color: white; color: gray; spacing: 0; height: 30px; font-size: 12px; width: 300px}"
        )
        self.input_search.returnPressed.connect(self.search)
        self.toolbar_search.addWidget(self.input_search)

        label_pos_search = QLabel()
        label_pos_search.setPixmap(QPixmap(
            resource.get_path('img/search.svg')))
        label_pos_search.setStyleSheet(
            "background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pos_search)

        self.ref_toolbar_search = self.toolbar_top.addWidget(
            self.toolbar_search)
        self.toolbar_top.addWidget(new_spacer())
        self.layout.addWidget(self.toolbar_top)

        self.toolbar = QToolBar()
        self.toolbar.setStyleSheet(
            'QToolBar {spacing: 4px; margin-top: 15px; margin-bottom: 5px}')

        self.checkbox_updates = QCheckBox()
        self.checkbox_updates.setText(self.i18n['updates'].capitalize())
        self.checkbox_updates.stateChanged.connect(self._handle_updates_filter)
        self.ref_checkbox_updates = self.toolbar.addWidget(
            self.checkbox_updates)

        self.checkbox_only_apps = QCheckBox()
        self.checkbox_only_apps.setText(
            self.i18n['manage_window.checkbox.only_apps'])
        self.checkbox_only_apps.setChecked(True)
        self.checkbox_only_apps.stateChanged.connect(
            self._handle_filter_only_apps)
        self.ref_checkbox_only_apps = self.toolbar.addWidget(
            self.checkbox_only_apps)

        self.any_type_filter = 'any'
        self.cache_type_filter_icons = {}
        self.combo_filter_type = QComboBox()
        self.combo_filter_type.setStyleSheet('QLineEdit { height: 2px}')
        self.combo_filter_type.setEditable(True)
        self.combo_filter_type.lineEdit().setReadOnly(True)
        self.combo_filter_type.lineEdit().setAlignment(Qt.AlignCenter)
        self.combo_filter_type.activated.connect(self._handle_type_filter)
        self.combo_filter_type.addItem(
            load_icon(resource.get_path('img/logo.svg'), 14),
            self.i18n[self.any_type_filter].capitalize(), self.any_type_filter)
        self.ref_combo_filter_type = self.toolbar.addWidget(
            self.combo_filter_type)

        self.input_name_filter = InputFilter(self.apply_filters_async)
        self.input_name_filter.setMaxLength(10)
        self.input_name_filter.setPlaceholderText(
            self.i18n['manage_window.name_filter.placeholder'] + '...')
        self.input_name_filter.setToolTip(
            self.i18n['manage_window.name_filter.tooltip'])
        self.input_name_filter.setStyleSheet(
            "QLineEdit { background-color: white; color: gray;}")
        self.input_name_filter.setFixedWidth(130)
        self.ref_input_name_filter = self.toolbar.addWidget(
            self.input_name_filter)

        self.toolbar.addWidget(new_spacer())

        self.bt_installed = QPushButton()
        self.bt_installed.setToolTip(
            self.i18n['manage_window.bt.installed.tooltip'])
        self.bt_installed.setIcon(QIcon(resource.get_path('img/disk.png')))
        self.bt_installed.setText(
            self.i18n['manage_window.bt.installed.text'].capitalize())
        self.bt_installed.clicked.connect(self._show_installed)
        self.bt_installed.setStyleSheet(toolbar_button_style('#A94E0A'))
        self.ref_bt_installed = self.toolbar.addWidget(self.bt_installed)

        self.bt_refresh = QPushButton()
        self.bt_refresh.setToolTip(i18n['manage_window.bt.refresh.tooltip'])
        self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg')))
        self.bt_refresh.setText(self.i18n['manage_window.bt.refresh.text'])
        self.bt_refresh.setStyleSheet(toolbar_button_style('#2368AD'))
        self.bt_refresh.clicked.connect(
            lambda: self.refresh_apps(keep_console=False))
        self.ref_bt_refresh = self.toolbar.addWidget(self.bt_refresh)

        self.bt_upgrade = QPushButton()
        self.bt_upgrade.setToolTip(i18n['manage_window.bt.upgrade.tooltip'])
        self.bt_upgrade.setIcon(QIcon(resource.get_path('img/app_update.svg')))
        self.bt_upgrade.setText(i18n['manage_window.bt.upgrade.text'])
        self.bt_upgrade.setStyleSheet(toolbar_button_style('#20A435'))
        self.bt_upgrade.clicked.connect(self.update_selected)
        self.ref_bt_upgrade = self.toolbar.addWidget(self.bt_upgrade)

        self.layout.addWidget(self.toolbar)

        self.table_apps = AppsTable(self,
                                    self.icon_cache,
                                    disk_cache=self.disk_cache,
                                    download_icons=self.download_icons)
        self.table_apps.change_headers_policy()

        self.layout.addWidget(self.table_apps)

        toolbar_console = QToolBar()

        self.checkbox_console = QCheckBox()
        self.checkbox_console.setText(
            self.i18n['manage_window.checkbox.show_details'])
        self.checkbox_console.stateChanged.connect(self._handle_console)
        self.checkbox_console.setVisible(False)
        self.ref_checkbox_console = toolbar_console.addWidget(
            self.checkbox_console)

        toolbar_console.addWidget(new_spacer())

        self.label_displayed = QLabel()
        toolbar_console.addWidget(self.label_displayed)

        self.layout.addWidget(toolbar_console)

        self.textarea_output = QPlainTextEdit(self)
        self.textarea_output.resize(self.table_apps.size())
        self.textarea_output.setStyleSheet("background: black; color: white;")
        self.layout.addWidget(self.textarea_output)
        self.textarea_output.setVisible(False)
        self.textarea_output.setReadOnly(True)

        self.toolbar_substatus = QToolBar()
        self.toolbar_substatus.addWidget(new_spacer())
        self.label_substatus = QLabel()
        self.toolbar_substatus.addWidget(self.label_substatus)
        self.toolbar_substatus.addWidget(new_spacer())
        self.layout.addWidget(self.toolbar_substatus)
        self._change_label_substatus('')

        self.thread_update = self._bind_async_action(
            UpdateSelectedApps(self.manager, self.i18n),
            finished_call=self._finish_update_selected)
        self.thread_refresh = self._bind_async_action(
            RefreshApps(self.manager),
            finished_call=self._finish_refresh_apps,
            only_finished=True)
        self.thread_uninstall = self._bind_async_action(
            UninstallApp(self.manager, self.icon_cache),
            finished_call=self._finish_uninstall)
        self.thread_get_info = self._bind_async_action(
            GetAppInfo(self.manager), finished_call=self._finish_get_info)
        self.thread_get_history = self._bind_async_action(
            GetAppHistory(self.manager, self.i18n),
            finished_call=self._finish_get_history)
        self.thread_search = self._bind_async_action(
            SearchPackages(self.manager),
            finished_call=self._finish_search,
            only_finished=True)
        self.thread_downgrade = self._bind_async_action(
            DowngradeApp(self.manager, self.i18n),
            finished_call=self._finish_downgrade)
        self.thread_suggestions = self._bind_async_action(
            FindSuggestions(man=self.manager),
            finished_call=self._finish_search,
            only_finished=True)
        self.thread_run_app = self._bind_async_action(
            LaunchApp(self.manager),
            finished_call=self._finish_run_app,
            only_finished=False)
        self.thread_custom_action = self._bind_async_action(
            CustomAction(manager=self.manager),
            finished_call=self._finish_custom_action)

        self.thread_apply_filters = ApplyFilters()
        self.thread_apply_filters.signal_finished.connect(
            self._finish_apply_filters_async)
        self.thread_apply_filters.signal_table.connect(
            self._update_table_and_upgrades)
        self.signal_table_update.connect(
            self.thread_apply_filters.stop_waiting)

        self.thread_install = InstallPackage(manager=self.manager,
                                             disk_cache=self.disk_cache,
                                             icon_cache=self.icon_cache,
                                             locale_keys=self.i18n)
        self._bind_async_action(self.thread_install,
                                finished_call=self._finish_install)

        self.thread_animate_progress = AnimateProgress()
        self.thread_animate_progress.signal_change.connect(
            self._update_progress)

        self.thread_verify_models = VerifyModels()
        self.thread_verify_models.signal_updates.connect(
            self._notify_model_data_change)

        self.toolbar_bottom = QToolBar()
        self.toolbar_bottom.setIconSize(QSize(16, 16))
        self.toolbar_bottom.setStyleSheet('QToolBar { spacing: 3px }')

        self.toolbar_bottom.addWidget(new_spacer())

        self.progress_bar = QProgressBar()
        self.progress_bar.setMaximumHeight(10 if QApplication.instance().style(
        ).objectName().lower() == 'windows' else 4)

        self.progress_bar.setTextVisible(False)
        self.ref_progress_bar = self.toolbar_bottom.addWidget(
            self.progress_bar)

        self.toolbar_bottom.addWidget(new_spacer())

        self.combo_styles = StylesComboBox(
            parent=self, i18n=i18n, show_panel_after_restart=bool(tray_icon))
        self.combo_styles.setStyleSheet('QComboBox {font-size: 12px;}')
        self.ref_combo_styles = self.toolbar_bottom.addWidget(
            self.combo_styles)

        bt_settings = IconButton(
            icon_path=resource.get_path('img/app_settings.svg'),
            action=self._show_settings_menu,
            background='#12ABAB',
            tooltip=self.i18n['manage_window.bt_settings.tooltip'])
        self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings)

        self.layout.addWidget(self.toolbar_bottom)

        qt_utils.centralize(self)

        self.filter_only_apps = True
        self.type_filter = self.any_type_filter
        self.filter_updates = False
        self._maximized = False
        self.progress_controll_enabled = True
        self.recent_installation = False

        self.dialog_about = None
        self.first_refresh = suggestions

        self.thread_warnings = ListWarnings(man=manager, locale_keys=i18n)
        self.thread_warnings.signal_warnings.connect(self._show_warnings)

    def set_tray_icon(self, tray_icon):
        self.tray_icon = tray_icon
        self.combo_styles.show_panel_after_restart = bool(tray_icon)

    def _update_process_progress(self, val: int):
        if self.progress_controll_enabled:
            self.thread_animate_progress.set_progress(val)

    def apply_filters_async(self):
        self.label_status.setText(self.i18n['manage_window.status.filtering'] +
                                  '...')

        self.ref_toolbar_search.setVisible(False)

        if self.ref_input_name_filter.isVisible():
            self.input_name_filter.setReadOnly(True)

        self.thread_apply_filters.filters = self._gen_filters()
        self.thread_apply_filters.pkgs = self.pkgs_available
        self.thread_apply_filters.start()

    def _update_table_and_upgrades(self, pkgs_info: dict):
        self._update_table(pkgs_info=pkgs_info, signal=True)
        self.update_bt_upgrade(pkgs_info)

    def _finish_apply_filters_async(self, success: bool):
        self.label_status.setText('')
        self.ref_toolbar_search.setVisible(True)

        if self.ref_input_name_filter.isVisible():
            self.input_name_filter.setReadOnly(False)

    def _bind_async_action(self,
                           action: AsyncAction,
                           finished_call,
                           only_finished: bool = False) -> AsyncAction:
        action.signal_finished.connect(finished_call)

        if not only_finished:
            action.signal_confirmation.connect(self._ask_confirmation)
            action.signal_output.connect(self._update_action_output)
            action.signal_message.connect(self._show_message)
            action.signal_status.connect(self._change_label_status)
            action.signal_substatus.connect(self._change_label_substatus)
            action.signal_progress.connect(self._update_process_progress)

            self.signal_user_res.connect(action.confirm)

        return action

    def _ask_confirmation(self, msg: dict):
        self.thread_animate_progress.pause()
        diag = ConfirmationDialog(title=msg['title'],
                                  body=msg['body'],
                                  locale_keys=self.i18n,
                                  components=msg['components'],
                                  confirmation_label=msg['confirmation_label'],
                                  deny_label=msg['deny_label'])
        res = diag.is_confirmed()
        self.thread_animate_progress.animate()
        self.signal_user_res.emit(res)

    def _show_message(self, msg: dict):
        self.thread_animate_progress.pause()
        dialog.show_message(title=msg['title'],
                            body=msg['body'],
                            type_=msg['type'])
        self.thread_animate_progress.animate()

    def _show_warnings(self, warnings: List[str]):
        if warnings:
            dialog.show_message(title=self.i18n['warning'].capitalize(),
                                body='<p>{}</p>'.format(
                                    '<br/><br/>'.join(warnings)),
                                type_=MessageType.WARNING)

    def show(self):
        super(ManageWindow, self).show()
        if not self.thread_warnings.isFinished():
            self.thread_warnings.start()

    def verify_warnings(self):
        self.thread_warnings.start()

    def _show_installed(self):
        if self.pkgs_installed:
            self.finish_action()
            self.ref_bt_upgrade.setVisible(True)
            self.ref_checkbox_only_apps.setVisible(True)
            self.input_search.setText('')
            self.input_name_filter.setText('')
            self.update_pkgs(new_pkgs=None, as_installed=True)

    def _show_about(self):
        if self.dialog_about is None:
            self.dialog_about = AboutDialog(self.i18n)

        self.dialog_about.show()

    def _handle_updates_filter(self, status: int):
        self.filter_updates = status == 2
        self.apply_filters_async()

    def _handle_filter_only_apps(self, status: int):
        self.filter_only_apps = status == 2
        self.apply_filters_async()

    def _handle_type_filter(self, idx: int):
        self.type_filter = self.combo_filter_type.itemData(idx)
        self.apply_filters_async()

    def _notify_model_data_change(self):
        self.table_apps.fill_async_data()

    def changeEvent(self, e: QEvent):
        if isinstance(e, QWindowStateChangeEvent):
            self._maximized = self.isMaximized()
            policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents
            self.table_apps.change_headers_policy(policy)

    def closeEvent(self, event):

        if self.tray_icon:
            event.ignore()
            self.hide()
            self._handle_console_option(False)

    def _handle_console(self, checked: bool):

        if checked:
            self.textarea_output.show()
        else:
            self.textarea_output.hide()

    def _handle_console_option(self, enable: bool):

        if enable:
            self.textarea_output.clear()

        self.ref_checkbox_console.setVisible(enable)
        self.checkbox_console.setChecked(False)
        self.textarea_output.hide()

    def refresh_apps(self,
                     keep_console: bool = True,
                     top_app: PackageView = None,
                     pkg_types: Set[Type[SoftwarePackage]] = None):
        self.recent_installation = False
        self.type_filter = None
        self.input_search.clear()

        if not keep_console:
            self._handle_console_option(False)

        self.ref_checkbox_updates.setVisible(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self._begin_action(self.i18n['manage_window.status.refreshing'],
                           keep_bt_installed=False,
                           clear_filters=True)

        self.thread_refresh.app = top_app  # the app will be on top when refresh happens
        self.thread_refresh.pkg_types = pkg_types
        self.thread_refresh.start()

    def _finish_refresh_apps(self, res: dict, as_installed: bool = True):
        self.finish_action()
        self.ref_checkbox_only_apps.setVisible(bool(res['installed']))
        self.ref_bt_upgrade.setVisible(True)
        self.update_pkgs(res['installed'],
                         as_installed=as_installed,
                         types=res['types'])
        self.first_refresh = False

    def uninstall_app(self, app: PackageView):
        pwd = None
        requires_root = self.manager.requires_root('uninstall', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.uninstalling'], app.model.name))

        self.thread_uninstall.app = app
        self.thread_uninstall.root_password = pwd
        self.thread_uninstall.start()

    def run_app(self, app: PackageView):
        self._begin_action(
            self.i18n['manage_window.status.running_app'].format(
                app.model.name))
        self.thread_run_app.app = app
        self.thread_run_app.start()

    def _finish_uninstall(self, pkgv: PackageView):
        self.finish_action()

        if pkgv:
            if self._can_notify_user():
                util.notify_user('{} ({}) {}'.format(pkgv.model.name,
                                                     pkgv.model.get_type(),
                                                     self.i18n['uninstalled']))

            self.refresh_apps(pkg_types={pkgv.model.__class__})
        else:
            if self._can_notify_user():
                util.notify_user('{}: {}'.format(
                    pkgv.model.name,
                    self.i18n['notification.uninstall.failed']))

            self.checkbox_console.setChecked(True)

    def _can_notify_user(self):
        return self.notifications and (self.isHidden() or self.isMinimized())

    def _finish_downgrade(self, res: dict):
        self.finish_action()

        if res['success']:
            if self._can_notify_user():
                util.notify_user('{} {}'.format(res['app'],
                                                self.i18n['downgraded']))

            self.refresh_apps(pkg_types={res['app'].model.__class__})

            if self.tray_icon:
                self.tray_icon.verify_updates(notify_user=False)
        else:
            if self._can_notify_user():
                util.notify_user(self.i18n['notification.downgrade.failed'])

            self.checkbox_console.setChecked(True)

    def _change_label_status(self, status: str):
        self.label_status.setText(status)

    def _change_label_substatus(self, substatus: str):
        self.label_substatus.setText('<p>{}</p>'.format(substatus))
        if not substatus:
            self.toolbar_substatus.hide()
        elif not self.toolbar_substatus.isVisible():
            self.toolbar_substatus.show()

    def _update_table(self, pkgs_info: dict, signal: bool = False):
        self.pkgs = pkgs_info['pkgs_displayed']

        self.table_apps.update_pkgs(
            self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0)

        if not self._maximized:
            self.table_apps.change_headers_policy(QHeaderView.Stretch)
            self.table_apps.change_headers_policy()
            self.resize_and_center(accept_lower_width=len(self.pkgs) > 0)
            self.label_displayed.setText('{} / {}'.format(
                len(self.pkgs), len(self.pkgs_available)))
        else:
            self.label_displayed.setText('')

        if signal:
            self.signal_table_update.emit()

    def update_bt_upgrade(self, pkgs_info: dict = None):
        show_bt_upgrade = False

        if not pkgs_info or pkgs_info['not_installed'] == 0:
            for app_v in (pkgs_info['pkgs_displayed']
                          if pkgs_info else self.pkgs):
                if app_v.update_checked:
                    show_bt_upgrade = True
                    break

        self.ref_bt_upgrade.setVisible(show_bt_upgrade)

    def change_update_state(self,
                            pkgs_info: dict,
                            trigger_filters: bool = True):
        self.update_bt_upgrade(pkgs_info)

        if pkgs_info['updates'] > 0:

            if pkgs_info['not_installed'] == 0:
                if not self.ref_checkbox_updates.isVisible():
                    self.ref_checkbox_updates.setVisible(True)

                if not self.filter_updates:
                    self._change_checkbox(self.checkbox_updates, True,
                                          'filter_updates', trigger_filters)

            if pkgs_info['napp_updates'] > 0 and self.filter_only_apps:
                self._change_checkbox(self.checkbox_only_apps, False,
                                      'filter_only_apps', trigger_filters)
        else:
            self._change_checkbox(self.checkbox_updates, False,
                                  'filter_updates', trigger_filters)

            self.ref_checkbox_updates.setVisible(False)

    def _change_checkbox(self,
                         checkbox: QCheckBox,
                         checked: bool,
                         attr: str = None,
                         trigger: bool = True):
        if not trigger:
            checkbox.blockSignals(True)

        checkbox.setChecked(checked)

        if not trigger:
            setattr(self, attr, checked)
            checkbox.blockSignals(False)

    def _gen_filters(self,
                     updates: int = 0,
                     ignore_updates: bool = False) -> dict:
        return {
            'only_apps':
            self.filter_only_apps,
            'type':
            self.type_filter,
            'updates':
            False if ignore_updates else self.filter_updates,
            'name':
            self.input_name_filter.get_text().lower()
            if self.input_name_filter.get_text() else None,
            'display_limit':
            self.display_limit if updates <= 0 else None
        }

    def update_pkgs(self,
                    new_pkgs: List[SoftwarePackage],
                    as_installed: bool,
                    types: Set[type] = None,
                    ignore_updates: bool = False):
        self.input_name_filter.setText('')
        pkgs_info = commons.new_pkgs_info()
        filters = self._gen_filters(ignore_updates)

        if new_pkgs is not None:
            old_installed = None

            if as_installed:
                old_installed = self.pkgs_installed
                self.pkgs_installed = []

            for pkg in new_pkgs:
                app_model = PackageView(model=pkg)
                commons.update_info(app_model, pkgs_info)
                commons.apply_filters(app_model, filters, pkgs_info)

            if old_installed and types:
                for pkgv in old_installed:
                    if not pkgv.model.__class__ in types:
                        commons.update_info(pkgv, pkgs_info)
                        commons.apply_filters(pkgv, filters, pkgs_info)

        else:  # use installed
            for pkgv in self.pkgs_installed:
                commons.update_info(pkgv, pkgs_info)
                commons.apply_filters(pkgv, filters, pkgs_info)

        if pkgs_info['apps_count'] == 0:
            if self.first_refresh:
                self._begin_search('')
                self.thread_suggestions.start()
                return
            else:
                self._change_checkbox(self.checkbox_only_apps,
                                      False,
                                      'filter_only_apps',
                                      trigger=False)
                self.checkbox_only_apps.setCheckable(False)
        else:
            self.checkbox_only_apps.setCheckable(True)
            self._change_checkbox(self.checkbox_only_apps,
                                  True,
                                  'filter_only_apps',
                                  trigger=False)

        self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False)
        self._apply_filters(pkgs_info, ignore_updates=ignore_updates)
        self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False)

        self.pkgs_available = pkgs_info['pkgs']

        if as_installed:
            self.pkgs_installed = pkgs_info['pkgs']

        self.pkgs = pkgs_info['pkgs_displayed']

        if self.pkgs:
            self.ref_input_name_filter.setVisible(True)

        self._update_type_filters(pkgs_info['available_types'])

        self._update_table(pkgs_info=pkgs_info)

        if new_pkgs:
            self.thread_verify_models.apps = self.pkgs
            self.thread_verify_models.start()

        if self.pkgs_installed:
            self.ref_bt_installed.setVisible(not as_installed
                                             and not self.recent_installation)

        self.resize_and_center(accept_lower_width=self.pkgs_installed)

    def _apply_filters(self, pkgs_info: dict, ignore_updates: bool):
        pkgs_info['pkgs_displayed'] = []
        filters = self._gen_filters(updates=pkgs_info['updates'],
                                    ignore_updates=ignore_updates)
        for pkgv in pkgs_info['pkgs']:
            commons.apply_filters(pkgv, filters, pkgs_info)

    def _update_type_filters(self, available_types: dict = None):

        if available_types is None:
            self.ref_combo_filter_type.setVisible(
                self.combo_filter_type.count() > 1)
        else:
            self.type_filter = self.any_type_filter

            if available_types and len(available_types) > 1:
                if self.combo_filter_type.count() > 1:
                    for _ in range(self.combo_filter_type.count() - 1):
                        self.combo_filter_type.removeItem(1)

                for app_type, icon_path in available_types.items():
                    icon = self.cache_type_filter_icons.get(app_type)

                    if not icon:
                        icon = load_icon(icon_path, 14)
                        self.cache_type_filter_icons[app_type] = icon

                    self.combo_filter_type.addItem(icon, app_type.capitalize(),
                                                   app_type)

                self.ref_combo_filter_type.setVisible(True)
            else:
                self.ref_combo_filter_type.setVisible(False)

    def resize_and_center(self, accept_lower_width: bool = True):
        if self.pkgs:
            new_width = reduce(operator.add, [
                self.table_apps.columnWidth(i)
                for i in range(self.table_apps.columnCount())
            ])

            if self.ref_bt_upgrade.isVisible(
            ) or self.ref_bt_settings.isVisible():
                new_width *= 1.07
        else:
            new_width = self.toolbar_top.width()

        if accept_lower_width or new_width > self.width():
            self.resize(new_width, self.height())

            if self.ref_bt_upgrade.isVisible(
            ) and self.bt_upgrade.visibleRegion().isEmpty():
                self.adjustSize()

        qt_utils.centralize(self)

    def update_selected(self):
        if self.pkgs:
            requires_root = False

            to_update = []

            for app_v in self.pkgs:
                if app_v.update_checked:
                    to_update.append(app_v)

                    if self.manager.requires_root('update', app_v.model):
                        requires_root = True

            if to_update and dialog.ask_confirmation(
                    title=self.i18n['manage_window.upgrade_all.popup.title'],
                    body=self.i18n['manage_window.upgrade_all.popup.body'],
                    locale_keys=self.i18n,
                    widgets=[
                        UpdateToggleButton(
                            None, self, self.i18n, clickable=False)
                    ]):
                pwd = None

                if not is_root() and requires_root:
                    pwd, ok = ask_root_password(self.i18n)

                    if not ok:
                        return

                self._handle_console_option(True)
                self.progress_controll_enabled = len(to_update) == 1
                self._begin_action(self.i18n['manage_window.status.upgrading'])
                self.thread_update.apps_to_update = to_update
                self.thread_update.root_password = pwd
                self.thread_update.start()

    def _finish_update_selected(self, res: dict):
        self.finish_action()

        if res['success']:
            if self._can_notify_user():
                util.notify_user('{} {}'.format(
                    res['updated'],
                    self.i18n['notification.update_selected.success']))

            self.refresh_apps(pkg_types=res['types'])

            if self.tray_icon:
                self.tray_icon.verify_updates()
        else:
            if self._can_notify_user():
                util.notify_user(
                    self.i18n['notification.update_selected.failed'])

            self.ref_bt_upgrade.setVisible(True)
            self.checkbox_console.setChecked(True)

    def _update_action_output(self, output: str):
        self.textarea_output.appendPlainText(output)

    def _begin_action(self,
                      action_label: str,
                      keep_search: bool = False,
                      keep_bt_installed: bool = True,
                      clear_filters: bool = False):
        self.ref_input_name_filter.setVisible(False)
        self.ref_combo_filter_type.setVisible(False)
        self.ref_bt_settings.setVisible(False)
        self.thread_animate_progress.stop = False
        self.thread_animate_progress.start()
        self.ref_progress_bar.setVisible(True)
        self.ref_combo_styles.setVisible(False)

        self.label_status.setText(action_label + "...")
        self.ref_bt_upgrade.setVisible(False)
        self.ref_bt_refresh.setVisible(False)
        self.checkbox_only_apps.setEnabled(False)
        self.table_apps.setEnabled(False)
        self.checkbox_updates.setEnabled(False)

        if not keep_bt_installed:
            self.ref_bt_installed.setVisible(False)
        elif self.ref_bt_installed.isVisible():
            self.ref_bt_installed.setEnabled(False)

        if keep_search:
            self.ref_toolbar_search.setVisible(True)
        else:
            self.ref_toolbar_search.setVisible(False)

        if clear_filters:
            self._update_type_filters({})
        else:
            self.combo_filter_type.setEnabled(False)

    def finish_action(self):
        self.ref_combo_styles.setVisible(True)
        self.thread_animate_progress.stop = True
        self.thread_animate_progress.wait()
        self.ref_progress_bar.setVisible(False)
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(False)

        self._change_label_substatus('')
        self.ref_bt_settings.setVisible(True)

        self.ref_bt_refresh.setVisible(True)
        self.checkbox_only_apps.setEnabled(True)
        self.table_apps.setEnabled(True)
        self.input_search.setEnabled(True)
        self.label_status.setText('')
        self.label_substatus.setText('')
        self.ref_toolbar_search.setVisible(True)
        self.ref_toolbar_search.setEnabled(True)
        self.combo_filter_type.setEnabled(True)
        self.checkbox_updates.setEnabled(True)
        self.progress_controll_enabled = True

        if self.pkgs:
            self.ref_input_name_filter.setVisible(True)
            self.update_bt_upgrade()
            self._update_type_filters()

            if self.ref_bt_installed.isVisible():
                self.ref_bt_installed.setEnabled(True)

    def downgrade(self, pkgv: PackageView):
        pwd = None
        requires_root = self.manager.requires_root('downgrade', pkgv.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.downgrading'], pkgv.model.name))

        self.thread_downgrade.app = pkgv
        self.thread_downgrade.root_password = pwd
        self.thread_downgrade.start()

    def get_app_info(self, pkg: dict):
        self._handle_console_option(False)
        self._begin_action(self.i18n['manage_window.status.info'])

        self.thread_get_info.app = pkg
        self.thread_get_info.start()

    def get_app_history(self, app: dict):
        self._handle_console_option(False)
        self._begin_action(self.i18n['manage_window.status.history'])

        self.thread_get_history.app = app
        self.thread_get_history.start()

    def _finish_get_info(self, app_info: dict):
        self.finish_action()
        dialog_info = InfoDialog(app=app_info,
                                 icon_cache=self.icon_cache,
                                 locale_keys=self.i18n,
                                 screen_size=self.screen_size)
        dialog_info.exec_()

    def _finish_get_history(self, res: dict):
        self.finish_action()

        if res.get('error'):
            self._handle_console_option(True)
            self.textarea_output.appendPlainText(res['error'])
            self.checkbox_console.setChecked(True)
        else:
            dialog_history = HistoryDialog(res['history'], self.icon_cache,
                                           self.i18n)
            dialog_history.exec_()

    def _begin_search(self, word):
        self._handle_console_option(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self.ref_checkbox_updates.setVisible(False)
        self.filter_updates = False
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.searching'], word if word else ''),
                           clear_filters=True)

    def search(self):
        word = self.input_search.text().strip()
        if word:
            self._begin_search(word)
            self.thread_search.word = word
            self.thread_search.start()

    def _finish_search(self, res: dict):
        self.finish_action()

        if not res['error']:
            self.ref_bt_upgrade.setVisible(False)
            self.update_pkgs(res['pkgs_found'],
                             as_installed=False,
                             ignore_updates=True)
        else:
            dialog.show_message(title=self.i18n['warning'].capitalize(),
                                body=self.i18n[res['error']],
                                type_=MessageType.WARNING)

    def install(self, pkg: PackageView):
        pwd = None
        requires_root = self.manager.requires_root('install', pkg.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.installing'], pkg.model.name))

        self.thread_install.pkg = pkg
        self.thread_install.root_password = pwd
        self.thread_install.start()

    def _finish_install(self, res: dict):
        self.input_search.setText('')
        self.finish_action()

        console_output = self.textarea_output.toPlainText()

        if console_output:
            log_path = '/tmp/bauh/logs/install/{}/{}'.format(
                res['pkg'].model.get_type(), res['pkg'].model.name)
            try:
                Path(log_path).mkdir(parents=True, exist_ok=True)

                log_file = log_path + '/{}.log'.format(int(time.time()))
                with open(log_file, 'w+') as f:
                    f.write(console_output)

                self.textarea_output.appendPlainText(
                    self.i18n['console.install_logs.path'].format(
                        '"{}"'.format(log_file)))
            except:
                self.textarea_output.appendPlainText(
                    "[warning] Could not write install log file to '{}'".
                    format(log_path))

        if res['success']:
            self.recent_installation = True
            if self._can_notify_user():
                util.notify_user(msg='{} ({}) {}'.format(
                    res['pkg'].model.name, res['pkg'].model.get_type(),
                    self.i18n['installed']))

            self._finish_refresh_apps({
                'installed': [res['pkg'].model],
                'total': 1,
                'types': None
            })
            self.ref_bt_installed.setVisible(False)
            self.ref_checkbox_only_apps.setVisible(False)
        else:
            if self._can_notify_user():
                util.notify_user('{}: {}'.format(
                    res['pkg'].model.name,
                    self.i18n['notification.install.failed']))

            self.checkbox_console.setChecked(True)

    def _update_progress(self, value: int):
        self.progress_bar.setValue(value)

    def _finish_run_app(self, success: bool):
        self.finish_action()

    def execute_custom_action(self, pkg: PackageView, action: PackageAction):
        pwd = None

        if not is_root() and action.requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(self.i18n[action.i18n_status_key],
                                          pkg.model.name))

        self.thread_custom_action.pkg = pkg
        self.thread_custom_action.root_password = pwd
        self.thread_custom_action.custom_action = action
        self.thread_custom_action.start()

    def _finish_custom_action(self, res: dict):
        self.finish_action()
        if res['success']:
            self.refresh_apps(pkg_types={res['pkg'].model.__class__})
        else:
            self.checkbox_console.setChecked(True)

    def show_gems_selector(self):
        gem_panel = GemSelectorPanel(window=self,
                                     manager=self.manager,
                                     i18n=self.i18n,
                                     config=self.config,
                                     show_panel_after_restart=bool(
                                         self.tray_icon))
        gem_panel.show()

    def _show_settings_menu(self):
        menu_row = QMenu()

        if isinstance(self.manager, GenericSoftwareManager):
            action_gems = QAction(self.i18n['manage_window.settings.gems'])
            action_gems.setIcon(self.icon_app)

            action_gems.triggered.connect(self.show_gems_selector)
            menu_row.addAction(action_gems)

        action_about = QAction(self.i18n['manage_window.settings.about'])
        action_about.setIcon(QIcon(resource.get_path('img/about.svg')))
        action_about.triggered.connect(self._show_about)
        menu_row.addAction(action_about)

        menu_row.adjustSize()
        menu_row.popup(QCursor.pos())
        menu_row.exec_()
Пример #7
0
class FullscreenableWidget(QMainWindow):
    widgetFullscreened = Signal(bool)

    def __init__(self, parent=None, icon_size=None):
        QMainWindow.__init__(self, parent)
        self.toolbar = QToolBar('tools', parent=self)
        self.toolbar.setContentsMargins(0, 0, 0, 0)
        self.icon_provider = IconProvider(self)
        self.dark_mode = self.icon_provider.get_theme_mode(self)
        if icon_size is not None:
            self.toolbar.setIconSize(QSize(icon_size, icon_size))
        self.addToolBar(Qt.RightToolBarArea, self.toolbar)

        self.action_fullscreen = QAction(self)
        self.action_fullscreen.setIcon(
            QIcon(self.icon_provider.get_icon_path('fullscreen.svg')))
        self.toolbar.addAction(self.action_fullscreen)
        self.action_windowed = QAction(self)
        self.action_windowed.setIcon(
            QIcon(self.icon_provider.get_icon_path('fullscreen_exit.svg')))
        # signalling:
        self.action_fullscreen.triggered.connect(self.go_fullscreen)
        self.action_windowed.triggered.connect(self.go_windowed)
        self.windowed_parent = None
        self.win_parent_layout = None
        self.index_in_layout = None
        self.windowed_flags = None
        self.windowed_geometry = None
        self.position_in_grid = None

    def go_fullscreen(self):
        self.windowed_flags = self.windowFlags()
        self.windowed_geometry = self.geometry()
        cur_scr = self.screen()  # to check which screen
        s_count = len(qApp.screens())
        for i in range(s_count):
            if qApp.screens()[i] == cur_scr:
                screen = qApp.screens()[i]
        if self.parent() is not None:
            self.windowed_parent = self.parent()
            self.win_parent_layout = self.windowed_parent.layout()
            self.index_in_layout = self.win_parent_layout.indexOf(self)
            if isinstance(self.win_parent_layout, QGridLayout):
                self.position_in_grid = \
                    self.win_parent_layout.getItemPosition(
                        self.index_in_layout)
            self.win_parent_layout.removeWidget(self)
        self.setParent(None)
        self.toolbar.insertAction(self.action_fullscreen, self.action_windowed)
        self.toolbar.removeAction(self.action_fullscreen)
        self.move(screen.geometry().x(), screen.geometry().y())
        self.showFullScreen()
        self.widgetFullscreened.emit(True)

    def go_windowed(self):
        self.showNormal()
        if self.windowed_parent is not None:
            if isinstance(self.win_parent_layout, (QBoxLayout, QSplitter)):
                self.win_parent_layout.insertWidget(self.index_in_layout, self)
            elif isinstance(self.win_parent_layout, QGridLayout):
                self.win_parent_layout.addWidget(self, *self.position_in_grid)
            self.setParent(self.windowed_parent)
            self.windowed_parent = None
        self.setGeometry(self.windowed_geometry)
        self.toolbar.insertAction(self.action_windowed, self.action_fullscreen)
        self.toolbar.removeAction(self.action_windowed)
        self.widgetFullscreened.emit(False)
Пример #8
0
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.resize(800, 600)
        MainWindow.setMinimumSize(600, 400)
        icon = QIcon()
        icon.addPixmap(QPixmap("icon.png"), QIcon.Normal, QIcon.Off)
        font = QFont()
        font.setFamily("MS Sans Serif")
        font.setBold(True)
        font.setWeight(75)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QWidget(MainWindow)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QMenuBar(MainWindow)
        self.menuFiles = QMenu(self.menubar)
        self.menuMark = QMenu(self.menubar)
        self.menuCommands = QMenu(self.menubar)
        self.menuNet = QMenu(self.menubar)
        self.menuShow = QMenu(self.menubar)
        self.menuConfiguration = QMenu(self.menubar)
        self.menuStart = QMenu(self.menubar)
        MainWindow.setMenuBar(self.menubar)
        self.helpMenuBar = QMenuBar(self.menubar)
        self.menuHelp = QMenu(self.helpMenuBar)
        self.helpMenuBar.addMenu(self.menuHelp)
        self.menubar.setCornerWidget(self.helpMenuBar)
        self.toolBar = QToolBar(MainWindow)
        self.toolBar.setMovable(False)
        MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
        self.toolBar_2 = QToolBar(MainWindow)

        font2 = QFont()
        font2.setFamily("MS Sans Serif")
        font2.setWeight(700)
        font2.setPixelSize(8)
        font2.setBold(True)

        self.model1 = QFileSystemModel()
        self.model1.setRootPath('C:\\Windows')
        self.tree1 = QTreeView()
        self.tree1.setModel(self.model1)
        self.tree1.setRootIndex(self.model1.index("C:\\Windows\\"))
        self.tree1.setAnimated(False)
        self.tree1.setFont(font2)
        self.tree1.setIndentation(20)
        self.tree1.setSortingEnabled(True)
        self.tree1.setItemsExpandable(False)
        self.tree1.setRootIsDecorated(False)
        self.tree1.sortByColumn(0, QtCore.Qt.AscendingOrder)
        self.tree1.resize(350, 480)

        self.model2 = QFileSystemModel()
        self.model2.setRootPath('C:\\Windows\\System32')
        self.tree2 = QTreeView()
        self.tree2.setModel(self.model2)
        self.tree2.setRootIndex(self.model2.index("C:\\Windows\\System32\\"))
        self.tree2.setAnimated(False)
        self.tree2.setFont(font2)
        self.tree2.setIndentation(20)
        self.tree2.setItemsExpandable(False)
        self.tree2.setRootIsDecorated(False)
        self.tree2.setSortingEnabled(True)
        self.tree2.resize(350, 480)

        for i in range(1, self.tree1.model().columnCount()):
            self.tree1.header().hideSection(i)

        for i in range(1, self.tree2.model().columnCount()):
            self.tree2.header().hideSection(i)

        self.centralHBox = QHBoxLayout()
        self.centralHBox.addWidget(self.tree1)
        self.centralHBox.addWidget(self.tree2)
        self.centralHBox.setContentsMargins(3, 3, 3, 3)
        self.centralwidget.setLayout(self.centralHBox)

        self.toolBar_2.setMovable(False)

        self.toolBar_3 = QToolBar(MainWindow)
        self.toolBar_3.setFixedHeight(30)
        self.toolBar_3.setContentsMargins(1, 1, 1, 1)
        self.dirLabel = QLabel()
        self.dirLabel.setText('c:\\Windows>')
        self.dirBox = QComboBox()
        self.dirBox.setEditable(True)
        self.dirBox.addItem('')
        self.dirBox.setFixedWidth(470)
        self.spacer = QWidget()
        self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        self.toolBar_3.addWidget(self.spacer)
        self.toolBar_3.addWidget(self.dirLabel)
        self.toolBar_3.addWidget(self.dirBox)
        self.toolBar_3.setFont(font)
        self.toolBar_3.setMovable(False)
        self.toolBar_3.setStyleSheet("QToolBar{spacing: 5px;}")

        MainWindow.addToolBar(QtCore.Qt.BottomToolBarArea, self.toolBar_2)
        MainWindow.addToolBar(QtCore.Qt.BottomToolBarArea, self.toolBar_3)
        MainWindow.insertToolBarBreak(self.toolBar_3)
        self.actionIndex = QAction(MainWindow)
        icon1 = QIcon()
        icon1.addPixmap(QPixmap("help.png"), QIcon.Normal, QIcon.Off)
        self.actionIndex.setIcon(icon1)
        self.actionKeyboard = QAction(MainWindow)
        self.actionRegistration_Info = QAction(MainWindow)
        self.actionVisit_Total_CMD_s_website = QAction(MainWindow)
        self.actionAbout_Total_Commander = QAction(MainWindow)
        self.actionOption_1 = QAction(MainWindow)
        self.actionOption_2 = QAction(MainWindow)
        self.actionOption_3 = QAction(MainWindow)
        self.actionOption_4 = QAction(MainWindow)
        self.actionOption_5 = QAction(MainWindow)
        self.actionOption_6 = QAction(MainWindow)
        self.actionOption_7 = QAction(MainWindow)
        self.actionOption_8 = QAction(MainWindow)
        self.actionOption_9 = QAction(MainWindow)
        self.actionOption_10 = QAction(MainWindow)
        self.actionOption_11 = QAction(MainWindow)
        self.actionOption_12 = QAction(MainWindow)
        self.actionOption_13 = QAction(MainWindow)
        self.actionOption_14 = QAction(MainWindow)
        self.Refresh = QAction(MainWindow)
        icon2 = QIcon()
        icon2.addPixmap(QPixmap("refresh.png"), QIcon.Normal, QIcon.Off)
        self.Refresh.setIcon(icon2)
        self.action_view1 = QAction(MainWindow)
        icon3 = QIcon()
        icon3.addPixmap(QPixmap("folder_structure_1.png"), QIcon.Normal,
                        QIcon.Off)
        self.view_group = QActionGroup(MainWindow)
        self.action_view1.setIcon(icon3)
        self.action_view1.setCheckable(True)
        self.action_view2 = QAction(MainWindow)
        self.action_view2.setCheckable(True)
        self.action_view2.setChecked(True)
        icon4 = QIcon()
        icon4.addPixmap(QPixmap("folder_structure_2.png"), QIcon.Normal,
                        QIcon.Off)
        self.action_view2.setIcon(icon4)
        self.view_group.addAction(self.action_view1)
        self.view_group.addAction(self.action_view2)
        self.view_group.setExclusive(True)
        self.actionF3_View = QToolButton(MainWindow)
        self.actionF3_View.setStyleSheet("QToolButton{border: none;}")
        self.actionF3_View.setFont(font)
        self.actionF4_Edit = QToolButton(MainWindow)
        self.actionF4_Edit.setStyleSheet("QToolButton{border: none;}")
        self.actionF4_Edit.setFont(font)
        self.actionF5_Copy = QToolButton(MainWindow)
        self.actionF5_Copy.setStyleSheet("QToolButton{border: none;}")
        self.actionF5_Copy.setFont(font)
        self.actionF6_Move = QToolButton(MainWindow)
        self.actionF6_Move.setStyleSheet("QToolButton{border: none;}")
        self.actionF6_Move.setFont(font)
        self.actionF5_NewFolder = QToolButton(MainWindow)
        self.actionF5_NewFolder.setStyleSheet("QToolButton{border: none;}")
        self.actionF5_NewFolder.setFont(font)
        self.actionF8_Delete = QToolButton(MainWindow)
        self.actionF8_Delete.setStyleSheet("QToolButton{border: none;}")
        self.actionF8_Delete.setFont(font)
        self.actionAlt_F4_Exit = QToolButton(MainWindow)
        self.actionAlt_F4_Exit.setStyleSheet("QToolButton{border: none;}")
        self.actionAlt_F4_Exit.setFont(font)
        self.menuFiles.addAction(self.actionOption_1)
        self.menuFiles.addAction(self.actionOption_2)
        self.menuMark.addAction(self.actionOption_3)
        self.menuMark.addAction(self.actionOption_4)
        self.menuCommands.addAction(self.actionOption_5)
        self.menuCommands.addAction(self.actionOption_6)
        self.menuNet.addAction(self.actionOption_7)
        self.menuNet.addAction(self.actionOption_8)
        self.menuShow.addAction(self.actionOption_9)
        self.menuShow.addAction(self.actionOption_10)
        self.menuConfiguration.addAction(self.actionOption_11)
        self.menuConfiguration.addAction(self.actionOption_12)
        self.menuStart.addAction(self.actionOption_13)
        self.menuStart.addAction(self.actionOption_14)
        self.menuHelp.addAction(self.actionIndex)
        self.menuHelp.addAction(self.actionKeyboard)
        self.menuHelp.addAction(self.actionRegistration_Info)
        self.menuHelp.addAction(self.actionVisit_Total_CMD_s_website)
        self.menuHelp.addSeparator()
        self.menuHelp.addAction(self.actionAbout_Total_Commander)
        self.menubar.addAction(self.menuFiles.menuAction())
        self.menubar.addAction(self.menuMark.menuAction())
        self.menubar.addAction(self.menuCommands.menuAction())
        self.menubar.addAction(self.menuNet.menuAction())
        self.menubar.addAction(self.menuShow.menuAction())
        self.menubar.addAction(self.menuConfiguration.menuAction())
        self.menubar.addAction(self.menuStart.menuAction())
        self.helpMenuBar.addAction(self.menuHelp.menuAction())
        self.toolBar.addAction(self.Refresh)
        self.toolBar.addSeparator()
        self.toolBar.addAction(self.action_view1)
        self.toolBar.addSeparator()
        self.toolBar.addAction(self.action_view2)

        self.separator1 = QFrame()
        self.separator1.setFrameShape(QFrame.VLine)
        self.separator1.setFrameShadow(QFrame.Sunken)
        self.separator2 = QFrame()
        self.separator2.setFrameShape(QFrame.VLine)
        self.separator2.setFrameShadow(QFrame.Sunken)
        self.separator3 = QFrame()
        self.separator3.setFrameShape(QFrame.VLine)
        self.separator3.setFrameShadow(QFrame.Sunken)
        self.separator4 = QFrame()
        self.separator4.setFrameShape(QFrame.VLine)
        self.separator4.setFrameShadow(QFrame.Sunken)
        self.separator5 = QFrame()
        self.separator5.setFrameShape(QFrame.VLine)
        self.separator5.setFrameShadow(QFrame.Sunken)
        self.separator6 = QFrame()
        self.separator6.setFrameShape(QFrame.VLine)
        self.separator6.setFrameShadow(QFrame.Sunken)

        self.toolbarHBoxWidget = QWidget()
        self.toolbarHBoxWidget.setSizePolicy(QSizePolicy.Expanding,
                                             QSizePolicy.Preferred)
        self.toolbarHBox = QGridLayout()
        self.toolbarHBoxWidget.setLayout(self.toolbarHBox)
        self.toolbarHBox.addWidget(self.actionF3_View, 0, 0)
        self.toolbarHBox.addWidget(self.separator1, 0, 1)
        self.toolbarHBox.addWidget(self.actionF4_Edit, 0, 2)
        self.toolbarHBox.addWidget(self.separator2, 0, 3)
        self.toolbarHBox.addWidget(self.actionF5_Copy, 0, 4)
        self.toolbarHBox.addWidget(self.separator3, 0, 5)
        self.toolbarHBox.addWidget(self.actionF6_Move, 0, 6)
        self.toolbarHBox.addWidget(self.separator4, 0, 7)
        self.toolbarHBox.addWidget(self.actionF5_NewFolder, 0, 8)
        self.toolbarHBox.addWidget(self.separator5, 0, 9)
        self.toolbarHBox.addWidget(self.actionF8_Delete, 0, 10)
        self.toolbarHBox.addWidget(self.separator6, 0, 11)
        self.toolbarHBox.addWidget(self.actionAlt_F4_Exit, 0, 12)
        self.toolBar_2.addWidget(self.toolbarHBoxWidget)
        self.toolBar_2.setFixedHeight(40)

        self.retranslateUi(MainWindow)
        QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(
            "Total Commander 7.50a - Politechnika Lodzka - Wydzial EEIA")
        self.menuFiles.setTitle("Files")
        self.menuMark.setTitle("Mark")
        self.menuCommands.setTitle("Commands")
        self.menuNet.setTitle("Net")
        self.menuShow.setTitle("Show")
        self.menuConfiguration.setTitle("Configuration")
        self.menuStart.setTitle("Start")
        self.menuHelp.setTitle("Help")
        self.toolBar.setWindowTitle("toolBar")
        self.toolBar_2.setWindowTitle("toolBar_2")
        self.actionIndex.setText("Index")
        self.actionIndex.setIconText("Index")
        self.actionIndex.setShortcut("F1")
        self.actionKeyboard.setText("Keyboard")
        self.actionRegistration_Info.setText("Registration Info")
        self.actionVisit_Total_CMD_s_website.setText(
            "Visit Totalcmd\'s Web Site")
        self.actionAbout_Total_Commander.setText("About Total Commander...")
        self.actionOption_1.setText("Option 1")
        self.actionOption_2.setText("Option 2")
        self.actionOption_3.setText("Option 1")
        self.actionOption_4.setText("Option 2")
        self.actionOption_5.setText("Option 1")
        self.actionOption_6.setText("Option 2")
        self.actionOption_7.setText("Option 1")
        self.actionOption_8.setText("Option 2")
        self.actionOption_9.setText("Option 1")
        self.actionOption_10.setText("Option 2")
        self.actionOption_11.setText("Option 1")
        self.actionOption_12.setText("Option 2")
        self.actionOption_13.setText("Option 1")
        self.actionOption_14.setText("Option 2")
        self.Refresh.setText("Refresh")
        self.action_view1.setText("View 1")
        self.action_view2.setText("View 2")
        self.actionF3_View.setText("F3 View")
        self.actionF3_View.setShortcut("F3")
        self.actionF4_Edit.setText("F4 Edit")
        self.actionF4_Edit.setShortcut("F4")
        self.actionF5_Copy.setText("F5 Copy")
        self.actionF5_Copy.setShortcut("F5")
        self.actionF6_Move.setText("F6 Move")
        self.actionF6_Move.setShortcut("F6")
        self.actionF5_NewFolder.setText("F7 NewFolder")
        self.actionF5_NewFolder.setShortcut("F7")
        self.actionF8_Delete.setText("F8 Delete")
        self.actionF8_Delete.setShortcut("F8")
        self.actionAlt_F4_Exit.setText("Alt+F4 Exit")
        self.actionAlt_F4_Exit.setShortcut("Ctrl+Alt+F4")
Пример #9
0
class LibraryManagerPanel(QDockWidget):
    def __init__(self, task_executor):
        super().__init__()
        self.lib_selector = QComboBox()
        self.libraries = self._setup_libraries(self.lib_selector)
        self.lib_selector.currentIndexChanged.connect(self._lib_changed)
        self.info_ico_lbl = QLabel("ico")
        self.info_lbl = QLabel("Library Information")
        self.info_lbl.setStyleSheet('font-size: 8pt;')
        self.task_executor = task_executor
        self.group_manager = LibraryGroupingManager()
        self.treeView = QTreeView()

        self.toolbar = QToolBar()
        self.refresh_action = CommonUtils.create_toolbar_action(
            "Refresh/Rescan this Library", QIcon.fromTheme("view-refresh"),
            self.refresh)
        self.edit_action = CommonUtils.create_toolbar_action(
            "Configure this Library", QIcon.fromTheme("preferences-system"),
            self.edit)
        self.delete_action = CommonUtils.create_toolbar_action(
            "Delete this Library", QIcon.fromTheme("list-remove"), self.delete)
        self.add_action = CommonUtils.create_toolbar_action(
            "Add a new Library", QIcon.fromTheme("list-add"), self.add)

        self._init_ui()

    def _init_ui(self):
        h_layout = QHBoxLayout()
        h_layout.addWidget(self.info_ico_lbl)
        h_layout.addWidget(self.lib_selector, 1)
        dummy = QWidget()
        dummy.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.toolbar.addWidget(dummy)
        self.toolbar.addActions([
            self.refresh_action, self.edit_action, self.delete_action,
            self.add_action
        ])
        self.toolbar.setContentsMargins(0, 0, 0, 0)
        self.toolbar.setIconSize(QSize(16, 16))

        layout = QVBoxLayout()
        layout.setContentsMargins(5, 0, 0, 0)
        layout.setMenuBar(self.toolbar)
        layout.addLayout(h_layout)
        layout.addWidget(self.info_lbl)
        layout.addWidget(self.group_manager)
        layout.addWidget(self.treeView, 1)
        container = QWidget()
        container.setLayout(layout)
        self.setFeatures(QDockWidget.DockWidgetFloatable
                         | QDockWidget.DockWidgetClosable)
        self.setWidget(container)
        self.setWindowTitle("Library Manager")
        if self.lib_selector.count() > 0:
            self._lib_changed()

    def refresh(self):
        lib = self.libraries[self.lib_selector.currentText()]
        task_name = f"Refreshing {lib['name']}"
        task_id = f"{time.time()} {task_name}"
        self.task_executor.execute_with_progress(
            LibraryManagement.refresh_library, (lib, task_id),
            (task_name, task_id))

    def delete(self):
        lib_name = self.lib_selector.currentText()
        choice = QMessageBox.question(
            self, "Confirm Library Deletion",
            f"Are you sure you want to delete {lib_name}. "
            f"Please note this action cannot be undone")
        if choice == QMessageBox.Yes:
            lib = self.libraries.pop(lib_name)
            self.task_executor.execute(LibraryManagement.delete_library,
                                       (lib, ))
            self.lib_selector.removeItem(self.lib_selector.currentIndex())

    def edit(self):
        library = self.libraries[self.lib_selector.currentText()]
        dialog = LibraryEditorPanel(library)
        result = dialog.exec()
        if result == QDialog.Accepted:
            self.libraries.pop(self.lib_selector.currentText())
            lib_new = dialog.get_library()
            self.libraries[self.lib_selector.currentText()] = lib_new
            task_name = f"Adding {lib_new['name']}"
            task_id = f"{time.time()} {task_name}"
            self.task_executor.execute(LibraryManagement.update_library,
                                       (lib_new, ))
            self._lib_changed()

    def add(self):
        dialog = LibraryEditorPanel()
        result = dialog.exec()
        if result == QDialog.Accepted:
            lib = dialog.get_library()
            self.libraries[lib['name']] = lib
            self.lib_selector.addItem(lib['name'])
            task_name = f"Adding {lib['name']}"
            task_id = f"{time.time()} {task_name}"
            self.task_executor.execute_with_progress(
                LibraryManagement.create_library,
                (lib['name'], lib['type'], lib['dirs'], None, task_id),
                (task_name, task_id))

    def _lib_changed(self):
        library = self.libraries[self.lib_selector.currentText()]
        info_ico = QIcon.fromTheme(
            LibraryManagement.Library_Types[library["type"]]["icon"])
        self.info_ico_lbl.setPixmap(info_ico.pixmap(28, 28))
        updated = 'Not updated'
        if library.__contains__('updated'):
            updated = f"Updated on {library['updated']}"
        dir_count = len(library['dirs'])
        lib_dir_details = f"{dir_count} directories"
        if dir_count == 1:
            lib_dir_details = f"{dir_count} directory"
        self.info_lbl.setText(
            f"Created on {library['created']}<br>{updated}<br>{lib_dir_details}"
        )
        self.group_manager.update_grouping(
            LibraryManagement.get_group_keys(library["type"]))

    @staticmethod
    def _setup_libraries(lib_combo_selector):
        libraries = LibraryManagement.get_all_libraries()
        lib_lookup = {}
        for lib_type in libraries:
            for lib in libraries[lib_type]:
                key = f"[{lib_type}] {lib['name']}"
                lib_lookup[key] = lib
                lib_combo_selector.addItem(key)
        return lib_lookup
Пример #10
0
class LibraryGroupingManager(CollapsibleWidget):
    def __init__(self):
        super().__init__("Library Tree Group Manager")
        self.source = QListWidget()
        self.target = QListWidget()
        self.action_toolbar = QToolBar()
        self.add_action = CommonUtils.create_toolbar_action(
            "Add selected key to grouping",
            QIcon.fromTheme("media-skip-forward"), self.add)
        self.sub_action = CommonUtils.create_toolbar_action(
            "Remove selected key to grouping",
            QIcon.fromTheme("media-skip-backward"), self.sub)
        self.reset_action = CommonUtils.create_toolbar_action(
            "Reset to library type default",
            QIcon.fromTheme("document-revert"), self.reset)
        self.apply_action = CommonUtils.create_toolbar_action(
            "Apply this view", QIcon.fromTheme("document-save"), self.save)
        self.icon = QIcon.fromTheme("folder")
        self._init_ui()

    def _init_ui(self):
        self.action_toolbar.setOrientation(Qt.Vertical)
        dummy = QWidget()
        dummy.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.action_toolbar.addWidget(dummy)
        self.action_toolbar.addActions([
            self.add_action, self.sub_action, self.reset_action,
            self.apply_action
        ])
        self.action_toolbar.setContentsMargins(0, 0, 0, 0)
        q_size = QSize(16, 16)
        self.action_toolbar.setIconSize(q_size)
        self.source.setIconSize(q_size)
        self.source.setMaximumHeight(120)
        self.target.setIconSize(q_size)
        self.target.setMaximumHeight(120)
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.source)
        layout.addWidget(self.action_toolbar)
        layout.addWidget(self.target)
        self.set_content_layout(layout)

    def update_grouping(self, grouping):
        self.source.clear()
        for key in grouping:
            item = QListWidgetItem(self.icon, key)
            self.source.addItem(item)

    def add(self):
        self.swap_items(self.source, self.target)

    def sub(self):
        self.swap_items(self.target, self.source)

    def save(self):
        pass

    def reset(self):
        pass

    @staticmethod
    def swap_items(source, target):
        list_items = source.selectedItems()
        if not list_items:
            return
        for item in list_items:
            source.takeItem(source.row(item))
            target.addItem(item)
Пример #11
0
class ManageWindow(QWidget):
    __BASE_HEIGHT__ = 400

    def __init__(self,
                 locale_keys: dict,
                 icon_cache: Cache,
                 manager: ApplicationManager,
                 disk_cache: bool,
                 download_icons: bool,
                 screen_size,
                 suggestions: bool,
                 tray_icon=None):
        super(ManageWindow, self).__init__()
        self.locale_keys = locale_keys
        self.manager = manager
        self.tray_icon = tray_icon
        self.working = False  # restrict the number of threaded actions
        self.apps = []
        self.label_flatpak = None
        self.icon_cache = icon_cache
        self.disk_cache = disk_cache
        self.download_icons = download_icons
        self.screen_size = screen_size

        self.icon_flathub = QIcon(resource.get_path('img/logo.svg'))
        self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
        self.setWindowIcon(self.icon_flathub)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.toolbar_top = QToolBar()
        self.toolbar_top.addWidget(self._new_spacer())

        self.label_status = QLabel()
        self.label_status.setText('')
        self.label_status.setStyleSheet("font-weight: bold")
        self.toolbar_top.addWidget(self.label_status)

        self.toolbar_search = QToolBar()
        self.toolbar_search.setStyleSheet("spacing: 0px;")
        self.toolbar_search.setContentsMargins(0, 0, 0, 0)

        label_pre_search = QLabel()
        label_pre_search.setStyleSheet(
            "background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pre_search)

        self.input_search = QLineEdit()
        self.input_search.setMaxLength(20)
        self.input_search.setFrame(False)
        self.input_search.setPlaceholderText(
            self.locale_keys['window_manage.input_search.placeholder'] + "...")
        self.input_search.setToolTip(
            self.locale_keys['window_manage.input_search.tooltip'])
        self.input_search.setStyleSheet(
            "QLineEdit { background-color: white; color: gray; spacing: 0;}")
        self.input_search.returnPressed.connect(self.search)
        self.toolbar_search.addWidget(self.input_search)

        label_pos_search = QLabel()
        label_pos_search.setPixmap(QPixmap(
            resource.get_path('img/search.svg')))
        label_pos_search.setStyleSheet(
            "background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pos_search)

        self.ref_toolbar_search = self.toolbar_top.addWidget(
            self.toolbar_search)
        self.toolbar_top.addWidget(self._new_spacer())
        self.layout.addWidget(self.toolbar_top)

        toolbar = QToolBar()

        self.checkbox_updates = QCheckBox()
        self.checkbox_updates.setText(self.locale_keys['updates'].capitalize())
        self.checkbox_updates.stateChanged.connect(self._handle_updates_filter)
        self.ref_checkbox_updates = toolbar.addWidget(self.checkbox_updates)

        self.checkbox_only_apps = QCheckBox()
        self.checkbox_only_apps.setText(
            self.locale_keys['manage_window.checkbox.only_apps'])
        self.checkbox_only_apps.setChecked(True)
        self.checkbox_only_apps.stateChanged.connect(
            self._handle_filter_only_apps)
        self.ref_checkbox_only_apps = toolbar.addWidget(
            self.checkbox_only_apps)

        self.extra_filters = QWidget()
        self.extra_filters.setLayout(QHBoxLayout())
        toolbar.addWidget(self.extra_filters)

        toolbar.addWidget(self._new_spacer())

        self.bt_refresh = QToolButton()
        self.bt_refresh.setToolTip(
            locale_keys['manage_window.bt.refresh.tooltip'])
        self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg')))
        self.bt_refresh.clicked.connect(
            lambda: self.refresh_apps(keep_console=False))
        toolbar.addWidget(self.bt_refresh)

        self.bt_upgrade = QToolButton()
        self.bt_upgrade.setToolTip(
            locale_keys['manage_window.bt.upgrade.tooltip'])
        self.bt_upgrade.setIcon(
            QIcon(resource.get_path('img/update_green.svg')))
        self.bt_upgrade.setEnabled(False)
        self.bt_upgrade.clicked.connect(self.update_selected)
        self.ref_bt_upgrade = toolbar.addWidget(self.bt_upgrade)

        self.layout.addWidget(toolbar)

        self.table_apps = AppsTable(self,
                                    self.icon_cache,
                                    disk_cache=self.disk_cache,
                                    download_icons=self.download_icons)
        self.table_apps.change_headers_policy()

        self.layout.addWidget(self.table_apps)

        toolbar_console = QToolBar()

        self.checkbox_console = QCheckBox()
        self.checkbox_console.setText(
            self.locale_keys['manage_window.checkbox.show_details'])
        self.checkbox_console.stateChanged.connect(self._handle_console)
        self.checkbox_console.setVisible(False)
        self.ref_checkbox_console = toolbar_console.addWidget(
            self.checkbox_console)

        toolbar_console.addWidget(self._new_spacer())
        self.layout.addWidget(toolbar_console)

        self.textarea_output = QPlainTextEdit(self)
        self.textarea_output.resize(self.table_apps.size())
        self.textarea_output.setStyleSheet("background: black; color: white;")
        self.layout.addWidget(self.textarea_output)
        self.textarea_output.setVisible(False)
        self.textarea_output.setReadOnly(True)

        self.thread_update = UpdateSelectedApps(self.manager)
        self.thread_update.signal_output.connect(self._update_action_output)
        self.thread_update.signal_finished.connect(
            self._finish_update_selected)
        self.thread_update.signal_status.connect(
            self._change_updating_app_status)

        self.thread_refresh = RefreshApps(self.manager)
        self.thread_refresh.signal.connect(self._finish_refresh_apps)

        self.thread_uninstall = UninstallApp(self.manager, self.icon_cache)
        self.thread_uninstall.signal_output.connect(self._update_action_output)
        self.thread_uninstall.signal_finished.connect(self._finish_uninstall)

        self.thread_downgrade = DowngradeApp(self.manager, self.locale_keys)
        self.thread_downgrade.signal_output.connect(self._update_action_output)
        self.thread_downgrade.signal_finished.connect(self._finish_downgrade)

        self.thread_get_info = GetAppInfo(self.manager)
        self.thread_get_info.signal_finished.connect(self._finish_get_info)

        self.thread_get_history = GetAppHistory(self.manager, self.locale_keys)
        self.thread_get_history.signal_finished.connect(
            self._finish_get_history)

        self.thread_search = SearchApps(self.manager)
        self.thread_search.signal_finished.connect(self._finish_search)

        self.thread_install = InstallApp(manager=self.manager,
                                         disk_cache=self.disk_cache,
                                         icon_cache=self.icon_cache,
                                         locale_keys=self.locale_keys)
        self.thread_install.signal_output.connect(self._update_action_output)
        self.thread_install.signal_finished.connect(self._finish_install)

        self.thread_animate_progress = AnimateProgress()
        self.thread_animate_progress.signal_change.connect(
            self._update_progress)

        self.thread_verify_models = VerifyModels()
        self.thread_verify_models.signal_updates.connect(
            self._notify_model_data_change)

        self.thread_refresh_app = RefreshApp(manager=self.manager)
        self.thread_refresh_app.signal_finished.connect(self._finish_refresh)
        self.thread_refresh_app.signal_output.connect(
            self._update_action_output)

        self.thread_suggestions = FindSuggestions(man=self.manager)
        self.thread_suggestions.signal_finished.connect(self._finish_search)

        self.toolbar_bottom = QToolBar()
        self.toolbar_bottom.setIconSize(QSize(16, 16))

        self.label_updates = QLabel()
        self.ref_label_updates = self.toolbar_bottom.addWidget(
            self.label_updates)

        self.toolbar_bottom.addWidget(self._new_spacer())

        self.progress_bar = QProgressBar()
        self.progress_bar.setTextVisible(False)
        self.ref_progress_bar = self.toolbar_bottom.addWidget(
            self.progress_bar)

        self.toolbar_bottom.addWidget(self._new_spacer())

        bt_about = QToolButton()
        bt_about.setStyleSheet('QToolButton { border: 0px; }')
        bt_about.setIcon(QIcon(resource.get_path('img/about.svg')))
        bt_about.clicked.connect(self._show_about)
        bt_about.setToolTip(self.locale_keys['manage_window.bt_about.tooltip'])
        self.ref_bt_about = self.toolbar_bottom.addWidget(bt_about)

        self.layout.addWidget(self.toolbar_bottom)

        self.centralize()

        self.filter_only_apps = True
        self.filter_types = set()
        self.filter_updates = False
        self._maximized = False

        self.dialog_about = None
        self.first_refresh = suggestions

        self.thread_warnings = ListWarnings(man=manager,
                                            locale_keys=locale_keys)
        self.thread_warnings.signal_warnings.connect(self._show_warnings)

    def _show_warnings(self, warnings: List[str]):
        if warnings:
            for warning in warnings:
                dialog.show_warning(
                    title=self.locale_keys['warning'].capitalize(),
                    body=warning)

    def show(self):
        super(ManageWindow, self).show()
        if not self.thread_warnings.isFinished():
            self.thread_warnings.start()

    def _show_about(self):
        if self.dialog_about is None:
            self.dialog_about = AboutDialog(self.locale_keys)

        self.dialog_about.show()

    def _handle_updates_filter(self, status: int):
        self.filter_updates = status == 2
        self.apply_filters()

    def _handle_filter_only_apps(self, status: int):
        self.filter_only_apps = status == 2
        self.apply_filters()

    def _handle_type_filter(self, status: int, app_type: str):

        if status == 2:
            self.filter_types.add(app_type)
        elif app_type in self.filter_types:
            self.filter_types.remove(app_type)

        self.apply_filters()

    def _notify_model_data_change(self):
        self.table_apps.fill_async_data()

    def _new_spacer(self):
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        return spacer

    def changeEvent(self, e: QEvent):
        if isinstance(e, QWindowStateChangeEvent):
            self._maximized = self.isMaximized()
            policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents
            self.table_apps.change_headers_policy(policy)

    def closeEvent(self, event):

        if self.tray_icon:
            event.ignore()
            self.hide()
            self._handle_console_option(False)

    def _handle_console(self, checked: bool):

        if checked:
            self.textarea_output.show()
        else:
            self.textarea_output.hide()

    def _handle_console_option(self, enable: bool):

        if enable:
            self.textarea_output.clear()

        self.ref_checkbox_console.setVisible(enable)
        self.checkbox_console.setChecked(False)
        self.textarea_output.hide()

    def refresh_apps(self, keep_console: bool = True):
        self.filter_types.clear()
        self.input_search.clear()

        if not keep_console:
            self._handle_console_option(False)

        self.ref_checkbox_updates.setVisible(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self._begin_action(self.locale_keys['manage_window.status.refreshing'],
                           clear_filters=True)
        self.thread_refresh.start()

    def _finish_refresh_apps(self, apps: List[Application]):
        self.finish_action()
        self.ref_checkbox_only_apps.setVisible(True)
        self.ref_bt_upgrade.setVisible(True)
        self.update_apps(apps)
        self.first_refresh = False

    def uninstall_app(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root('uninstall', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.uninstalling'],
            app.model.base_data.name))

        self.thread_uninstall.app = app
        self.thread_uninstall.root_password = pwd
        self.thread_uninstall.start()

    def refresh(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root('refresh', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.refreshing_app'],
            app.model.base_data.name))

        self.thread_refresh_app.app = app
        self.thread_refresh_app.root_password = pwd
        self.thread_refresh_app.start()

    def _finish_uninstall(self, app: ApplicationView):
        self.finish_action()

        if app:
            if self._can_notify_user():
                system.notify_user('{} ({}) {}'.format(
                    app.model.base_data.name, app.model.get_type(),
                    self.locale_keys['uninstalled']))

            self.refresh_apps()
        else:
            if self._can_notify_user():
                system.notify_user('{}: {}'.format(
                    app.model.base_data.name,
                    self.locale_keys['notification.uninstall.failed']))

            self.checkbox_console.setChecked(True)

    def _can_notify_user(self):
        return self.isHidden() or self.isMinimized()

    def _finish_downgrade(self, success: bool):
        self.finish_action()

        if success:
            if self._can_notify_user():
                app = self.table_apps.get_selected_app()
                system.notify_user('{} ({}) {}'.format(
                    app.model.base_data.name, app.model.get_type(),
                    self.locale_keys['downgraded']))

            self.refresh_apps()

            if self.tray_icon:
                self.tray_icon.verify_updates(notify_user=False)
        else:
            if self._can_notify_user():
                system.notify_user(
                    self.locale_keys['notification.downgrade.failed'])

            self.checkbox_console.setChecked(True)

    def _finish_refresh(self, success: bool):
        self.finish_action()

        if success:
            self.refresh_apps()
        else:
            self.checkbox_console.setChecked(True)

    def _change_updating_app_status(self, app_name: str):
        self.label_status.setText('{} {}...'.format(
            self.locale_keys['manage_window.status.upgrading'], app_name))

    def apply_filters(self):
        if self.apps:
            visible_apps = len(self.apps)
            for idx, app_v in enumerate(self.apps):
                hidden = self.filter_only_apps and app_v.model.is_library()

                if not hidden and self.filter_types is not None:
                    hidden = app_v.model.get_type() not in self.filter_types

                if not hidden and self.filter_updates:
                    hidden = not app_v.model.update

                self.table_apps.setRowHidden(idx, hidden)
                app_v.visible = not hidden
                visible_apps -= 1 if hidden else 0

            self.change_update_state(change_filters=False)

            if not self._maximized:
                self.table_apps.change_headers_policy(QHeaderView.Stretch)
                self.table_apps.change_headers_policy()
                self.resize_and_center(accept_lower_width=visible_apps > 0)

    def change_update_state(self, change_filters: bool = True):
        enable_bt_update = False
        app_updates, library_updates, not_installed = 0, 0, 0

        for app_v in self.apps:
            if app_v.model.update:
                if app_v.model.runtime:
                    library_updates += 1
                else:
                    app_updates += 1

            if not app_v.model.installed:
                not_installed += 1

        for app_v in self.apps:
            if not_installed == 0 and app_v.visible and app_v.update_checked:
                enable_bt_update = True
                break

        self.bt_upgrade.setEnabled(enable_bt_update)

        total_updates = app_updates + library_updates

        if total_updates > 0:
            self.label_updates.setPixmap(
                QPixmap(resource.get_path('img/exclamation.svg')).scaled(
                    16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation))
            self.label_updates.setToolTip('{}: {} ( {} {} | {} {} )'.format(
                self.locale_keys['manage_window.label.updates'], total_updates,
                app_updates,
                self.locale_keys['manage_window.checkbox.only_apps'].lower(),
                library_updates, self.locale_keys['others'].lower()))

            if not_installed == 0:
                if not self.ref_checkbox_updates.isVisible():
                    self.ref_checkbox_updates.setVisible(True)

                if change_filters and not self.checkbox_updates.isChecked():
                    self.checkbox_updates.setChecked(True)

            if change_filters and library_updates > 0 and self.checkbox_only_apps.isChecked(
            ):
                self.checkbox_only_apps.setChecked(False)
        else:
            self.checkbox_updates.setChecked(False)
            self.ref_checkbox_updates.setVisible(False)
            self.label_updates.setPixmap(QPixmap())

    def centralize(self):
        geo = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(
            QApplication.desktop().cursor().pos())
        center_point = QApplication.desktop().screenGeometry(screen).center()
        geo.moveCenter(center_point)
        self.move(geo.topLeft())

    def update_apps(self,
                    apps: List[Application],
                    update_check_enabled: bool = True):
        self.apps = []

        napps = 0  # number of apps (not libraries)
        available_types = set()

        if apps:
            for app in apps:
                app_model = ApplicationView(
                    model=app,
                    visible=(not app.is_library())
                    or not self.checkbox_only_apps.isChecked())
                available_types.add(app.get_type())
                napps += 1 if not app.is_library() else 0
                self.apps.append(app_model)

        if napps == 0:

            if self.first_refresh:
                self._begin_search('')
                self.thread_suggestions.start()
                return
            else:
                self.checkbox_only_apps.setChecked(False)
                self.checkbox_only_apps.setCheckable(False)
        else:
            self.checkbox_only_apps.setCheckable(True)
            self.checkbox_only_apps.setChecked(True)

        self._update_type_filters(available_types)
        self.table_apps.update_apps(self.apps,
                                    update_check_enabled=update_check_enabled)
        self.apply_filters()
        self.change_update_state()
        self.resize_and_center()

        self.thread_verify_models.apps = self.apps
        self.thread_verify_models.start()

    def _update_type_filters(self, available_types: Set[str]):

        self.filter_types = available_types

        filters_layout = self.extra_filters.layout()
        for i in reversed(range(filters_layout.count())):
            filters_layout.itemAt(i).widget().setParent(None)

        if available_types:
            for app_type in sorted(list(available_types)):
                checkbox_app_type = QCheckBox()
                checkbox_app_type.setChecked(True)
                checkbox_app_type.setText(app_type.capitalize())

                def handle_click(status: int, filter_type: str = app_type):
                    self._handle_type_filter(status, filter_type)

                checkbox_app_type.stateChanged.connect(handle_click)
                filters_layout.addWidget(checkbox_app_type)

    def resize_and_center(self, accept_lower_width: bool = True):
        new_width = reduce(operator.add, [
            self.table_apps.columnWidth(i)
            for i in range(len(self.table_apps.column_names))
        ]) * 1.05

        if accept_lower_width or new_width > self.width():
            self.resize(new_width, self.height())

        self.centralize()

    def update_selected(self):
        if self.apps:

            to_update = [
                app_v for app_v in self.apps
                if app_v.visible and app_v.update_checked
            ]

            if to_update:
                if dialog.ask_confirmation(
                        title=self.
                        locale_keys['manage_window.upgrade_all.popup.title'],
                        body=self.
                        locale_keys['manage_window.upgrade_all.popup.body'],
                        locale_keys=self.locale_keys):
                    self._handle_console_option(True)

                    self._begin_action(
                        self.locale_keys['manage_window.status.upgrading'])
                    self.thread_update.apps_to_update = to_update
                    self.thread_update.start()

    def _finish_update_selected(self, success: bool, updated: int):
        self.finish_action()

        if success:
            if self._can_notify_user():
                system.notify_user('{} {}'.format(
                    updated,
                    self.locale_keys['notification.update_selected.success']))

            self.refresh_apps()

            if self.tray_icon:
                self.tray_icon.verify_updates()
        else:
            if self._can_notify_user():
                system.notify_user(
                    self.locale_keys['notification.update_selected.failed'])

            self.bt_upgrade.setEnabled(True)
            self.checkbox_console.setChecked(True)

    def _update_action_output(self, output: str):
        self.textarea_output.appendPlainText(output)

    def _begin_action(self,
                      action_label: str,
                      keep_search: bool = False,
                      clear_filters: bool = False):
        self.ref_bt_about.setVisible(False)
        self.ref_label_updates.setVisible(False)
        self.thread_animate_progress.stop = False
        self.thread_animate_progress.start()
        self.ref_progress_bar.setVisible(True)

        self.label_status.setText(action_label + "...")
        self.bt_upgrade.setEnabled(False)
        self.bt_refresh.setEnabled(False)
        self.checkbox_only_apps.setEnabled(False)
        self.table_apps.setEnabled(False)
        self.checkbox_updates.setEnabled(False)

        if keep_search:
            self.ref_toolbar_search.setVisible(True)
        else:
            self.ref_toolbar_search.setVisible(False)

        if clear_filters:
            self._update_type_filters(set())
        else:
            self.extra_filters.setEnabled(False)

    def finish_action(self):
        self.ref_bt_about.setVisible(True)
        self.ref_progress_bar.setVisible(False)
        self.ref_label_updates.setVisible(True)
        self.thread_animate_progress.stop = True
        self.progress_bar.setValue(0)
        self.bt_refresh.setEnabled(True)
        self.checkbox_only_apps.setEnabled(True)
        self.table_apps.setEnabled(True)
        self.input_search.setEnabled(True)
        self.label_status.setText('')
        self.ref_toolbar_search.setVisible(True)
        self.ref_toolbar_search.setEnabled(True)
        self.extra_filters.setEnabled(True)
        self.checkbox_updates.setEnabled(True)

    def downgrade_app(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root(
            'downgrade',
            self.table_apps.get_selected_app().model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.downgrading'],
            app.model.base_data.name))

        self.thread_downgrade.app = app
        self.thread_downgrade.root_password = pwd
        self.thread_downgrade.start()

    def get_app_info(self, app: dict):
        self._handle_console_option(False)
        self._begin_action(self.locale_keys['manage_window.status.info'])

        self.thread_get_info.app = app
        self.thread_get_info.start()

    def get_app_history(self, app: dict):
        self._handle_console_option(False)
        self._begin_action(self.locale_keys['manage_window.status.history'])

        self.thread_get_history.app = app
        self.thread_get_history.start()

    def _finish_get_info(self, app_info: dict):
        self.finish_action()
        self.change_update_state(change_filters=False)
        dialog_info = InfoDialog(app=app_info,
                                 icon_cache=self.icon_cache,
                                 locale_keys=self.locale_keys,
                                 screen_size=self.screen_size)
        dialog_info.exec_()

    def _finish_get_history(self, app: dict):
        self.finish_action()
        self.change_update_state(change_filters=False)

        if app.get('error'):
            self._handle_console_option(True)
            self.textarea_output.appendPlainText(app['error'])
            self.checkbox_console.setChecked(True)
        else:
            dialog_history = HistoryDialog(app, self.icon_cache,
                                           self.locale_keys)
            dialog_history.exec_()

    def _begin_search(self, word):
        self._handle_console_option(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self.ref_checkbox_updates.setVisible(False)
        self.filter_updates = False
        self._begin_action('{}{}'.format(
            self.locale_keys['manage_window.status.searching'],
            '"{}"'.format(word) if word else ''),
                           clear_filters=True)

    def search(self):

        word = self.input_search.text().strip()

        if word:
            self._begin_search(word)
            self.thread_search.word = word
            self.thread_search.start()

    def _finish_search(self, apps_found: List[Application]):
        self.finish_action()
        self.ref_bt_upgrade.setVisible(False)
        self.update_apps(apps_found, update_check_enabled=False)

    def install_app(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root('install', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.installing'],
            app.model.base_data.name))

        self.thread_install.app = app
        self.thread_install.root_password = pwd
        self.thread_install.start()

    def _finish_install(self, app: ApplicationView):
        self.input_search.setText('')
        self.finish_action()

        if app:
            if self._can_notify_user():
                system.notify_user('{} ({}) {}'.format(
                    app.model.base_data.name, app.model.get_type(),
                    self.locale_keys['installed']))

            self.refresh_apps()
        else:
            if self._can_notify_user():
                system.notify_user('{}: {}'.format(
                    app.model.base_data.name,
                    self.locale_keys['notification.install.failed']))

            self.checkbox_console.setChecked(True)

    def _update_progress(self, value: int):
        self.progress_bar.setValue(value)
Пример #12
0
class Ui_MainWindow(object):
    def __init__(self):
        super(Ui_MainWindow, self).__init__()
        setting_file = open('../settings/settings.json', 'r')
        self.json_settings = json.load(setting_file)
        setting_file.close()

    def setupUi(self, MainWindow):
        self.mainwindow = MainWindow
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1600, 900)
        MainWindow.setWindowIcon(QIcon('../images/logo.png'))
        MainWindow.setWindowTitle('网络文章下载与存储系统')
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
        self.centralwidget.setSizePolicy(sizePolicy)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")

        '''菜单栏部分'''
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 992, 26))
        self.menubar.setObjectName("menubar")
        self.menubar.setStyleSheet(QSS.QMenuBar)
        self.localfile = QtWidgets.QMenu(self.menubar)
        self.localfile.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.localfile.setToolTipDuration(5)
        self.localfile.setToolTipsVisible(False)
        self.localfile.setObjectName("localfile")
        self.localfile_action = QAction(self.mainwindow)
        self.localfile_action.setCheckable(False)
        self.localfile_action.setObjectName('localFileAction')
        self.localfile_action.triggered.connect(self.openLocalFile)
        self.localfile_action.setText('本地文件')

        self.usagespace = QtWidgets.QMenu(self.menubar)
        self.usagespace.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.usagespace.setToolTipsVisible(True)
        self.usagespace.setObjectName("usagespace")
        self.usagespace_action = QAction(self.mainwindow)
        self.usagespace_action.setCheckable(False)
        self.usagespace_action.setObjectName('usageSpaceAction')
        self.usagespace_action.triggered.connect(self.spaceUsage)
        self.usagespace_action.setText('占用空间')

        self.settings = QtWidgets.QMenu(self.menubar)
        self.settings.setToolTipsVisible(True)
        self.settings.setObjectName("settings")
        self.settings_action = QAction(self.mainwindow)
        self.settings_action.setCheckable(False)
        self.settings_action.setObjectName('settingAction')
        self.settings_action.triggered.connect(self.showEnvWindow)
        self.settings_action.setText('存储设置')

        self.about = QtWidgets.QMenu(self.menubar)
        self.about.setToolTipsVisible(True)
        self.about.setObjectName("about")
        self.about_action = QAction(self.mainwindow)
        self.about_action.setCheckable(False)
        self.about_action.setObjectName('aboutAction')
        self.about_action.triggered.connect(self.showAboutWindow)
        self.about_action.setText('关于')

        self.menubar.addAction(self.localfile_action)
        self.menubar.addAction(self.usagespace_action)
        self.menubar.addAction(self.settings_action)
        self.menubar.addAction(self.about_action)

        MainWindow.setMenuBar(self.menubar)

        '''工具栏部分'''
        self.toolBar = QToolBar(MainWindow)
        self.toolBar.setObjectName("toolBar")
        self.toolBar.setContentsMargins(QMargins(20, 0, 20, 0))
        MainWindow.addToolBar(Qt.TopToolBarArea, self.toolBar)
        self.refresh_button = QPushButton()
        self.refresh_button.setText('刷新')
        refresh_icon = QIcon()
        refresh_icon.addPixmap(QPixmap('../images/refresh.png'), QIcon.Normal, QIcon.Off)
        self.refresh_button.setIcon(refresh_icon)
        self.refresh_button.setIconSize(QSize(25, 25))
        self.refresh_button.setStyleSheet(QSS.ButtonStyle)
        self.refresh_button.clicked.connect(self.refresh_treewidget)

        self.search_edit = QLineEdit()
        self.search_edit.setMaximumWidth(200)
        self.search_edit.setFixedHeight(30)
        self.search_edit.setPlaceholderText('输入搜索内容')
        self.search_edit.setStyleSheet(QSS.LineEdit)
        self.toolBar.addWidget(self.search_edit)

        self.search_button = QPushButton()
        self.search_button.setText('搜索')
        search_icon = QIcon()
        search_icon.addPixmap(QPixmap('../images/search.png'), QIcon.Normal, QIcon.Off)
        self.search_button.setIcon(search_icon)
        self.search_button.setIconSize(QSize(25, 25))
        self.search_button.setStyleSheet(QSS.ButtonStyle)
        self.search_button.clicked.connect(self._search)

        '''放大按钮'''
        self.zoom_in_button = QPushButton(self.mainwindow)
        self.zoom_in_button.setText('放大')
        zoom_in_icon = QIcon()
        zoom_in_icon.addPixmap(QPixmap('../images/zoom_in.png'), QIcon.Normal, QIcon.Off)
        self.zoom_in_button.setIcon(zoom_in_icon)
        self.zoom_in_button.setIconSize(QSize(25, 25))
        self.zoom_in_button.setStyleSheet(QSS.ButtonStyle)

        self.sp = QDoubleSpinBox(self.mainwindow)
        self.sp.setRange(1,4.95)
        self.sp.setValue(1.2)
        self.sp.setSingleStep(0.1)
        self.sp.setMinimum(1)
        self.sp.setMinimumHeight(35)
        self.sp.setMinimumWidth(60)
        self.sp.valueChanged.connect(self.sp_value_change)


        '''缩小按钮'''
        self.zoom_out_button = QPushButton(self.mainwindow)
        self.zoom_out_button.setText('缩小')
        zoom_out_icon = QIcon()
        zoom_out_icon.addPixmap(QPixmap('../images/zoom_out.png'), QIcon.Normal, QIcon.Off)
        self.zoom_out_button.setIcon(zoom_out_icon)
        self.zoom_out_button.setIconSize(QSize(25, 25))
        self.zoom_out_button.setStyleSheet(QSS.ButtonStyle)

        '''弹出页面url按钮'''
        self.url_button = QPushButton()
        self.url_button.setText('URL')
        url_icon = QIcon()
        url_icon.addPixmap(QPixmap('../images/url.png'), QIcon.Normal, QIcon.Off)
        self.url_button.setIcon(url_icon)
        self.url_button.setIconSize(QSize(25, 25))
        self.url_button.setStyleSheet(QSS.ButtonStyle)

        self.back_button = QPushButton()
        self.back_button.setText('上级')
        back_icon = QIcon()
        back_icon.addPixmap(QPixmap('../images/back.png'), QIcon.Normal, QIcon.Off)
        self.back_button.setIcon(back_icon)
        self.back_button.setIconSize(QSize(25, 25))
        self.back_button.setStyleSheet(QSS.ButtonStyle)
        self.back_button.clicked.connect(self.file_back)

        self.toolBar.addWidget(self.search_button)
        self.toolBar.addWidget(self.refresh_button)
        self.toolBar.addWidget(self.zoom_in_button)
        self.toolBar.addWidget(self.sp)
        self.toolBar.addWidget(self.zoom_out_button)
        self.toolBar.addWidget(self.url_button)
        self.toolBar.addWidget(self.back_button)


        '''可调节伸缩区域'''
        self.splitter = QtWidgets.QSplitter(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth())
        self.splitter.setSizePolicy(sizePolicy)
        self.splitter.setOrientation(QtCore.Qt.Horizontal)
        self.splitter.setObjectName("splitter")

        '''左侧'''
        self.treeWidget_2 = QtWidgets.QTreeWidget(self.splitter)
        self.treeWidget_2.setStyleSheet(QSS.QTreeView)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.treeWidget_2.sizePolicy().hasHeightForWidth())
        self.treeWidget_2.setSizePolicy(sizePolicy)
        self.treeWidget_2.setMinimumSize(QtCore.QSize(300, 0))
        self.treeWidget_2.setObjectName("treeWidget_2")
        self.treeWidget_2.setHeaderLabels(['文件名', '文件路径'])
        self.treeWidget_2.setHeaderHidden(True)
        self.treeWidget_2.setColumnHidden(1, True)
        self.root = QTreeWidgetItem(self.treeWidget_2)
        self.json_settings['FILE_LOCATION'] = self.json_settings['FILE_LOCATION'].replace('\\', '/')
        self.root.setText(0, self.json_settings['FILE_LOCATION'].split('/')[-1])
        self.root.setText(1, self.json_settings['FILE_LOCATION'])
        self.root.setIcon(0, QIcon('../images/folder.png'))

        self.file_thread = FileListThread(self.root, self.json_settings['FILE_LOCATION'])
        self.file_thread.start()
        self.file_thread.sinOut.connect(self.getTreeRoot)

        '''右侧文件管理'''
        self.tabWidget = QtWidgets.QTabWidget(self.splitter)
        self.tabWidget.setStyleSheet(QSS.QTabWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth())
        self.tabWidget.setSizePolicy(sizePolicy)
        self.tabWidget.setObjectName("tabWidget")
        self.tab_3 = QtWidgets.QWidget()
        self.tab_3.setObjectName("tab_3")
        self.table_widget = self.get_file_list(self.json_settings['FILE_LOCATION'])
        self.paths = [self.json_settings['FILE_LOCATION'],]
        self.tab3_layout = QVBoxLayout(self.tab_3)
        self.tab3_layout.addWidget(self.tablewidget)
        self.tabWidget.addTab(self.tab_3, "文件管理")

        '''右侧文件预览'''
        self.tab_4 = QtWidgets.QWidget()
        self.tab_4.setStyleSheet('''
            background:transparent;
        ''')
        self.tab_4.setObjectName("tab_4")
        self.tabWidget.addTab(self.tab_4, "文件预览")
        self.browser = Browser(self.tab_4)
        self.zoom_in_button.clicked.connect(self.zoom_in_func)  # 放大与缩小按钮出发事件设置
        self.zoom_out_button.clicked.connect(self.zoom_out_func)
        self.sp.setValue(self.browser.zoomFactor())
        self.tab_layout = QVBoxLayout(self.tab_4)
        self.browser.setMinimumSize(QSize(400, 200))
        self.tab_layout.addWidget(self.browser)
        self.browser.setHtml('''
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <style type="text/css">
                    * {
                        padding: 0;
                        margin: 0;
                    }
            
                    div {
                        padding: 4px 48px;
                    }
            
                    body {
                        background: #fff;
                        font-family: "微软雅黑";
                        color: #333;
                    }
            
                    h1 {
                        font-size: 100px;
                        font-weight: normal;
                        margin-bottom: 12px;
                    }
            
                    p {
                        line-height: 1.8em;
                        font-size: 36px
                    }
                </style>
            </head>
            <div style="padding: 24px 48px;">
                <h1>:)</h1>
                <p><b>欢迎使用!</b></p>
                <br>
                <hr>
                <br>
                <ul>
                    <li><b>图片 JPG格式</b></li>
                    <li><b>网页文件 HTML格式</b></li>
                    <li><b>记事本 TXT格式</b></l>
                </ul>
            </div>
            
            </html>
        ''')
        self.browser.show()
        self.tabWidget.setCurrentIndex(1)

        self.horizontalLayout.addWidget(self.splitter)
        self.horizontalLayout_2.addLayout(self.horizontalLayout)
        MainWindow.setCentralWidget(self.centralwidget)

        '''状态栏部分'''
        self.statusBar = QtWidgets.QStatusBar(MainWindow)
        self.statusBar.setObjectName("statusBar")
        self.statusBar.showMessage('程序就绪!',msecs=5000)
        self.statusBar.setStyleSheet(QSS.QStatusBar)
        MainWindow.setStatusBar(self.statusBar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate



    def openLocalFile(self):
        '''
        菜单栏打开本地文件方法
        :return:
        '''
        local_path = self.json_settings['FILE_LOCATION']
        if '/' in local_path:
            local_path = local_path.replace('/', '\\')
        os.system("explorer.exe %s" % os.path.dirname(local_path))

    def spaceUsage(self):
        '''
        菜单栏占用空间方法
        :return:
        '''
        local_path = self.json_settings['FILE_LOCATION']
        if '/' in local_path:
            local_path = local_path.replace('/', '\\')
        if os.path.isdir(local_path):
            # 文件夹大小需要遍历文件夹内容 然后求和
            file_size = self.getdirsize(local_path)
        else:
            file_size = os.path.getsize(local_path)
        file_size = self.approximateSize(file_size)
        QMessageBox.about(self.mainwindow, '占用空间', local_path + ':' + file_size)

    def showEnvWindow(self):
        pass
        env_dialog = EnvDialog()
        env_window = QMainWindow(MainWindow)
        env_dialog.setupUi(env_window)
        env_window.setWindowModality(Qt.ApplicationModal)
        env_window.show()


    def showAboutWindow(self):
        '''
        打开关于窗口方法
        :return: None
        '''
        aboutdialog = AboutDialog()
        aboutdialog_window = QMainWindow(MainWindow)
        aboutdialog.setupUi(aboutdialog_window)
        aboutdialog_window.setWindowModality(Qt.ApplicationModal)
        aboutdialog_window.show()



    '''文件存储空间单位转换方法'''

    def approximateSize(self, size, a_kilobyte_is_1024_bytes=True):
        '''Convert a file size to human-readable form.

        Keyword arguments:
        size -- file size in bytes
        a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                    if False, use multiples of 1000

        Returns: string

        '''
        SUFFIXES = {1024: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
                    1000: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

        if size < 0:
            raise ValueError('number must be non-negative')
        multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
        for suffix in SUFFIXES[multiple]:
            size /= multiple
            if size < multiple:
                return '{0:.1f} {1}'.format(size, suffix)

    def browser_finish(self):
        self.statusBar.showMessage('加载成功',5000)


    '''搜索按钮点击事件函数'''
    def _search(self):
        self.search_name = self.search_edit.text()
        # print(self.search_name)
        if not self.search_name:
            pass
        else:
            self.search_res = [] # 搜索结果
            self.recursion_search(self.search_name,self.json_settings['FILE_LOCATION'],self.search_res)
            # print(self.search_res)
            self.tablewidget.clear()
            self.tablewidget.setHorizontalHeaderLabels(['文件名','最后修改日期','文件类型','大小','位置'])
            # self.tablewidget.setColumnHidden(4,False)
            if not self.search_res: #搜索结果为空
                item = QTableWidgetItem('什么都没有搜索到!')
                self.table_widget.setItem(0,0,item)
                self.table_widget.setDisabled(True)
            else:
                self.tablewidget.setDisabled(False)
                self.tablewidget.setDisabled(False)
                for num in range(len(self.search_res)):
                    res = self.search_res[num]
                    res = res.replace('\\','/')
                    if os.path.isdir(res): # 添加文件夹
                        file_name = res.split('/')[-1]
                        file_pos = os.path.dirname(res)
                        file_size = self.approximateSize(self.getdirsize(res))
                        last_update_time = self.time_format(os.stat(res).st_mtime)  # 最后修改时间
                        file_type = '文件夹'
                        file_info = [file_name,last_update_time,file_type,file_size,file_pos]
                    else: # 文件类型的结果
                        file_name = res.split('/')[-1]
                        file_pos = os.path.dirname(res)
                        file_size = self.approximateSize(os.stat(res).st_size)
                        last_update_time = self.time_format(os.stat(res).st_mtime)
                        if file_name.endswith('.html'):
                            file_type = '网页文件(*.html)'
                        elif file_name.endswith('.png'):
                            file_type = '图片文件(*.png)'
                        elif file_name.endswith('.jpg'):
                            file_type = '图片文件(*.jpg)'
                        elif file_name.endswith('.xlsx'):
                            file_type = '数据表格文件(*.xlsx)'
                        elif file_name.endswith('.txt'):
                            file_type = '图片文件(*.txt)'
                        else:
                            file_type = '未知文件类型(*.*)'
                        file_info = [file_name,last_update_time,file_type,file_size,file_pos]

                    for i in range(5):
                        item = QTableWidgetItem(file_info[i])
                        self.table_widget.setItem(num,i,item)
        self.tabWidget.setCurrentIndex(0)


    def recursion_search(self,search_name,search_path,res=[]):
        for filename in os.listdir(search_path):
            temp_path = os.path.join(search_path,filename)
            if os.path.isdir(temp_path):
                if search_name in filename:
                    res.append(temp_path)
                else:
                    self.recursion_search(search_name,temp_path,res)
            else:
                if search_name in filename:
                    res.append(temp_path)
                else:
                    return res

    '''获取文件加载子线程返回的root节点'''
    def refresh_treewidget(self):
        self.updateFileTree()

    def getTreeRoot(self, root):
        '''
        文件树添加item方法
        :param root:
        :return: None
        '''
        self.file_thread.wait()
        self.treeWidget_2.addTopLevelItem(root)
        self.treeWidget_2.doubleClicked.connect(self.fileItemDoubleClick)
        self.treeWidget_2.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeWidget_2.customContextMenuRequested.connect(self.fileTreeCustomRightMenu)


    def fileItemDoubleClick(self):
        '''文件树双击事件'''

        item = self.treeWidget_2.currentItem()
        file_path = item.text(1)
        file_path = file_path.replace('\\', '/')
        # print(file_path)
        if os.path.isdir(file_path):
            self.paths.append(file_path)
            # self.tab3_layout.removeWidget(self.tablewidget)
            self.tablewidget.clear()
            self.get_file_list(file_path,self.tablewidget)
            self.tablewidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
            self.tablewidget.customContextMenuRequested['QPoint'].connect(self.tablewidget_right_menu)
            self.tabWidget.setCurrentIndex(0)
        else:
            if file_path.endswith('.html') or file_path.endswith('.jpg') or file_path.endswith('.png') or file_path.endswith('.txt'):
                file_url = file_path.replace('\\', '/')
                if file_path.endswith('.html'):
                    f = open(file_url,'r',encoding='utf-8')
                    html = f.read()
                    self.browser.stop()
                    self.browser.setHtml(html)
                else:
                    self.browser.stop()
                    # self.browser.load(QUrl('file:///' + file_url))
                    self.browser.load(QUrl('file:///' + file_url))

                self.browser.show()
                self.zoom_in_button.clicked.connect(self.zoom_in_func)  # 放大与缩小按钮出发事件设置
                self.zoom_out_button.clicked.connect(self.zoom_out_func)
                self.sp.setValue(self.browser.zoomFactor())
                filename = file_url.split('/')[-1]
                self.tabWidget.setCurrentIndex(1)
                self.statusBar.showMessage(f'预览文件 -> {filename} ')
            else:
                reply = QMessageBox.information(self.mainwindow,
                                                "调用系统软件",
                                                "软件不支持此类型文件打开!\n是否调用系统程序打开此文件?",
                                                QMessageBox.Yes | QMessageBox.No)
                if reply == 16384:
                    os.startfile(file_path)


    def fileTreeCustomRightMenu(self, pos):
        '''文件树右键菜单'''

        item = self.treeWidget_2.currentItem()
        file_path = item.text(1)
        menu = QMenu(self.treeWidget_2)
        delete = menu.addAction('删除')
        copy = menu.addAction('复制')
        paste = menu.addAction('粘贴')
        openLocalFile = menu.addAction('浏览本地文件')
        file_roperty = menu.addAction("属性")
        action = menu.exec_(self.treeWidget_2.mapToGlobal(pos))
        if action == delete:
            reply = QMessageBox.warning(self.mainwindow, '删除确认', '确认删除吗?', QMessageBox.Yes | QMessageBox.No,
                                        QMessageBox.No)
            if os.path.isdir(file_path) and reply == 16384:
                # print('delete dir')
                shutil.rmtree(file_path)
                self.statusBar.showMessage(f'删除 {file_path} 成功!')
                if file_path == self.paths[-1]:
                    # 删除的是当前文件管理显示的文件夹 需要推倒上一级
                    current_path = self.paths.pop()
                    self.paths.append(os.path.dirname(current_path))
                self.updateFileTree()
            elif not os.path.isdir(file_path) and reply == 16384:
                # print('delete file')
                os.remove(file_path)
                self.statusBar.showMessage("删除 -> %s 成功!" % file_path)
                self.updateFileTree()
                self.statusBar.showMessage(f'删除 {file_path} 成功!')

        elif action == copy:
            try:
                data = QMimeData()
                url = QUrl.fromLocalFile(file_path)
                clipboard = QApplication.clipboard()
                data.setUrls([url])
                clipboard.setMimeData(data)
                self.statusBar.showMessage("已复制 -> %s 到剪切板" % file_path)
            except Exception as e:
                QMessageBox.about(self.mainwindow, '错误', '文件不存在!')
                self.statusBar.showMessage("复制 -> %s  出错,文件不存在!" % file_path)

        elif action == paste:
            data = QApplication.clipboard().mimeData()
            try:
                source_file_url = data.urls()[0].url()
                self.paste_thread = FilePasteThread(source_file_url[8:], file_path)
                self.paste_thread.sinOut.connect(self.filePasteComplete)
                self.paste_thread.start()
            except IndexError as e:
                self.statusBar.showMessage('剪切板为空,不能粘贴')
        elif action == openLocalFile:
            try:
                local_path = file_path.replace('/', '\\')
                os.system("explorer.exe %s" % os.path.dirname(local_path))
            except Exception as e:
                QMessageBox.warning(self.mainwindow, '错误', '打开文件不存在!')

        elif action == file_roperty:
            # print('查看文件属性')
            if os.path.isdir(file_path):
                file_type = '文件夹'
                file_image = '../images/folder_status.png'
                _dir = True
            else:
                _dir = False
                if file_path.endswith('.jpg'):
                    file_type = 'JPG图片文件( *.jpg )'
                    file_image = '../images/jpg_status.png'
                elif file_path.endswith('.html'):
                    file_type = 'HTML页面文件( *.html )'
                    file_image = '../images/html_status.png'
                elif file_path.endswith('.xlsx'):
                    file_type = 'XLSX表格文件( *.xlsx )'
                    file_image = '../images/excel_status.png'
                elif file_path.endswith('.png'):
                    file_type = 'PNG图片文件( *.png )'
                    file_image = '../images/png_status.png'
                elif file_path.endswith('.txt'):
                    file_type = 'TXT图片文件( *.txt )'
                    file_image = '../images/txt_status.png'
                else:
                    file_type = 'Other其他文件类型( *.%s)' % (os.path.splitext(file_path)[1])
                    file_image = '../images/file_status.png'
            if _dir:
                '''文件夹大小去要遍历每个子文件夹与文件累加'''
                file_size = self.getdirsize(file_path)
                # print(file_path)
                statinfo = os.stat(file_path)
            else:
                statinfo = os.stat(file_path)
                file_size = statinfo.st_size
            file_atime = self.time_format(statinfo.st_atime)  # 文件最后访问时间
            file_ctime = self.time_format(statinfo.st_ctime)  # 文件创建时间
            file_mtime = self.time_format(statinfo.st_mtime)  # 文件最后修改时间
            self.file_status_window = FileStatusWindow()
            self.file_status_window.filename = file_path.replace('\\', '/').split('/')[-1]
            self.status_main_window = QMainWindow(MainWindow)
            self.file_status_window.setupUi(self.status_main_window)
            self.file_status_window.lineEdit.setText(self.file_status_window.filename)
            self.file_status_window.label_3.setText(file_type)
            self.file_status_window.label_5.setText(file_path.replace('/', '\\'))
            self.file_status_window.label_9.setText(file_ctime)
            self.file_status_window.label_11.setText(file_mtime)
            self.file_status_window.label_13.setText(file_atime)
            self.file_status_window.label_7.setText(self.approximateSize(file_size))
            self.file_status_window.pushButton.clicked.connect(self.fileStatusUse)  # 应用按钮click出发函数
            self.file_status_window.pushButton_2.clicked.connect(self.fileStatusConfirm)
            self.file_status_window.pushButton_3.clicked.connect(self.fileStatusCancel)
            pix = QPixmap(file_image)
            self.file_status_window.label.setPixmap(pix)
            self.status_main_window.show()

    '''文件粘贴完毕后执行方法'''

    def filePasteComplete(self, msg):
        '''
        文件粘贴成功回调函数
        成功后刷新重构文件树
        :param msg:
        :return: None
        '''
        self.paste_thread.wait()
        self.statusBar.showMessage("粘贴文件 -> %s 成功!" % msg)
        self.updateFileTree()

    '''更新文件树方法'''

    def updateFileTree(self):
        self.treeWidget_2.clear()
        self.root = QTreeWidgetItem(self.treeWidget_2)
        self.root.setText(0, self.json_settings['FILE_LOCATION'].split('\\')[-1])
        self.root.setText(1, self.json_settings['FILE_LOCATION'])
        self.root.setIcon(0, QIcon('../images/folder.png'))

        # self.tab3_layout.removeWidget(self.tablewidget)
        self.tablewidget.clear()
        self.get_file_list(self.paths[-1],self.tablewidget)

        self.file_thread = FileListThread(self.root, self.json_settings['FILE_LOCATION'])
        self.file_thread.start()
        self.file_thread.sinOut.connect(self.getTreeRoot)

    def getdirsize(self, dir_path):
        '''获取文件夹大小方法'''

        size = 0
        for root, dirs, files in os.walk(dir_path):
            size += sum([getsize(join(root, name)) for name in files])
        return size

    def time_format(self, timestamp):
        '''时间格式化方法'''

        time_array = time.localtime(timestamp)
        week = {
            '0': '星期日',
            '1': '星期一',
            '2': '星期二',
            '3': '星期三',
            '4': '星期四',
            '5': '星期五',
            '6': '星期六'
        }
        if time_array.tm_mon < 10:
            tm_mon = '0'+str(time_array.tm_mon)
        else:
            tm_mon = time_array.tm_mon
        if time_array.tm_mday < 10:
            tm_mday = '0' + str(time_array.tm_mday)
        else:
            tm_mday = time_array.tm_mday
        if time_array.tm_hour < 10:
            tm_hour = '0'+ str(time_array.tm_hour)
        else:
            tm_hour = time_array.tm_hour
        if time_array.tm_min < 10:
            tm_min = '0'+ str(time_array.tm_min)
        else:
            tm_min = time_array.tm_min
        if time_array.tm_sec < 10:
            tm_sec = '0'+ str(time_array.tm_sec)
        else:
            tm_sec = time_array.tm_sec

        return f'{time_array.tm_year}年 {tm_mon}月 {tm_mday}日, {week[str(time_array.tm_wday)]}, {tm_hour}:{tm_min}:{tm_sec}'

    def fileStatusConfirm(self):
        self.status_main_window.close()

    def fileStatusUse(self):
        status_filename = self.file_status_window.lineEdit.text()
        if status_filename != self.file_status_window.filename:
            # print('修改文件名')
            old_file_path = self.file_status_window.label_5.text()
            # print(old_file_path)
            new_fila_path = '\\'.join(old_file_path.split('\\')[:-1]) + '\\' + status_filename
            os.rename(old_file_path, new_fila_path)
            self.statusBar.showMessage('重命名文件 -> %s' % new_fila_path)
            self.file_status_window.pushButton.setEnabled(False)
            self.updateFileTree()

    def fileStatusCancel(self):
        self.status_main_window.close()

    def zoom_in_func(self):
        self.browser.setZoomFactor(self.browser.zoomFactor() + 0.3)
        self.sp.setValue(self.browser.zoomFactor())

    def zoom_out_func(self):
        if self.browser.zoomFactor() > 1:
            self.browser.setZoomFactor(self.browser.zoomFactor() - 0.2)
            self.sp.setValue(self.browser.zoomFactor())

    def sp_value_change(self):
        self.browser.setZoomFactor(self.sp.value())

    def get_file_list(self,file_path,tablewidget=None):
        file_list = os.listdir(file_path)
        # print(file_list)
        rows = len(file_list)
        # print(rows)
        if not tablewidget:
            self.tablewidget = QTableWidget()
            self.tablewidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.tablewidget.horizontalHeader().setStretchLastSection(True)# 所有列自动拉伸,充满界面
            self.tablewidget.setRowCount(rows)
            self.tablewidget.setColumnCount(5)
            # self.tablewidget.setColumnHidden(4,True)

            self.tablewidget.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.tablewidget.verticalHeader().setVisible(False)
            self.tablewidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.tablewidget.setShowGrid(False)
            self.tablewidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
            self.tablewidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Interactive)
            self.tablewidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.Interactive)
            self.tablewidget.horizontalHeader().setSectionResizeMode(2, QHeaderView.Interactive)
            self.tablewidget.horizontalHeader().setSectionResizeMode(3, QHeaderView.Interactive)
            self.tablewidget.horizontalHeader().setSectionResizeMode(4, QHeaderView.Interactive)
            self.tablewidget.setFocusPolicy(Qt.NoFocus)  #  去除选中后的虚线框
            self.tablewidget.itemDoubleClicked.connect(self.tablewidget_double_clicked)
            self.tablewidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
            self.tablewidget.customContextMenuRequested['QPoint'].connect(self.tablewidget_right_menu)

            table_header = self.tablewidget.horizontalHeader()
            table_header.setDefaultAlignment(Qt.AlignLeft | Qt.AlignVCenter)
            header_font = table_header.font()
            header_font.setBold(True)
            table_header.setFont(header_font)
            table_header.setStyleSheet('''
                    QHeaderView::section{
                            padding-left: 30px;
                            height:40px;
                            font-size:18px; 
                            background-color: #D1D1D1;
                        }
                    ''')
        self.tablewidget.setRowCount(rows)
        self.tablewidget.setHorizontalHeaderLabels(['文件名', '最后修改日期', '类型', '大小', '位置'])
        for row_num in range(rows): # 行号
            if os.path.isdir(os.path.join(file_path,file_list[row_num])):
                file_type = '文件夹'
                statinfo = os.stat(os.path.join(file_path, file_list[row_num]))
                file_pos = file_path
                file_size = self.approximateSize(self.getdirsize(os.path.join(file_path,file_list[row_num])))
                last_update_time = self.time_format(statinfo.st_mtime)  # 最后修改时间
                file_info = [file_list[row_num],last_update_time,file_type,file_size,file_pos]
            else:
                if file_list[row_num].endswith('.jpg'):
                    file_type = 'JPG图片文件( *.jpg )'
                    file_image = '../images/jpg_status.png'
                elif file_list[row_num].endswith('.html'):
                    file_type = 'HTML页面文件( *.html )'
                    file_image = '../images/html_status.png'
                elif file_list[row_num].endswith('.xlsx'):
                    file_type = 'XLSX表格文件( *.xlsx )'
                    file_image = '../images/excel_status.png'
                elif file_list[row_num].endswith('.png'):
                    file_type = 'PNG图片文件( *.png )'
                    file_image = '../images/png_status.png'
                elif file_list[row_num].endswith('.txt'):
                    file_type = 'txt图片文件( *.txt )'
                    file_image = '../images/txt_status.png'
                else:
                    file_type = 'Other其他文件类型( *.%s)' % (os.path.splitext(file_list[row_num])[1])
                    file_image = '../images/file_status.png'
                statinfo = os.stat(os.path.join(file_path, file_list[row_num]))
                last_update_time = self.time_format(statinfo.st_mtime)  # 最后修改时间
                file_size = self.approximateSize(statinfo.st_size)
                file_info = [file_list[row_num], last_update_time, file_type,file_size,file_path]
                # print(file_info)
            for i in range(5):
                item = QTableWidgetItem(file_info[i])
                self.tablewidget.setItem(row_num,i,item)
            self.tablewidget.setRowHeight(row_num,50)
            self.tablewidget.setStyleSheet('''
                    QTableWidget {
                    background:transparent;
                    }
                    QTableWidget::item {
                        padding: 10px;
                        border: 0px solid red;
                        
                        }
                    QTableWidget::item:selected {
                        color: black;
                        background-color: rgb(102,204,204);
                        }

            ''')
        return self.tablewidget

    def tablewidget_right_menu(self,pos,file_path=None):
        item_row = self.tablewidget.currentRow()
        file_name = self.tablewidget.item(item_row,0).text()
        file_path = self.tablewidget.item(item_row,4).text()
        if not file_path:
            file_path = os.path.join(self.paths[-1],file_name)
            file_path = file_path.replace('\\','/')
        # print(file_path)
        menu = QMenu(self.tablewidget)
        delete = menu.addAction('删除')
        copy = menu.addAction('复制')
        paste = menu.addAction('粘贴')
        openLocalFile = menu.addAction('浏览本地文件')
        file_roperty = menu.addAction("属性")
        action = menu.exec_(self.tablewidget.mapToGlobal(pos))
        if action == delete:
            reply = QMessageBox.warning(self.mainwindow, '删除确认', '确认删除吗?', QMessageBox.Yes | QMessageBox.No,
                                        QMessageBox.No)
            if os.path.isdir(file_path) and reply == 16384:
                # print('delete dir')
                shutil.rmtree(file_path)
                self.statusBar.showMessage(f'删除 {file_path} 成功!')
                self.updateFileTree()
            elif not os.path.isdir(file_path) and reply == 16384:
                # print('delete file')
                os.remove(file_path)
                self.statusBar.showMessage("删除 -> %s 成功!" % file_path)
                # print(self.paths)
                self.updateFileTree()
                self.statusBar.showMessage(f'删除 {file_path} 成功!')

        elif action == copy:
            try:
                data = QMimeData()
                url = QUrl.fromLocalFile(file_path)
                clipboard = QApplication.clipboard()
                data.setUrls([url])
                clipboard.setMimeData(data)
                self.statusBar.showMessage("已复制 -> %s 到剪切板" % file_path)
            except Exception as e:
                QMessageBox.about(self.mainwindow, '错误', '文件不存在!')
                self.statusBar.showMessage("复制 -> %s  出错,文件不存在!" % file_path)

        elif action == paste:
            data = QApplication.clipboard().mimeData()
            source_file_url = data.urls()[0].url()
            self.paste_thread = FilePasteThread(source_file_url[8:], file_path)
            self.paste_thread.sinOut.connect(self.filePasteComplete)
            self.paste_thread.start()


        elif action == openLocalFile:
            try:
                local_path = file_path.replace('/', '\\')
                os.system("explorer.exe %s" % os.path.dirname(local_path))
            except Exception as e:
                QMessageBox.warning(self.mainwindow, '错误', '打开文件不存在!')

        elif action == file_roperty:
            # print('查看文件属性')
            if os.path.isdir(file_path):
                file_type = '文件夹'
                file_image = '../images/folder_status.png'
                _dir = True
            else:
                _dir = False
                if file_path.endswith('.jpg'):
                    file_type = 'JPG图片文件( *.jpg )'
                    file_image = '../images/jpg_status.png'
                elif file_path.endswith('.html'):
                    file_type = 'HTML页面文件( *.html )'
                    file_image = '../images/html_status.png'
                elif file_path.endswith('.xlsx'):
                    file_type = 'XLSX表格文件( *.xlsx )'
                    file_image = '../images/excel_status.png'
                elif file_path.endswith('.png'):
                    file_type = 'PNG表格文件( *.png )'
                    file_image = '../images/png_status.png'
                else:
                    file_type = 'Other其他文件类型( *.%s)' % (os.path.splitext(file_path)[1])
                    file_image = '../images/file_status.png'
            if _dir:
                '''文件夹大小去要遍历每个子文件夹与文件累加'''
                file_size = self.getdirsize(file_path)
                # print(file_path)
                statinfo = os.stat(file_path)
            else:
                statinfo = os.stat(file_path)
                file_size = statinfo.st_size
            file_atime = self.time_format(statinfo.st_atime)  # 文件最后访问时间
            file_ctime = self.time_format(statinfo.st_ctime)  # 文件创建时间
            file_mtime = self.time_format(statinfo.st_mtime)  # 文件最后修改时间
            self.file_status_window = FileStatusWindow()
            self.file_status_window.filename = file_path.replace('\\', '/').split('/')[-1]
            self.status_main_window = QMainWindow(MainWindow)
            self.file_status_window.setupUi(self.status_main_window)
            self.file_status_window.lineEdit.setText(self.file_status_window.filename)
            self.file_status_window.label_3.setText(file_type)
            self.file_status_window.label_5.setText(file_path.replace('/', '\\'))
            self.file_status_window.label_9.setText(file_ctime)
            self.file_status_window.label_11.setText(file_mtime)
            self.file_status_window.label_13.setText(file_atime)
            self.file_status_window.label_7.setText(self.approximateSize(file_size))
            self.file_status_window.pushButton.clicked.connect(self.fileStatusUse)  # 应用按钮click出发函数
            self.file_status_window.pushButton_2.clicked.connect(self.fileStatusConfirm)
            self.file_status_window.pushButton_3.clicked.connect(self.fileStatusCancel)
            pix = QPixmap(file_image)
            self.file_status_window.label.setPixmap(pix)
            self.status_main_window.show()

    def tablewidget_double_clicked(self):
        '''文件管理列表双击事件'''

        row = self.tablewidget.currentRow() # 拿到当前行
        file_path = self.tablewidget.item(row,4).text() # 文件上级路径
        file_name = self.tablewidget.item(row,0).text()  # 文件名
        file_full_name = os.path.join(file_path,file_name) # 文件绝对路径
        if os.path.isdir(file_full_name):
            '''文件夹执行双击打操作'''
            self.paths.append(file_path) # 打开文件夹 记录上级
            self.tablewidget.clear()
            self.get_file_list(file_full_name,self.tablewidget)
        else:
            '''文件打开操作'''
            if file_full_name.endswith('jpg') or file_full_name.endswith('.png') or file_full_name.endswith(
                    'html') or file_full_name.endswith('pdf'):
                file_path = file_full_name.replace('\\', '/')
                if file_path.endswith('.html'):
                    self.browser.stop()
                    f = open(file_path,'r',encoding='utf-8')
                    html = f.read()
                    self.browser.setHtml(html)
                    self.browser.show()
                else:
                    self.browser.stop()
                    self.browser.load(QUrl('file:///' + file_path))
                    self.browser.show()
                self.zoom_in_button.clicked.connect(self.zoom_in_func)  # 放大与缩小按钮出发事件设置
                self.zoom_out_button.clicked.connect(self.zoom_out_func)
                self.sp.setValue(self.browser.zoomFactor())
                filename = file_path.split('/')[-1]
                self.tabWidget.setCurrentIndex(1)
                self.statusBar.showMessage(f'预览文件 -> {filename} ')
                # self.browser.loadFinished.connect(self.browser_finished)
            else:
                reply = QMessageBox.information(self.mainwindow,
                                                "调用系统软件",
                                                "软件不支持此类型文件打开!\n是否调用系统程序打开此文件?",
                                                QMessageBox.Yes | QMessageBox.No)
                if reply == 16384:
                    os.startfile(file_path)


    def file_back(self):
        # print(self.paths)
        self.tablewidget.clear()
        if len(self.paths) == 1:
            file_path = self.paths[-1]
            self.get_file_list(file_path,self.tablewidget)
        else:
            self.paths.pop()
            self.get_file_list(self.paths[-1],self.tablewidget)