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('生成参考')
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)
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)
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)
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)
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)
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()
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)
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()
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()
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()
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)
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)
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)
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)