示例#1
0
 def __init__(self, parent=None, min=0, max=999999):
     super(intLineEdit, self).__init__(parent)
     self.min = min
     self.max = max
     qiv = QIntValidator()
     qiv.setRange(min, max)
     self.setValidator(qiv)
示例#2
0
 def __init__(self):
     super().__init__()
     self.resize(800, 150)
     self.setWindowTitle('AI智能打轴 (测试版)')
     layout = QGridLayout()
     self.setLayout(layout)
     layout.addWidget(QLabel('前侧留白(ms)'), 0, 0, 1, 1)
     self.beforeEdit = QLineEdit('20')
     validator = QIntValidator()
     validator.setRange(0, 5000)
     self.beforeEdit.setValidator(validator)
     self.beforeEdit.setFixedWidth(50)
     layout.addWidget(self.beforeEdit, 0, 1, 1, 1)
     layout.addWidget(QLabel(''), 0, 2, 1, 1)
     layout.addWidget(QLabel('后侧留白(ms)'), 0, 3, 1, 1)
     self.afterEdit = QLineEdit('300')
     self.afterEdit.setValidator(validator)
     self.afterEdit.setFixedWidth(50)
     layout.addWidget(self.afterEdit, 0, 4, 1, 1)
     layout.addWidget(QLabel(''), 0, 5, 1, 1)
     self.autoFill = QPushButton('填充字符')
     self.autoFill.setStyleSheet('background-color:#3daee9')
     self.autoFill.clicked.connect(self.setAutoFill)
     layout.addWidget(self.autoFill, 0, 6, 1, 1)
     self.fillWord = QLineEdit('#AI自动识别#')
     layout.addWidget(self.fillWord, 0, 7, 1, 1)
     layout.addWidget(QLabel(''), 0, 8, 1, 1)
     self.autoSpan = QPushButton('自动合并')
     self.autoSpan.setStyleSheet('background-color:#3daee9')
     self.autoSpan.clicked.connect(self.setAutoSpan)
     layout.addWidget(self.autoSpan, 0, 9, 1, 1)
     self.multiCheck = QPushButton('启用多进程')
     self.multiCheck.setStyleSheet('background-color:#3daee9')
     self.multiCheck.clicked.connect(self.setMultipleThread)
     layout.addWidget(self.multiCheck, 0, 10, 1, 1)
     self.processBar = QProgressBar()
     layout.addWidget(self.processBar, 1, 0, 1, 10)
     self.checkButton = QPushButton('开始')
     self.checkButton.setFixedWidth(100)
     self.checkButton.clicked.connect(self.separateProcess)
     layout.addWidget(self.checkButton, 1, 10, 1, 1)
示例#3
0
class MainWindow(QMainWindow):
    def __init__(self, ui_file, devMode, parent=None):
        super(MainWindow, self).__init__()
        self.uiFileName = ui_file  # use this if there are multiple UI files
        if devMode == "development":  # use ui file directly made by QDesigner
            uiFile = QFile(ui_file)
            uiFile.open(QFile.ReadOnly)  # read in UI for the form
            loader = QUiLoader()
            self.window = loader.load(uiFile)
            uiFile.close()
        else:  #deployment - use .py version of ui file, with slightly different setup
            self.ui = Ui_MainWindow()
            self.ui.setupUi(self)
        self.getParts()  # identify form objects
        self.connectParts()  # connect buttons to operations
        self.convs = Converters(
        )  # load converters for main conversion categories
        self.yc = YearConverters(
        )  # load converters for japanese years, zodiac years
        self.mess = Mess()  # instructions and messages in local language
        self.validFloat = QDoubleValidator()
        self.validYear = QIntValidator()
        self.validYear.setRange(1, 9999)  # used for years
        self.btnConvert.hide()
        self.widgetSetup("start")  # initial conditions for form
        if devMode == "development": self.window.show()
        else: self.show()

    def widgetSetup(self, themode):  # based on which main button is pressed
        self.themode = themode  # this is set when a main button is pressed, or on initial load
        self.emptyConvLayout(
        )  # get rid of old radio buttons if they exist, hide input/output, etc.
        thetext, thestyle = makeHeader1(
            themode, self.uiFileName)  # make heading for convType = themode
        self.header1.setStyleSheet(
            thestyle)  # can have different background colors
        self.header1.setText(thetext)
        self.header1.show()
        self.hideBigInstructions()
        if themode in self.convs.getValidConvTypes(
        ):  # set up radio buttons for conversions
            oneConvTypeInfo = self.convs.convTypeToConvInfo(
                themode)  # get convCodes, displays for this convType
            for ix, (convCode, convDisplay) in enumerate(
                    oneConvTypeInfo
            ):  # iterate through enumerated list of tuples
                self.oneRadio = QRadioButton(
                    "{}".format(convDisplay))  # text for button
                self.oneRadio.setObjectName("object_{}".format(
                    convCode))  # to be used later by sender()
                self.oneRadio.setStyleSheet(
                    makeStyleSheet("radiobutton", self.uiFileName))
                self.layoutConv.addWidget(self.oneRadio)
                self.oneRadio.clicked.connect(
                    self.convSetup
                )  # convSetup will call sender() to find convCode
                if ix == 0:
                    self.oneRadio.setFocus(
                    )  # set focus on first button in list
        if themode == "fromjpyear":  # set up radio buttons for modern eras
            self.fromjpyearmode = "modern"  # start with modern eras only
            self.showJpEras()
        if themode == "fromjpyearhistoric":  # set up radio buttons for all eras
            self.fromjpyearmode = "all"  # start with modern eras only
            self.showJpEras()
        if themode == "tojpyear":  # no choices, go straight to input box
            self.showInstructions(self.mess.getToJpYear(self.yc.getMinYear()),
                                  "")
            self.input1.setText(str(
                self.yc.getNowYear()))  # start with current year
            self.setUpValidator()
            self.convertUnits()
        if themode == "zodiac":  # no choices, go straight to input box
            self.showInstructions(self.mess.getEnterYearZodiac(), "")
            self.input1.setText(str(
                self.yc.getNowYear()))  # start with current year
            self.setUpValidator()
            self.convertUnits()
        if themode == "start":
            self.btnFromMetric.setFocus()
            self.showBigInstructions(self.mess.getStartMsg2())

    def showBigInstructions(self, themsg):
        self.instructions2.setText(themsg)
        self.scrollArea.hide()
        self.instructions2.show()

    def hideBigInstructions(self):
        self.scrollArea.show()
        self.instructions2.hide()

    def showJpEras(self):  # display radio buttons for Japanese eras
        jpEraList = self.yc.getEraNamesPlusCodes(
            self.fromjpyearmode)  # modern or all
        for ix, (eraText, eraCode) in enumerate(jpEraList):
            self.oneRadio = QRadioButton(
                "{}".format(eraText))  # text for button
            self.oneRadio.setObjectName(
                "object_{}".format(eraCode))  # to be used later by sender()
            self.oneRadio.setStyleSheet(
                makeStyleSheet("radiobutton", self.uiFileName))
            self.layoutConv.addWidget(self.oneRadio)
            self.oneRadio.clicked.connect(
                self.eraSetup)  # eraSetup will call sender() to find eraCode
            if ix == 0: self.oneRadio.setFocus()  # set focus on first button
        if self.fromjpyearmode == "modern":
            self.oneRadio = QRadioButton(
                "{}".format("Show more eras"))  # now add option for more eras
            self.oneRadio.setObjectName(
                "object_{}".format("all"))  # to be used later by sender()
        else:
            self.oneRadio = QRadioButton("{}".format(
                "Show fewer eras"))  # now add option to return to modern eras
            self.oneRadio.setObjectName(
                "object_{}".format("modern"))  # to be used later by sender()
        self.oneRadio.setStyleSheet(
            makeStyleSheet("radiobutton", self.uiFileName))
        self.layoutConv.addWidget(self.oneRadio)
        self.oneRadio.clicked.connect(
            self.eraSetup)  # eraSetup will call sender() to find eraCode

    def eraSetup(self):  # this is called when radio button is chosen
        theEra = self.getCodeFromSenderName(
        )  # era or mode is determined by object name of sending radio button
        if theEra in ["all", "modern"]:  # switch between modern and all
            self.fromjpyearmode = theEra  # set the mode here
            self.emptyConvLayout()  # clear former list
            if theEra == "all": self.widgetSetup("fromjpyearhistoric")
            else: self.widgetSetup("fromjpyear")
        else:  # theEra will be eraCode for chosen era
            self.chosenEra = theEra  # set this property
            if self.yc.getNowEra() == theEra:
                theHint = "(1- )"  # no final year if it's the current era
            else:
                theHint = "(1-{})".format(
                    self.yc.getNumYears(theEra))  # final year in that era
            self.showInstructions(
                self.mess.getFromJpYear(self.yc.getENameUnparsed(theEra)),
                theHint)
            self.output2.hide()
            self.setUpValidator()

    def setUpValidator(self):
        if self.themode in self.convs.getValidConvTypes():  # measures
            if self.convs.isTempConv(self.theConvCode):
                self.validFloat.setBottom(
                    -999999)  # temperatures can be negative
            else:
                self.validFloat.setBottom(
                    0.0)  # other measures can't be negative
            self.input1.setValidator(self.validFloat)
        if self.themode in [
                "fromjpyear", "fromjpyearhistoric", "tojpyear", "zodiac"
        ]:
            self.input1.setValidator(
                self.validYear)  # range already set to 1-9999

    def convSetup(self):  # set up based on which radio button was set
        self.theConvCode = self.getCodeFromSenderName(
        )  # get the code based on the sending button, and set it
        self.showInstructions(self.mess.getEnterAmt())
        self.setUpValidator()
        self.input1.setText("1")  # show units=1 to start
        self.convertUnits()

    def getCodeFromSenderName(self):  # based on objectName of sender button
        sending_button = self.sender()
        return str(sending_button.objectName()).replace("object_", "")

    def convertUnits(self):  # main conversion routine
        pstart = self.mess.getPstart(
        )  # get paragraph HTML from Message object
        pstopstart = self.mess.getPstopstart()
        pstop = self.mess.getPstop()
        jcol = self.mess.getJColor()  # special color for Japanese text
        if len(self.input1.text()) < 1:  # test for blank input
            self.output2.setText(
                self.mess.getBlankConvertMsg())  # show error message
            self.output2.show()
            return
        if self.themode in self.convs.getValidConvTypes(
        ):  # standard conversions: fromjpmeasure, tometric, etc
            amt1Text = self.input1.text()
            amt1 = strToNum(amt1Text)
            if self.convs.isTooCold(self.theConvCode, amt1):
                eqString = self.mess.getTooCold(
                )  #check for temp below abs zero
            else:
                eqString = self.convs.getEquation(self.theConvCode, amt1,
                                                  " =" + pstopstart, jcol)
            self.output2.setText(
                pstart + eqString +
                pstop)  # use paragraphs for better control of appearance
        elif self.themode == "tojpyear":  # int'l year to Japanese year
            try:
                iYear = int(self.input1.text())
                yearDisplay = self.mess.makeJYearDisplay(
                    self.yc.iYearToJYear(iYear, jcol), iYear)
            except ValueError as errorMsg:
                yearDisplay = pstart + str(
                    errorMsg) + pstop  # display the error message
            self.output2.setText(yearDisplay)
            #print (yearDisplay)
        elif self.themode in ["fromjpyear", "fromjpyearhistoric"
                              ]:  # Japanese year to int'l year
            jYear = int(self.input1.text()
                        )  # validator at work, so this should be an integer
            try:  # raise exception if not in range
                iYear = self.yc.jYearToIYear(self.chosenEra, jYear)
                yearDisplay = pstart +  "{} ({}) {}".format(self.yc.getENameUnparsed(self.chosenEra), \
                    self.yc.getJName(self.chosenEra, jcol), jYear) + \
                    pstopstart + self.mess.getIs() + " " + str(iYear) + pstop
            except ValueError as errorMsg:
                yearDisplay = pstart + str(
                    errorMsg) + pstop  # display the error message
            self.output2.setText(yearDisplay)
        elif self.themode == "zodiac":  # international year to zodiac sign
            iYear = int(self.input1.text())
            # display is HTML with paragraph, line height, and Japanese characters in color (specified in Mess class)
            yearDisplay = pstart + str(iYear) + " " + self.mess.getIs() + ":" + pstopstart + \
                self.mess.getYearOfThe().capitalize() + " " + self.yc.getZodEName(iYear) + pstop
            self.output2.setText(yearDisplay)
            zBig = self.mess.makeZodiacBigDisplay(
                self.yc.getZodEName(iYear), self.yc.getZodJName(iYear, jcol),
                self.yc.getZodJZName(iYear, jcol))
            self.showBigInstructions(zBig)
        self.output2.show()  # common to all conversion types

    def emptyConvLayout(
            self):  # clear the radio buttons in the converter/jpYear layout
        for i in reversed(range(
                self.layoutConv.count())):  # delete them in reverse order
            self.layoutConv.itemAt(i).widget().deleteLater()
        self.instructions1.hide()
        self.input1.setPlaceholderText("")  # clear previous hints
        self.input1.hide()  # conversion not chosen yet, so hide this
        self.btnConvert.hide()  # conversion not chosen yet, so hide this
        self.output2.hide()  # conversion not chosen yet, so hide this

    def showInstructions(
            self,
            instructionsText,
            inputHint=""):  # position UI elements, set label, show input box
        self.instructions1.setText(instructionsText)
        self.instructions1.show()
        self.input1.setText("")
        self.input1.setPlaceholderText(inputHint)
        self.input1.show()
        self.btnConvert.show()
        self.input1.setFocus()

    def convertQuickly(self):  # testing
        if not self.input1.text(): amt = 0
        else: amt = float(self.input1.text()) * 2.0
        amtString = '{} kilograms'.format(amt)
        self.output2.setText(amtString)

    def getParts(self):  # map form elements to object properties
        self.centralw = self.findWidget(QWidget, 'central_widget')
        self.input1 = self.findWidget(QLineEdit, 'input1')
        self.output2 = self.findWidget(QTextEdit, 'label_output2')
        self.header1 = self.findWidget(QLabel, 'label_header1')
        self.btnConvert = self.findWidget(QPushButton, 'button_convert')
        self.btnExit = self.findWidget(QPushButton, 'button_exit')
        self.btnFromMetric = self.findWidget(QPushButton, 'button_from_metric')
        self.btnToMetric = self.findWidget(QPushButton, 'button_to_metric')
        self.btnFromJpMeasure = self.findWidget(QPushButton,
                                                'button_from_jpmeasure')
        self.btnToJpMeasure = self.findWidget(QPushButton,
                                              'button_to_jpmeasure')
        self.btnFromJpYear = self.findWidget(QPushButton, 'button_from_jpyear')
        self.btnToJpYear = self.findWidget(QPushButton, 'button_to_jpyear')
        self.btnZodiac = self.findWidget(QPushButton, 'button_zodiac')
        self.scrollArea = self.findWidget(QScrollArea, 'conv_layout')
        content_widget = QWidget(
        )  # now add the QVBoxLayout widget programmatically, for scrolling
        self.scrollArea.setWidget(content_widget)
        self.scrollArea.setWidgetResizable(True)
        self.layoutConv = QVBoxLayout(content_widget)
        self.layoutConv.setAlignment(
            Qt.AlignTop
        )  # don't evenly space the radio buttons, but start at the top
        self.instructions1 = self.findWidget(QLabel, 'label_instructions1')
        self.instructions2 = self.findWidget(QLabel, 'label_instructions2')
        self.menuExit = self.findWidget(QAction, 'action_exit')
        self.menuFromMetric = self.findWidget(QAction, 'action_from_metric')
        self.menuToMetric = self.findWidget(QAction, 'action_to_metric')
        self.menuFromJpMeasure = self.findWidget(QAction,
                                                 'action_from_jpmeasure')
        self.menuToJpMeasure = self.findWidget(QAction, 'action_to_jpmeasure')
        self.menuFromJpYear = self.findWidget(QAction, 'action_from_jpyear')
        self.menuFromJpYearHistoric = self.findWidget(
            QAction, 'action_from_jpyear_historic')
        self.menuToJpYear = self.findWidget(QAction, 'action_to_jpyear')
        self.menuZodiac = self.findWidget(QAction, 'action_zodiac')
        self.menubar = self.findWidget(QMenuBar, 'menubar')
        if self.uiFileName == "main3.ui":
            self.menubar.setStyleSheet(
                getMenuStyle("main3.ui"))  # in jpconvhelper.py

    def findWidget(self, widgetType, widgetName):
        if devMode == "development":
            return self.window.findChild(widgetType, widgetName)
        else:
            return self.findChild(widgetType, widgetName)

    def connectParts(self):  # connect buttons and menu actions to operations
        self.input1.returnPressed.connect(self.convertUnits)
        self.btnConvert.clicked.connect(self.convertUnits)
        self.btnExit.clicked.connect(self.exitHandler)
        self.menuExit.triggered.connect(self.exitHandler)
        self.btnFromMetric.clicked.connect(
            lambda: self.widgetSetup("frommetric"))
        self.menuFromMetric.triggered.connect(
            lambda: self.widgetSetup("frommetric"))
        self.btnToMetric.clicked.connect(lambda: self.widgetSetup("tometric"))
        self.menuToMetric.triggered.connect(
            lambda: self.widgetSetup("tometric"))
        self.btnFromJpMeasure.clicked.connect(
            lambda: self.widgetSetup("fromjpmeasure"))
        self.menuFromJpMeasure.triggered.connect(
            lambda: self.widgetSetup("fromjpmeasure"))
        self.btnToJpMeasure.clicked.connect(
            lambda: self.widgetSetup("tojpmeasure"))
        self.menuToJpMeasure.triggered.connect(
            lambda: self.widgetSetup("tojpmeasure"))
        self.btnFromJpYear.clicked.connect(
            lambda: self.widgetSetup("fromjpyear"))
        self.menuFromJpYear.triggered.connect(
            lambda: self.widgetSetup("fromjpyear"))
        self.menuFromJpYearHistoric.triggered.connect(
            lambda: self.widgetSetup("fromjpyearhistoric"))
        self.btnToJpYear.clicked.connect(lambda: self.widgetSetup("tojpyear"))
        self.menuToJpYear.triggered.connect(
            lambda: self.widgetSetup("tojpyear"))
        self.btnZodiac.clicked.connect(lambda: self.widgetSetup("zodiac"))
        self.menuZodiac.triggered.connect(lambda: self.widgetSetup("zodiac"))

    def exitHandler(self):
        app.exit()
示例#4
0
class MainWindow(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.setWindowTitle('MASK标注工具')
        self.setFixedSize(1000, 650)
        self.move_to_center()

        #
        self.mask_img_size = QSize(512, 512)
        self.brush_color = QColor(255, 255, 0)
        self.eraser_color = QColor(0, 0, 0)
        self.label_img = ImageLabel(self, self.mask_img_size, self.brush_color,
                                    self.eraser_color)
        self.label_img.setAlignment(Qt.AlignCenter)
        self.label_img.setText('没有选择任何图片')
        self.label_img.setFixedWidth(700)
        self.label_img.setFixedHeight(600)

        #
        self.btn_select_dir = QPushButton(self)
        self.btn_select_dir.setText('选择目录...')
        self.btn_select_dir.clicked.connect(self.on_btn_select_dir)

        self.btn_prev_img = QPushButton(self)
        self.btn_prev_img.setText('上一张')
        self.btn_prev_img.clicked.connect(self.on_btn_prev_img)
        self.connect(QShortcut(QKeySequence(Qt.Key_Left), self),
                     QtCore.SIGNAL('activated()'), self.btn_prev_img.click)

        self.btn_next_img = QPushButton(self)
        self.btn_next_img.setText('下一张')
        self.btn_next_img.clicked.connect(self.on_btn_next_img)
        self.connect(QShortcut(QKeySequence(Qt.Key_Right), self),
                     QtCore.SIGNAL('activated()'), self.btn_next_img.click)

        self.btn_select_brush_color = QPushButton(self)
        self.btn_select_brush_color.setText('修改画笔颜色')
        self.btn_select_brush_color.clicked.connect(
            self.on_btn_select_brush_color)

        self.label_brush_color = QLabel(self)
        self.label_brush_color.setFixedWidth(50)

        pe = QPalette()
        pe.setColor(QPalette.Window, self.brush_color)
        self.label_brush_color.setPalette(pe)
        self.label_brush_color.setAutoFillBackground(True)

        self.defalut_brush_pixle_size = 5
        self.min_brush_pixle_size = 1
        self.max_brush_pixle_size = 50
        self.label_brush_pixle_size = QLabel('画笔像素大小:')
        self.label_brush_pixle_size.setFixedWidth(110)
        self.edit_brush_pixle_size_validator = QIntValidator()
        self.edit_brush_pixle_size_validator.setRange(
            self.min_brush_pixle_size, self.max_brush_pixle_size)
        self.edit_brush_pixle_size = QLineEdit(self)
        self.edit_brush_pixle_size.setText(f'{self.defalut_brush_pixle_size}')
        self.edit_brush_pixle_size.setValidator(
            self.edit_brush_pixle_size_validator)
        self.edit_brush_pixle_size.textChanged.connect(
            self.on_edit_brush_pixle_size_change)

        self.label_img.update_brush_pixle_size(self.defalut_brush_pixle_size)

        self.defalut_eraser_pixle_size = 50
        self.min_eraser_pixle_size = 5
        self.max_eraser_pixle_size = 50
        self.label_eraser_pixle_size = QLabel('橡皮擦像素大小:')
        self.label_eraser_pixle_size.setFixedWidth(110)
        self.edit_eraser_pixle_size_validator = QIntValidator()
        self.edit_eraser_pixle_size_validator.setRange(
            self.min_eraser_pixle_size, self.max_eraser_pixle_size)
        self.edit_eraser_pixle_size = QLineEdit(self)
        self.edit_eraser_pixle_size.setText(
            f'{self.defalut_eraser_pixle_size}')
        self.edit_eraser_pixle_size.setValidator(
            self.edit_eraser_pixle_size_validator)
        self.edit_eraser_pixle_size.textChanged.connect(
            self.on_edit_eraser_pixle_size_change)

        self.label_img.update_eraser_pixle_size(self.defalut_eraser_pixle_size)

        self.btn_clear_mask = QPushButton(self)
        self.btn_clear_mask.setText('全部擦除')
        self.btn_clear_mask.clicked.connect(self.on_btn_clear_mask)

        self.btn_roate = QPushButton(self)
        self.btn_roate.setText('旋转')
        self.btn_roate.clicked.connect(self.on_btn_roate)

        self.btn_roate_img = QPushButton(self)
        self.btn_roate_img.setText('旋转原图')
        self.btn_roate_img.clicked.connect(self.on_btn_roate_img)
        self.connect(QShortcut(QKeySequence(Qt.Key_Up), self),
                     QtCore.SIGNAL('activated()'), self.btn_roate_img.click)

        self.btn_roate_mask = QPushButton(self)
        self.btn_roate_mask.setText('旋转标注图')
        self.btn_roate_mask.clicked.connect(self.on_btn_roate_mask)

        self.label_lable_docs = QLabel(self)
        self.label_lable_docs.setAlignment(Qt.AlignLeft)
        self.label_lable_docs.setText(r'''
- 鼠标左键拖动,绘制标注内容
- ALT+鼠标左键拖动,擦除标注内容

- CTRL+鼠标滚轮,调整画笔像素大小
- ALT+鼠标滚轮,调整橡皮擦像素大小

- 键盘左方向键切换到上一张图片
- 键盘右方向键切换到下一张图片

- 输入张数加回车跳转到指定张数
        ''')

        self.label_status_running1 = QLabel(self)
        self.label_status_running1.setAlignment(Qt.AlignLeft)
        self.label_status_running1.setText('请选择需要标注的目录')

        self.label_status_page_number_validator = QIntValidator()
        self.label_status_page_number = QLineEdit(self)
        self.label_status_page_number.setMaximumWidth(50)
        self.label_status_page_number.setValidator(
            self.label_status_page_number_validator)
        self.label_status_page_number.hide()
        self.label_status_page_number.returnPressed.connect(self.on_page_jump)

        self.label_status_running2 = QLabel(self)
        self.label_status_running2.setAlignment(Qt.AlignLeft)
        self.label_status_running2.setText('张')
        self.label_status_running2.hide()

        # 布局
        layout_root = QVBoxLayout()

        layout_root2 = QHBoxLayout()
        layout_col1 = QVBoxLayout()
        layout_col2 = QVBoxLayout()
        layout_root2.addLayout(layout_col1)
        layout_root2.addLayout(layout_col2)
        layout_root_row2 = QHBoxLayout()

        layout_root.addLayout(layout_root2)
        layout_root.addLayout(layout_root_row2)

        layout_root_row2.addWidget(self.label_status_running1)
        layout_root_row2.addWidget(self.label_status_page_number)
        layout_root_row2.addWidget(self.label_status_running2)

        layout_col1.addWidget(self.label_img)

        layout_col2_row1 = QHBoxLayout()
        layout_col2_row1.addWidget(self.btn_select_dir)

        layout_col2_row2 = QHBoxLayout()
        layout_col2_row2.addWidget(self.btn_prev_img)
        layout_col2_row2.addWidget(self.btn_next_img)

        layout_col2_row3 = QHBoxLayout()
        layout_col2_row3.addWidget(self.label_brush_pixle_size)
        layout_col2_row3.addWidget(self.edit_brush_pixle_size)

        layout_col2_row4 = QHBoxLayout()
        layout_col2_row4.addWidget(self.label_eraser_pixle_size)
        layout_col2_row4.addWidget(self.edit_eraser_pixle_size)

        layout_col2_row5 = QHBoxLayout()
        layout_col2_row5.addWidget(self.btn_select_brush_color)
        layout_col2_row5.addWidget(self.label_brush_color)

        layout_col2.addLayout(layout_col2_row1)
        layout_col2.addLayout(layout_col2_row2)
        layout_col2.addLayout(layout_col2_row5)
        layout_col2.addLayout(layout_col2_row3)
        layout_col2.addLayout(layout_col2_row4)
        layout_col2.addWidget(self.btn_clear_mask)
        layout_col2.addWidget(self.btn_roate)
        layout_col2.addWidget(self.btn_roate_img)
        layout_col2.addWidget(self.btn_roate_mask)
        layout_col2.addWidget(self.label_lable_docs)
        layout_col2.addStretch()

        self.setLayout(layout_root)

        # 其他数据
        self.directory = None
        self.all_img_file = []
        self.all_img_file_index = 0

        self.update_btn_status()

    def move_to_center(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) / 2,
                  (screen.height() - size.height()) / 2)

    def on_btn_select_dir(self):
        try:
            self.directory = QFileDialog.getExistingDirectory(self, '选择图片目录')

            all_img_file = sorted([
                x for x in Path(self.directory).iterdir() if x.is_file()
                and x.suffix.upper() in ['.JPG', '.JPEG', '.BMP', '.PNG']
            ])

            if len(all_img_file) <= 0:
                QMessageBox.information(self, '<提示>',
                                        f'{self.directory}\n目录下没有找到图片文件',
                                        QMessageBox.Ok)
                return

            self.setWindowTitle(f'MASK标注工具: {self.directory}')
            self.all_img_file = all_img_file
            self.all_img_file_index = 0
            self.show_label_img()
        finally:
            self.update_btn_status()

    def on_btn_next_img(self):
        try:
            self.all_img_file_index += 1
            self.show_label_img()
        finally:
            self.update_btn_status()

    def on_btn_prev_img(self):
        try:
            self.all_img_file_index -= 1
            self.show_label_img()
        finally:
            self.update_btn_status()

    def on_btn_select_brush_color(self):
        self.brush_color = QColorDialog.getColor()
        self.label_img.update_brush_color(self.brush_color)

        pe = QPalette()
        pe.setColor(QPalette.Window, self.brush_color)
        self.label_brush_color.setPalette(pe)
        self.label_brush_color.setAutoFillBackground(True)

    def on_btn_clear_mask(self):
        self.show_label_img(do_clear=True)

    def on_btn_roate(self):
        self.show_label_img(do_roate=True)

    def on_btn_roate_img(self):
        self.show_label_img(do_roate_img=True)

    def on_btn_roate_mask(self):
        self.show_label_img(do_roate_mask=True)

    def on_page_jump(self):
        try:
            page_num = int(self.label_status_page_number.text())
            if page_num >= 1 and page_num <= len(self.all_img_file):
                self.all_img_file_index = page_num - 1
            self.show_label_img()
            self.setFocus()
        finally:
            self.update_btn_status()

    def wheelEvent(self, event):
        if QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier:
            brush_pixle_size = int(self.edit_brush_pixle_size.text())
            delta = event.delta()
            if delta > 0:
                brush_pixle_size += 1
            elif delta < 0:
                brush_pixle_size -= 1

            if brush_pixle_size >= self.min_brush_pixle_size and brush_pixle_size <= self.max_brush_pixle_size:
                self.edit_brush_pixle_size.setText(f'{brush_pixle_size}')
        elif QApplication.keyboardModifiers() == QtCore.Qt.AltModifier:
            eraser_pixle_size = int(self.edit_eraser_pixle_size.text())
            delta = event.delta()
            if delta > 0:
                eraser_pixle_size += 1
            elif delta < 0:
                eraser_pixle_size -= 1

            if eraser_pixle_size >= self.min_eraser_pixle_size and eraser_pixle_size <= self.max_eraser_pixle_size:
                self.edit_eraser_pixle_size.setText(f'{eraser_pixle_size}')

    def on_edit_brush_pixle_size_change(self):
        if self.edit_brush_pixle_size.text():
            brush_pixle_size = int(self.edit_brush_pixle_size.text())
            self.label_img.update_brush_pixle_size(brush_pixle_size)

    def on_edit_eraser_pixle_size_change(self):
        if self.edit_eraser_pixle_size.text():
            eraser_pixle_size = int(self.edit_eraser_pixle_size.text())
            self.label_img.update_eraser_pixle_size(eraser_pixle_size)

    def update_btn_status(self):
        try:
            self.btn_next_img.setEnabled(False)
            self.btn_prev_img.setEnabled(False)
            self.btn_roate.setEnabled(False)
            self.btn_roate_img.setEnabled(False)
            self.btn_roate_mask.setEnabled(False)
            self.btn_clear_mask.setEnabled(False)

            if not self.all_img_file:
                self.label_status_running1.setText('请选择需要标注的目录')
                self.label_status_page_number.hide()
                self.label_status_running2.hide()
            else:
                img_name = self.all_img_file[self.all_img_file_index]

                # self.label_status_page_number.show()
                # self.label_status_running2.show()
                self.label_status_page_number_validator.setRange(
                    1, len(self.all_img_file))
                self.label_status_page_number.setText(
                    f'{self.all_img_file_index+1}')
                self.label_status_running1.setText(
                    f'当前图片: {img_name} ({self.all_img_file_index + 1}/{len(self.all_img_file)}) 跳转到'
                )
                self.label_status_running2.setText(f'张')

                if self.all_img_file_index == 0:
                    self.btn_prev_img.setEnabled(False)
                else:
                    self.btn_prev_img.setEnabled(True)

                if self.all_img_file_index == len(self.all_img_file) - 1:
                    self.btn_next_img.setEnabled(False)
                else:
                    self.btn_next_img.setEnabled(True)

                self.btn_roate.setEnabled(True)
                self.btn_roate_img.setEnabled(True)
                self.btn_roate_mask.setEnabled(True)
                self.btn_clear_mask.setEnabled(True)
        except:
            logging.exception('update_btn_status exception')

    def show_label_img(self,
                       do_roate=False,
                       do_roate_img=False,
                       do_roate_mask=False,
                       do_clear=False):
        if not self.all_img_file:
            return

        img_path = self.all_img_file[self.all_img_file_index]
        img = QPixmap(str(img_path))

        img_mask_path = img_path.parent.joinpath(f'mask/{img_path.stem}.bmp')
        img_mask_path.parent.mkdir(parents=True, exist_ok=True)

        if img_mask_path.is_dir():
            QMessageBox.warning(
                self, '<致命错误>',
                f'标注文件<{img_mask_path}>是一个目录, 请把它拷贝到别的地方, 然后重新打开标注工具!!!',
                QMessageBox.Ok)
            sys.exit(-1)

        if img_mask_path.exists():
            img_mask = QPixmap(str(img_mask_path))
            if img_mask.size() != self.mask_img_size:
                os.remove(str(img_mask_path))

        if not img_mask_path.exists() or do_clear:
            img_mask = QPixmap(self.mask_img_size)
            img_mask.fill(QColor(0, 0, 0))
            img_mask.save(str(img_mask_path))

        if do_roate:
            rm = QMatrix()
            rm.rotate(90)
            img = img.transformed(rm)
            img.save(str(img_path))
            img_mask = img_mask.transformed(rm)
            img_mask.save(str(img_mask_path))

        if do_roate_img:
            rm = QMatrix()
            rm.rotate(90)
            img = img.transformed(rm)
            img.save(str(img_path))

        if do_roate_mask:
            rm = QMatrix()
            rm.rotate(90)
            img_mask = img_mask.transformed(rm)
            img_mask.save(str(img_mask_path))

        self.label_img.update_label_img(img, img_mask, str(img_mask_path))