class MainWindow(QMainWindow): changePage = Signal(bool) def __init__(self): super().__init__() self.setWindowTitle('Petro-Explorer v1.0') win_icon = QIcon('icons/Logo.ico') self.setWindowIcon(win_icon) self.setStyleSheet('background-color: #363f49;') self.header = Header() self.navigation = Navigation() self.p_top_bar = PetrophysicsTopBar() self.s_top_bar = StatisticsTopBar() self.p_options_1 = PetrophysicsOptions(_mode='KCarman') self.p_options_2 = PetrophysicsOptions(_mode='TCoates') self.p_options_3 = PetrophysicsOptions(_mode='Winland') self.p_options_4 = PetrophysicsOptions(_mode='RQIFZI') self.p_options_5 = PetrophysicsOptions(_mode='Lucia') self.p_options_6 = PetrophysicsOptions(_mode='DParsons') self.s_options_1 = StatisticsOptions(_mode='Regression') self.s_options_2 = StatisticsOptions(_mode='Statistics') self.s_options_3 = StatisticsOptions(_mode='Histogram') self.s_options_4 = StatisticsOptions(_mode='Boxplot') self.sp_controls = SpreadsheetControls() self.plot_controls = PlotControls() self.plot_viewer = PlotViewer() self.spreadsheet = Spreadsheet() self.status_bar = QStatusBar() self.scroll_area_1 = QScrollArea() self.scroll_area_1.setWidget(self.spreadsheet) self.scroll_area_1.setWidgetResizable(True) self.scroll_area_2 = QScrollArea() self.scroll_area_2.setWidget(self.plot_viewer) self.scroll_area_2.setWidgetResizable(True) self.bar_widget = QWidget() self.bar_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.central_widget = QWidget() self.options_widget = QWidget() self.docking_options = QDockWidget() self.docking_options.setWidget(self.options_widget) self.docking_options.setTitleBarWidget( DockWidgetRibbon(' Opções de cálculo')) self.docking_options2 = QDockWidget() self.docking_options2.setWidget(self.sp_controls) self.docking_options2.setTitleBarWidget( DockWidgetRibbon(' Controles de visualização')) self.setCentralWidget(self.central_widget) self.setStatusBar(self.status_bar) self.addToolBar(self.navigation) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.docking_options) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.docking_options2) self.connections() self.buildLayout() self.centralWidget().setStyleSheet('background-color: #2e3843') self.spreadsheet.setStyleSheet('background-color: white') self.status_bar.setStyleSheet('color: white') def buildLayout(self): stacked_layout_1 = QStackedLayout() # stacked_layout_1.addWidget(QWidget()) stacked_layout_1.addWidget(self.p_top_bar) stacked_layout_1.addWidget(self.s_top_bar) stacked_layout_2 = QStackedLayout() # stacked_layout_2.addWidget(QWidget()) stacked_layout_2.addWidget(self.p_options_1) stacked_layout_2.addWidget(self.p_options_2) stacked_layout_2.addWidget(self.p_options_3) stacked_layout_2.addWidget(self.p_options_4) stacked_layout_2.addWidget(self.p_options_5) stacked_layout_2.addWidget(self.p_options_6) stacked_layout_2.addWidget(self.s_options_1) stacked_layout_2.addWidget(self.s_options_2) stacked_layout_2.addWidget(self.s_options_3) stacked_layout_2.addWidget(self.s_options_4) self.stacked_layout_3 = QStackedLayout() self.stacked_layout_3.addWidget(self.scroll_area_1) self.stacked_layout_3.addWidget(self.scroll_area_2) central_widget_layout = QVBoxLayout() central_widget_layout.addWidget(self.bar_widget) central_widget_layout.addLayout(self.stacked_layout_3) self.central_widget.setLayout(central_widget_layout) self.bar_widget.setLayout(stacked_layout_1) self.options_widget.setLayout(stacked_layout_2) def connections(self): self.navigation.PetroAnalysis.connect( lambda: self.bar_widget.layout().setCurrentIndex(0)) self.navigation.StatsAnalysis.connect( lambda: self.bar_widget.layout().setCurrentIndex(1)) self.navigation.Save.connect(lambda: self.saveDialog()) self.navigation.Import.connect(lambda: self.importDialog()) self.navigation.About.connect(lambda: self.aboutDialog()) self.navigation.Help.connect(lambda: self.helpDialog()) self.navigation.header.HomePage.connect( lambda: self.changePage.emit(True)) self.navigation.ViewSheet.connect(lambda: self.displaySheet()) self.navigation.ViewPlot.connect(lambda: self.displayPltE()) self.navigation.New.connect(lambda: self.spreadsheet.addBlankSheet()) self.p_top_bar.KCarman.connect( lambda: self.options_widget.layout().setCurrentIndex(0)) self.p_top_bar.TCoates.connect( lambda: self.options_widget.layout().setCurrentIndex(1)) self.p_top_bar.Winland.connect( lambda: self.options_widget.layout().setCurrentIndex(2)) self.p_top_bar.DParsons.connect( lambda: self.options_widget.layout().setCurrentIndex(5)) self.p_top_bar.Lucia.connect( lambda: self.options_widget.layout().setCurrentIndex(4)) self.p_top_bar.RF.connect( lambda: self.options_widget.layout().setCurrentIndex(3)) self.s_top_bar.Regr.connect( lambda: self.options_widget.layout().setCurrentIndex(6)) self.s_top_bar.StatDesc.connect( lambda: self.options_widget.layout().setCurrentIndex(7)) self.s_top_bar.Boxplt.connect( lambda: self.options_widget.layout().setCurrentIndex(9)) self.s_top_bar.Hist.connect( lambda: self.options_widget.layout().setCurrentIndex(8)) self.s_options_1.run_button.clicked.connect(self.startRegr) self.s_options_2.run_button.clicked.connect(self.startStat) self.s_options_3.run_button.clicked.connect(self.startHist) self.s_options_4.run_button.clicked.connect(self.startBxpl) self.p_options_1.run.clicked.connect(self.startKCoz) self.p_options_2.run.clicked.connect(self.startTCoa) self.p_options_3.run.clicked.connect(self.startWinl) self.p_options_4.run.clicked.connect(self.startFZIR) self.p_options_5.run.clicked.connect(self.startLuci) self.p_options_6.run.clicked.connect(self.startDyks) self.sp_controls.AddColumn.connect( lambda: self.spreadsheet.addColumn()) self.sp_controls.AddRow.connect(lambda: self.spreadsheet.addRow()) self.sp_controls.DeleteRow.connect( lambda: self.spreadsheet.deleteRow()) self.sp_controls.DeleteColumn.connect( lambda: self.spreadsheet.deleteColumn()) self.plot_controls.run_button.clicked.connect(lambda: self.startPltE()) self.plot_controls.erasePlot.connect( lambda: self.plot_viewer.erasePlot()) self.plot_viewer.feedPlotControls.connect( self.plot_controls.fillFromJson) def startPltE(self): old_json = self.plot_viewer.json_data title = self.plot_controls.modify_title.text() xtype = self.plot_controls.modify_x_axis_type.currentText() ytype = self.plot_controls.modify_y_axis_type.currentText() xlabel = self.plot_controls.modify_x_axis_label.text() ylabel = self.plot_controls.modify_y_axis_label.text() if len(self.plot_controls.modify_lower_x_range.text()) > 0: lxrange = float(self.plot_controls.modify_lower_x_range.text()) else: lxrange = None if len(self.plot_controls.modify_upper_x_range.text()) > 0: uxrange = float(self.plot_controls.modify_upper_x_range.text()) else: uxrange = None if len(self.plot_controls.modify_lower_y_range.text()) > 0: lyrange = float(self.plot_controls.modify_lower_y_range.text()) else: lyrange = None if len(self.plot_controls.modify_upper_y_range.text()) > 0: uyrange = float(self.plot_controls.modify_upper_y_range.text()) else: uyrange = None if len(self.plot_controls.add_x_trace.text()) > 0 and len( self.plot_controls.add_y_trace.text()): trace_type = self.plot_controls.type_of_trace.currentText() x_trace = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.plot_controls. add_x_trace.text( ))] y_trace = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.plot_controls. add_y_trace.text( ))] trace_name = self.plot_controls.trace_name.text() else: trace_type = None x_trace = None y_trace = None trace_name = None io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.loadPltE(io_operations_handler.results)) io_operations_handler.loadParameters(f=pceManifold, _args=[ old_json, title, xtype, ytype, xlabel, ylabel, lxrange, uxrange, lyrange, uyrange, trace_type, x_trace, y_trace, trace_name ]) io_operations_handler.start() def loadPltE(self, results, typ='Regressão'): self.plot_viewer.loadPlot(results[0], results[1], _type=typ) self.displayPltE() def displayPltE(self): self.stacked_layout_3.setCurrentIndex(1) self.docking_options2.setWidget(self.plot_controls) #self.removeDockWidget(self.docking_options3) #self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.docking_options2) def loadSheet(self, df): self.spreadsheet.changeModel(data=df, header=list(df.keys())) self.displaySheet() def displaySheet(self): self.stacked_layout_3.setCurrentIndex(0) self.docking_options2.setWidget(self.sp_controls) self.status_bar.clearMessage() #self.removeDockWidget(self.docking_options2) #self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.docking_options3) def importDialog(self): func = None file_settings = QFileDialog().getOpenFileName( self, 'Importar Arquivo', filter= "Todos os arquivos (*);; Arquivo de Texto (*.txt);; Arquivo CSV (*.csv);; " "Planilha Excel (*.xlsx)") if ".txt" in file_settings[0]: func = Data.readTXT if ".csv" in file_settings[0]: func = Data.readCSV if ".xlsx" in file_settings[0]: func = Data.readExcel self.status_bar.showMessage('Importando arquivo. Aguarde.') io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.loadSheet(io_operations_handler.results)) io_operations_handler.loadParameters(f=func, _args=[ file_settings[0], ]) io_operations_handler.start() def saveDialog(self): func = None file_settings = QFileDialog().getSaveFileName( self, "Salvar Arquivo", filter="Arquivo de Texto (*.txt);; Arquivo CSV (*.csv);; " "Planilha Excel (*.xlsx)") if ".txt" in file_settings[0]: func = Data.toTXT if ".csv" in file_settings[0]: func = Data.toCSV if ".xlsx" in file_settings[0]: func = Data.toExcel io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.loadParameters( f=func, _args=[self.spreadsheet.retrieveModel(), file_settings[0]]) io_operations_handler.start() def aboutDialog(self): icon = QIcon(r'icons/sobre.png') text = "O Petro-Explorer é um software desenvolvido no Laboratório de Exploração e Produção de Petróleo (" \ "LENEP) da UENF para análises petrofísicas e estatísticas de dados de rocha. " msg = QMessageBox() msg.setWindowTitle('Sobre Petro-Explorer') msg.setWindowIcon(icon) msg.setText(text) msg.exec_() def helpDialog(self): icon = QIcon(r'icons/ajuda.png') text = r"Para utilizar o Petro-Explorer de maneira correta siga os seguintes passos:" \ "\n1 - Clique no botão Novo na barra de navegação, isto irá limpar quaisquer dados de antigas análises.\n" \ "2 - Para começar a sua análise é preciso importar os dados, assim, clique no botão Importar para escolher o seu arquivo. Atente para o fato que somente três tipos de arquivo são suportados (i.e. *.txt, *.csv, *.xlsx). Depois da sua escolha, os dados devem aparecer na planilha do software.\n" \ "3 - Se você desejar realizar uma análise petrofísica, clique no botão de Análise Petrofísica na barra de navegação. Caso queira realizar uma análise estatística, clique no botão de Análise Estatística na barra de navegação.\n" \ "4 - Selecione na barra superior à planilha, a análise que desejas realizar sobre seus dados.\n" \ "5 - No canto direito da janela, selecione os parâmetros de sua análise assim como o destino de seus resultados. Clique no botão 'Começar Análise' para continuar.\n" \ "6 - Analise seus resultados na planilha, e, quando disponível, no gráfico também.\n" \ "7 - Quando terminar, clique no botão Salvar para salvar os resultados de suas análises no disco.\n" \ "8 - Caso queira realizar outra análise, clique no botão \"Novo\" e comece novamente.\n" msg = QMessageBox() msg.setWindowTitle('Ajuda') msg.setWindowIcon(icon) msg.setText(text) msg.exec_() def startRegr(self): xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.s_options_1. payload['x_column'])] ydata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.s_options_1. payload['y_column'])] _type = self.s_options_1.payload['calculate'] dgr = self.s_options_1.degree.value() io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayRegr(self.s_options_1.payload['output_column'], io_operations_handler.results)) io_operations_handler.loadParameters(f=regressionManifold, _args=[xdata, ydata, dgr, _type]) io_operations_handler.start() def startStat(self): xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.s_options_2. payload['x_column'])] mthd = self.s_options_2.payload['calculate'] qtl = self.s_options_2.quartile.value() pctl = self.s_options_2.percentile.value() io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayStat(self.s_options_2.payload['output_column'], io_operations_handler.results)) io_operations_handler.loadParameters(f=statisticsManifold, _args=[xdata, mthd, qtl, pctl]) io_operations_handler.start() def startHist(self): xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.s_options_3. payload['x_column'])] nbins = self.s_options_3.payload['y_column'] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayHist(io_operations_handler.results)) io_operations_handler.loadParameters(f=histogramManifold, _args=[xdata, nbins]) io_operations_handler.start() def startBxpl(self): indexes = [] columns = self.s_options_4.payload['column_range'] for col in columns: indexes.append(self.spreadsheet.model.header_info.index(col)) xdata = self.spreadsheet.model.input_data[:, indexes] box_orientation = self.s_options_4.payload['y_column'] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayBxpl(io_operations_handler.results)) io_operations_handler.loadParameters(f=boxplotManifold, _args=[xdata, box_orientation]) io_operations_handler.start() def startKCoz(self): #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr k = None phi = None svgr = None prp = self.p_options_1.payload['calculate'] if prp == 'Permeabilidade (mD)': phi = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.p_options_1. payload['x_column'])] svgr = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_1. payload['y_column'])] elif prp == 'SVgr (cm-1)': k = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.p_options_1. payload['x_column'])] phi = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.p_options_1. payload['y_column'])] else: k = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.p_options_1. payload['x_column'])] phi = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info. index(self.p_options_1. payload['y_column'])] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayKCoz(io_operations_handler.results)) io_operations_handler.loadParameters(f=kCarmanManifold, _args=[k, phi, svgr, prp]) io_operations_handler.start() def startTCoa(self): #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr prp = self.p_options_2.payload['calculate'] if prp == "Swir (%)": xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_2. payload['x_column'])] ydata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_2. payload['y_column'])] zdata = None else: xdata = None ydata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_2. payload['y_column'])] zdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_2. payload['x_column'])] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayTCoa(io_operations_handler.results)) io_operations_handler.loadParameters(f=tCoatesManifold, _args=[xdata, ydata, zdata, prp]) io_operations_handler.start() def startWinl(self): #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr if self.p_options_3.payload['x_column'] != '': xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_3. payload['x_column'])] else: xdata = [] if self.p_options_3.payload['y_column'] != '': ydata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_3. payload['y_column'])] else: ydata = [] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayWinl(io_operations_handler.results)) io_operations_handler.loadParameters(f=winlandManifold, _args=[xdata, ydata]) io_operations_handler.start() def startFZIR(self): # you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr if self.p_options_4.payload['x_column'] != '': xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_4. payload['x_column'])] else: xdata = None if self.p_options_4.payload['y_column'] != '': ydata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_4. payload['y_column'])] else: ydata = None if self.p_options_4.payload['z_column'] != '': zdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_4. payload['z_column'])] else: zdata = None prp = self.p_options_4.payload['calculate'] un = self.p_options_4.payload['column_range'] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayFZIR(io_operations_handler.results)) io_operations_handler.loadParameters( f=fzManifold, _args=[xdata, ydata, zdata, un, prp]) io_operations_handler.start() def startLuci(self): #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr if self.p_options_5.payload['x_column'] != '': xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_5. payload['x_column'])] else: xdata = [] if self.p_options_5.payload['y_column'] != '': ydata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_5. payload['y_column'])] else: ydata = [] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayLuci(io_operations_handler.results)) io_operations_handler.loadParameters(f=luciaManifold, _args=[xdata, ydata]) io_operations_handler.start() def startDyks(self): #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr if self.p_options_6.payload['x_column'] != '': xdata = self.spreadsheet.model.input_data[:, self.spreadsheet.model. header_info.index( self.p_options_6. payload['x_column'])] else: xdata = [] prp = self.p_options_6.payload['calculate'] io_operations_handler = HandlerThread() io_operations_handler.messageSent.connect(self.status_bar.showMessage) io_operations_handler.daemon = True io_operations_handler.hasFinished.connect( lambda: self.displayDyks(io_operations_handler.results)) io_operations_handler.loadParameters(f=dParsonsManifold, _args=[xdata, prp]) io_operations_handler.start() def displayKCoz(self, content): content = list(content) content.insert(0, 'Resultados - Kozeny-Carman') self.spreadsheet.addColumn(content) def displayWinl(self, content): self.loadPltE(content[:2]) content[2].insert(0, 'R35 - Winland') content[3].insert(0, 'Ports - Winland') self.spreadsheet.addColumn(content[2]) self.spreadsheet.addColumn(content[3]) def displayTCoa(self, content): content.insert(0, 'Resultados - Timur Coates') self.spreadsheet.addColumn(content) def displayDyks(self, content): content.insert(0, 'Resultados - Dykstra-Parsons') self.spreadsheet.addColumn(content) self.p_options_6.results.setText( 'Atenção! Resultado pode ser diferente do coeficiente calculado através do gráfico de probabilidade. \n' + str(content[0]) + ': ' + str(content[1])) def displayLuci(self, results): self.loadPltE(results[:2]) results[2].insert(0, 'Resultados - RFN/Lucia') self.spreadsheet.addColumn(results[2]) def displayFZIR(self, results): results[0].insert(0, 'Resultados - RQI (μm)') self.spreadsheet.addColumn(results[0]) results[1].insert(0, 'Resultados - PhiZ') self.spreadsheet.addColumn(results[1]) self.loadPltE(results[2:]) def displayRegr(self, display_name, results): self.s_options_1.results.setText(str(results[0])) self.loadPltE(results[-2:]) def displayStat(self, display_name, results): self.s_options_2.results.setText(str(results)) def displayHist(self, results): self.loadPltE(results, 'Histograma') def displayBxpl(self, results): self.loadPltE(results, 'Boxplot')
class Chrono(QMainWindow): def __init__(self, parent=None): super(Chrono, self).__init__(parent) self.createMenus() self.createSystemTrayIcon() self.timer = QTimer(self) self.timer.timeout.connect(self.tick) self.isRunning = False self.refresh_rate = 100 # ms self.progressBar = QProgressBar() self.progressBar.setValue(0) self.begin_time = self.end_time = 0 self.label = QLabel(" ") self.button = QPushButton() self.button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause)) self.end_delay = self.begin_delay = 0 bottomLayout = QHBoxLayout() bottomLayout.addWidget(self.progressBar) bottomLayout.addWidget(self.button) self.button.clicked.connect(self.pause) mainLayout = QVBoxLayout() mainLayout.addWidget(self.label) mainLayout.addLayout(bottomLayout) centralWidget = QWidget() centralWidget.setLayout(mainLayout) self.setCentralWidget(centralWidget) self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) self.notification = self.notification_popup = self.notification_tray = self.notification_sound = True self.notification_soundfile = os.path.dirname( sys.argv[0]) + '/notification.mp3' # os.path.dirname(__file__) + self.setWindowTitle(TITLE) self.resize(400, self.sizeHint().height()) self.setFixedHeight(self.sizeHint().height()) def createMenus(self): menus = QMenuBar() fileMenu = menus.addMenu("&Fichier") file_newMenu = fileMenu.addMenu( self.style().standardIcon(QStyle.SP_FileIcon), "Nouveau") file_newMenu.addAction("Date", self.createDateDialog, 'CTRL+D') file_newMenu.addAction("Durée", self.createDurationDialog, 'CTRL+N') fileMenu.addSeparator() fileMenu.addAction(self.style().standardIcon(QStyle.SP_BrowserStop), "Quitter", sys.exit, 'CTRL+Q') optionMenu = menus.addMenu("&Options") optionMenu.addAction( self.style().standardIcon(QStyle.SP_MessageBoxInformation), "Évènements", self.createNotificationPopup, 'CTRL+O') optionMenu.addAction( QAction("Rester au premier plan", optionMenu, triggered=self.stayOnTop, checkable=True)) aideMenu = menus.addMenu("&Aide") aideMenu.addAction( self.style().standardIcon(QStyle.SP_DialogHelpButton), "À propos", lambda: QMessageBox.information( self, "À propos", TITLE + " " + str(VERSION)), 'CTRL+H') aideMenu.addSeparator() aideMenu.addAction( self.style().standardIcon(QStyle.SP_TitleBarMenuButton), "À propos de Qt", QApplication.aboutQt, 'CTRL+A') self.setMenuBar(menus) def createSystemTrayIcon(self): self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(os.path.dirname(sys.argv[0]) + '/icon.svg')) # os.path.dirname(__file__) + self.tray.setToolTip(TITLE) self.tray.show() systemTrayMenu = QMenu() pauseAction = QAction(self.style().standardIcon(QStyle.SP_MediaPause), "Pause / Reprendre", systemTrayMenu) pauseAction.triggered.connect(self.pause) systemTrayMenu.addAction(pauseAction) systemTrayMenu.addSeparator() systemTrayMenu.addAction( self.style().standardIcon(QStyle.SP_BrowserStop), "Quitter", sys.exit) self.tray.setContextMenu(systemTrayMenu) self.tray.activated.connect(self.show) def stayOnTop(self): self.setWindowFlags(self.windowFlags() ^ Qt.WindowStaysOnTopHint) # self.windowFlags() | Qt.CustomizeWindowHint | Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) self.show() def createNotificationPopup(self): popup = QDialog(self) popup.setFixedSize(popup.sizeHint().height(), popup.sizeHint().width()) popup.setWindowTitle("Évènements") innerLayout = QVBoxLayout() GroupBox = QGroupBox("Activer les notifications") GroupBox.setCheckable(True) GroupBox.setChecked(self.notification) checkBox_popup = QCheckBox("Afficher une popup") checkBox_notification = QCheckBox("Afficher une notification") checkBox_sound = QCheckBox("Jouer un son") if self.notification_popup: checkBox_popup.setCheckState(Qt.Checked) if self.notification_tray: checkBox_notification.setCheckState(Qt.Checked) if self.notification_sound: checkBox_sound.setCheckState(Qt.Checked) innerLayout.addWidget(checkBox_popup) innerLayout.addWidget(checkBox_notification) innerLayout.addWidget(checkBox_sound) innerLayout.addStretch(1) GroupBox.setLayout(innerLayout) button = QPushButton("Ok") button.clicked.connect(lambda: self.changeNotificationSettings( popup, GroupBox, checkBox_popup, checkBox_notification, checkBox_sound)) outerLayout = QVBoxLayout() outerLayout.addWidget(GroupBox) outerLayout.addWidget(button) popup.setLayout(outerLayout) popup.exec_() def changeNotificationSettings(self, popup, GroupBox, checkBox_popup, checkBox_notification, checkBox_sound): self.notification, self.notification_popup, self.notification_tray, self.notification_sound = GroupBox.isChecked( ), checkBox_popup.isChecked(), checkBox_notification.isChecked( ), checkBox_sound.isChecked() if not any([ self.notification_popup, self.notification_tray, self.notification_sound ]): self.notification = False popup.close() def createDateDialog(self): popup = QDialog(self) popup.setFixedSize(270, 60) popup.setWindowTitle("Nouvelle date") layout = QHBoxLayout() prefix = QLabel("Heure cible: ") layout.addWidget(prefix) qline = QTimeEdit() qline.setDisplayFormat("hh:mm:ss") qline.setTime(QTime.currentTime()) layout.addWidget(qline) button = QPushButton("Ok") button.clicked.connect(lambda: self.createDate(popup, qline.time().hour(), qline.time().minute(), qline.time().second())) layout.addWidget(button) popup.setLayout(layout) popup.exec_() def createDurationDialog(self): popup = QDialog(self) popup.setFixedSize(150, 150) popup.setWindowTitle("Nouvelle durée") layout = QVBoxLayout() hourLayout = QHBoxLayout() hourLabel = QLabel("Heures:") hourSpin = QSpinBox() hourLayout.addWidget(hourLabel) hourLayout.addWidget(hourSpin) minuteLayout = QHBoxLayout() minuteLabel = QLabel("Minutes:") minuteSpin = QSpinBox() minuteLayout.addWidget(minuteLabel) minuteLayout.addWidget(minuteSpin) secondLayout = QHBoxLayout() secondLabel = QLabel("Secondes:") secondSpin = QSpinBox() secondLayout.addWidget(secondLabel) secondLayout.addWidget(secondSpin) layout.addLayout(hourLayout) layout.addLayout(minuteLayout) layout.addLayout(secondLayout) button = QPushButton("Ok") button.clicked.connect(lambda: self.createDuration( popup, hourSpin.value(), minuteSpin.value(), secondSpin.value())) layout.addWidget(button) popup.setLayout(layout) popup.exec_() def createDuration(self, popup: QDialog, hours: int, minutes: int, seconds: int): popup.close() self.begin_time = datetime.timestamp(datetime.now()) self.end_time = self.begin_time + seconds + minutes * 60 + hours * 3600 self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.isRunning = True self.timer.stop() self.timer.start(self.refresh_rate) def createDate(self, popup: QDialog, hours: int, minutes: int, seconds: int): popup.close() self.begin_time = datetime.timestamp(datetime.now()) now = datetime.now().time() target = time(hours, minutes, seconds) now_delta = timedelta(hours=now.hour, minutes=now.minute, seconds=now.second) target_delta = timedelta(hours=target.hour, minutes=target.minute, seconds=target.second) if target_delta == now_delta: self.end_time = self.begin_time + 60 * 60 * 24 else: d = target_delta - now_delta self.end_time = self.begin_time + d.seconds self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.isRunning = True self.timer.stop() self.timer.start(self.refresh_rate) def tick(self): self.progressBar.setValue( 100 * (datetime.timestamp(datetime.now()) - self.begin_time) / (self.end_time - self.begin_time)) seconds = int( ceil(self.end_time - datetime.timestamp(datetime.now())) % 60) minutes = int( ceil(self.end_time - datetime.timestamp(datetime.now())) / 60 % 60) hours = int( ceil(self.end_time - datetime.timestamp(datetime.now())) / 3600) self.label.setText(f'{hours:02}:{minutes:02}:{seconds:02}') self.setWindowTitle(f'{TITLE} - {hours:02}:{minutes:02}:{seconds:02}') self.tray.setToolTip(f'{hours:02}:{minutes:02}:{seconds:02}') if datetime.timestamp(datetime.now()) >= self.end_time: self.isRunning = False self.timer.stop() self.progressBar.setRange(0, 0) self.show() self.notify() def notify(self): if not self.notification: return if self.notification_tray: self.tray.showMessage( "Finished", "Le décompte est terminé", self.style().standardIcon(QStyle.SP_MessageBoxInformation)) if self.notification_sound: test = QMediaPlayer() test.setMedia(QUrl.fromLocalFile(self.notification_soundfile)) test.play() if self.notification_popup: QMessageBox.information(self, "Finished", "Le décompte est terminé") def pause(self): if not self.isRunning: return self.progressBar.setDisabled(self.timer.isActive()) if self.timer.isActive(): self.end_delay = self.end_time - datetime.timestamp(datetime.now()) self.begin_delay = datetime.timestamp( datetime.now()) - self.begin_time print(self.begin_time) print(self.end_time) print(self.end_delay) self.statusBar.showMessage("Pause") self.tray.setToolTip(self.tray.toolTip() + ' - Pause') self.timer.stop() self.button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) else: self.begin_time = datetime.timestamp( datetime.now()) - self.begin_delay self.end_time = datetime.timestamp(datetime.now()) + self.end_delay print(self.begin_time) print(self.end_time) self.statusBar.clearMessage() self.timer.start() self.button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) # Override def closeEvent(self, event): self.hide() event.ignore()
class MainWindow(QMainWindow): """Main Window of the application""" def __init__(self): super().__init__() self.schedule_object = None self.init_ui() self.setStyleSheet(WINDOW_STYLE) self.setWindowTitle("NRC a iCalendar") self.setWindowIcon(QIcon(get_path("assets", "icon.svg"))) self.clikboard = QClipboard() # Dialogo para guardar el archivo self.save_dialog = QFileDialog(self) self.save_dialog.setFileMode(QFileDialog.AnyFile) self.save_dialog.setNameFilter("iCalendar (*.ics)") self.save_dialog.setDefaultSuffix("ics") self.save_dialog.setAcceptMode(QFileDialog.AcceptSave) def init_ui(self): """Makes the layout""" # Barra de opciones y links de interés menu_bar = self.menuBar() options_menu = menu_bar.addMenu("&Opciones") act_allways_visible = options_menu.addAction("Siempre visible") act_allways_visible.setCheckable(True) act_allways_visible.toggled.connect(self.__allways_visible) uc_calendars_menu = menu_bar.addMenu("&Calendarios") for name, link in OTHER_CALENDARS: calendar_option = uc_calendars_menu.addAction(name) # TODO: Ni idea pq se necesita tener una variable `s`, sin esta no funciona calendar_option.triggered.connect( lambda s=None, l=link: self.__to_clipboard(l)) go_to_menu = menu_bar.addMenu("&Ir a") go_to_options = [ ("Feed del calendario de Canvas", "https://cursos.canvas.uc.cl/calendar"), ( "Importar calendario a Google", "https://calendar.google.com/calendar/a/uc.cl/r/settings/export", ), ] for name, link in go_to_options: new_option = go_to_menu.addAction(name) new_option.triggered.connect( lambda s=None, l=link: webbrowser.open(l)) # Main widget main_widget = QFrame() self.setCentralWidget(main_widget) main_layout = QVBoxLayout(main_widget) main_widget.setLayout(main_layout) main_layout.setSizeConstraint(QLayout.SetMinimumSize) # Lista de códigos a ingresar code_layout = QHBoxLayout() main_layout.addLayout(code_layout) self.code_list = [QLineEdit(main_widget) for i in range(6)] code_validator = QIntValidator(main_layout, 10**4, 10**5) for code in self.code_list: code.setObjectName("code_field") code.setAlignment(Qt.AlignCenter) code.setMaxLength(5) code.setValidator(code_validator) code.textEdited.connect(self.check_codes) code_layout.addWidget(code) self.get_button = QPushButton("Obtener horario", main_widget) self.get_button.clicked.connect(self.get_schedule) self.get_button.setCursor(Qt.PointingHandCursor) self.get_button.setDisabled(True) main_layout.addWidget(self.get_button) self.schedule_view = ScheduleView(8, 6, main_widget) main_layout.addWidget(self.schedule_view) self.save_button = QPushButton("Guardar horario", main_widget) self.save_button.clicked.connect(self.save_schedule) self.save_button.setCursor(Qt.PointingHandCursor) self.save_button.setDisabled(True) main_layout.addWidget(self.save_button) self.status_bar = QStatusBar(self) self.status_bar.showMessage("Ingrese los códigos NRC") self.setStatusBar(self.status_bar) self.adjustSize() def __allways_visible(self, option): flags = self.windowFlags() if option: self.setWindowFlags(flags | Qt.WindowStaysOnTopHint) else: self.setWindowFlags(flags ^ Qt.WindowStaysOnTopHint) self.show() def __to_clipboard(self, link): self.clikboard.setText(link) self.status_bar.showMessage( "URL del calendario copiado a portapapeles") def check_codes(self): """Check if the codes are valid""" at_least_one_valid = False for code in self.code_list: if valid_nrc(code.text()): at_least_one_valid = True elif code.text(): # TODO: cambiar el estilo al ser invalido y tener texto pass else: pass self.get_button.setDisabled(not at_least_one_valid) if at_least_one_valid: self.status_bar.clearMessage() else: self.status_bar.showMessage("Ingrese los códigos NRC") def get_schedule(self): """Get the schedule of Buscacursos UC""" valid_codes = list( filter(valid_nrc, map(QLineEdit.text, self.code_list))) if not valid_codes: return try: self.schedule_object = Schedule.get(valid_codes) except OSError: error_box = QMessageBox(QMessageBox.Critical, "Error", "No se ha podido importar el horario") error_box.exec_() else: self.show_schedule() def show_schedule(self): """Show the schedule in the table""" # Limpia el horario self.schedule_view.clearContents() # Si no hay módulos, se deshabilita la opción de guardar y termina if not self.schedule_object.courses: self.schedule_view.update_size() self.save_button.setDisabled(True) return # Si existen módulos, se muestran en el horario for i_row, row in enumerate(self.schedule_object.get_table()): for sub_row in row: for i_col, element in enumerate(sub_row): if not element: continue module_label = QLabel(self.schedule_view) module_label.setText(element.code) module_label.setObjectName(element.type_) module_label.setAlignment(Qt.AlignCenter) module_label.setFixedHeight(20) cell_frame = self.schedule_view.cellWidget(i_row, i_col) # Se crea un frame para los widgets si no existe if not cell_frame: cell_frame = QFrame(self.schedule_view) cell_layout = QVBoxLayout(cell_frame) cell_layout.setSpacing(0) cell_layout.setMargin(0) self.schedule_view.setCellWidget( i_row, i_col, cell_frame) cell_frame.layout().addWidget(module_label) self.schedule_view.update_size() self.save_button.setDisabled(False) def save_schedule(self): """Saves the schedule""" if self.save_dialog.exec_(): out_dir = self.save_dialog.selectedFiles()[0] with open(out_dir, mode="w", encoding="utf-8") as file: file.write(self.schedule_object.to_ics())