Ejemplo n.º 1
0
class mainWindow(QMainWindow):
    def __init__(self):
        super(mainWindow, self).__init__()
        self.__image = None  # SimpleITK.Image
        self.__grade = None  # Numpy array with shape [num_slice, num_class]
        self.child_stat = statAnalyzeWidget()
        self.cls_SVC = clsABWidget(ifSVC=True)
        self.cls_Logi = clsABWidget(ifSVC=False)
        self.statusBar().showMessage('Initializing UI...')
        self.initUI()
        self.statusBar().showMessage('Initializing Core...')
        self.main = mainLogic()
        self.statusBar().showMessage('Initializing Signal...')
        self.initSignals()
        self.statusBar().showMessage('Ready')

    def initUI(self):
        self.setWindowTitle('Radiomics System')
        screenGeometry = QApplication.desktop().screenGeometry()
        aspectRatio = 4 / 3.0
        blockLen = min(screenGeometry.width()/aspectRatio, \
                screenGeometry.height())
        self.resize(blockLen * aspectRatio, blockLen)
        #qr = self.frameGeometry()
        #cp = QDesktopWidget().availableGeometry().center()
        #qr.moveCenter(cp)
        #self.move(qr.topLeft())
        self.initUI_Menubar()
        self.initUI_Toolbar()
        self.initUI_ContextMenu()
        self.initUI_Layout()
        self.show()

    def initUI_Menubar(self):
        menubar = self.menuBar()
        self.menuLoad = menubar.addMenu('Load')
        # self.menutools = QMenu(menubar)
        # self.menutools.setObjectName("menutools")
        self.menuImage = self.menuLoad.addMenu('Image')
        self.menuROI = self.menuLoad.addMenu('ROI')
        # self.menuload.setObjectName("menuload")
        self.actionimagedir = QAction('DICOM DIR', self)
        self.actionimagedir.triggered.connect(self.actLoadImgDir)
        self.actionimagefiles = QAction('NIfTI File', self)
        self.actionimagefiles.triggered.connect(self.actLoadStudy)

        self.actionROIdir = QAction('DICOM DIR', self)
        self.actionROIdir.triggered.connect(self.actLoadROIDir)
        self.actionROIfiles = QAction('NIfTI File', self)
        self.actionROIfiles.triggered.connect(self.actLoadROI)
        self.menuImage.addAction(self.actionimagedir)
        self.menuImage.addAction(self.actionimagefiles)
        self.menuROI.addAction(self.actionROIdir)
        self.menuROI.addAction(self.actionROIfiles)

        self.menuSave = menubar.addMenu('Save')
        self.actionSaveImg = QAction('Image', self)
        self.actionSaveImg.triggered.connect(self.actSaveImg)
        self.actionSaveSelROI = QAction('Selected ROI', self)
        self.actionSaveSelROI.triggered.connect(self.actSaveSelROI)
        self.actionSaveAllROI = QAction('All ROI', self)
        self.actionSaveAllROI.triggered.connect(self.actSaveAllROI)
        self.menuSave.addAction(self.actionSaveImg)
        self.menuSave.addAction(self.actionSaveSelROI)
        self.menuSave.addAction(self.actionSaveAllROI)

        self.menuRadiomics = menubar.addMenu('Radiomics')
        self.menuExt = self.menuRadiomics.addMenu('Feature Extraction')
        self.actionPyExt = QAction('PyRadiomics', self)
        self.actionPyExt.triggered.connect(self.actFeatureExt)
        self.actionCuExt = QAction('cuRadiomics(2D only)', self)
        self.actionCuExt.triggered.connect(self.actCuFeatureExt)
        self.menuExt.addAction(self.actionPyExt)
        self.menuExt.addAction(self.actionCuExt)

        self.actionFeatSel = QAction('Feature Selection', self)
        self.actionFeatSel.triggered.connect(self.child_stat.show)
        self.menuRadiomics.addAction(self.actionFeatSel)

        LogiRegAct = QAction('Logit Regression', self)
        LogiRegAct.triggered.connect(self.cls_Logi.show)

        SVCAct = QAction('Support Vector Classification', self)
        SVCAct.triggered.connect(self.cls_SVC.show)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('Classification')
        fileMenu.addAction(LogiRegAct)
        fileMenu.addAction(SVCAct)

        DenoiseAct = QAction('Wavelet Denoise', self)
        DenoiseAct.triggered.connect(self.actDenoise)

        ThredAct = QAction('Threshold Seg', self)
        ThredAct.triggered.connect(self.actThreshold)

        GasSmoothAct = QAction('Guassian Smooth', self)
        GasSmoothAct.triggered.connect(self.actGasSmooth)

        MeanSmoothAct = QAction('Mean Smooth', self)
        MeanSmoothAct.triggered.connect(self.actMeanSmooth)

        MedSmoothAct = QAction('Median Smooth', self)
        MedSmoothAct.triggered.connect(self.actMedSmooth)

        ResampleAct = QAction('Resample', self)
        ResampleAct.triggered.connect(self.actResample)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('Pre-processing')
        fileMenu.addAction(DenoiseAct)
        fileMenu.addAction(ThredAct)
        fileMenu.addAction(GasSmoothAct)
        fileMenu.addAction(MeanSmoothAct)
        fileMenu.addAction(MedSmoothAct)
        fileMenu.addAction(ResampleAct)

        SRAct = QAction('Super Resolution', self)
        SRAct.triggered.connect(self.actGetSR)

        DPSegAct = QAction('Segmentation', self)
        DPSegAct.triggered.connect(self.actDPSeg)

        segbar = self.menuBar()
        segMenu = segbar.addMenu('Deep Learning')
        segMenu.addAction(SRAct)
        segMenu.addAction(DPSegAct)

        #loadStudyAct = QAction('Load Dicom Study', self)
        #fileMenu.addAction(loadStudyAct)
        #exitAct = QAction('Quit', self)
        #exitAct.triggered.connect(qApp.quit)
        #fileMenu.addAction(exitAct)

    def initUI_Toolbar(self):
        pass

    def initUI_ContextMenu(self):
        pass

    def initUI_Layout(self):
        self.volumeViewer = volumeViewerWidget(self)
        self.FeatureDisp = featureDispWidget(self)
        self.dicomInfo = dicomInfoWidget(self)
        # self.controlPanel = controlPannelWidget(self)
        self.annotationPanel = annotationPannelWidget(self)
        #self.FeatureSel = featureSelWidget(self)
        #self.gradeDisp = gradeDispWidget(self)
        # self.refViewer = curveWidget(self)
        self.SRViewer = volumeViewerWidget(self)
        colormap = (plt.cm.bwr(np.array( \
                list([x for x in range(256)]), dtype=np.uint8 \
                ))*255).astype(np.uint8).reshape(256,1,4) #RGBA
        colormap[:, 0, 3] = list(range(255, 0, -2)) + list(range(1, 256, 2))
        # dock and central widget
        # volumeViewer
        self.setCentralWidget(self.volumeViewer)

        # dicomInfo
        self.dockDicomInfo = QDockWidget("DICOM Info", self)
        self.dockDicomInfo.setObjectName("dockDicomInfo")
        self.addDockWidget(Qt.RightDockWidgetArea, self.dockDicomInfo)
        self.dockDicomInfo.setWidget(self.dicomInfo)
        # FeatureExtract
        self.dockFeatureDisp = QDockWidget("Extracted Feature", self)
        self.dockFeatureDisp.setObjectName("dockFeatureDisp")
        self.addDockWidget(Qt.RightDockWidgetArea, self.dockFeatureDisp)
        self.dockFeatureDisp.setWidget(self.FeatureDisp)
        self.tabifyDockWidget(self.dockDicomInfo, self.dockFeatureDisp)

        # Annoation
        self.dockAnnotation = QDockWidget("Annotation Tools", self)
        self.dockAnnotation.setObjectName("dockAnnotation")
        self.addDockWidget(Qt.RightDockWidgetArea, self.dockAnnotation)
        self.dockAnnotation.setWidget(self.annotationPanel)
        self.dockAnnotation.showMinimized()
        self.resizeDocks([self.dockAnnotation, self.dockFeatureDisp], \
                [1,10], Qt.Vertical)
        # self.tabifyDockWidget(self.dockAnnotation, self.dockFeatureDisp)

        # controlPanel
        # self.dockControlPanel = QDockWidget("Control Panel", self)
        # self.dockControlPanel.setObjectName("dockControlPanel")
        # self.addDockWidget(Qt.RightDockWidgetArea, self.dockControlPanel)
        # self.dockControlPanel.setWidget(self.controlPanel)
        # self.dockControlPanel.showMinimized()
        # self.resizeDocks([self.dockControlPanel, self.dockFeatureDisp], \
        #         [1,10], Qt.Vertical)
        # FeatureSelection
        # self.dockFeatureSel = QDockWidget("Selected Feature", self)
        # self.dockFeatureSel.setObjectName("dockFeatureSel")
        # self.addDockWidget(Qt.RightDockWidgetArea, self.dockFeatureSel)
        # self.dockFeatureSel.setWidget(self.FeatureSel)
        # self.tabifyDockWidget(self.dockFeatureDisp, self.dockFeatureSel)

        # # gradeDisp
        # self.dockGradeDisp = QDockWidget("Grade (Prediction)", self)
        # self.dockGradeDisp.setObjectName("dockGradeDisp")
        # self.addDockWidget(Qt.RightDockWidgetArea, self.dockGradeDisp)
        # self.dockGradeDisp.setWidget(self.gradeDisp)
        # self.tabifyDockWidget(self.dockDicomInfo, self.dockGradeDisp)

        # reference image and SR image
        # self.dockRefViewer = QDockWidget("Reference", self)
        # self.dockRefViewer.setObjectName("dockRefViewer")
        # self.addDockWidget(Qt.LeftDockWidgetArea, self.dockRefViewer)
        # self.dockRefViewer.setWidget(self.refViewer)
        # self.dockRefViewer.setFloating(True)
        # self.dockRefViewer.setVisible(False)
        self.dockSRViewer = QDockWidget("Super-Resolution", self)
        self.dockSRViewer.setObjectName("dockSRViewer")
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dockSRViewer)
        self.dockSRViewer.setWidget(self.SRViewer)
        self.dockSRViewer.setFloating(True)
        self.dockSRViewer.setVisible(False)

    def initSignals(self):
        # self.controlPanel.btnLoad.clicked.connect(self.actLoadStudy)
        # self.controlPanel.btnROI.clicked.connect(self.actLoadROI)
        # self.controlPanel.btnExt.clicked.connect(self.actFeatureExt)
        # self.controlPanel.btnSel.clicked.connect(self.child_stat.show)

        self.annotationPanel.btnDoAnn.clicked.connect(self.actDoAnn)
        self.annotationPanel.btnDoAnn.setIcon(
            QIcon("./qdarkstyle/polygon.png"))

        self.annotationPanel.btnPoly.clicked.connect(self.actSetPoly)
        self.annotationPanel.btnPoly.setIcon(
            (QIcon("./qdarkstyle/polygon.png")))
        # self.annotationPanel.btnPoly.setStyleSheet("QPushButton{border-image: url(./qdarkstyle/polygon.png)}")

        self.annotationPanel.btnCircle.clicked.connect(self.actSetCircle)
        self.annotationPanel.btnCircle.setIcon(
            (QIcon("./qdarkstyle/circle.png")))

        # self.annotationPanel.btnTri.clicked.connect(self.actSetTri)
        # self.annotationPanel.btnTri.setIcon((QIcon("./qdarkstyle/tri.png")))

        self.annotationPanel.btnJux.clicked.connect(self.actSetJux)
        self.annotationPanel.btnJux.setIcon((QIcon("./qdarkstyle/juxing.png")))

        # childBoxGeo.addWidget(self.btnPoly)
        # childBoxGeo.addWidget(self.btnCircle)
        # childBoxGeo.addWidget(self.btnTri)
        # childBoxGeo.addWidget(self.btnJux)

        self.annotationPanel.btnAccROI.clicked.connect(self.actAccROI)
        self.annotationPanel.btnClrSelROI.clicked.connect(self.actClrSelROI)
        self.annotationPanel.btnClrAllROI.clicked.connect(self.actClrAllROI)
        # self.annotationPanel.btnSaveSelROI.clicked.connect(self.actSaveSelROI)
        # self.annotationPanel.btnSaveAllROI.clicked.connect(self.actSaveAllROI)

        # self.controlPanel.btnCla.clicked.connect(self.actGetGrade)
        # self.controlPanel.btnSeg.clicked.connect(self.actGetSeg)
        # self.controlPanel.btnRef.clicked.connect(self.actGetReference)
        # self.controlPanel.btnSR.clicked.connect(self.actGetSR)
        #self.volumeViewer.sliderIndex.valueChanged.connect( \
        #        lambda x: self.refViewer.setIndex(x-1))
        self.FeatureDisp.cellPressed.connect( \
                lambda row, col: self.volumeViewer.sliderIndex.setValue(row+1))

    def actSetPoly(self):
        self.__type = 'Poly'
        self.actDoAnn()
        return

    def actSetCircle(self):
        self.__type = 'Circle'
        self.actDoAnn()
        return

    # def actSetTri(self):
    #     self.__type ='Tri'
    #     self.actDoAnn()
    #     return

    def actSetJux(self):
        self.__type = 'Jux'
        self.actDoAnn()
        return

    def actDoAnn(self):
        if self.annotationPanel.rbS.isChecked() == True:
            self.volumeViewer.setROI(ax='S', type=self.__type)
        if self.annotationPanel.rbA.isChecked() == True:
            self.volumeViewer.setROI(ax='A', type=self.__type)
        if self.annotationPanel.rbC.isChecked() == True:
            self.volumeViewer.setROI(ax='C', type=self.__type)
        return

    def actAccROI(self):
        # labelArr=self.volumeViewer.accROI()
        # print(np.unique(labelArr))
        # print(labelArr.dtype)
        self.main.setROI(self.volumeViewer.accROI())
        # self.volumeViewer.setLabel(ROI)
        return

    def actSaveSelROI(self):
        self.volumeViewer.saveSelROI()
        return

    def actSaveAllROI(self):
        self.volumeViewer.saveAllROI()
        return

    def actClrSelROI(self):
        self.volumeViewer.clrSelROI()
        return

    def actClrAllROI(self):
        self.volumeViewer.clrAllROI()
        return

    def actDenoise(self):
        self.volumeViewer.denoise()
        return

    def actGasSmooth(self):
        self.volumeViewer.setPreprocessMethod(method='Gaussian')
        return

    def actMeanSmooth(self):
        self.volumeViewer.setPreprocessMethod(method='Mean')
        return

    def actMedSmooth(self):
        self.volumeViewer.setPreprocessMethod(method='Median')
        return

    def actResample(self):
        self.volumeViewer.setPreprocessMethod(method='Resample')
        return

    def actThreshold(self):
        self.volumeViewer.setSegMethod(method='Threshold')
        return

    def actDPSeg(self):
        pass

    @pyqtSlot()
    def actLoadImgDir(self, directory=None):
        start = time.time()
        self.statusBar().showMessage('Loading Study...')
        if directory is None:
            dialog = QFileDialog(self)
            dialog.setFileMode(QFileDialog.DirectoryOnly)
            dialog.setViewMode(QFileDialog.List)
            dialog.setOption(QFileDialog.ShowDirsOnly, True)
            if dialog.exec_():
                directory = str(dialog.selectedFiles()[0])
            else:
                self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
                return
        try:
            images = ReadSagittalPDs(directory)
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
            return
        selectDialog = dicomSelectDialog(images)
        if selectDialog.exec_():
            image = images[selectDialog.selectedIndex]
        else:
            self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
            return

        # get image data
        # image_out = sitk.GetImageFromArray(sitk.GetArrayFromImage(image))
        #
        # # setup other image characteristics
        # image_out.SetOrigin(image.GetOrigin())
        # image_out.SetSpacing(image.GetSpacing())
        # # set to RAI
        # image_out.SetDirection(tuple(-0.0, 0.0, -1.0, 1.0, -0.0, 0.0, 0.0, -1.0, 0.0))
        # # sitk.WriteImage(image_out, 'test.mha')
        # image = image_out
        self.main.setImage(image)
        self.volumeViewer.setImage(image)
        self.dicomInfo.setImage(image)
        self.dockDicomInfo.setVisible(True)
        # self.dockRefViewer.setVisible(False)
        self.dockSRViewer.setVisible(False)
        # self.gradeDisp.setGrade(None)
        self.statusBar().showMessage( \
            'Ready ({:.2f}s)'.format(time.time() - start))
        return

    def actLoadStudy(self, directory=None):
        start = time.time()
        self.statusBar().showMessage('Loading Study...')
        directory = QFileDialog.getOpenFileName(self,
                                                "Select one file to open",
                                                "./", "Files (*.nii *.dcm)")
        print(directory)
        directory = str(directory[0])
        #if directory is None:
        #dialog = QFileDialog(self)
        # dialog.setFileMode(QFileDialog.DirectoryOnly)
        # dialog.setViewMode(QFileDialog.List)
        # dialog.setOption(QFileDialog.ShowDirsOnly, True)
        # if dialog.exec_():
        #     directory = str(dialog.selectedFiles()[0])
        # else:
        #     self.statusBar().showMessage( \
        #             'Ready ({:.2f}s)'.format(time.time() - start))
        #     return
        try:
            #images = ReadSagittalPDs(directory)
            image = ReadImage(directory)
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        # selectDialog = dicomSelectDialog(images)
        # if selectDialog.exec_():
        #     image = images[selectDialog.selectedIndex]
        # else:
        #     self.statusBar().showMessage( \
        #             'Ready ({:.2f}s)'.format(time.time() - start))
        #     return
        self.main.setImage(image)
        self.volumeViewer.setImage(image)
        self.dicomInfo.setImage(image)
        self.dockDicomInfo.setVisible(True)
        # self.dockRefViewer.setVisible(False)
        self.dockSRViewer.setVisible(False)
        # self.gradeDisp.setGrade(None)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
        return

    def actLoadROIDir(self, directory=None):
        start = time.time()
        self.statusBar().showMessage('Loading Study...')
        if directory is None:
            dialog = QFileDialog(self)
            dialog.setFileMode(QFileDialog.DirectoryOnly)
            dialog.setViewMode(QFileDialog.List)
            dialog.setOption(QFileDialog.ShowDirsOnly, True)
            if dialog.exec_():
                directory = str(dialog.selectedFiles()[0])
            else:
                self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
                return
        try:
            ROIs = ReadSagittalPDs(directory)
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
            return
        selectDialog = dicomSelectDialog(images)
        if selectDialog.exec_():
            ROI = ROIs[selectDialog.selectedIndex]
        else:
            self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
            return
        self.main.setROI(ROI)
        self.volumeViewer.setLabel(ROI)
        # self.dockRefViewer.setVisible(False)
        self.dockSRViewer.setVisible(False)
        # self.gradeDisp.setGrade(None)
        self.statusBar().showMessage( \
            'Ready ({:.2f}s)'.format(time.time() - start))
        return

    def actLoadROI(self, directory=None):
        start = time.time()
        self.statusBar().showMessage('Loading ROI...')
        directory = QFileDialog.getOpenFileName(self,
                                                "Select one file to open",
                                                "./", "Files (*.nii *.dcm)")
        print(directory)
        directory = str(directory[0])
        try:
            ROI = ReadROI(directory)
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return

        self.main.setROI(ROI)
        self.volumeViewer.setLabel(ROI)
        self.dockSRViewer.setVisible(False)

        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
        return

    def actFeatureExt(self):
        start = time.time()
        self.statusBar().showMessage('Extracting Feature...')
        try:
            feature_extracted = self.main.getFeature()
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        self.FeatureDisp.setFeature(feature_extracted)
        self.dockFeatureDisp.setVisible(True)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))

        return

    def actCuFeatureExt(self):
        start = time.time()
        self.statusBar().showMessage('Extracting Feature...')
        try:
            feature_extracted = self.main.getCuFeature()
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        self.FeatureDisp.setFeature(feature_extracted)
        self.dockFeatureDisp.setVisible(True)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))

        return

    def actSaveImg(self):
        pass

    # def actFeatureSel(self):
    #     start = time.time()
    #     self.statusBar().showMessage('Statistical Analyzing...')
    #     directory = QFileDialog.getOpenFileName(self,
    #                                          "Select the feature folder",
    #                                          "./",
    #                                          "Files (*.nii *.dcm)")
    #     print(directory)
    #     directory = str(directory[0])
    #     try:
    #         feature_selected = self.main.featureSel()
    #     except Exception as err:
    #         msgBox = QMessageBox(self)
    #         msgBox.setText(str(type(err)) + str(err))
    #         msgBox.exec()
    #         self.statusBar().showMessage( \
    #                 'Ready ({:.2f}s)'.format(time.time() - start))
    #         return
    #     msgBox = QMessageBox(self)
    #     if feature_selected is True:
    #         msgBox.setText('Done! Please check the folder.')
    #     return

    @pyqtSlot()
    def actGetGrade(self):
        start = time.time()
        self.statusBar().showMessage('Predicting Grade...')
        try:
            grade = self.main.getGrade()
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        self.gradeDisp.setGrade(grade)
        self.dockGradeDisp.setVisible(True)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
        msgBox = QMessageBox(self)
        msgBox.setText('得出诊断意见:'+
                '我们从四千多个膝关节测共振图层(健康人和患者)'+\
                '与医生的诊断结果中,利用深度学习的方法,'+\
                '提取图像的特征及其与诊断结果的关系。'+\
                '利用训练出的模型,'+\
                '计算机可以自动从图像中预测诊断意见。'+'\n'+\
                '提示水肿区域:'+\
                '我们从三百多层带有水肿区域的磁共振图像'+\
                '与医生手工勾勒出的水肿区域中,'+\
                '利用深度学习的方法提取特征。利用训练出的模型,'+\
                '计算机可以自动从图像中预测图像是否有水肿,'+\
                '以及水肿区域的位置。')
        #msgBox.exec()
        return

    @pyqtSlot()
    def actGetSeg(self):
        start = time.time()
        self.statusBar().showMessage('Predicting Segmentation...')
        try:
            seg = self.main.getSeg()
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        self.volumeViewer.setLabel(seg)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
        msgBox = QMessageBox(self)
        msgBox.setText('生成分割结果:'+\
                '我们从100多例患者膝关节磁共振的手工标记结果中,'+\
                '利用深度学习的方法提取特征。利用训练出的模型,'+\
                '计算机可以自动从图像中得到膝关节结构的分割结果。')
        #msgBox.exec()

    @pyqtSlot()
    def actGetSR(self):
        start = time.time()
        self.statusBar().showMessage('Enhancing Resolution...')
        try:
            SR = self.main.getSuperResolution()
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        self.SRViewer.setImage(SR)
        if not self.dockSRViewer.isVisible():
            self.dockSRViewer.setVisible(True)
            if self.dockSRViewer.isFloating():
                self.dockSRViewer.resize( \
                        self.SRViewer.viewerSlice.viewportSizeHint()*1.4)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
        msgBox = QMessageBox(self)
        msgBox.setText('分辨率增强(层间距)')
        #msgBox.exec()

    @pyqtSlot()
    def actGetReference(self):
        start = time.time()
        self.statusBar().showMessage('Predicting Reference...')
        try:
            #'PatientAge': '0010|1010', \
            img = self.main.getImage()
            self.refViewer.openCSV(os.path.join( \
                    os.path.dirname(__file__), 'plot.csv'))
            age = img.GetMetaData('0010|1010')
            age = int(age[0:3])
            thickness = self.main.getThickness()
        except Exception as err:
            msgBox = QMessageBox(self)
            msgBox.setText(str(type(err)) + str(err))
            msgBox.exec()
            self.statusBar().showMessage( \
                    'Ready ({:.2f}s)'.format(time.time() - start))
            return
        #print(age, *thickness)
        self.refViewer.plotCurve(age, *thickness)
        if not self.dockRefViewer.isVisible():
            self.dockRefViewer.setVisible(True)
            if self.dockRefViewer.isFloating():
                self.dockRefViewer.resize( \
                        self.refViewer.sizeHint()*1.1)
        self.statusBar().showMessage( \
                'Ready ({:.2f}s)'.format(time.time() - start))
        msgBox = QMessageBox(self)
        msgBox.setText('生成参考')
Ejemplo n.º 2
0
class QDebugPanel(QMainWindow):
    def __init__(self, app, flags=None):
        super(QDebugPanel, self).__init__(flags)
        self.setDockOptions(QMainWindow.AnimatedDocks
                            | QMainWindow.AllowNestedDocks
                            | QMainWindow.AllowTabbedDocks)

        self.app = app
        self.q_settings = app.q_settings

        screen_size = QtWidgets.QDesktopWidget().screenGeometry(-1)
        m_width = screen_size.width()

        self.memory_panel = HexEditor(self.app)
        self.memory_panel.debug_panel = self
        self.memory_panel.dataChanged.connect(self.on_memory_modified)

        self.disassembly_panel = DisassemblyView(self.app)
        self.disassembly_panel.debug_panel = self

        self.dock_memory_panel = QDockWidget('Memory', self)
        self.dock_memory_panel.setWidget(self.memory_panel)
        self.dock_memory_panel.setObjectName('memory')

        self.dock_disassembly_panel = QDockWidget('Disassembly', self)
        self.dock_disassembly_panel.setWidget(self.disassembly_panel)
        self.dock_disassembly_panel.setObjectName('disassembly')

        self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_memory_panel,
                           Qt.Horizontal)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_disassembly_panel,
                           Qt.Horizontal)
        if m_width >= 1920:
            self.splitDockWidget(self.dock_memory_panel,
                                 self.dock_disassembly_panel, Qt.Horizontal)
        else:
            self.tabifyDockWidget(self.dock_memory_panel,
                                  self.dock_disassembly_panel)

        self.restoreUiState()

    def restoreUiState(self):
        ui_state = self.q_settings.value('dwarf_debug_ui_state')
        if ui_state:
            self.restoreGeometry(ui_state)
        window_state = self.q_settings.value('dwarf_debug_ui_window')
        if window_state:
            self.restoreState(window_state)

    def closeEvent(self, event):
        self.q_settings.setValue('dwarf_debug_ui_state', self.saveGeometry())
        self.q_settings.setValue('dwarf_debug_ui_window', self.saveState())

    def showEvent(self, event):
        main_width = self.size().width()
        new_widths = [int(main_width * .4), int(main_width * .5)]
        self.resizeDocks([self.dock_memory_panel, self.dock_disassembly_panel],
                         new_widths, Qt.Horizontal)
        return super().showEvent(event)

    def on_context_setup(self):
        self.memory_panel.on_context_setup()

    def on_memory_modified(self, pos, length):
        data_pos = self.memory_panel.base + pos
        data = self.memory_panel.data[pos:pos + length]
        data = [data[0]]  # todo: strange js part

        if self.app.dwarf.dwarf_api('writeBytes', [data_pos, data]):
            pass
        else:
            utils.show_message_box('Failed to write Memory')

    def raise_memory_panel(self):
        self.dock_memory_panel.raise_()

    def raise_disassembly_panel(self):
        self.dock_disassembly_panel.raise_()

    def jump_to_address(self, address, view=DEBUG_VIEW_MEMORY, force=False):
        address = utils.parse_ptr(address)

        if not force:
            if view == DEBUG_VIEW_MEMORY:
                if self.memory_panel.number_of_lines() > 0:
                    if self.is_address_in_view(view, address):
                        return
            elif view == DEBUG_VIEW_DISASSEMBLY:
                if self.disassembly_panel.number_of_lines() > 0:
                    if self.is_address_in_view(view, address):
                        return

        self.app.show_progress('reading data...')
        self.app.dwarf.read_range_async(
            address, lambda base, data, offset: self._apply_data(
                base, data, offset, view=view))

    def _apply_data(self, base, data, offset, view=DEBUG_VIEW_MEMORY):
        self.app.hide_progress()

        if view == DEBUG_VIEW_MEMORY:
            self.memory_panel.set_data(data, base=base, offset=offset)
            if not self.dock_memory_panel.isVisible():
                self.dock_memory_panel.show()
            self.raise_memory_panel()

            if self.disassembly_panel.number_of_lines() == 0:
                self.disassembly_panel.disasm(base, data, offset)
        elif view == DEBUG_VIEW_DISASSEMBLY:
            self.disassembly_panel.disasm(base, data, offset)
            if not self.dock_disassembly_panel.isVisible():
                self.dock_disassembly_panel.show()
            self.raise_disassembly_panel()

            if self.memory_panel.number_of_lines() == 0:
                self.memory_panel.set_data(data, base=base, offset=offset)

    def is_address_in_view(self, view, address):
        if view == DEBUG_VIEW_MEMORY:
            if self.memory_panel.data:
                ptr_exists = self.memory_panel.base <= address <= self.memory_panel.base + len(
                    self.memory_panel.data)
                if ptr_exists:
                    self.memory_panel.caret.position = address - self.memory_panel.base
                    return True
        elif view == DEBUG_VIEW_DISASSEMBLY:
            if self.disassembly_panel.visible_lines() > 0:
                line_index_for_address = self.disassembly_panel.get_line_for_address(
                    address)
                if line_index_for_address >= 0:
                    self.disassembly_panel.highlighted_line = line_index_for_address
                    self.disassembly_panel.verticalScrollBar().setValue(
                        line_index_for_address)
                    return True
        return False

    def on_cm_jump_to_address(self, view=DEBUG_VIEW_MEMORY):
        ptr, _ = InputDialog.input_pointer(self.app)
        if ptr > 0:
            self.jump_to_address(ptr, view=view)

    def dump_data(self, address, _len):
        def _dump(ptr, data):
            if data is not None:
                from PyQt5.QtWidgets import QFileDialog
                _file = QFileDialog.getSaveFileName(self.app)
                with open(_file[0], 'wb') as f:
                    f.write(data)

        self.app.dwarf.read_memory_async(address, _len, _dump)
Ejemplo n.º 3
0
class MainWindow(QMainWindow):
    """Pyspread main window

    :application: QApplication
    :args: Command line arguments object from argparse
    :unit_test: If True then the application runs in unit_test mode
    :type unit_test: bool, defaults to False

    """

    gui_update = pyqtSignal(dict)

    def __init__(self, application, args, unit_test=False):
        super().__init__()

        self._loading = True
        self.application = application
        self.unit_test = unit_test

        self.settings = Settings(self)
        self.workflows = Workflows(self)
        self.undo_stack = QUndoStack(self)
        self.refresh_timer = QTimer()

        self._init_widgets()

        self.main_window_actions = MainWindowActions(self)

        self._init_window()
        self._init_toolbars()

        self.settings.restore()
        if self.settings.signature_key is None:
            self.settings.signature_key = genkey()

        # Update recent files in the file menu
        self.menuBar().file_menu.history_submenu.update()

        if not self.unit_test:
            self.show()

        self._update_action_toggles()

        # Update the GUI so that everything matches the model
        cell_attributes = self.grid.model.code_array.cell_attributes
        attributes = cell_attributes[self.grid.current]
        self.on_gui_update(attributes)

        self._loading = False
        self._previous_window_state = self.windowState()

        # Open initial file if provided by the command line
        if args.file is not None:
            if self.workflows.filepath_open(args.file):
                self.workflows.update_main_window_title()
            else:
                msg = "File '{}' could not be opened.".format(args.file)
                self.statusBar().showMessage(msg)

    def _init_window(self):
        """Initialize main window components"""

        self.setWindowTitle(APP_NAME)
        self.setWindowIcon(Icon.pyspread)

        self.safe_mode_widget = QSvgWidget(str(IconPath.warning), self)
        msg = "%s is in safe mode.\nExpressions are not evaluated." % APP_NAME
        self.safe_mode_widget.setToolTip(msg)
        self.statusBar().addPermanentWidget(self.safe_mode_widget)
        self.safe_mode_widget.hide()

        # Disable the approve fiel menu button
        self.main_window_actions.approve.setEnabled(False)

        self.setMenuBar(MenuBar(self))

    def resizeEvent(self, event):
        super(MainWindow, self).resizeEvent(event)
        if self._loading:
            return

    def closeEvent(self, event=None):
        """Overloaded close event, allows saving changes or canceling close"""

        if event:
            event.ignore()
        self.workflows.file_quit()  # has @handle_changed_since_save decorator

    def _init_widgets(self):
        """Initialize widgets"""

        self.widgets = Widgets(self)

        self.entry_line = Entryline(self)
        self.grid = Grid(self)

        self.macro_panel = MacroPanel(self, self.grid.model.code_array)

        self.main_splitter = QSplitter(Qt.Vertical, self)
        self.setCentralWidget(self.main_splitter)

        self.main_splitter.addWidget(self.entry_line)
        self.main_splitter.addWidget(self.grid)
        self.main_splitter.addWidget(self.grid.table_choice)
        self.main_splitter.setSizes(
            [self.entry_line.minimumHeight(), 9999, 20])

        self.macro_dock = QDockWidget("Macros", self)
        self.macro_dock.setObjectName("Macro Panel")
        self.macro_dock.setWidget(self.macro_panel)
        self.addDockWidget(Qt.RightDockWidgetArea, self.macro_dock)

        self.macro_dock.installEventFilter(self)

        self.gui_update.connect(self.on_gui_update)
        self.refresh_timer.timeout.connect(self.on_refresh_timer)

    def eventFilter(self, source, event):
        """Event filter for handling QDockWidget close events

        Updates the menu if the macro panel is closed.

        """

        if event.type() == QEvent.Close \
           and isinstance(source, QDockWidget) \
           and source.windowTitle() == "Macros":
            self.main_window_actions.toggle_macro_panel.setChecked(False)
        return super().eventFilter(source, event)

    def _init_toolbars(self):
        """Initialize the main window toolbars"""

        self.main_toolbar = MainToolBar(self)
        self.macro_toolbar = MacroToolbar(self)
        self.find_toolbar = FindToolbar(self)
        self.format_toolbar = FormatToolbar(self)

        self.addToolBar(self.main_toolbar)
        self.addToolBar(self.macro_toolbar)
        self.addToolBar(self.find_toolbar)
        self.addToolBarBreak()
        self.addToolBar(self.format_toolbar)

    def _update_action_toggles(self):
        """Updates the toggle menu check states"""

        self.main_window_actions.toggle_main_toolbar.setChecked(
            self.main_toolbar.isVisible())

        self.main_window_actions.toggle_macro_toolbar.setChecked(
            self.macro_toolbar.isVisible())

        self.main_window_actions.toggle_format_toolbar.setChecked(
            self.format_toolbar.isVisible())

        self.main_window_actions.toggle_find_toolbar.setChecked(
            self.find_toolbar.isVisible())

        self.main_window_actions.toggle_entry_line.setChecked(
            self.entry_line.isVisible())

        self.main_window_actions.toggle_macro_panel.setChecked(
            self.macro_dock.isVisible())

    @property
    def safe_mode(self):
        """Returns safe_mode state. In safe_mode cells are not evaluated."""

        return self.grid.model.code_array.safe_mode

    @safe_mode.setter
    def safe_mode(self, value):
        """Sets safe mode.

        This triggers the safe_mode icon in the statusbar.

        If safe_mode changes from True to False then caches are cleared and
        macros are executed.

        """

        if self.grid.model.code_array.safe_mode == bool(value):
            return

        self.grid.model.code_array.safe_mode = bool(value)

        if value:  # Safe mode entered
            self.safe_mode_widget.show()
            # Enable approval menu entry
            self.main_window_actions.approve.setEnabled(True)
        else:  # Safe_mode disabled
            self.safe_mode_widget.hide()
            # Disable approval menu entry
            self.main_window_actions.approve.setEnabled(False)
            # Clear result cache
            self.grid.model.code_array.result_cache.clear()
            # Execute macros
            self.macro_panel.on_apply()

    def on_print(self):
        """Print event handler"""

        # Create printer
        printer = QPrinter(mode=QPrinter.HighResolution)

        # Get print area
        self.print_area = PrintAreaDialog(self, self.grid).area
        if self.print_area is None:
            return

        # Create print dialog
        dialog = QPrintDialog(printer, self)
        if dialog.exec_() == QPrintDialog.Accepted:
            self.on_paint_request(printer)

    def on_preview(self):
        """Print preview event handler"""

        # Create printer
        printer = QPrinter(mode=QPrinter.HighResolution)

        # Get print area
        self.print_area = PrintAreaDialog(self, self.grid).area
        if self.print_area is None:
            return

        # Create print preview dialog
        dialog = PrintPreviewDialog(printer)

        dialog.paintRequested.connect(self.on_paint_request)
        dialog.exec_()

    def on_paint_request(self, printer):
        """Paints to printer"""

        painter = QPainter(printer)
        option = QStyleOptionViewItem()
        painter.setRenderHints(QPainter.SmoothPixmapTransform
                               | QPainter.SmoothPixmapTransform)

        page_rect = printer.pageRect()

        rows = list(self.workflows.get_paint_rows(self.print_area))
        columns = list(self.workflows.get_paint_columns(self.print_area))
        if not rows or not columns:
            return

        zeroidx = self.grid.model.index(0, 0)
        zeroidx_rect = self.grid.visualRect(zeroidx)

        minidx = self.grid.model.index(min(rows), min(columns))
        minidx_rect = self.grid.visualRect(minidx)

        maxidx = self.grid.model.index(max(rows), max(columns))
        maxidx_rect = self.grid.visualRect(maxidx)

        grid_width = maxidx_rect.x() + maxidx_rect.width() - minidx_rect.x()
        grid_height = maxidx_rect.y() + maxidx_rect.height() - minidx_rect.y()
        grid_rect = QRectF(minidx_rect.x() - zeroidx_rect.x(),
                           minidx_rect.y() - zeroidx_rect.y(), grid_width,
                           grid_height)

        self.settings.print_zoom = min(page_rect.width() / grid_width,
                                       page_rect.height() / grid_height)

        with self.grid.delegate.painter_save(painter):
            painter.scale(self.settings.print_zoom, self.settings.print_zoom)

            # Translate so that the grid starts at upper left paper edge
            painter.translate(zeroidx_rect.x() - minidx_rect.x(),
                              zeroidx_rect.y() - minidx_rect.y())

            # Draw grid cells
            self.workflows.paint(painter, option, grid_rect, rows, columns)

        self.settings.print_zoom = None

    def on_fullscreen(self):
        """Fullscreen toggle event handler"""

        if self.windowState() == Qt.WindowFullScreen:
            self.setWindowState(self._previous_window_state)
        else:
            self._previous_window_state = self.windowState()
            self.setWindowState(Qt.WindowFullScreen)

    def on_approve(self):
        """Approve event handler"""

        if ApproveWarningDialog(self).choice:
            self.safe_mode = False

    def on_clear_globals(self):
        """Clear globals event handler"""

        self.grid.model.code_array.result_cache.clear()

        # Clear globals
        self.grid.model.code_array.clear_globals()
        self.grid.model.code_array.reload_modules()

    def on_preferences(self):
        """Preferences event handler (:class:`dialogs.PreferencesDialog`) """

        data = PreferencesDialog(self).data

        if data is not None:
            max_file_history_changed = \
                self.settings.max_file_history != data['max_file_history']

            # Dialog has been approved --> Store data to settings
            for key in data:
                if key == "signature_key" and not data[key]:
                    data[key] = genkey()
                self.settings.__setattr__(key, data[key])

            # Immediately adjust file history in menu
            if max_file_history_changed:
                self.menuBar().file_menu.history_submenu.update()

    def on_dependencies(self):
        """Dependancies installer (:class:`installer.InstallerDialog`) """

        dial = DependenciesDialog(self)
        dial.exec_()

    def on_undo(self):
        """Undo event handler"""

        self.undo_stack.undo()

    def on_redo(self):
        """Undo event handler"""

        self.undo_stack.redo()

    def on_toggle_refresh_timer(self, toggled):
        """Toggles periodic timer for frozen cells"""

        if toggled:
            self.refresh_timer.start(self.settings.refresh_timeout)
        else:
            self.refresh_timer.stop()

    def on_refresh_timer(self):
        """Event handler for self.refresh_timer.timeout

        Called for periodic updates of frozen cells.
        Does nothing if either the entry_line or a cell editor is active.

        """

        if not self.entry_line.hasFocus() \
           and self.grid.state() != self.grid.EditingState:
            self.grid.refresh_frozen_cells()

    def _toggle_widget(self, widget, action_name, toggled):
        """Toggles widget visibility and updates toggle actions"""

        if toggled:
            widget.show()
        else:
            widget.hide()

        self.main_window_actions[action_name].setChecked(widget.isVisible())

    def on_toggle_main_toolbar(self, toggled):
        """Main toolbar toggle event handler"""

        self._toggle_widget(self.main_toolbar, "toggle_main_toolbar", toggled)

    def on_toggle_macro_toolbar(self, toggled):
        """Macro toolbar toggle event handler"""

        self._toggle_widget(self.macro_toolbar, "toggle_macro_toolbar",
                            toggled)

    def on_toggle_format_toolbar(self, toggled):
        """Format toolbar toggle event handler"""

        self._toggle_widget(self.format_toolbar, "toggle_format_toolbar",
                            toggled)

    def on_toggle_find_toolbar(self, toggled):
        """Find toolbar toggle event handler"""

        self._toggle_widget(self.find_toolbar, "toggle_find_toolbar", toggled)

    def on_toggle_entry_line(self, toggled):
        """Entryline toggle event handler"""

        self._toggle_widget(self.entry_line, "toggle_entry_line", toggled)

    def on_toggle_macro_panel(self, toggled):
        """Macro panel toggle event handler"""

        self._toggle_widget(self.macro_dock, "toggle_macro_panel", toggled)

    def on_manual(self):
        """Show manual browser"""

        dialog = ManualDialog(self)
        dialog.show()

    def on_tutorial(self):
        """Show tutorial browser"""

        dialog = TutorialDialog(self)
        dialog.show()

    def on_about(self):
        """Show about message box"""

        about_msg_template = "<p>".join((
            "<b>%s</b>" % APP_NAME,
            "A non-traditional Python spreadsheet application",
            "Version {version}",
            "Created by:<br>{devs}",
            "Documented by:<br>{doc_devs}",
            "Copyright:<br>Martin Manns",
            "License:<br>{license}",
            '<a href="https://pyspread.gitlab.io">pyspread.gitlab.io</a>',
        ))

        devs = "Martin Manns, Jason Sexauer<br>Vova Kolobok, mgunyho, " \
               "Pete Morgan"

        doc_devs = "Martin Manns, Bosko Markovic, Pete Morgan"

        about_msg = about_msg_template.format(version=VERSION,
                                              license=LICENSE,
                                              devs=devs,
                                              doc_devs=doc_devs)
        QMessageBox.about(self, "About %s" % APP_NAME, about_msg)

    def on_gui_update(self, attributes):
        """GUI update event handler.

        Emitted on cell change. Attributes contains current cell_attributes.

        """

        widgets = self.widgets
        menubar = self.menuBar()

        is_bold = attributes["fontweight"] == QFont.Bold
        self.main_window_actions.bold.setChecked(is_bold)

        is_italic = attributes["fontstyle"] == QFont.StyleItalic
        self.main_window_actions.italics.setChecked(is_italic)

        underline_action = self.main_window_actions.underline
        underline_action.setChecked(attributes["underline"])

        strikethrough_action = self.main_window_actions.strikethrough
        strikethrough_action.setChecked(attributes["strikethrough"])

        renderer = attributes["renderer"]
        widgets.renderer_button.set_current_action(renderer)
        widgets.renderer_button.set_menu_checked(renderer)

        freeze_action = self.main_window_actions.freeze_cell
        freeze_action.setChecked(attributes["frozen"])

        lock_action = self.main_window_actions.lock_cell
        lock_action.setChecked(attributes["locked"])
        self.entry_line.setReadOnly(attributes["locked"])

        button_action = self.main_window_actions.button_cell
        button_action.setChecked(attributes["button_cell"] is not False)

        rotation = "rotate_{angle}".format(angle=int(attributes["angle"]))
        widgets.rotate_button.set_current_action(rotation)
        widgets.rotate_button.set_menu_checked(rotation)
        widgets.justify_button.set_current_action(attributes["justification"])
        widgets.justify_button.set_menu_checked(attributes["justification"])
        widgets.align_button.set_current_action(attributes["vertical_align"])
        widgets.align_button.set_menu_checked(attributes["vertical_align"])

        border_action = self.main_window_actions.border_group.checkedAction()
        if border_action is not None:
            icon = border_action.icon()
            menubar.format_menu.border_submenu.setIcon(icon)
            self.format_toolbar.border_menu_button.setIcon(icon)

        border_width_action = \
            self.main_window_actions.border_width_group.checkedAction()
        if border_width_action is not None:
            icon = border_width_action.icon()
            menubar.format_menu.line_width_submenu.setIcon(icon)
            self.format_toolbar.line_width_button.setIcon(icon)

        if attributes["textcolor"] is None:
            text_color = self.grid.palette().color(QPalette.Text)
        else:
            text_color = QColor(*attributes["textcolor"])
        widgets.text_color_button.color = text_color

        if attributes["bgcolor"] is None:
            bgcolor = self.grid.palette().color(QPalette.Base)
        else:
            bgcolor = QColor(*attributes["bgcolor"])
        widgets.background_color_button.color = bgcolor

        if attributes["textfont"] is None:
            widgets.font_combo.font = QFont().family()
        else:
            widgets.font_combo.font = attributes["textfont"]
        widgets.font_size_combo.size = attributes["pointsize"]

        merge_cells_action = self.main_window_actions.merge_cells
        merge_cells_action.setChecked(attributes["merge_area"] is not None)
Ejemplo n.º 4
0
class QDebugPanel(QMainWindow):
    def __init__(self, app, flags=None):
        super(QDebugPanel, self).__init__(flags)
        self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks)

        self.app = app
        self.q_settings = app.q_settings

        self.functions_list = DwarfListView()
        self.functions_list_model = QStandardItemModel(0, 1)
        self.functions_list_model.setHeaderData(0, Qt.Horizontal, '')
        self.functions_list.setModel(self.functions_list_model)
        self.functions_list.setHeaderHidden(True)
        self.functions_list.doubleClicked.connect(self._function_double_clicked)

        self.dock_functions_list = QDockWidget('Functions', self)
        self.dock_functions_list.setObjectName('functions')
        self.dock_functions_list.setWidget(self.functions_list)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_functions_list)
        self.app.debug_view_menu.addAction(self.dock_functions_list.toggleViewAction())

        self.memory_panel_range = None
        self.disassembly_panel_range = None

        screen_size = QtWidgets.QDesktopWidget().screenGeometry(-1)
        m_width = screen_size.width()

        self.memory_panel = HexEditor(self.app)
        self.memory_panel.debug_panel = self
        self.memory_panel.dataChanged.connect(self.on_memory_modified)

        self.disassembly_panel = DisassemblyView(self.app)
        self.disassembly_panel.debug_panel = self

        self.dock_memory_panel = QDockWidget('Memory', self)
        self.dock_memory_panel.setWidget(self.memory_panel)
        self.dock_memory_panel.setObjectName('memory')

        self.dock_disassembly_panel = QDockWidget('Disassembly', self)
        self.dock_disassembly_panel.setWidget(self.disassembly_panel)
        self.dock_disassembly_panel.setObjectName('disassembly')

        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_memory_panel)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_disassembly_panel)
        if m_width >= 1920:
            self.splitDockWidget(self.dock_memory_panel, self.dock_disassembly_panel, Qt.Horizontal)
        else:
            self.tabifyDockWidget(self.dock_memory_panel, self.dock_disassembly_panel)

        self.restoreUiState()

    def restoreUiState(self):
        ui_state = self.q_settings.value('dwarf_debug_ui_state')
        if ui_state:
            self.restoreGeometry(ui_state)
        window_state = self.q_settings.value('dwarf_debug_ui_window')
        if window_state:
            self.restoreState(window_state)

    def closeEvent(self, event):
        self.q_settings.setValue('dwarf_debug_ui_state', self.saveGeometry())
        self.q_settings.setValue('dwarf_debug_ui_window', self.saveState())

    def showEvent(self, event):
        main_width = self.size().width()
        new_widths = []
        new_widths.append(main_width * .1)
        new_widths.append(main_width * .4)
        new_widths.append(main_width * .5)
        self.resizeDocks([
            self.dock_functions_list, self.dock_memory_panel, self.dock_disassembly_panel
        ], new_widths, Qt.Horizontal)
        return super().showEvent(event)

    def update_functions(self, functions_list=None):
        if functions_list is None:
            functions_list = {}
        self.functions_list_model.setRowCount(0)
        for module_info_base in self.app.dwarf.database.modules_info:
            module_info = self.app.dwarf.database.modules_info[module_info_base]
            if len(module_info.functions) > 0:
                self.functions_list.show()
                for function in module_info.functions:
                    functions_list[function.name] = function.address

        for function_name in sorted(functions_list.keys()):
            function_addr = functions_list[function_name]
            item = QStandardItem(function_name.replace('.', '_'))
            item.setData(function_addr, Qt.UserRole + 2)
            self.functions_list_model.appendRow([item])

    def _function_double_clicked(self, model_index):
        item = self.functions_list_model.itemFromIndex(model_index)
        address = item.data(Qt.UserRole + 2)
        self.jump_to_address(address, view=DEBUG_VIEW_DISASSEMBLY)

    def on_context_setup(self):
        self.memory_panel.on_context_setup()

    def on_memory_modified(self, pos, length):
        data_pos = self.memory_panel.base + pos
        data = self.memory_panel.data[pos:pos + length]
        data = [data[0]]  # todo: strange js part

        if self.dwarf.dwarf_api('writeBytes', [data_pos, data]):
            pass
        else:
            utils.show_message_box('Failed to write Memory')

    def raise_memory_panel(self):
        self.dock_memory_panel.raise_()

    def raise_disassembly_panel(self):
        self.dock_disassembly_panel.raise_()

    def jump_to_address(self, address, view=DEBUG_VIEW_MEMORY):
        address = utils.parse_ptr(address)

        if view == DEBUG_VIEW_MEMORY:
            if self.memory_panel_range is not None:
                if self.is_address_in_view(view, address):
                    return

            self.memory_panel_range = \
                Range.build_or_get(self.app.dwarf, address, cb=lambda x: self._apply_range(address, view=view))
        elif view == DEBUG_VIEW_DISASSEMBLY:
            if self.disassembly_panel_range is not None:
                if self.is_address_in_view(view, address):
                    return

            self.disassembly_panel_range = \
                Range.build_or_get(self.app.dwarf, address, cb=lambda x: self._apply_range(address, view=view))

    def _apply_range(self, address, view=DEBUG_VIEW_MEMORY):
        self.update_functions()

        if view == DEBUG_VIEW_MEMORY:
            self.memory_panel.set_data(
                self.memory_panel_range.data, base=self.memory_panel_range.base, focus_address=address)
            if not self.dock_memory_panel.isVisible():
                self.dock_memory_panel.show()
            self.raise_memory_panel()

            if self.disassembly_panel_range is None:
                self.disassembly_panel_range = self.memory_panel_range
                self.disassembly_panel.apply_range(self.disassembly_panel_range)
        elif view == DEBUG_VIEW_DISASSEMBLY:
            self.disassembly_panel.apply_range(self.disassembly_panel_range)
            if not self.dock_disassembly_panel.isVisible():
                self.dock_disassembly_panel.show()
            self.raise_disassembly_panel()

            if self.memory_panel_range is None:
                self.memory_panel_range = self.disassembly_panel_range
                self.memory_panel.set_data(
                    self.memory_panel_range.data, base=self.memory_panel_range.base, focus_address=address)

    def is_address_in_view(self, view, address):
        if view == DEBUG_VIEW_MEMORY:
            if self.memory_panel_range is not None:
                ptr_exists = self.memory_panel.base <= address <= self.memory_panel.base + len(self.memory_panel.data)
                if ptr_exists:
                    self.memory_panel.caret.position = address - self.memory_panel.base
                    return True
        elif view == DEBUG_VIEW_DISASSEMBLY:
            if self.disassembly_panel_range is not None:
                line_index_for_address = self.disassembly_panel.get_line_for_address(address)
                if line_index_for_address >= 0:
                    self.disassembly_panel.verticalScrollBar().setValue(line_index_for_address)
                    return True
        return False

    def on_cm_jump_to_address(self, view=DEBUG_VIEW_MEMORY):
        ptr, _ = InputDialog.input_pointer(self.app)
        if ptr > 0:
            self.jump_to_address(ptr, view=view)

    def dump_data(self, address, _len):
        def _dump(dwarf_range):
            if address + _len > dwarf_range.tail:
                self.display_error('length is higher than range size')
            else:
                data = dwarf_range.data[address:address + _len]
                if data is not None:
                    from PyQt5.QtWidgets import QFileDialog
                    _file = QFileDialog.getSaveFileName(self.app)
                    with open(_file[0], 'wb') as f:
                        f.write(data)
        Range.build_or_get(self.app.dwarf, address, cb=_dump)
Ejemplo n.º 5
0
class MainForm(QMainWindow):
    areas = []
    toolBoxs = []
    tab = 0
    ui_ = None
    debugLevel = 100
    mPAreas = {}  # Almacena los nombre de submenus areas de menú pineboo
    mPModulos = {}  # Almacena los nombre de submenus modulos de menú pineboo
    openTabs = []
    favoritosW = None
    wid = None  # widget principal

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

    @classmethod
    def setDebugLevel(self, q):
        MainForm.debugLevel = q

    def load(self):

        self.ui_ = pineboolib.project.conn.managerModules().createUI(
            filedir('plugins/mainform/pineboo/mainform.ui'), None, self)

        self.w_ = self
        frameGm = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(
            QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())

        self.areasTab = QTabWidget()
        self.areasTab.setTabPosition(QTabWidget.West)
        self.formTab = QTabWidget()
        try:
            self.areasTab.removeItem = self.areasTab.removeTab
            self.areasTab.addItem = self.areasTab.addTab
        except Exception:
            pass

        self.dockAreasTab = QDockWidget()
        self.dockAreasTab.setWindowTitle("Módulos")
        #self.dockAreas = QtWidgets.QDockWidget()
        self.dockFavoritos = QDockWidget()
        self.dockFavoritos.setWindowTitle("Favoritos")

        self.dockForm = QDockWidget()

        self.dockConsole = None

        self.dockAreasTab.setWidget(self.areasTab)
        self.dockAreasTab.setMaximumWidth(400)
        self.dockFavoritos.setMaximumWidth(400)
        self.dockFavoritos.setMaximumHeight(500)
        # self.dockAreasTab.setMinimumWidth(400)
        # self.dockAreasTab.setMaximumHeight(500)

        self.dockForm.setWidget(self.formTab)

        self.addDockWidget(Qt.RightDockWidgetArea, self.dockForm)
        # self.dockForm.setMaximumWidth(950)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dockFavoritos)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dockAreasTab)
        # self.dockAreasTab.show()
        # self.dockForm.show()

        # self.areasTab.removeItem(0) #Borramos tab de ejemplo.

        self.formTab.setTabsClosable(True)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.formTab.tabCloseRequested[int].connect(self.closeFormTab)
        self.formTab.removeTab(0)
        #app_icon = QtGui.QIcon('share/icons/pineboo-logo-16.png')
        # app_icon.addFile(filedir('share/icons/pineboo-logo-16.png'),
        #                 QtCore.QSize(16, 16))
        # app_icon.addFile(filedir('share/icons/pineboo-logo-24.png'),
        #                 QtCore.QSize(24, 24))
        # app_icon.addFile(filedir('share/icons/pineboo-logo-32.png'),
        #                 QtCore.QSize(32, 32))
        # app_icon.addFile(filedir('share/icons/pineboo-logo-48.png'),
        #                 QtCore.QSize(48, 48))
        # app_icon.addFile(filedir('share/icons/pineboo-logo-64.png'),
        #                 QtCore.QSize(64, 64))
        # app_icon.addFile(filedir('share/icons/pineboo-logo-128.png'),
        #                 QtCore.QSize(128, 128))
        # app_icon.addFile(filedir('share/icons/pineboo-logo-256.png'),
        #                 QtCore.QSize(256, 256))
        # self.setWindowIcon(app_icon)
        self.setWindowIcon(QtGui.QIcon('share/icons/pineboo-logo-16.png'))
        self.actionAcercaQt.triggered.connect(pineboolib.project.aboutQt)
        self.actionAcercaPineboo.triggered.connect(pineboolib.project.aboutPineboo)
        self.actionFavoritos.triggered.connect(self.changeStateDockFavoritos)
        self.dockFavoritos.visibilityChanged.connect(self.changeStateActionFavoritos)
        self.actionModulos.triggered.connect(self.changeStateDockAreas)
        self.dockAreasTab.visibilityChanged.connect(self.changeStateActionAreas)
        self.actionTipografia.triggered.connect(pineboolib.project.chooseFont)
        self.menuPineboo.addSeparator()
        # self.actionEstilo.triggered.connect(pineboolib.main.styleDialog)
        # pineboolib.pnapplication.initStyle(self.configMenu)
        self.setWindowTitle("Pineboo")

        logger.info("Módulos y pestañas ...")
        for k, area in sorted(pineboolib.project.areas.items()):
            self.loadArea(area)
        for k, module in sorted(pineboolib.project.modules.items()):
            self.loadModule(module)

        # Cargando Area desarrollo si procede ...
        sett_ = FLSettings()
        if (sett_.readBoolEntry("application/isDebuggerMode", False)):
            areaDevelop = Struct(idarea="dvl", descripcion="Desarrollo")
            self.loadArea(areaDevelop)

            self.loadDevelop()

        self.restoreOpenedTabs()

        self.loadState()
        # Cargamos nombre de vertical
        util = FLUtil()
        verticalName = util.sqlSelect("flsettings", "valor", "flkey='verticalName'")
        cbPosInfo = util.sqlSelect("flsettings", "valor", "flkey='PosInfo'")

        statusText = ""

        if verticalName != None:
            statusText = verticalName

        if cbPosInfo == 'True':
            from pineboolib.pncontrolsfactory import SysType
            sys_ = SysType()
            statusText += "\t\t\t" + sys_.nameUser() + "@" + sys_.nameBD()

        self.statusBar().showMessage(statusText)

    def closeFormTab(self, numero):
        if isinstance(numero, str):
            i = 0
            name = numero
            numero = None
            for n in self.openTabs:
                if name == n:
                    numero = i
                    break
                i = i + 1

        if numero is not None:
            logger.debug("Cerrando pestaña número %s ", numero)
            self.formTab.removeTab(numero)

            i = 0
            for name in self.openTabs:
                if i == numero:
                    self.openTabs.remove(name)
                    break
                i = i + 1

    def addFormTab(self, action):
        widget = action.mainform_widget
        if action.name in self.openTabs:
            self.closeFormTab(action.name)
        logger.debug("Añadiendo Form a pestaña %s", action)
        icon = None
        try:
            icon = action.mod.mod.mainform.actions[action.name].icon
            self.formTab.addTab(widget, icon, widget.windowTitle())
        except Exception as e:
            logger.warning("addFormTab: No pude localizar icono para %s: %s", action.name, e)
            self.formTab.addTab(widget, widget.windowTitle())

        self.formTab.setCurrentWidget(widget)
        self.openTabs.append(action.name)

    def loadArea(self, area):
        assert area.idarea not in self.areas
        vl = QWidget()
        vl.layout = QVBoxLayout()  # layout de la pestaña
        vl.layout.setSpacing(0)
        vl.layout.setContentsMargins(0, 0, 0, 0)
        vl.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)

        moduleToolBox = QToolBox(self)  # toolbox de cada módulo

        self.areas.append(area.idarea)
        self.toolBoxs.append(moduleToolBox)
        self.tab = self.tab + 1
        vl.setLayout(vl.layout)
        vl.layout.addWidget(moduleToolBox)
        self.areasTab.addItem(vl, area.descripcion)

    def loadModule(self, module):
        logger.debug("loadModule: Procesando %s ", module.name)
        # Creamos pestañas de areas y un vBLayout por cada módulo. Despues ahí metemos los actions de cada módulo
        if module.areaid not in self.areas:
            self.loadArea(Struct(idarea=module.areaid,
                                 descripcion=module.areaid))

        moduleToolBox = self.toolBoxs[self.areas.index(module.areaid)]

        vBLayout = QWidget()
        vBLayout.layout = QVBoxLayout()  # layout de cada módulo.
        vBLayout.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)

        vBLayout.layout.setSpacing(0)
        vBLayout.layout.setContentsMargins(0, 0, 0, 0)

        vBLayout.setLayout(vBLayout.layout)
        if module.icon[0] != "":
            pixmap = QtGui.QPixmap(module.icon)
            moduleToolBox.addItem(vBLayout, QtGui.QIcon(pixmap), module.description)
        else:
            moduleToolBox.addItem(vBLayout, module.description)

        try:
            self.moduleLoad(vBLayout.layout, module)
        except Exception:
            logger.exception("ERROR al procesar modulo %s", module.name)

    def moduleLoad(self, vBLayout, module):
        if not module.loaded:
            module.load()
        if not module.loaded:
            logger.warning("moduleLoad: Ignorando modulo %s por fallo al cargar", module.name)
            return False
        logger.trace("moduleLoad: Running module %s . . . ", module.name)
        iconsize = QtCore.QSize(22, 22)
        iconsize = QtCore.QSize(16, 16)
        vBLayout.setSpacing(0)
        vBLayout.setContentsMargins(0, 0, 0, 0)
        for key in module.mainform.toolbar:
            action = module.mainform.actions[key]
            button = QToolButton()
            button.setText(action.text)
            button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            button.setIconSize(iconsize)
            button.setAutoRaise(True)
            if action.icon:
                button.setIcon(action.icon)
            button.clicked.connect(action.run)
            vBLayout.addWidget(button)
            self.addToMenuPineboo(action, module)
        vBLayout.addStretch()

    def closeEvent(self, evnt):

        res = QMessageBox.information(
            QApplication.activeWindow(),
            "Salir de Pineboo",
            "¿ Desea salir ?",
            QMessageBox.Yes, QMessageBox.No)

        if res == QMessageBox.No:
            evnt.ignore()

        self.saveState()

    def saveState(self):
        if self:
            sett_ = FLSettings()
            sett_.writeEntryList("application/mainForm/tabsOpened", self.openTabs)
            sett_.writeEntry("application/mainForm/viewFavorites", self.dockFavoritos.isVisible())
            sett_.writeEntry("application/mainForm/FavoritesSize", self.dockFavoritos.size())
            sett_.writeEntry("application/mainForm/viewAreas", self.dockAreasTab.isVisible())
            sett_.writeEntry("application/mainForm/AreasSize", self.dockFavoritos.size())
            sett_.writeEntry("application/mainForm/mainFormSize", self.size())

    def addToMenuPineboo(self, ac, mod):
        #print(mod.name, ac.name, pineboolib.project.areas[mod.areaid].descripcion)
        # Comprueba si el area ya se ha creado
        if mod.areaid not in self.mPAreas.keys():
            areaM = self.menuPineboo.addMenu(QtGui.QIcon('share/icons/gtk-open.png'),
                                             pineboolib.project.areas[mod.areaid].descripcion)
            self.mPAreas[mod.areaid] = areaM
        else:
            areaM = self.mPAreas[mod.areaid]

        # Comprueba si el modulo ya se ha creado
        if mod.name not in self.mPModulos.keys():
            pixmap = None
            if mod.icon[0] != "":
                pixmap = QtGui.QPixmap(mod.icon)
            if pixmap:
                moduloM = areaM.addMenu(QtGui.QIcon(pixmap), mod.description)
            else:
                moduloM = areaM.addMenu(mod.description)

            self.mPModulos[mod.name] = moduloM
        else:
            moduloM = self.mPModulos[mod.name]

        action_ = moduloM.addAction(ac.icon, ac.text)
        action_.triggered.connect(ac.run)

    def restoreOpenedTabs(self):
        # Cargamos pestañas abiertas
        sett_ = FLSettings()
        tabsOpened_ = sett_.readListEntry("application/mainForm/tabsOpened")
        if tabsOpened_:
            for t in tabsOpened_:
                for k, module in sorted(pineboolib.project.modules.items()):
                    if hasattr(module, "mainform"):
                        if t in module.mainform.actions:
                            module.mainform.actions[t].run()
                            break

    def loadState(self):
        sett_ = FLSettings()
        viewFavorites_ = sett_.readBoolEntry("application/mainForm/viewFavorites", True)
        viewAreas_ = sett_.readBoolEntry("application/mainForm/viewAreas", True)
        sizeF_ = sett_.readEntry("application/mainForm/FavoritesSize", None)
        sizeA_ = sett_.readEntry("application/mainForm/AreasSize", None)
        sizeMF_ = sett_.readEntry("application/mainForm/mainFormSize", None)
        if sizeF_ is not None:
            self.dockFavoritos.resize(sizeF_)

        if sizeA_ is not None:
            self.dockAreasTab.resize(sizeA_)

        if sizeMF_ is not None:
            self.resize(sizeMF_)
        else:
            self.showMaximized()

        """
        self.dockFavoritos.setVisible(viewFavorites_)
        self.actionFavoritos.setChecked(viewFavorites_)
        self.dockAreasTab.setVisible(viewAreas_)
        self.actionModulos.setChecked(viewAreas_)
        """

    def changeStateDockFavoritos(self):
        visible_ = self.actionFavoritos.isChecked()
        if visible_:
            sett_ = FLSettings()
            sizeF_ = sett_.readEntry("application/mainForm/FavoritesSize", None)
            if sizeF_ is not None:
                self.dockFavoritos.resize(sizeF_)

        self.dockFavoritos.setVisible(visible_)

    def changeStateActionFavoritos(self):
        if self.dockFavoritos.isVisible():
            self.actionFavoritos.setChecked(True)
        else:
            self.actionFavoritos.setChecked(False)

    def changeStateDockAreas(self):
        visible_ = self.actionModulos.isChecked()
        if visible_:
            sett_ = FLSettings()
            sizeA_ = sett_.readEntry("application/mainForm/AreasSize", None)
            if sizeA_ is not None:
                self.dockAreasTab.resize(sizeA_)
        self.dockAreasTab.setVisible(visible_)

    def changeStateActionAreas(self):
        if self.dockAreasTab.isVisible():
            self.actionModulos.setChecked(True)
        else:
            self.actionModulos.setChecked(False)

    def loadDevelop(self):
        moduleToolBox = self.toolBoxs[self.areas.index("dvl")]
        vBLayout = QWidget()
        vBLayout.layout = QVBoxLayout()  # layout de cada módulo.
        vBLayout.layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
        vBLayout.layout.setSpacing(0)
        vBLayout.layout.setContentsMargins(0, 0, 0, 0)
        vBLayout.setLayout(vBLayout.layout)

        button = QToolButton()
        button.setText("Consola")
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        iconsize = QtCore.QSize(16, 16)
        button.setIconSize(iconsize)
        button.setAutoRaise(True)
        button.setIcon(QtGui.QIcon('share/icons/terminal.png'))
        button.clicked.connect(self.showConsole)
        vBLayout.layout.addWidget(button)
        moduleToolBox.addItem(vBLayout, "Desarrollo")

        #self.addToMenuPineboo(action, module)

    def showConsole(self):
        if not self.dockConsole:
            self.dockConsole = QDockWidget()
            self.dockConsole.setWindowTitle("Consola")
            self.addDockWidget(Qt.BottomDockWidgetArea, self.dockConsole)
            self.teo_ = OutputWindow()
            self.dockConsole.setWidget(self.teo_)

        self.dockConsole.setVisible(True)
Ejemplo n.º 6
0
class CalculatorWindow(NodeEditorWindow):
    """Class representing the MainWindow of the application.

    Instance Attributes:
        name_company and name_product - used to register the settings
    """
    def initUI(self):
        """UI is composed with """

        # variable for QSettings
        self.name_company = 'Michelin'
        self.name_product = 'Calculator NodeEditor'

        # Load filesheets
        self.stylesheet_filename = os.path.join(os.path.dirname(__file__),
                                                'qss/nodeeditor.qss')
        loadStylessheets(
            os.path.join(os.path.dirname(__file__), 'qss/nodeeditor-dark.qss'),
            self.stylesheet_filename)

        self.empty_icon = QIcon(".")

        if DEBUG:
            print('Registered Node')
            pp(CALC_NODES)

        # Instantiate the MultiDocument Area
        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setViewMode(QMdiArea.TabbedView)
        self.mdiArea.setTabsClosable(True)
        self.setCentralWidget(self.mdiArea)

        # Connect subWindowActivate to updateMenu
        # Activate the items on the file_menu and the edit_menu
        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        # from mdi example...
        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow)

        # instantiate various elements
        self.createNodesDock()
        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.readSettings()

        self.setWindowTitle("Calculator NodeEditor Example")

    def createActions(self):
        """Instantiate various `QAction` for the main toolbar.

        File and Edit menu actions are instantiated in the :classs:~`node_editor.node_editor_widget.NodeEditorWidget`
        Window and Help actions are specific to the :class:~`examples.calc_window.CalcWindow`
        """
        super().createActions()
        self.actClose = QAction("Cl&ose",
                                self,
                                statusTip="Close the active window",
                                triggered=self.mdiArea.closeActiveSubWindow)
        self.actCloseAll = QAction("Close &All",
                                   self,
                                   statusTip="Close all the windows",
                                   triggered=self.mdiArea.closeAllSubWindows)
        self.actTile = QAction("&Tile",
                               self,
                               statusTip="Tile the windows",
                               triggered=self.mdiArea.tileSubWindows)
        self.actCascade = QAction("&Cascade",
                                  self,
                                  statusTip="Cascade the windows",
                                  triggered=self.mdiArea.cascadeSubWindows)
        self.actNext = QAction("Ne&xt",
                               self,
                               shortcut=QKeySequence.NextChild,
                               statusTip="Move the focus to the next window",
                               triggered=self.mdiArea.activateNextSubWindow)
        self.actPrevious = QAction(
            "Pre&vious",
            self,
            shortcut=QKeySequence.PreviousChild,
            statusTip="Move the focus to the previous window",
            triggered=self.mdiArea.activatePreviousSubWindow)

        self.actSeparator = QAction(self)
        self.actSeparator.setSeparator(True)

        self.actAbout = QAction("&About",
                                self,
                                statusTip="Show the application's About box",
                                triggered=self.about)

    def createMenus(self):
        """Populate File, Edit, Window and Help with `QAction`"""
        super().createMenus()

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.actAbout)

        # Any time the edit menu is about to be shown, update it
        self.editMenu.aboutToShow.connect(self.updateEditMenu)

    def onWindowNodesToolbar(self):
        """Event handling the visibility of the `Nodes Dock`"""
        if self.nodesDock.isVisible():
            self.nodesDock.hide()
        else:
            self.nodesDock.show()

    def createToolBars(self):
        pass

    def createNodesDock(self):
        """Create `Nodes Dock` and populates it with the list of `Nodes`

        The `Nodes` are automatically detected via the :class:~`examples.calc_drag_listbox.QNEDragListBox`
        """
        self.nodeListWidget = QNEDragListbox()

        self.nodesDock = QDockWidget("Nodes")
        self.nodesDock.setWidget(self.nodeListWidget)
        self.nodesDock.setFloating(False)

        self.addDockWidget(Qt.RightDockWidgetArea, self.nodesDock)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready", )

    def updateMenus(self):
        active = self.getCurrentNodeEditorWidget()
        hasMdiChild = (active is not None)

        self.actSave.setEnabled(hasMdiChild)
        self.actSaveAs.setEnabled(hasMdiChild)
        self.actClose.setEnabled(hasMdiChild)
        self.actCloseAll.setEnabled(hasMdiChild)
        self.actTile.setEnabled(hasMdiChild)
        self.actCascade.setEnabled(hasMdiChild)
        self.actNext.setEnabled(hasMdiChild)
        self.actPrevious.setEnabled(hasMdiChild)
        self.actSeparator.setVisible(hasMdiChild)

        self.updateEditMenu()

    def updateEditMenu(self):
        if DEBUG: print('updateEditMenu')
        try:
            active = self.getCurrentNodeEditorWidget()
            hasMdiChild = (active is not None)
            hasSelectedItems = hasMdiChild and active.hasSelectedItems()

            self.actPaste.setEnabled(hasMdiChild)

            self.actCut.setEnabled(hasSelectedItems)
            self.actCopy.setEnabled(hasSelectedItems)
            self.actDelete.setEnabled(hasSelectedItems)

            self.actUndo.setEnabled(hasMdiChild and active.canUndo())
            self.actRedo.setEnabled(hasMdiChild and active.canRedo())
        except Exception as e:
            dumpException(e)

    def updateWindowMenu(self):
        self.windowMenu.clear()

        toolbar_nodes = self.windowMenu.addAction('Nodes toolbar')
        toolbar_nodes.setCheckable(True)
        toolbar_nodes.triggered.connect(self.onWindowNodesToolbar)
        toolbar_nodes.setChecked(self.nodesDock.isVisible())
        self.windowMenu.addSeparator()

        self.windowMenu.addAction(self.actClose)
        self.windowMenu.addAction(self.actCloseAll)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.actTile)
        self.windowMenu.addAction(self.actCascade)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.actNext)
        self.windowMenu.addAction(self.actPrevious)
        self.windowMenu.addAction(self.actSeparator)

        windows = self.mdiArea.subWindowList()
        self.actSeparator.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.getUserFriendlyFilename())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.getCurrentNodeEditorWidget())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    def getCurrentNodeEditorWidget(self) -> NodeEditorWidget:
        """Return the widget currently holding the scene.

        For different application, the method can be overridden to return mdiArea, the central widget...

        Returns
        -------
        NodeEditorWidget
            Node editor Widget. The widget holding the scene.
        """
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def onFileNew(self):
        try:
            subwnd = self.createMdiChild()
            subwnd.widget().fileNew()
            subwnd.show()
        except Exception as e:
            dumpException(e)

    def onFileOpen(self):
        """Open OpenFileDialog"""
        # OpenFile dialog
        fnames, filter = QFileDialog.getOpenFileNames(
            self, 'Open graph from file', self.getFileDialogDirectory(),
            self.getFileDialogFilter())

        try:
            for fname in fnames:
                if fname:
                    existing = self.findMdiChild(fname)
                    if existing:
                        self.mdiArea.setActiveSubWindow(existing)
                    else:
                        # do not use createMdiChild as a new node editor to call the fileLoad method
                        # Create new subwindow and open file
                        nodeeditor = CalculatorSubWindow()
                        if nodeeditor.fileLoad(fname):
                            self.statusBar().showMessage(
                                f'File {fname} loaded', 5000)
                            nodeeditor.setTitle()
                            subwnd = self.createMdiChild(nodeeditor)
                            subwnd.show()
                        else:
                            nodeeditor.close()
        except Exception as e:
            dumpException(e)

    def about(self):
        QMessageBox.about(
            self, "About Calculator NodeEditor Example",
            "The <b>Calculator NodeEditor</b> example demonstrates how to write multiple "
            "document interface applications using PyQt5 and NodeEditor.")

    def closeEvent(self, event: QCloseEvent) -> None:
        try:
            self.mdiArea.closeAllSubWindows()
            if self.mdiArea.currentSubWindow():
                event.ignore()
            else:
                self.writeSettings()
                event.accept()
                # In case of fixing the application closing
                # import sys
                # sys.exit(0)
        except Exception as e:
            dumpException(e)

    def createMdiChild(self, child_widget=None):
        nodeeditor = child_widget if child_widget is not None else CalculatorSubWindow(
        )
        subwnd = self.mdiArea.addSubWindow(nodeeditor, )
        subwnd.setWindowIcon(self.empty_icon)
        # nodeeditor.scene.addItemSelectedListener(self.updateEditMenu)
        # nodeeditor.scene.addItemsDeselectedListener(self.updateEditMenu)
        nodeeditor.scene.history.addHistoryModifiedListener(
            self.updateEditMenu)
        nodeeditor.addCloseEventListener(self.onSubWndClose)
        return subwnd

    def onSubWndClose(self, widget: CalculatorSubWindow, event: QCloseEvent):
        # close event from the nodeeditor works by asking the active widget
        # if modification occurs on the active widget, ask to save or not.
        # Therefore when closing a subwindow, select the corresponding subwindow
        existing = self.findMdiChild(widget.filename)
        self.mdiArea.setActiveSubWindow(existing)

        # Does the active widget need to be saved ?
        if self.maybeSave():
            event.accept()
        else:
            event.ignore()

    def findMdiChild(self, fileName):
        for window in self.mdiArea.subWindowList():
            if window.widget().filename == fileName:
                return window
        return None

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)
Ejemplo n.º 7
0
class MusicPlayer(QMainWindow):
    """MusicPlayer houses all of elements that directly interact with the main window."""

    def __init__(self, parent=None):
        """Initialize the QMainWindow widget.

        The window title, window icon, and window size are initialized here as well
        as the following widgets: QMediaPlayer, QMediaPlaylist, QMediaContent, QMenuBar,
        QToolBar, QLabel, QPixmap, QSlider, QDockWidget, QListWidget, QWidget, and
        QVBoxLayout. The connect signals for relavant widgets are also initialized.
        """
        super(MusicPlayer, self).__init__(parent)
        self.setWindowTitle('Mosaic')

        window_icon = utilities.resource_filename('mosaic.images', 'icon.png')
        self.setWindowIcon(QIcon(window_icon))
        self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

        # Initiates Qt objects to be used by MusicPlayer
        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.playlist_location = defaults.Settings().playlist_path
        self.content = QMediaContent()
        self.menu = self.menuBar()
        self.toolbar = QToolBar()
        self.art = QLabel()
        self.pixmap = QPixmap()
        self.slider = QSlider(Qt.Horizontal)
        self.duration_label = QLabel()
        self.playlist_dock = QDockWidget('Playlist', self)
        self.library_dock = QDockWidget('Media Library', self)
        self.playlist_view = QListWidget()
        self.library_view = library.MediaLibraryView()
        self.library_model = library.MediaLibraryModel()
        self.preferences = configuration.PreferencesDialog()
        self.widget = QWidget()
        self.layout = QVBoxLayout(self.widget)
        self.duration = 0
        self.playlist_dock_state = None
        self.library_dock_state = None

        # Sets QWidget() as the central widget of the main window
        self.setCentralWidget(self.widget)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.art.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)

        # Initiates the playlist dock widget and the library dock widget
        self.addDockWidget(defaults.Settings().dock_position, self.playlist_dock)
        self.playlist_dock.setWidget(self.playlist_view)
        self.playlist_dock.setVisible(defaults.Settings().playlist_on_start)
        self.playlist_dock.setFeatures(QDockWidget.DockWidgetClosable)

        self.addDockWidget(defaults.Settings().dock_position, self.library_dock)
        self.library_dock.setWidget(self.library_view)
        self.library_dock.setVisible(defaults.Settings().media_library_on_start)
        self.library_dock.setFeatures(QDockWidget.DockWidgetClosable)
        self.tabifyDockWidget(self.playlist_dock, self.library_dock)

        # Sets the range of the playback slider and sets the playback mode as looping
        self.slider.setRange(0, self.player.duration() / 1000)
        self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)

        # OSX system menu bar causes conflicts with PyQt5 menu bar
        if sys.platform == 'darwin':
            self.menu.setNativeMenuBar(False)

        # Initiates Settings in the defaults module to give access to settings.toml
        defaults.Settings()

        # Signals that connect to other methods when they're called
        self.player.metaDataChanged.connect(self.display_meta_data)
        self.slider.sliderMoved.connect(self.seek)
        self.player.durationChanged.connect(self.song_duration)
        self.player.positionChanged.connect(self.song_position)
        self.player.stateChanged.connect(self.set_state)
        self.playlist_view.itemActivated.connect(self.activate_playlist_item)
        self.library_view.activated.connect(self.open_media_library)
        self.playlist.currentIndexChanged.connect(self.change_index)
        self.playlist.mediaInserted.connect(self.initialize_playlist)
        self.playlist_dock.visibilityChanged.connect(self.dock_visiblity_change)
        self.library_dock.visibilityChanged.connect(self.dock_visiblity_change)
        self.preferences.dialog_media_library.media_library_line.textChanged.connect(self.change_media_library_path)
        self.preferences.dialog_view_options.dropdown_box.currentIndexChanged.connect(self.change_window_size)
        self.art.mousePressEvent = self.press_playback

        # Creating the menu controls, media controls, and window size of the music player
        self.menu_controls()
        self.media_controls()
        self.load_saved_playlist()

    def menu_controls(self):
        """Initiate the menu bar and add it to the QMainWindow widget."""
        self.file = self.menu.addMenu('File')
        self.edit = self.menu.addMenu('Edit')
        self.playback = self.menu.addMenu('Playback')
        self.view = self.menu.addMenu('View')
        self.help_ = self.menu.addMenu('Help')

        self.file_menu()
        self.edit_menu()
        self.playback_menu()
        self.view_menu()
        self.help_menu()

    def media_controls(self):
        """Create the bottom toolbar and controls used for media playback."""
        self.addToolBar(Qt.BottomToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)

        play_icon = utilities.resource_filename('mosaic.images', 'md_play.png')
        self.play_action = QAction(QIcon(play_icon), 'Play', self)
        self.play_action.triggered.connect(self.player.play)

        stop_icon = utilities.resource_filename('mosaic.images', 'md_stop.png')
        self.stop_action = QAction(QIcon(stop_icon), 'Stop', self)
        self.stop_action.triggered.connect(self.player.stop)

        previous_icon = utilities.resource_filename('mosaic.images', 'md_previous.png')
        self.previous_action = QAction(QIcon(previous_icon), 'Previous', self)
        self.previous_action.triggered.connect(self.previous)

        next_icon = utilities.resource_filename('mosaic.images', 'md_next.png')
        self.next_action = QAction(QIcon(next_icon), 'Next', self)
        self.next_action.triggered.connect(self.playlist.next)

        repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png')
        self.repeat_action = QAction(QIcon(repeat_icon), 'Repeat', self)
        self.repeat_action.setShortcut('R')
        self.repeat_action.triggered.connect(self.repeat_song)

        self.toolbar.addAction(self.play_action)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.addAction(self.previous_action)
        self.toolbar.addAction(self.next_action)
        self.toolbar.addAction(self.repeat_action)
        self.toolbar.addWidget(self.slider)
        self.toolbar.addWidget(self.duration_label)

    def file_menu(self):
        """Add a file menu to the menu bar.

        The file menu houses the Open File, Open Multiple Files, Open Playlist,
        Open Directory, and Exit Application menu items.
        """
        self.open_action = QAction('Open File', self)
        self.open_action.setShortcut('O')
        self.open_action.triggered.connect(self.open_file)

        self.open_multiple_files_action = QAction('Open Multiple Files', self)
        self.open_multiple_files_action.setShortcut('M')
        self.open_multiple_files_action.triggered.connect(self.open_multiple_files)

        self.open_playlist_action = QAction('Open Playlist', self)
        self.open_playlist_action.setShortcut('CTRL+P')
        self.open_playlist_action.triggered.connect(self.open_playlist)

        self.open_directory_action = QAction('Open Directory', self)
        self.open_directory_action.setShortcut('D')
        self.open_directory_action.triggered.connect(self.open_directory)

        self.save_playlist_action = QAction('Save Playlist', self)
        self.save_playlist_action.setShortcut('CTRL+S')
        self.save_playlist_action.triggered.connect(self.save_playlist)

        self.exit_action = QAction('Quit', self)
        self.exit_action.setShortcut('CTRL+Q')
        self.exit_action.triggered.connect(self.closeEvent)

        self.file.addAction(self.open_action)
        self.file.addAction(self.open_multiple_files_action)
        self.file.addAction(self.open_playlist_action)
        self.file.addAction(self.open_directory_action)
        self.file.addSeparator()
        self.file.addAction(self.save_playlist_action)
        self.file.addSeparator()
        self.file.addAction(self.exit_action)

    def edit_menu(self):
        """Add an edit menu to the menu bar.

        The edit menu houses the preferences item that opens a preferences dialog
        that allows the user to customize features of the music player.
        """
        self.preferences_action = QAction('Preferences', self)
        self.preferences_action.setShortcut('CTRL+SHIFT+P')
        self.preferences_action.triggered.connect(lambda: self.preferences.exec_())

        self.edit.addAction(self.preferences_action)

    def playback_menu(self):
        """Add a playback menu to the menu bar.

        The playback menu houses
        """
        self.play_playback_action = QAction('Play', self)
        self.play_playback_action.setShortcut('P')
        self.play_playback_action.triggered.connect(self.player.play)

        self.stop_playback_action = QAction('Stop', self)
        self.stop_playback_action.setShortcut('S')
        self.stop_playback_action.triggered.connect(self.player.stop)

        self.previous_playback_action = QAction('Previous', self)
        self.previous_playback_action.setShortcut('B')
        self.previous_playback_action.triggered.connect(self.previous)

        self.next_playback_action = QAction('Next', self)
        self.next_playback_action.setShortcut('N')
        self.next_playback_action.triggered.connect(self.playlist.next)

        self.playback.addAction(self.play_playback_action)
        self.playback.addAction(self.stop_playback_action)
        self.playback.addAction(self.previous_playback_action)
        self.playback.addAction(self.next_playback_action)

    def view_menu(self):
        """Add a view menu to the menu bar.

        The view menu houses the Playlist, Media Library, Minimalist View, and Media
        Information menu items. The Playlist item toggles the playlist dock into and
        out of view. The Media Library items toggles the media library dock into and
        out of view. The Minimalist View item resizes the window and shows only the
        menu bar and player controls. The Media Information item opens a dialog that
        shows information relevant to the currently playing song.
        """
        self.dock_action = self.playlist_dock.toggleViewAction()
        self.dock_action.setShortcut('CTRL+ALT+P')

        self.library_dock_action = self.library_dock.toggleViewAction()
        self.library_dock_action.setShortcut('CTRL+ALT+L')

        self.minimalist_view_action = QAction('Minimalist View', self)
        self.minimalist_view_action.setShortcut('CTRL+ALT+M')
        self.minimalist_view_action.setCheckable(True)
        self.minimalist_view_action.triggered.connect(self.minimalist_view)

        self.view_media_info_action = QAction('Media Information', self)
        self.view_media_info_action.setShortcut('CTRL+SHIFT+M')
        self.view_media_info_action.triggered.connect(self.media_information_dialog)

        self.view.addAction(self.dock_action)
        self.view.addAction(self.library_dock_action)
        self.view.addSeparator()
        self.view.addAction(self.minimalist_view_action)
        self.view.addSeparator()
        self.view.addAction(self.view_media_info_action)

    def help_menu(self):
        """Add a help menu to the menu bar.

        The help menu houses the about dialog that shows the user information
        related to the application.
        """
        self.about_action = QAction('About', self)
        self.about_action.setShortcut('H')
        self.about_action.triggered.connect(lambda: about.AboutDialog().exec_())

        self.help_.addAction(self.about_action)

    def open_file(self):
        """Open the selected file and add it to a new playlist."""
        filename, success = QFileDialog.getOpenFileName(self, 'Open File', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly)

        if success:
            file_info = QFileInfo(filename).fileName()
            playlist_item = QListWidgetItem(file_info)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(filename)))
            self.player.setPlaylist(self.playlist)
            playlist_item.setToolTip(file_info)
            self.playlist_view.addItem(playlist_item)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_multiple_files(self):
        """Open the selected files and add them to a new playlist."""
        filenames, success = QFileDialog.getOpenFileNames(self, 'Open Multiple Files', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly)

        if success:
            self.playlist.clear()
            self.playlist_view.clear()
            for file in natsort.natsorted(filenames, alg=natsort.ns.PATH):
                file_info = QFileInfo(file).fileName()
                playlist_item = QListWidgetItem(file_info)
                self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file)))
                self.player.setPlaylist(self.playlist)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)
                self.playlist_view.setCurrentRow(0)
                self.player.play()

    def open_playlist(self):
        """Load an M3U or PLS file into a new playlist."""
        playlist, success = QFileDialog.getOpenFileName(self, 'Open Playlist', '', 'Playlist (*.m3u *.pls)', '', QFileDialog.ReadOnly)

        if success:
            playlist = QUrl.fromLocalFile(playlist)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def save_playlist(self):
        """Save the media in the playlist dock as a new M3U playlist."""
        playlist, success = QFileDialog.getSaveFileName(self, 'Save Playlist', '', 'Playlist (*.m3u)', '')
        if success:
            saved_playlist = "{}.m3u" .format(playlist)
            self.playlist.save(QUrl().fromLocalFile(saved_playlist), "m3u")

    def load_saved_playlist(self):
        """Load the saved playlist if user setting permits."""
        saved_playlist = "{}/.m3u" .format(self.playlist_location)
        if os.path.exists(saved_playlist):
            playlist = QUrl().fromLocalFile(saved_playlist)
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)

    def open_directory(self):
        """Open the selected directory and add the files within to an empty playlist."""
        directory = QFileDialog.getExistingDirectory(self, 'Open Directory', '', QFileDialog.ReadOnly)

        if directory:
            self.playlist.clear()
            self.playlist_view.clear()
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

            self.player.setPlaylist(self.playlist)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_media_library(self, index):
        """Open a directory or file from the media library into an empty playlist."""
        self.playlist.clear()
        self.playlist_view.clear()

        if self.library_model.fileName(index).endswith(('mp3', 'flac')):
            self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(self.library_model.filePath(index))))
            self.playlist_view.addItem(self.library_model.fileName(index))

        elif self.library_model.isDir(index):
            directory = self.library_model.filePath(index)
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

        self.player.setPlaylist(self.playlist)
        self.player.play()

    def display_meta_data(self):
        """Display the current song's metadata in the main window.

        If the current song contains metadata, its cover art is extracted and shown in
        the main window while the track number, artist, album, and track title are shown
        in the window title.
        """
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
            (album, artist, title, track_number, *__, artwork) = metadata.metadata(file_path)

            try:
                self.pixmap.loadFromData(artwork)
            except TypeError:
                self.pixmap = QPixmap(artwork)

            meta_data = '{} - {} - {} - {}' .format(track_number, artist, album, title)

            self.setWindowTitle(meta_data)
            self.art.setScaledContents(True)
            self.art.setPixmap(self.pixmap)
            self.layout.addWidget(self.art)

    def initialize_playlist(self, start):
        """Display playlist and reset playback mode when media inserted into playlist."""
        if start == 0:
            if self.library_dock.isVisible():
                self.playlist_dock.setVisible(True)
                self.playlist_dock.show()
                self.playlist_dock.raise_()

            if self.playlist.playbackMode() != QMediaPlaylist.Sequential:
                self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
                repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png')
                self.repeat_action.setIcon(QIcon(repeat_icon))

    def press_playback(self, event):
        """Change the playback of the player on cover art mouse event.

        When the cover art is clicked, the player will play the media if the player is
        either paused or stopped. If the media is playing, the media is set
        to pause.
        """
        if event.button() == 1 and configuration.Playback().cover_art_playback.isChecked():
            if (self.player.state() == QMediaPlayer.StoppedState or
                    self.player.state() == QMediaPlayer.PausedState):
                self.player.play()
            elif self.player.state() == QMediaPlayer.PlayingState:
                self.player.pause()

    def seek(self, seconds):
        """Set the position of the song to the position dragged to by the user."""
        self.player.setPosition(seconds * 1000)

    def song_duration(self, duration):
        """Set the slider to the duration of the currently played media."""
        duration /= 1000
        self.duration = duration
        self.slider.setMaximum(duration)

    def song_position(self, progress):
        """Move the horizontal slider in sync with the duration of the song.

        The progress is relayed to update_duration() in order
        to display the time label next to the slider.
        """
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.update_duration(progress)

    def update_duration(self, current_duration):
        """Calculate the time played and the length of the song.

        Both of these times are sent to duration_label() in order to display the
        times on the toolbar.
        """
        duration = self.duration

        if current_duration or duration:
            time_played = QTime((current_duration / 3600) % 60, (current_duration / 60) % 60,
                                (current_duration % 60), (current_duration * 1000) % 1000)
            song_length = QTime((duration / 3600) % 60, (duration / 60) % 60, (duration % 60),
                                (duration * 1000) % 1000)

            if duration > 3600:
                time_format = "hh:mm:ss"
            else:
                time_format = "mm:ss"

            time_display = "{} / {}" .format(time_played.toString(time_format), song_length.toString(time_format))

        else:
            time_display = ""

        self.duration_label.setText(time_display)

    def set_state(self, state):
        """Change the icon in the toolbar in relation to the state of the player.

        The play icon changes to the pause icon when a song is playing and
        the pause icon changes back to the play icon when either paused or
        stopped.
        """
        if self.player.state() == QMediaPlayer.PlayingState:
            pause_icon = utilities.resource_filename('mosaic.images', 'md_pause.png')
            self.play_action.setIcon(QIcon(pause_icon))
            self.play_action.triggered.connect(self.player.pause)

        elif (self.player.state() == QMediaPlayer.PausedState or self.player.state() == QMediaPlayer.StoppedState):
            self.play_action.triggered.connect(self.player.play)
            play_icon = utilities.resource_filename('mosaic.images', 'md_play.png')
            self.play_action.setIcon(QIcon(play_icon))

    def previous(self):
        """Move to the previous song in the playlist.

        Moves to the previous song in the playlist if the current song is less
        than five seconds in. Otherwise, restarts the current song.
        """
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def repeat_song(self):
        """Set the current media to repeat and change the repeat icon accordingly.

        There are four playback modes: repeat none, repeat all, repeat once, and shuffle.
        Clicking the repeat button cycles through each playback mode.
        """
        if self.playlist.playbackMode() == QMediaPlaylist.Sequential:
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
            repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_all.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Loop:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
            repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_once.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop:
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
            repeat_icon = utilities.resource_filename('mosaic.images', 'md_shuffle.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Random:
            self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
            repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

    def activate_playlist_item(self, item):
        """Set the active media to the playlist item dobule-clicked on by the user."""
        current_index = self.playlist_view.row(item)
        if self.playlist.currentIndex() != current_index:
            self.playlist.setCurrentIndex(current_index)

        if self.player.state() != QMediaPlayer.PlayingState:
            self.player.play()

    def change_index(self, row):
        """Highlight the row in the playlist of the active media."""
        self.playlist_view.setCurrentRow(row)

    def minimalist_view(self):
        """Resize the window to only show the menu bar and audio controls."""
        if self.minimalist_view_action.isChecked():

            if self.playlist_dock.isVisible():
                self.playlist_dock_state = True
            if self.library_dock.isVisible():
                self.library_dock_state = True

            self.library_dock.close()
            self.playlist_dock.close()

            QTimer.singleShot(10, lambda: self.resize(500, 0))

        else:
            self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

            if self.library_dock_state:
                self.library_dock.setVisible(True)

            if self.playlist_dock_state:
                self.playlist_dock.setVisible(True)

    def dock_visiblity_change(self, visible):
        """Change the size of the main window when the docks are toggled."""
        if visible and self.playlist_dock.isVisible() and not self.library_dock.isVisible():
            self.resize(defaults.Settings().window_size + self.playlist_dock.width() + 6,
                        self.height())

        elif visible and not self.playlist_dock.isVisible() and self.library_dock.isVisible():
            self.resize(defaults.Settings().window_size + self.library_dock.width() + 6,
                        self.height())

        elif visible and self.playlist_dock.isVisible() and self.library_dock.isVisible():
            self.resize(defaults.Settings().window_size + self.library_dock.width() + 6,
                        self.height())

        elif (not visible and not self.playlist_dock.isVisible() and not
                self.library_dock.isVisible()):
            self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

    def media_information_dialog(self):
        """Show a dialog of the current song's metadata."""
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
        else:
            file_path = None
        dialog = information.InformationDialog(file_path)
        dialog.exec_()

    def change_window_size(self):
        """Change the window size of the music player."""
        self.playlist_dock.close()
        self.library_dock.close()
        self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63)

    def change_media_library_path(self, path):
        """Change the media library path to the new path selected in the preferences dialog."""
        self.library_model.setRootPath(path)
        self.library_view.setModel(self.library_model)
        self.library_view.setRootIndex(self.library_model.index(path))

    def closeEvent(self, event):
        """Override the PyQt close event in order to handle save playlist on close."""
        playlist = "{}/.m3u" .format(self.playlist_location)
        if defaults.Settings().save_playlist_on_close:
            self.playlist.save(QUrl().fromLocalFile(playlist), "m3u")
        else:
            if os.path.exists(playlist):
                os.remove(playlist)
        QApplication.quit()
Ejemplo n.º 8
0
class Browser(Application):
    # pylint: disable=too-many-instance-attributes
    """The main browser"""
    url_scheme: QWebEngineUrlScheme
    bridge_initialized: bool
    dev_view: QWebEngineView
    dev_page: WebPage
    qdock: QDockWidget

    def __init__(self):
        super().__init__()
        self.init()
        # self.load()

    def init(self):
        """Initialize browser"""
        logger.debug("Initializing Browser Window")

        if web_greeter_config["config"]["greeter"]["debug_mode"]:
            os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '12345'

        url_scheme = "web-greeter"
        self.url_scheme = QWebEngineUrlScheme(url_scheme.encode())
        self.url_scheme.setDefaultPort(QWebEngineUrlScheme.PortUnspecified)
        self.url_scheme.setFlags(QWebEngineUrlScheme.SecureScheme
                                 or QWebEngineUrlScheme.LocalScheme
                                 or QWebEngineUrlScheme.LocalAccessAllowed)
        QWebEngineUrlScheme.registerScheme(self.url_scheme)

        self.profile = QWebEngineProfile.defaultProfile()
        self.interceptor = QtUrlRequestInterceptor(url_scheme)
        self.url_scheme_handler = QtUrlSchemeHandler()

        self.view = QWebEngineView(parent=self.window)
        self.page = WebPage()
        self.view.setPage(self.page)
        self.page.setObjectName("WebG Page")
        self.view.setObjectName("WebG View")

        self.channel = QWebChannel(self.page)
        self.bridge_initialized = False

        self.profile.installUrlSchemeHandler(url_scheme.encode(),
                                             self.url_scheme_handler)

        self._initialize_page()

        if web_greeter_config["config"]["greeter"]["debug_mode"]:
            self._initialize_devtools()
        else:
            self.view.setContextMenuPolicy(Qt.PreventContextMenu)

        self._init_actions()
        if web_greeter_config["app"]["frame"]:
            self._init_menu_bar()
        else:
            self.window.setWindowFlags(self.window.windowFlags()
                                       | Qt.FramelessWindowHint)

        if web_greeter_config["config"]["greeter"]["secure_mode"]:
            if hasattr(QWebEngineProfile, "setUrlRequestInterceptor"):
                self.profile.setUrlRequestInterceptor(self.interceptor)
            else:  # Older Qt5 versions
                self.profile.setRequestInterceptor(self.interceptor)

        self.page.setBackgroundColor(QColor(0, 0, 0))
        self.window.setStyleSheet("""QMainWindow, QWebEngineView {
	                                background: #000000;
                                 }""")

        self.window.setCentralWidget(self.view)

        logger.debug("Browser Window created")

    def load(self):
        """Load theme and initialize bridge"""
        self.load_theme()

        self.bridge_objects = (self.greeter, self.greeter_config,
                               self.theme_utils)
        self.initialize_bridge_objects()
        self.load_script(':/_greeter/js/bundle.js', 'Web Greeter Bundle')

    def _initialize_devtools(self):
        self.dev_view = QWebEngineView(parent=self.window)
        self.dev_page = WebPage()
        self.dev_view.setPage(self.dev_page)
        self.page.setDevToolsPage(self.dev_page)
        self.dev_view.setObjectName("Devtools view")
        self.dev_page.setObjectName("Devtools page")

        self.dev_page.windowCloseRequested.connect(
            lambda: self.toggle_devtools_value(False))

        inspect_element_action = self.page.action(self.page.InspectElement)
        inspect_element_action.triggered.connect(
            lambda: self.toggle_devtools_value(True))

        self.qdock = QDockWidget()
        self.qdock.setWidget(self.dev_view)
        self.qdock.setFeatures(QDockWidget.DockWidgetMovable
                               or QDockWidget.DockWidgetClosable)

        self.window.addDockWidget(Qt.RightDockWidgetArea, self.qdock)
        self.qdock.hide()
        logger.debug("DevTools initialized")

    def toggle_devtools(self):
        """Toggle devtools"""
        if not web_greeter_config["config"]["greeter"]["debug_mode"]:
            return
        self.toggle_devtools_value(not self.qdock.isVisible())

    def toggle_devtools_value(self, value: bool):
        """Toggle devtools by value"""
        if not web_greeter_config["config"]["greeter"]["debug_mode"]:
            return

        if value:
            self.qdock.show()
            self.dev_view.setFocus()
        else:
            self.qdock.hide()
            self.view.setFocus()

    def _init_actions(self):
        """Init browser actions"""
        self.exit_action = QAction(QIcon("exit.png"), "&Quit", self.window)
        self.exit_action.setShortcut("Ctrl+Q")
        self.exit_action.setStatusTip("Exit application")
        self.exit_action.triggered.connect(qApp.quit)

        self.toggle_dev_action = QAction("Toggle Developer Tools", self.window)
        self.toggle_dev_action.setShortcut("Ctrl+Shift+I")
        self.toggle_dev_action.triggered.connect(self.toggle_devtools)

        self.fullscreen_action = QAction("Toggle Fullscreen", self.window)
        self.fullscreen_action.setShortcut("F11")
        self.fullscreen_action.triggered.connect(
            lambda: self.toggle_fullscreen(not self.window.isFullScreen()))

        self.inc_zoom_action = QAction("Zoom In", self.window)
        self.inc_zoom_action.setShortcut("Ctrl++")
        self.inc_zoom_action.triggered.connect(self._inc_zoom)
        self.dec_zoom_action = QAction("Zoom Out", self.window)
        self.dec_zoom_action.setShortcut("Ctrl+-")
        self.dec_zoom_action.triggered.connect(self._dec_zoom)
        self.reset_zoom_action = QAction("Actual Size", self.window)
        self.reset_zoom_action.setShortcut("Ctrl+0")
        self.reset_zoom_action.triggered.connect(self._reset_zoom)

        self.window.addAction(self.exit_action)
        self.window.addAction(self.toggle_dev_action)
        self.window.addAction(self.fullscreen_action)
        self.window.addAction(self.inc_zoom_action)
        self.window.addAction(self.dec_zoom_action)
        self.window.addAction(self.reset_zoom_action)

    def _inc_zoom(self):
        if self.view.hasFocus():
            self.page.increaseZoom()
        else:
            self.dev_page.increaseZoom()

    def _dec_zoom(self):
        if self.view.hasFocus():
            self.page.decreaseZoom()
        else:
            self.dev_page.decreaseZoom()

    def _reset_zoom(self):
        if self.view.hasFocus():
            self.page.setZoomFactor(1)
        else:
            self.dev_page.setZoomFactor(1)

    def _init_menu_bar(self):
        minimize_action = QAction("Minimize", self.window)
        minimize_action.setShortcut("Ctrl+M")
        minimize_action.triggered.connect(self.window.showMinimized)
        close_action = QAction("Close", self.window)
        close_action.setShortcut("Ctrl+W")
        close_action.triggered.connect(self.window.close)

        self.page.action(
            self.page.ReloadAndBypassCache).setText("Force Reload")

        self.page.fullScreenRequested.connect(self.accept_fullscreen)

        self.menu_bar = QMenuBar()

        file_menu = self.menu_bar.addMenu("&File")
        file_menu.addAction(self.exit_action)

        edit_menu = self.menu_bar.addMenu("&Edit")
        edit_menu.addAction(self.page.action(self.page.Undo))
        edit_menu.addAction(self.page.action(self.page.Redo))
        edit_menu.addSeparator()
        edit_menu.addAction(self.page.action(self.page.Cut))
        edit_menu.addAction(self.page.action(self.page.Copy))
        edit_menu.addAction(self.page.action(self.page.Paste))
        edit_menu.addSeparator()
        edit_menu.addAction(self.page.action(self.page.SelectAll))

        view_menu = self.menu_bar.addMenu("&View")
        view_menu.addAction(self.page.action(self.page.Reload))
        view_menu.addAction(self.page.action(self.page.ReloadAndBypassCache))
        view_menu.addAction(self.toggle_dev_action)
        view_menu.addSeparator()
        view_menu.addAction(self.reset_zoom_action)
        view_menu.addAction(self.inc_zoom_action)
        view_menu.addAction(self.dec_zoom_action)
        view_menu.addSeparator()
        view_menu.addAction(self.fullscreen_action)

        window_menu = self.menu_bar.addMenu("&Window")
        window_menu.addAction(minimize_action)
        window_menu.addAction(close_action)

        # help_menu = menu_bar.addMenu("&Help")

        self.window.setMenuBar(self.menu_bar)

    def accept_fullscreen(self, request):
        """Accepts fullscreen requests"""
        if web_greeter_config["config"]["greeter"]["debug_mode"]:
            request.reject()
            return
        if request.toggleOn():
            self.toggle_fullscreen(True)
        else:
            self.toggle_fullscreen(False)
        request.accept()

    def toggle_fullscreen(self, value: bool):
        """Toggle fullscreen"""
        if not web_greeter_config["config"]["greeter"]["debug_mode"]:
            return
        if value:
            state = self.states["FULLSCREEN"]
            self.window.setWindowFlags(self.window.windowFlags()
                                       or Qt.FramelessWindowHint)
            self.menu_bar.setParent(None)
            self.window.setMenuBar(None)
        else:
            state = self.states["NORMAL"]
            self.window.setWindowFlags(self.window.windowFlags()
                                       or not Qt.FramelessWindowHint)
            self.window.setMenuBar(self.menu_bar)
        try:
            self.window.windowHandle().setWindowState(state)
        except (AttributeError, TypeError):
            self.window.setWindowState(state)

    def _initialize_page(self):
        page_settings = self.page.settings().globalSettings()

        if not web_greeter_config["config"]["greeter"]["secure_mode"]:
            ENABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls')
        else:
            DISABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls')

        for setting in DISABLED_SETTINGS:
            try:
                page_settings.setAttribute(
                    getattr(QWebEngineSettings, setting), False)
            except AttributeError:
                pass

        for setting in ENABLED_SETTINGS:
            try:
                page_settings.setAttribute(
                    getattr(QWebEngineSettings, setting), True)
            except AttributeError:
                pass

        self.page.setView(self.view)

    def load_theme(self):
        """Load theme"""
        theme = web_greeter_config["config"]["greeter"]["theme"]
        dir_t = "/usr/share/web-greeter/themes/"
        path_to_theme = os.path.join(dir_t, theme, "index.html")
        def_theme = "gruvbox"

        if theme.startswith("/"):
            path_to_theme = theme
        elif theme.__contains__(".") or theme.__contains__("/"):
            path_to_theme = os.path.join(os.getcwd(), theme)
            path_to_theme = os.path.realpath(path_to_theme)

        if not path_to_theme.endswith(".html"):
            path_to_theme = os.path.join(path_to_theme, "index.html")

        if not os.path.exists(path_to_theme):
            print("Path does not exists", path_to_theme)
            path_to_theme = os.path.join(dir_t, def_theme, "index.html")

        web_greeter_config["config"]["greeter"]["theme"] = path_to_theme

        url = QUrl(f"web-greeter://app/{path_to_theme}")
        self.page.load(url)

        logger.debug("Theme loaded")

    @staticmethod
    def _create_webengine_script(path: Url, name: str) -> QWebEngineScript:
        script = QWebEngineScript()
        script_file = QFile(path)

        # print(script_file, path)

        if script_file.open(QFile.ReadOnly):
            script_string = str(script_file.readAll(), 'utf-8')

            script.setInjectionPoint(QWebEngineScript.DocumentCreation)
            script.setName(name)
            script.setWorldId(QWebEngineScript.MainWorld)
            script.setSourceCode(script_string)
            # print(script_string)

        return script

    def _get_channel_api_script(self) -> QWebEngineScript:
        return self._create_webengine_script(':/qtwebchannel/qwebchannel.js',
                                             'QWebChannel API')

    def _init_bridge_channel(self) -> None:
        self.page.setWebChannel(self.channel)
        self.bridge_initialized = True

    def initialize_bridge_objects(self) -> None:
        """Initialize bridge objects :D"""
        if not self.bridge_initialized:
            self._init_bridge_channel()
        registered_objects = self.channel.registeredObjects()

        for obj in self.bridge_objects:
            if obj not in registered_objects:
                # pylint: disable=protected-access
                self.channel.registerObject(obj._name, obj)
                # print("Registered", obj._name)

    def load_script(self, path: Url, name: str):
        """Loads a script in page"""
        qt_api = self._get_channel_api_script()
        qt_api_source = qt_api.sourceCode()
        script = self._create_webengine_script(path, name)
        script.setSourceCode(qt_api_source + "\n" + script.sourceCode())
        self.page.scripts().insert(script)
Ejemplo n.º 9
0
class MusicPlayer(QMainWindow):
    """MusicPlayer houses all of elements that directly interact with the main window."""
    def __init__(self, parent=None):
        """Initialize the QMainWindow widget.

        The window title, window icon, and window size are initialized here as well
        as the following widgets: QMediaPlayer, QMediaPlaylist, QMediaContent, QMenuBar,
        QToolBar, QLabel, QPixmap, QSlider, QDockWidget, QListWidget, QWidget, and
        QVBoxLayout. The connect signals for relavant widgets are also initialized.
        """
        super(MusicPlayer, self).__init__(parent)
        self.setWindowTitle('Mosaic')

        window_icon = utilities.resource_filename('mosaic.images', 'icon.png')
        self.setWindowIcon(QIcon(window_icon))
        self.resize(defaults.Settings().window_size,
                    defaults.Settings().window_size + 63)

        # Initiates Qt objects to be used by MusicPlayer
        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.playlist_location = defaults.Settings().playlist_path
        self.content = QMediaContent()
        self.menu = self.menuBar()
        self.toolbar = QToolBar()
        self.art = QLabel()
        self.pixmap = QPixmap()
        self.slider = QSlider(Qt.Horizontal)
        self.duration_label = QLabel()
        self.playlist_dock = QDockWidget('Playlist', self)
        self.library_dock = QDockWidget('Media Library', self)
        self.playlist_view = QListWidget()
        self.library_view = library.MediaLibraryView()
        self.library_model = library.MediaLibraryModel()
        self.preferences = configuration.PreferencesDialog()
        self.widget = QWidget()
        self.layout = QVBoxLayout(self.widget)
        self.duration = 0
        self.playlist_dock_state = None
        self.library_dock_state = None

        # Sets QWidget() as the central widget of the main window
        self.setCentralWidget(self.widget)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.art.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)

        # Initiates the playlist dock widget and the library dock widget
        self.addDockWidget(defaults.Settings().dock_position,
                           self.playlist_dock)
        self.playlist_dock.setWidget(self.playlist_view)
        self.playlist_dock.setVisible(defaults.Settings().playlist_on_start)
        self.playlist_dock.setFeatures(QDockWidget.DockWidgetClosable)

        self.addDockWidget(defaults.Settings().dock_position,
                           self.library_dock)
        self.library_dock.setWidget(self.library_view)
        self.library_dock.setVisible(
            defaults.Settings().media_library_on_start)
        self.library_dock.setFeatures(QDockWidget.DockWidgetClosable)
        self.tabifyDockWidget(self.playlist_dock, self.library_dock)

        # Sets the range of the playback slider and sets the playback mode as looping
        self.slider.setRange(0, self.player.duration() / 1000)
        self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)

        # OSX system menu bar causes conflicts with PyQt5 menu bar
        if sys.platform == 'darwin':
            self.menu.setNativeMenuBar(False)

        # Initiates Settings in the defaults module to give access to settings.toml
        defaults.Settings()

        # Signals that connect to other methods when they're called
        self.player.metaDataChanged.connect(self.display_meta_data)
        self.slider.sliderMoved.connect(self.seek)
        self.player.durationChanged.connect(self.song_duration)
        self.player.positionChanged.connect(self.song_position)
        self.player.stateChanged.connect(self.set_state)
        self.playlist_view.itemActivated.connect(self.activate_playlist_item)
        self.library_view.activated.connect(self.open_media_library)
        self.playlist.currentIndexChanged.connect(self.change_index)
        self.playlist.mediaInserted.connect(self.initialize_playlist)
        self.playlist_dock.visibilityChanged.connect(
            self.dock_visiblity_change)
        self.library_dock.visibilityChanged.connect(self.dock_visiblity_change)
        self.preferences.dialog_media_library.media_library_line.textChanged.connect(
            self.change_media_library_path)
        self.preferences.dialog_view_options.dropdown_box.currentIndexChanged.connect(
            self.change_window_size)
        self.art.mousePressEvent = self.press_playback

        # Creating the menu controls, media controls, and window size of the music player
        self.menu_controls()
        self.media_controls()
        self.load_saved_playlist()

    def menu_controls(self):
        """Initiate the menu bar and add it to the QMainWindow widget."""
        self.file = self.menu.addMenu('File')
        self.edit = self.menu.addMenu('Edit')
        self.playback = self.menu.addMenu('Playback')
        self.view = self.menu.addMenu('View')
        self.help_ = self.menu.addMenu('Help')

        self.file_menu()
        self.edit_menu()
        self.playback_menu()
        self.view_menu()
        self.help_menu()

    def media_controls(self):
        """Create the bottom toolbar and controls used for media playback."""
        self.addToolBar(Qt.BottomToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)

        play_icon = utilities.resource_filename('mosaic.images', 'md_play.png')
        self.play_action = QAction(QIcon(play_icon), 'Play', self)
        self.play_action.triggered.connect(self.player.play)

        stop_icon = utilities.resource_filename('mosaic.images', 'md_stop.png')
        self.stop_action = QAction(QIcon(stop_icon), 'Stop', self)
        self.stop_action.triggered.connect(self.player.stop)

        previous_icon = utilities.resource_filename('mosaic.images',
                                                    'md_previous.png')
        self.previous_action = QAction(QIcon(previous_icon), 'Previous', self)
        self.previous_action.triggered.connect(self.previous)

        next_icon = utilities.resource_filename('mosaic.images', 'md_next.png')
        self.next_action = QAction(QIcon(next_icon), 'Next', self)
        self.next_action.triggered.connect(self.playlist.next)

        repeat_icon = utilities.resource_filename('mosaic.images',
                                                  'md_repeat_none.png')
        self.repeat_action = QAction(QIcon(repeat_icon), 'Repeat', self)
        self.repeat_action.triggered.connect(self.repeat_song)

        self.toolbar.addAction(self.play_action)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.addAction(self.previous_action)
        self.toolbar.addAction(self.next_action)
        self.toolbar.addAction(self.repeat_action)
        self.toolbar.addWidget(self.slider)
        self.toolbar.addWidget(self.duration_label)

    def file_menu(self):
        """Add a file menu to the menu bar.

        The file menu houses the Open File, Open Multiple Files, Open Playlist,
        Open Directory, and Exit Application menu items.
        """
        self.open_action = QAction('Open File', self)
        self.open_action.setShortcut('O')
        self.open_action.triggered.connect(self.open_file)

        self.open_multiple_files_action = QAction('Open Multiple Files', self)
        self.open_multiple_files_action.setShortcut('M')
        self.open_multiple_files_action.triggered.connect(
            self.open_multiple_files)

        self.open_playlist_action = QAction('Open Playlist', self)
        self.open_playlist_action.setShortcut('CTRL+P')
        self.open_playlist_action.triggered.connect(self.open_playlist)

        self.open_directory_action = QAction('Open Directory', self)
        self.open_directory_action.setShortcut('D')
        self.open_directory_action.triggered.connect(self.open_directory)

        self.save_playlist_action = QAction('Save Playlist', self)
        self.save_playlist_action.setShortcut('CTRL+S')
        self.save_playlist_action.triggered.connect(self.save_playlist)

        self.exit_action = QAction('Quit', self)
        self.exit_action.setShortcut('CTRL+Q')
        self.exit_action.triggered.connect(self.closeEvent)

        self.file.addAction(self.open_action)
        self.file.addAction(self.open_multiple_files_action)
        self.file.addAction(self.open_playlist_action)
        self.file.addAction(self.open_directory_action)
        self.file.addSeparator()
        self.file.addAction(self.save_playlist_action)
        self.file.addSeparator()
        self.file.addAction(self.exit_action)

    def edit_menu(self):
        """Add an edit menu to the menu bar.

        The edit menu houses the preferences item that opens a preferences dialog
        that allows the user to customize features of the music player.
        """
        self.preferences_action = QAction('Preferences', self)
        self.preferences_action.setShortcut('CTRL+SHIFT+P')
        self.preferences_action.triggered.connect(
            lambda: self.preferences.exec_())

        self.edit.addAction(self.preferences_action)

    def playback_menu(self):
        """Add a playback menu to the menu bar.

        The playback menu houses
        """
        self.play_playback_action = QAction('Play', self)
        self.play_playback_action.setShortcut('P')
        self.play_playback_action.triggered.connect(self.player.play)

        self.stop_playback_action = QAction('Stop', self)
        self.stop_playback_action.setShortcut('S')
        self.stop_playback_action.triggered.connect(self.player.stop)

        self.previous_playback_action = QAction('Previous', self)
        self.previous_playback_action.setShortcut('B')
        self.previous_playback_action.triggered.connect(self.previous)

        self.next_playback_action = QAction('Next', self)
        self.next_playback_action.setShortcut('N')
        self.next_playback_action.triggered.connect(self.playlist.next)

        self.playback.addAction(self.play_playback_action)
        self.playback.addAction(self.stop_playback_action)
        self.playback.addAction(self.previous_playback_action)
        self.playback.addAction(self.next_playback_action)

    def view_menu(self):
        """Add a view menu to the menu bar.

        The view menu houses the Playlist, Media Library, Minimalist View, and Media
        Information menu items. The Playlist item toggles the playlist dock into and
        out of view. The Media Library items toggles the media library dock into and
        out of view. The Minimalist View item resizes the window and shows only the
        menu bar and player controls. The Media Information item opens a dialog that
        shows information relevant to the currently playing song.
        """
        self.dock_action = self.playlist_dock.toggleViewAction()
        self.dock_action.setShortcut('CTRL+ALT+P')

        self.library_dock_action = self.library_dock.toggleViewAction()
        self.library_dock_action.setShortcut('CTRL+ALT+L')

        self.minimalist_view_action = QAction('Minimalist View', self)
        self.minimalist_view_action.setShortcut('CTRL+ALT+M')
        self.minimalist_view_action.setCheckable(True)
        self.minimalist_view_action.triggered.connect(self.minimalist_view)

        self.view_media_info_action = QAction('Media Information', self)
        self.view_media_info_action.setShortcut('CTRL+SHIFT+M')
        self.view_media_info_action.triggered.connect(
            self.media_information_dialog)

        self.view.addAction(self.dock_action)
        self.view.addAction(self.library_dock_action)
        self.view.addSeparator()
        self.view.addAction(self.minimalist_view_action)
        self.view.addSeparator()
        self.view.addAction(self.view_media_info_action)

    def help_menu(self):
        """Add a help menu to the menu bar.

        The help menu houses the about dialog that shows the user information
        related to the application.
        """
        self.about_action = QAction('About', self)
        self.about_action.setShortcut('H')
        self.about_action.triggered.connect(
            lambda: about.AboutDialog().exec_())

        self.help_.addAction(self.about_action)

    def open_file(self):
        """Open the selected file and add it to a new playlist."""
        filename, success = QFileDialog.getOpenFileName(
            self, 'Open File', '', 'Audio (*.mp3 *.flac)', '',
            QFileDialog.ReadOnly)

        if success:
            file_info = QFileInfo(filename).fileName()
            playlist_item = QListWidgetItem(file_info)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.addMedia(
                QMediaContent(QUrl().fromLocalFile(filename)))
            self.player.setPlaylist(self.playlist)
            playlist_item.setToolTip(file_info)
            self.playlist_view.addItem(playlist_item)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_multiple_files(self):
        """Open the selected files and add them to a new playlist."""
        filenames, success = QFileDialog.getOpenFileNames(
            self, 'Open Multiple Files', '', 'Audio (*.mp3 *.flac)', '',
            QFileDialog.ReadOnly)

        if success:
            self.playlist.clear()
            self.playlist_view.clear()
            for file in natsort.natsorted(filenames, alg=natsort.ns.PATH):
                file_info = QFileInfo(file).fileName()
                playlist_item = QListWidgetItem(file_info)
                self.playlist.addMedia(
                    QMediaContent(QUrl().fromLocalFile(file)))
                self.player.setPlaylist(self.playlist)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)
                self.playlist_view.setCurrentRow(0)
                self.player.play()

    def open_playlist(self):
        """Load an M3U or PLS file into a new playlist."""
        playlist, success = QFileDialog.getOpenFileName(
            self, 'Open Playlist', '', 'Playlist (*.m3u *.pls)', '',
            QFileDialog.ReadOnly)

        if success:
            playlist = QUrl.fromLocalFile(playlist)
            self.playlist.clear()
            self.playlist_view.clear()
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(
                    song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def save_playlist(self):
        """Save the media in the playlist dock as a new M3U playlist."""
        playlist, success = QFileDialog.getSaveFileName(
            self, 'Save Playlist', '', 'Playlist (*.m3u)', '')
        if success:
            saved_playlist = "{}.m3u".format(playlist)
            self.playlist.save(QUrl().fromLocalFile(saved_playlist), "m3u")

    def load_saved_playlist(self):
        """Load the saved playlist if user setting permits."""
        saved_playlist = "{}/.m3u".format(self.playlist_location)
        if os.path.exists(saved_playlist):
            playlist = QUrl().fromLocalFile(saved_playlist)
            self.playlist.load(playlist)
            self.player.setPlaylist(self.playlist)

            for song_index in range(self.playlist.mediaCount()):
                file_info = self.playlist.media(
                    song_index).canonicalUrl().fileName()
                playlist_item = QListWidgetItem(file_info)
                playlist_item.setToolTip(file_info)
                self.playlist_view.addItem(playlist_item)

            self.playlist_view.setCurrentRow(0)

    def open_directory(self):
        """Open the selected directory and add the files within to an empty playlist."""
        directory = QFileDialog.getExistingDirectory(self, 'Open Directory',
                                                     '', QFileDialog.ReadOnly)

        if directory:
            self.playlist.clear()
            self.playlist_view.clear()
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(
                            QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

            self.player.setPlaylist(self.playlist)
            self.playlist_view.setCurrentRow(0)
            self.player.play()

    def open_media_library(self, index):
        """Open a directory or file from the media library into an empty playlist."""
        self.playlist.clear()
        self.playlist_view.clear()

        if self.library_model.fileName(index).endswith(('mp3', 'flac')):
            self.playlist.addMedia(
                QMediaContent(QUrl().fromLocalFile(
                    self.library_model.filePath(index))))
            self.playlist_view.addItem(self.library_model.fileName(index))

        elif self.library_model.isDir(index):
            directory = self.library_model.filePath(index)
            for dirpath, __, files in os.walk(directory):
                for filename in natsort.natsorted(files, alg=natsort.ns.PATH):
                    file = os.path.join(dirpath, filename)
                    if filename.endswith(('mp3', 'flac')):
                        self.playlist.addMedia(
                            QMediaContent(QUrl().fromLocalFile(file)))
                        playlist_item = QListWidgetItem(filename)
                        playlist_item.setToolTip(filename)
                        self.playlist_view.addItem(playlist_item)

        self.player.setPlaylist(self.playlist)
        self.player.play()

    def display_meta_data(self):
        """Display the current song's metadata in the main window.

        If the current song contains metadata, its cover art is extracted and shown in
        the main window while the track number, artist, album, and track title are shown
        in the window title.
        """
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
            (album, artist, title, track_number, *__,
             artwork) = metadata.metadata(file_path)

            try:
                self.pixmap.loadFromData(artwork)
            except TypeError:
                self.pixmap = QPixmap(artwork)

            meta_data = '{} - {} - {} - {}'.format(track_number, artist, album,
                                                   title)

            self.setWindowTitle(meta_data)
            self.art.setScaledContents(True)
            self.art.setPixmap(self.pixmap)
            self.layout.addWidget(self.art)

    def initialize_playlist(self, start):
        """Display playlist and reset playback mode when media inserted into playlist."""
        if start == 0:
            if self.library_dock.isVisible():
                self.playlist_dock.setVisible(True)
                self.playlist_dock.show()
                self.playlist_dock.raise_()

            if self.playlist.playbackMode() != QMediaPlaylist.Sequential:
                self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
                repeat_icon = utilities.resource_filename(
                    'mosaic.images', 'md_repeat_none.png')
                self.repeat_action.setIcon(QIcon(repeat_icon))

    def press_playback(self, event):
        """Change the playback of the player on cover art mouse event.

        When the cover art is clicked, the player will play the media if the player is
        either paused or stopped. If the media is playing, the media is set
        to pause.
        """
        if event.button() == 1 and configuration.Playback(
        ).cover_art_playback.isChecked():
            if (self.player.state() == QMediaPlayer.StoppedState
                    or self.player.state() == QMediaPlayer.PausedState):
                self.player.play()
            elif self.player.state() == QMediaPlayer.PlayingState:
                self.player.pause()

    def seek(self, seconds):
        """Set the position of the song to the position dragged to by the user."""
        self.player.setPosition(seconds * 1000)

    def song_duration(self, duration):
        """Set the slider to the duration of the currently played media."""
        duration /= 1000
        self.duration = duration
        self.slider.setMaximum(duration)

    def song_position(self, progress):
        """Move the horizontal slider in sync with the duration of the song.

        The progress is relayed to update_duration() in order
        to display the time label next to the slider.
        """
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.update_duration(progress)

    def update_duration(self, current_duration):
        """Calculate the time played and the length of the song.

        Both of these times are sent to duration_label() in order to display the
        times on the toolbar.
        """
        duration = self.duration

        if current_duration or duration:
            time_played = QTime(
                (current_duration / 3600) % 60, (current_duration / 60) % 60,
                (current_duration % 60), (current_duration * 1000) % 1000)
            song_length = QTime((duration / 3600) % 60, (duration / 60) % 60,
                                (duration % 60), (duration * 1000) % 1000)

            if duration > 3600:
                time_format = "hh:mm:ss"
            else:
                time_format = "mm:ss"

            time_display = "{} / {}".format(time_played.toString(time_format),
                                            song_length.toString(time_format))

        else:
            time_display = ""

        self.duration_label.setText(time_display)

    def set_state(self, state):
        """Change the icon in the toolbar in relation to the state of the player.

        The play icon changes to the pause icon when a song is playing and
        the pause icon changes back to the play icon when either paused or
        stopped.
        """
        if self.player.state() == QMediaPlayer.PlayingState:
            pause_icon = utilities.resource_filename('mosaic.images',
                                                     'md_pause.png')
            self.play_action.setIcon(QIcon(pause_icon))
            self.play_action.triggered.connect(self.player.pause)

        elif (self.player.state() == QMediaPlayer.PausedState
              or self.player.state() == QMediaPlayer.StoppedState):
            self.play_action.triggered.connect(self.player.play)
            play_icon = utilities.resource_filename('mosaic.images',
                                                    'md_play.png')
            self.play_action.setIcon(QIcon(play_icon))

    def previous(self):
        """Move to the previous song in the playlist.

        Moves to the previous song in the playlist if the current song is less
        than five seconds in. Otherwise, restarts the current song.
        """
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def repeat_song(self):
        """Set the current media to repeat and change the repeat icon accordingly.

        There are four playback modes: repeat none, repeat all, repeat once, and shuffle.
        Clicking the repeat button cycles through each playback mode.
        """
        if self.playlist.playbackMode() == QMediaPlaylist.Sequential:
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
            repeat_on_icon = utilities.resource_filename(
                'mosaic.images', 'md_repeat_all.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Loop:
            self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
            repeat_on_icon = utilities.resource_filename(
                'mosaic.images', 'md_repeat_once.png')
            self.repeat_action.setIcon(QIcon(repeat_on_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop:
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
            repeat_icon = utilities.resource_filename('mosaic.images',
                                                      'md_shuffle.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

        elif self.playlist.playbackMode() == QMediaPlaylist.Random:
            self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
            repeat_icon = utilities.resource_filename('mosaic.images',
                                                      'md_repeat_none.png')
            self.repeat_action.setIcon(QIcon(repeat_icon))

    def activate_playlist_item(self, item):
        """Set the active media to the playlist item dobule-clicked on by the user."""
        current_index = self.playlist_view.row(item)
        if self.playlist.currentIndex() != current_index:
            self.playlist.setCurrentIndex(current_index)

        if self.player.state() != QMediaPlayer.PlayingState:
            self.player.play()

    def change_index(self, row):
        """Highlight the row in the playlist of the active media."""
        self.playlist_view.setCurrentRow(row)

    def minimalist_view(self):
        """Resize the window to only show the menu bar and audio controls."""
        if self.minimalist_view_action.isChecked():

            if self.playlist_dock.isVisible():
                self.playlist_dock_state = True
            if self.library_dock.isVisible():
                self.library_dock_state = True

            self.library_dock.close()
            self.playlist_dock.close()

            QTimer.singleShot(10, lambda: self.resize(500, 0))

        else:
            self.resize(defaults.Settings().window_size,
                        defaults.Settings().window_size + 63)

            if self.library_dock_state:
                self.library_dock.setVisible(True)

            if self.playlist_dock_state:
                self.playlist_dock.setVisible(True)

    def dock_visiblity_change(self, visible):
        """Change the size of the main window when the docks are toggled."""
        if visible and self.playlist_dock.isVisible(
        ) and not self.library_dock.isVisible():
            self.resize(
                defaults.Settings().window_size + self.playlist_dock.width() +
                6, self.height())

        elif visible and not self.playlist_dock.isVisible(
        ) and self.library_dock.isVisible():
            self.resize(
                defaults.Settings().window_size + self.library_dock.width() +
                6, self.height())

        elif visible and self.playlist_dock.isVisible(
        ) and self.library_dock.isVisible():
            self.resize(
                defaults.Settings().window_size + self.library_dock.width() +
                6, self.height())

        elif (not visible and not self.playlist_dock.isVisible()
              and not self.library_dock.isVisible()):
            self.resize(defaults.Settings().window_size,
                        defaults.Settings().window_size + 63)

    def media_information_dialog(self):
        """Show a dialog of the current song's metadata."""
        if self.player.isMetaDataAvailable():
            file_path = self.player.currentMedia().canonicalUrl().toLocalFile()
        else:
            file_path = None
        dialog = information.InformationDialog(file_path)
        dialog.exec_()

    def change_window_size(self):
        """Change the window size of the music player."""
        self.playlist_dock.close()
        self.library_dock.close()
        self.resize(defaults.Settings().window_size,
                    defaults.Settings().window_size + 63)

    def change_media_library_path(self, path):
        """Change the media library path to the new path selected in the preferences dialog."""
        self.library_model.setRootPath(path)
        self.library_view.setModel(self.library_model)
        self.library_view.setRootIndex(self.library_model.index(path))

    def closeEvent(self, event):
        """Override the PyQt close event in order to handle save playlist on close."""
        playlist = "{}/.m3u".format(self.playlist_location)
        if defaults.Settings().save_playlist_on_close:
            self.playlist.save(QUrl().fromLocalFile(playlist), "m3u")
        else:
            if os.path.exists(playlist):
                os.remove(playlist)
        QApplication.quit()
Ejemplo n.º 10
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.chunk_directory = Directory(
            "CHUNK", QIcon(Assets.get_asset_path("document_a4_locked.png")),
            None)
        self.mod_directory = Directory(
            "MOD", QIcon(Assets.get_asset_path("document_a4.png")), None)
        self.workspace = Workspace([self.mod_directory, self.chunk_directory],
                                   parent=self)
        self.workspace.fileOpened.connect(self.handle_workspace_file_opened)
        self.workspace.fileClosed.connect(self.handle_workspace_file_closed)
        self.workspace.fileActivated.connect(
            self.handle_workspace_file_activated)
        self.workspace.fileLoadError.connect(
            self.handle_workspace_file_load_error)
        self.init_actions()
        self.init_menu_bar()
        self.init_toolbar()
        self.setStatusBar(QStatusBar())
        self.setWindowTitle("MHW-Editor-Suite")
        self.init_file_tree(self.chunk_directory,
                            "Chunk directory",
                            self.open_chunk_directory_action,
                            filtered=True)
        self.init_file_tree(self.mod_directory, "Mod directory",
                            self.open_mod_directory_action)
        self.init_help()
        self.setCentralWidget(self.init_editor_tabs())
        self.load_settings()

    def closeEvent(self, event):
        self.write_settings()

    def load_settings(self):
        self.settings = AppSettings()
        with self.settings.main_window() as group:
            size = group.get("size", QSize(1000, 800))
            position = group.get("position", QPoint(300, 300))
        with self.settings.application() as group:
            chunk_directory = group.get("chunk_directory", None)
            mod_directory = group.get("mod_directory", None)
            lang = group.get("lang", None)
        with self.settings.import_export() as group:
            self.import_export_default_attrs = {
                key: group.get(key, "").split(";")
                for key in group.childKeys()
            }
        # apply settings
        self.resize(size)
        self.move(position)
        if chunk_directory:
            self.chunk_directory.set_path(chunk_directory)
        if mod_directory:
            self.mod_directory.set_path(mod_directory)
        if lang:
            self.handle_set_lang_action(lang)

    def write_settings(self):
        with self.settings.main_window() as group:
            group["size"] = self.size()
            group["position"] = self.pos()
        with self.settings.application() as group:
            group["chunk_directory"] = self.chunk_directory.path
            group["mod_directory"] = self.mod_directory.path
            group["lang"] = FilePluginRegistry.lang
        with self.settings.import_export() as group:
            for key, value in self.import_export_default_attrs.items():
                group[key] = ";".join(value)

    def get_icon(self, name):
        return self.style().standardIcon(name)

    def init_actions(self):
        self.open_chunk_directory_action = create_action(
            self.get_icon(QStyle.SP_DirOpenIcon), "Open chunk_directory ...",
            self.handle_open_chunk_directory, None)
        self.open_mod_directory_action = create_action(
            self.get_icon(QStyle.SP_DirOpenIcon), "Open mod directory ...",
            self.handle_open_mod_directory, QKeySequence.Open)
        self.save_file_action = create_action(
            self.get_icon(QStyle.SP_DriveHDIcon), "Save file",
            self.handle_save_file_action, QKeySequence.Save)
        self.save_file_action.setDisabled(True)
        self.export_action = create_action(self.get_icon(QStyle.SP_FileIcon),
                                           "Export file ...",
                                           self.handle_export_file_action)
        self.export_action.setDisabled(True)
        self.import_action = create_action(self.get_icon(QStyle.SP_FileIcon),
                                           "Import file ...",
                                           self.handle_import_file_action)
        self.import_action.setDisabled(True)
        self.help_action = create_action(None, "Show help",
                                         self.handle_show_help_action)
        self.about_action = create_action(None, "About",
                                          self.handle_about_action)
        self.lang_actions = {
            lang: create_action(None,
                                name,
                                partial(self.handle_set_lang_action, lang),
                                checkable=True)
            for lang, name in LANG
        }
        self.quick_access_actions = [
            create_action(
                None, title,
                partial(self.workspace.open_file_any_dir, file_rel_path))
            for title, file_rel_path in QUICK_ACCESS_ITEMS
        ]

    def init_menu_bar(self):
        menu_bar = self.menuBar()
        # file menu
        file_menu = menu_bar.addMenu("File")
        file_menu.insertAction(None, self.open_chunk_directory_action)
        file_menu.insertAction(None, self.open_mod_directory_action)
        file_menu.insertAction(None, self.export_action)
        file_menu.insertAction(None, self.import_action)
        file_menu.insertAction(None, self.save_file_action)

        quick_access_menu = menu_bar.addMenu("Quick Access")
        for action in self.quick_access_actions:
            quick_access_menu.insertAction(None, action)

        # lang menu
        lang_menu = menu_bar.addMenu("Language")
        for action in self.lang_actions.values():
            lang_menu.insertAction(None, action)

        # help menu
        help_menu = menu_bar.addMenu("Help")
        help_menu.insertAction(None, self.help_action)
        help_menu.insertAction(None, self.about_action)

    def init_toolbar(self):
        toolbar = self.addToolBar("Main")
        toolbar.setIconSize(QSize(16, 16))
        toolbar.setFloatable(False)
        toolbar.setMovable(False)
        toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        toolbar.insertAction(None, self.open_mod_directory_action)
        toolbar.insertAction(None, self.save_file_action)

    def init_file_tree(self, directory, title, action, filtered=False):
        widget = DirectoryDockWidget(directory, filtered=filtered, parent=self)
        widget.path_label.addAction(action, QLineEdit.LeadingPosition)
        widget.tree_view.activated.connect(
            partial(self.handle_directory_tree_view_activated, directory))
        dock = QDockWidget(title, self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        dock.setFeatures(QDockWidget.DockWidgetMovable)
        dock.setWidget(widget)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

    def init_help(self):
        self.help_widget = HelpWidget(self)
        self.help_widget_dock = QDockWidget("Help", self)
        self.help_widget_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                              | Qt.RightDockWidgetArea)
        self.help_widget_dock.setFeatures(QDockWidget.DockWidgetMovable)
        self.help_widget_dock.setWidget(self.help_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, self.help_widget_dock)
        self.help_widget_dock.hide()

    def handle_show_help_action(self):
        if self.help_widget_dock.isVisible():
            self.help_widget_dock.hide()
        else:
            self.help_widget_dock.show()

    def handle_directory_tree_view_activated(self, directory,
                                             qindex: QModelIndex):
        if qindex.model().isDir(qindex):
            return
        file_path = qindex.model().filePath(qindex)
        self.workspace.open_file(directory, file_path)

    def init_editor_tabs(self):
        self.editor_tabs = QTabWidget()
        self.editor_tabs.setDocumentMode(True)
        self.editor_tabs.setTabsClosable(True)
        self.editor_tabs.tabCloseRequested.connect(
            self.handle_editor_tab_close_requested)
        return self.editor_tabs

    def handle_workspace_file_opened(self, path, rel_path):
        ws_file = self.workspace.files[path]
        editor_view = EditorView.factory(self.editor_tabs, ws_file)
        editor_view.setObjectName(path)
        self.editor_tabs.addTab(editor_view, ws_file.directory.file_icon,
                                f"{ws_file.directory.name}: {rel_path}")
        self.editor_tabs.setCurrentWidget(editor_view)
        self.save_file_action.setDisabled(False)
        self.export_action.setDisabled(False)
        self.import_action.setDisabled(False)

    def handle_workspace_file_activated(self, path, rel_path):
        widget = self.editor_tabs.findChild(QWidget, path)
        self.editor_tabs.setCurrentWidget(widget)

    def handle_workspace_file_closed(self, path, rel_path):
        widget = self.editor_tabs.findChild(QWidget, path)
        widget.deleteLater()
        has_no_files_open = not self.workspace.files
        self.save_file_action.setDisabled(has_no_files_open)
        self.export_action.setDisabled(has_no_files_open)
        self.import_action.setDisabled(has_no_files_open)

    def handle_workspace_file_load_error(self, path, rel_path, error):
        QMessageBox.warning(self, f"Error loading file `{rel_path}`",
                            f"Error while loading\n{path}:\n\n{error}",
                            QMessageBox.Ok, QMessageBox.Ok)

    def handle_editor_tab_close_requested(self, tab_index):
        editor_view = self.editor_tabs.widget(tab_index)
        self.workspace.close_file(editor_view.workspace_file)

    def handle_open_chunk_directory(self):
        path = QFileDialog.getExistingDirectory(parent=self,
                                                caption="Open chunk directory")
        if path:
            self.chunk_directory.set_path(os.path.normpath(path))

    def handle_open_mod_directory(self):
        path = QFileDialog.getExistingDirectory(parent=self,
                                                caption="Open mod directory")
        if path:
            self.mod_directory.set_path(os.path.normpath(path))

    def handle_save_file_action(self):
        main_ws_file = self.get_current_workspace_file()
        for ws_file in main_ws_file.get_files_modified():
            if ws_file.directory is self.chunk_directory:
                if self.mod_directory.is_valid:
                    self.transfer_file_to_mod_workspace(
                        ws_file, ws_file is main_ws_file)
                else:
                    self.save_base_content_file(ws_file)
            else:
                with show_error_dialog(self, "Error writing file"):
                    self.save_workspace_file(ws_file)

    def handle_export_file_action(self):
        ws_file = self.get_current_workspace_file()
        plugin = FilePluginRegistry.get_plugin(ws_file.abs_path)
        fields = plugin.data_factory.EntryFactory.fields()
        data = [it.as_dict() for it in ws_file.data.entries]
        dialog = ExportDialog.init(self, data, fields,
                                   plugin.import_export.get("safe_attrs"))
        dialog.open()

    def handle_import_file_action(self):
        ws_file = self.get_current_workspace_file()
        plugin = FilePluginRegistry.get_plugin(ws_file.abs_path)
        fields = plugin.data_factory.EntryFactory.fields()
        dialog = ImportDialog.init(self,
                                   fields,
                                   plugin.import_export.get("safe_attrs"),
                                   as_list=True)
        if dialog:
            dialog.import_accepted.connect(self.handle_import_accepted)
            dialog.open()

    def handle_import_accepted(self, import_data):
        ws_file = self.get_current_workspace_file()
        num_items = min(len(import_data), len(ws_file.data))
        for idx in range(num_items):
            ws_file.data[idx].update(import_data[idx])
        self.statusBar().showMessage(
            f"Import contains {len(import_data)} items. "
            f"Model contains {len(ws_file.data)} items. "
            f"Imported {num_items}.", STATUSBAR_MESSAGE_TIMEOUT)

    def handle_set_lang_action(self, lang):
        FilePluginRegistry.lang = lang
        for act in self.lang_actions.values():
            act.setChecked(False)
        self.lang_actions[lang].setChecked(True)

    def get_current_workspace_file(self):
        editor = self.editor_tabs.currentWidget()
        return editor.workspace_file

    def save_base_content_file(self, ws_file):
        result = QMessageBox.question(
            self, "Save base content file?",
            "Do you really want to update this chunk file?",
            QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Cancel)
        if result == QMessageBox.Ok:
            with show_error_dialog(self, "Error writing file"):
                self.save_workspace_file(ws_file)

    def transfer_file_to_mod_workspace(self, ws_file, reopen=False):
        mod_abs_path, exists = self.mod_directory.get_child_path(
            ws_file.rel_path)
        if not exists:
            return self.transfer_file(ws_file, self.mod_directory, reopen)

        result = QMessageBox.question(
            self, "File exists, overwrite?",
            f"File '{ws_file.rel_path}' already found in mod directory, overwrite?",
            QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok)
        if result == QMessageBox.Ok:
            self.transfer_file(ws_file, self.mod_directory, reopen)

    def transfer_file(self, ws_file, target_directory, reopen=False):
        if target_directory is ws_file.directory:
            return
        self.workspace.close_file(ws_file)
        ws_file.set_directory(target_directory)
        self.save_workspace_file(ws_file)
        if reopen:
            self.workspace.open_file(target_directory, ws_file.abs_path)

    def save_workspace_file(self, ws_file):
        ws_file.save()
        self.statusBar().showMessage(f"File '{ws_file.abs_path}' saved.",
                                     STATUSBAR_MESSAGE_TIMEOUT)

    def handle_about_action(self):
        dialog = QDialog(self)
        dialog.setWindowTitle("About MHW Editor Suite")
        layout = QVBoxLayout()
        dialog.setLayout(layout)
        about_text = QLabel(ABOUT_TEXT)
        about_text.setTextFormat(Qt.RichText)
        about_text.setTextInteractionFlags(Qt.TextBrowserInteraction)
        about_text.setOpenExternalLinks(True)
        layout.addWidget(about_text)
        dialog.exec()
Ejemplo n.º 11
0
class Main(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.filename = ""
        self.changed = False

        self.initUI()

        style = open("style.qss", 'r')
        self.setStyleSheet(style.read())

    def initAction(self):
        self.newAction = QAction(QtGui.QIcon("icons/new.png"), "New", self)
        self.newAction.setStatusTip("Create a new document")
        self.newAction.setShortcut("Ctrl+N")
        self.newAction.triggered.connect(self.new)

        self.openAction = QAction(QtGui.QIcon("icons/open.png"), "Open file",
                                  self)
        self.openAction.setStatusTip("Open existing document")
        self.openAction.setShortcut("Ctrl+O")
        self.openAction.triggered.connect(self.open)

        self.saveAction = QAction(QtGui.QIcon("icons/save.png"), "Save", self)
        self.saveAction.setStatusTip("Save document")
        self.saveAction.setShortcut("Ctrl+S")
        self.saveAction.triggered.connect(self.save)

        self.saveAsAction = QAction("Save As", self)
        self.saveAsAction.setStatusTip("Save document as")
        self.saveAsAction.setShortcut("Ctrl+Shift+S")
        self.saveAsAction.triggered.connect(self.saveAs)

        self.exitAction = QAction("Exit", self)
        self.exitAction.setStatusTip("Exit word processor")
        self.exitAction.setShortcut("Ctrl+W")
        self.exitAction.triggered.connect(self.close)

        self.hideDirAction = QAction("Toggle Dir Visibility", self)
        self.hideDirAction.setStatusTip("Show/Hide the Directory View")
        self.hideDirAction.setShortcut("Ctrl+\\")
        self.hideDirAction.triggered.connect(self.toggleDirVisibility)

    # def initFormatbar(self):
    #     self.formatbar = self.addToolBar("Format")

    def initMenubar(self):
        menubar = self.menuBar()

        file = menubar.addMenu("File")
        edit = menubar.addMenu("Edit")
        view = menubar.addMenu("View")

        file.addAction(self.newAction)
        file.addAction(self.openAction)
        file.addAction(self.saveAction)
        file.addAction(self.saveAsAction)
        file.addAction(self.exitAction)

        view.addAction(self.hideDirAction)

    def initFont(self):
        self.font = QtGui.QFont("Futura", pointSize=16)
        self.document.setDefaultFont(self.font)

    def initFileTree(self):
        #TODO: fix the scope of these variables

        self.dockWidget = QDockWidget(self, flags=Qt.FramelessWindowHint)
        # titleWidget = QtWidget(self)
        # self.dockWidget.setTitleBarWidget(titleWidget)
        self.view = QTreeView(self)

        self.model = QFileSystemModel()
        self.model.setRootPath("/Users/kevinhouyang/Development/")
        self.view.setModel(self.model)
        self.view.setRootIndex(
            self.model.index("/Users/kevinhouyang/Development/typy"))
        self.view.hideColumn(1)
        self.view.hideColumn(2)
        self.view.hideColumn(3)
        self.view.hideColumn(4)
        self.view.setHeaderHidden(True)
        self.dockWidget.setWidget(self.view)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dockWidget)
        self.dockWidget.setFeatures(QDockWidget.NoDockWidgetFeatures)
        self.dockWidget.setVisible(False)
        self.dockWidget.setTitleBarWidget(None)

    def initUI(self):
        self.editor = QPlainTextEdit(self)
        self.editor.setCenterOnScroll(True)
        self.editor.textChanged.connect(self.toggleChanged)
        self.document = self.editor.document()

        self.document.setDocumentMargin(100)
        self.initFont()

        self.setWindowIcon(QtGui.QIcon("icons/icon.png"))

        self.initAction()
        self.initMenubar()

        self.setCentralWidget(self.editor)
        self.initFileTree()

        self.setGeometry(100, 100, 1030, 800)
        self.setWindowTitle("QtWriter")

    #TODO fix this..? how do i get formatting to show
    def updateFocus(self):
        for i in range(self.document.lineCount()):
            currBlock = self.document.findBlockByLineNumber(i)
            # print(currBlock.text())
            # print(currBlock.text())
            # for c in currBlock.text():
            # currBlock.text().charFormat().setForeground(QtGui.QBrush(QtGui.QColor(10,10,10)))
            # currBlock.blockFormat().setForeground(QtGui.QBrush(QtGui.QColor(10,10,10)))

            # print(currBlock.blockFormat().fontUnderline())
            #
        self.editor.update()
        # for each line in the textdocument
        # adjust its lightness in accordance to distance from cursor line

    def new(self):
        spawn = Main(self)
        spawn.show()

    def open(self):
        self.filename = QFileDialog.getOpenFileName(self, 'Open File', ".",
                                                    "(*.txt)")[0]

        if self.filename:
            with open(self.filename, "r") as file:
                self.document.setPlainText(file.read())

        self.setWindowTitle("QtWriter - " + os.path.basename(self.filename))
        self.updateFocus()

    def save(self):
        if not self.filename:
            # if event is not None:
            self.filename = QFileDialog.getSaveFileName(self, 'Save File')[0]

        if self.filename:
            if not self.filename.endswith(".txt"):
                self.filename += ".txt"

            with open(self.filename, "w") as file:
                file.write(self.editor.toPlainText())

    def saveAs(self):
        self.filename = QFileDialog.getSaveFileName(self, 'Save File')[0]
        self.save()

    def exit(self):
        print("just tried to exit!")

    def toggleDirVisibility(self):
        self.dockWidget.setVisible(not self.dockWidget.isVisible())

    def toggleChanged(self):
        self.changed = True

    def closeEvent(self, event):
        if self.changed:
            popup = QMessageBox(self)
            popup.setIcon(QMessageBox.Warning)
            popup.setText("The document has been modified")
            popup.setInformativeText("Do you want to save your changes?")
            popup.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel
                                     | QMessageBox.Discard)
            popup.setDefaultButton(QMessageBox.Save)
            answer = popup.exec_()
            if answer == QMessageBox.Save:
                self.save()
            elif answer == QMessageBox.Discard:
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()
Ejemplo n.º 12
0
class MainWindow(QMainWindow):
    """Pyspread main window"""

    gui_update = pyqtSignal(dict)

    def __init__(self, application):
        super().__init__()

        self._loading = True
        self.application = application
        self.settings = Settings(self)
        self.workflows = Workflows(self)
        self.undo_stack = QUndoStack(self)
        self.refresh_timer = QTimer()

        self._init_widgets()

        self.main_window_actions = MainWindowActions(self)

        self._init_window()
        self._init_toolbars()

        self.settings.restore()
        if self.settings.signature_key is None:
            self.settings.signature_key = genkey()

        self.show()
        self._update_action_toggles()

        # Update the GUI so that everything matches the model
        cell_attributes = self.grid.model.code_array.cell_attributes
        attributes = cell_attributes[self.grid.current]
        self.on_gui_update(attributes)

        self._loading = False
        self._previous_window_state = self.windowState()

    def _init_window(self):
        """Initialize main window components"""

        self.setWindowTitle(APP_NAME)
        self.setWindowIcon(Icon.pyspread)

        self.safe_mode_widget = QSvgWidget(str(IconPath.warning), self)
        msg = "%s is in safe mode.\nExpressions are not evaluated." % APP_NAME
        self.safe_mode_widget.setToolTip(msg)
        self.statusBar().addPermanentWidget(self.safe_mode_widget)
        self.safe_mode_widget.hide()

        self.setMenuBar(MenuBar(self))

    def resizeEvent(self, event):
        super(MainWindow, self).resizeEvent(event)
        if self._loading:
            return

    def closeEvent(self, event=None):
        """Overloaded close event, allows saving changes or canceling close"""
        self.workflows.file_quit()  # has @handle_changed_since_save decorator
        self.settings.save()
        if event:
            event.ignore()
        # Maybe a warn of closing
        sys.exit()

    def _init_widgets(self):
        """Initialize widgets"""

        self.widgets = Widgets(self)

        self.entry_line = Entryline(self)
        self.grid = Grid(self)

        self.macro_panel = MacroPanel(self, self.grid.model.code_array)

        self.main_splitter = QSplitter(Qt.Vertical, self)
        self.setCentralWidget(self.main_splitter)

        self.main_splitter.addWidget(self.entry_line)
        self.main_splitter.addWidget(self.grid)
        self.main_splitter.addWidget(self.grid.table_choice)
        self.main_splitter.setSizes([self.entry_line.minimumHeight(),
                                     9999, 20])

        self.macro_dock = QDockWidget("Macros", self)
        self.macro_dock.setObjectName("Macro Panel")
        self.macro_dock.setWidget(self.macro_panel)
        self.addDockWidget(Qt.RightDockWidgetArea, self.macro_dock)

        self.macro_dock.installEventFilter(self)

        self.gui_update.connect(self.on_gui_update)
        self.refresh_timer.timeout.connect(self.on_refresh_timer)

    def eventFilter(self, source, event):
        """Event filter for handling QDockWidget close events

        Updates the menu if the macro panel is closed.

        """

        if event.type() == QEvent.Close \
           and isinstance(source, QDockWidget) \
           and source.windowTitle() == "Macros":
            self.main_window_actions.toggle_macro_panel.setChecked(False)
        return super().eventFilter(source, event)

    def _init_toolbars(self):
        """Initialize the main window toolbars"""

        self.main_toolbar = MainToolBar(self)
        self.find_toolbar = FindToolbar(self)
        self.format_toolbar = FormatToolbar(self)
        self.macro_toolbar = MacroToolbar(self)
        self.widget_toolbar = WidgetToolbar(self)

        self.addToolBar(self.main_toolbar)
        self.addToolBar(self.find_toolbar)
        self.addToolBarBreak()
        self.addToolBar(self.format_toolbar)
        self.addToolBar(self.macro_toolbar)
        self.addToolBar(self.widget_toolbar)

    def _update_action_toggles(self):
        """Updates the toggle menu check states"""

        self.main_window_actions.toggle_main_toolbar.setChecked(
                self.main_toolbar.isVisible())

        self.main_window_actions.toggle_macro_toolbar.setChecked(
                self.macro_toolbar.isVisible())

        self.main_window_actions.toggle_widget_toolbar.setChecked(
                self.widget_toolbar.isVisible())

        self.main_window_actions.toggle_format_toolbar.setChecked(
                self.format_toolbar.isVisible())

        self.main_window_actions.toggle_find_toolbar.setChecked(
                self.find_toolbar.isVisible())

        self.main_window_actions.toggle_entry_line.setChecked(
                self.entry_line.isVisible())

        self.main_window_actions.toggle_macro_panel.setChecked(
                self.macro_dock.isVisible())

    @property
    def safe_mode(self):
        """Returns safe_mode state. In safe_mode cells are not evaluated."""

        return self.grid.model.code_array.safe_mode

    @safe_mode.setter
    def safe_mode(self, value):
        """Sets safe mode.

        This triggers the safe_mode icon in the statusbar.

        If safe_mode changes from True to False then caches are cleared and
        macros are executed.

        """

        if self.grid.model.code_array.safe_mode == bool(value):
            return

        self.grid.model.code_array.safe_mode = bool(value)

        if value:  # Safe mode entered
            self.safe_mode_widget.show()
        else:  # Safe_mode disabled
            self.safe_mode_widget.hide()
            # Clear result cache
            self.grid.model.code_array.result_cache.clear()
            # Execute macros
            self.grid.model.code_array.execute_macros()

    def on_nothing(self):
        """Dummy action that does nothing"""

        sender = self.sender()
        print("on_nothing > ", sender.text(), sender)

    def on_fullscreen(self):
        """Fullscreen toggle event handler"""

        if self.windowState() == Qt.WindowFullScreen:
            self.setWindowState(self._previous_window_state)
        else:
            self._previous_window_state = self.windowState()
            self.setWindowState(Qt.WindowFullScreen)

    def on_approve(self):
        """Approve event handler"""

        if ApproveWarningDialog(self).choice:
            self.safe_mode = False

    def on_preferences(self):
        """Preferences event handler (:class:`dialogs.PreferencesDialog`) """

        data = PreferencesDialog(self).data

        if data is not None:
            # Dialog has not been approved --> Store data to settings
            for key in data:
                if key == "signature_key" and not data[key]:
                    data[key] = genkey()
                self.settings.__setattr__(key, data[key])

    def on_undo(self):
        """Undo event handler"""

        self.undo_stack.undo()

    def on_redo(self):
        """Undo event handler"""

        self.undo_stack.redo()

    def on_toggle_refresh_timer(self, toggled):
        """Toggles periodic timer for frozen cells"""

        if toggled:
            self.refresh_timer.start(self.settings.refresh_timeout)
        else:
            self.refresh_timer.stop()

    def on_refresh_timer(self):
        """Event handler for self.refresh_timer.timeout

        Called for periodic updates of frozen cells.
        Does nothing if either the entry_line or a cell editor is active.

        """

        if not self.entry_line.hasFocus() \
           and self.grid.state() != self.grid.EditingState:
            self.grid.refresh_frozen_cells()

    def _toggle_widget(self, widget, action_name, toggled):
        """Toggles widget visibility and updates toggle actions"""

        if toggled:
            widget.show()
        else:
            widget.hide()

        self.main_window_actions[action_name].setChecked(widget.isVisible())

    def on_toggle_main_toolbar(self, toggled):
        """Main toolbar toggle event handler"""

        self._toggle_widget(self.main_toolbar, "toggle_main_toolbar", toggled)

    def on_toggle_macro_toolbar(self, toggled):
        """Macro toolbar toggle event handler"""

        self._toggle_widget(self.macro_toolbar, "toggle_macro_toolbar",
                            toggled)

    def on_toggle_widget_toolbar(self, toggled):
        """Widget toolbar toggle event handler"""

        self._toggle_widget(self.widget_toolbar, "toggle_widget_toolbar",
                            toggled)

    def on_toggle_format_toolbar(self, toggled):
        """Format toolbar toggle event handler"""

        self._toggle_widget(self.format_toolbar, "toggle_format_toolbar",
                            toggled)

    def on_toggle_find_toolbar(self, toggled):
        """Find toolbar toggle event handler"""

        self._toggle_widget(self.find_toolbar, "toggle_find_toolbar", toggled)

    def on_toggle_entry_line(self, toggled):
        """Entryline toggle event handler"""

        self._toggle_widget(self.entry_line, "toggle_entry_line", toggled)

    def on_toggle_macro_panel(self, toggled):
        """Macro panel toggle event handler"""

        self._toggle_widget(self.macro_dock, "toggle_macro_panel", toggled)

    def on_about(self):
        """Show about message box"""

        about_msg_template = "<p>".join((
            "<b>%s</b>" % APP_NAME,
            "A non-traditional Python spreadsheet application",
            "Version {version}",
            "Created by:<br>{devs}",
            "Documented by:<br>{doc_devs}",
            "Copyright:<br>Martin Manns",
            "License:<br>{license}",
            '<a href="https://pyspread.gitlab.io">pyspread.gitlab.io</a>',
            ))

        devs = "Martin Manns, Jason Sexauer<br>Vova Kolobok, mgunyho, " \
               "Pete Morgan"

        doc_devs = "Martin Manns, Bosko Markovic, Pete Morgan"

        about_msg = about_msg_template.format(
                    version=VERSION, license=LICENSE,
                    devs=devs, doc_devs=doc_devs)
        QMessageBox.about(self, "About %s" % APP_NAME, about_msg)

    def on_gui_update(self, attributes):
        """GUI update event handler.

        Emitted on cell change. Attributes contains current cell_attributes.
        """

        widgets = self.widgets

        is_bold = attributes["fontweight"] == QFont.Bold
        self.main_window_actions.bold.setChecked(is_bold)

        is_italic = attributes["fontstyle"] == QFont.StyleItalic
        self.main_window_actions.italics.setChecked(is_italic)

        underline_action = self.main_window_actions.underline
        underline_action.setChecked(attributes["underline"])

        strikethrough_action = self.main_window_actions.strikethrough
        strikethrough_action.setChecked(attributes["strikethrough"])

        renderer = attributes["renderer"]
        widgets.renderer_button.set_current_action(renderer)
        widgets.renderer_button.set_menu_checked(renderer)

        freeze_action = self.main_window_actions.freeze_cell
        freeze_action.setChecked(attributes["frozen"])

        lock_action = self.main_window_actions.lock_cell
        lock_action.setChecked(attributes["locked"])
        self.entry_line.setReadOnly(attributes["locked"])

        rotation = "rotate_{angle}".format(angle=int(attributes["angle"]))
        widgets.rotate_button.set_current_action(rotation)
        widgets.rotate_button.set_menu_checked(rotation)
        widgets.justify_button.set_current_action(attributes["justification"])
        widgets.justify_button.set_menu_checked(attributes["justification"])
        widgets.align_button.set_current_action(attributes["vertical_align"])
        widgets.align_button.set_menu_checked(attributes["vertical_align"])

        border_action = self.main_window_actions.border_group.checkedAction()
        if border_action is not None:
            icon = border_action.icon()
            self.menuBar().border_submenu.setIcon(icon)
            self.format_toolbar.border_menu_button.setIcon(icon)

        border_width_action = \
            self.main_window_actions.border_width_group.checkedAction()
        if border_width_action is not None:
            icon = border_width_action.icon()
            self.menuBar().line_width_submenu.setIcon(icon)
            self.format_toolbar.line_width_button.setIcon(icon)

        if attributes["textcolor"] is None:
            text_color = self.grid.palette().color(QPalette.Text)
        else:
            text_color = QColor(*attributes["textcolor"])
        widgets.text_color_button.color = text_color

        if attributes["bgcolor"] is None:
            bgcolor = self.grid.palette().color(QPalette.Base)
        else:
            bgcolor = QColor(*attributes["bgcolor"])
        widgets.background_color_button.color = bgcolor

        if attributes["textfont"] is None:
            widgets.font_combo.font = QFont().family()
        else:
            widgets.font_combo.font = attributes["textfont"]
        widgets.font_size_combo.size = attributes["pointsize"]

        merge_cells_action = self.main_window_actions.merge_cells
        merge_cells_action.setChecked(attributes["merge_area"] is not None)
Ejemplo n.º 13
0
class QGameCounter(QMainWindow):
    def __init__(self):
        super().__init__()

        self.imageGridViewer = QImageGridViewer()
        self.imagePainter = QImagePainter()
        self.tracker = QGameCountTracker()

        self.setCentralWidget(self.imagePainter)

        self.imageGridDock = QDockWidget('Grid Viewer', self)
        self.imageGridDock.setAllowedAreas(Qt.RightDockWidgetArea)
        self.imageGridDock.setWidget(self.imageGridViewer)
        self.imageGridDock.setTitleBarWidget(QWidget())
        self.addDockWidget(Qt.RightDockWidgetArea, self.imageGridDock)

        self.trackerAddDock = QDockWidget('Add New Animals', self)
        self.trackerAddDock.setAllowedAreas(Qt.RightDockWidgetArea)
        self.trackerAddDock.setWidget(self.tracker.addAnimalForm)
        self.addDockWidget(Qt.RightDockWidgetArea, self.trackerAddDock)

        self.trackerDock = QDockWidget('Animal Tracker', self)
        self.trackerDock.setAllowedAreas(
            Qt.RightDockWidgetArea)  # Qt.RightDockWidgetArea)
        self.trackerDock.setWidget(self.tracker)
        self.addDockWidget(Qt.RightDockWidgetArea, self.trackerDock)

        self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea)

        # should be overwritten by the fbs run function
        self._appContext = None
        self._version = None

        self.stylesheetPath = 'QMainWindowStyle.qss'
        self.readStyleSheet()

        # self.threadpool = QThreadPool()

        self.createConnections()
        self.createActions()
        self.createMenus()
        self.createToolbars()
        self.populateMenus()
        self.populateToolbars()

        self.setWindowTitle('Animal Counter')
        self.setWindowIconFbs()

        self.fileDialogDirectory = Path().home()

        QCoreApplication.setOrganizationName('WAO')
        QCoreApplication.setApplicationName('Game Counter')
        self.readSettings()

    def closeEvent(self, event):
        self.writeSettings()
        event.accept()

    @property
    def version(self):
        return self._version

    @version.setter
    def version(self, value):
        self._version = value
        self.aboutAct.setText(f'&About v{self.version}')

    @property
    def appContext(self):
        return self._appContext

    @appContext.setter
    def appContext(self, context):
        self._appContext = context
        self.imageGridViewer.appContext = context
        self.imagePainter.appContext = context
        self.tracker.appContext = context

        self.readStyleSheet()

        self.setWindowIconFbs()
        self.createActions()

        self.clearToolbars()
        self.clearMenus()

        self.populateToolbars()
        self.populateMenus()

    def readStyleSheet(self):
        if self.appContext is not None:
            fp = self.appContext.get_resource(self.stylesheetPath)
            f = QFile(fp)
            f.open(QFile.ReadOnly | QFile.Text)
            stream = QTextStream(f)
            self.setStyleSheet(stream.readAll())
            # f.close()

    def setWindowIconFbs(self):
        if self.appContext is None:
            fp = '../icons/mainWindowIcon.png'
        else:
            fp = self.appContext.get_resource('mainWindowIcon.png')
        self.setWindowIcon(QIcon(fp))

    def writeSettings(self):
        settings = QSettings()

        settings.beginGroup('MainWindow')
        settings.setValue('size', self.size())
        settings.setValue('pos', self.pos())
        settings.setValue('isMaximized', self.isMaximized())
        settings.setValue('fileDialogDirectory', self.fileDialogDirectory)
        settings.endGroup()

        settings.beginGroup('Panels')
        settings.setValue('tracker', self.trackerDock.isVisible())
        settings.setValue('imageGrid', self.imageGridDock.isVisible())
        settings.setValue('trackerAdd', self.trackerAddDock.isVisible())
        settings.endGroup()

        settings.beginGroup('ImageGrid')
        settings.setValue('rows', self.imageGridViewer.rows)
        settings.setValue('cols', self.imageGridViewer.cols)
        settings.endGroup()

        settings.beginGroup('ImagePainter')
        settings.setValue('penWidth', self.imagePainter.penWidth)
        settings.setValue('penColor', self.imagePainter.penColor)
        settings.endGroup()

    def readSettings(self):
        settings = QSettings()

        settings.beginGroup('MainWindow')

        if settings.value('isMaximized') == True:
            self.setWindowState(Qt.WindowMaximized)
        else:
            self.resize(settings.value('size', QSize(800, 500)))

            # if there is no position already saved, it will center itself
            if settings.contains('pos'):
                self.move(settings.value('pos'))
            else:
                screenGeometry = QGuiApplication.primaryScreen().geometry()
                screenHeight = screenGeometry.height()
                screenWidth = screenGeometry.width()
                self.move(
                    QPoint((screenWidth - self.size().width()) / 2,
                           (screenHeight - self.size().height()) / 2))

        self.fileDialogDirectory = Path(
            settings.value('fileDialogDirectory',
                           Path().home()))

        settings.endGroup()

        settings.beginGroup('Panels')
        self.trackerDock.setVisible(
            settings.value('tracker', 'true') == 'true')
        self.imageGridDock.setVisible(
            settings.value('imageGrid', 'true') == 'true')
        self.trackerAddDock.setVisible(
            settings.value('trackerAdd', 'true') == 'true')
        settings.endGroup()

        settings.beginGroup('ImageGrid')
        self.imageGridViewer.rows = settings.value('rows', 2)
        self.imageGridViewer.cols = settings.value('cols', 2)
        settings.endGroup()

        settings.beginGroup('ImagePainter')
        self.imagePainter.penWidth = settings.value('penWidth', 30)
        if settings.contains('penColor'):
            self.imagePainter.setPenColor(settings.value('penColor'))
        else:
            self.imagePainter.setDefaultPenColor()
        settings.endGroup()

    def resetSettings(self):
        settings = QSettings()
        settings.clear()
        self.readSettings()

    @pyqtSlot(QPixmap)
    def changeMainImage(self, newPixmap):
        self.imagePainter.setMainPixmap(newPixmap)
        self.imagePainter.bestFitImage()

    @pyqtSlot(QPixmap)
    def updateWindowTitle(self):
        fp = Path(self.imageGridViewer.imageGrids.getFocusedGrid().baseImgPath)
        self.setWindowTitle(f'Animal Counter - {fp.name}')

    @pyqtSlot(QPixmap)
    def updateTrackerFile(self):
        fp = Path(self.imageGridViewer.imageGrids.getFocusedGrid().baseImgPath)
        self.tracker.currentImageFile = fp.name
        self.tracker.render()

    def updateImageGridVisibility(self):
        if self.imageGridsToggle.isChecked():
            self.imageGridViewer.show()
        else:
            self.imageGridViewer.hide()

    def save(self):
        if self.imageGridViewer.count() != 0:
            self.imagePainter.flattenImage()
            self.tracker.dump()

    def open(self):
        QFileDialog().setDirectory(str(self.fileDialogDirectory))
        options = QFileDialog.Options()
        fileNames, _ = QFileDialog.getOpenFileNames(
            self,
            'Open transect files',
            '',
            'Images/JSON (*.png *.jpeg *.jpg *.bmp *.gif *.json)',
            options=options)

        filePaths: Path = [Path(name) for name in fileNames]
        if not filePaths:
            return

        self.fileDialogDirectory = filePaths[0].parent

        # remove JSON files from the paths
        JSONPaths: Path = []
        imagePaths: Path = []

        for fp in filePaths:
            if fp.suffix == '.json':
                JSONPaths.append(fp)
            else:
                imagePaths.append(fp)

        if imagePaths:

            # worker = Worker(self.imageGridViewer.openFiles, imagePaths)
            # worker.signals.finished.connect(self.imageGridViewer.focusFirstGrid)
            # worker.signals.finished.connect(self.imagePainter.centerImage)
            # worker.signals.progress.connect(self.updateImageGridProgressBar)

            # self.threadpool.start(worker)

            self.imageGridViewer.openFiles(imagePaths)
            self.tracker.JSONDumpFile = imagePaths[0].parent / Path(
                'counts.json')
            self.imageGridViewer.focusFirstGrid()
            self.imagePainter.centerImage()

        if JSONPaths:
            self.tracker.load(JSONPaths[0])

    def openFile(self, fileName):
        self.imageGridViewer.openFile(fileName)

    @pyqtSlot(int)
    def updateImageGridProgressBar(self, value):
        print(f'progress on images is: {value}')

    def about(self):
        QMessageBox.about(
            self, f'About Game Counter v{self.version}',
            '<p>The <b>Game Counter</b> provides convenient tools for '
            'counting game animals in arial photographs.</p>'
            '<p>Ideally, one can open a entire folder of photos '
            'from a single transect, and use the built-in tools to '
            '<b>circle</b> and <b>count</b> the game animals present.<p>'
            '<p>Summarize your counts with <b>tracker | summarize.</b>')

    def keyPressEvent(self, event: QKeyEvent):
        key = event.key()
        if key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right,
                   Qt.Key_Space):
            self.imagePainter.keyPressEvent(event)
        else:
            super().keyPressEvent(event)

    def createConnections(self):
        self.imageGridViewer.imageGrids.focusChanged.connect(
            self.changeMainImage)
        self.imageGridViewer.imageGrids.focusChanged.connect(
            self.imagePainter.clearDrawnItems)
        self.imageGridViewer.imageGrids.focusChanged.connect(
            self.updateWindowTitle)
        self.imageGridViewer.imageGrids.focusChanged.connect(
            self.updateTrackerFile)
        self.imagePainter.imageFlattened.connect(
            self.imageGridViewer.changeFocusedImageData)

    def createActions(self):

        if self.appContext is None:
            addAnimalFp = './resources/addAnimalIcon.png'
            trackerViewIconFp = 'showTrackerIcon.png'
            gridIconFp = 'showGridIcon.png'
            openIconFp = 'openIcon.png'
            saveIconFp = 'saveIcon.png'
        else:
            addAnimalFp = self.appContext.get_resource(
                'showAnimalAdderIcon.png')
            trackerViewIconFp = self.appContext.get_resource(
                'showTrackerIcon.png')
            gridIconFp = self.appContext.get_resource('showGridIcon.png')
            openIconFp = self.appContext.get_resource('openIcon.png')
            saveIconFp = self.appContext.get_resource('saveIcon.png')

        self.saveAct = QAction(QIcon(saveIconFp),
                               'Save',
                               self,
                               shortcut=QKeySequence.Save,
                               triggered=self.save)
        self.openAct = QAction(QIcon(openIconFp),
                               '&Open...',
                               self,
                               shortcut=QKeySequence.Open,
                               triggered=self.open)
        self.exitAct = QAction('E&xit',
                               self,
                               shortcut='Ctrl+Q',
                               triggered=self.close)
        self.aboutAct = QAction(f'&About', self, triggered=self.about)
        self.aboutQtAct = QAction('About &Qt', self, triggered=qApp.aboutQt)
        self.resetSettingsAct = QAction('Default Settings',
                                        self,
                                        triggered=self.resetSettings)

        self.imageGridsToggle = self.imageGridDock.toggleViewAction()
        self.imageGridsToggle.setShortcut(Qt.CTRL + Qt.Key_G)
        self.imageGridsToggle.setIcon(QIcon(gridIconFp))

        self.addAnimalToggle = self.trackerAddDock.toggleViewAction()
        self.addAnimalToggle.setIcon(QIcon(addAnimalFp))

        self.trackerViewToggle = self.trackerDock.toggleViewAction()
        self.trackerViewToggle.setIcon(QIcon(trackerViewIconFp))

    def clearMenus(self):
        self.fileMenu.clear()
        self.viewMenu.clear()
        self.helpMenu.clear()

    def clearToolbars(self):
        self.viewToolBar.clear()
        self.fileToolBar.clear()

    def createMenus(self):
        self.fileMenu = QMenu('&File', self)
        self.viewMenu = QMenu('&View', self)
        self.helpMenu = QMenu('&Help', self)

    def populateMenus(self):

        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.resetSettingsAct)
        self.fileMenu.addAction(self.exitAct)

        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.imageGridsToggle)
        self.viewMenu.addAction(self.addAnimalToggle)
        self.viewMenu.addAction(self.trackerViewToggle)

        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

        self.arangeMenus()

    def arangeMenus(self):

        self.menuBar().addMenu(self.fileMenu)
        self.menuBar().addMenu(self.viewMenu)
        self.menuBar().addMenu(self.imageGridViewer.menu)
        self.menuBar().addMenu(self.tracker.menu)
        self.menuBar().addMenu(self.helpMenu)

    def createToolbars(self):
        self.viewToolBar = QToolBar()
        self.fileToolBar = QToolBar()

    def populateToolbars(self):

        self.viewToolBar.addAction(self.imageGridsToggle)
        self.viewToolBar.addAction(self.addAnimalToggle)
        self.viewToolBar.addAction(self.trackerViewToggle)

        self.fileToolBar.addAction(self.saveAct)
        self.fileToolBar.addAction(self.openAct)

        self.arangeToolbars()

    def arangeToolbars(self):

        self.addToolBar(Qt.LeftToolBarArea, self.fileToolBar)
        self.addToolBar(Qt.LeftToolBarArea, self.imagePainter.toolbar)
        self.addToolBar(Qt.LeftToolBarArea, self.imageGridViewer.toolbar)
        self.addToolBar(Qt.LeftToolBarArea, self.tracker.toolbar)
        self.addToolBar(Qt.LeftToolBarArea, self.viewToolBar)
Ejemplo n.º 14
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        maxWidth = QDesktopWidget().availableGeometry().width()
        maxHeight = QDesktopWidget().availableGeometry().height()
        workSpace = QWidget(self)

        ####    MainWindow Configuration
        self.setWindowTitle("Application")
        self.setWindowIcon(QIcon("icon.png"))

        self.tray_icon = QSystemTrayIcon(QIcon("icon.png"))
        self.tray_icon.show()

        ####    Tray MENU   #################
        trayIconMenu = QMenu(self)
        trayIconMenu.setTitle("Main menu")
        trayIconMenu.addAction("Show Main Window", self.showMainWindow)
        trayIconMenu.addAction("Switch chat", self.toggleDockWidget)
        trayIconMenu.addSeparator()
        trayIconMenu.addAction("Quit", self.close)
        self.tray_icon.setContextMenu(trayIconMenu)

        ######################################

        ####     MENUBAR description:    #################
        mainmenu = self.menuBar()
        file = mainmenu.addMenu("File")
        file.addAction("isFloating", self.floatingInfo)
        file.addAction("Toggle Chat", self.toggleDockWidget)
        file.addAction("Close", self.close)
        ######################

        self.dockWidget = QDockWidget("Messenger", self)
        self.dockWidget.setFloating(True)
        self.dockWidget.setVisible(True)
        self.dockWidget.setMinimumSize(300, 460)
        self.dockWidget.setMaximumSize(maxWidth / 2, maxHeight)
        self.dockWidget.setBaseSize(maxWidth / 2, maxHeight)
        self.dockWidget.resize(maxWidth / 4, maxHeight / 1.5)
        self.dockWidget.setFeatures(QDockWidget.NoDockWidgetFeatures)
        self.dockWidget.setVisible(False)

        ####    TODO:" set dockWidget widgets"      ################

        messengerWidget = QWidget()

        masterVertical = QVBoxLayout()
        messengerWidget.setLayout(masterVertical)

        slaveHorizont = QHBoxLayout()
        subSlaveVertical = QVBoxLayout()

        msgSpace = QListView()
        textEdit = QTextEdit()
        sendBtn = QPushButton("Send")

        masterVertical.addWidget(msgSpace)
        masterVertical.addLayout(slaveHorizont)

        slaveHorizont.addWidget(textEdit)
        slaveHorizont.addLayout(subSlaveVertical)

        subSlaveVertical.addWidget(sendBtn)

        # self.dockWidget.setLayout(QVBoxLayout())
        # print(self.dockWidget.setLayout(QVBoxLayout()))

        ############################################################

        # self.statusBar()

        toggleDockWidgetBtn = QPushButton("Show geomerty", workSpace)
        toggleDockWidgetBtn.move(10, 40)
        toggleDockWidgetBtn.clicked.connect(self.showGeometry)

        ####     TEST BUTONS     ####
        hideBtn = QPushButton("hide main", workSpace)
        hideBtn.move(10, 70)
        hideBtn.clicked.connect(self.hideMainWindow)
        #######################################################

        self.setGeometry(300, 300, 300, 500)
        self.dockWidget.resize(333, 1000)
        self.setCentralWidget(workSpace)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidget)

    def toggleDockWidget(self):
        flag = False if self.dockWidget.isVisible() else True
        text = "Messenger status was changed to {}".format(
            "visible" if flag else "hide")
        self.dockWidget.setVisible(flag)

        # self.dockWidget.setGeometry(650, 300, 200, 500)

        ####     SET DOCKWINDOW GEAMETRY AND POSITION
        qtRect = self.dockWidget.frameGeometry()
        rightPoint = QDesktopWidget().availableGeometry().bottomRight()

        ####    FOR LINUX POSITION    ###############################
        # qtRect.moveBottomRight(rightPoint)
        # self.dockWidget.move(qtRect.bottomRight())
        ####    FOR WINDOWS POSITION    #############################
        qtRect.moveBottomRight(rightPoint)
        self.dockWidget.move(qtRect.topLeft())
        ##############################################################

        self.statusBar().showMessage(text, 500)

    ####    test functionality
    def showGeometry(self):
        availGeometry = QDesktopWidget().availableGeometry()
        self.statusBar().showMessage("Available geometry.")
        print("Available geometry: ", availGeometry)
        print("Right point: ",
              QDesktopWidget().availableGeometry().bottomRight())
        print("DockWidget frame bottomRight: ",
              self.dockWidget.frameGeometry().bottomRight())
        print("DockWidget frame topLeft: ",
              self.dockWidget.frameGeometry().topLeft())
        print("\n" + "|---|" * 15 + "\n")

    def hideMainWindow(self):
        self.hide()

    def showMainWindow(self):
        self.show()

    def floatingInfo(self):
        flag = False if self.dockWidget.isFloating() else True
        print("dockWidget status is: ", flag)
        self.dockWidget.setFloating(flag)
Ejemplo n.º 15
0
class MediaPlayer(QMainWindow, Form):
    def __init__(self):
        Form.__init__(self)
        QMainWindow.__init__(self)
        self.setupUi(self)
        # Video Part
        self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface)
        self.player.setVideoOutput(self.videowidget)
        self.setWindowTitle(" Media Player")
        self.setAcceptDrops(True)

        # Define some Events
        self.videowidget.mouseDoubleClickEvent = self.Doubleclick_mouse
        self.wheelEvent = self.Scroll_mouse
        self.mouseReleaseEvent = self.MainWindow_Event
        self.videowidget.mouseMoveEvent = self.MousePos
        self.resizeEvent = self.main_size_Change

        self.lineEdit_Bookmark.setVisible(False)
        self.lineEdit_Bookmark.returnPressed.connect(self.save_Bookmarks)

        # threads part
        self.tag_thread = None
        self.search_Thread = None
        self.SearchAnimation = None
        self.BookMarkAnimation = None

        # Create Tags
        self.allTag = {}
        self.tag_Path = None

        # create Volume Slide
        self.Slider_Volume = Slider(self)
        self.Slider_Volume.setOrientation(Qt.Horizontal)
        self.horizontalLayout_3.addWidget(self.Slider_Volume, 0)
        self.Slider_Volume.setMaximumWidth(100)
        self.Slider_Volume.setMaximumHeight(30)

        # create Play Slider
        self.Slider_Play = Slider(self)
        self.Slider_Play.setOrientation(Qt.Horizontal)
        self.Slider_Play.resize(644, 22)
        self.horizontalLayout_4.addWidget(self.label_Time)
        self.horizontalLayout_4.addWidget(self.Slider_Play)
        self.horizontalLayout_4.addWidget(self.label_Duration)

        # To Apply initial Theme
        self.Setting = Setting.SettingWindow(self)
        Theme_module.Theme_apply(self.Setting)

        #Create Help
        self.helpW = Help.helpWindow(self)

        # PushButttons
        self.pushButton_Start.setEnabled(False)
        self.pushButton_Start.clicked.connect(self.start)

        self.pushButton_volume.setEnabled(False)
        self.pushButton_volume.clicked.connect(self.volumeOnOff)

        self.pushButton_stop.setEnabled(False)
        self.pushButton_stop.clicked.connect(self.stop)
        self.pushButton_stop.setToolTip("Stop (Ctrl+Z)")

        self.pushButton_next.setEnabled(False)
        self.pushButton_next.clicked.connect(self.next)
        self.pushButton_next.setToolTip("Next (PgUp)")

        self.pushButton_previous.setEnabled(False)
        self.pushButton_previous.clicked.connect(self.previous)
        self.pushButton_previous.setToolTip("Previous (PgDn)")

        self.pushButton_open.clicked.connect(self.Load_video)
        self.pushButton_open.setToolTip("Open (Ctrl+O)")

        self.pushButton_Search.clicked.connect(self.sch_icon_Event)
        self.pushButton_Search.setToolTip("Search (Ctrl+S)")

        self.pushButton_BookMark.setVisible(False)
        self.pushButton_BookMark.clicked.connect(self.add_BookMarks)
        self.pushButton_BookMark.setToolTip("Add BookMark (Ctrl+B)")

        self.pushButton_Setting.clicked.connect(self.Settingshow)
        self.pushButton_Setting.setToolTip("Setting")

        self.pushButton_Playlist.clicked.connect(self.Play_list)
        self.pushButton_Playlist.setToolTip("Play list")
        self.PlaylistW = Playlist.PlaylistWindow(self)

        # Create Tags of file Dockwidget
        self.pushButton_Tag_of_file.clicked.connect(self.Show_Tags_of_file)
        self.pushButton_Tag_of_file.setToolTip("Tags of file")
        self.DockWidget_Tags_of_file = QDockWidget("Tags of file", self)
        self.DockWidget_Tags_of_file.setVisible(False)
        self.DockWidget_Tags_of_file.setMinimumWidth(150)
        self.ComboBox_Tags_of_file = QComboBox(self)
        self.ListWidget_Tags_of_file = QListWidget()
        widget = QWidget()  # Create Widget for Tags of file DockWidget
        layout = QVBoxLayout()  # Create Layout for Tags of file DockWidget
        # Add Listwiget and ComboBox to layout
        layout.addWidget(self.ComboBox_Tags_of_file)
        layout.addWidget(self.ListWidget_Tags_of_file)
        widget.setLayout(layout)  # Set layout on the widget
        # set Widget on Tags of file DockWidget
        self.DockWidget_Tags_of_file.setWidget(widget)
        self.addDockWidget(Qt.RightDockWidgetArea,
                           self.DockWidget_Tags_of_file)
        self.ComboBox_Tags_of_file.activated.connect(self.ListWidget_Tag)
        self.ListWidget_Tags_of_file.itemActivated.connect(self.GoToTagtime)

        # Slider Play
        self.Slider_Play.setRange(0, 0)
        self.player.positionChanged.connect(self.Position_changed)
        self.player.durationChanged.connect(self.Duration_changed)
        self.Slider_Play.setUP_Slider.connect(self.Set_Position)
        self.Slider_Play.moveMent_Position.connect(self.slider_play_pos)

        # Label Time
        self.Slider_play_label = QLabel("Time", parent=self)
        self.Slider_play_label.resize(50, 20)
        self.Slider_play_label.setAlignment(Qt.AlignCenter)
        self.Slider_play_label.setVisible(False)

        # Slider Volume
        self.Slider_Volume.setRange(0, 0)
        self.Slider_Volume.setUP_Slider.connect(self.Set_volume)
        self.Slider_Volume.moveMent_Position.connect(self.slider_volume_pos)

        # Label Volume
        self.Slider_Volume_label = QLabel("Volume", parent=self)
        self.Slider_Volume_label.resize(30, 20)
        self.Slider_Volume_label.setAlignment(Qt.AlignCenter)
        self.Slider_Volume_label.setVisible(False)

        # Create listWidget for search part
        self.sch_listWidget = QListWidget(self)
        self.sch_listWidget.setVisible(False)
        self.search_lineEdit.setVisible(False)
        # Search signals
        self.sch_listWidget.itemDoubleClicked.connect(
            self.item_searchlist_Event)
        self.search_lineEdit.textChanged.connect(self.search_Tag)

        # Define Action for Tool Bar
        self.actionOpen.triggered.connect(self.Load_video)
        self.actionChange_Password.triggered.connect(self.menuBarAccout)
        self.actionDelete_Account.triggered.connect(self.menuBarAccout)

        self.actionHelp.triggered.connect(self.Help)
        self.actionLogOut.triggered.connect(self.Logout)
        self.actionExit.triggered.connect(lambda: self.close())

        self.actionFullScreen.triggered.connect(self.fullscreen)
        self.actionSpeed.triggered.connect(self.menuBarSpeed)
        self.actionThme.triggered.connect(self.menuBarTheme)

        self.actionCreate_Tag.triggered.connect(self.menuBarCreateTag)
        self.actionOpen_Tags.triggered.connect(self.openTags)
        self.actionEdit_Tag.triggered.connect(self.menuBarEditTag)
        self.actionClose_Tag.triggered.connect(self.menuBarCloseTag)

        # Shortcut
        QShortcut(QKeySequence('Ctrl+B'),
                  self).activated.connect(self.add_BookMarks)
        QShortcut(QKeySequence('Ctrl+Z'), self).activated.connect(self.stop)
        QShortcut(QKeySequence('Ctrl+M'),
                  self).activated.connect(self.volumeOnOff)
        QShortcut(QKeySequence('Ctrl+S'),
                  self).activated.connect(self.sch_icon_Event)
        QShortcut(QKeySequence('Ctrl+O'),
                  self).activated.connect(self.Load_video)

        # For FullScreen part
        self.firstTime_fullscreen = True

        # close event to stop running thread
        self.closeEvent = self.Close

    # KeyPress Event
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_PageDown and self.pushButton_next.isEnabled():
            self.next()
        if event.key() == Qt.Key_PageUp and self.pushButton_previous.isEnabled(
        ):
            self.previous()
        if event.key() == Qt.Key_F5:
            self.fullscreen()
        if event.key() == Qt.Key_6:
            self.Move_Slider_play(self.width() * 4500 / self.player.duration())
        if event.key() == Qt.Key_4:
            self.Move_Slider_play(
                (-1 * self.width() * 4500 / self.player.duration()))
        if event.key() == Qt.Key_8:
            self.Move_Slider_volume(+1)
        if event.key() == Qt.Key_2:
            self.Move_Slider_volume(-1)

    def menuBarTheme(self):
        # Open Theme Tab of Setting Window
        self.Setting.Account_changing = False
        self.Settingshow()
        self.Setting.Tab.setCurrentIndex(0)

    def Help(self):
        self.helpW.show()

    def menuBarSpeed(self):
        # Open Speed Tab of Setting Window
        self.Setting.Account_changing = False
        self.Settingshow()
        self.Setting.Tab.setCurrentIndex(1)

    def menuBarEditTag(self):
        # Open Tag Tab of Setting Window
        self.Setting.Account_changing = False
        self.Settingshow()
        self.Setting.Tab.setCurrentIndex(2)

    def menuBarAccout(self):
        # Open Account Tab of Setting Window
        self.Setting.Account_changing = False
        self.Settingshow()
        self.Setting.Tab.setCurrentIndex(3)

    def menuBarCreateTag(self):
        # create new Tag file
        if self.tag_Path:
            self.confirmCloseTag = confrimWin(
                self,
                Title="Create Tag",
                Text="Are you sure to close current tag and create new one")
            self.actionCreate_Tag.setEnabled(False)
            self.confirmCloseTag.show()
        else:
            dialog = QFileDialog(self, 'File tag', directory=os.getcwd())
            _path = dialog.getSaveFileName(filter="*.csv")[0]
            try:
                if _path:
                    self.tag_Path = _path
                    open(self.tag_Path, "w")
            except:
                pass

    def menuBarCloseTag(self):
        # close current tags
        if self.tag_Path:
            self.confirmCloseTag = confrimWin(
                self,
                Title="Close Tag",
                Text="Are you sure to close current tag")
            self.confirmCloseTag.show()
            self.actionClose_Tag.setEnabled(False)
        else:
            self.confirmCloseTag = confrimWin(self,
                                              Title="Warning",
                                              Text="There is no tag to close")
            self.confirmCloseTag.show()
            self.actionClose_Tag.setEnabled(False)

    def fullscreen(self):
        self.DockWidget_Tags_of_file.setVisible(False)
        self.Slider_play_label.setVisible(False)
        self.Slider_Volume_label.setVisible(False)

        Full_screen.fullscreen(self)

    # Define some Events
    def MousePos(self, position):
        if self.isFullScreen():
            Full_screen.MousePosition(self, position)
        self.Slider_play_label.setVisible(False)
        self.Slider_Volume_label.setVisible(False)

    def Doubleclick_mouse(self, position):
        if self.pushButton_Start.isEnabled():
            self.start()

    def Scroll_mouse(self, event):
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            self.Set_volume(
                int(self.player.volume() + event.angleDelta().y() / 120))

    def Move_Slider_play(self, move):
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            Now = self.player.position() * self.Slider_Play.width(
            ) / self.player.duration()
            self.Set_Position(Now + move)

    def Move_Slider_volume(self, move):
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            self.Set_volume(self.player.volume() + move)

    def Settingshow(self):
        # To show Setting window
        self.Setting.Tab.setCurrentIndex(0)
        self.Setting.lineEdit_CurrentPass.clear()
        self.Setting.lineEdit_NewPass.clear()
        self.Setting.lineEdit_ReNewpass.clear()
        self.Setting.label_finish.setVisible(False)
        self.Setting.label_NotMatch.setVisible(False)
        self.Setting.label_PassLong.setVisible(False)
        self.Setting.label_OldPass.setVisible(False)
        self.Setting.label_Wait.setVisible(False)
        self.Setting.label_Error.setVisible(False)
        self.Setting.Account_changing = False
        self.Setting.show()

    def start(self):
        # Start and Pause the player
        if self.player.state() == QMediaPlayer.PlayingState:  # Stop
            self.player.pause()
            self.pushButton_Start.setEnabled(True)
            self.pushButton_Start.setIcon(QIcon('./Icons/play.png'))
            self.pushButton_Start.setToolTip("Play")

        else:  # Start
            self.player.play()
            self.pushButton_Start.setEnabled(True)
            self.pushButton_Start.setIcon(QIcon('./Icons/pause.png'))
            self.pushButton_Start.setToolTip("Pause")
        self.pushButton_Start.setFocus(True)

    def stop(self):
        # Stop player
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            self.player.stop()
            self.pushButton_next.setEnabled(False)
            self.pushButton_previous.setEnabled(False)
            self.pushButton_volume.setEnabled(False)
            self.pushButton_Start.setEnabled(False)
            self.pushButton_stop.setEnabled(False)
            self.pushButton_BookMark.setVisible(False)
            self.label_Duration.setText("00:00:00")
            self.label_Time.setText("00:00:00")
            self.Slider_Volume.setEnabled(False)

    def next(self):
        # Play next file from playlist
        # If the playing file is in the playlist
        if self.windowTitle()[16:] in self.PlaylistW.Files.keys():
            self.PlaylistW.listWidget_Playlist.setCurrentRow(
                list(self.PlaylistW.Files.keys()).index(self.windowTitle()
                                                        [16:]) + 1)
        else:  # If the playing file is not in the playlist
            self.PlaylistW.listWidget_Playlist.setCurrentRow(
                self.PlaylistW.listWidget_Playlist.currentRow() + 1)

        self.PlaylistW.spliter = len(
            str(self.PlaylistW.listWidget_Playlist.currentRow() + 1)) + 3
        self.player.setMedia(
            QMediaContent(
                QUrl.fromLocalFile(self.PlaylistW.Files[
                    self.PlaylistW.listWidget_Playlist.currentItem().text()
                    [self.PlaylistW.spliter:]])))
        # Change title
        self.setWindowTitle(
            f" Media Player - {self.PlaylistW.listWidget_Playlist.currentItem().text()[self.PlaylistW.spliter:]}"
        )
        currentText = self.PlaylistW.listWidget_Playlist.currentItem().text(
        )[self.PlaylistW.spliter:]
        index = self.ComboBox_Tags_of_file.findText(currentText)
        self.ComboBox_Tags_of_file.setCurrentIndex(index)
        self.set_TagonListwidget(".".join(currentText.split(".")[:-1]))
        self.start()
        # To handle Next button
        if self.PlaylistW.listWidget_Playlist.currentRow(
        ) == self.PlaylistW.listWidget_Playlist.count() - 1:
            self.pushButton_next.setEnabled(False)

        self.pushButton_previous.setEnabled(True)

    def previous(self):
        # Play previous file from playlist
        # If the playing file is in the playlist
        if self.windowTitle()[16:] in self.PlaylistW.Files.keys():
            self.PlaylistW.listWidget_Playlist.setCurrentRow(
                list(self.PlaylistW.Files.keys()).index(self.windowTitle()
                                                        [16:]) - 1)
        else:  # If the playing file is not in the playlist
            self.PlaylistW.listWidget_Playlist.setCurrentRow(
                self.PlaylistW.listWidget_Playlist.currentRow())

        self.PlaylistW.spliter = len(
            str(self.PlaylistW.listWidget_Playlist.currentRow() + 1)) + 3

        self.player.setMedia(
            QMediaContent(
                QUrl.fromLocalFile(self.PlaylistW.Files[
                    self.PlaylistW.listWidget_Playlist.currentItem().text()
                    [self.PlaylistW.spliter:]])))
        self.setWindowTitle(
            f" Media Player - {self.PlaylistW.listWidget_Playlist.currentItem().text()[self.PlaylistW.spliter:]}"
        )
        currentText = self.PlaylistW.listWidget_Playlist.currentItem().text(
        )[self.PlaylistW.spliter:]
        index = self.ComboBox_Tags_of_file.findText(currentText)
        self.ComboBox_Tags_of_file.setCurrentIndex(index)
        self.set_TagonListwidget(".".join(currentText.split(".")[:-1]))
        self.start()

        # To handle previous button
        if not self.PlaylistW.listWidget_Playlist.currentRow():
            self.pushButton_previous.setEnabled(False)
        self.pushButton_next.setEnabled(True)

    def Load_video(self, filepath=None):
        # Load Files of directory
        # just mp4 ,mkv , mp3
        if not filepath:
            try:
                # To read initial Directory from csvfile
                with open("./PlayListPart/Filepath.csv") as file:
                    initial_FilePath = file.read()
                    file_path, _ = QFileDialog.getOpenFileName(
                        self,
                        "Open video",
                        directory=initial_FilePath,
                        filter='*.mp4 *.mkv *.mp3')
            except:
                initial_FilePath = os.getcwd()
                file_path, _ = QFileDialog.getOpenFileName(
                    self,
                    "Open video",
                    directory=initial_FilePath,
                    filter='*.mp4 *.mkv *.mp3')
        else:
            initial_FilePath = None
            file_path = filepath
        if file_path:
            # To write initial Directory in csvfile
            if file_path.replace(file_path.split("/")[-1],
                                 "") != initial_FilePath:
                with open("./PlayListPart/Filepath.csv", 'w') as file:
                    file.write(file_path.replace(file_path.split("/")[-1], ""))
            self.player.setMedia(QMediaContent(QUrl.fromLocalFile(file_path)))
            self.start()
            self.pushButton_volume.setEnabled(True)
            self.pushButton_volume.setIcon(QIcon('./Icons/unmute.png'))
            self.pushButton_volume.setToolTip("Mute (Ctrl+M)")
            if not self.isFullScreen():
                self.pushButton_BookMark.setVisible(True)
            self.pushButton_stop.setEnabled(True)
            # Handle Volume Slider
            self.Slider_Volume.setEnabled(True)
            self.Slider_Volume.setRange(0, self.player.volume())
            self.Slider_Volume.setValue(60)
            self.player.setVolume(60)

            # Create Playlist
            self.Setting.comboBox_Tag.clear()
            self.ComboBox_Tags_of_file.clear()
            self.PlaylistW.Create_Playlist(file_path)
            self.set_TagonListwidget(".".join(
                self.windowTitle()[16:].split(".")[:-1]))

    def Position_changed(self, position):
        if position > self.player.duration():
            position = self.player.duration()
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            hour = position // (1000 * 3600)
            minute = (position % (1000 * 3600)) // (60 * 1000)
            second = ((position % (1000 * 3600)) % (60 * 1000)) // 1000
            # Show Time
            self.label_Time.setText(
                f'{str(hour).zfill(2)}:{str(minute).zfill(2)}:{str(second).zfill(2)}'
            )
        # Automatic Next
        if self.player.duration() and position == self.player.duration():
            if self.pushButton_next.isEnabled():
                self.next()
            else:
                self.stop()
        self.Slider_Play.setValue(position)

    def Duration_changed(self, duration):
        # To set file duration
        self.Slider_Play.setRange(0, duration)
        # convert millsec to xx:xx:xx format
        hour = duration // (1000 * 3600)
        minute = (duration % (1000 * 3600)) // (60 * 1000)
        second = ((duration % (1000 * 3600)) % (60 * 1000)) // 1000
        self.label_Duration.setText(
            f'{str(hour).zfill(2)}:{str(minute).zfill(2)}:{str(second).zfill(2)}'
        )

        self.Slider_Volume.setEnabled(True)

    def Set_Position(self, position):
        if self.Slider_Play.width() - 1 <= position:
            position = self.Slider_Play.width() - 1
            self.player.pause()
            self.pushButton_Start.setEnabled(True)
            self.pushButton_Start.setIcon(QIcon('./Icons/play.png'))
            self.pushButton_Start.setToolTip("Paly")

        # if not (position < 0 and position > self.Slider_Play.width()):
        position = int(self.player.duration() *
                       (position / self.Slider_Play.width()))
        self.Slider_Play.setValue(position)
        self.player.setPosition(position)

    def slider_play_pos(self, position):
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            # Convert Width of Slider to time
            Time = int(self.player.duration() * position /
                       self.Slider_Play.width())
            # convert millsec to xx:xx:xx format
            hour = Time // (1000 * 3600)
            minute = (Time % (1000 * 3600)) // (60 * 1000)
            second = ((Time % (1000 * 3600)) % (60 * 1000)) // 1000
            self.Slider_play_label.setText(
                f'{str(hour).zfill(2)}:{str(minute).zfill(2)}:{str(second).zfill(2)}'
            )
            if self.isFullScreen():
                self.Slider_play_label.move(position + self.Slider_Play.x(),
                                            self.height() - 65)
            else:
                self.Slider_play_label.move(position + self.Slider_Play.x(),
                                            self.height() - 80)
            self.Slider_play_label.setVisible(True)

    def slider_volume_pos(self, position):
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            # Convert Width of Slider to volume
            volume = int(100 * position / self.Slider_Volume.width())
            self.Slider_Volume_label.setText(f'{volume}%')
            if self.isFullScreen():
                self.Slider_Volume_label.move(
                    position + self.Slider_Volume.x() - 5,
                    self.height() - 37)
            else:
                self.Slider_Volume_label.move(
                    position + self.Slider_Volume.x() - 5,
                    self.height() - 48)
            self.Slider_Volume_label.setVisible(True)

    def Set_volume(self, volume):
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            # Volume must be between 0 , 100
            if 100 <= volume:
                volume = 100
            elif volume <= 0:
                volume = 0

            if not volume:
                self.player.setMuted(True)
                self.pushButton_volume.setIcon(QIcon('./Icons/mute.png'))
                self.pushButton_volume.setToolTip("UnMute (Ctrl+M)")

            else:
                self.player.setMuted(False)
                self.pushButton_volume.setIcon(QIcon('./Icons/unmute.png'))
                self.pushButton_volume.setToolTip("Mute (Ctrl+M)")

            self.Slider_Volume.setValue(volume)
            self.player.setVolume(volume)

    def volumeOnOff(self):
        """Mute and unmute"""
        if self.player.isAudioAvailable() or self.player.isVideoAvailable():
            if self.player.isMuted():
                self.player.setMuted(False)
                self.pushButton_volume.setIcon(QIcon('./Icons/unmute.png'))
                self.pushButton_volume.setToolTip("Mute (Ctrl+M)")
            else:
                self.player.setMuted(True)
                self.pushButton_volume.setIcon(QIcon('./Icons/mute.png'))
                self.pushButton_volume.setToolTip("UnMute (Ctrl+M)")

    # save bookmarks and updatae tag list widget
    def save_Bookmarks(self):
        if not self.tag_Path:
            error_addBookmark = confrimWin(
                self,
                Title="Warning",
                Text="First select or create a tag file and try again")
            error_addBookmark.show()
            return False
        try:
            # use add bookmark function to add bookmarks in tag part
            add_Bookmark(
                self.lineEdit_Bookmark.text() + "#" +
                tc.millis_to_format(self.player.position()),
                ".".join(self.windowTitle()[16:].split(".")[:-1]),
                self.tag_Path)
            # there isn't any tags for movie we want to add bookmark it
            if not ".".join(
                    self.windowTitle()[16:].split(".")[:-1]) in self.allTag:
                self.allTag.update(
                    {".".join(self.windowTitle()[16:].split(".")[:-1]): {}})
            self.allTag[".".join(
                self.windowTitle()[16:].split(".")[:-1])].update({
                    self.lineEdit_Bookmark.text():
                    tc.millis_to_format(self.player.position())
                })
            self.set_TagonListwidget(
                self.windowTitle()[16:].split(".")[0])  # update tag listwidget
            # update combo box of edit and Main window
            self.pushButton_Start.setFocus(True)
            index = self.ComboBox_Tags_of_file.findText(
                self.windowTitle()[16:])
            self.ComboBox_Tags_of_file.setCurrentIndex(index)
            index = self.Setting.comboBox_Tag.findText(self.windowTitle()[16:])
            self.Setting.comboBox_Tag.setCurrentIndex(index)
            # ****

        except:
            pass

        self.lineEdit_Bookmark.clear()
        self.lineEdit_Bookmark.setVisible(False)

    def sch_icon_Event(self):
        # Show search lineEdit to search
        self.search_lineEdit.setFixedWidth(0)

        # Start Animation of search lineEdit
        self.SearchAnimation = Search_Animation(self)
        self.SearchAnimation.update_Animation.connect(
            self.Update_Search_Animation)
        self.pushButton_Search.setEnabled(False)
        self.SearchAnimation.start()

        self.search_lineEdit.setVisible(True)
        self.search_lineEdit.setFocus(True)

    def Update_Search_Animation(self, size):
        if size == -1:  # Animation is finished
            self.pushButton_Search.setEnabled(True)
        else:  # Animation is running
            self.search_lineEdit.setFixedWidth(size)

    def add_BookMarks(self):
        # Show Bookmark lineEdit to search

        if self.player.isVideoAvailable() or self.player.isAudioAvailable():
            self.lineEdit_Bookmark.setFixedWidth(0)

            # Start Animation of search lineEdit
            self.BookMarkAnimation = BookMark_Animation(self)
            self.BookMarkAnimation.update_Animation.connect(
                self.Update_BookMark_Animation)

            self.pushButton_BookMark.setEnabled(False)
            self.BookMarkAnimation.start()

            self.lineEdit_Bookmark.setVisible(True)
            self.lineEdit_Bookmark.setFocus(True)

    def Update_BookMark_Animation(self, size):
        if size == -1:  # Animation is finished
            self.pushButton_BookMark.setEnabled(True)
        else:  # Animation is running
            self.lineEdit_Bookmark.setFixedWidth(size)

    def MainWindow_Event(self, type):
        # Click on MainWindow
        self.search_lineEdit.setText("")
        self.search_lineEdit.setVisible(False)

        self.lineEdit_Bookmark.setText("")
        self.lineEdit_Bookmark.setVisible(False)

        self.sch_listWidget.setVisible(False)
        self.sch_listWidget.clear()

    # handle main size change to set correct size to
    # search and bookmark line edits and searchlistwidgetd
    def main_size_Change(self, val):
        self.lineEdit_Bookmark.setFixedWidth(int(self.size().width() / 4))
        self.search_lineEdit.setFixedWidth(int(self.size().width() / 4))

        self.sch_listWidget.resize(  # Handle size of search listwidget
            int(self.size().width() / 4),
            int((200 / 600) * self.size().height()))
        self.sch_listWidget.move(  # Move search listwidget
            self.size().width() - self.pushButton_Search.geometry().width() -
            self.search_lineEdit.geometry().width() - 15,
            self.search_lineEdit.geometry().height() +
            self.search_lineEdit.pos().y() + self.menubar.geometry().height())

    # Item clicked event in search tag part
    def item_searchlist_Event(self, item):
        session, tag = re.split(" -> ", item.text())
        # get all session to compare that is selected tag in playlist or user select tag wrong
        all_sessions = list(self.PlaylistW.Files.keys())
        # there is bug here if mp3 and mp4 has same name
        all_sessions = [
            ".".join(item.split(".")[:-1]) for item in all_sessions
        ]
        if session in all_sessions:
            if session != ".".join(self.windowTitle()[16:].split(".")[:-1]):
                # Show confirm window to get accept user for change video
                self.confirmWin = confrimWin(
                    self,
                    session=session,
                    tag_Text=tag,
                    Text=
                    f"Are you sure to change video to {session} from search")
                self.confirmWin.show()
            else:
                try:
                    # concert time format to second for using in change position
                    time_second = tc.to_second(self.allTag[session][tag])
                    self.change_Position(time_second)
                except:  # handle unexcepted error!
                    pass
        else:
            Warning_user_wrongTags = confrimWin(
                self, Title="Warning", Text="You have opened wrong tag files")
            Warning_user_wrongTags.show()

    # Create search listwidget and running thread to starting search

    def search_Tag(self, val):
        # Create QListWidget
        self.sch_listWidget.resize(int((200 / 800) * self.size().width()),
                                   int((200 / 600) * self.size().height()))
        self.sch_listWidget.move(
            self.search_lineEdit.x(),
            self.search_lineEdit.geometry().height() +
            self.search_lineEdit.pos().y() + self.menubar.geometry().height())

        self.sch_listWidget.setVisible(True)
        if val == "":
            self.sch_listWidget.clear()
        # start search thread
        else:
            self.search_Thread = search_thread(self, self.allTag, val)
            self.search_Thread.update_schTag.connect(self.update_searchTags)
            self.search_Thread.start()

    # Update searchtags function to update search widget by searching instantly
    def update_searchTags(self, tagsDict):
        self.sch_listWidget.clear()  # clear search list widget

        for session, Tags in tagsDict.items(
        ):  # writing data on search listwidget
            for text in Tags:
                self.sch_listWidget.addItem(session + " -> " + text)

    def Logout(self):
        os.remove("LoginPart/User.csv")
        self.close()
        self.player.stop()
        self.Setting.close()
        self.PlaylistW.close()
        self.helpW.close()
        self.DockWidget_Tags_of_file.close()
        subprocess.call(['python', 'MediaPlayer.py'])  # Start again

    def Play_list(self):
        # Open playlist
        # To show current Row for every time it is opened

        try:
            if self.PlaylistW.listWidget_Playlist.count():
                self.PlaylistW.listWidget_Playlist.setCurrentRow(
                    list(self.PlaylistW.Files.keys()).index(
                        self.windowTitle()[16:]))
        except Exception as e:
            pass

        self.PlaylistW.show()
        # setPosition of playlist when it is opened
        self.PlaylistW.move(
            QtGui.QCursor().pos().x(),
            QtGui.QCursor().pos().y() - self.PlaylistW.size().height() - 25)

    def Show_Tags_of_file(self):
        self.sch_listWidget.setVisible(False)
        # To show Tags of file in DockWidget
        self.DockWidget_Tags_of_file.setVisible(
            not self.DockWidget_Tags_of_file.isVisible())
        index = self.ComboBox_Tags_of_file.findText(self.windowTitle()[16:])
        self.ComboBox_Tags_of_file.setCurrentIndex(index)
        # To Change features of the Dockwidget according Fullscreen
        if self.isFullScreen():
            self.DockWidget_Tags_of_file.setFeatures(
                QDockWidget.DockWidgetClosable)
            Full_screen.Set_visible(self, False)

        else:  # To Change features of the Dockwidget according Normalscreen
            self.DockWidget_Tags_of_file.setFeatures(
                QDockWidget.DockWidgetFloatable
                | QDockWidget.DockWidgetMovable)

    def ListWidget_Tag(self, index):
        """Tag combo box item clicked function"""
        videoName = ".".join(
            list(self.PlaylistW.Files.keys())[index].split(".")[:-1])
        self.set_TagonListwidget(videoName, Setting_Tags=False)

    def openTags(self):
        """OpenTag in csv, pptx, docx format and start tag thread for reading data"""
        try:
            # To read initial Tag Directory from csvfile
            # if there is FilePath_Tag.csv
            with open("./tagPart/Filepath_Tag.csv") as file:
                initial_FilePath_Tag = file.read()
                _tag_Path, _ = QFileDialog.getOpenFileName(
                    self,
                    "Open Tag",
                    directory=initial_FilePath_Tag,
                    filter='*.csv *.docx *.pptx')

        except Exception as e:
            initial_FilePath_Tag = os.path.join(os.getcwd(), 'Tags')
            _tag_Path, _ = QFileDialog.getOpenFileName(
                self,
                "Open Tag",
                directory=initial_FilePath_Tag,
                filter='*.csv *.docx *.pptx')

        # if tagpath is correct we starting tag_thread to read tags from path
        if _tag_Path:
            # save filepath in csv file
            self.tag_Path = _tag_Path
            try:
                with open("./tagPart/Filepath_Tag.csv", 'w') as file:
                    file.write(
                        self.tag_Path.replace(
                            self.tag_Path.split("/")[-1], ""))
            except Exception as e:
                pass
            Tagname = self.tag_Path.split("/")[-1]
            fileFormat = Tagname.split(".")[-1]
            self.tag_thread = read_Tag(self, self.tag_Path, fileFormat)
            self.tag_thread.Tag_Ready.connect(self.getTag)
            self.tag_thread.start()

    # Tag is ready to use
    # if tags ready we shoud save them in self.allTag variable to use them properly
    def getTag(self, tags):
        self.allTag = tags
        index = self.ComboBox_Tags_of_file.findText(self.windowTitle()[16:])
        self.ComboBox_Tags_of_file.setCurrentIndex(index)
        self.set_TagonListwidget(".".join(
            self.windowTitle()[16:].split(".")[:-1]))

    # set tags on tag listwidget using vedio name
    # update tags

    def set_TagonListwidget(self,
                            videoName,
                            Setting_Tags=True,
                            Media_Tags=True):
        if Media_Tags:
            self.ListWidget_Tags_of_file.clear()
        if Setting_Tags:
            self.Setting.Edit_tag_Listwidget.clear()
        try:
            if videoName in self.allTag:
                sessionTag = self.allTag[videoName]
                # sorted tags by time
                sessionTag = {
                    text: time
                    for text, time in sorted(
                        sessionTag.items(),
                        key=lambda item: tc.to_second(item[1]))
                }
                for text in sessionTag:
                    # setting part tags by qtreewidget
                    if Setting_Tags:
                        item = QTreeWidgetItem(
                            self.Setting.Edit_tag_Listwidget,
                            [text, sessionTag[text]])
                    # media part tags by qlistwidget
                    if Media_Tags:
                        self.ListWidget_Tags_of_file.addItem(
                            f'{self.ListWidget_Tags_of_file.count()+1} . {text}'
                        )

        except:
            pass

    # item clicked event to go to time correlate clicked tag in video

    # change time when clicking on tags in search part and main listwidget if tags

    def GoToTagtime(self, item):
        spliter = len(str(self.ListWidget_Tags_of_file.currentRow() + 1)) + 3
        tag_Text = item.text()[spliter:]
        # change time if clicked on tags that belong to playing session
        if self.windowTitle()[16:] == self.ComboBox_Tags_of_file.currentText():
            session = ".".join(self.windowTitle()[16:].split(".")[:-1])
            try:
                # convert time to seconds using tc mudole(write by own)
                time_second = tc.to_second(self.allTag[session][tag_Text])
                # using change position function to handle sliders and time
                self.change_Position(time_second)
            except:
                pass
        else:
            session = ".".join(
                self.ComboBox_Tags_of_file.currentText().split(".")[:-1])
            self.confirmWin = confrimWin(
                self,
                session=session,
                tag_Text=tag_Text,
                Text=f"Are you sure to change video to {session}")
            self.confirmWin.show()

    def change_Position(self, time_second):
        # change slider position using item time
        self.Slider_Play.setValue(int(time_second) * 1000)
        # change video position using item time
        self.player.setPosition(int(time_second) * 1000)

    # change video function to change video when clicked on
    # tags that there is not in current movie's tags

    def change_Video(self, session):
        # there is bug here if user select mp3 file that has same name with mp4 similar file
        for key in self.PlaylistW.Files:
            if session + ".mp4" == key:
                return self._change_Video(key)
            elif session + ".mp3" == key:
                return self._change_Video(key)
            elif session + ".mkv" == key:
                return self._change_Video(key)
        return False

    def _change_Video(self, key):
        self.player.setMedia(
            QMediaContent(QUrl.fromLocalFile(
                self.PlaylistW.Files[key])))  # set video
        self.start()
        self.setWindowTitle(f" Media Player - {key}")  # change title
        # update combo box of session in MediaPlayer
        index = self.ComboBox_Tags_of_file.findText(key)
        self.ComboBox_Tags_of_file.setCurrentIndex(index)  # update combo box
        # update tags on setting and MediaPlayer window
        self.set_TagonListwidget(".".join(key.split(".")[:-1]))
        return True

    # close Event to stop runnig thread and preventing error
    def Close(self, val):
        if self.tag_thread:
            self.tag_thread.stop()
        if self.search_Thread:
            self.search_Thread.stop()
        if self.SearchAnimation:
            self.SearchAnimation.stop()
        if self.BookMarkAnimation:
            self.BookMarkAnimation.stop()

    # dragEnterEvent to hanle drag and drop option

    def dragEnterEvent(self, event):
        # pass
        if event.mimeData().hasUrls:
            event.accept()
        else:
            event.ignore()

    # dragMoveEvent to hanle drag and drop option
    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls:
            event.accept()
        else:
            event.ignore()

    # dropEvent to hanle drag and drop option
    def dropEvent(self, event):
        if event.mimeData().hasUrls:
            event.setDropAction(Qt.CopyAction)
            # get droppen file path
            file_path = event.mimeData().urls()[0].toLocalFile()
            file_format = (file_path.split("/")[-1]).split(".")[-1]
            desired_format = ['mp4', 'mkv', 'mp3']  # desired path to open
            if file_format in desired_format:  # check file format
                self.Load_video(filepath=file_path)