示例#1
0
    def init_ui(self):
        glay_lup = QHBoxLayout()
        glay_lup.addWidget(self.btn_fo)
        glay_lup.addWidget(self.btn_pid)
        glay_lup.addWidget(self.btn_uid)
        glay_lup.addWidget(self.btn_snao)

        glay_ldown = QGridLayout()
        glay_ldown.addWidget(QLabel('画廊ID'), 0, 0, 1, 1)
        glay_ldown.addWidget(self.ledit_pid, 0, 1, 1, 5)
        glay_ldown.addWidget(QLabel('用户ID'), 1, 0, 1, 1)
        glay_ldown.addWidget(self.ledit_uid, 1, 1, 1, 5)
        glay_ldown.addWidget(QLabel('数量'), 2, 0, 1, 1)
        glay_ldown.addWidget(self.ledit_num, 2, 1, 1, 1)
        glay_ldown.setColumnStretch(1, 1)
        glay_ldown.setColumnStretch(2, 5)

        vlay_left = QVBoxLayout()
        vlay_left.addLayout(glay_lup)
        vlay_left.addLayout(glay_ldown)
        hlay_thumb = QHBoxLayout()
        hlay_thumb.addLayout(vlay_left)
        hlay_thumb.addWidget(self.thumbnail)
        left_wid = QWidget()
        left_wid.setLayout(hlay_thumb)

        self.thumbnail.setFixedHeight(left_wid.sizeHint().height())
        self.thumbnail.setFixedWidth(150)
        self.thumbnail.setPixmap(self.thumb_default)
        if self.show_thumb_flag:
            self.thumbnail.show()
        else:
            self.thumbnail.hide()

        vlay_right = QVBoxLayout()
        vlay_right.addWidget(self.user_info, alignment=Qt.AlignHCenter)
        vlay_right.addWidget(self.btn_logout)
        vlay_right.addWidget(self.btn_get)
        vlay_right.addWidget(self.btn_dl)
        right_wid = QWidget()
        right_wid.setLayout(vlay_right)

        splitter = QSplitter(Qt.Horizontal)
        splitter.setHandleWidth(3)
        splitter.addWidget(left_wid)
        splitter.addWidget(right_wid)
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 0)
        splitter.handle(1).setDisabled(True)

        vlay_main = QVBoxLayout()
        vlay_main.addWidget(splitter)
        vlay_main.addWidget(self.table_viewer)
        self.setLayout(vlay_main)
示例#2
0
class MyMainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.title = "PyQt5 Splitter"
        self.top = 200
        self.left = 500
        self.width = 400
        self.height = 300
        hbox = QHBoxLayout()
        self.widg_1 = QFrame()
        self.widg_1.setFrameShape(QFrame.StyledPanel)
        self.my_splitter = QSplitter(Qt.Horizontal)
        self.widg_2 = QLineEdit()
        self.my_splitter.addWidget(self.widg_1)
        self.my_splitter.addWidget(self.widg_2)
        self.my_splitter.setSizes([200, 200])
        hbox.addWidget(self.my_splitter)
        # Note: splitter handle 0 is always hidden and handle 1 is between the widgets 1 & 2
        self.my_splitter_handle = self.my_splitter.handle(1)
        self.my_splitter.setStyleSheet(
            "QSplitter::handle {background: 3px blue};")
        self.setLayout(hbox)
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.show()
示例#3
0
class originalWindow(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.splitter = QSplitter(self)
        self.splitter.addWidget(QTextEdit(self))
        self.splitter.addWidget(QTextEdit(self))
        layout = QVBoxLayout(self)
        layout.addWidget(self.splitter)
        handle = self.splitter.handle(1)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        button = QToolButton(handle)
        button.setArrowType(QtCore.Qt.LeftArrow)
        button.clicked.connect(
            lambda: self.handleSplitterButton(True))
        layout.addWidget(button)
        button = QToolButton(handle)
        button.setArrowType(QtCore.Qt.RightArrow)
        button.clicked.connect(
            lambda: self.handleSplitterButton(False))
        layout.addWidget(button)
        handle.setLayout(layout)

    def handleSplitterButton(self, left=True):
        if not all(self.splitter.sizes()):
            self.splitter.setSizes([1, 1])
        elif left:
            self.splitter.setSizes([0, 1])
        else:
            self.splitter.setSizes([1, 0])
class DockWidget(QDockWidget):
    def __init__(self,
                 parent=None,
                 window_name=None,
                 widget1=None,
                 widget2=None,
                 button=None,
                 hide_button_name=None):
        super(DockWidget, self).__init__(window_name, parent)
        self.setFeatures(QDockWidget.DockWidgetFloatable
                         | QDockWidget.DockWidgetMovable
                         | QDockWidget.DockWidgetClosable)
        dock_widgets = HorizontalWidget(widget1=widget1, widget2=widget2)
        v_layout = QVBoxLayout()
        v_layout.addWidget(dock_widgets, alignment=Qt.AlignCenter)
        if button:
            self.button = button
            v_layout.addWidget(self.button, alignment=Qt.AlignBottom)
        if hide_button_name:
            self.hide_button = QPushButton(str(hide_button_name))
            self.splitter = QSplitter(Qt.Horizontal)
            button_layout = QVBoxLayout()
            button_layout.addWidget(self.hide_button, alignment=Qt.AlignCenter)
            set_widget = QWidget()
            set_widget.setLayout(button_layout)
            self.splitter.addWidget(set_widget)
            set_widget = QWidget()
            set_widget.setLayout(v_layout)
            self.splitter.addWidget(set_widget)
            self.splitter.setSizes([1, 0])
            self.splitter.show()
            handle = self.splitter.handle(1)
            self.layout = QVBoxLayout()
            self.layout.setContentsMargins(0, 0, 0, 0)
            self.switch_frame = QAction(handle)
            self.switch_frame.triggered.connect(self.handle_splitter)
            self.hide_button.clicked.connect(self.switch_frame.trigger)
            self.setWidget(self.splitter)
        else:
            set_widget = QWidget()
            set_widget.setLayout(v_layout)
            self.setWidget(set_widget)
        self.show()

    def handle_splitter(self):
        if self.splitter.sizes()[0] > 0:
            self.splitter.setSizes([0, 1])
        else:
            self.splitter.setSizes([1, 0])

    def closeEvent(self, event):
        event.ignore()
        if self.isFloating():
            self.setFloating(False)
        if self.splitter.sizes()[1] > 0:
            self.splitter.setSizes([1, 0])
示例#5
0
    def __init__(self):
        super().__init__()
        self.language = settings.LANG
        self.csv_separator = settings.CSV_SEPARATOR
        self.fmt_float = settings.FMT_FLOAT
        self.logging_level = settings.LOGGING_LEVEL
        self.panel = MainPanel(self)

        config_button = QPushButton('Global\nConfiguration')
        config_button.setMinimumHeight(40)
        config_button.clicked.connect(self.global_config)

        pageList = QListWidget()
        for name in ['Start', 'Extract variables', 'Max/Min/Mean/Arrival/Duration', 'Interpolate on points',
                     'Interpolate along lines', 'Project along lines', 'Project mesh',
                     'Compute volume', 'Compute flux', 'Compare two results',
                     'Transform coordinate systems', 'Convert geom file formats', 'Variable Calculator']:
            pageList.addItem('\n' + name + '\n')
        pageList.setFlow(QListView.TopToBottom)
        pageList.currentRowChanged.connect(self.panel.layout().setCurrentIndex)

        pageList.setCurrentRow(0)

        splitter = QSplitter()
        left_widget = QWidget()
        vlayout = QVBoxLayout()
        vlayout.addWidget(config_button)
        vlayout.addWidget(pageList)
        left_widget.setLayout(vlayout)
        splitter.addWidget(left_widget)
        splitter.addWidget(self.panel)
        splitter.setHandleWidth(5)
        splitter.setCollapsible(0, False)
        splitter.setCollapsible(1, False)

        handle = splitter.handle(1)
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        line = QFrame()
        line.setFrameShape(QFrame.VLine)
        line.setFrameShadow(QFrame.Sunken)
        layout.addWidget(line)
        handle.setLayout(layout)

        mainLayout = QHBoxLayout()
        mainLayout.addWidget(splitter)
        mainLayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(mainLayout)

        self.setWindowTitle('PyTelTools :: Classic interface')
        self.setWindowFlags(self.windowFlags() | Qt.CustomizeWindowHint)
        self.frameGeom = self.frameGeometry()
        self.move(self.frameGeom.center())
class SplitterFrameWidget(QWidget):
    status_message = pyqtSignal(str, name='status_message')

    def __init__(self, parent, widget1, widget2, orientation='horizontal', arrow_direction='right'):
        super(SplitterFrameWidget, self).__init__(parent)
        if orientation == 'horizontal':
            orientation = Qt.Horizontal
        else:
            orientation = Qt.Vertical
        if arrow_direction == 'right':
            arrow = Qt.RightArrow
        else:
            arrow = Qt.LeftArrow

        self.splitter = QSplitter(orientation)
        self.splitter.addWidget(self.center_widget(widget1))
        self.splitter.addWidget(widget2)
        self.splitter.setSizes([1, 0])

        layout = QVBoxLayout(self)
        layout.addWidget(self.splitter, alignment=Qt.AlignBottom)
        handle = self.splitter.handle(1)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        button = QToolButton(handle)
        button.setText("Save")
        button.setCheckable(True)
        button.setArrowType(arrow)
        button.clicked.connect(self.handle_splitter_button)
        layout.addWidget(button, alignment=Qt.AlignCenter)
        handle.setLayout(layout)
        self.center_frame()
        self.show()

    def handle_splitter_button(self):
        if not all(self.splitter.sizes()):
            self.splitter.setSizes([1, 1])
        else:
            self.splitter.setSizes([1, 0])

    def center_widget(self, widget):
        centered_widget = QWidget()
        temp_layout = QHBoxLayout()
        temp_layout.addWidget(widget, alignment=Qt.AlignCenter)
        centered_widget.setLayout(temp_layout)
        return centered_widget

    def center_frame(self):
        screen_dimensions = self.geometry()
        center_dimensions = QDesktopWidget().availableGeometry().topLeft()
        screen_dimensions.moveCenter(center_dimensions)
        self.move(screen_dimensions.center())
示例#7
0
    def decorate(splitter: QSplitter, index: int = 1):

        gripLength = 35
        gripWidth = 1  # may need to be 1 or 2 depending on theme
        gripSpacing = 0
        grips = 3

        splitter.setOpaqueResize(False)
        splitter.setChildrenCollapsible(False)
        splitter.setHandleWidth(7)
        handle = splitter.handle(index)
        orientation = splitter.orientation()
        layout = QHBoxLayout(handle)
        layout.setSpacing(gripSpacing)
        layout.setContentsMargins(0, 0, 0, 0)

        if orientation == Qt.Horizontal:
            for i in range(grips):
                line = QFrame(handle)
                line.setMinimumSize(gripWidth, gripLength)
                line.setMaximumSize(gripWidth, gripLength)
                line.setLineWidth(gripWidth)
                line.setFrameShape(line.StyledPanel)
                line.setStyleSheet("border: 1px solid lightgray;")
                layout.addWidget(line)
        else:
            # center the vertical grip by adding spacers before and after
            layout.addStretch()
            vBox = QVBoxLayout()

            for i in range(grips):
                line = QFrame(handle)
                line.setMinimumSize(gripLength, gripWidth)
                line.setMaximumSize(gripLength, gripWidth)
                line.setFrameShape(line.StyledPanel)
                line.setStyleSheet("border: 1px solid lightgray;")
                vBox.addWidget(line)
            layout.addLayout(vBox)
            layout.addStretch()
示例#8
0
class Window(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.splitter = QSplitter( QtCore.Qt.Vertical, self)

        self.splitter.addWidget(QTextEdit(self))
        self.splitter.addWidget(QTextEdit(self))

        layout = QHBoxLayout(self)
        layout.addWidget(self.splitter)
        handle = self.splitter.handle(1)
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        button = QToolButton(handle)
        button.setArrowType(QtCore.Qt.UpArrow)
        button.clicked.connect(
            lambda: self.handleSplitterButton(True,[0,1]))
        layout.addWidget(button)

        button = QToolButton(handle)
        button.setArrowType(QtCore.Qt.DownArrow)
        button.clicked.connect(
            lambda: self.handleSplitterButton(False,[0,1]))
        layout.addWidget(button)

        handle.setLayout(layout)



    def handleSplitterButton(self, up=True, lines = [0,1]):
        if not all(self.splitter.sizes()):
            self.splitter.setSizes([1, 1, 1])
        elif up:
            self.splitter.setSizes([0, 1, 1])
        else:
            self.splitter.setSizes([1, 0, 1])
示例#9
0
class Model(QWidget):
    def __init__(self, list_of_widgets):
        QWidget.__init__(self)
        self.splitter = QSplitter(QtCore.Qt.Vertical, self)

        for widget in list_of_widgets:
            scroll = QScrollArea()
            scroll.setWidget(widget)
            scroll.setWidgetResizable(True)
            self.splitter.addWidget(scroll)
        # scroll.setFixedHeight(400)
        # layout = QtGui.QVBoxLayout(self)
        # layout.addWidget(scroll)

        # self.splitter.addWidget(QTextEdit(self))
        # self.splitter.addWidget(QTextEdit(self))

        layout = QHBoxLayout(self)
        layout.addWidget(self.splitter)
        handle = self.splitter.handle(1)
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        handle.setLayout(layout)
示例#10
0
class SubTabWidget(QWidget):
    _tabChanged = pyqtSignal(int, name = "tabChanged")

    def __init__(self, subtitleData, videoWidget, parent = None):
        super(SubTabWidget, self).__init__(parent)
        self._subtitleData = subtitleData
        self.__initTabWidget(videoWidget)

    def __initTabWidget(self, videoWidget):
        settings = SubSettings()

        mainLayout = QVBoxLayout(self)
        mainLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.setSpacing(0)

        #TabBar
        self.tabBar = QTabBar(self)

        # Splitter (bookmarks + pages)
        self.splitter = QSplitter(self)
        self.splitter.setObjectName("sidebar_splitter")

        self._toolbox = ToolBox(self._subtitleData, self)
        self._toolbox.setObjectName("sidebar")
        self._toolbox.setMinimumWidth(100)

        self._toolbox.addTool(Details(self._subtitleData, self))
        self._toolbox.addTool(Synchronizer(videoWidget, self._subtitleData, self))
        self._toolbox.addTool(History(self))

        self.rightWidget = QWidget()
        rightLayout = QGridLayout()
        rightLayout.setContentsMargins(0, 0, 0, 0)
        self.rightWidget.setLayout(rightLayout)

        self._mainTab = FileList(_("Subtitles"), self._subtitleData, self)

        self.pages = QStackedWidget(self)
        rightLayout.addWidget(self.pages, 0, 0)

        self.tabBar.addTab(self._mainTab.name)
        self.pages.addWidget(self._mainTab)

        self.splitter.addWidget(self._toolbox)
        self.splitter.addWidget(self.rightWidget)
        self.__drawSplitterHandle(1)

        # Setting widgets
        mainLayout.addWidget(self.tabBar)
        mainLayout.addWidget(self.splitter)

        # Widgets settings
        self.tabBar.setMovable(True)
        self.tabBar.setTabsClosable(True)
        self.tabBar.setExpanding(False)

        # Don't resize left panel if it's not needed
        leftWidgetIndex = self.splitter.indexOf(self._toolbox)
        rightWidgetIndex = self.splitter.indexOf(self.rightWidget)

        self.splitter.setStretchFactor(leftWidgetIndex, 0)
        self.splitter.setStretchFactor(rightWidgetIndex, 1)
        self.splitter.setCollapsible(leftWidgetIndex, False)
        self.splitter.setSizes([250])

        # Some signals
        self.tabBar.currentChanged.connect(self.showTab)
        self.tabBar.tabCloseRequested.connect(self.closeTab)
        self.tabBar.tabMoved.connect(self.moveTab)
        self._mainTab.requestOpen.connect(self.openTab)
        self._mainTab.requestRemove.connect(self.removeFile)

        self.tabChanged.connect(lambda i: self._toolbox.setContentFor(self.tab(i)))

        self.setLayout(mainLayout)

    def __addTab(self, filePath):
        """Returns existing tab index. Creates a new one if it isn't opened and returns its index
        otherwise."""
        for i in range(self.tabBar.count()):
            widget = self.pages.widget(i)
            if not widget.isStatic and filePath == widget.filePath:
                return i
        tab = SubtitleEditor(filePath, self._subtitleData, self)
        newIndex = self.tabBar.addTab(self._createTabName(tab.name, tab.history.isClean()))
        tab.history.cleanChanged.connect(
            lambda clean: self._cleanStateForFileChanged(filePath, clean))
        self.pages.addWidget(tab)
        return newIndex

    def __drawSplitterHandle(self, index):
        splitterHandle = self.splitter.handle(index)

        splitterLayout = QVBoxLayout(splitterHandle)
        splitterLayout.setSpacing(0)
        splitterLayout.setContentsMargins(0, 0, 0, 0)

        line = QFrame(splitterHandle)
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)
        splitterLayout.addWidget(line)
        splitterHandle.setLayout(splitterLayout)

    def _createTabName(self, name, cleanState):
        if cleanState is True:
            return name
        else:
            return "%s +" % name

    def _cleanStateForFileChanged(self, filePath, cleanState):
        page = self.tabByPath(filePath)
        if page is not None:
            for i in range(self.tabBar.count()):
                if self.tabBar.tabText(i)[:len(page.name)] == page.name:
                    self.tabBar.setTabText(i, self._createTabName(page.name, cleanState))
                    return

    def saveWidgetState(self, settings):
        settings.setState(self.splitter, self.splitter.saveState())
        settings.setHidden(self._toolbox, self._toolbox.isHidden())

    def restoreWidgetState(self, settings):
        self.showPanel(not settings.getHidden(self._toolbox))

        splitterState = settings.getState(self.splitter)
        if not splitterState.isEmpty():
            self.splitter.restoreState(settings.getState(self.splitter))

    @pyqtSlot(str, bool)
    def openTab(self, filePath, background=False):
        if self._subtitleData.fileExists(filePath):
            tabIndex = self.__addTab(filePath)
            if background is False:
                self.showTab(tabIndex)
        else:
            log.error(_("SubtitleEditor not created for %s!" % filePath))

    @pyqtSlot(str)
    def removeFile(self, filePath):
        tab = self.tabByPath(filePath)
        command = RemoveFile(filePath)
        if tab is not None:
            index = self.pages.indexOf(tab)
            if self.closeTab(index):
                self._subtitleData.execute(command)
        else:
            self._subtitleData.execute(command)


    @pyqtSlot(int)
    def closeTab(self, index):
        tab = self.tab(index)
        if tab.canClose():
            widgetToRemove = self.pages.widget(index)
            self.tabBar.removeTab(index)
            self.pages.removeWidget(widgetToRemove)
            widgetToRemove.deleteLater()
            return True
        return False


    def count(self):
        return self.tabBar.count()

    def currentIndex(self):
        return self.tabBar.currentIndex()

    def currentPage(self):
        return self.pages.currentWidget()

    @pyqtSlot(int, int)
    def moveTab(self, fromIndex, toIndex):
        fromWidget = self.pages.widget(fromIndex)
        toWidget = self.pages.widget(toIndex)
        if fromWidget.isStatic or toWidget.isStatic:
            self.tabBar.blockSignals(True) # signals would cause infinite recursion
            self.tabBar.moveTab(toIndex, fromIndex)
            self.tabBar.blockSignals(False)
            return
        else:
            self.pages.removeWidget(fromWidget)
            self.pages.removeWidget(toWidget)

            if fromIndex < toIndex:
                self.pages.insertWidget(fromIndex, toWidget)
                self.pages.insertWidget(toIndex, fromWidget)
            else:
                self.pages.insertWidget(toIndex, fromWidget)
                self.pages.insertWidget(fromIndex, toWidget)

            # Hack
            # Qt changes tabs during mouse drag and dropping. The next line is added
            # to prevent it.
            self.showTab(self.tabBar.currentIndex())

    @pyqtSlot(int)
    def showTab(self, index):
        showWidget = self.pages.widget(index)
        if showWidget:
            self.pages.setCurrentWidget(showWidget)
            self.tabBar.blockSignals(True)
            self.tabBar.setCurrentIndex(index)
            self.tabBar.blockSignals(False)

            # Try to update current tab.
            showWidget.updateTab()

            self._tabChanged.emit(index)

    def showPanel(self, val):
        if val is True:
            self._toolbox.show()
        else:
            self._toolbox.hide()

    def togglePanel(self):
        if self._toolbox.isHidden():
            self._toolbox.show()
        else:
            self._toolbox.hide()

    def tab(self, index):
        return self.pages.widget(index)

    def tabByPath(self, path):
        for i in range(self.pages.count()):
            page = self.tab(i)
            if not page.isStatic and page.filePath == path:
                return page
        return None

    @property
    def fileList(self):
        return self._mainTab
示例#11
0
class DetailsDialog(DetailsDialogBase):
    def __init__(self, parent, app):
        self.vController = None
        super().__init__(parent, app)

    def _setupUi(self):
        self.setWindowTitle(tr("Details"))
        self.resize(502, 502)
        self.setMinimumSize(QSize(250, 250))
        self.splitter = QSplitter(Qt.Vertical)
        self.topFrame = EmittingFrame()
        self.topFrame.setFrameShape(QFrame.StyledPanel)
        self.horizontalLayout = QGridLayout()
        # Minimum width for the toolbar in the middle:
        self.horizontalLayout.setColumnMinimumWidth(1, 10)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setColumnStretch(0, 32)
        # Smaller value for the toolbar in the middle to avoid excessive resize
        self.horizontalLayout.setColumnStretch(1, 2)
        self.horizontalLayout.setColumnStretch(2, 32)
        # This avoids toolbar getting incorrectly partially hidden when window resizes
        self.horizontalLayout.setRowStretch(0, 1)
        self.horizontalLayout.setRowStretch(1, 24)
        self.horizontalLayout.setRowStretch(2, 1)
        self.horizontalLayout.setSpacing(1)  # probably not important

        self.selectedImageViewer = ScrollAreaImageViewer(self, "selectedImage")
        self.horizontalLayout.addWidget(self.selectedImageViewer, 0, 0, 3, 1)
        # Use a specific type of controller depending on the underlying viewer type
        self.vController = ScrollAreaController(self)

        self.verticalToolBar = ViewerToolBar(self, self.vController)
        self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
        self.horizontalLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1,
                                        Qt.AlignCenter)

        self.referenceImageViewer = ScrollAreaImageViewer(
            self, "referenceImage")
        self.horizontalLayout.addWidget(self.referenceImageViewer, 0, 2, 3, 1)
        self.topFrame.setLayout(self.horizontalLayout)
        self.splitter.addWidget(self.topFrame)
        self.splitter.setStretchFactor(0, 8)

        self.tableView = DetailsTable(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.tableView.setSizePolicy(sizePolicy)
        # self.tableView.setMinimumSize(QSize(0, 190))
        # self.tableView.setMaximumSize(QSize(16777215, 190))
        self.tableView.setAlternatingRowColors(True)
        self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.tableView.setShowGrid(False)
        self.splitter.addWidget(self.tableView)
        self.splitter.setStretchFactor(1, 1)
        # Late population needed here for connections to the toolbar
        self.vController.setupViewers(self.selectedImageViewer,
                                      self.referenceImageViewer)
        # self.setCentralWidget(self.splitter)  # only as QMainWindow
        self.setWidget(self.splitter)  # only as QDockWidget

        self.topFrame.resized.connect(self.resizeEvent)

    def _update(self):
        if self.vController is None:  # Not yet constructed!
            return
        if not self.app.model.selected_dupes:
            # No item from the model, disable and clear everything.
            self.vController.resetViewersState()
            return
        dupe = self.app.model.selected_dupes[0]
        group = self.app.model.results.get_group_of_duplicate(dupe)
        ref = group.ref

        self.vController.updateView(ref, dupe, group)

    # --- Override
    @pyqtSlot(QResizeEvent)
    def resizeEvent(self, event):
        self.ensure_same_sizes()
        if self.vController is None or not self.vController.bestFit:
            return
        # Only update the scaled down pixmaps
        self.vController.updateBothImages()

    def show(self):
        # Give the splitter a maximum height to reach. This is assuming that
        # all rows below their headers have the same height
        self.tableView.setMaximumHeight(
            self.tableView.rowHeight(1) * self.tableModel.model.row_count() +
            self.tableView.verticalHeader().sectionSize(0)
            # looks like the handle is taken into account by the splitter
            + self.splitter.handle(1).size().height())
        DetailsDialogBase.show(self)
        self.ensure_same_sizes()
        self._update()

    def ensure_same_sizes(self):
        # HACK This ensures same size while shrinking.
        # ReferenceViewer might be 1 pixel shorter in width
        # due to the toolbar in the middle keeping the same width,
        # so resizing in the GridLayout's engine leads to not enough space
        # left for the panel on the right.
        # This work as a QMainWindow, but doesn't work as a QDockWidget:
        # resize can only grow. Might need some custom sizeHint somewhere...
        # self.horizontalLayout.setColumnMinimumWidth(
        #     0, self.selectedImageViewer.size().width())
        # self.horizontalLayout.setColumnMinimumWidth(
        #     2, self.selectedImageViewer.size().width())

        # This works when expanding but it's ugly:
        if self.selectedImageViewer.size().width(
        ) > self.referenceImageViewer.size().width():
            self.selectedImageViewer.resize(self.referenceImageViewer.size())

    # model --> view
    def refresh(self):
        DetailsDialogBase.refresh(self)
        if self.isVisible():
            self._update()
示例#12
0
class CodecTab(QScrollArea):

    # BUG: codec_frame should have height 210 but has 480.
    # WORKAROUND: manually set height to 210 height.
    # SEE: https://forum.qt.io/topic/42055/qwidget-height-returns-incorrect-value-in-5-3/7
    FRAME_HEIGHT = 210

    def __init__(self, parent, context, commands):
        super(QWidget, self).__init__(parent)
        self._context = context
        self._logger = context.logger()
        self._commands = commands

        self._next_frame_id = 1
        self._frames = QSplitter(Qt.Vertical)
        self._frames.setChildrenCollapsible(False)
        self._frames.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
        self._frames.setContentsMargins(0, 0, 0, 0)

        self._main_frame = QFrame(self)
        self._main_frame_layout = QVBoxLayout()
        self._main_frame_layout.addWidget(self._frames)
        self._main_frame_layout.addWidget(VSpacer(self))
        self._main_frame.setLayout(self._main_frame_layout)
        self.newFrame("", "")

        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.setWidgetResizable(True)
        self.setWidget(self._main_frame)

    def newFrame(self,
                 text,
                 title,
                 previous_frame=None,
                 status=None,
                 msg=None):
        try:
            # BUG: Setting complex default values is not possible in python
            # WORKAROUND: Set default value to None and set real default later.
            if status is None:
                status = StatusWidget.DEFAULT

            if previous_frame and previous_frame.hasNext():
                next_frame = previous_frame.next()
                next_frame.setTitle(title)
                finished = False
                if status == StatusWidget.ERROR:
                    while not finished:
                        next_frame.flashStatus(status, msg)
                        # Display error only for the first frame.
                        msg = None
                        finished = not next_frame.hasNext()
                        next_frame = next_frame.next()
                else:
                    next_frame.setInputText(text, msg is not None
                                            and len(msg) > 0)
                    next_frame.flashStatus(status, msg)

                previous_frame.focusInputText()
            else:
                new_frame = CodecFrame(self, self._context,
                                       self._next_frame_id, self,
                                       self._commands, previous_frame, text)
                self._next_frame_id += 1
                if self._frames.count() > 0:
                    new_frame.flashStatus(status, msg)
                new_frame.setTitle(title)
                new_frame.setContentsMargins(0, 0, 0, 0)
                new_frame.layout().setContentsMargins(0, 0, 0, 0)
                self._frames.addWidget(new_frame)

                # BUG: QSplitter does not allow frames to be wider than the surrounding area (here: QScrollArea).
                # WORKAROUND: Set a fixed size for codec frames and disable handles which prevents users from
                #             trying to resize the codec frames.
                new_frame.setFixedHeight(self.FRAME_HEIGHT)
                self._frames.handle(self._frames.count() - 1).setEnabled(False)

                if previous_frame:
                    previous_frame.focusInputText()
                else:
                    new_frame.focusInputText()
        except Exception as e:
            self._logger.error("Unknown error: {}".format(str(e)))

    def removeFrames(self, frame):
        if frame:
            if frame.previous():
                frame.previous().setNext(None)

            frames_to_remove = [frame]
            while frame.next():
                frames_to_remove.append(frame.next())
                frame = frame.next()
            for frame_to_remove in reversed(frames_to_remove):
                frame_to_remove.deleteLater()

    def getFocussedFrame(self):
        widget = self._frames.focusWidget()
        while widget:
            if isinstance(widget, CodecFrame):
                return widget
            widget = widget.parent()
        return self._frames.widget(0)
示例#13
0
文件: gui.py 项目: Sunsetra/PETSpider
    def init_ui(self):
        hlay_addr = QHBoxLayout()
        hlay_addr.addWidget(QLabel('画廊地址'))
        hlay_addr.addWidget(self.ledit_addr)
        hlay_addr.setStretch(0, 0)
        hlay_cbox = QHBoxLayout()
        hlay_cbox.addWidget(self.cbox_rename)
        hlay_cbox.addWidget(self.cbox_rewrite)
        hlay_cbox.addStretch(1)
        hlay_cbox.addWidget(QLabel('从'))
        hlay_cbox.addWidget(self.sbox_begin_page)
        hlay_cbox.addWidget(QLabel('下载至'))
        hlay_cbox.addWidget(self.sbox_end_page)
        hlay_cbox.addWidget(QLabel('页'))
        hlay_cbox.setAlignment(Qt.AlignLeft)
        hlay_acts = QHBoxLayout()
        hlay_acts.addStretch(1)
        hlay_acts.addWidget(self.btn_get)
        hlay_acts.addWidget(self.btn_add)
        hlay_acts.addWidget(self.btn_remove)
        hlay_acts.addWidget(self.btn_start)
        hlay_acts.addStretch(1)

        vlay_left = QVBoxLayout()
        vlay_left.addLayout(hlay_addr)
        vlay_left.addLayout(hlay_cbox)
        vlay_left.addLayout(hlay_acts)
        left_wid = QWidget()
        left_wid.setLayout(vlay_left)

        vlay_right = QVBoxLayout()
        vlay_right.addWidget(self.user_info, alignment=Qt.AlignHCenter)
        vlay_right.addWidget(self.btn_refresh)
        vlay_right.addWidget(self.btn_logout)
        right_wid = QWidget()
        right_wid.setLayout(vlay_right)

        splitter = QSplitter(Qt.Horizontal)
        splitter.setHandleWidth(3)
        splitter.addWidget(left_wid)
        splitter.addWidget(right_wid)
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 0)
        splitter.handle(1).setDisabled(True)

        vlay_info = QVBoxLayout()
        vlay_info.addWidget(self.thumbnail)
        vlay_info.addWidget(self.info)

        hlay_down = QHBoxLayout()
        hlay_down.addLayout(vlay_info)
        hlay_down.addWidget(self.que)

        vlay_main = QVBoxLayout()
        vlay_main.addWidget(splitter)
        vlay_main.addLayout(hlay_down)
        self.setLayout(vlay_main)

        self.thumbnail.setFixedHeight(left_wid.sizeHint().height())
        self.thumbnail.setFixedWidth(250)
        self.thumbnail.setFixedHeight(360)
        self.thumbnail.setPixmap(self.thumb_default)
        if self.show_thumb_flag:  # Cannot put code after show(), or flick
            self.thumbnail.show()
        else:
            self.thumbnail.hide()
示例#14
0
    def _setupUI(self):
        """
        Sets up thee UI for the rearrangement Viewer.

        Notes:
             helper method for __init__

        Returns:
            None
        """
        self.scrollA = QScrollArea()
        self.listA = SourceList(self)
        self.listA.itemSelectionChanged.connect(self.show_relevant_tools)
        self.scrollA.setWidget(self.listA)
        self.scrollA.setWidgetResizable(True)
        self.scrollB = QScrollArea()
        self.listB = SinkList(self)
        self.listB.itemSelectionChanged.connect(self.show_relevant_tools)
        self.scrollB.setWidget(self.listB)
        self.scrollB.setWidgetResizable(True)

        self.appendB = QToolButton()
        # TODO: move &A here and use alt-Enter to Accept dialog?
        self.appendB.setText("Add &Page(s)")
        self.appendB.setArrowType(Qt.DownArrow)
        self.appendB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.removeB = QToolButton()
        self.removeB.setArrowType(Qt.UpArrow)
        self.removeB.setText("&Remove Page(s)")
        self.removeB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.sLeftB = QToolButton()
        self.sLeftB.setArrowType(Qt.LeftArrow)
        self.sLeftB.setText("Shift Left")
        self.sLeftB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.sRightB = QToolButton()
        self.sRightB.setArrowType(Qt.RightArrow)
        self.sRightB.setText("Shift Right")
        self.sRightB.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.reverseB = QPushButton("Reverse Order")
        self.revertB = QPushButton("Revert to original state")
        self.revertB.clicked.connect(self.populateListOriginal)

        self.rotateB_cw = QPushButton("\N{Clockwise Open Circle Arrow} Rotate CW")
        self.rotateB_ccw = QPushButton("\N{Anticlockwise Open Circle Arrow} Rotate CCW")

        self.closeB = QPushButton("&Cancel")
        self.acceptB = QPushButton("&Accept")

        self.permute = [False]

        def GrippyMcGrab():
            """Grippy bars to spice-up QSplitterHandles."""
            width = 64
            pad = 20
            hb = QHBoxLayout()
            hb.addItem(
                QSpacerItem(pad, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
            )
            vb = QVBoxLayout()
            hb.addLayout(vb)
            hb.addItem(
                QSpacerItem(pad, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
            )

            vb.setContentsMargins(0, 1, 0, 1)
            vb.setSpacing(2)
            vb.addItem(
                QSpacerItem(
                    width, 3, QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding
                )
            )
            for i in range(3):
                f = QFrame()
                f.setFrameShape(QFrame.HLine)
                f.setFrameShadow(QFrame.Sunken)
                vb.addWidget(f)
            vb.addItem(
                QSpacerItem(
                    width, 3, QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding
                )
            )
            return hb

        hb3 = QHBoxLayout()
        self.tools = QFrame()
        hb = QHBoxLayout()
        self.tools.setLayout(hb)
        hb.setContentsMargins(0, 0, 0, 0)
        hb.addWidget(self.rotateB_ccw)
        hb.addWidget(self.rotateB_cw)
        hb.addItem(QSpacerItem(16, 20, QSizePolicy.Minimum, QSizePolicy.Minimum))
        hb.addWidget(self.sLeftB)
        hb.addWidget(self.sRightB)
        hb.addItem(QSpacerItem(16, 20, QSizePolicy.Minimum, QSizePolicy.Minimum))
        hb3.addWidget(self.tools)
        hb3.addWidget(self.reverseB)
        hb3.addItem(
            QSpacerItem(16, 20, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
        )
        hb3.addWidget(self.acceptB)
        hb3.addWidget(self.closeB)

        allPages = QLabel("Other Pages in Exam")
        thisQuestion = QLabel("Pages for this Question")

        # center add/remove buttons on label row
        hb1 = QHBoxLayout()
        hb1.addWidget(thisQuestion)
        hb1.addLayout(GrippyMcGrab())
        hb = QHBoxLayout()
        hb.addWidget(self.appendB)
        hb.addItem(QSpacerItem(64, 20, QSizePolicy.Minimum, QSizePolicy.Minimum))
        hb.addWidget(self.removeB)
        hb1.addLayout(hb)
        hb1.addLayout(GrippyMcGrab())
        hb1.addWidget(self.revertB)

        vb0 = QVBoxLayout()
        s = QSplitter()
        s.setOrientation(Qt.Vertical)
        # s.setOpaqueResize(False)
        s.setChildrenCollapsible(False)
        s.setHandleWidth(50)  # TODO: better not to hardcode, take from children?
        vb0.addWidget(s)
        f = QFrame()
        s.addWidget(f)
        vb = QVBoxLayout()
        vb.setContentsMargins(0, 0, 0, 0)
        f.setLayout(vb)
        vb.addWidget(allPages)
        vb.addWidget(self.scrollA)
        f = QFrame()
        s.addWidget(f)
        vb = QVBoxLayout()
        vb.setContentsMargins(0, 0, 0, 0)
        f.setLayout(vb)
        vb.addWidget(self.scrollB)
        vb.addLayout(hb3)

        handle = s.handle(1)
        vb = QVBoxLayout()
        vb.setContentsMargins(0, 0, 0, 0)
        vb.setSpacing(0)
        handle.setLayout(hb1)
        hb1.setContentsMargins(0, 0, 0, 0)
        # TODO: Buttons inside the splitter bar, disable drag and custom cursor
        for b in (self.removeB, self.appendB, self.revertB):
            b.mouseMoveEvent = lambda *args: None
            b.setCursor(Qt.ArrowCursor)

        self.setLayout(vb0)
        self.resize(QSize(self.parent.width() * 7 / 8, self.parent.height() * 9 / 10))

        self.closeB.clicked.connect(self.close)
        self.sLeftB.clicked.connect(self.shuffleLeft)
        self.sRightB.clicked.connect(self.shuffleRight)
        self.reverseB.clicked.connect(self.reverseOrder)
        self.rotateB_cw.clicked.connect(lambda: self.rotateImages(90))
        self.rotateB_ccw.clicked.connect(lambda: self.rotateImages(-90))
        self.appendB.clicked.connect(self.sourceToSink)
        self.removeB.clicked.connect(self.sinkToSource)
        self.acceptB.clicked.connect(self.doShuffle)

        allPageWidgets = [self.listA, self.listB]

        self.listA.selectionModel().selectionChanged.connect(
            lambda sel, unsel: self.singleSelect(self.listA, allPageWidgets)
        )
        self.listB.selectionModel().selectionChanged.connect(
            lambda sel, unsel: self.singleSelect(self.listB, allPageWidgets)
        )
示例#15
0
class ClientWindow(QMainWindow):
    """GUI主窗口类。"""

    def __init__(self):
        super().__init__()
        self.files = []  # 发送端待发送文件列表
        self.del_list = []  # 发送端待删除文件列表
        self.received_files = 0  # 接收端已接受文件数
        self.succeed_files = 0  # 接收端成功接受文件数
        self.failed_files = 0  # 接收端传输失败文件数
        self.client_que = Queue()
        self.server_que = Queue()

        self.setFont(QFont('Arial', 10))
        self.resolution = QGuiApplication.primaryScreen().availableGeometry()
        self.reso_height = self.resolution.height()
        self.reso_width = self.resolution.width()
        self.settings = QSettings(os.path.join(os.path.abspath('.'), 'settings.ini'), QSettings.IniFormat)
        self.settings.beginGroup('UISetting')
        self.setting_detail_view = int(self.settings.value('detail_view', False))
        self.settings.endGroup()
        self.settings.beginGroup('ClientSetting')
        self.setting_del_timeout = float(self.settings.value('del_timeout', 1))
        self.settings.endGroup()

        self.init_ui()

    """UI构造函数"""

    def init_ui(self):

        self.sender_frame = QFrame()
        self.chat_frame = QFrame()
        self.splitter = QSplitter(Qt.Horizontal)
        self.splitter.setHandleWidth(self.reso_width / 384)  # 拖动杆宽度
        self.splitter.splitterMoved.connect(self.resizeEvent)
        self.splitter.addWidget(self.sender_frame)
        self.splitter.addWidget(self.chat_frame)
        self.splitter.handle(1).setStyleSheet('QSplitterHandle{background-color: rgb(210,210,210)}')
        self.setCentralWidget(self.splitter)  # 将分隔控件作为窗口主控件

        # 菜单栏定义
        menu_bar = self.menuBar()
        file_menu = menu_bar.addMenu('文件(&F)')
        self.act_choose = QAction('选择文件(&C)', self)
        self.act_send = QAction('发送(&S)', self)
        self.act_stop_send = QAction('中止传输(&E)', self)
        self.act_exit = QAction('退出(&Q)', self)
        file_menu.addAction(self.act_choose)
        file_menu.addAction(self.act_send)
        file_menu.addAction(self.act_stop_send)
        file_menu.addSeparator()
        file_menu.addAction(self.act_exit)
        self.act_choose.triggered.connect(self.file_dialog)
        self.act_send.setDisabled(True)
        self.act_send.triggered.connect(self.file_checker)
        self.act_stop_send.setDisabled(True)
        self.act_stop_send.triggered.connect(self.abort_trans)
        self.act_exit.triggered.connect(self.close)

        setting_menu = menu_bar.addMenu('设置(&S)')
        act_client = QAction('发送端设置(&C)', self)
        act_server = QAction('接收端设置(&S)', self)
        act_ui = QAction('界面设置(&U)', self)
        setting_menu.addAction(act_client)
        setting_menu.addAction(act_server)
        setting_menu.addAction(act_ui)
        act_client.triggered.connect(self.client_setting_dialog)
        act_server.triggered.connect(self.server_setting_dialog)
        act_ui.triggered.connect(self.ui_setting_dialog)

        # 状态栏定义
        self.status_bar = self.statusBar()
        self.status_bar.setSizeGripEnabled(False)
        self.Lclient_status = QLabel('发送端已就绪')
        self.Lserver_status = QLabel('接收端未启动')
        self.status_bar.addWidget(self.Lclient_status, 1)
        self.status_bar.addWidget(self.Lserver_status, 1)

        # 传输区域控件定义
        self.Bselector = QPushButton('选择文件', self)
        self.Bselector.setDefault(True)
        self.Bselector.clicked.connect(self.file_dialog)
        self.Lfile_empty = QLabel('未选中文件')
        self.Lfile_empty.setAlignment(Qt.AlignTop)
        self.file_table = QTableWidget()  # 文件列表采用表格视图
        self.file_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.file_table.setSelectionMode(QAbstractItemView.NoSelection)
        self.file_table.verticalScrollBar().setStyleSheet('QScrollBar{{width:{}px;}}'.format(self.reso_width / 192))
        self.file_table.verticalScrollBar().setContextMenuPolicy(Qt.NoContextMenu)
        self.file_table.verticalHeader().setSectionsClickable(False)
        self.file_table.horizontalHeader().setSectionsClickable(False)
        self.file_table.hide()
        self.Bfile_sender = QPushButton('发送', self)
        self.Bfile_sender.setDisabled(True)
        self.Bfile_sender.clicked.connect(self.file_checker)

        self.vbox = QVBoxLayout()
        self.vbox.setSpacing(self.reso_height / 70)
        self.vbox.addWidget(self.Bselector)
        self.vbox.addWidget(self.file_table)
        self.vbox.addWidget(self.Lfile_empty)
        self.vbox.addWidget(self.Bfile_sender)
        self.sender_frame.setLayout(self.vbox)

        # 消息区域控件定义
        self.Emessage_area = def_widget.MessageDisplayEdit(self)
        self.Emessage_area.setContextMenuPolicy(Qt.DefaultContextMenu)
        self.Emessage_area.setReadOnly(True)
        self.Emessage_writer = def_widget.MessageWriter(self)
        self.Emessage_writer.setPlaceholderText('输入消息')
        self.Emessage_writer.returnPressed.connect(self.chat_checker)
        self.Bfolder = QPushButton('<< 收起', self)
        self.Bfolder.clicked.connect(functools.partial(self.splitter.setSizes, [self.geometry().width(), 0]))
        self.Bmessage_sender = QPushButton('发送', self)
        self.Bmessage_sender.clicked.connect(self.chat_checker)

        self.chat_vbox = QVBoxLayout()
        self.chat_vbox.setSpacing(self.reso_height / 70)
        self.chat_vbox.addWidget(self.Emessage_area)
        self.chat_vbox.addWidget(self.Emessage_writer)
        self.chat_hbox = QHBoxLayout()
        self.chat_hbox.addWidget(self.Bfolder)
        self.chat_hbox.addStretch(1)
        self.chat_hbox.addWidget(self.Bmessage_sender)
        self.chat_vbox.addLayout(self.chat_hbox)
        self.chat_frame.setLayout(self.chat_vbox)

        if not self.setting_detail_view:  # 显示模式不同,最小宽度不同
            self.sender_frame.setMinimumWidth(self.reso_width / 7.68)
        else:
            self.sender_frame.setMinimumWidth(self.reso_width / 6)
        self.ui_setting_checker()
        self.settings.beginGroup('Misc')  # 恢复上次关闭时的窗口尺寸
        setting_window_size = self.settings.value('window_size', (self.reso_height / 6.4, self.reso_width / 2.7))
        setting_frame_width = self.settings.value('frame_width', (self.geometry().width(), 0))
        self.settings.endGroup()
        self.resize(setting_window_size[0], setting_window_size[1])
        self.splitter.setSizes([setting_frame_width[0], setting_frame_width[1]])
        self.frameGeometry().moveCenter(self.resolution.center())  # 在屏幕中央打开窗口
        self.setWindowTitle('FileTransfer')
        self.show()

        self.settings.beginGroup('ServerSetting')
        setting_open_server = int(self.settings.value('open_server', True))
        self.settings.endGroup()
        if setting_open_server:  # 启动服务端
            self.settings.beginGroup('ServerSetting')
            setting_incoming_ip = self.settings.value('incoming_ip', '0.0.0.0')
            setting_bind_port = int(self.settings.value('bind_port', 54321))
            setting_receive_dir = self.settings.value('receive_dir', os.path.abspath('.'))
            self.settings.endGroup()
            self.server_starter = Process(target=server.starter, name='ServerStarter', args=(
                setting_incoming_ip, setting_bind_port, setting_receive_dir, self.server_que))
            self.server_starter.start()
            self.server_timer = QTimer()  # 服务端消息读取循环
            self.server_timer.timeout.connect(self.server_status)
            self.server_timer.start(200)

    def simple_viewer(self, add_files):
        """简明视图:仅包含按钮及进度条。"""
        # 简明视图样式:无框线,水平抬头不可见,按钮定宽,文件名列宽自适应留白
        self.file_table.setColumnCount(2)
        row = len(self.files) + len(add_files)
        self.file_table.setRowCount(row)
        self.file_table.setShowGrid(False)
        self.file_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        self.file_table.horizontalHeader().setVisible(False)
        self.file_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.file_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)

        num = 0
        for inst in add_files:
            prog_widget = QWidget()
            prog_stack = QStackedLayout()
            prog_stack.addWidget(inst.prog)
            prog_stack.addWidget(inst.label)
            prog_stack.setStackingMode(QStackedLayout.StackAll)
            prog_widget.setLayout(prog_stack)

            inst.button.clicked.connect(functools.partial(self.del_file, inst))
            inst.button.pressed.connect(self.button_pressed)
            inst.button.released.connect(self.button_released)

            index = num + len(self.files)
            num += 1
            self.file_table.setCellWidget(index, 0, inst.button)
            self.file_table.setCellWidget(index, 1, prog_widget)
        self.files += add_files
        self.file_table.show()
        for inst in self.files:  # 列表显示后再截断文件名
            inst.label.setText(self.shorten_filename(inst.name, self.file_table.columnWidth(1)))

    def detail_viewer(self, add_files):
        """详细视图:包括按钮、进度条、详细进度、文件大小、状态"""
        self.file_table.setColumnCount(5)
        row = len(self.files) + len(add_files)
        self.file_table.setRowCount(row)
        self.file_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        self.file_table.setHorizontalHeaderLabels(['', '文件名', '传输进度', '文件大小', '状态'])
        # 要用表头的ResizeMode函数而不能用列的ResizeMode函数,否则表头仍可拖动
        self.file_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.file_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)

        num = 0
        for inst in add_files:
            prog_stack = QStackedLayout()
            prog_stack.addWidget(inst.prog)
            prog_stack.addWidget(inst.label)
            prog_stack.setStackingMode(QStackedLayout.StackAll)
            prog_widget = QWidget()
            prog_widget.setLayout(prog_stack)
            # 定义按钮点击删除,长按清空的行为
            inst.button.clicked.connect(functools.partial(self.del_file, inst))
            inst.button.pressed.connect(self.button_pressed)
            inst.button.released.connect(self.button_released)

            file_prog = QTableWidgetItem('0.00 %')
            file_prog.setTextAlignment(Qt.AlignCenter)
            file_size = QTableWidgetItem(inst.size)
            file_size.setTextAlignment(Qt.AlignCenter)
            file_status = QTableWidgetItem(inst.status[1])
            file_status.setTextAlignment(Qt.AlignCenter)

            index = num + len(self.files)
            num += 1
            self.file_table.setCellWidget(index, 0, inst.button)
            self.file_table.setCellWidget(index, 1, prog_widget)
            self.file_table.setItem(index, 2, file_prog)
            self.file_table.setItem(index, 3, file_size)
            self.file_table.setItem(index, 4, file_status)
        self.files += add_files
        self.file_table.show()

        row_height = self.file_table.rowHeight(0) * self.file_table.rowCount()
        header_height = self.file_table.horizontalHeader().height()
        if row_height + header_height >= self.file_table.height():  # 计算是否出现滚动条
            for inst in self.files:
                changed_text = self.shorten_filename(inst.name, self.file_table.columnWidth(1) - self.reso_width / 192)
                inst.label.setText(changed_text)
        else:
            for inst in self.files:
                changed_text = self.shorten_filename(inst.name, self.file_table.columnWidth(1))
                inst.label.setText(changed_text)

    def ui_sending(self):
        """将UI转变为传输中状态。"""
        self.Bfile_sender.setText('中止传输')
        self.Bfile_sender.clicked.disconnect(self.file_checker)
        self.Bfile_sender.clicked.connect(self.abort_trans)
        self.act_choose.setDisabled(True)
        self.act_send.setDisabled(True)
        self.act_stop_send.setDisabled(False)
        self.Bselector.setDisabled(True)
        for inst in self.files:  # 禁用删除按钮
            inst.button.setDisabled(True)

    def ui_pending(self):
        """将UI转变为等待中状态。"""
        self.Bfile_sender.setText('发送')
        self.Bfile_sender.clicked.disconnect(self.abort_trans)
        self.Bfile_sender.clicked.connect(self.file_checker)
        self.act_choose.setDisabled(False)
        self.act_stop_send.setDisabled(True)
        self.Bselector.setDisabled(False)
        for inst in self.files:  # 启用删除按钮
            inst.button.setDisabled(False)

    def ui_setting_checker(self):
        """UI设置窗口关闭后转变UI"""
        self.settings.beginGroup('UISetting')
        setting_status_bar = int(self.settings.value('status_bar', True))
        setting_chat_frame = int(self.settings.value('chat_frame', True))
        self.settings.endGroup()
        if setting_status_bar:
            self.status_bar.show()
        else:
            self.status_bar.hide()
        if setting_chat_frame:
            self.chat_frame.show()
        else:
            self.chat_frame.hide()

    def client_setting_checker(self):
        """客户端设置窗口关闭后转变删除延时"""
        self.settings.beginGroup('ClientSetting')
        self.setting_del_timeout = float(self.settings.value('del_timeout', 1))
        self.settings.endGroup()

    """进程启动函数"""

    def chat_checker(self):
        """聊天进程启动函数。"""
        text = self.Emessage_writer.text()
        if text:
            self.settings.beginGroup('ClientSetting')
            setting_host = self.settings.value('host', '127.0.0.1')
            setting_port = int(self.settings.value('server_port', 12345))
            self.settings.endGroup()
            try:
                if self.chat_sender.is_alive():  # 若上个消息没发完就发新消息则结束掉上个子进程
                    self.chat_sender.terminate()
                    self.chat_sender.join()
                    self.Emessage_area.moveCursor(QTextCursor.End)
                    self.Emessage_area.insertHtml('<font color=red>✘<font color=black> ')
            except AttributeError:
                pass
            self.Emessage_area.append('本机(localhost):\n    {0} '.format(text))
            self.chat_sender = Process(target=chat_client.chat_starter, name='ChatSender',
                                       args=(setting_host, setting_port, text, self.server_que))
            self.chat_sender.start()
            self.Emessage_writer.clear()

    def file_checker(self):
        """文件列表构建及客户端传输进程开启。"""
        self.ui_sending()
        self.settings.beginGroup('ClientSetting')
        setting_file_at_same_time = int(self.settings.value('file_at_same_time', 2))
        setting_host = self.settings.value('host', '127.0.0.1')
        setting_port = int(self.settings.value('server_port', 12345))
        self.settings.endGroup()
        for inst in self.files:
            inst.status = 'uploading'
            inst.prog.setValue(0)
            index = self.find_index_by_name(inst.name)
            self.file_table.item(index, 4).setText('传输中')
        path_list = [inst.path for inst in self.files]
        try:
            self.file_sender = Process(target=trans_client.starter, name='FileSender', args=(
                setting_host, setting_port, path_list, setting_file_at_same_time, self.client_que))
            self.file_sender.start()
            self.Lclient_status.setText(
                '''传输中:<font color=green>0<font color=black>/<font color=red>0<font color=black>/{0} 
                (<font color=green>Comp<font color=black>/<font color=red>Err<font color=black>/Up)'''.format(
                    len(self.find_instance_by_status('uploading'))))
        except Exception as e:
            self.Lclient_status.setText(repr(e))
        self.prog_timer = QTimer()  # 启动进度条更新监控
        self.prog_timer.timeout.connect(self.update_prog)
        self.prog_timer.start(5)

    """后台监控函数"""

    def server_status(self):
        """启动时循环读取服务端状态及更新聊天信息。"""
        try:
            message = self.server_que.get(block=False)
            if message['type'] == 'server_info':
                if message['message'] == 'error':
                    self.Lserver_status.setText(message['detail'])
                elif message['message'] == 'ready':
                    self.Lserver_status.setText('接收端已就绪')
                elif message['message'] == 'MD5_passed':
                    self.received_files += 1
                    self.succeed_files += 1
                    self.Lserver_status.setText(
                        '''{0}=<font color=green>{1}<font color=black>+<font color=red>{2}<font color=black> 
                        (Tol=<font color=green>Comp<font color=black>+<font color=red>Err<font color=black>)'''.format(
                            self.received_files, self.succeed_files, self.failed_files))
                elif message['message'] == 'MD5_failed':  # 失败数统计
                    self.received_files += 1
                    self.failed_files += 1
                    self.Lserver_status.setText(
                        '''{0}=<font color=green>{1}<font color=black>+<font color=red>{2}<font color=black> 
                        (Tol=<font color=green>Comp<font color=black>+<font color=red>Err<font color=black>)'''.format(
                            self.received_files, self.succeed_files, self.failed_files))
                elif message['message'] == 'started':  # 正在传输数统计
                    self.Lserver_status.setText(
                        '''新文件传入 {0}=<font color=green>{1}<font color=black>+<font color=red>{2}<font color=black> 
                        (Tol=<font color=green>Comp<font color=black>+<font color=red>Err<font color=black>)'''.format(
                            self.received_files, self.succeed_files, self.failed_files))
                elif message['message'] == 'aborted':  # 中断数统计
                    self.Lserver_status.setText(
                        '''传输中断 {0}=<font color=green>{1}<font color=black>+<font color=red>{2}<font color=black> 
                        (Tol=<font color=green>Comp<font color=black>+<font color=red>Err<font color=black>)'''.format(
                            self.received_files, self.succeed_files, self.failed_files))
            elif message['type'] == 'chat':
                if message['status'] == 'success':
                    self.chat_sender.terminate()
                    self.chat_sender.join()
                    self.Emessage_area.moveCursor(QTextCursor.End)
                    self.Emessage_area.insertHtml('<font color=green>✓<font color=black> ')
                elif message['status'] == 'failed':
                    self.chat_sender.terminate()
                    self.chat_sender.join()
                    self.Emessage_area.moveCursor(QTextCursor.End)
                    self.Emessage_area.insertHtml('<font color=red>✘<font color=black> ')
                elif message['status'] == 'received':
                    self.Emessage_area.append(
                        '{0}:{1}:\n    {2}'.format(message['from'][0], message['from'][1], message['message']))
                    self.Lserver_status.setText('收到来自{0}:{1}的聊天消息'.format(message['from'][0], message['from'][1]))
        except queue.Empty:
            pass

    def update_prog(self):
        """传输过程管理及进度条更新函数。"""
        try:
            message = self.client_que.get(block=False)
            inst = self.find_instance_by_name(message['name'])  # 考虑到简明视图的性能,不全局计算index
            if message['type'] == 'info':
                if message['message'] == 'MD5_passed':
                    inst.status = 'complete'
                    index = self.find_index_by_name(message['name'])
                    self.file_table.item(index, 4).setText('传输完成')
                    self.file_table.item(index, 2).setText('100 %')
                    self.del_list.append(inst.path)
                elif message['message'] == 'MD5_failed':
                    inst.status = 'error'
                    index = self.find_index_by_name(message['name'])
                    self.file_table.item(index, 4).setText('传输错误')
                    self.file_table.item(index, 2).setText('100 %')
                elif message['message'] == 'aborted':
                    for inst in self.find_instance_by_status('uploading'):
                        inst.status = 'error'
                        i = self.find_index_by_name(inst.name)
                        self.file_table.item(i, 4).setText('传输中断')
                    self.ui_pending()
                    self.prog_timer.stop()
                # 若无上传中的文件且未被中断,关闭传输进程,清空上传列表且提示完成结果
                if not self.find_instance_by_status('uploading') and self.prog_timer.isActive():
                    self.Lclient_status.setText(
                        '''传输完成:<font color=green>{0}<font color=black>/<font color=red>{1}<font color=black>/0 
                        (<font color=green>Comp<font color=black>/<font color=red>Err<font color=black>/Up)'''.format(
                            len(self.find_instance_by_status('complete')), len(self.find_instance_by_status('error'))))
                    self.file_sender.terminate()
                    self.file_sender.join()
                    self.file_sender.close()
                    self.prog_timer.stop()
                    if self.del_list and not self.find_instance_by_status('error'):
                        # 待删除列表不为空且没有传输错误则考虑是否删除源文件
                        self.settings.beginGroup('ClientSetting')
                        setting_del_source = int(self.settings.value('del_source', False))
                        self.settings.endGroup()
                        if setting_del_source:
                            msg_box = QMessageBox(self)
                            msg_box.setWindowTitle('成功')
                            msg_box.setIcon(QMessageBox.Information)
                            msg_box.setText('传输成功完成!\n点击确定删除源文件')
                            msg_box.addButton('确定', QMessageBox.AcceptRole)
                            msg_box.addButton('取消', QMessageBox.DestructiveRole)
                            reply = msg_box.exec()
                            if reply == QMessageBox.AcceptRole:
                                for path in self.del_list:
                                    try:
                                        os.remove(path)
                                    except Exception as e:
                                        msg_box = QMessageBox(self)
                                        msg_box.setWindowTitle('错误')
                                        msg_box.setIcon(QMessageBox.Critical)
                                        msg_box.setInformativeText('无法删除\n{0}'.format(path))
                                        msg_box.setText(repr(e))
                                        msg_box.addButton('确定', QMessageBox.AcceptRole)
                                        msg_box.exec()
                                self.remove_all()
                        else:
                            msg_box = QMessageBox(self)
                            msg_box.setWindowTitle('成功')
                            msg_box.setIcon(QMessageBox.Information)
                            msg_box.setText('传输成功完成!')
                            msg_box.addButton('确定', QMessageBox.AcceptRole)
                            msg_box.exec()
                    else:
                        msg_box = QMessageBox(self)
                        msg_box.setWindowTitle('警告')
                        msg_box.setIcon(QMessageBox.Warning)
                        msg_box.setText('传输完成,但有文件传输出错!')
                        msg_box.addButton('确定', QMessageBox.AcceptRole)
                        msg_box.exec()
                    self.ui_pending()
                else:
                    self.Lclient_status.setText(
                        '''传输中:<font color=green>{0}<font color=black>/<font color=red>{1}<font color=black>/{2} 
                        (<font color=green>Comp<font color=black>/<font color=red>Err<font color=black>/Up)'''.format(
                            len(self.find_instance_by_status('complete')), len(self.find_instance_by_status('error')),
                            len(self.find_instance_by_status('uploading'))))
            elif message['type'] == 'prog':
                inst.prog.setValue(message['part'] + 1)
                if self.setting_detail_view:  # 详细视图:更新进度百分比
                    index = self.find_index_by_name(message['name'])
                    file_prog = message['part'] / inst.prog.maximum() * 100
                    self.file_table.item(index, 2).setText('{0:.2f} %'.format(file_prog))
        except queue.Empty:
            pass

    """绑定事件函数"""

    def del_file(self, inst):
        """按钮绑定删除事件:从当前表格中找到行索引并删除以及删除文件列表中的实例。"""
        index = self.find_index_by_name(inst.name)
        self.file_table.removeRow(index)
        self.files.remove(inst)
        if not self.files:
            self.file_table.hide()
            self.Lfile_empty.show()
            self.act_send.setDisabled(True)
            self.Bfile_sender.setDisabled(True)

    def remove_all(self):
        for i in range(len(self.files), 0, -1):  # 批量删除时需要从末尾开始逐个删除
            self.del_file(self.files[i - 1])

    def button_pressed(self):
        self.button_timer = QTimer()
        self.button_timer.timeout.connect(self.remove_all)
        self.button_timer.setSingleShot(True)
        self.button_timer.start(self.setting_del_timeout * 1000)

    def button_released(self):
        self.button_timer.stop()

    """通用功能性函数"""

    def abort_trans(self):
        """中断传输函数:发送取消消息"""
        self.settings.beginGroup('ClientSetting')
        setting_host = self.settings.value('host', '127.0.0.1')
        setting_port = int(self.settings.value('server_port', 12345))
        self.settings.endGroup()
        try:
            self.file_sender.terminate()  # 关闭当前的发送进程
            self.file_sender.join()
            self.file_sender.close()
            del self.file_sender
        except ValueError:
            pass
        except AttributeError:
            pass

        self.abort_sender = Process(target=trans_client.starter, name='AbortSender',
                                    args=(setting_host, setting_port, '', None, self.client_que))
        self.abort_sender.start()
        self.abort_sender.join(5)
        if self.abort_sender.exitcode is None:  # join超时,未收到中断回包时
            msg_box = QMessageBox(self)
            msg_box.setWindowTitle('警告')
            msg_box.setIcon(QMessageBox.Warning)
            msg_box.setText('接收端无响应!')
            msg_box.addButton('确定', QMessageBox.AcceptRole)
            msg_box.exec()
            self.abort_sender.kill()
            for inst in self.find_instance_by_status('uploading'):
                inst.status = 'error'
                i = self.find_index_by_name(inst.name)
                self.file_table.item(i, 4).setText('传输中断')
            self.ui_pending()
            self.abort_sender.join()
        self.abort_sender.close()
        del self.abort_sender

    def shorten_filename(self, name, width):
        """根据给定的宽度截断文件名并添加...,返回截断后的文件名。"""
        metrics = QFontMetrics(self.font())
        if metrics.width(name) > width - self.reso_width / 128:
            for i in range(4, len(name)):  # 从第4个字符开始计算长度
                if metrics.width(name[:i]) > width - self.reso_width / 128:
                    return name[:i] + '...'
        return name

    def safe_close(self):
        """结束各个子进程及计时器,做好退出准备。"""
        self.settings.beginGroup('Misc')  # 记录关闭时的窗口尺寸
        self.settings.setValue('window_size', (self.geometry().width(), self.geometry().height()))
        self.settings.setValue('frame_width', (self.sender_frame.width(), self.chat_frame.width()))
        self.settings.endGroup()
        self.settings.sync()
        try:  # 关闭后台监控进程和传输子进程
            self.server_timer.stop()
            del self.server_timer
            self.server_starter.terminate()
            self.server_starter.join()
            self.server_starter.close()
            self.prog_timer.stop()
            del self.prog_timer
            self.file_sender.terminate()
            self.file_sender.join()
            self.file_sender.close()
        except ValueError:  # 忽略传输子进程已关闭时的异常
            pass
        except AttributeError:  # 忽略还未声明变量时的异常
            pass
        try:  # 关闭聊天子进程
            self.chat_sender.terminate()
            self.chat_sender.join()
            self.chat_sender.close()
        except ValueError:
            pass
        except AttributeError:
            pass

    def find_instance_by_name(self, name):
        """按文件名在文件列表中查找实例,没有返回None。"""
        for inst in self.files:
            if inst.name == name:
                return inst
        return None

    def find_instance_by_status(self, status):
        """按文件状态在文件列表中查找实例,没有返回空列表。"""
        inst_list = [inst for inst in self.files if inst.status[0] == status]
        return inst_list

    def find_index_by_name(self, name):
        """按文件名在表格视图中查找索引,没有返回None。"""
        row = self.file_table.rowCount()
        for i in range(row):
            if self.file_table.cellWidget(i, 1).layout().widget(1).toolTip() == name:
                return i
        return None

    """对话框实例创建函数"""

    def file_dialog(self):
        """文件选择对话框,制作文件实例。"""
        self.settings.beginGroup('Misc')
        setting_path_history = self.settings.value('path_history', '.')
        self.settings.endGroup()
        fname = QFileDialog.getOpenFileNames(self, '请选择文件', setting_path_history)
        if fname[0]:
            new_list = set(fname[0])
            old_list = {inst.path for inst in self.files}
            old_name = {inst.name for inst in self.files}
            same_list = {path for path in new_list if os.path.split(path)[1] in old_name}
            add_list = new_list - old_list - same_list  # 用集合set求纯新增文件路径列表(剔除重名项)
            add_files = [FileStatus(path) for path in add_list]
            self.Lfile_empty.hide()
            if not self.setting_detail_view:
                self.simple_viewer(add_files)
            else:
                self.detail_viewer(add_files)
            self.Bfile_sender.setDisabled(False)
            self.act_send.setDisabled(False)
            self.settings.beginGroup('Misc')
            self.settings.setValue('path_history', os.path.split(fname[0][-1])[0])
            self.settings.endGroup()
        self.settings.sync()

    def client_setting_dialog(self):
        self.client_setting = def_widget.ClientSettingDialog(self)
        self.client_setting.setAttribute(Qt.WA_DeleteOnClose)
        self.client_setting.destroyed.connect(self.client_setting_checker)
        self.client_setting.show()

    def server_setting_dialog(self):
        self.server_setting = def_widget.ServerSettingDialog(self)
        self.server_setting.setAttribute(Qt.WA_DeleteOnClose)
        self.server_setting.show()

    def ui_setting_dialog(self):
        self.ui_setting = def_widget.UIDialog(self)
        self.ui_setting.setAttribute(Qt.WA_DeleteOnClose)
        self.ui_setting.destroyed.connect(self.ui_setting_checker)
        self.ui_setting.show()

    """事件函数重载"""

    def closeEvent(self, event):
        """关闭事件函数:检查是否有传输中的文件并退出"""
        if self.find_instance_by_status('uploading'):
            msg_box = QMessageBox(self)
            msg_box.setWindowTitle('警告')
            msg_box.setIcon(QMessageBox.Warning)
            msg_box.setText('文件传输中,是否中断并退出?')
            msg_box.addButton('确定', QMessageBox.AcceptRole)
            msg_box.addButton('取消', QMessageBox.DestructiveRole)
            reply = msg_box.exec()
            if reply == QMessageBox.AcceptRole:
                self.abort_trans()
                self.safe_close()
                event.accept()
            else:
                event.ignore()
        else:
            self.safe_close()
            event.accept()

    def keyPressEvent(self, k):
        """按ESC时触发的关闭行为。"""
        if k.key() == Qt.Key_Escape:
            self.close()

    def resizeEvent(self, event):
        """调整窗口尺寸时触发。"""
        for inst in self.files:  # 根据表格列宽调整截断文件名
            changed_text = self.shorten_filename(inst.name, self.file_table.columnWidth(1))
            inst.label.setText(changed_text)