diff --git a/.github/workflows/bundle.yml b/.github/workflows/bundle.yml index 2b301bb..b2531f6 100644 --- a/.github/workflows/bundle.yml +++ b/.github/workflows/bundle.yml @@ -43,7 +43,7 @@ jobs: - name: Setup python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.12' cache: 'pip' - name: Download external resources (Ubuntu & macOS) diff --git a/inlinino/__init__.py b/inlinino/__init__.py index 0992608..098d71b 100644 --- a/inlinino/__init__.py +++ b/inlinino/__init__.py @@ -12,7 +12,7 @@ # Setup Logger logging.basicConfig(level=logging.DEBUG) -logging.getLogger('PyQt5').setLevel(logging.WARNING) +logging.getLogger('PyQt6').setLevel(logging.WARNING) root_logger = logging.getLogger() # Get root logger diff --git a/inlinino/gui.py b/inlinino/gui.py index 1823b9b..2bccc3e 100644 --- a/inlinino/gui.py +++ b/inlinino/gui.py @@ -12,7 +12,7 @@ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore, QtWidgets, uic -from PyQt5 import QtMultimedia +from PyQt6 import QtMultimedia from pyACS.acs import ACS as ACSParser import pySatlantic.instrument as pySat @@ -50,7 +50,7 @@ def seconds_to_strmmss(seconds): return '%d:%02d' % (min, sec) -class MainWindow(QtGui.QMainWindow): +class MainWindow(QtWidgets.QMainWindow): BACKGROUND_COLOR = '#F8F8F2' FOREGROUND_COLOR = '#26292C' PEN_COLORS = COLOR_SET @@ -61,13 +61,13 @@ def __init__(self, instrument=None): super(MainWindow, self).__init__() uic.loadUi(os.path.join(PATH_TO_RESOURCES, 'main.ui'), self) # Graphical Adjustments - self.dock_widget_primary.setTitleBarWidget(QtGui.QWidget(None)) - self.dock_widget_secondary.setTitleBarWidget(QtGui.QWidget(None)) + self.dock_widget_primary.setTitleBarWidget(QtWidgets.QWidget(None)) + self.dock_widget_secondary.setTitleBarWidget(QtWidgets.QWidget(None)) self.label_app_version.setText('Inlinino v' + __version__) # Set Colors palette = QtGui.QPalette() - palette.setColor(palette.Window, QtGui.QColor(self.BACKGROUND_COLOR)) # Background - palette.setColor(palette.WindowText, QtGui.QColor(self.FOREGROUND_COLOR)) # Foreground + palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor(self.BACKGROUND_COLOR)) # Background + palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor(self.FOREGROUND_COLOR)) # Foreground self.setPalette(palette) pg.setConfigOption('background', pg.mkColor(self.BACKGROUND_COLOR)) pg.setConfigOption('foreground', pg.mkColor(self.FOREGROUND_COLOR)) @@ -172,11 +172,11 @@ def init_instrument(self, instrument): # Add vertical spacer to docks if primary_vertical_spacer: self.docked_widget_primary_layout.addItem( - QtGui.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) ) if secondary_vertical_spacer: self.docked_widget_secondary_layout.addItem( - QtGui.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.MinimumExpanding) ) # Set secondary dock self.toggle_secondary_dock(init=True) @@ -254,7 +254,7 @@ def act_instrument_setup(self): logger.debug('Setup instrument') setup_dialog = DialogInstrumentUpdate(self.instrument.uuid, self) setup_dialog.show() - if setup_dialog.exec_(): + if setup_dialog.exec(): self.instrument.setup(setup_dialog.cfg) self.label_instrument_name.setText(self.instrument.short_name) # Set Interface Name @@ -276,10 +276,10 @@ def act_instrument_setup(self): def act_instrument_interface(self): def error_dialog(): logger.warning(e) - QtGui.QMessageBox.warning(self, "Inlinino: Connect " + self.instrument.name, - 'ERROR: Failed connecting ' + self.instrument.name + '. ' + - str(e), - QtGui.QMessageBox.Ok) + QtWidgets.QMessageBox.warning(self, "Inlinino: Connect " + self.instrument.name, + 'ERROR: Failed connecting ' + self.instrument.name + '. ' + + str(e), + QtWidgets.QMessageBox.StandardButton.Ok) if self.instrument.alive: logger.debug('Disconnect instrument') self.instrument.close() @@ -287,7 +287,7 @@ def error_dialog(): if issubclass(type(self.instrument._interface), SerialInterface): dialog = DialogSerialConnection(self) dialog.show() - if dialog.exec_(): + if dialog.exec(): try: self.instrument.open(port=dialog.port, baudrate=dialog.baudrate, bytesize=dialog.bytesize, parity=dialog.parity, stopbits=dialog.stopbits, timeout=dialog.timeout) @@ -302,7 +302,7 @@ def error_dialog(): elif issubclass(type(self.instrument._interface), SocketInterface): dialog = DialogSocketConnection(self) dialog.show() - if dialog.exec_(): + if dialog.exec(): try: self.instrument.open(ip=dialog.ip, port=dialog.port) # Save connection parameters for next time @@ -332,7 +332,7 @@ def act_instrument_log(self): else: dialog = DialogLoggerOptions(self) dialog.show() - if dialog.exec_(): + if dialog.exec(): self.instrument.log_update_cfg({'filename_prefix': dialog.cover_log_prefix + self.instrument.bare_log_prefix, 'path': dialog.log_path}) @@ -529,16 +529,16 @@ def on_custom_alarm(self, text, info_text): self.alarm_message_box.show(text, info_text, sound=False) def closeEvent(self, event): - icon, txt = QtGui.QMessageBox.Question, "Are you sure you want to exit?" + icon, txt = QtWidgets.QMessageBox.Icon.Question, "Are you sure you want to exit?" if self.instrument.widget_hypernav_cal_enabled: sbs_txt = self.instrument.check_sbs_sn() if sbs_txt: - icon, txt = QtGui.QMessageBox.Warning, txt + "\nDo you want to exit anyway?" - msg = QtGui.QMessageBox(icon, "Inlinino: Closing Application", txt, - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, self) - msg.setWindowModality(QtCore.Qt.WindowModal) - if msg.exec_() == QtGui.QMessageBox.Yes: - QtGui.QApplication.instance().closeAllWindows() # NEEDED IF OTHER WINDOWS OPEN BY SPECIFIC INSTRUMENTS + icon, txt = QtWidgets.QMessageBox.Icon.Warning, txt + "\nDo you want to exit anyway?" + msg = QtWidgets.QMessageBox(icon, "Inlinino: Closing Application", txt, + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, self) + msg.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + if msg.exec() == QtWidgets.QMessageBox.StandardButton.Yes: + QtWidgets.QApplication.instance().closeAllWindows() # NEEDED IF OTHER WINDOWS OPEN BY SPECIFIC INSTRUMENTS event.accept() else: event.ignore() @@ -552,22 +552,21 @@ class MessageBoxAlarm(QtWidgets.QMessageBox): " + Is the instruments configured properly (e.g. automatically send data)?\n" def __init__(self, parent): - super().__init__(QtWidgets.QMessageBox.Warning, "Inlinino: Data Timeout Alarm", - self.TEXT, QtWidgets.QMessageBox.Ignore, parent) + super().__init__(QtWidgets.QMessageBox.Icon.Warning, "Inlinino: Data Timeout Alarm", + self.TEXT, QtWidgets.QMessageBox.StandardButton.Ignore, parent) self.setInformativeText(self.INFO_TEXT) - self.setWindowModality(QtCore.Qt.WindowModal) + self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) self.active = False self.buttonClicked.connect(self.ignore) # Setup Sound - self.alarm_sound = QtMultimedia.QMediaPlayer() - self.alarm_playlist = QtMultimedia.QMediaPlaylist(self.alarm_sound) - for file in sorted(glob.glob(os.path.join(PATH_TO_RESOURCES, 'alarm*.wav'))): - self.alarm_playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file))) - if self.alarm_playlist.mediaCount() < 1: + self.alarm_sound = QtMultimedia.QSoundEffect() + alarm_files = sorted(glob.glob(os.path.join(PATH_TO_RESOURCES, 'alarm*.wav'))) + if alarm_files: + self.alarm_sound.setSource(QtCore.QUrl.fromLocalFile(alarm_files[0])) + self.alarm_sound.setLoopCount(-1) # -1 = infinite loop + else: logger.warning('No alarm sounds available: disabled alarm') - self.alarm_playlist.setPlaybackMode(QtMultimedia.QMediaPlaylist.Loop) # Playlist is needed for infinite loop - self.alarm_sound.setPlaylist(self.alarm_playlist) def show(self, txt: str = None, info_txt: str = None, sound: bool = True): if not self.active: @@ -575,7 +574,6 @@ def show(self, txt: str = None, info_txt: str = None, sound: bool = True): self.setInformativeText(self.INFO_TEXT if info_txt is None else info_txt) super().show() if sound: - self.alarm_playlist.setCurrentIndex(0) self.alarm_sound.play() self.active = True @@ -590,7 +588,7 @@ def ignore(self): self.hide() -class DialogStartUp(QtGui.QDialog): +class DialogStartUp(QtWidgets.QDialog): LOAD_INSTRUMENT = 1 SETUP_INSTRUMENT = 2 @@ -622,17 +620,17 @@ def act_delete_instrument(self): index = self.combo_box_instrument_to_delete.currentIndex() uuid = self.instrument_uuids[index] instrument = self.combo_box_instrument_to_delete.currentText() - msg = QtGui.QMessageBox(QtWidgets.QMessageBox.Warning, f"Inlinino: Delete {instrument}", - f"Are you sure to delete instrument: {instrument} ?", - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, self) - msg.setWindowModality(QtCore.Qt.WindowModal) - if msg.exec_() == QtGui.QMessageBox.Yes: + msg = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Warning, f"Inlinino: Delete {instrument}", + f"Are you sure to delete instrument: {instrument} ?", + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, self) + msg.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + if msg.exec() == QtWidgets.QMessageBox.StandardButton.Yes: CFG.read() if uuid not in CFG.instruments.keys(): txt = f"Failed to delete instrument [{uuid}] {instrument}, " \ f"configuration was updated by another instance of Inlinino." logger.warning(txt) - QtGui.QMessageBox.warning(self, "Inlinino: Configuration Error", txt, QtGui.QMessageBox.Ok) + QtWidgets.QMessageBox.warning(self, "Inlinino: Configuration Error", txt, QtWidgets.QMessageBox.StandardButton.Ok) return del CFG.instruments[uuid] CFG.write() @@ -642,7 +640,7 @@ def act_delete_instrument(self): logger.warning(f"Deleted instrument [{uuid}] {instrument}") -class DialogInstrumentSetup(QtGui.QDialog): +class DialogInstrumentSetup(QtWidgets.QDialog): ENCODING = 'ascii' OPTIONAL_FIELDS = ['Variable Precision', 'Prefix Custom'] ADU100_AN01_GAIN2RANGE = {0: '2.5V', 1: '1.25V', 2: '0.625V', 3: '0.312V', 4: '0.156V', 5: '78.12mV', 6: '39.06mV', @@ -690,27 +688,27 @@ def connect_backend(self): self.combobox_relay2_mode.currentIndexChanged.connect(self.act_adu_update_relay3_available) # Cannot use default save button as does not provide mean to correctly validate user input - self.button_save = QtGui.QPushButton('Save') + self.button_save = QtWidgets.QPushButton('Save') self.button_save.setDefault(True) self.button_save.clicked.connect(self.act_save) - self.button_box.addButton(self.button_save, QtGui.QDialogButtonBox.ActionRole) + self.button_box.addButton(self.button_save, QtWidgets.QDialogButtonBox.ButtonRole.ActionRole) self.button_box.rejected.connect(self.reject) def act_browse_log_directory(self): - self.le_log_path.setText(QtGui.QFileDialog.getExistingDirectory(caption='Choose logging directory')) + self.le_log_path.setText(QtWidgets.QFileDialog.getExistingDirectory(caption='Choose logging directory')) def act_browse_device_file(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose device file', filter='Device File (*.dev *.txt)') self.le_device_file.setText(file_name) def act_browse_calibration_file(self): # Specific to Suna and HydroScat - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose calibration file', filter='Calibration File (*.cal *.CAL)') self.le_calibration_file.setText(file_name) def act_browse_tdf_files(self): # sip, cal, or tdf - file_names, selected_filter = QtGui.QFileDialog.getOpenFileNames( + file_names, selected_filter = QtWidgets.QFileDialog.getOpenFileNames( caption='Choose calibration file', filter='Calibration File (*.cal *.CAL *.tdf *.TDF *.sip)') # Check if sip file is_sip = False @@ -724,9 +722,9 @@ def act_browse_tdf_files(self): # sip, cal, or tdf # Empty current files for immersed selection for i in reversed(range(self.scroll_area_layout_immersed.count())): item = self.scroll_area_layout_immersed.itemAt(i) - if type(item) == QtGui.QWidgetItem: + if type(item) == QtWidgets.QWidgetItem: item.widget().setParent(None) - elif type(item) == QtGui.QLayoutItem: + elif type(item) == QtWidgets.QLayoutItem: item.layout().setParent(None) # Update selection of immersed files if is_sip: @@ -739,41 +737,41 @@ def act_browse_tdf_files(self): # sip, cal, or tdf for f in file_names: self.scroll_area_layout_immersed.addWidget(QtWidgets.QCheckBox(os.path.basename(f))) self.scroll_area_layout_immersed.addItem( - QtGui.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)) + QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)) def act_browse_ini_file(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose initialization file', filter='Ini File (*.ini)') self.le_ini_file.setText(file_name) def act_browse_dcal_file(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose DCAL file', filter='DCAL File (*.asc)') self.le_dcal_file.setText(file_name) def act_browse_zsc_file(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose ZSC file', filter='ZSC File (*.asc)') self.le_zsc_file.setText(file_name) def act_browse_plaque_file(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose plaque calibration file', filter='Plaque File (*.mat)') self.le_plaque_file.setText(file_name) def act_browse_temperature_file(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose temperature calibration file', filter='Temperature File (*.mat)') self.le_temperature_file.setText(file_name) def act_browse_px_reg_prt(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose port side pixel registration file', filter='Registration File (*.cgs *.cal *.tdf *.CGS *.CAL *.TDF)') self.le_optional_px_reg_path_prt.setText(file_name) def act_browse_px_reg_sbd(self): - file_name, selected_filter = QtGui.QFileDialog.getOpenFileName( + file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName( caption='Choose starboard pixel registration file', filter='Registration File (*.cgs *.cal *.tdf *.CGS *.CAL *.TDF)') self.le_optional_px_reg_path_sbd.setText(file_name) @@ -1035,7 +1033,7 @@ def act_save(self): self.cfg['immersed'] = [] for i in range(self.scroll_area_layout_immersed.count()): item = self.scroll_area_layout_immersed.itemAt(i) - if type(item) == QtGui.QWidgetItem: + if type(item) == QtWidgets.QWidgetItem: self.cfg['immersed'].append(bool(item.widget().checkState())) elif self.cfg['module'] == 'hypernav': try: @@ -1114,13 +1112,13 @@ def check_variables_pass(self): return True def notification(self, message, details=None): - msg = QtGui.QMessageBox(QtWidgets.QMessageBox.Warning, "Inlinino: Setup Instrument Warning", - message, - QtGui.QMessageBox.Ok, self) + msg = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Warning, "Inlinino: Setup Instrument Warning", + message, + QtWidgets.QMessageBox.StandardButton.Ok, self) if details: msg.setDetailedText(str(details)) - msg.setWindowModality(QtCore.Qt.WindowModal) - msg.exec_() + msg.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + msg.exec() class DialogInstrumentCreate(DialogInstrumentSetup): @@ -1145,9 +1143,9 @@ def __init__(self, uuid, parent=None): # Check if instrument exists if uuid not in CFG.instruments.keys(): logger.warning('Instrument was deleted.') - QtGui.QMessageBox.warning(self, "Inlinino: Configuration Error", - 'ERROR: Instrument was deleted.', - QtGui.QMessageBox.Ok) + QtWidgets.QMessageBox.warning(self, "Inlinino: Configuration Error", + 'ERROR: Instrument was deleted.', + QtWidgets.QMessageBox.StandardButton.Ok) self.cancel() # Load from preconfigured instrument self.cfg_uuid = uuid @@ -1266,21 +1264,21 @@ def __init__(self, uuid, parent=None): widget.setChecked(True) self.scroll_area_layout_immersed.addWidget(widget) self.scroll_area_layout_immersed.addItem( - QtGui.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)) + QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)) else: self.tdf_files = [] # Connect Buttons self.connect_backend() -class DialogSerialConnection(QtGui.QDialog): +class DialogSerialConnection(QtWidgets.QDialog): def __init__(self, parent): super().__init__(parent) uic.loadUi(os.path.join(PATH_TO_RESOURCES, 'serial_connection.ui'), self) instrument = parent.instrument # Connect buttons - self.button_box.button(QtGui.QDialogButtonBox.Open).clicked.connect(self.accept) - self.button_box.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.reject) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Open).clicked.connect(self.accept) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.reject) # Update ports list self.ports = list_serial_comports() # self.ports.append(type('obj', (object,), {'device': '/dev/ttys004', 'product': 'macOS Virtual Serial', 'description': 'n/a'})) # Debug macOS serial @@ -1377,7 +1375,7 @@ def timeout(self) -> float: return self.sb_timeout.value() -class DialogSocketConnection(QtGui.QDialog): +class DialogSocketConnection(QtWidgets.QDialog): def __init__(self, parent): super().__init__(parent) instrument = parent.instrument @@ -1393,8 +1391,8 @@ def __init__(self, parent): port = CFG.interfaces[instrument.uuid]['socket_port'] self.sb_port.setValue(port) # Connect buttons - self.button_box.button(QtGui.QDialogButtonBox.Open).clicked.connect(self.accept) - self.button_box.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.reject) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Open).clicked.connect(self.accept) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.reject) @property def ip(self) -> str: @@ -1409,7 +1407,7 @@ def port(self) -> int: # return int(self.sb_timeout.value()) -class DialogLoggerOptions(QtGui.QDialog): +class DialogLoggerOptions(QtWidgets.QDialog): def __init__(self, parent): super().__init__(parent) #, QtCore.Qt.WindowStaysOnTopHint uic.loadUi(os.path.join(PATH_TO_RESOURCES, 'logger_options.ui'), self) @@ -1425,9 +1423,9 @@ def __init__(self, parent): self.cb_prefix_dark.toggled.connect(self.update_filename_template) self.cb_prefix_custom.toggled.connect(self.update_filename_template) # Connect buttons - self.button_box.button(QtGui.QDialogButtonBox.Save).setDefault(True) - self.button_box.button(QtGui.QDialogButtonBox.Save).clicked.connect(self.accept) - self.button_box.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.reject) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Save).setDefault(True) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Save).clicked.connect(self.accept) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.reject) @property def cover_log_prefix(self) -> str: @@ -1456,7 +1454,7 @@ def log_path(self) -> str: return self.le_log_path.text() def act_browse_log_directory(self): - self.le_log_path.setText(QtGui.QFileDialog.getExistingDirectory(caption='Choose logging directory', + self.le_log_path.setText(QtWidgets.QFileDialog.getExistingDirectory(caption='Choose logging directory', directory=self.le_log_path.text())) self.show() @@ -1466,10 +1464,10 @@ def update_filename_template(self): '_YYYYMMDD_hhmmss.' + self.instrument.log_get_file_ext()) -class App(QtGui.QApplication): +class App(QtWidgets.QApplication): def __init__(self, *args): - QtGui.QApplication.__init__(self, *args) - self.splash_screen = QtGui.QSplashScreen(QtGui.QPixmap(os.path.join(PATH_TO_RESOURCES, 'inlinino.ico'))) + QtWidgets.QApplication.__init__(self, *args) + self.splash_screen = QtWidgets.QSplashScreen(QtGui.QPixmap(os.path.join(PATH_TO_RESOURCES, 'inlinino.ico'))) self.splash_screen.show() self.setWindowIcon(QtGui.QIcon(os.path.join(PATH_TO_RESOURCES, 'inlinino.ico'))) self.main_window = MainWindow() @@ -1485,13 +1483,13 @@ def start(self, instrument_index=None): else: logger.debug('Startup Dialog') self.startup_dialog.show() - act = self.startup_dialog.exec_() + act = self.startup_dialog.exec() if act == self.startup_dialog.LOAD_INSTRUMENT: instrument_uuid = self.startup_dialog.selected_uuid elif act == self.startup_dialog.SETUP_INSTRUMENT: setup_dialog = DialogInstrumentCreate(self.startup_dialog.selected_template) setup_dialog.show() - if setup_dialog.exec_(): + if setup_dialog.exec(): instrument_uuid = setup_dialog.cfg_uuid else: logger.info('Setup closed') @@ -1531,11 +1529,11 @@ def start(self, instrument_index=None): setup_dialog = DialogInstrumentUpdate(instrument_uuid) setup_dialog.show() setup_dialog.notification('Unable to load instrument. Please check configuration.', e) - if setup_dialog.exec_(): + if setup_dialog.exec(): logger.info('Updated configuration') else: logger.info('Setup closed') self.start() # Restart application to go back to startup screen # Start Main Window self.main_window.show() - sys.exit(self.exec_()) + sys.exit(self.exec()) diff --git a/inlinino/widgets/__init__.py b/inlinino/widgets/__init__.py index 61e5bff..f20a8b1 100644 --- a/inlinino/widgets/__init__.py +++ b/inlinino/widgets/__init__.py @@ -49,11 +49,11 @@ def __snake_name__(cls) -> str: def __init__(self, parent=None, *args, **kwargs): super().__init__(parent, *args, **kwargs) if parent is not None and parent.isActiveWindow(): - self.setWindowModality(QtCore.Qt.WindowModal) + self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) uic.loadUi(os.path.join(PATH_TO_RESOURCES, f'dialog_{self.__snake_name__[:-7]}.ui'), self) - self.run_button = self.button_box.addButton("Run", QtGui.QDialogButtonBox.ActionRole) + self.run_button = self.button_box.addButton("Run", QtWidgets.QDialogButtonBox.ButtonRole.ActionRole) self.run_button.clicked.connect(self.start) - self.button_box.button(QtGui.QDialogButtonBox.Close).clicked.connect(self.accept) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Close).clicked.connect(self.accept) def disable_run_button(self, text='Processing ...'): self.run_button.setText(text) diff --git a/inlinino/widgets/aux_data.py b/inlinino/widgets/aux_data.py index f3feccc..b5b2096 100644 --- a/inlinino/widgets/aux_data.py +++ b/inlinino/widgets/aux_data.py @@ -1,4 +1,4 @@ -from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets from inlinino.widgets import GenericWidget @@ -19,8 +19,8 @@ def setup(self): self.group_box_aux_data_layout.itemAt(i).widget().setParent(None) # Set aux variable names for v in self.instrument.widget_aux_data_variable_names: - self.variable_names.append(QtGui.QLabel(v)) - self.variable_values.append(QtGui.QLabel('?')) + self.variable_names.append(QtWidgets.QLabel(v)) + self.variable_values.append(QtWidgets.QLabel('?')) self.group_box_aux_data_layout.addRow(self.variable_names[-1], self.variable_values[-1]) diff --git a/inlinino/widgets/file_explorer.py b/inlinino/widgets/file_explorer.py index 6dbeb0f..6bd50a7 100644 --- a/inlinino/widgets/file_explorer.py +++ b/inlinino/widgets/file_explorer.py @@ -69,20 +69,19 @@ def handle_selection(self, selected, deselected): def download(self): items = [i.internalPointer() for i in self.tree_view.selectedIndexes() if i.column() == 0] if len(items) < 1: - msg = QtGui.QMessageBox(QtWidgets.QMessageBox.Warning, "Download File(s)", - f"Nothing to download, please select files to download first.", - QtGui.QMessageBox.Close, self) - msg.setWindowModality(QtCore.Qt.WindowModal) - msg.exec_() + msg = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Warning, "Download File(s)", + f"Nothing to download, please select files to download first.", + QtWidgets.QMessageBox.StandardButton.Close, self) + msg.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + msg.exec() return - msg = QtGui.QMessageBox(QtWidgets.QMessageBox.Question, "Download File(s)", - f"Are you sure you want to download the {len(items)} file(s) selected?", - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, self) - msg.setWindowModality(QtCore.Qt.WindowModal) - if msg.exec_() == QtGui.QMessageBox.Yes: + msg = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Question, "Download File(s)", + f"Are you sure you want to download the {len(items)} file(s) selected?", + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, self) + msg.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + if msg.exec() == QtWidgets.QMessageBox.StandardButton.Yes: dialog = DialogDownloadFiles(self, items) - status = dialog.exec_() - if not dialog.exec_(): # 0: reject | 1: accept + if not dialog.exec(): # 0: reject | 1: accept # User cancelled self.instrument.signal.warning.emit( 'Data download cancelled. HyperNav is still transmitting data, ' @@ -94,10 +93,10 @@ def download(self): # raise NotImplementedError('Not Implemented.') -class DialogDownloadFiles(QtGui.QDialog): +class DialogDownloadFiles(QtWidgets.QDialog): def __init__(self, parent, items): # p = parent - # while not isinstance(p, QtGui.QMainWindow): + # while not isinstance(p, QtWidgets.QMainWindow): # p = p.parent() super().__init__(parent) uic.loadUi(os.path.join(PATH_TO_RESOURCES, 'dialog_download_files.ui'), self) @@ -105,15 +104,16 @@ def __init__(self, parent, items): self.items = items self.idx = 0 - self.button_box.button(QtGui.QDialogButtonBox.Close).clicked.connect(self.accept) - self.button_box.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.reject) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Close).clicked.connect(self.accept) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.reject) self.instrument.signal.cmd_list.connect(self.expand_folder) self.instrument.signal.cmd_dump.connect(self.next) - self.button_box.button(QtGui.QDialogButtonBox.Close).setEnabled(False) + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Close).setEnabled(False) # Prevent computer to sleep - wakepy.set_keepawake(keep_screen_awake=False) + self._wakepy_mode = wakepy.keep.running() + self._wakepy_mode.__enter__() # Start downloading files self.next() @@ -124,7 +124,7 @@ def __init__(self, parent, items): @QtCore.pyqtSlot(int) def next(self, status: int = None): - self.view.moveCursor(QtGui.QTextCursor.End) + self.view.moveCursor(QtGui.QTextCursor.MoveOperation.End) if status == -1: # Folder expanded self.view.insertPlainText(' Done\n') elif status == -2: # Error while downloading file @@ -142,14 +142,14 @@ def next(self, status: int = None): self.instrument.send_cmd(f'dump * {path}', check_timing=False) self.view.insertPlainText(f'Downloading {path} ... ') self.idx += 1 - self.view.moveCursor(QtGui.QTextCursor.StartOfLine) + self.view.moveCursor(QtGui.QTextCursor.MoveOperation.StartOfLine) else: if self.idx != 0: self.view.insertPlainText('All files downloaded.\n') else: self.view.insertPlainText('Nothing to download.\n') - self.button_box.button(QtGui.QDialogButtonBox.Close).setEnabled(True) - wakepy.unset_keepawake() + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Close).setEnabled(True) + self._wakepy_mode.__exit__(None, None, None) @QtCore.pyqtSlot() def expand_folder(self): @@ -198,7 +198,7 @@ def data(self, index: QtCore.QModelIndex, role: int): if not index.isValid(): return None item = index.internalPointer() - if role == QtCore.Qt.DisplayRole: + if role == QtCore.Qt.ItemDataRole.DisplayRole: return item.data(index.column()) return None @@ -206,17 +206,17 @@ def data(self, index: QtCore.QModelIndex, role: int): class QRemoteFileSystemModel(QItemModel): def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int): - if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole and section < len(QFileItem.HEADER): + if orientation == QtCore.Qt.Orientation.Horizontal and role == QtCore.Qt.ItemDataRole.DisplayRole and section < len(QFileItem.HEADER): return QFileItem.HEADER[section] return super().headerData(section, orientation, role) - def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags: + def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag: # if index.column() == 0: - # return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable + # return QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsUserCheckable if index.internalPointer().is_dir and index.internalPointer().childCount() != 0: - return QtCore.Qt.ItemIsEnabled + return QtCore.Qt.ItemFlag.ItemIsEnabled else: - return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + return QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable # Methods from QtWidgets.QFileSystemModel # def fileInfo(self): diff --git a/inlinino/widgets/hypernav/__init__.py b/inlinino/widgets/hypernav/__init__.py index 923a75e..46ec48a 100644 --- a/inlinino/widgets/hypernav/__init__.py +++ b/inlinino/widgets/hypernav/__init__.py @@ -90,18 +90,18 @@ def counter_reset(self): @QtCore.pyqtSlot(str, str, str) def warning_message_box(self, message, informative_text='', icon='warning'): if icon in ('error', 'critical'): - icon = QtWidgets.QMessageBox.Critical + icon = QtWidgets.QMessageBox.Icon.Critical elif icon in ('info', 'information'): - icon = QtWidgets.QMessageBox.Information + icon = QtWidgets.QMessageBox.Icon.Information else: - icon = QtWidgets.QMessageBox.Warning - msg = QtGui.QMessageBox(icon, "Inlinino: HyperNav", - message, QtGui.QMessageBox.Ok, parent=self) + icon = QtWidgets.QMessageBox.Icon.Warning + msg = QtWidgets.QMessageBox(icon, "Inlinino: HyperNav", + message, QtWidgets.QMessageBox.StandardButton.Ok, parent=self) if informative_text: msg.setInformativeText(informative_text) if self.isActiveWindow(): - msg.setWindowModality(QtCore.Qt.WindowModal) - msg.exec_() + msg.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + msg.exec() """ Control @@ -196,9 +196,9 @@ def cal(self): @QtCore.pyqtSlot(bytes) def update_serial_console(self, data: bytes): data.replace(b'\r', b'') # Use only \n otherwise create extra line - self.serial_monitor_console.moveCursor(QtGui.QTextCursor.End) + self.serial_monitor_console.moveCursor(QtGui.QTextCursor.MoveOperation.End) self.serial_monitor_console.insertPlainText(data.decode('utf8', errors='replace')) - self.serial_monitor_console.moveCursor(QtGui.QTextCursor.StartOfLine) + self.serial_monitor_console.moveCursor(QtGui.QTextCursor.MoveOperation.StartOfLine) def send_command(self): cmd = self.serial_monitor_command.text() diff --git a/inlinino/widgets/hypernav/analyze.py b/inlinino/widgets/hypernav/analyze.py index 44c9340..063d731 100644 --- a/inlinino/widgets/hypernav/analyze.py +++ b/inlinino/widgets/hypernav/analyze.py @@ -1,6 +1,6 @@ import os -from pyqtgraph.Qt import QtCore, QtGui, uic +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, uic from inlinino.shared.worker import Worker from inlinino.instruments.hypernav import HyperNav @@ -71,7 +71,7 @@ def __init__(self, parent, instrument: HyperNav, join_target=None): @QtCore.pyqtSlot() def browse_datafile(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose HyperNav data file', filter='Device File (*.raw *.txt)') self.le_datafile.setText(file_name) @@ -130,14 +130,14 @@ def __init__(self, parent, instrument: HyperNav): @QtCore.pyqtSlot() def browse_datafile(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose HyperNav data file', filter='Device File (*.raw *.txt)') self.le_raw_filename.setText(file_name) @QtCore.pyqtSlot() def browse_wavelength_file(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose wavelength registration file', filter='Device File (*.cgs)') self.le_wavelength_path.setText(file_name) @@ -192,7 +192,7 @@ def __init__(self, parent, instrument: HyperNav): @QtCore.pyqtSlot() def browse_history_path(self): - path = QtGui.QFileDialog.getExistingDirectory(self, caption='Choose history directory') + path = QtWidgets.QFileDialog.getExistingDirectory(self, caption='Choose history directory') self.le_history_path.setText(path) def start(self): diff --git a/inlinino/widgets/hypernav/calibrate_dialog.py b/inlinino/widgets/hypernav/calibrate_dialog.py index 652494d..9bb8f35 100644 --- a/inlinino/widgets/hypernav/calibrate_dialog.py +++ b/inlinino/widgets/hypernav/calibrate_dialog.py @@ -36,26 +36,26 @@ def __init__(self, parent, instrument: HyperNav): @QtCore.pyqtSlot() def browse_datafile(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose HyperNav data file', filter='Device File (*.raw *.txt)') self.le_log_file.setText(file_name) @QtCore.pyqtSlot() def browse_lamp_file(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose FEL lamp file', filter='Device File (*.dat *.FIT)') self.le_lamp_path.setText(file_name) @QtCore.pyqtSlot() def browse_plaque_file(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose reflectance plaque file', filter='Device File (*.dat *.FIT)') self.le_plaque_path.setText(file_name) @QtCore.pyqtSlot() def browse_wavelength_file(self): - file_name, _ = QtGui.QFileDialog.getOpenFileName(self, + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, caption='Choose wavelength registration file', filter='Device File (*.cgs *.txt)') self.le_wavelength_path.setText(file_name) try: @@ -70,7 +70,7 @@ def browse_wavelength_file(self): @QtCore.pyqtSlot() def browse_history_cal_dir(self): - self.le_history_cal_path.setText(QtGui.QFileDialog.getExistingDirectory( + self.le_history_cal_path.setText(QtWidgets.QFileDialog.getExistingDirectory( caption='Choose historical calibration directory')) @QtCore.pyqtSlot(str) diff --git a/inlinino/widgets/monitor.py b/inlinino/widgets/monitor.py index f216e08..0e10951 100644 --- a/inlinino/widgets/monitor.py +++ b/inlinino/widgets/monitor.py @@ -24,9 +24,9 @@ def clear(self): @QtCore.pyqtSlot(bytes) def update_monitor(self, data: bytes): data.replace(b'\r', b'') # Use only \n otherwise create extra line - self.monitor_view.moveCursor(QtGui.QTextCursor.End) + self.monitor_view.moveCursor(QtGui.QTextCursor.MoveOperation.End) self.monitor_view.insertPlainText(data.decode('utf8', errors='replace')) - self.monitor_view.moveCursor(QtGui.QTextCursor.StartOfLine) + self.monitor_view.moveCursor(QtGui.QTextCursor.MoveOperation.StartOfLine) def send_command(self): cmd = self.command_field.text() diff --git a/inlinino/widgets/select_channel.py b/inlinino/widgets/select_channel.py index ebef055..5fe9206 100644 --- a/inlinino/widgets/select_channel.py +++ b/inlinino/widgets/select_channel.py @@ -11,11 +11,11 @@ def __init__(self, instrument): self.variables_model = QtGui.QStandardItemModel() self.variables_filter_proxy_model = QtCore.QSortFilterProxyModel() self.variables_filter_proxy_model.setSourceModel(self.variables_model) - self.variables_filter_proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + self.variables_filter_proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive) # Setup super().__init__(instrument) # Connect Search Field to Filter Proxy Model - self.variables_search.textChanged.connect(self.variables_filter_proxy_model.setFilterRegExp) + self.variables_search.textChanged.connect(self.variables_filter_proxy_model.setFilterRegularExpression) # Add Data Filter Proxy Model to List View self.list_variables.setModel(self.variables_filter_proxy_model) self.list_variables.clicked.connect(self.update) @@ -26,11 +26,11 @@ def setup(self): # Set Current Variables for v in self.instrument.widget_active_timeseries_variables_names: item = QtGui.QStandardItem(v) - item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) + item.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled) if v in self.instrument.widget_active_timeseries_variables_selected: - item.setData(QtCore.QVariant(QtCore.Qt.Checked), QtCore.Qt.CheckStateRole) + item.setData(QtCore.Qt.CheckState.Checked, QtCore.Qt.ItemDataRole.CheckStateRole) else: - item.setData(QtCore.QVariant(QtCore.Qt.Unchecked), QtCore.Qt.CheckStateRole) + item.setData(QtCore.Qt.CheckState.Unchecked, QtCore.Qt.ItemDataRole.CheckStateRole) self.variables_model.appendRow(item) # Clear Search field self.variables_search.setText('') diff --git a/make.py b/make.py index e98dbed..1ada072 100644 --- a/make.py +++ b/make.py @@ -64,9 +64,9 @@ # Include hidden imports hidden_imports = [] for i in [ - 'pyqtgraph.graphicsItems.ViewBox.axisCtrlTemplate_pyqt5', - 'pyqtgraph.graphicsItems.PlotItem.plotConfigTemplate_pyqt5', - 'pyqtgraph.imageview.ImageViewTemplate_pyqt5', + 'pyqtgraph.graphicsItems.ViewBox.axisCtrlTemplate_generic', + 'pyqtgraph.graphicsItems.PlotItem.plotConfigTemplate_generic', + 'pyqtgraph.imageview.ImageViewTemplate_generic', ]: hidden_imports.append(f'--hidden-import={i}') diff --git a/requirements.txt b/requirements.txt index d5b65d4..aebef42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ pyserial==3.5 -pyqt5==5.15.* -pyqtgraph==0.12.1 +pyqt6==6.7.* +pyqtgraph==0.13.7 colorama==0.4.4 -numpy==1.22.* -scipy==1.9.* +numpy==2.2.* +scipy==1.15.* pyacs pysatlantic -pynmea2==1.18.* -pyusb==1.2.1 -hidapi==0.11.2 -plotly==5.13.* -kaleido==0.2.1 -wakepy==0.7.1 \ No newline at end of file +pynmea2==1.19.* +pyusb==1.3.* +hidapi==0.15.* +plotly==6.* +kaleido==1.* +wakepy==1.0.* \ No newline at end of file diff --git a/setup.py b/setup.py index 3d76120..38e19ee 100644 --- a/setup.py +++ b/setup.py @@ -24,8 +24,8 @@ long_description_content_type="text/markdown", url="https://github.com/OceanOptics/Inlinino", packages=setuptools.find_packages(), - install_requires=['pyserial>=3.4', 'numpy', 'scipy', 'PyQt5>=5.15', 'pyqtgraph>=0.12.1', 'pyACS', 'pySatlantic', 'pynmea2'], - python_requires='==3.8.*', + install_requires=['pyserial>=3.4', 'numpy>=2.0', 'scipy>=1.14', 'PyQt6>=6.7', 'pyqtgraph>=0.13.7', 'pyACS', 'pySatlantic', 'pynmea2'], + python_requires='>=3.12,<4', license='GPLv3', classifiers=[ "Programming Language :: Python :: 3",