def __init__(self, parent=None): super(FlowSolvingPopup, self).__init__(parent) layout = QBoxLayout(QBoxLayout.TopToBottom) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) self._solverWidget = FlowSolverWidget() self._solverWidget.finished.connect(self._solverFinished) layout.addWidget(self._solverWidget) status = QStatusBar() status.setSizeGripEnabled(False) self._againButton = QPushButton("next") self._againButton.setVisible(False) self._againButton.clicked.connect(self._againClicked) status.addPermanentWidget(self._againButton) self._abortButton = QPushButton("close") self._abortButton.clicked.connect(self._abortClicked) status.addPermanentWidget(self._abortButton) self._messageLabel = QLabel("ready") status.addWidget(self._messageLabel) layout.addWidget(status) layout.setSizeConstraint(QLayout.SetFixedSize) self.setLayout(layout) self._timer = QTimer() self._timer.timeout.connect(self._timerTick)
class Window(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setFixedSize(800, 685) self.setWindowTitle("Spotify Lyrics Finder") icon = QIcon("logo.ico") self.setWindowIcon(icon) self.CenterPanel = UiPanel(self) self.setCentralWidget(self.CenterPanel) self.menubar = QMenuBar(self) self.menubar.setGeometry(QRect(0, 0, 800, 21)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle("File") self.setMenuBar(self.menubar) self.statusbar = QStatusBar(self) self.statusbar.setSizeGripEnabled(False) self.setStatusBar(self.statusbar) self.actionClose = QAction(self) self.actionClose.setText("Close") self.actionClose.setShortcut("Ctrl+Q") self.actionProject = QAction(self) self.actionProject.setText("Copyright © White Aspect") self.menuFile.addAction(self.actionClose) self.menubar.addAction(self.menuFile.menuAction()) self.actionClose.triggered.connect(close_app) self.actionProject.triggered.connect(project_website)
def create_status_bar(self): status_bar = QStatusBar() status_bar.setSizeGripEnabled(False) self.size_label = QLabel() self.size_label.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) status_bar.addPermanentWidget(self.size_label) status_bar.showMessage("Status") self.setStatusBar(status_bar)
class ControlPanelWindow(QDialog): def __init__(self, parent, cli_iface, iface_name): super(ControlPanelWindow, self).__init__(parent) self.setWindowTitle('SLCAN Adapter Control Panel') self.setAttribute(Qt.WA_DeleteOnClose) # This is required to stop background timers! self._cli_iface = cli_iface self._iface_name = iface_name self._state_widget = StateWidget(self, self._cli_iface) self._config_widget = ConfigWidget(self, self._cli_iface) self._cli_widget = CLIWidget(self, self._cli_iface) self._tab_widget = QTabWidget(self) self._tab_widget.addTab(self._state_widget, get_icon('dashboard'), 'Adapter State') self._tab_widget.addTab(self._config_widget, get_icon('wrench'), 'Configuration') self._tab_widget.addTab(self._cli_widget, get_icon('terminal'), 'Command Line') self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) iface_name_label = QLabel(iface_name.split('/')[-1], self) iface_name_label.setFont(get_monospace_font()) layout = QVBoxLayout(self) layout.addWidget(iface_name_label) layout.addWidget(self._tab_widget) layout.addWidget(self._status_bar) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout) self.resize(400, 400) def closeEvent(self, close_event): if self._config_widget.have_unsaved_changes: if request_confirmation('Save changes?', 'You made changes to the adapter configuration that were not saved. ' 'Do you want to go back and save them?', parent=self): close_event.ignore() self._tab_widget.setCurrentWidget(self._config_widget) return super(ControlPanelWindow, self).closeEvent(close_event) def show_message(self, text, *fmt, duration=0): self._status_bar.showMessage(text % fmt, duration * 1000)
def create_status_bar(self): """ Creates the status bar """ statusbar = QStatusBar() statusbar.setSizeGripEnabled(False) statusbar.ind_label = QLabel('0 ') statusbar.addPermanentWidget(statusbar.ind_label) statusbar.ind_label.hide() self.create_loading_bar(statusbar) self.setStatusBar(statusbar) statusbar.showMessage('Welcome', 2000) return statusbar
class GlobalSettingsWindow(QMainWindow): def __init__(self): super().__init__() # self.setStyle(ProxyStyle()) self.vertical_tabs = TabWidget() self.status_bar = QStatusBar() self.reset_button = QPushButton('Reset') self.init_ui() self.show() def init_ui(self) -> None: self.setStyleSheet(get_color_scheme()) self.setWindowTitle('Settings') user_w, user_h = get_user_resolution() w = int(user_w / 3) h = int(user_h * 4 / 9) self.resize(w, h) self.setCentralWidget(self.vertical_tabs) self.reset_button.clicked.connect(self.on_reset) status_bar_content_widget = QWidget() status_bar_content_layout = QGridLayout(status_bar_content_widget) status_bar_content_layout.addWidget( QLabel( "Changes are auto saved; some will only be effective upon next start" ), 0, 0, 1, 1, Qt.AlignVCenter | Qt.AlignLeft) status_bar_content_layout.addWidget(self.reset_button, 0, 1, 1, 1, Qt.AlignVCenter | Qt.AlignRight) self.status_bar.addWidget(status_bar_content_widget, 1) self.status_bar.setSizeGripEnabled(False) self.setStatusBar(self.status_bar) self.setLayout(QVBoxLayout()) def on_reset(self) -> None: if self.vertical_tabs.currentIndex() == 0: self.vertical_tabs.ui_settings.on_reset() elif self.vertical_tabs.currentIndex() == 1: self.vertical_tabs.game_settings.on_reset() elif self.vertical_tabs.currentIndex() == 2: self.vertical_tabs.tabs_settings.on_reset() else: pass
class NodePropertiesWindow(QDialog): def __init__(self, parent, node, target_node_id, file_server_widget, node_monitor, dynamic_node_id_allocator_widget): super(NodePropertiesWindow, self).__init__(parent) self.setAttribute( Qt.WA_DeleteOnClose) # This is required to stop background timers! self.setWindowTitle('Node Properties [%d]' % target_node_id) self.setMinimumWidth(640) self._target_node_id = target_node_id self._node = node self._file_server_widget = file_server_widget self._info_box = InfoBox(self, target_node_id, node_monitor) self._controls = Controls(self, node, target_node_id, file_server_widget, dynamic_node_id_allocator_widget) self._config_params = ConfigParams(self, node, target_node_id) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout = QVBoxLayout(self) layout.addWidget(self._info_box) layout.addWidget(self._controls) layout.addWidget(self._config_params) layout.addWidget(self._status_bar) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout) def show_message(self, text, *fmt, duration=0): self._status_bar.showMessage(text % fmt, duration * 1000) @property def target_node_id(self): return self._target_node_id
class NodePropertiesWindow(QDialog): def __init__( self, parent, node, target_node_id, file_server_widget, node_monitor, dynamic_node_id_allocator_widget ): super(NodePropertiesWindow, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) # This is required to stop background timers! self.setWindowTitle("Node Properties [%d]" % target_node_id) self.setMinimumWidth(640) self._target_node_id = target_node_id self._node = node self._file_server_widget = file_server_widget self._info_box = InfoBox(self, target_node_id, node_monitor) self._controls = Controls(self, node, target_node_id, file_server_widget, dynamic_node_id_allocator_widget) self._config_params = ConfigParams(self, node, target_node_id) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout = QVBoxLayout(self) layout.addWidget(self._info_box) layout.addWidget(self._controls) layout.addWidget(self._config_params) layout.addWidget(self._status_bar) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout) def show_message(self, text, *fmt, duration=0): self._status_bar.showMessage(text % fmt, duration * 1000) @property def target_node_id(self): return self._target_node_id
class BatchTrackingDialog(QDialog): """Dialog for batch processing videos.""" def __init__(self, root_folder, parent=None): super(BatchTrackingDialog, self).__init__(parent) self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) self.setWindowTitle('Batch Processing Wizard') app_icon = QIcon(QPixmap(os.path.join(DIR, '..', 'icons', 'logo.png'))) self.setWindowIcon(app_icon) self.step_number = 0 self.video_settings = [] self.root_folder = root_folder self.layout = QGridLayout() # move all steps into a stacked layout. # this allows each layout to be shown individually, # while providing a container for all layouts. self.widgets = QStackedWidget() file_widget = StackedStepWidget(0, BatchFileSelector(self.root_folder, 0)) arena_widget = StackedStepWidget(1, BatchArenaSpecifier(1)) try: arena_widget.settings_widget.background_frame_ix.connect( self.update_progress_bar) except RuntimeError: print('Error while trying to connect the arena widget' + 'to the update_progress_bar fxn.') female_widget = StackedStepWidget(2, BatchFemaleSpecifier(2)) tight_threshold_widget = StackedStepWidget( 3, BatchTightThresholdSpecifier(3)) tight_threshold_widget.settings_widget.image_calc_progress.connect( self.update_progress_bar) loose_threshold_widget = StackedStepWidget( 4, BatchLooseThresholdSpecifier(4)) loose_threshold_widget.settings_widget.image_calc_progress.connect( self.update_progress_bar) tracking_widget = StackedStepWidget(5, BatchTrackingWidget(5)) tracking_widget.settings_widget.tracking_progress.connect( self.update_progress_bar) self.widgets.addWidget(file_widget) self.widgets.addWidget(arena_widget) self.widgets.addWidget(female_widget) self.widgets.addWidget(tight_threshold_widget) self.widgets.addWidget(loose_threshold_widget) self.widgets.addWidget(tracking_widget) # connect all widget signals to this class's update_settings # function. self.connect_widget_signals() self.layout.addWidget(self.widgets, 0, 0, 6, 6) # setup the next and previous buttons self.next_button = QPushButton('Next') self.next_button.setEnabled(False) previous_button = QPushButton('Previous') self.next_button.clicked.connect(self.step_forward) previous_button.clicked.connect(self.step_backward) self.layout.addWidget(previous_button, 6, 0, 1, 1) self.layout.addWidget(self.next_button, 6, 5, 1, 1) # setup the status bar interface self.set_status() self.layout.addWidget(self.status, 7, 0, 1, 6) self.setLayout(self.layout) self.resize(1000, 600) def connect_widget_signals(self): """Connects all widgets in the StackedLayout to self.update_settings.""" for i in xrange(self.widgets.count() + 1): # this call returns 0 if widget at index is not present. if self.widgets.widget(i): self.widgets.widget( i).settings_widget.all_settings_valid.connect( self.update_settings) def set_status(self): """Sets interface for status bar at bottom of dialog window.""" self.status = QStatusBar() self.status.setSizeGripEnabled(True) self.process_label = QLabel('Process') self.process_label.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.progress_bar = QProgressBar() self.status.addWidget(self.process_label) self.status.addPermanentWidget(self.progress_bar) def _step(self): """Updates layout currently being displayed in StackedLayout.""" self.widgets.setCurrentIndex(self.step_number) if self.widgets.currentWidget().settings_widget.check_settings_valid(): self.next_button.setEnabled(True) def step_forward(self): """Moves the StackedLayout forward by one index.""" if self.step_number < self.widgets.count() - 1: self.step_number += 1 self._step() def step_backward(self): """Moves the StackedLayout backward by one index.""" if self.step_number > 0: self.step_number -= 1 self._step() @pyqtSlot(bool, int, list) def update_settings(self, is_set, widget_ix, settings_list): """This function updates all of the video settings. It is a slot for the BatchSettingsWidget.all_settings_valid signal, and not only sets the video_settings of this class (TrackingDialog), but updates all of the settings in each of the widgets in the StackedLayout. """ if is_set: self.next_button.setEnabled(True) self.video_settings = settings_list for i in xrange(self.widgets.count() + 1): if self.widgets.widget(i): self.widgets.widget(i).settings_widget.update_settings( settings_list) else: self.next_button.setEnabled(False) @pyqtSlot(int, str) def update_progress_bar(self, progress, description): """Updates the progress bar based on actions happening in widgets within the StackedLayout.""" self.process_label.setText('Process -- {}'.format(description)) self.progress_bar.setValue(progress)
class MainWin(QMainWindow): def __init__(self): super(MainWin, self).__init__() self.settings = QSettings("RadioBOB", "settings") self.setStyleSheet(mystylesheet(self)) self.radioNames = [] self.radiolist = [] self.channels = [] self.imagelist = [] self.radiofile = "" self.radioStations = "" self.rec_name = "" self.rec_url = "" self.old_meta = "" self.notificationsEnabled = True self.headerlogo = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "headerlogo.png")) self.setContentsMargins(5, 0, 5, 0) self.wg = QWidget() self.er_label = QLabel("Image") self.er_label.setPixmap(self.headerlogo.pixmap(QSize(300, 140))) self.er_label.setScaledContents(False) self.er_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.layout = QVBoxLayout() ### combo box self.urlCombo = QComboBox() self.er_label.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.er_label, 0, Qt.AlignCenter) self.layout1 = QHBoxLayout() self.layout1.setContentsMargins(50, 0, 50, 0) self.tIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "logo.png")) self.playIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "media-playback-start.svg")) self.stopIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "media-playback-stop.svg")) self.recordIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "media-record.svg")) self.hideIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "hide.png")) self.outfile = QStandardPaths.standardLocations( QStandardPaths.TempLocation)[0] + "/er_tmp.mp3" self.recording_enabled = False self.is_recording = False spc1 = QSpacerItem(6, 10, QSizePolicy.Expanding, QSizePolicy.Maximum) self.play_btn = QPushButton("", self) self.play_btn.setFixedWidth(btnwidth) self.play_btn.setIcon(self.playIcon) self.layout1.addWidget(self.play_btn) self.stop_btn = QPushButton("", self) self.stop_btn.setFixedWidth(btnwidth) self.stop_btn.setIcon(self.stopIcon) self.layout1.addWidget(self.stop_btn) ### record self.rec_btn = QPushButton("", self) self.rec_btn.setFixedWidth(btnwidth) self.rec_btn.setIcon(self.recordIcon) self.rec_btn.clicked.connect(self.recordRadio) self.rec_btn.setToolTip("Aufnehmen") self.layout1.addWidget(self.rec_btn) ### stop record self.stoprec_btn = QPushButton("", self) self.stoprec_btn.setFixedWidth(btnwidth) self.stoprec_btn.setIcon(self.stopIcon) self.stoprec_btn.clicked.connect(self.stop_recording) self.stoprec_btn.setToolTip("Aufnahme stoppen") self.layout1.addWidget(self.stoprec_btn) ### Hauptfenster verbergen self.hide_btn = QPushButton("", self) self.hide_btn.setFixedWidth(btnwidth) self.hide_btn.setToolTip("Fenster ins Tray minimieren") self.hide_btn.setIcon(self.hideIcon) self.hide_btn.clicked.connect(self.showMain) self.layout1.addWidget(self.hide_btn) self.level_sld = QSlider(self) self.level_sld.setFixedWidth(310) self.level_sld.setToolTip("Lautstärkeregler") self.level_sld.setTickPosition(1) self.level_sld.setOrientation(Qt.Horizontal) self.level_sld.setValue(65) self.level_lbl = QLabel(self) self.level_lbl.setAlignment(Qt.AlignHCenter) self.level_lbl.setText("Lautstärke 65") self.layout.addItem(spc1) self.layout.addWidget(self.level_sld, Qt.AlignCenter) self.layout.addWidget(self.level_lbl, Qt.AlignCenter) self.layout.addItem(spc1) self.layout.addLayout(self.layout1) self.player = RadioPlayer(self) self.player.metaDataChanged.connect(self.metaDataChanged) self.player.error.connect(self.handleError) self.play_btn.clicked.connect(self.playRadioStation) self.stop_btn.clicked.connect(self.stop_preview) self.level_sld.valueChanged.connect(self.set_sound_level) self.urlCombo.currentIndexChanged.connect(self.url_changed) self.current_station = "" self.process = QProcess() self.process.started.connect(self.getPID) self.wg.setLayout(self.layout) self.setCentralWidget(self.wg) self.stoprec_btn.setVisible(False) self.readStations() self.createStatusBar() self.setAcceptDrops(True) self.setWindowTitle("Radio BOB") self.setWindowIcon(self.tIcon) self.stationActs = [] self.layout.addItem(spc1) self.setFixedSize(340, 360) self.move(30, 30) # Init tray icon trayIcon = QIcon(self.tIcon) self.trayIcon = QSystemTrayIcon() self.trayIcon.setIcon(self.headerlogo) self.trayIcon.show() self.trayIcon.activated.connect(self.showMainfromTray) self.geo = self.geometry() self.showWinAction = QAction(QIcon.fromTheme("view-restore"), "Hauptfenster anzeigen", triggered=self.showMain) self.notifAction = QAction(QIcon.fromTheme("dialog-information"), "Tray Meldungen ausschalten", triggered=self.toggleNotif) self.togglePlayerAction = QAction("Wiedergabe stoppen", triggered=self.togglePlay) self.togglePlayerAction.setIcon(QIcon.fromTheme("media-playback-stop")) self.recordAction = QAction(QIcon.fromTheme("media-record"), "Aufnahme starten", triggered=self.recordRadio) self.stopRecordAction = QAction(QIcon.fromTheme("media-playback-stop"), "Aufnahme stoppen", triggered=self.stop_recording) self.findExecutable() self.readSettings() self.makeTrayMenu() self.createWindowMenu() if QSystemTrayIcon.isSystemTrayAvailable(): print("System Tray Icon verfügbar") else: print("System Tray Icon nicht verfügbar") if self.player.state() == QMediaPlayer.StoppedState: self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) elif self.player.state() == QMediaPlayer.PlayingState: self.togglePlayerAction.setText("Wiedergabe stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) def showTrayMessage(self, title, message, icon, timeout=4000): self.trayIcon.showMessage(title, message, icon, timeout) def handleError(self): print(f"Fehler: {self.player.errorString()}") self.showTrayMessage(f"Error:\n{self.player.errorString()}", self.tIcon, 3000) self.statusLabel.setText(f"Fehler:\n{self.player.errorString()}") def togglePlay(self): if self.togglePlayerAction.text() == "Wiedergabe stoppen": self.stop_preview() self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) else: self.playRadioStation() self.togglePlayerAction.setText("Aufnahme stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) def createWindowMenu(self): self.tb = self.addToolBar("Menu") self.tb_menu = QMenu() self.tb.setIconSize(QSize(44, 20)) ##### submenus from categories ########## b = self.radioStations.splitlines() for x in reversed(range(len(b))): line = b[x] if line == "": print(f"empty line {x} removed") del (b[x]) i = 0 for x in range(0, len(b)): line = b[x] menu_line = line.split(",") ch = menu_line[0] data = menu_line[1] if len(menu_line) > 2: image = menu_line[2] self.tb_menu.addAction(self.stationActs[i]) i += 1 #################################### toolButton = QToolButton() toolButton.setIcon(self.headerlogo) toolButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolButton.setText(" Stationen") toolButton.setFixedWidth(120) toolButton.setMenu(self.tb_menu) toolButton.setPopupMode(QToolButton.InstantPopup) self.tb.addWidget(toolButton) empty = QWidget() self.tb.addWidget(empty) self.tb.setContextMenuPolicy(Qt.PreventContextMenu) self.tb.setMovable(False) self.tb.setAllowedAreas(Qt.TopToolBarArea) def makeTrayMenu(self): self.stationActs = [] self.tray_menu = QMenu() self.tray_menu.addAction(self.togglePlayerAction) ##### submenus from categories ########## b = self.radioStations.splitlines() for x in reversed(range(len(b))): line = b[x] if line == "": print(f"empty line {x} removed") del (b[x]) i = 0 for x in range(0, len(b)): line = b[x] menu_line = line.split(",") ch = menu_line[0] data = menu_line[1] if len(menu_line) > 2: image = menu_line[2] self.stationActs.append( QAction(self.tIcon, ch, triggered=self.openTrayStation)) self.stationActs[i].setData(str(i)) self.tray_menu.addAction(self.stationActs[i]) i += 1 #################################### self.tray_menu.addSeparator() if not self.is_recording: if not self.urlCombo.currentText().startswith("--"): self.tray_menu.addAction(self.recordAction) self.recordAction.setText( f"starte Aufnahme von {self.urlCombo.currentText()}") if self.is_recording: self.tray_menu.addAction(self.stopRecordAction) self.tray_menu.addSeparator() self.tray_menu.addAction(self.showWinAction) self.tray_menu.addSeparator() self.tray_menu.addAction(self.notifAction) self.tray_menu.addSeparator() exitAction = self.tray_menu.addAction( QIcon.fromTheme("application-exit"), "Beenden") exitAction.triggered.connect(self.exitApp) self.trayIcon.setContextMenu(self.tray_menu) def showMain(self): if self.isVisible() == False: self.showWinAction.setText("Hauptfenster verbergen") self.setVisible(True) elif self.isVisible() == True: self.showWinAction.setText("Hauptfenster anzeigen") self.setVisible(False) def showMainfromTray(self): buttons = qApp.mouseButtons() if buttons == Qt.LeftButton: if self.isVisible() == False: self.showWinAction.setText("Hauptfenster verbergen") self.setVisible(True) elif self.isVisible() == True: self.showWinAction.setText("Hauptfenster anzeigen") self.setVisible(False) def toggleNotif(self): if self.notifAction.text() == "Tray Meldungen ausschalten": self.notifAction.setText("Tray Meldungen einschalten") self.notificationsEnabled = False elif self.notifAction.text() == "Tray Meldungen einschalten": self.notifAction.setText("Tray Meldungen ausschalten") self.notificationsEnabled = True print(f"Notifications {self.notificationsEnabled}") self.metaDataChanged() def openTrayStation(self): action = self.sender() if action: ind = action.data() name = action.text() self.urlCombo.setCurrentIndex(self.urlCombo.findText(name)) print(f"swith to Station: {ind} - {self.urlCombo.currentText()}") def exitApp(self): self.close() QApplication.quit() def message(self, message): QMessageBox.information(None, 'Meldung', message) def closeEvent(self, e): self.writeSettings() print("schreibe Konfiguratinsdatei ...\nbis bald, keep on rocking...") QApplication.quit() def readSettings(self): print("lese Konfiguratinsdatei ...") if self.settings.contains("pos"): pos = self.settings.value("pos", QPoint(200, 200)) self.move(pos) else: self.move(0, 26) if self.settings.contains("lastChannel"): lch = self.settings.value("lastChannel") self.urlCombo.setCurrentIndex(self.urlCombo.findText(lch)) if self.settings.contains("notifications"): self.notificationsEnabled = self.settings.value("notifications") if self.settings.value("notifications") == "false": self.notificationsEnabled = False self.notifAction.setText("Tray Meldungen einschalten") else: self.notifAction.setText("Tray Meldungen ausschalten") self.notificationsEnabled = True if self.settings.contains("windowstate"): print(self.settings.value("windowstate")) if self.settings.value("windowstate") == "Hauptfenster anzeigen": self.show() self.showWinAction.setText("Hauptfenster verbergen") else: self.hide() self.showWinAction.setText("Hauptfenster anzeigen") if self.settings.contains("volume"): vol = self.settings.value("volume") print(f"set volume to {vol}") self.level_sld.setValue(int(vol)) def writeSettings(self): self.settings.setValue("pos", self.pos()) self.settings.setValue("index", self.urlCombo.currentIndex()) self.settings.setValue("lastChannel", self.urlCombo.currentText()) self.settings.setValue("notifications", self.notificationsEnabled) if self.isVisible(): self.settings.setValue("windowstate", "Hauptfenster anzeigen") else: self.settings.setValue("windowstate", "Hauptfenster verbergen") self.settings.setValue("volume", self.level_sld.value()) self.settings.sync() def readStations(self): self.urlCombo.clear() self.radiolist = [] self.channels = [] self.imagelist = [] dir = os.path.dirname(sys.argv[0]) self.radiofile = os.path.join(dir, "bob.txt") with open(self.radiofile, 'r') as f: self.radioStations = f.read() f.close() newlist = [list(x) for x in self.radioStations.splitlines()] for lines in self.radioStations.splitlines(): mLine = lines.split(",") if not mLine[0].startswith("--"): self.urlCombo.addItem(self.tIcon, mLine[0], Qt.UserRole - 1) self.radiolist.append(mLine[1]) def findExecutable(self): wget = QStandardPaths.findExecutable("wget") if wget != "": print(f"found wget at {wget} *** recording available") self.statusLabel.setText("Aufnahmen möglich") self.showTrayMessage("Hinweis", "wget gefunden\nAufnahmen möglich", self.tIcon) self.recording_enabled = True else: self.showTrayMessage( "Hinweis", "wget icht gefunden\nkeine Aufnahmen möglich", self.tIcon) print("wget icht gefunden\nkeine Aufnahmen möglich") self.recording_enabled = False def remove_last_line_from_string(self, s): return s[:s.rfind('\n')] def createStatusBar(self): self.statusLabel = QLabel("Info") self.statusLabel.setWordWrap(True) self.statusLabel.setAlignment(Qt.AlignCenter) #self.statusLabel.setStyleSheet("color:#73d216;") self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.setStatusBar(self.statusBar) self.statusLabel.setText("Willkommen bei Radio BOB") self.statusBar.addWidget(self.statusLabel, 1) pixmap = QIcon(self.headerlogo) self.home_label = QPushButton() self.home_label.setIconSize(QSize(52, 26)) self.home_label.setFixedSize(60, 32) self.home_label.setToolTip("Radio BOB Homepage besuchen") self.home_label.setIcon(self.headerlogo) self.home_label.clicked.connect(self.showHomepage) self.statusBar.addPermanentWidget(self.home_label) def showHomepage(self): url = QUrl('https://radiobob.de') QDesktopServices.openUrl(url) def metaDataChanged(self): if self.player.isMetaDataAvailable(): trackInfo = (self.player.metaData("Title")) if trackInfo is None: self.statusLabel.setText( f"playing {self.urlCombo.currentText()}") new_trackInfo = "" new_trackInfo = str(trackInfo) #print(new_trackInfo) if not new_trackInfo == "None" and " - " in new_trackInfo: self.statusLabel.setText( f"{new_trackInfo.split(' - ')[0]}\n{new_trackInfo.split(' - ')[1]}" ) else: self.statusLabel.setText( f" playing {self.urlCombo.currentText()}") mt = new_trackInfo if not mt == "None" and " - " in mt: if self.notificationsEnabled: if not mt == self.old_meta: print(mt) self.showTrayMessage( "Radio BOB", f"{mt.split(' - ')[0]}\n{mt.split(' - ')[1]}", self.tIcon) self.old_meta = mt self.trayIcon.setToolTip(mt) else: self.trayIcon.setToolTip(mt) self.old_meta = mt else: self.statusLabel.setText(f"playing {self.urlCombo}") def url_changed(self): if self.urlCombo.currentIndex() < self.urlCombo.count() - 1: if not self.urlCombo.currentText().startswith("--"): ind = self.urlCombo.currentIndex() url = self.radiolist[ind] self.current_station = url self.player.stop() self.rec_btn.setVisible(True) self.stop_btn.setVisible(True) self.play_btn.setVisible(True) name = self.urlCombo.currentText() print(f"playing {name} from {url}") self.playRadioStation() if self.togglePlayerAction.text() == "Wiedergabe stoppen": self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) else: self.togglePlayerAction.setText("Wiedergabe stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) else: self.rec_btn.setVisible(False) self.stop_btn.setVisible(False) self.play_btn.setVisible(False) def playRadioStation(self): if self.player.is_on_pause: self.set_running_player() self.player.start() self.stop_btn.setFocus() self.togglePlayerAction.setText("Aufnahme stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) if not self.current_station: return self.player.set_media(self.current_station) self.set_running_player() self.player.start() if self.is_recording: self.recordAction.setText(f"stoppe Aufnahme von {self.rec_name}") self.recordAction.setIcon(QIcon.fromTheme("media-playback-stop")) else: self.recordAction.setText( f"starte Aufnahme von {self.urlCombo.currentText()}") self.recordAction.setIcon(QIcon.fromTheme("media-record")) self.statusLabel.setText(f"playing {self.urlCombo.currentText()}") self.setWindowTitle(self.urlCombo.currentText()) def set_running_player(self): self.play_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.rec_btn.setEnabled(True) def stop_preview(self): self.player.finish() self.play_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.rec_btn.setEnabled(False) self.statusLabel.setText("stopped") self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) def set_sound_level(self, level): self.player.set_sound_level(level) self.level_lbl.setText("Lautstärke " + str(level)) self.player.setVolume(level) def update_volume_slider(self, level): self.level_lbl.setText("Lautstärke " + str(level)) self.level_sld.blockSignals(True) self.level_sld.setValue(value) self.level_lbl.setText("Lautstärke " + str(level)) self.level_sld.blockSignals(False) def recordRadio(self): if not self.is_recording: self.deleteOutFile() self.rec_url = self.current_station self.rec_name = self.urlCombo.currentText() cmd = ("wget -q " + self.rec_url + " -O " + self.outfile) print(cmd) self.is_recording = True self.process.startDetached(cmd) self.recordAction.setText(f"stoppe Aufnahme von {self.rec_name}") self.recordAction.setIcon(QIcon.fromTheme("media-playback-stop")) self.rec_btn.setVisible(False) self.stoprec_btn.setVisible(True) else: self.stop_recording() def stop_recording(self): if self.is_recording: self.process.close() print("stoppe Aufnahme") self.is_recording = False QProcess.execute("killall wget") self.saveRecord() self.stoprec_btn.setVisible(False) self.rec_btn.setVisible(True) self.recordAction.setText( f"starte Aufnahme von {self.urlCombo.currentText()}") self.recordAction.setIcon(QIcon.fromTheme("media-record")) else: self.showTrayMessage("Hinweis", "keine Aufnahme gestartet", self.tIcon) def saveRecord(self): if not self.is_recording: print("saving Audio") musicfolder = QStandardPaths.standardLocations( QStandardPaths.MusicLocation)[0] recname = self.rec_name.replace("-", " ").replace(" - ", " ") + ".mp3" infile = QFile(self.outfile) savefile, _ = QFileDialog.getSaveFileName( None, "Speichern als...", f'{musicfolder}/{recname}', "Audio (*.mp3)") if (savefile != ""): if QFile(savefile).exists: QFile(savefile).remove() print(f"saving {savefile}") if not infile.copy(savefile): QMessageBox.warning( self, "Fehler", f"File {savefile} {infile.errorString()}") print(f"Prozess-State: {str(self.process.state())}") if QFile(self.outfile).exists: print(f"{self.outfile} existiert") QFile(self.outfile).remove() def deleteOutFile(self): if QFile(self.outfile).exists: print(f"delete file {self.outfile}") if QFile(self.outfile).remove: print(f"{self.outfile} deleted") else: print(f"{self.outfile} not deleted") def getPID(self): print(f"{self.process.pid()} {self.process.processId()}")
class ChessClaimView(QMainWindow): """ The main window of the application. Attributes: rowCount(int): The number of the row the TreeView Table has. iconsSize(int): The recommended size of the icons. mac_notification: Notification for macOS win_notification: Notification for windows OS """ def __init__(self): super().__init__() self.resize(720, 275) self.iconsSize = 16 self.setWindowTitle('Chess Claim Tool') self.center() self.rowCount = 0 if (platform.system() == "Darwin"): from MacNotification import Notification self.mac_notification = Notification() elif (platform.system() == "Windows"): from win10toast import ToastNotifier self.win_notification = ToastNotifier() def center(self): """ Centers the window on the screen """ screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2) def set_GUI(self): """ Initialize GUI components. """ # Create the Menu self.livePgnOption = QAction('Live PGN',self) self.livePgnOption.setCheckable(True) aboutAction = QAction('About',self) menubar = self.menuBar() optionsMenu = menubar.addMenu('&Options') optionsMenu.addAction(self.livePgnOption) aboutMenu = menubar.addMenu('&Help') aboutMenu.addAction(aboutAction) aboutAction.triggered.connect(self.slots.on_about_clicked) # Create the Claims Table (TreeView) self.claimsTable = QTreeView() self.claimsTable.setFocusPolicy(Qt.NoFocus) self.claimsTable.setEditTriggers(QAbstractItemView.NoEditTriggers) self.claimsTable.header().setDefaultAlignment(Qt.AlignCenter) self.claimsTable.setSortingEnabled(True) self.claimsTable.doubleClicked.connect(self.open_game) # Create the Claims Model self.claimsTableModel = QStandardItemModel() labels = ["#","Timestamp","Type","Board","Players","Move"] self.claimsTableModel.setHorizontalHeaderLabels(labels) self.claimsTable.setModel(self.claimsTableModel) # Create the Scan & Stop Button Box self.buttonBox = ButtonBox(self) # Create the Sources Button sourcesButton = QPushButton("Add Sources") sourcesButton.setObjectName("sources") sourcesButton.clicked.connect(self.slots.on_sourcesButton_clicked) # Create the Status Bar self.pixmapCheck = QPixmap(resource_path("check_icon.png")) self.pixmapError = QPixmap(resource_path("error_icon.png")) self.sourceLabel = QLabel() self.sourceLabel.setObjectName("source-label") self.sourceImage = QLabel() self.sourceImage.setObjectName("source-image") self.downloadLabel = QLabel() self.downloadLabel.setObjectName("download-label") self.downloadImage = QLabel() self.downloadImage.setObjectName("download-image") self.scanningLabel = QLabel() self.scanningLabel.setObjectName("scanning") self.spinnerLabel = QLabel() self.spinnerLabel.setVisible(False) self.spinnerLabel.setObjectName("spinner") self.spinner = QMovie(resource_path("spinner.gif")) self.spinner.setScaledSize(QSize(self.iconsSize, self.iconsSize)) self.spinnerLabel.setMovie(self.spinner) self.spinner.start() self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.statusBar.addWidget(self.sourceLabel) self.statusBar.addWidget(self.sourceImage) self.statusBar.addWidget(self.downloadLabel) self.statusBar.addWidget(self.downloadImage) self.statusBar.addWidget(self.scanningLabel) self.statusBar.addWidget(self.spinnerLabel) self.statusBar.addPermanentWidget(sourcesButton) self.statusBar.setContentsMargins(10,5,9,5) # Container Layout for the Central Widget containerLayout = QVBoxLayout() containerLayout.setSpacing(0) containerLayout.addWidget(self.claimsTable) containerLayout.addWidget(self.buttonBox) # Central Widget containerWidget = QWidget() containerWidget.setLayout(containerLayout) self.setCentralWidget(containerWidget) self.setStatusBar(self.statusBar) def open_game(self): """ TODO: Double click should open a window to replay the game.""" pass def resize_claimsTable(self): """ Resize the table (if needed) after the insertion of a new element""" for index in range(0,6): self.claimsTable.resizeColumnToContents(index) def set_slots(self, slots): """ Connect the Slots """ self.slots = slots def add_to_table(self,type,bo_number,players,move): """ Add new row to the claimsTable Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). bo_number: The number of the boards, if this information is available. players: The name of the players. move: With which move the draw is valid. """ # Before insertion, remove rows as descripted in the remove_rows function self.remove_rows(type,players) timestamp = str(datetime.now().strftime('%H:%M:%S')) row = [] items = [str(self.rowCount+1),timestamp,type,bo_number,players,move] """ Convert each item(str) to QStandardItem, make the necessary stylistic additions and append it to row.""" for index in range(len(items)): standardItem = QStandardItem(items[index]) standardItem.setTextAlignment(Qt.AlignCenter) if(index == 2): font = standardItem.font() font.setBold(True) standardItem.setFont(font) if (items[index] == "5 Fold Repetition" or items[index] == "75 Moves Rule"): standardItem.setData(QColor(255,0,0), Qt.ForegroundRole) row.append(standardItem) self.claimsTableModel.appendRow(row) self.rowCount = self.rowCount+1 # After the insertion resize the table self.resize_claimsTable() # Always the last row(the bottom of the table) should be visible. self.claimsTable.scrollToBottom() #Send Notification self.notify(type,players,move) def notify(self,type,players,move): """ Send notification depending on the OS. Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). players: The names of the players. move: With which move the draw is valid. """ if (platform.system() == "Darwin"): self.mac_notification.clearNotifications() self.mac_notification.notify(type,players,move) elif(platform.system() == "Windows"): self.win_notification.show_toast(type, players+"\n"+move, icon_path=resource_path("logo.ico"), duration=5, threaded=True) def remove_from_table(self,index): """ Remove element from the claimsTable. Args: index: The index of the row we want to remove. First row has index=0. """ self.claimsTableModel.removeRow(index) def remove_rows(self,type,players): """ Removes a existing row from the Claims Table when same players made the same type of draw with a new move - or they made 5 Fold Repetition over the 3 Fold or 75 Moves Rule over 50 moves Rule. Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). players: The names of the players. """ for index in range(self.rowCount): try: modelType = self.claimsTableModel.item(index,2).text() modelPlayers = self.claimsTableModel.item(index,4).text() except AttributeError: modelType = "" modelPlayers = "" if (modelType == type and modelPlayers == players): self.remove_from_table(index) self.rowCount = self.rowCount - 1 break elif (type == "5 Fold Repetition" and modelType == "3 Fold Repetition" and modelPlayers == players) : self.remove_from_table(index) self.rowCount = self.rowCount - 1 break elif (type == "75 Moves Rule" and modelType == "50 Moves Rule" and modelPlayers == players): self.remove_from_table(index) self.rowCount = self.rowCount - 1 break def clear_table(self): """ Clear all the elements off the Claims Table and resets the rowCount. """ for index in range(self.rowCount): self.claimsTableModel.removeRow(0) self.rowCount = 0 def set_sources_status(self,status,validSources=None): """ Adds the sourcess in the statusBar. Args: status(str): The status of the validity of the sources. "ok": At least one source is valid. "error": None of the sources are valid. validSources(list): The list of valid sources, if there is any. This list is used here to display the ToolTip. """ self.sourceLabel.setText("Sources:") # Set the ToolTip if there are sources. try: text = "" for index in range(len(validSources)): if (index == len(validSources) - 1): number = str(index+1) text = text+number+") "+validSources[index].get_value() else: number = str(index+1) text = text+number+") "+validSources[index].get_value()+"\n" self.sourceLabel.setToolTip(text) except TypeError: pass if (status == "ok"): self.sourceImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) else: self.sourceImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) def set_download_status(self,status): """ Adds download status in the statusBar. Args: status(str): The status of the download(s). "ok": The download of the sources is successful. "error": The download of the sources failed. "stop": The download process stopped. """ timestamp = str(datetime.now().strftime('%H:%M:%S')) self.downloadLabel.setText(timestamp+" Download:") if (status == "ok"): self.downloadImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) elif (status == "error"): self.downloadImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) elif (status == "stop"): self.downloadImage.clear() self.downloadLabel.clear() def set_scan_status(self,status): """ Adds the scan status in the statusBar. Args: status(str): The status of the scan process. "active": The scan process is active. "error": The scan process waits for a new file. "stop": The scan process stopped. """ if (status == "wait"): self.scanningLabel.setText("Scan: Waiting") self.spinnerLabel.setVisible(False) elif (status == "active"): self.scanningLabel.setText("Scanning...") self.spinnerLabel.setVisible(True) elif (status == "stop"): self.scanningLabel.clear() self.spinnerLabel.setVisible(False) def change_scanButton_text(self,status): """ Changes the text of the scanButton depending on the status of the application. Args: status(str): The status of the scan process. "active": The scan process is active. "wait": The scan process is being terminated "stop": The scan process stopped. """ if (status == "active"): self.buttonBox.scanButton.setText("Scanning PGN...") elif (status == "stop"): self.buttonBox.scanButton.setText("Start Scan") elif(status == "wait"): self.buttonBox.scanButton.setText("Please Wait") def enable_buttons(self): self.buttonBox.scanButton.setEnabled(True) self.buttonBox.stopButton.setEnabled(True) def disable_buttons(self): self.buttonBox.scanButton.setEnabled(False) self.buttonBox.stopButton.setEnabled(False) def enable_statusBar(self): """ Show download and scan status messages - if they were previously hidden (by disable_statusBar) - from the statusBar.""" self.downloadLabel.setVisible(True) self.scanningLabel.setVisible(True) self.downloadImage.setVisible(True) def disable_statusBar(self): """ Hide download and scan status messages from the statusBar. """ self.downloadLabel.setVisible(False) self.downloadImage.setVisible(False) self.scanningLabel.setVisible(False) self.spinnerLabel.setVisible(False) def closeEvent(self,event): """ Reimplement the close button If the program is actively scanning a pgn a warning dialog shall be raised in order to make sure that the user didn't clicked the close Button accidentally. Args: event: The exit QEvent. """ try: if (self.slots.scanWorker.isRunning): exitDialog = QMessageBox() exitDialog.setWindowTitle("Warning") exitDialog.setText("Scanning in Progress") exitDialog.setInformativeText("Do you want to quit?") exitDialog.setIcon(exitDialog.Warning) exitDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) exitDialog.setDefaultButton(QMessageBox.Cancel) replay = exitDialog.exec() if replay == QMessageBox.Yes: event.accept() else: event.ignore() except: event.accept() def load_warning(self): """ Displays a Warning Dialog. trigger: User clicked the "Start Scanning" Button without any valid pgn source. """ warningDialog = QMessageBox() warningDialog.setIcon(warningDialog.Warning) warningDialog.setWindowTitle("Warning") warningDialog.setText("PGN File(s) Not Found") warningDialog.setInformativeText("Please enter at least one valid PGN source.") warningDialog.exec() def load_about_dialog(self): """ Displays the About Dialog.""" self.aboutDialog = AboutDialog() self.aboutDialog.set_GUI() self.aboutDialog.show()
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.models = { '朴素贝叶斯': NaiveBayes, '高斯贝叶斯': GaussianBayes, 'Fisher线性判别': Fisher, '神经网络模型': AlexnetTrainer } self.model_names = list(self.models.keys()) self.models2names = {v: k for k, v in self.models.items()} self.initUi() text = self.select_model_box.currentText() self.classfier = self.models[text](num_classes=10) if isinstance(self.classfier, AlexnetTrainer): self.classfier.load_model() self.statusbar.showMessage( f'当前模型是 “{self.models2names[type(self.classfier)]}”模型,已经加载预训练模型,可直接测试。' ) else: self.statusbar.showMessage( f'当前模型是 “{self.models2names[type(self.classfier)]}”模型,请先按“训练”键进行训练,再进行测试。' ) def initUi(self): self.setWindowTitle("手写数字识别DEMO") self.lastPoint = QPoint() # 起始点 self.endPoint = QPoint() # 终点 # 窗口大小设置为600*500 self.resize(600, 410) # 画布大小为400*400,背景为白色 self.pix = QPixmap(400, 400) self.pix.fill(Qt.white) self.pix_lab = QLabel() self.pix_lab.setPixmap(self.pix) self.bin_pix = QPixmap(100, 100) self.bin_pix.fill(Qt.white) self.bin_pix_lab = QLabel() self.bin_pix_lab.setPixmap(self.bin_pix) self.train_button = QPushButton(self) self.test_button = QPushButton(self) self.clear_button = QPushButton(self) self.save_button = QPushButton(self) self.train_button.setText('训练') self.train_button.clicked.connect(self.train) self.test_button.setText('测试') self.test_button.clicked.connect(self.test) self.clear_button.setText('清除') self.clear_button.clicked.connect(self.clear) self.save_button.setText('保存') self.save_button.clicked.connect(self.save) self.select_model_box = QComboBox() for model_name in self.model_names: self.select_model_box.addItem(model_name) self.select_model_box.currentIndexChanged.connect(self.selectionchange) self.pred_box = QLabel() ft = QFont() ft.setPointSize(24) self.pred_box.setFont(ft) left_widget = self.create_left_widget() right_widget = self.create_right_widget() main_layout = QHBoxLayout() main_layout.addWidget(left_widget) main_layout.addWidget(right_widget) main_layout.setStretch(0, 1) main_layout.setStretch(1, 4) main_widget = QWidget() main_widget.setLayout(main_layout) self.statusbar = QStatusBar(self) self.setStatusBar(self.statusbar) self.statusbar.setSizeGripEnabled(False) self.setCentralWidget(main_widget) def create_left_widget(self): left_widget = QGroupBox('请在此处画图:') left_layout = QVBoxLayout() left_layout.addWidget(self.pix_lab) left_layout.addStretch(5) left_widget.setLayout(left_layout) return left_widget def create_right_widget(self): upper_right_widget = QGroupBox("二值化后图像:") upper_right_layout = QVBoxLayout() upper_right_layout.addWidget(self.bin_pix_lab) upper_right_widget.setLayout(upper_right_layout) mid_right_widget = QGroupBox('模型识别的数字:') mid_right_layout = QVBoxLayout() mid_right_layout.addWidget(self.pred_box) mid_right_widget.setLayout(mid_right_layout) lower_right_widget = QGroupBox("选择操作") lower_right_layout = QVBoxLayout() lower_right_layout.addWidget(QLabel("请选择模型:")) lower_right_layout.addWidget(self.select_model_box) lower_right_layout.addWidget(self.train_button) lower_right_layout.addWidget(self.test_button) lower_right_layout.addWidget(self.clear_button) lower_right_layout.addWidget(self.save_button) lower_right_layout.addStretch(5) lower_right_widget.setLayout(lower_right_layout) right_layout = QVBoxLayout() right_layout.addWidget(upper_right_widget) right_layout.addWidget(mid_right_widget) right_layout.addWidget(lower_right_widget) right_layout.setStretch(0, 1) right_layout.setStretch(1, 2) right_widget = QWidget() right_widget.setLayout(right_layout) return right_widget # 重绘的复写函数 主要在这里绘制 def paintEvent(self, event): pp = QPainter(self.pix) # a little ugly here. pp.setWindow(18, 33, 400, 400) pen = QPen() # 定义笔格式对象 pen.setWidth(15) # 设置笔的宽度 pp.setPen(pen) # 将笔格式赋值给 画笔 # 根据鼠标指针前后两个位置绘制直线 if self.lastPoint != self.endPoint: pp.drawLine(self.lastPoint, self.endPoint) # 让前一个坐标值等于后一个坐标值, # 这样就能实现画出连续的线 self.lastPoint = self.endPoint self.pix_lab.setPixmap(self.pix) # 鼠标按压事件 def mousePressEvent(self, event): # 鼠标左键按下 if event.button() == Qt.LeftButton: self.lastPoint = event.pos() self.endPoint = self.lastPoint # 鼠标移动事件 def mouseMoveEvent(self, event): # 鼠标左键按下的同时移动鼠标 if event.buttons() and Qt.LeftButton: self.endPoint = event.pos() # 进行重新绘制 self.update() # 鼠标释放事件 def mouseReleaseEvent(self, event): # 鼠标左键释放 if event.button() == Qt.LeftButton: self.endPoint = event.pos() # 进行重新绘制 self.update() def selectionchange(self, i): text = self.select_model_box.currentText() self.classfier = self.models[text](num_classes=10) if isinstance(self.classfier, AlexnetTrainer): self.classfier.load_model() self.statusbar.showMessage( f'当前模型是 “{self.models2names[type(self.classfier)]}”模型,已经加载预训练模型,可直接测试。' ) else: self.statusbar.showMessage( f'当前模型是 “{self.models2names[type(self.classfier)]}”模型,请先按“训练”键进行训练,再进行测试。' ) def train(self): msg = '' if DATASET == 'mnist': msg = '开始训练模型,使用完整的mnist数据集,请稍等几分钟……' elif DATASET == 'mnist_100': msg = '开始训练模型,每类使用mnist的张图片,请稍等……' else: msg = '开始训练模型,请稍等……' self.statusbar.showMessage(msg) # need to repait. self.repaint() self.classfier.train(f'./data/{DATASET}/training') self.statusbar.showMessage('训练结束,可以进行测试。') def test(self, ): qimg = self.pix.toImage() img = self.pix2img(qimg) pred_class = self.classfier.test(img) # print(pred_class) self.pred_box.setText(str(pred_class)) # show the bin img if not isinstance(self.classfier, AlexnetTrainer): bin_img = self.classfier.binary_img.astype(np.uint8) w = bin_img.shape[1] h = bin_img.shape[0] qimg = QImage(bin_img.data, w, h, w, QImage.Format_Grayscale8) qimg = qimg.scaled(100, 100) self.bin_pix = QPixmap().fromImage(qimg) self.bin_pix_lab.setPixmap(self.bin_pix) self.update() def save(self, ): qimg = self.pix.toImage() img = self.pix2img(qimg) collect_data_dir = "./data/collect_data" for i in range(10): sub_data_dir = os.path.join(collect_data_dir, str(i)) if not os.path.exists(sub_data_dir): os.makedirs(sub_data_dir) get_dir_path = QFileDialog.getExistingDirectory( self, "选取指定文件夹", collect_data_dir) rel_dir_path = os.path.relpath( get_dir_path) # avoid chinese char in path img_num = len(os.listdir(rel_dir_path)) cv.imwrite(os.path.join(rel_dir_path, '{}.jpg'.format(img_num)), img) def clear(self, ): self.pix.fill(Qt.white) self.bin_pix.fill(Qt.white) self.bin_pix_lab.setPixmap(self.bin_pix) self.update() def pix2img(self, qimg): temp_shape = (qimg.height(), qimg.bytesPerLine() * 8 // qimg.depth()) temp_shape += (4, ) ptr = qimg.bits() ptr.setsize(qimg.byteCount()) img = np.array(ptr, dtype=np.uint8).reshape(temp_shape) img = img[..., :3] return img
class ConfigParamEditWindow(QDialog): def __init__(self, parent, node, target_node_id, param_struct, update_callback): super(ConfigParamEditWindow, self).__init__(parent) self.setWindowTitle('Edit configuration parameter') self.setModal(True) self._node = node self._target_node_id = target_node_id self._param_struct = param_struct self._update_callback = update_callback min_val = get_union_value(param_struct.min_value) if 'uavcan.protocol.param.Empty' in str(min_val): min_val = None max_val = get_union_value(param_struct.max_value) if 'uavcan.protocol.param.Empty' in str(max_val): max_val = None value = get_union_value(param_struct.value) self._value_widget = None value_type = uavcan.get_active_union_field(param_struct.value) if value_type == 'integer_value': min_val = min_val if min_val is not None else -0x8000000000000000 max_val = max_val if max_val is not None else 0x7FFFFFFFFFFFFFFF if min_val >= -0x80000000 and \ max_val <= +0x7FFFFFFF: self._value_widget = QSpinBox(self) self._value_widget.setMaximum(max_val) self._value_widget.setMinimum(min_val) self._value_widget.setValue(value) if value_type == 'real_value': min_val = round_float( min_val) if min_val is not None else -3.4028235e+38 max_val = round_float( max_val) if max_val is not None else 3.4028235e+38 value = round_float(value) if value_type == 'boolean_value': self._value_widget = QCheckBox(self) self._value_widget.setChecked(bool(value)) if self._value_widget is None: self._value_widget = QLineEdit(self) self._value_widget.setText(str(value)) self._value_widget.setFont(get_monospace_font()) layout = QGridLayout(self) def add_const_field(label, *values): row = layout.rowCount() layout.addWidget(QLabel(label, self), row, 0) if len(values) == 1: layout.addWidget(FieldValueWidget(self, values[0]), row, 1) else: sub_layout = QHBoxLayout(self) for idx, v in enumerate(values): sub_layout.addWidget(FieldValueWidget(self, v)) layout.addLayout(sub_layout, row, 1) add_const_field('Name', param_struct.name) add_const_field( 'Type', uavcan.get_active_union_field(param_struct.value).replace( '_value', '')) add_const_field('Min/Max', min_val, max_val) add_const_field('Default', render_union(param_struct.default_value)) layout.addWidget(QLabel('Value', self), layout.rowCount(), 0) layout.addWidget(self._value_widget, layout.rowCount() - 1, 1) fetch_button = make_icon_button('refresh', 'Read parameter from the node', self, text='Fetch', on_clicked=self._do_fetch) set_default_button = make_icon_button('fire-extinguisher', 'Restore default value', self, text='Restore', on_clicked=self._restore_default) send_button = make_icon_button('flash', 'Send parameter to the node', self, text='Send', on_clicked=self._do_send) cancel_button = make_icon_button( 'remove', 'Close this window; unsent changes will be lost', self, text='Cancel', on_clicked=self.close) controls_layout = QGridLayout(self) controls_layout.addWidget(fetch_button, 0, 0) controls_layout.addWidget(send_button, 0, 1) controls_layout.addWidget(set_default_button, 1, 0) controls_layout.addWidget(cancel_button, 1, 1) layout.addLayout(controls_layout, layout.rowCount(), 0, 1, 2) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout.addWidget(self._status_bar, layout.rowCount(), 0, 1, 2) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout) def show_message(self, text, *fmt): self._status_bar.showMessage(text % fmt) def _assign(self, value_union): value = get_union_value(value_union) if uavcan.get_active_union_field(value_union) == 'real_value': value = round_float(value) if hasattr(self._value_widget, 'setValue'): self._value_widget.setValue(value) self._update_callback(value) elif hasattr(self._value_widget, 'setChecked'): self._value_widget.setChecked(bool(value)) self._update_callback(bool(value)) else: self._value_widget.setText(str(value)) self._update_callback(value) def _on_response(self, e): if e is None: self.show_message('Request timed out') else: logger.info('Param get/set response: %s', e.response) self._assign(e.response.value) self.show_message('Response received') def _restore_default(self): self._assign(self._param_struct.default_value) def _do_fetch(self): try: request = uavcan.protocol.param.GetSet.Request( name=self._param_struct.name) self._node.request(request, self._target_node_id, self._on_response, priority=REQUEST_PRIORITY) except Exception as ex: show_error('Node error', 'Could not send param get request', ex, self) else: self.show_message('Fetch request sent') def _do_send(self): value_type = uavcan.get_active_union_field(self._param_struct.value) try: if value_type == 'integer_value': if hasattr(self._value_widget, 'value'): value = int(self._value_widget.value()) else: value = int(self._value_widget.text()) self._param_struct.value.integer_value = value elif value_type == 'real_value': value = float(self._value_widget.text()) self._param_struct.value.real_value = value elif value_type == 'boolean_value': value = bool(self._value_widget.isChecked()) self._param_struct.value.boolean_value = value elif value_type == 'string_value': value = self._value_widget.text() self._param_struct.value.string_value = value else: raise RuntimeError('This is not happening!') except Exception as ex: show_error('Format error', 'Could not parse value', ex, self) return try: request = uavcan.protocol.param.GetSet.Request( name=self._param_struct.name, value=self._param_struct.value) logger.info('Sending param set request: %s', request) self._node.request(request, self._target_node_id, self._on_response, priority=REQUEST_PRIORITY) except Exception as ex: show_error('Node error', 'Could not send param set request', ex, self) else: self.show_message('Set request sent')
class BondEditor(ToolInstance): help = "https://github.com/QChASM/SEQCROW/wiki/Bond-Editor-Tool" SESSION_ENDURING = True SESSION_SAVE = True def __init__(self, session, name): super().__init__(session, name) self.tool_window = MainToolWindow(self) self.settings = _BondEditorSettings(session, name) self._build_ui() def _build_ui(self): layout = QGridLayout() tabs = QTabWidget() layout.addWidget(tabs) ts_bond_tab = QWidget() ts_options = QFormLayout(ts_bond_tab) self.tsbond_color = ColorButton(has_alpha_channel=False, max_size=(16, 16)) self.tsbond_color.set_color(self.settings.tsbond_color) ts_options.addRow("color:", self.tsbond_color) self.tsbond_transparency = QSpinBox() self.tsbond_transparency.setRange(1, 99) self.tsbond_transparency.setValue(self.settings.tsbond_transparency) self.tsbond_transparency.setSuffix("%") ts_options.addRow("transparency:", self.tsbond_transparency) self.tsbond_radius = QDoubleSpinBox() self.tsbond_radius.setRange(0.01, 1) self.tsbond_radius.setDecimals(3) self.tsbond_radius.setSingleStep(0.005) self.tsbond_radius.setSuffix(" \u212B") self.tsbond_radius.setValue(self.settings.tsbond_radius) ts_options.addRow("radius:", self.tsbond_radius) draw_tsbonds = QPushButton("draw TS bonds on selected atoms/bonds") draw_tsbonds.clicked.connect(self.run_tsbond) ts_options.addRow(draw_tsbonds) self.draw_tsbonds = draw_tsbonds erase_tsbonds = QPushButton("erase selected TS bonds") erase_tsbonds.clicked.connect(self.run_erase_tsbond) ts_options.addRow(erase_tsbonds) self.erase_tsbonds = erase_tsbonds bond_tab = QWidget() bond_options = QFormLayout(bond_tab) self.bond_halfbond = QCheckBox() self.bond_halfbond.setChecked(self.settings.bond_halfbond) self.bond_halfbond.setToolTip( "each half of the bond will be colored according to the atom's color" ) bond_options.addRow("half-bond:", self.bond_halfbond) self.bond_color = ColorButton(has_alpha_channel=True, max_size=(16, 16)) self.bond_color.set_color(self.settings.bond_color) self.bond_color.setEnabled( self.bond_halfbond.checkState() != Qt.Checked) self.bond_halfbond.stateChanged.connect( lambda state, widget=self.bond_color: self.bond_color.setEnabled( state != Qt.Checked)) bond_options.addRow("color:", self.bond_color) self.bond_radius = QDoubleSpinBox() self.bond_radius.setRange(0.01, 1) self.bond_radius.setDecimals(3) self.bond_radius.setSingleStep(0.005) self.bond_radius.setSuffix(" \u212B") self.bond_radius.setValue(self.settings.bond_radius) bond_options.addRow("radius:", self.bond_radius) draw_tsbonds = QPushButton("draw bond between selected atoms") draw_tsbonds.clicked.connect(self.run_bond) bond_options.addRow(draw_tsbonds) self.draw_tsbonds = draw_tsbonds erase_bonds = QPushButton("erase selected bonds") erase_bonds.clicked.connect( lambda *, ses=self.session: run(ses, "delete bonds sel")) bond_options.addRow(erase_bonds) self.erase_bonds = erase_bonds hbond_tab = QWidget() hbond_options = QFormLayout(hbond_tab) self.hbond_color = ColorButton(has_alpha_channel=True, max_size=(16, 16)) self.hbond_color.set_color(self.settings.hbond_color) hbond_options.addRow("color:", self.hbond_color) self.hbond_radius = QDoubleSpinBox() self.hbond_radius.setDecimals(3) self.hbond_radius.setSuffix(" \u212B") self.hbond_radius.setValue(self.settings.hbond_radius) hbond_options.addRow("radius:", self.hbond_radius) self.hbond_dashes = QSpinBox() self.hbond_dashes.setRange(0, 28) self.hbond_dashes.setSingleStep(2) self.hbond_radius.setSingleStep(0.005) self.hbond_dashes.setValue(self.settings.hbond_dashes) hbond_options.addRow("dashes:", self.hbond_dashes) draw_hbonds = QPushButton("draw H-bonds") draw_hbonds.clicked.connect(self.run_hbond) hbond_options.addRow(draw_hbonds) self.draw_hbonds = draw_hbonds erase_hbonds = QPushButton("erase all H-bonds") erase_hbonds.clicked.connect( lambda *, ses=self.session: run(ses, "~hbonds")) hbond_options.addRow(erase_hbonds) self.erase_hbonds = erase_hbonds tm_bond_tab = QWidget() tm_bond_options = QFormLayout(tm_bond_tab) self.tm_bond_color = ColorButton(has_alpha_channel=True, max_size=(16, 16)) self.tm_bond_color.set_color(self.settings.tm_bond_color) tm_bond_options.addRow("color:", self.tm_bond_color) self.tm_bond_radius = QDoubleSpinBox() self.tm_bond_radius.setDecimals(3) self.tm_bond_radius.setSuffix(" \u212B") self.tm_bond_radius.setValue(self.settings.tm_bond_radius) tm_bond_options.addRow("radius:", self.tm_bond_radius) self.tm_bond_dashes = QSpinBox() self.tm_bond_dashes.setRange(0, 28) self.tm_bond_dashes.setSingleStep(2) self.tm_bond_radius.setSingleStep(0.005) self.tm_bond_dashes.setValue(self.settings.tm_bond_dashes) tm_bond_options.addRow("dashes:", self.tm_bond_dashes) draw_tm_bonds = QPushButton("draw metal coordination bonds") draw_tm_bonds.clicked.connect(self.run_tm_bond) tm_bond_options.addRow(draw_tm_bonds) self.draw_tm_bonds = draw_tm_bonds erase_tm_bonds = QPushButton("erase all metal coordination bonds") erase_tm_bonds.clicked.connect(self.del_tm_bond) tm_bond_options.addRow(erase_tm_bonds) self.erase_tm_bonds = erase_tm_bonds bond_length_tab = QWidget() bond_length_layout = QFormLayout(bond_length_tab) self.bond_distance = QDoubleSpinBox() self.bond_distance.setRange(0.5, 10.0) self.bond_distance.setSingleStep(0.05) self.bond_distance.setValue(1.51) self.bond_distance.setSuffix(" \u212B") bond_length_layout.addRow("bond length:", self.bond_distance) self.move_fragment = QComboBox() self.move_fragment.addItems(["both", "smaller", "larger"]) bond_length_layout.addRow("move side:", self.move_fragment) bond_lookup = QGroupBox("bond length lookup:") bond_lookup_layout = QGridLayout(bond_lookup) bond_lookup_layout.addWidget( QLabel("elements:"), 0, 0, ) self.ele1 = QPushButton("C") self.ele1.setMinimumWidth( int(1.3 * self.ele1.fontMetrics().boundingRect("QQ").width())) self.ele1.setMaximumWidth( int(1.3 * self.ele1.fontMetrics().boundingRect("QQ").width())) self.ele1.setMinimumHeight( int(1.5 * self.ele1.fontMetrics().boundingRect("QQ").height())) self.ele1.setMaximumHeight( int(1.5 * self.ele1.fontMetrics().boundingRect("QQ").height())) self.ele1.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) ele_color = tuple(list(element_color(ELEMENTS.index("C")))[:-1]) self.ele1.setStyleSheet( "QPushButton { background: rgb(%i, %i, %i); color: %s; font-weight: bold; }" % (*ele_color, 'white' if sum(int(x < 130) - int(x > 225) for x in ele_color) - int(ele_color[1] > 225) + int(ele_color[2] > 200) >= 2 else 'black')) self.ele1.clicked.connect( lambda *args, button=self.ele1: self.open_ptable(button)) bond_lookup_layout.addWidget(self.ele1, 0, 1, Qt.AlignRight | Qt.AlignTop) bond_lookup_layout.addWidget(QLabel("-"), 0, 2, Qt.AlignHCenter | Qt.AlignVCenter) self.ele2 = QPushButton("C") self.ele2.setMinimumWidth( int(1.3 * self.ele2.fontMetrics().boundingRect("QQ").width())) self.ele2.setMaximumWidth( int(1.3 * self.ele2.fontMetrics().boundingRect("QQ").width())) self.ele2.setMinimumHeight( int(1.5 * self.ele2.fontMetrics().boundingRect("QQ").height())) self.ele2.setMaximumHeight( int(1.5 * self.ele2.fontMetrics().boundingRect("QQ").height())) self.ele2.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) ele_color = tuple(list(element_color(ELEMENTS.index("C")))[:-1]) self.ele2.setStyleSheet( "QPushButton { background: rgb(%i, %i, %i); color: %s; font-weight: bold; }" % (*ele_color, 'white' if sum(int(x < 130) - int(x > 225) for x in ele_color) - int(ele_color[1] > 225) + int(ele_color[2] > 200) >= 2 else 'black')) self.ele2.clicked.connect( lambda *args, button=self.ele2: self.open_ptable(button)) bond_lookup_layout.addWidget(self.ele2, 0, 3, Qt.AlignLeft | Qt.AlignTop) bond_lookup_layout.addWidget(QLabel("bond order:"), 1, 0) self.bond_order = BondOrderSpinBox() self.bond_order.setRange(1., 3.) self.bond_order.setValue(1) self.bond_order.setSingleStep(0.5) self.bond_order.setDecimals(1) self.bond_order.valueChanged.connect(self.check_bond_lengths) bond_lookup_layout.addWidget(self.bond_order, 1, 1, 1, 3) bond_lookup_layout.setColumnStretch(0, 0) bond_lookup_layout.setColumnStretch(1, 0) bond_lookup_layout.setColumnStretch(2, 0) bond_lookup_layout.setColumnStretch(3, 1) bond_length_layout.addRow(bond_lookup) self.status = QStatusBar() self.status.setSizeGripEnabled(False) bond_lookup_layout.addWidget(self.status, 2, 0, 1, 4) self.do_bond_change = QPushButton("change selected bond lengths") self.do_bond_change.clicked.connect(self.change_bond_length) bond_length_layout.addRow(self.do_bond_change) tabs.addTab(bond_tab, "covalent bonds") tabs.addTab(ts_bond_tab, "TS bonds") tabs.addTab(hbond_tab, "H-bonds") tabs.addTab(tm_bond_tab, "coordination bonds") tabs.addTab(bond_length_tab, "bond length") self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def run_tsbond(self, *args): args = ["tsbond", "sel"] color = self.tsbond_color.get_color() args.extend(["color", "rgb(%i, %i, %i)" % tuple(color[:-1])]) self.settings.tsbond_color = tuple([c / 255. for c in color]) radius = self.tsbond_radius.value() args.extend(["radius", "%.3f" % radius]) self.settings.tsbond_radius = radius transparency = self.tsbond_transparency.value() args.extend(["transparency", "%i" % transparency]) self.settings.tsbond_transparency = transparency run(self.session, " ".join(args)) def run_erase_tsbond(self, *args): run(self.session, "~tsbond sel") def run_bond(self, *args): # TODO: switch to `bond sel` in 1.2 sel = selected_atoms(self.session) halfbond = self.bond_halfbond.checkState() == Qt.Checked self.settings.bond_halfbond = halfbond if not halfbond: color = self.bond_color.get_color() color = tuple(x / 255. for x in color) self.settings.bond_color = color radius = self.bond_radius.value() self.settings.bond_radius = radius for b in selected_bonds(self.session): b.halfbond = halfbond if not halfbond: b.color = np.array([int(x * 255) for x in color]) b.radius = radius for i, a1 in enumerate(sel): for a2 in sel[:i]: if a1.structure is a2.structure and a2 not in a1.neighbors: new_bond = a1.structure.new_bond(a1, a2) new_bond.halfbond = halfbond if not halfbond: new_bond.color = np.array( [int(x * 255) for x in color]) new_bond.radius = radius def run_hbond(self, *args): args = ["hbonds", "reveal", "true"] color = self.hbond_color.get_color() args.extend(["color", "rgb(%i, %i, %i)" % tuple(color[:-1])]) self.settings.hbond_color = tuple([c / 255. for c in color]) radius = self.hbond_radius.value() args.extend(["radius", "%.3f" % radius]) self.settings.hbond_radius = radius dashes = self.hbond_dashes.value() args.extend(["dashes", "%i" % dashes]) self.settings.hbond_dashes = dashes run(self.session, " ".join(args)) def run_tm_bond(self, *args): color = self.tm_bond_color.get_color() self.settings.tm_bond_color = tuple([c / 255. for c in color]) radius = self.tm_bond_radius.value() self.settings.tm_bond_radius = radius dashes = self.tm_bond_dashes.value() self.settings.tm_bond_dashes = dashes models = self.session.models.list(type=AtomicStructure) for model in models: rescol = ResidueCollection(model, bonds_matter=False) try: tm_list = rescol.find([ AnyTransitionMetal(), "Na", "K", "Rb", "Cs", "Fr", "Mg", "Ca", "Sr", "Ba", "Ra" ]) for tm in tm_list: for atom in rescol.atoms: if atom is tm: continue if atom.is_connected(tm): pbg = model.pseudobond_group( model.PBG_METAL_COORDINATION, create_type="normal") pbg.new_pseudobond(tm.chix_atom, atom.chix_atom) pbg.dashes = dashes pbg.color = color pbg.radius = radius except LookupError: pass def del_tm_bond(self, *args): models = self.session.models.list(type=AtomicStructure) for model in models: pbg = model.pseudobond_group(model.PBG_METAL_COORDINATION, create_type=None) if pbg is not None: pbg.delete() def open_ptable(self, button): self.tool_window.create_child_window("select element", window_class=PTable2, button=button, callback=self.check_bond_lengths) def check_bond_lengths(self, *args): ele1 = self.ele1.text() ele2 = self.ele2.text() key = ORDER_BOND_ORDER.key(ele1, ele2) order = "%.1f" % self.bond_order.value() if key in ORDER_BOND_ORDER.bonds and order in ORDER_BOND_ORDER.bonds[ key]: self.bond_distance.setValue(ORDER_BOND_ORDER.bonds[key][order]) self.status.showMessage("") else: self.status.showMessage("no bond data for %s-%s %sx bonds" % (ele1, ele2, order)) def change_bond_length(self, *args): dist = self.bond_distance.value() atom_pairs = [] sel = selected_atoms(self.session) if len(sel) == 2 and sel[0].structure is sel[1].structure: atom_pairs.append(sel) for bond in selected_bonds(self.session): if not all(atom in sel for atom in bond.atoms): atom_pairs.append(bond.atoms) for bond in selected_pseudobonds(self.session): if not all(atom in sel for atom in bond.atoms): atom_pairs.append(bond.atoms) for pair in atom_pairs: atom1, atom2 = pair frag1 = get_fragment(atom1, stop=atom2, max_len=atom1.structure.num_atoms) frag2 = get_fragment(atom2, stop=atom1, max_len=atom1.structure.num_atoms) v = atom2.coord - atom1.coord cur_dist = np.linalg.norm(v) change = dist - cur_dist if self.move_fragment.currentText() == "both": change = 0.5 * change frag1.coords -= change * v / cur_dist frag2.coords += change * v / cur_dist elif self.move_fragment.currentText() == "smaller": if len(frag1) < len(frag2) or (len(frag1) == len(frag2) and sum(frag1.elements.masses) < sum(frag2.elements.masses)): frag1.coords -= change * v / cur_dist else: frag2.coords += change * v / cur_dist elif self.move_fragment.currentText() == "larger": if len(frag1) > len(frag2) or (len(frag1) == len(frag2) and sum(frag1.elements.masses) > sum(frag2.elements.masses)): frag1.coords -= change * v / cur_dist else: frag2.coords += change * v / cur_dist
class MainWindow(QWidget): def __init__(self): super(MainWindow, self).__init__() self.create_elements() self.set_property() self.add_elements() self.clean_file_selection() self.add_connect() self.STOP = False def create_elements(self): self.mainLayout = QVBoxLayout() self.fileLayout = QHBoxLayout() self.fileLine = QLineEdit() self.fileButton = QPushButton('Open') self.unselectButton = QPushButton('Clean Selections') self.inverseButton = QPushButton('Inverse') self.fileDialog = QFileDialog(self) self.inverseThread = InverseThread(self) self.statusBar = QStatusBar(self) self.progressBar = QProgressBar() def set_property(self): # set main window properties self.setWindowTitle('Pdf Inverse') self.resize(600, 200) # set widget properties self.fileButton.setToolTip( 'Click here to open one or more pdf files ro inverse color') self.inverseButton.setToolTip('Click here to start inverse') self.fileLine.setReadOnly(True) self.statusBar.setSizeGripEnabled(False) def add_elements(self): self.setLayout(self.mainLayout) self.mainLayout.addLayout(self.fileLayout) self.fileLayout.addWidget(self.fileLine) self.fileLayout.addWidget(self.fileButton) self.fileLayout.addWidget(self.unselectButton) self.mainLayout.addWidget(self.inverseButton) self.mainLayout.addWidget(self.progressBar) def add_connect(self): self.fileButton.clicked.connect(self.open_file_dialog) self.unselectButton.clicked.connect(self.clean_file_selection) self.inverseButton.clicked.connect(self.inverse) @pyqtSlot() def open_file_dialog(self): self.pdf_path.extend( self.fileDialog.getOpenFileNames(self, 'Choose files to inverse', '.', '*.pdf')[0]) self.fileLine.setText(str(self.pdf_path)) self.inverseThread.set_pdf_path(self.pdf_path) @pyqtSlot() def clean_file_selection(self): self.pdf_path = [] self.fileLine.setText(str(self.pdf_path)) self.inverseThread.set_pdf_path(self.pdf_path) @pyqtSlot() def inverse(self): self.fileButton.setEnabled(False) self.unselectButton.setEnabled(False) self.inverseButton.setText('Processing...Click to stop') self.inverseButton.clicked.disconnect(self.inverse) self.inverseButton.clicked.connect(self.stop) self.inverseThread.start() @pyqtSlot() def stop(self): self.STOP = True self.inverseButton.clicked.disconnect(self.stop) self.inverseButton.clicked.connect(self.inverse) self.inverseButton.setEnabled(False) self.inverseButton.setText('Stopping...')
class MainWindow(QtWidgets.QMainWindow): """ This class is the main window of the program, containing all the graphics objects such as the toolbar, the state bar, the menu and the canvas scene. Attributes ---------- SYSNAME : str The application name displayed in the window. nav_menu_bar : QMenuBar Menu bar of the window, containing different menus. status_bar : QStatusBar Status bar of the window. toolbar : BlocksToolbar Toolbar appearing on the left of the window, showing several buttons to add elements to the canvas. parameters : ParamToolbar Fixed toolbar on the right of the window, displaying details about a certain block. canvas : Canvas Central view of the window, containing a blank space in which the blocks appear. Methods ---------- connect_events() Connects to all signals of the elements. init_menu_bar() Sets all menus of the menu bar and their actions. update_status() Changes the status bar displaying on it the canvas mode and the selected items. change_draw_mode(DrawingMode) Changes the drawing mode of the canvas. create_from(NodeButton) Draws in the canvas the block corresponding to the button pressed. reset() Clears both graphical and logical network. open() Procedure to open an existing network. save(bool) Saves the current network in a new file or in the opened one. """ def __init__(self): super(MainWindow, self).__init__() # Init window appearance self.SYSNAME = "NeVer 2" self.setWindowTitle(self.SYSNAME) self.setWindowIcon(QtGui.QIcon(ROOT_DIR + '/res/icons/logo.png')) self.setStyleSheet("background-color: " + style.GREY_1) # Navigation menu self.nav_menu_bar = self.menuBar() self.init_nav_menu_bar() # Blocks toolbar self.toolbar = BlocksToolbar(ROOT_DIR + '/res/json/blocks.json') # Parameters toolbar self.parameters = ParamToolbar() # Drawing Canvas self.canvas = Canvas(self.toolbar.blocks) # Status bar self.status_bar = QStatusBar() self.status_bar.setSizeGripEnabled(False) self.setStatusBar(self.status_bar) self.status_bar.setStyleSheet(style.STATUS_BAR_STYLE) self.status_bar_mode_label = CustomLabel() self.status_bar_mode_label.setStyleSheet(style.STATUS_BAR_WIDGET_STYLE) self.status_bar_selections_label = CustomLabel() self.status_bar_selections_label.setStyleSheet(style.STATUS_BAR_WIDGET_STYLE) self.status_bar.addPermanentWidget(self.status_bar_selections_label) self.status_bar.addPermanentWidget(self.status_bar_mode_label) # And adding them to the window self.addDockWidget(Qt.RightDockWidgetArea, self.parameters, Qt.Vertical) self.addToolBar(QtCore.Qt.ToolBarArea.LeftToolBarArea, self.toolbar) self.setCentralWidget(self.canvas.view) self.connect_events() def connect_events(self): """ Associate the various events coming from button signals and other graphical objects to the correct actions. """ # Block buttons for b in itertools.chain(self.toolbar.b_buttons.values(), self.toolbar.p_buttons.values()): b.clicked.connect(self.create_from(b)) # Draw line button self.toolbar.f_buttons["draw_line"].clicked \ .connect(lambda: self.change_draw_mode(DrawingMode.DRAW_LINE)) # Insert block button self.toolbar.f_buttons["insert_block"].clicked \ .connect(lambda: self.change_draw_mode(DrawingMode.DRAW_BLOCK)) # Parameters box appearing self.canvas.param_requested \ .connect(lambda: self.parameters.display(self.canvas.block_to_show)) # State bar updating self.canvas.scene.has_changed_mode \ .connect(lambda: self.update_status()) self.canvas.scene.selectionChanged \ .connect(lambda: self.update_status()) def init_nav_menu_bar(self): """ This method creates the navigation bar by adding the menus and corresponding actions. """ self.nav_menu_bar.setStyleSheet(style.MENU_BAR_STYLE) self.setContextMenuPolicy(Qt.PreventContextMenu) actions_dict = dict() with open(ROOT_DIR + '/res/json/menu.json') as json_menu: menu = json.loads(json_menu.read()) for menu_item, actions in menu.items(): entry = self.nav_menu_bar.addMenu(menu_item) entry.setStyleSheet(style.MENU_BAR_STYLE) for a, v in actions.items(): action_item = QAction(a, self) if "Shortcut" in v.keys(): action_item.setShortcut(v["Shortcut"]) if v["checkable"] == "True": action_item.setCheckable(True) action_item.setChecked(True) entry.addAction(action_item) actions_dict[f"{menu_item}:{a}"] = action_item # Triggers connection actions_dict["File:New..."].triggered.connect(lambda: self.reset()) actions_dict["File:Open..."].triggered.connect(lambda: self.open()) actions_dict["File:Load property..."].triggered.connect(lambda: self.canvas.project.open_property()) actions_dict["File:Save"].triggered.connect(lambda: self.save(False)) actions_dict["File:Save as..."].triggered.connect(lambda: self.save()) actions_dict["File:Exit"].triggered.connect(lambda: self.close()) actions_dict["Edit:Copy"].triggered.connect(lambda: self.canvas.copy_selected()) actions_dict["Edit:Paste"].triggered.connect(lambda: self.canvas.paste_selected()) actions_dict["Edit:Cut"].triggered.connect(lambda: self.canvas.cut_selected()) actions_dict["Edit:Delete"].triggered.connect(lambda: self.canvas.delete_selected()) actions_dict["Edit:Clear canvas"].triggered.connect(lambda: self.clear()) actions_dict["Edit:Draw connection"].triggered.connect(lambda: self.change_draw_mode(DrawingMode.DRAW_LINE)) actions_dict["Edit:Edit node"].triggered.connect( lambda: self.canvas.scene.edit_node(self.edit_action_validation())) actions_dict["View:Zoom in"].triggered.connect(lambda: self.canvas.zoom_in()) actions_dict["View:Zoom out"].triggered.connect(lambda: self.canvas.zoom_out()) actions_dict["View:Dimensions"].toggled.connect(lambda: self.canvas.scene.switch_dim_visibility()) actions_dict["View:Tools"].toggled.connect(lambda: self.toolbar.change_tools_mode()) actions_dict["View:Blocks"].toggled.connect(lambda: self.toolbar.change_blocks_mode()) actions_dict["View:Parameters"].triggered.connect( lambda: self.canvas.show_parameters(self.parameters_action_validation())) actions_dict["Learning:Train..."].triggered.connect(lambda: self.canvas.train_network()) actions_dict["Learning:Prune..."].triggered.connect(lambda: self.temp_window()) actions_dict["Verification:Verify..."].triggered.connect(lambda: self.canvas.verify_network()) actions_dict["Verification:Repair..."].triggered.connect(lambda: self.temp_window()) actions_dict["Help:Show guide"].triggered.connect(lambda: self.show_help()) @staticmethod def temp_window(): dialog = MessageDialog("Work in progress...", MessageType.MESSAGE) dialog.exec() def create_from(self, button: QPushButton): """ This method draws on the canvas the block corresponding to the pressed BlockButton. Parameters ---------- button : CustomButton The pressed button. """ def pressed(): if isinstance(button, NodeButton): self.canvas.draw_node(button.node_type) elif isinstance(button, PropertyButton): if self.canvas.project.network.nodes: self.canvas.draw_property(button.name) return pressed def update_status(self): """ This method updates the widget in the status bar, displaying the items selected and the current drawing mode. """ # Show the canvas drawing mode if self.canvas.scene.mode == DrawingMode.DRAW_LINE: self.status_bar_mode_label.setText("GraphicLine drawing") elif self.canvas.scene.mode == DrawingMode.DRAW_BLOCK: self.status_bar_mode_label.setText("Block insertion") else: self.status_bar_mode_label.setText("") # Show the selected items, if any if not self.canvas.scene.selectedItems(): self.status_bar_selections_label.setText("") else: selections = "" semicolons = ["; " for _ in range(len(self.canvas.scene.selectedItems()))] semicolons[-1] = "" # No semicolon for the last element in the selections list for counter, item in enumerate(self.canvas.scene.selectedItems()): if type(item) is QGraphicsRectItem: # If the item is a rect, prev_node_id is written selections += self.canvas.scene.blocks[item].block_id selections += semicolons[counter] elif type(item) is GraphicLine: # If the item is a line, origin and destination ids are written origin = self.canvas.scene.blocks[item.origin].block_id destination = self.canvas.scene.blocks[item.destination].block_id selections += origin + "->" + destination selections += semicolons[counter] self.status_bar_selections_label.setText(selections) def change_draw_mode(self, newmode: DrawingMode = None): """ This method changes the drawing mode of the canvas when the user clicks on the corresponding button. The mode changes depending on the previous one. Parameters ---------- newmode : DrawingMode, optional Specifies the new DrawingMode to use. (Default: None) """ if newmode is None: self.canvas.scene.set_mode(DrawingMode.IDLE) else: curmode = self.canvas.scene.mode if newmode == curmode: self.canvas.scene.set_mode(DrawingMode.IDLE) else: self.canvas.scene.set_mode(newmode) def clear(self): """ Utility for deleting the content of the window. Before taking effect, it prompts the user to confirm. """ if self.canvas.num_nodes > 0: alert_dialog = ConfirmDialog("Clear workspace", "The network will be erased and your work will be lost.\n" "Do you wish to continue?") alert_dialog.exec() if alert_dialog.confirm: self.canvas.clear_scene() self.canvas.scene.has_changed_mode.connect(lambda: self.update_status()) self.canvas.scene.selectionChanged.connect(lambda: self.update_status()) self.update_status() self.setWindowTitle(self.SYSNAME) else: self.canvas.clear_scene() self.canvas.scene.has_changed_mode.connect(lambda: self.update_status()) self.canvas.scene.selectionChanged.connect(lambda: self.update_status()) self.update_status() self.setWindowTitle(self.SYSNAME) def reset(self): """ This method clears the scene and the network, stops to work on the file and restarts from scratch. """ self.clear() self.canvas.project.file_name = ("", "") self.setWindowTitle(self.SYSNAME) def open(self): """ This method handles the opening of a file. """ if self.canvas.renderer.disconnected_network: # If there is already a network in the canvas, it is asked to the # user if continuing with opening. confirm_dialog = ConfirmDialog("Open network", "A new network will be opened " "cancelling the current nodes.\n" "Do you wish to continue?") confirm_dialog.exec() # If the user clicks on "yes", the canvas is cleaned, a net is # opened and the window title is updated. if confirm_dialog is not None: if confirm_dialog.confirm: # The canvas is cleaned self.canvas.clear_scene() self.canvas.scene.has_changed_mode.connect(lambda: self.update_status()) self.canvas.scene.selectionChanged.connect(lambda: self.update_status()) self.update_status() # A file is opened self.canvas.project.open() if self.canvas.project.network is not None: self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) else: # If the canvas was already empty, the opening function is directly # called self.canvas.project.open() if self.canvas.project.network is not None: self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) def save(self, _as: bool = True): """ This method saves the current network if the format is correct Parameters ---------- _as : bool, optional This attribute distinguishes between "save" and "save as". If _as is True the network will be saved in a new file, while if _as is False the network will overwrite the current one. (Default: True) """ if len(self.canvas.renderer.NN.nodes) == 0 or \ len(self.canvas.renderer.NN.edges) == 0: # Limit case: one disconnected node -> new network with one node if len(self.canvas.renderer.disconnected_network) == 1: for node in self.canvas.renderer.disconnected_network: try: self.canvas.renderer.add_node_to_nn(node) self.canvas.project.save(_as) self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) except Exception as e: error_dialog = MessageDialog(str(e), MessageType.ERROR) error_dialog.exec() # More than one disconnected nodes cannot be saved elif len(self.canvas.renderer.disconnected_network) > 1: not_sequential_dialog = MessageDialog("The network is not sequential, and " "cannot be saved.", MessageType.ERROR) not_sequential_dialog.exec() else: # Network is empty message = MessageDialog("The network is empty!", MessageType.MESSAGE) message.exec() elif self.canvas.renderer.is_nn_sequential(): # If there are logical nodes, the network is sequential every_node_connected = True # every node has to be in the nodes dictionary for node in self.canvas.renderer.disconnected_network: if node not in self.canvas.project.network.nodes: every_node_connected = False break if every_node_connected: self.canvas.project.save(_as) if self.canvas.project.network is not None: self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) else: # If there are disconnected nodes, a message is displayed to the # user to choose if saving only the connected network confirm_dialog = ConfirmDialog("Save network", "All the nodes outside the " "sequential network will lost.\n" "Do you wish to continue?") confirm_dialog.exec() if confirm_dialog.confirm: self.canvas.project.save(_as) if self.canvas.project.network is not None: self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) else: # If the network is not sequential, it cannot be saved. not_sequential_dialog = MessageDialog("The network is not sequential and " "cannot be saved.", MessageType.ERROR) not_sequential_dialog.exec() def edit_action_validation(self) -> Optional[NodeBlock]: """ This method performs a check on the object on which the edit action is called, in order to prevent unwanted operations. Returns ---------- NodeBlock The graphic wrapper of the NetworkNode selected, if present. """ if self.canvas.scene.selectedItems(): if type(self.canvas.scene.selectedItems()[0]) is QGraphicsRectItem: # Return block graphic object return self.canvas.scene.blocks[self.canvas.scene.selectedItems()[0]] elif type(self.canvas.scene.selectedItems()[0]) is GraphicLine: msg_dialog = MessageDialog("Can't edit edges, please select a block instead.", MessageType.ERROR) msg_dialog.exec() else: err_dialog = MessageDialog("No block selected.", MessageType.MESSAGE) err_dialog.exec() def parameters_action_validation(self) -> Optional[NodeBlock]: """ This method performs a check on the object on which the parameters action is called, in order to prevent unwanted operations. Returns ---------- NodeBlock The graphic wrapper of the NetworkNode selected, if present. """ if self.canvas.scene.selectedItems(): if type(self.canvas.scene.selectedItems()[0]) is QGraphicsRectItem: # Return block graphic object return self.canvas.scene.blocks[self.canvas.scene.selectedItems()[0]] elif type(self.canvas.scene.selectedItems()[0]) is GraphicLine: msg_dialog = MessageDialog("No parameters available for connections.", MessageType.ERROR) msg_dialog.exec() else: err_dialog = MessageDialog("No block selected.", MessageType.MESSAGE) err_dialog.exec() @staticmethod def show_help(): help_dialog = HelpDialog() help_dialog.exec()
class PrecisionRotate(ToolInstance): help = "https://github.com/QChASM/SEQCROW/wiki/Rotate-Tool" SESSION_ENDURING = True SESSION_SAVE = True def __init__(self, session, name): super().__init__(session, name) self.tool_window = MainToolWindow(self) self.settings = _PrecisionRotateSettings(session, name) self.bonds = {} self.bond_centers = {} self.groups = {} self.perpendiculars = {} self.perp_centers = {} self.manual_center = {} self._build_ui() self._show_rot_vec = self.session.triggers.add_handler( SELECTION_CHANGED, self.show_rot_vec) global_triggers = get_triggers() self._changes = global_triggers.add_handler("changes done", self.show_rot_vec) self.show_rot_vec() def _build_ui(self): layout = QGridLayout() layout.addWidget(QLabel("center of rotation:"), 0, 0, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) self.cor_button = QComboBox() self.cor_button.addItems( ["automatic", "select atoms", "view's center of rotation"]) layout.addWidget(self.cor_button, 0, 1, 1, 1, Qt.AlignTop) self.set_cor_selection = QPushButton("set selection") self.cor_button.currentTextChanged.connect( lambda t, widget=self.set_cor_selection: widget.setEnabled( t == "select atoms")) self.set_cor_selection.clicked.connect(self.manual_cor) layout.addWidget(self.set_cor_selection, 0, 2, 1, 1, Qt.AlignTop) self.set_cor_selection.setEnabled(False) layout.addWidget(QLabel("rotation vector:"), 1, 0, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) self.vector_option = QComboBox() self.vector_option.addItems([ "axis", "view axis", "bond", "perpendicular to plane", "centroid of atoms", "custom" ]) layout.addWidget(self.vector_option, 1, 1, 1, 1, Qt.AlignVCenter) vector = QWidget() vector.setToolTip("vector will be normalized before rotating") vector_layout = QHBoxLayout(vector) vector_layout.setContentsMargins(0, 0, 0, 0) self.vector_x = QDoubleSpinBox() self.vector_y = QDoubleSpinBox() self.vector_z = QDoubleSpinBox() self.vector_z.setValue(1.0) for c, t in zip([self.vector_x, self.vector_y, self.vector_z], [" x", " y", " z"]): c.setSingleStep(0.01) c.setRange(-100, 100) # c.setSuffix(t) c.valueChanged.connect(self.show_rot_vec) vector_layout.addWidget(c) layout.addWidget(vector, 1, 2, 1, 1, Qt.AlignTop) vector.setVisible(self.vector_option.currentText() == "custom") self.vector_option.currentTextChanged.connect( lambda text, widget=vector: widget.setVisible(text == "custom")) self.view_axis = QComboBox() self.view_axis.addItems(["z", "y", "x"]) layout.addWidget(self.view_axis, 1, 2, 1, 1, Qt.AlignTop) self.view_axis.setVisible( self.vector_option.currentText() == "view axis") self.vector_option.currentTextChanged.connect( lambda text, widget=self.view_axis: widget.setVisible(text == "view axis")) self.axis = QComboBox() self.axis.addItems(["z", "y", "x"]) layout.addWidget(self.axis, 1, 2, 1, 1, Qt.AlignTop) self.axis.setVisible(self.vector_option.currentText() == "axis") self.vector_option.currentTextChanged.connect( lambda text, widget=self.axis: widget.setVisible(text == "axis")) self.bond_button = QPushButton("set selected bond") self.bond_button.clicked.connect(self.set_bonds) layout.addWidget(self.bond_button, 1, 2, 1, 1, Qt.AlignTop) self.bond_button.setVisible(self.vector_option.currentText() == "bond") self.vector_option.currentTextChanged.connect( lambda text, widget=self.bond_button: widget.setVisible(text == "bond")) self.perp_button = QPushButton("set selected atoms") self.perp_button.clicked.connect(self.set_perpendicular) layout.addWidget(self.perp_button, 1, 2, 1, 1, Qt.AlignTop) self.perp_button.setVisible( self.vector_option.currentText() == "perpendicular to plane") self.vector_option.currentTextChanged.connect( lambda text, widget=self.perp_button: widget.setVisible( text == "perpendicular to plane")) self.group_button = QPushButton("set selected atoms") self.group_button.clicked.connect(self.set_group) layout.addWidget(self.group_button, 1, 2, 1, 1, Qt.AlignTop) self.group_button.setVisible( self.vector_option.currentText() == "centroid of atoms") self.vector_option.currentTextChanged.connect( lambda text, widget=self.group_button: widget.setVisible( text == "centroid of atoms")) layout.addWidget(QLabel("angle:"), 2, 0, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) self.angle = QDoubleSpinBox() self.angle.setRange(-360, 360) self.angle.setSingleStep(5) self.angle.setSuffix("°") layout.addWidget(self.angle, 2, 1, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) layout.addWidget(QLabel("preview rotation axis:"), 3, 0, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) self.display_rot_vec = QCheckBox() self.display_rot_vec.setCheckState(Qt.Checked) self.display_rot_vec.stateChanged.connect(self.show_rot_vec) layout.addWidget(self.display_rot_vec, 3, 1, 1, 1, Qt.AlignLeft | Qt.AlignVCenter) rotate_button = QPushButton("rotate selected atoms") rotate_button.clicked.connect(self.do_rotate) layout.addWidget(rotate_button, 4, 0, 1, 3, Qt.AlignTop) self.rotate_button = rotate_button self.status_bar = QStatusBar() self.status_bar.setSizeGripEnabled(False) layout.addWidget(self.status_bar, 5, 0, 1, 3, Qt.AlignTop) self.vector_option.currentTextChanged.connect(self.show_auto_status) self.cor_button.currentIndexChanged.connect( lambda *args: self.show_auto_status("select atoms")) self.cor_button.currentIndexChanged.connect(self.show_rot_vec) self.set_cor_selection.clicked.connect(self.show_rot_vec) self.vector_option.currentIndexChanged.connect(self.show_rot_vec) self.axis.currentIndexChanged.connect(self.show_rot_vec) self.view_axis.currentIndexChanged.connect(self.show_rot_vec) self.bond_button.clicked.connect(self.show_rot_vec) self.perp_button.clicked.connect(self.show_rot_vec) self.group_button.clicked.connect(self.show_rot_vec) layout.setRowStretch(0, 0) layout.setRowStretch(1, 0) layout.setRowStretch(2, 0) layout.setRowStretch(3, 0) layout.setRowStretch(4, 0) layout.setRowStretch(5, 1) layout.setColumnStretch(0, 0) layout.setColumnStretch(1, 1) layout.setColumnStretch(2, 1) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def manual_cor(self, *args): selection = selected_atoms(self.session) models = {} for atom in selection: if atom.structure not in models: models[atom.structure] = [atom] else: models[atom.structure].append(atom) self.manual_center = {} for model in models: atoms = models[model] coords = np.array([atom.coord for atom in atoms]) self.manual_center[model] = np.mean(coords, axis=0) def show_auto_status(self, text): if self.cor_button.currentText() == "automatic": if text == "bond": self.status_bar.showMessage( "center set to one of the bonded atoms") elif text == "perpendicular to plane": self.status_bar.showMessage("center set to centroid of atoms") else: self.status_bar.showMessage( "center set to centroid of rotating atoms") elif self.cor_button.currentText() == "select atoms": self.status_bar.showMessage( "center set to centroid of specified atoms") else: self.status_bar.showMessage( "center set to view's center of rotation") def set_bonds(self, *args): bonds = selected_bonds(self.session) if len(bonds) == 0: self.session.logger.error("no bonds selected") return models = [bond.structure for bond in bonds] if any(models.count(m) > 1 for m in models): self.session.logger.error( "multiple bonds selected on the same structure") return self.bonds = { model: (bond.atoms[0].coord - bond.atoms[1].coord) for model, bond in zip(models, bonds) } self.bond_centers = { model: bond.atoms[1].coord for model, bond in zip(models, bonds) } def set_perpendicular(self, *args): atoms = selected_atoms(self.session) if len(atoms) == 0: self.session.logger.error("no atoms selected") return self.perpendiculars = {} self.perp_centers = {} models = set(atom.structure for atom in atoms) for model in models: atom_coords = [] for atom in atoms: if atom.structure is model: atom_coords.append(atom.coord) if len(atom_coords) < 3: self.session.logger.error("fewer than 3 atoms selected on %s" % model.atomspec) continue xyz = np.array(atom_coords) xyz -= np.mean(atom_coords, axis=0) R = np.dot(xyz.T, xyz) u, s, vh = np.linalg.svd(R, compute_uv=True) vector = u[:, -1] self.perpendiculars[model] = vector self.perp_centers[model] = np.mean(atom_coords, axis=0) def set_group(self, *args): atoms = selected_atoms(self.session) if len(atoms) == 0: self.session.logger.error("no atoms selected") return self.groups = {} models = set(atom.structure for atom in atoms) for model in models: atom_coords = [] for atom in atoms: if atom.structure is model: atom_coords.append(atom.coord) self.groups[model] = np.mean(atom_coords, axis=0) def do_rotate(self, *args): selection = selected_atoms(self.session) models = {} for atom in selection: if atom.structure not in models: models[atom.structure] = [atom] else: models[atom.structure].append(atom) if len(models.keys()) == 0: return if self.vector_option.currentText() == "axis": if self.axis.currentText() == "z": vector = np.array([0., 0., 1.]) elif self.axis.currentText() == "y": vector = np.array([0., 1., 0.]) elif self.axis.currentText() == "x": vector = np.array([1., 0., 0.]) elif self.vector_option.currentText() == "view axis": if self.view_axis.currentText() == "z": vector = self.session.view.camera.get_position().axes()[2] elif self.view_axis.currentText() == "y": vector = self.session.view.camera.get_position().axes()[1] elif self.view_axis.currentText() == "x": vector = self.session.view.camera.get_position().axes()[0] elif self.vector_option.currentText() == "bond": vector = self.bonds elif self.vector_option.currentText() == "perpendicular to plane": vector = self.perpendiculars elif self.vector_option.currentText() == "centroid of atoms": vector = self.groups elif self.vector_option.currentText() == "custom": x = self.vector_x.value() y = self.vector_y.value() z = self.vector_z.value() vector = np.array([x, y, z]) angle = np.deg2rad(self.angle.value()) center = {} for model in models: atoms = models[model] coords = np.array([atom.coord for atom in atoms]) center[model] = np.mean(coords, axis=0) if self.cor_button.currentText() == "automatic": if self.vector_option.currentText() == "perpendicular to plane": center = self.perp_centers elif self.vector_option.currentText() == "bond": center = self.bond_centers elif self.cor_button.currentText() == "select atoms": center = self.manual_center else: center = self.session.main_view.center_of_rotation for model in models: if isinstance(vector, dict): if model not in vector.keys(): continue else: v = vector[model] else: v = vector if isinstance(center, dict): if model not in center.keys(): continue else: c = center[model] else: c = center if self.vector_option.currentText( ) == "centroid of atoms" and self.cor_button.currentText( ) != "automatic": v = v - c v = v / np.linalg.norm(v) q = np.hstack(([np.cos(angle / 2)], v * np.sin(angle / 2))) q /= np.linalg.norm(q) qs = q[0] qv = q[1:] xyz = np.array([a.coord for a in models[model]]) xyz -= c xprod = np.cross(qv, xyz) qs_xprod = 2 * qs * xprod qv_xprod = 2 * np.cross(qv, xprod) xyz += qs_xprod + qv_xprod + c for t, coord in zip(models[model], xyz): t.coord = coord def show_rot_vec(self, *args): for model in self.session.models.list(type=Generic3DModel): if model.name == "rotation vector": model.delete() if self.display_rot_vec.checkState() == Qt.Unchecked: return selection = selected_atoms(self.session) if len(selection) == 0: return models = {} for atom in selection: if atom.structure not in models: models[atom.structure] = [atom] else: models[atom.structure].append(atom) if len(models.keys()) == 0: return if self.vector_option.currentText() == "axis": if self.axis.currentText() == "z": vector = np.array([0., 0., 1.]) elif self.axis.currentText() == "y": vector = np.array([0., 1., 0.]) elif self.axis.currentText() == "x": vector = np.array([1., 0., 0.]) elif self.vector_option.currentText() == "view axis": if self.view_axis.currentText() == "z": vector = self.session.view.camera.get_position().axes()[2] elif self.view_axis.currentText() == "y": vector = self.session.view.camera.get_position().axes()[1] elif self.view_axis.currentText() == "x": vector = self.session.view.camera.get_position().axes()[0] elif self.vector_option.currentText() == "bond": vector = self.bonds elif self.vector_option.currentText() == "perpendicular to plane": vector = self.perpendiculars elif self.vector_option.currentText() == "centroid of atoms": vector = self.groups elif self.vector_option.currentText() == "custom": x = self.vector_x.value() y = self.vector_y.value() z = self.vector_z.value() vector = np.array([x, y, z]) angle = np.deg2rad(self.angle.value()) center = {} for model in models: atoms = models[model] coords = np.array([atom.coord for atom in atoms]) center[model] = np.mean(coords, axis=0) if self.cor_button.currentText() == "automatic": if self.vector_option.currentText() == "perpendicular to plane": center = self.perp_centers elif self.vector_option.currentText() == "bond": center = self.bond_centers elif self.cor_button.currentText() == "select atoms": center = self.manual_center else: center = self.session.main_view.center_of_rotation for model in models: if isinstance(vector, dict): if model not in vector.keys(): continue else: v = vector[model] else: v = vector if isinstance(center, dict): if model not in center.keys(): continue else: c = center[model] else: c = center if self.vector_option.currentText( ) == "centroid of atoms" and self.cor_button.currentText( ) != "automatic": v = v - c if np.linalg.norm(v) == 0: continue residues = [] for atom in models[model]: if atom.residue not in residues: residues.append(atom.residue) v_c = c + v s = ".color red\n" s += ".arrow %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f 0.2 0.4 0.7\n" % ( *c, *v_c) stream = BytesIO(bytes(s, 'utf-8')) bild_obj, status = read_bild(self.session, stream, "rotation vector") self.session.models.add(bild_obj, parent=model) def delete(self): self.session.triggers.remove_handler(self._show_rot_vec) global_triggers = get_triggers() global_triggers.remove_handler(self._changes) for model in self.session.models.list(type=Generic3DModel): if model.name == "rotation vector": model.delete() return super().delete() def close(self): self.session.triggers.remove_handler(self._show_rot_vec) global_triggers = get_triggers() global_triggers.remove_handler(self._changes) for model in self.session.models.list(type=Generic3DModel): if model.name == "rotation vector": model.delete() return super().close()
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = True resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: settings.SettingsHandler""" savedWidgetGeometry = settings.Setting(None) def __new__(cls, parent=None, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) setattr(self, gui.CONTROLLED_ATTRIBUTES, gui.ControlledAttributesDict(self)) self.__reportData = None OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" if self.want_basic_layout: self.insertLayout() return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(2, 2, 2, 2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox(self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0) self.mainArea.layout().setContentsMargins(4, 4, 4, 4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-red.png")) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass def restoreWidgetPosition(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored: space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored: # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before showEvent and we must not overwrite the the savedGeometry # with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() self.__was_restored = False QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() self.__was_restored = False QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position: # Restore saved geometry on show self.restoreWidgetPosition() self.__was_restored = True def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if not any(s.name == signalName for s in self.outputs): raise ValueError( '{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) gui.notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) def onDeleteWidget(self): """ Invoked by the canvas to notify the widget it has been deleted from the workflow. If possible, subclasses should gracefully cancel any currently executing tasks. """ pass def handleNewSignals(self): """ Invoked by the workflow signal propagation manager after all signals handlers have been called. Reimplement this method in order to coalesce updates from multiple updated inputs. """ pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn( "progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle( self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar(highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar(highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = { "Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation) } current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center".format( background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): iconpaths = { "Info": gui.resource_filename("icons/information.png"), "Warning": gui.resource_filename("icons/warning.png"), "Error": gui.resource_filename("icons/error.png") } items = [] for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconpaths[what], "\n".join( self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): info = QPixmap(gui.resource_filename("icons/information.png")) warning = QPixmap(gui.resource_filename("icons/warning.png")) error = QPixmap(gui.resource_filename("icons/error.png")) cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible()) } def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the workflow signal manager. This is useful for instance if the widget does it's work in a separate thread or schedules processing from the event queue. In this case it can set the blocking flag in it's processNewSignals method schedule the task and return immediately. After the task has completed the widget can clear the flag and send the updated outputs. .. note:: Failure to clear this flag will block dependent nodes forever. """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self)
class awakeScanGui(QWidget): ''' Init Self ''' def __init__(self,*args): super().__init__() self.initScans(*args) self.initUI() ''' Initialize GUI ''' def initUI(self): #self.connect(self,QtCore.Signal('triggered()'),self.closeEvent) #self.file_menu = QtWidgets.QMenu('&File', self) #self.file_menu.addAction('&Quit', self.closeEvent,QtCore.Qt.CTRL + QtCore.Qt.Key_Q) #self.menuBar().addMenu(self.file_menu) # Create a combobox for selecting camera self.selectScan = QLabel('Select Scan:') self.scanList = QComboBox(self) self.scanList.addItems(list(self.scanFunDict.keys())) self.scanList.currentIndexChanged.connect(self.selectScanFun) self.btnStart = QPushButton('Start', self) self.btnStart.setStyleSheet("background-color:#63f29a") self.btnStart.clicked[bool].connect(self.doStart) self.btnStop = QPushButton('Stop', self) self.btnStop.setStyleSheet("background-color:#63f29a") self.btnStop.clicked[bool].connect(self.doStop) # Create a group box for camera properties prop_box = QGroupBox('Camera Properties') self.inpNShot = QLineEdit(self) self.inpNShot.setValidator(QDoubleValidator(-100000,100000,4)) self.inpAnfang = QLineEdit(self) self.inpAnfang.setValidator(QDoubleValidator(-100000,100000,4)) self.inpEnde = QLineEdit(self) self.inpEnde.setValidator(QDoubleValidator(-100000,100000,4)) self.inpStep = QLineEdit(self) self.inpStep.setValidator(QDoubleValidator(-100000,100000,4)) self.inpSavestr = QLineEdit(self) self.regExp=QtCore.QRegExp('^.*$')# accept everything self.inpSavestr.setValidator(QRegExpValidator(self.regExp)) #self.inpSavestr.setValidator(QRegExpValidator('^ .*$')) prop_form = QFormLayout() prop_form.addRow('Number of Shots (empty=5):',self.inpNShot) prop_form.addRow('Start Value:',self.inpAnfang) prop_form.addRow('End Value:',self.inpEnde) prop_form.addRow('Step Value:',self.inpStep) prop_form.addRow('Savestring (empty = no save):',self.inpSavestr) prop_box.setLayout(prop_form) # Create a plotting window self.main_widget = QWidget(self) def lpass(x,*args): pass self.screen=awakeIMClass.awakeScreen(lpass,parent=self.main_widget) # Create Status Bar self.statusBar = QStatusBar(self) self.statusBar.setSizeGripEnabled(False) # Create Layout self.vbox = QVBoxLayout() self.vbox.addWidget(self.selectScan) self.vbox.addWidget(self.scanList) self.vbox.addWidget(self.btnStart) self.vbox.addWidget(self.btnStop) self.vbox.addWidget(prop_box) self.vbox.addStretch(1) self.vbox.addWidget(self.statusBar) self.hbox = QHBoxLayout() self.hbox.addLayout(self.vbox) self.hbox.addStretch() self.hbox.addWidget(self.screen, QtCore.Qt.AlignRight) self.setLayout(self.hbox) self.setGeometry(1600, 300, 900, 500) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__),'awakeicon1_FkV_icon.ico'))) self.setWindowTitle('Streak scan selector') self.show() self.statusBar.showMessage('Here speaks God, give Gandalf your Ring!') print('Init screen') self.hbox.removeWidget(self.screen) self.screen=self.activeScan.finfun self.screen.setParent(self.main_widget) self.hbox.addWidget(self.screen, QtCore.Qt.AlignRight) def closeEvent(self, event): self.doStop() #QCoreApplication.quit() #QWidget.closeEvent(self, event) def doStart(self): self.doStop() buffMax=self.inpNShot.text() savestr=self.inpSavestr.text() if buffMax !='': try: self.activeScan.MaxCall=np.float(buffMax) except: self.statusBar.showMessage('Error: Number of shots could not be converted to float') raise ValueError('No float constructable for N shot!') try: start=np.float(self.inpAnfang.text()) end=np.float(self.inpEnde.text()) step=np.float(self.inpStep.text()) except: self.statusBar.showMessage('Error: one or more of start,end,stop could be converted to float!') raise ValueError('One or more of start,end,stop could be converted to float!') self.activeScan.ScanList=np.arange(start,end,step) print(self.activeScan.ScanList) if savestr != '': self.activeScan.file_string=savestr self.activeScan.savePickle=True else: self.activeScan.savePickle=False self.statusBar.showMessage('Acquiring!') self.activeScan.start() return def doStop(self): self.activeScan.stop() self.statusBar.showMessage('Stopped the acquisition!',5) return def about(self): QtWidgets.QMessageBox.about(self, "About","""A single Awake window, able to show something nice""") def initScans(self,*args): self.activeScan=None self.scanFunDict={} zeroname='' i=0 for k in args: if isinstance(k,list): for l in k: if not isinstance(l,awakeLoop): raise IOError('please provide awakeLoop instance') #self.scanFunDict[l.name]=l if i==0: i+=1 zeroname=l.name # speichere nicht die awakeloop sondern nur die argumente für sie subList=[] for m in l.subs.subNum.keys(): subList+=[l.subs.subNum[m]] self.scanFunDict[l.name]={'subs':subList,'finfun':l.finfun,'scanList':l.ScanList,'wkQue':l.callFKT,'checkBeam':l.checkBeam,'selector':l.subs.selector,'savestr':l.file_string,'name':l.name} if i==1: i+=1 self.activeScan=awakeIMClass.awakeLoop(**self.scanFunDict[zeroname]) else: #speichere nur die argumente fuer awakeloop #self.scanFunDict[k.name]=k subList=[] for m in k.subs.subNum.keys(): subList+=[k.subs.subNum[m]] self.scanFunDict[k.name]={'subs':subList,'finfun':k.finfun,'scanList':k.ScanList,'wkQue':k.callFKT,'checkBeam':k.checkBeam,'selector':k.subs.selector,'savestr':k.file_string,'name':k.name} if i==0: i+=1 print(k.name) self.activeScan=awakeIMClass.awakeLoop(**self.scanFunDict[k.name]) if not isinstance(args[0],list): # not working atm! print('Passing atm')#self.activeScan=args[0] else: # not working atm! print('Passing list atm') #self.activeScan=args[0][0] def selectScanFun(self,l): #self.activeScan=self.scanFunDict[self.scanList.currentText()] #del(self.activeScan) self.activeScan=None print(self.scanFunDict[self.scanList.currentText()]) kwargs=self.scanFunDict[self.scanList.currentText()] self.activeScan=awakeIMClass.awakeLoop(**kwargs) print('Now self.activeScan') self.activeScan.print() if self.activeScan.finfun is not None and isinstance(self.activeScan.finfun,awakeIMClass.awakeScreen): self.hbox.removeWidget(self.screen) self.screen=self.activeScan.finfun self.screen.setParent(self.main_widget) self.hbox.addWidget(self.screen, QtCore.Qt.AlignRight)
class MainWindow(QMainWindow): def __init__(self, logger=None): super(MainWindow, self).__init__() self.setWindowTitle("League PhD") # browser self.web_view = WebView(self) self.setCentralWidget(self.web_view) # version try: with open('version.txt', 'r') as f: self.version = f.read() except FileNotFoundError: self.version = None # status bar: connection status self.status_bar = QStatusBar(self) self.status_bar.setSizeGripEnabled(False) self.status_bar.showMessage("Waiting for a connection...") # status bar: version status self.label_update = QLabel(f"{self.version} (latest) ") self.status_bar.addPermanentWidget(self.label_update) self.setStatusBar(self.status_bar) # check updates self.check_update() # logger self.logger = logger self.setWindowIcon(QIcon(str(Path('assets/icon.ico')))) def call_update(self, result_dict, dict_updated): self.logger.info('sent a call') result_dict['updated'] = dict_updated self.web_view.page().runJavaScript(f'app_call({json.dumps(result_dict)});') def go_to_pick_now(self): try: self.web_view.page_loaded._loop.call_soon_threadsafe(self.web_view.page_loaded.set) except AttributeError: pass self.logger.info('redirect to pick now') self.web_view.page().runJavaScript(f'go_to_pick_now();') def check_update(self): try: latest_version = requests.get("https://api.github.com/repos/leaguephd/leaguephd-app/releases/latest").json()['tag_name'] if self.version != latest_version: self.label_update.setText(f"Current: {self.version} (<a href=\"https://github.com/leaguephd/leaguephd-app/releases\">{latest_version} available</a>) ") self.label_update.setOpenExternalLinks(True) except KeyError: pass except requests.exceptions.ConnectionError: self.label_update.setText("Cannot connect to the file archive")
class ResultWindow(QMainWindow): def __init__(self, parent, app, **kwargs): super().__init__(parent, **kwargs) self.app = app self._setupUi() if app.model.app_mode == AppMode.Picture: MODEL_CLASS = ResultsModelPicture elif app.model.app_mode == AppMode.Music: MODEL_CLASS = ResultsModelMusic else: MODEL_CLASS = ResultsModelStandard self.resultsModel = MODEL_CLASS(self.app, self.resultsView) self.stats = StatsLabel(app.model.stats_label, self.statusLabel) self._update_column_actions_status() self.menuColumns.triggered.connect(self.columnToggled) self.resultsView.doubleClicked.connect(self.resultsDoubleClicked) self.resultsView.spacePressed.connect(self.resultsSpacePressed) self.detailsButton.clicked.connect(self.actionDetails.triggered) self.dupesOnlyCheckBox.stateChanged.connect(self.powerMarkerTriggered) self.deltaValuesCheckBox.stateChanged.connect(self.deltaTriggered) self.searchEdit.searchChanged.connect(self.searchChanged) self.app.willSavePrefs.connect(self.appWillSavePrefs) def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ ("actionDetails", "Ctrl+I", "", tr("Details"), self.detailsTriggered), ("actionActions", "", "", tr("Actions"), self.actionsTriggered), ( "actionPowerMarker", "Ctrl+1", "", tr("Show Dupes Only"), self.powerMarkerTriggered, ), ("actionDelta", "Ctrl+2", "", tr("Show Delta Values"), self.deltaTriggered), ( "actionDeleteMarked", "Ctrl+D", "", tr("Send Marked to Recycle Bin..."), self.deleteTriggered, ), ( "actionMoveMarked", "Ctrl+M", "", tr("Move Marked to..."), self.moveTriggered, ), ( "actionCopyMarked", "Ctrl+Shift+M", "", tr("Copy Marked to..."), self.copyTriggered, ), ( "actionRemoveMarked", "Ctrl+R", "", tr("Remove Marked from Results"), self.removeMarkedTriggered, ), ( "actionReprioritize", "", "", tr("Re-Prioritize Results..."), self.reprioritizeTriggered, ), ( "actionRemoveSelected", "Ctrl+Del", "", tr("Remove Selected from Results"), self.removeSelectedTriggered, ), ( "actionIgnoreSelected", "Ctrl+Shift+Del", "", tr("Add Selected to Ignore List"), self.addToIgnoreListTriggered, ), ( "actionMakeSelectedReference", "Ctrl+Space", "", tr("Make Selected into Reference"), self.app.model.make_selected_reference, ), ( "actionOpenSelected", "Ctrl+O", "", tr("Open Selected with Default Application"), self.openTriggered, ), ( "actionRevealSelected", "Ctrl+Shift+O", "", tr("Open Containing Folder of Selected"), self.revealTriggered, ), ( "actionRenameSelected", "F2", "", tr("Rename Selected"), self.renameTriggered, ), ("actionMarkAll", "Ctrl+A", "", tr("Mark All"), self.markAllTriggered), ( "actionMarkNone", "Ctrl+Shift+A", "", tr("Mark None"), self.markNoneTriggered, ), ( "actionInvertMarking", "Ctrl+Alt+A", "", tr("Invert Marking"), self.markInvertTriggered, ), ( "actionMarkSelected", "", "", tr("Mark Selected"), self.markSelectedTriggered, ), ( "actionExportToHTML", "", "", tr("Export To HTML"), self.app.model.export_to_xhtml, ), ( "actionExportToCSV", "", "", tr("Export To CSV"), self.app.model.export_to_csv, ), ( "actionSaveResults", "Ctrl+S", "", tr("Save Results..."), self.saveResultsTriggered, ), ( "actionInvokeCustomCommand", "Ctrl+Alt+I", "", tr("Invoke Custom Command"), self.app.invokeCustomCommand, ), ] createActions(ACTIONS, self) self.actionDelta.setCheckable(True) self.actionPowerMarker.setCheckable(True) def _setupMenu(self): self.menubar = QMenuBar() self.menubar.setGeometry(QRect(0, 0, 630, 22)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle(tr("File")) self.menuMark = QMenu(self.menubar) self.menuMark.setTitle(tr("Mark")) self.menuActions = QMenu(self.menubar) self.menuActions.setTitle(tr("Actions")) self.menuColumns = QMenu(self.menubar) self.menuColumns.setTitle(tr("Columns")) self.menuView = QMenu(self.menubar) self.menuView.setTitle(tr("View")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle(tr("Help")) self.setMenuBar(self.menubar) self.menuActions.addAction(self.actionDeleteMarked) self.menuActions.addAction(self.actionMoveMarked) self.menuActions.addAction(self.actionCopyMarked) self.menuActions.addAction(self.actionRemoveMarked) self.menuActions.addAction(self.actionReprioritize) self.menuActions.addSeparator() self.menuActions.addAction(self.actionRemoveSelected) self.menuActions.addAction(self.actionIgnoreSelected) self.menuActions.addAction(self.actionMakeSelectedReference) self.menuActions.addSeparator() self.menuActions.addAction(self.actionOpenSelected) self.menuActions.addAction(self.actionRevealSelected) self.menuActions.addAction(self.actionInvokeCustomCommand) self.menuActions.addAction(self.actionRenameSelected) self.menuMark.addAction(self.actionMarkAll) self.menuMark.addAction(self.actionMarkNone) self.menuMark.addAction(self.actionInvertMarking) self.menuMark.addAction(self.actionMarkSelected) self.menuView.addAction(self.actionPowerMarker) self.menuView.addAction(self.actionDelta) self.menuView.addSeparator() self.menuView.addAction(self.actionDetails) self.menuView.addAction(self.app.actionIgnoreList) self.menuView.addAction(self.app.actionPreferences) self.menuHelp.addAction(self.app.actionShowHelp) self.menuHelp.addAction(self.app.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionAbout) self.menuFile.addAction(self.actionSaveResults) self.menuFile.addAction(self.actionExportToHTML) self.menuFile.addAction(self.actionExportToCSV) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionQuit) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuMark.menuAction()) self.menubar.addAction(self.menuActions.menuAction()) self.menubar.addAction(self.menuColumns.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) # Columns menu menu = self.menuColumns self._column_actions = [] for index, (display, visible) in enumerate( self.app.model.result_table.columns.menu_items() ): action = menu.addAction(display) action.setCheckable(True) action.setChecked(visible) action.item_index = index self._column_actions.append(action) menu.addSeparator() action = menu.addAction(tr("Reset to Defaults")) action.item_index = -1 # Action menu actionMenu = QMenu(tr("Actions"), self.menubar) actionMenu.addAction(self.actionDeleteMarked) actionMenu.addAction(self.actionMoveMarked) actionMenu.addAction(self.actionCopyMarked) actionMenu.addAction(self.actionRemoveMarked) actionMenu.addSeparator() actionMenu.addAction(self.actionRemoveSelected) actionMenu.addAction(self.actionIgnoreSelected) actionMenu.addAction(self.actionMakeSelectedReference) actionMenu.addSeparator() actionMenu.addAction(self.actionOpenSelected) actionMenu.addAction(self.actionRevealSelected) actionMenu.addAction(self.actionInvokeCustomCommand) actionMenu.addAction(self.actionRenameSelected) self.actionActions.setMenu(actionMenu) self.actionsButton.setMenu(self.actionActions.menu()) def _setupUi(self): self.setWindowTitle(tr("{} Results").format(self.app.NAME)) self.resize(630, 514) self.centralwidget = QWidget(self) self.verticalLayout = QVBoxLayout(self.centralwidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.actionsButton = QPushButton(tr("Actions")) self.detailsButton = QPushButton(tr("Details")) self.dupesOnlyCheckBox = QCheckBox(tr("Dupes Only")) self.deltaValuesCheckBox = QCheckBox(tr("Delta Values")) self.searchEdit = SearchEdit() self.searchEdit.setMaximumWidth(300) self.horizontalLayout = horizontalWrap( [ self.actionsButton, self.detailsButton, self.dupesOnlyCheckBox, self.deltaValuesCheckBox, None, self.searchEdit, 8, ] ) self.horizontalLayout.setSpacing(8) self.verticalLayout.addLayout(self.horizontalLayout) self.resultsView = ResultsView(self.centralwidget) self.resultsView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.resultsView.setSelectionBehavior(QAbstractItemView.SelectRows) self.resultsView.setSortingEnabled(True) self.resultsView.setWordWrap(False) self.resultsView.verticalHeader().setVisible(False) h = self.resultsView.horizontalHeader() h.setHighlightSections(False) h.setSectionsMovable(True) h.setStretchLastSection(False) h.setDefaultAlignment(Qt.AlignLeft) self.verticalLayout.addWidget(self.resultsView) self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() self.statusbar = QStatusBar(self) self.statusbar.setSizeGripEnabled(True) self.setStatusBar(self.statusbar) self.statusLabel = QLabel(self) self.statusbar.addPermanentWidget(self.statusLabel, 1) if self.app.prefs.resultWindowIsMaximized: self.setWindowState(self.windowState() | Qt.WindowMaximized) else: if self.app.prefs.resultWindowRect is not None: self.setGeometry(self.app.prefs.resultWindowRect) # if not on any screen move to center of default screen # moves to center of closest screen if partially off screen frame = self.frameGeometry() if QDesktopWidget().screenNumber(self) == -1: moveToScreenCenter(self) elif QDesktopWidget().availableGeometry(self).contains(frame) is False: frame.moveCenter(QDesktopWidget().availableGeometry(self).center()) self.move(frame.topLeft()) else: moveToScreenCenter(self) # --- Private def _update_column_actions_status(self): # Update menu checked state menu_items = self.app.model.result_table.columns.menu_items() for action, (display, visible) in zip(self._column_actions, menu_items): action.setChecked(visible) # --- Actions def actionsTriggered(self): self.actionsButton.showMenu() def addToIgnoreListTriggered(self): self.app.model.add_selected_to_ignore_list() def copyTriggered(self): self.app.model.copy_or_move_marked(True) def deleteTriggered(self): self.app.model.delete_marked() def deltaTriggered(self, state=None): # The sender can be either the action or the checkbox, but both have a isChecked() method. self.resultsModel.delta_values = self.sender().isChecked() self.actionDelta.setChecked(self.resultsModel.delta_values) self.deltaValuesCheckBox.setChecked(self.resultsModel.delta_values) def detailsTriggered(self): self.app.show_details() def markAllTriggered(self): self.app.model.mark_all() def markInvertTriggered(self): self.app.model.mark_invert() def markNoneTriggered(self): self.app.model.mark_none() def markSelectedTriggered(self): self.app.model.toggle_selected_mark_state() def moveTriggered(self): self.app.model.copy_or_move_marked(False) def openTriggered(self): self.app.model.open_selected() def powerMarkerTriggered(self, state=None): # see deltaTriggered self.resultsModel.power_marker = self.sender().isChecked() self.actionPowerMarker.setChecked(self.resultsModel.power_marker) self.dupesOnlyCheckBox.setChecked(self.resultsModel.power_marker) def preferencesTriggered(self): self.app.show_preferences() def removeMarkedTriggered(self): self.app.model.remove_marked() def removeSelectedTriggered(self): self.app.model.remove_selected() def renameTriggered(self): index = self.resultsView.selectionModel().currentIndex() # Our index is the current row, with column set to 0. Our filename column is 1 and that's # what we want. index = index.sibling(index.row(), 1) self.resultsView.edit(index) def reprioritizeTriggered(self): dlg = PrioritizeDialog(self, self.app) result = dlg.exec() if result == QDialog.Accepted: dlg.model.perform_reprioritization() def revealTriggered(self): self.app.model.reveal_selected() def saveResultsTriggered(self): title = tr("Select a file to save your results to") files = tr("dupeGuru Results (*.dupeguru)") destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files) if destination: if not destination.endswith(".dupeguru"): destination = "{}.dupeguru".format(destination) self.app.model.save_as(destination) self.app.recentResults.insertItem(destination) # --- Events def appWillSavePrefs(self): prefs = self.app.prefs prefs.resultWindowIsMaximized = self.isMaximized() prefs.resultWindowRect = self.geometry() def columnToggled(self, action): index = action.item_index if index == -1: self.app.model.result_table.columns.reset_to_defaults() self._update_column_actions_status() else: visible = self.app.model.result_table.columns.toggle_menu_item(index) action.setChecked(visible) def contextMenuEvent(self, event): self.actionActions.menu().exec_(event.globalPos()) def resultsDoubleClicked(self, modelIndex): self.app.model.open_selected() def resultsSpacePressed(self): self.app.model.toggle_selected_mark_state() def searchChanged(self): self.app.model.apply_filter(self.searchEdit.text()) def closeEvent(self, event): # this saves the location of the results window when it is closed self.appWillSavePrefs()
class ErrorStatusBar(QWidget): """ A pop-up status bar for displaying messages about application errors to the user. Messages will be displayed for a given duration or until the message is cleared or updated with a new message. """ CSS = ''' #error_vertical_bar { background-color: #f22b5d; } #error_icon { background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fff, stop: 0.2 #fff, stop: 1 #fff ); } #error_status_bar { background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fff, stop: 0.2 #fff, stop: 1 #fff ); font-weight: bold; color: #f22b5d; } ''' def __init__(self): super().__init__() # Set styles self.setStyleSheet(self.CSS) # Set layout layout = QHBoxLayout(self) self.setLayout(layout) # Remove margins and spacing layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Error vertical bar self.vertical_bar = QWidget() self.vertical_bar.setObjectName('error_vertical_bar') # Set css id self.vertical_bar.setFixedWidth(10) # Error icon self.label = SvgLabel('error_icon.svg', svg_size=QSize(32, 32)) self.label.setObjectName('error_icon') # Set css id self.label.setFixedWidth(42) # Error status bar self.status_bar = QStatusBar() self.status_bar.setObjectName('error_status_bar') # Set css id self.status_bar.setSizeGripEnabled(False) # Add widgets to layout layout.addWidget(self.vertical_bar) layout.addWidget(self.label) layout.addWidget(self.status_bar) # Hide until a message needs to be displayed self.vertical_bar.hide() self.label.hide() self.status_bar.hide() # Only show errors for a set duration self.status_timer = QTimer() self.status_timer.timeout.connect(self._on_status_timeout) def _hide(self): self.vertical_bar.hide() self.label.hide() self.status_bar.hide() def _show(self): self.vertical_bar.show() self.label.show() self.status_bar.show() def _on_status_timeout(self): self._hide() def update_message(self, message: str, duration: int): """ Display a status message to the user for a given duration. """ self.status_bar.showMessage(message, duration) self.status_timer.start(duration) self._show() def clear_message(self): """ Clear any message currently in the status bar. """ self.status_bar.clearMessage() self._hide()
class PcerWindow(QWidget): experiment = None vbox = None statusBar = None start = None def __init__(self, experiment): super(PcerWindow, self).__init__() if self.start is None: self.start = time.time() self.experiment = experiment self.vbox = QVBoxLayout() self.statusBar = QStatusBar() self.setParticipantIdGroupInStatusBar(experiment.participant_id, experiment.participant_group) self.initBaseUI() self.initUI() # invokes method in subclass self.setStatusBar() self.setLayout(self.vbox) def initBaseUI(self): self.setWindowFlags(QtCore.Qt.FramelessWindowHint) with open("config.yml", "r") as stream: try: self.config = yaml.safe_load(stream) except yaml.YAMLError as exc: print(exc) self.width = self.config['window_size']['width'] self.height = self.config['window_size']['height'] self.setFixedSize(self.width, self.height) self.centerOnScreen() def setStatusBar(self): self.statusBar.setSizeGripEnabled(False) self.vbox.addWidget(self.statusBar) self.statusBar.setStyleSheet("background: rgba(250, 250, 250)") def setParticipantIdGroupInStatusBar(self, id, group): self.statusBar.showMessage("[ID: " + str(id) + " - Group: " + str(group) + "]") def addTimer(self, timer): self.statusBar.insertPermanentWidget(0, timer) timer.show() def centerOnScreen(self): resolution = QDesktopWidget().screenGeometry() self.move((resolution.width() / 2) - (self.frameSize().width() / 2), (resolution.height() / 2) - (self.frameSize().height() / 2)) print("Monitor resolution: %d (w) x %d (h)" % (resolution.width(), resolution.height())) def popUpWarning(self, msg): warning = QMessageBox() warning.setIcon(QMessageBox.Warning) warning.setText(msg) warning.setWindowTitle('Warning') warning.setStandardButtons(QMessageBox.Ok) warning.exec_()
class ConfigParamEditWindow(QDialog): def __init__(self, parent, node, target_node_id, param_struct, update_callback): super(ConfigParamEditWindow, self).__init__(parent) self.setWindowTitle("Edit configuration parameter") self.setModal(True) self._node = node self._target_node_id = target_node_id self._param_struct = param_struct self._update_callback = update_callback min_val = get_union_value(param_struct.min_value) if "uavcan.protocol.param.Empty" in str(min_val): min_val = None max_val = get_union_value(param_struct.max_value) if "uavcan.protocol.param.Empty" in str(max_val): max_val = None value = get_union_value(param_struct.value) self._value_widget = None value_type = uavcan.get_active_union_field(param_struct.value) if value_type == "integer_value": min_val = min_val if min_val is not None else -0x8000000000000000 max_val = max_val if max_val is not None else 0x7FFFFFFFFFFFFFFF if min_val >= -0x80000000 and max_val <= +0x7FFFFFFF: self._value_widget = QSpinBox(self) self._value_widget.setMaximum(max_val) self._value_widget.setMinimum(min_val) self._value_widget.setValue(value) if value_type == "real_value": min_val = round_float(min_val) if min_val is not None else -3.4028235e38 max_val = round_float(max_val) if max_val is not None else 3.4028235e38 value = round_float(value) if value_type == "boolean_value": self._value_widget = QCheckBox(self) self._value_widget.setChecked(bool(value)) if self._value_widget is None: self._value_widget = QLineEdit(self) self._value_widget.setText(str(value)) self._value_widget.setFont(get_monospace_font()) layout = QGridLayout(self) def add_const_field(label, *values): row = layout.rowCount() layout.addWidget(QLabel(label, self), row, 0) if len(values) == 1: layout.addWidget(FieldValueWidget(self, values[0]), row, 1) else: sub_layout = QHBoxLayout(self) for idx, v in enumerate(values): sub_layout.addWidget(FieldValueWidget(self, v)) layout.addLayout(sub_layout, row, 1) add_const_field("Name", param_struct.name) add_const_field("Type", uavcan.get_active_union_field(param_struct.value).replace("_value", "")) add_const_field("Min/Max", min_val, max_val) add_const_field("Default", render_union(param_struct.default_value)) layout.addWidget(QLabel("Value", self), layout.rowCount(), 0) layout.addWidget(self._value_widget, layout.rowCount() - 1, 1) fetch_button = make_icon_button( "refresh", "Read parameter from the node", self, text="Fetch", on_clicked=self._do_fetch ) set_default_button = make_icon_button( "fire-extinguisher", "Restore default value", self, text="Restore", on_clicked=self._restore_default ) send_button = make_icon_button( "flash", "Send parameter to the node", self, text="Send", on_clicked=self._do_send ) cancel_button = make_icon_button( "remove", "Close this window; unsent changes will be lost", self, text="Cancel", on_clicked=self.close ) controls_layout = QGridLayout(self) controls_layout.addWidget(fetch_button, 0, 0) controls_layout.addWidget(send_button, 0, 1) controls_layout.addWidget(set_default_button, 1, 0) controls_layout.addWidget(cancel_button, 1, 1) layout.addLayout(controls_layout, layout.rowCount(), 0, 1, 2) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout.addWidget(self._status_bar, layout.rowCount(), 0, 1, 2) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout) def show_message(self, text, *fmt): self._status_bar.showMessage(text % fmt) def _assign(self, value_union): value = get_union_value(value_union) if uavcan.get_active_union_field(value_union) == "real_value": value = round_float(value) if hasattr(self._value_widget, "setValue"): self._value_widget.setValue(value) self._update_callback(value) elif hasattr(self._value_widget, "setChecked"): self._value_widget.setChecked(bool(value)) self._update_callback(bool(value)) else: self._value_widget.setText(str(value)) self._update_callback(value) def _on_response(self, e): if e is None: self.show_message("Request timed out") else: logger.info("Param get/set response: %s", e.response) self._assign(e.response.value) self.show_message("Response received") def _restore_default(self): self._assign(self._param_struct.default_value) def _do_fetch(self): try: request = uavcan.protocol.param.GetSet.Request(name=self._param_struct.name) self._node.request(request, self._target_node_id, self._on_response, priority=REQUEST_PRIORITY) except Exception as ex: show_error("Node error", "Could not send param get request", ex, self) else: self.show_message("Fetch request sent") def _do_send(self): value_type = uavcan.get_active_union_field(self._param_struct.value) try: if value_type == "integer_value": if hasattr(self._value_widget, "value"): value = int(self._value_widget.value()) else: value = int(self._value_widget.text()) self._param_struct.value.integer_value = value elif value_type == "real_value": value = float(self._value_widget.text()) self._param_struct.value.real_value = value elif value_type == "boolean_value": value = bool(self._value_widget.isChecked()) self._param_struct.value.boolean_value = value elif value_type == "string_value": value = self._value_widget.text() self._param_struct.value.string_value = value else: raise RuntimeError("This is not happening!") except Exception as ex: show_error("Format error", "Could not parse value", ex, self) return try: request = uavcan.protocol.param.GetSet.Request(name=self._param_struct.name, value=self._param_struct.value) logger.info("Sending param set request: %s", request) self._node.request(request, self._target_node_id, self._on_response, priority=REQUEST_PRIORITY) except Exception as ex: show_error("Node error", "Could not send param set request", ex, self) else: self.show_message("Set request sent")
class ResultWindow(QMainWindow): def __init__(self, parent, app, **kwargs): super().__init__(parent, **kwargs) self.app = app self._setupUi() self.resultsModel = app.RESULT_MODEL_CLASS(self.app, self.resultsView) self.stats = StatsLabel(app.model.stats_label, self.statusLabel) self._update_column_actions_status() self.menuColumns.triggered.connect(self.columnToggled) self.resultsView.doubleClicked.connect(self.resultsDoubleClicked) self.resultsView.spacePressed.connect(self.resultsSpacePressed) self.detailsButton.clicked.connect(self.actionDetails.triggered) self.dupesOnlyCheckBox.stateChanged.connect(self.powerMarkerTriggered) self.deltaValuesCheckBox.stateChanged.connect(self.deltaTriggered) self.searchEdit.searchChanged.connect(self.searchChanged) self.app.willSavePrefs.connect(self.appWillSavePrefs) def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ ('actionDetails', 'Ctrl+I', '', tr("Details"), self.detailsTriggered), ('actionActions', '', '', tr("Actions"), self.actionsTriggered), ('actionPowerMarker', 'Ctrl+1', '', tr("Show Dupes Only"), self.powerMarkerTriggered), ('actionDelta', 'Ctrl+2', '', tr("Show Delta Values"), self.deltaTriggered), ('actionDeleteMarked', 'Ctrl+D', '', tr("Send Marked to Recycle Bin..."), self.deleteTriggered), ('actionMoveMarked', 'Ctrl+M', '', tr("Move Marked to..."), self.moveTriggered), ('actionCopyMarked', 'Ctrl+Shift+M', '', tr("Copy Marked to..."), self.copyTriggered), ('actionRemoveMarked', 'Ctrl+R', '', tr("Remove Marked from Results"), self.removeMarkedTriggered), ('actionReprioritize', '', '', tr("Re-Prioritize Results..."), self.reprioritizeTriggered), ('actionRemoveSelected', 'Ctrl+Del', '', tr("Remove Selected from Results"), self.removeSelectedTriggered), ('actionIgnoreSelected', 'Ctrl+Shift+Del', '', tr("Add Selected to Ignore List"), self.addToIgnoreListTriggered), ('actionMakeSelectedReference', 'Ctrl+Space', '', tr("Make Selected into Reference"), self.app.model.make_selected_reference), ('actionOpenSelected', 'Ctrl+O', '', tr("Open Selected with Default Application"), self.openTriggered), ('actionRevealSelected', 'Ctrl+Shift+O', '', tr("Open Containing Folder of Selected"), self.revealTriggered), ('actionRenameSelected', 'F2', '', tr("Rename Selected"), self.renameTriggered), ('actionMarkAll', 'Ctrl+A', '', tr("Mark All"), self.markAllTriggered), ('actionMarkNone', 'Ctrl+Shift+A', '', tr("Mark None"), self.markNoneTriggered), ('actionInvertMarking', 'Ctrl+Alt+A', '', tr("Invert Marking"), self.markInvertTriggered), ('actionMarkSelected', '', '', tr("Mark Selected"), self.markSelectedTriggered), ('actionExportToHTML', '', '', tr("Export To HTML"), self.app.model.export_to_xhtml), ('actionExportToCSV', '', '', tr("Export To CSV"), self.app.model.export_to_csv), ('actionSaveResults', 'Ctrl+S', '', tr("Save Results..."), self.saveResultsTriggered), ('actionInvokeCustomCommand', 'Ctrl+Alt+I', '', tr("Invoke Custom Command"), self.app.invokeCustomCommand), ] createActions(ACTIONS, self) self.actionDelta.setCheckable(True) self.actionPowerMarker.setCheckable(True) def _setupMenu(self): self.menubar = QMenuBar() self.menubar.setGeometry(QRect(0, 0, 630, 22)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle(tr("File")) self.menuMark = QMenu(self.menubar) self.menuMark.setTitle(tr("Mark")) self.menuActions = QMenu(self.menubar) self.menuActions.setTitle(tr("Actions")) self.menuColumns = QMenu(self.menubar) self.menuColumns.setTitle(tr("Columns")) self.menuView = QMenu(self.menubar) self.menuView.setTitle(tr("View")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle(tr("Help")) self.setMenuBar(self.menubar) self.menuActions.addAction(self.actionDeleteMarked) self.menuActions.addAction(self.actionMoveMarked) self.menuActions.addAction(self.actionCopyMarked) self.menuActions.addAction(self.actionRemoveMarked) self.menuActions.addAction(self.actionReprioritize) self.menuActions.addSeparator() self.menuActions.addAction(self.actionRemoveSelected) self.menuActions.addAction(self.actionIgnoreSelected) self.menuActions.addAction(self.actionMakeSelectedReference) self.menuActions.addSeparator() self.menuActions.addAction(self.actionOpenSelected) self.menuActions.addAction(self.actionRevealSelected) self.menuActions.addAction(self.actionInvokeCustomCommand) self.menuActions.addAction(self.actionRenameSelected) self.menuMark.addAction(self.actionMarkAll) self.menuMark.addAction(self.actionMarkNone) self.menuMark.addAction(self.actionInvertMarking) self.menuMark.addAction(self.actionMarkSelected) self.menuView.addAction(self.actionPowerMarker) self.menuView.addAction(self.actionDelta) self.menuView.addSeparator() self.menuView.addAction(self.actionDetails) self.menuView.addAction(self.app.actionIgnoreList) self.menuView.addAction(self.app.actionPreferences) self.menuHelp.addAction(self.app.actionShowHelp) self.menuHelp.addAction(self.app.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionAbout) self.menuFile.addAction(self.actionSaveResults) self.menuFile.addAction(self.actionExportToHTML) self.menuFile.addAction(self.actionExportToCSV) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionQuit) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuMark.menuAction()) self.menubar.addAction(self.menuActions.menuAction()) self.menubar.addAction(self.menuColumns.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) # Columns menu menu = self.menuColumns self._column_actions = [] for index, (display, visible) in enumerate(self.app.model.result_table.columns.menu_items()): action = menu.addAction(display) action.setCheckable(True) action.setChecked(visible) action.item_index = index self._column_actions.append(action) menu.addSeparator() action = menu.addAction(tr("Reset to Defaults")) action.item_index = -1 # Action menu actionMenu = QMenu(tr("Actions"), self.menubar) actionMenu.addAction(self.actionDeleteMarked) actionMenu.addAction(self.actionMoveMarked) actionMenu.addAction(self.actionCopyMarked) actionMenu.addAction(self.actionRemoveMarked) actionMenu.addSeparator() actionMenu.addAction(self.actionRemoveSelected) actionMenu.addAction(self.actionIgnoreSelected) actionMenu.addAction(self.actionMakeSelectedReference) actionMenu.addSeparator() actionMenu.addAction(self.actionOpenSelected) actionMenu.addAction(self.actionRevealSelected) actionMenu.addAction(self.actionInvokeCustomCommand) actionMenu.addAction(self.actionRenameSelected) self.actionActions.setMenu(actionMenu) self.actionsButton.setMenu(self.actionActions.menu()) def _setupUi(self): self.setWindowTitle(tr("{} Results").format(self.app.NAME)) self.resize(630, 514) self.centralwidget = QWidget(self) self.verticalLayout = QVBoxLayout(self.centralwidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.actionsButton = QPushButton(tr("Actions")) self.detailsButton = QPushButton(tr("Details")) self.dupesOnlyCheckBox = QCheckBox(tr("Dupes Only")) self.deltaValuesCheckBox = QCheckBox(tr("Delta Values")) self.searchEdit = SearchEdit() self.searchEdit.setMaximumWidth(300) self.horizontalLayout = horizontalWrap([self.actionsButton, self.detailsButton, self.dupesOnlyCheckBox, self.deltaValuesCheckBox, None, self.searchEdit, 8]) self.horizontalLayout.setSpacing(8) self.verticalLayout.addLayout(self.horizontalLayout) self.resultsView = ResultsView(self.centralwidget) self.resultsView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.resultsView.setSelectionBehavior(QAbstractItemView.SelectRows) self.resultsView.setSortingEnabled(True) self.resultsView.verticalHeader().setVisible(False) h = self.resultsView.horizontalHeader() h.setHighlightSections(False) h.setSectionsMovable(True) h.setStretchLastSection(False) h.setDefaultAlignment(Qt.AlignLeft) self.verticalLayout.addWidget(self.resultsView) self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() self.statusbar = QStatusBar(self) self.statusbar.setSizeGripEnabled(True) self.setStatusBar(self.statusbar) self.statusLabel = QLabel(self) self.statusbar.addPermanentWidget(self.statusLabel, 1) if self.app.prefs.resultWindowIsMaximized: self.setWindowState(self.windowState() | Qt.WindowMaximized) else: if self.app.prefs.resultWindowRect is not None: self.setGeometry(self.app.prefs.resultWindowRect) else: moveToScreenCenter(self) #--- Private def _update_column_actions_status(self): # Update menu checked state menu_items = self.app.model.result_table.columns.menu_items() for action, (display, visible) in zip(self._column_actions, menu_items): action.setChecked(visible) #--- Actions def actionsTriggered(self): self.actionsButton.showMenu() def addToIgnoreListTriggered(self): self.app.model.add_selected_to_ignore_list() def copyTriggered(self): self.app.model.copy_or_move_marked(True) def deleteTriggered(self): self.app.model.delete_marked() def deltaTriggered(self, state=None): # The sender can be either the action or the checkbox, but both have a isChecked() method. self.resultsModel.delta_values = self.sender().isChecked() self.actionDelta.setChecked(self.resultsModel.delta_values) self.deltaValuesCheckBox.setChecked(self.resultsModel.delta_values) def detailsTriggered(self): self.app.show_details() def markAllTriggered(self): self.app.model.mark_all() def markInvertTriggered(self): self.app.model.mark_invert() def markNoneTriggered(self): self.app.model.mark_none() def markSelectedTriggered(self): self.app.model.toggle_selected_mark_state() def moveTriggered(self): self.app.model.copy_or_move_marked(False) def openTriggered(self): self.app.model.open_selected() def powerMarkerTriggered(self, state=None): # see deltaTriggered self.resultsModel.power_marker = self.sender().isChecked() self.actionPowerMarker.setChecked(self.resultsModel.power_marker) self.dupesOnlyCheckBox.setChecked(self.resultsModel.power_marker) def preferencesTriggered(self): self.app.show_preferences() def removeMarkedTriggered(self): self.app.model.remove_marked() def removeSelectedTriggered(self): self.app.model.remove_selected() def renameTriggered(self): index = self.resultsView.selectionModel().currentIndex() # Our index is the current row, with column set to 0. Our filename column is 1 and that's # what we want. index = index.sibling(index.row(), 1) self.resultsView.edit(index) def reprioritizeTriggered(self): dlg = PrioritizeDialog(self, self.app) result = dlg.exec() if result == QDialog.Accepted: dlg.model.perform_reprioritization() def revealTriggered(self): self.app.model.reveal_selected() def saveResultsTriggered(self): title = tr("Select a file to save your results to") files = tr("dupeGuru Results (*.dupeguru)") destination, chosen_filter = QFileDialog.getSaveFileName(self, title, '', files) if destination: if not destination.endswith('.dupeguru'): destination = '{}.dupeguru'.format(destination) self.app.model.save_as(destination) self.app.recentResults.insertItem(destination) #--- Events def appWillSavePrefs(self): prefs = self.app.prefs prefs.resultWindowIsMaximized = self.isMaximized() prefs.resultWindowRect = self.geometry() def columnToggled(self, action): index = action.item_index if index == -1: self.app.model.result_table.columns.reset_to_defaults() self._update_column_actions_status() else: visible = self.app.model.result_table.columns.toggle_menu_item(index) action.setChecked(visible) def contextMenuEvent(self, event): self.actionActions.menu().exec_(event.globalPos()) def resultsDoubleClicked(self, modelIndex): self.app.model.open_selected() def resultsSpacePressed(self): self.app.model.toggle_selected_mark_state() def searchChanged(self): self.app.model.apply_filter(self.searchEdit.text())
class Thermochem(ToolInstance): """tool for calculating free energy corrections based on frequencies and energies associated with FileReaders there are two tabs: absolute and relative the absolute tab can be used to combine the thermo corrections from one FileReader with the energy of another the relative tab can do the same, but it prints the energies relative to those of another FileReader multiple molecule groups can be added (i.e. for reactions with multiple reactants and products) each molecule group can have multiple conformers the energies of these conformers are boltzmann weighted, and the boltzmann-weighted energy is used to calculate the energy of either the reference group or the 'other' group""" SESSION_ENDURING = False SESSION_SAVE = False help = "https://github.com/QChASM/SEQCROW/wiki/Process-Thermochemistry-Tool" theory_helper = { "Grimme's Quasi-RRHO": "https://doi.org/10.1002/chem.201200497", "Truhlar's Quasi-Harmonic": "https://doi.org/10.1021/jp205508z" } def __init__(self, session, name): super().__init__(session, name) self.display_name = "Thermochemistry" self.tool_window = MainToolWindow(self) self.settings = _ComputeThermoSettings(self.session, name) self.nrg_fr = {} self.thermo_fr = {} self.thermo_co = {} self._build_ui() self.set_sp() self.set_thermo_mdl() self.calc_relative_thermo() def _build_ui(self): #each group has an empty widget at the bottom so they resize the way I want while also having the #labels where I want them layout = QGridLayout() self.tab_widget = QTabWidget() layout.addWidget(self.tab_widget) #layout for absolute thermo stuff absolute_widget = QWidget() absolute_layout = QGridLayout(absolute_widget) #box for sp sp_area_widget = QGroupBox("Single-point") sp_layout = QGridLayout(sp_area_widget) self.sp_selector = FilereaderComboBox(self.session, otherItems=['energy']) self.sp_selector.currentIndexChanged.connect(self.set_sp) sp_layout.addWidget(self.sp_selector, 0, 0, 1, 3, Qt.AlignTop) nrg_label = QLabel("E =") sp_layout.addWidget(nrg_label, 1, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.sp_nrg_line = QLineEdit() self.sp_nrg_line.setReadOnly(True) self.sp_nrg_line.setToolTip("electronic energy") sp_layout.addWidget(self.sp_nrg_line, 1, 1, 1, 1, Qt.AlignTop) sp_layout.addWidget(QLabel("E<sub>h</sub>"), 1, 2, 1, 1, Qt.AlignVCenter) sp_layout.addWidget(QWidget(), 2, 0) sp_layout.setColumnStretch(0, 0) sp_layout.setColumnStretch(1, 1) sp_layout.setRowStretch(0, 0) sp_layout.setRowStretch(1, 0) sp_layout.setRowStretch(2, 1) row = 0 #box for thermo therm_area_widget = QGroupBox("Thermal corrections") thermo_layout = QGridLayout(therm_area_widget) self.thermo_selector = FilereaderComboBox(self.session, otherItems=['frequency']) self.thermo_selector.currentIndexChanged.connect(self.set_thermo_mdl) thermo_layout.addWidget(self.thermo_selector, row, 0, 1, 3, Qt.AlignTop) row += 1 thermo_layout.addWidget(QLabel("T ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.temperature_line = QDoubleSpinBox() self.temperature_line.setMaximum(2**31 - 1) self.temperature_line.setValue(298.15) self.temperature_line.setSingleStep(10) self.temperature_line.setSuffix(" K") self.temperature_line.setMinimum(0) self.temperature_line.valueChanged.connect(self.set_thermo) thermo_layout.addWidget(self.temperature_line, row, 1, 1, 2, Qt.AlignTop) row += 1 thermo_layout.addWidget(QLabel("𝜔<sub>0</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.v0_edit = QDoubleSpinBox() self.v0_edit.setMaximum(4000) self.v0_edit.setValue(self.settings.w0) self.v0_edit.setSingleStep(25) self.v0_edit.setSuffix(" cm\u207b\u00b9") self.v0_edit.valueChanged.connect(self.set_thermo) self.v0_edit.setMinimum(0) self.v0_edit.setToolTip( "frequency parameter for quasi treatments of entropy") thermo_layout.addWidget(self.v0_edit, row, 1, 1, 2, Qt.AlignTop) row += 1 thermo_layout.addWidget(QLabel("𝛿ZPE ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.zpe_line = QLineEdit() self.zpe_line.setReadOnly(True) self.zpe_line.setToolTip("zero-point energy correction") thermo_layout.addWidget(self.zpe_line, row, 1, 1, 1, Qt.AlignTop) thermo_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 thermo_layout.addWidget(QLabel("𝛿H<sub>RRHO</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.enthalpy_line = QLineEdit() self.enthalpy_line.setReadOnly(True) self.enthalpy_line.setToolTip("RRHO enthalpy correction") thermo_layout.addWidget(self.enthalpy_line, row, 1, 1, 1, Qt.AlignTop) thermo_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 thermo_layout.addWidget(QLabel("𝛿G<sub>RRHO</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.rrho_g_line = QLineEdit() self.rrho_g_line.setReadOnly(True) self.rrho_g_line.setToolTip( """RRHO free energy correction\nRotational motion is completely independent from vibrations\nNormal mode vibrations behave like harmonic oscillators""" ) thermo_layout.addWidget(self.rrho_g_line, row, 1, 1, 1, Qt.AlignTop) thermo_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 dqrrho_g_label = QLabel() dqrrho_g_label.setText( "<a href=\"Grimme's Quasi-RRHO\" style=\"text-decoration: none;\">𝛿G<sub>Quasi-RRHO</sub></a> =" ) dqrrho_g_label.setTextFormat(Qt.RichText) dqrrho_g_label.setTextInteractionFlags(Qt.TextBrowserInteraction) dqrrho_g_label.linkActivated.connect(self.open_link) dqrrho_g_label.setToolTip("click to open a relevant reference\n" + \ "see the \"Computation of internal (gas‐phase) entropies\" section for a description of the method") thermo_layout.addWidget(dqrrho_g_label, row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.qrrho_g_line = QLineEdit() self.qrrho_g_line.setReadOnly(True) self.qrrho_g_line.setToolTip("Quasi-RRHO free energy correction\n" + \ "Vibrational entropy of each mode is weighted and complemented with rotational entropy\n" + \ "The weighting is much lower for modes with frequencies less than 𝜔\u2080") thermo_layout.addWidget(self.qrrho_g_line, row, 1, 1, 1, Qt.AlignTop) thermo_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 dqharm_g_label = QLabel("") dqharm_g_label.setText( "<a href=\"Truhlar's Quasi-Harmonic\" style=\"text-decoration: none;\">𝛿G<sub>Quasi-Harmonic</sub></a> =" ) dqharm_g_label.setTextFormat(Qt.RichText) dqharm_g_label.setTextInteractionFlags(Qt.TextBrowserInteraction) dqharm_g_label.linkActivated.connect(self.open_link) dqharm_g_label.setToolTip("click to open a relevant reference\n" + \ "see the \"Computations\" section for a description of the method") thermo_layout.addWidget(dqharm_g_label, row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.qharm_g_line = QLineEdit() self.qharm_g_line.setReadOnly(True) self.qharm_g_line.setToolTip("Quasi-hamonic free energy correction\n" + \ "For entropy, real vibrational modes lower than 𝜔\u2080 are treated as 𝜔\u2080") thermo_layout.addWidget(self.qharm_g_line, row, 1, 1, 1, Qt.AlignTop) thermo_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) thermo_layout.addWidget(QWidget(), row + 1, 0) thermo_layout.setColumnStretch(0, 0) thermo_layout.setColumnStretch(1, 1) for i in range(0, row): thermo_layout.setRowStretch(i, 0) thermo_layout.setRowStretch(row + 1, 1) row = 0 # for for total sum_area_widget = QGroupBox("Thermochemistry") sum_layout = QGridLayout(sum_area_widget) sum_layout.addWidget(QLabel("ZPE ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.zpe_sum_line = QLineEdit() self.zpe_sum_line.setReadOnly(True) self.zpe_sum_line.setToolTip( "sum of electronic energy and ZPE correction") sum_layout.addWidget(self.zpe_sum_line, row, 1, 1, 1, Qt.AlignTop) sum_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 sum_layout.addWidget(QLabel("H<sub>RRHO</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.h_sum_line = QLineEdit() self.h_sum_line.setReadOnly(True) self.h_sum_line.setToolTip( "sum of electronic energy and RRHO enthalpy correction") sum_layout.addWidget(self.h_sum_line, row, 1, 1, 1, Qt.AlignTop) sum_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 sum_layout.addWidget(QLabel("G<sub>RRHO</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.rrho_g_sum_line = QLineEdit() self.rrho_g_sum_line.setReadOnly(True) self.rrho_g_sum_line.setToolTip( "sum of electronic energy and RRHO free energy correction\nRotational motion is completely independent from vibrations\nNormal mode vibrations behave like harmonic oscillators" ) sum_layout.addWidget(self.rrho_g_sum_line, row, 1, 1, 1, Qt.AlignTop) sum_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 sum_layout.addWidget(QLabel("G<sub>Quasi-RRHO</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.qrrho_g_sum_line = QLineEdit() self.qrrho_g_sum_line.setReadOnly(True) self.qrrho_g_sum_line.setToolTip("Sum of electronic energy and quasi-RRHO free energy correction\n" + \ "Vibrational entropy of each mode is weighted and complemented with rotational entropy\n" + \ "The weighting is much lower for modes with frequencies less than 𝜔\u2080") sum_layout.addWidget(self.qrrho_g_sum_line, row, 1, 1, 1, Qt.AlignTop) sum_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) row += 1 sum_layout.addWidget(QLabel("G<sub>Quasi-Harmonic</sub> ="), row, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.qharm_g_sum_line = QLineEdit() self.qharm_g_sum_line.setReadOnly(True) self.qharm_g_sum_line.setToolTip("Sum of electronic energy and quasi-harmonic free energy correction\n" + \ "For entropy, real vibrational modes lower than 𝜔\u2080 are treated as 𝜔\u2080") sum_layout.addWidget(self.qharm_g_sum_line, row, 1, 1, 1, Qt.AlignTop) sum_layout.addWidget(QLabel("E<sub>h</sub>"), row, 2, 1, 1, Qt.AlignVCenter) sum_layout.addWidget(QWidget(), row + 1, 0) sum_layout.setColumnStretch(0, 0) for i in range(0, row): sum_layout.setRowStretch(i, 0) sum_layout.setRowStretch(row + 1, 1) splitter = QSplitter(Qt.Horizontal) splitter.setChildrenCollapsible(False) splitter.addWidget(sp_area_widget) splitter.addWidget(therm_area_widget) splitter.addWidget(sum_area_widget) absolute_layout.addWidget(splitter) self.status = QStatusBar() self.status.setSizeGripEnabled(False) self.status.setStyleSheet("color: red") absolute_layout.addWidget(self.status, 1, 0, 1, 1, Qt.AlignTop) self.tab_widget.addTab(absolute_widget, "absolute") relative_widget = QWidget() relative_layout = QGridLayout(relative_widget) size = [self.settings.ref_col_1, self.settings.ref_col_2] self.ref_group = ThermoGroup("reference group", self.session, self.nrg_fr, self.thermo_co, size) self.ref_group.changes.connect(self.calc_relative_thermo) relative_layout.addWidget(self.ref_group, 0, 0, 1, 3, Qt.AlignTop) size = [self.settings.other_col_1, self.settings.other_col_2] self.other_group = ThermoGroup("other group", self.session, self.nrg_fr, self.thermo_co, size) self.other_group.changes.connect(self.calc_relative_thermo) relative_layout.addWidget(self.other_group, 0, 3, 1, 3, Qt.AlignTop) self.relative_temperature = QDoubleSpinBox() self.relative_temperature.setMaximum(2**31 - 1) self.relative_temperature.setValue(self.settings.rel_temp) self.relative_temperature.setSingleStep(10) self.relative_temperature.setSuffix(" K") self.relative_temperature.setMinimum(0) self.relative_temperature.valueChanged.connect( self.calc_relative_thermo) relative_layout.addWidget(QLabel("T ="), 1, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) relative_layout.addWidget(self.relative_temperature, 1, 1, 1, 5, Qt.AlignLeft | Qt.AlignVCenter) self.relative_v0 = QDoubleSpinBox() self.relative_v0.setMaximum(2**31 - 1) self.relative_v0.setValue(self.settings.w0) self.relative_v0.setSingleStep(25) self.relative_v0.setSuffix(" cm\u207b\u00b9") self.relative_v0.setMinimum(0) self.relative_v0.setToolTip( "frequency parameter for quasi treatments of entropy") self.relative_v0.valueChanged.connect(self.calc_relative_thermo) relative_layout.addWidget(QLabel("𝜔<sub>0</sub> ="), 2, 0, 1, 1, Qt.AlignRight | Qt.AlignVCenter) relative_layout.addWidget(self.relative_v0, 2, 1, 1, 5, Qt.AlignLeft | Qt.AlignVCenter) relative_layout.addWidget( QLabel("Boltzmann-weighted relative energies in kcal/mol:"), 3, 0, 1, 6, Qt.AlignVCenter | Qt.AlignLeft) relative_layout.addWidget(QLabel("ΔE"), 4, 0, Qt.AlignTop | Qt.AlignVCenter) self.relative_dE = QLineEdit() self.relative_dE.setReadOnly(True) relative_layout.addWidget(self.relative_dE, 5, 0, Qt.AlignTop | Qt.AlignVCenter) relative_layout.addWidget(QLabel("ΔZPE"), 4, 1, Qt.AlignTop | Qt.AlignVCenter) self.relative_dZPVE = QLineEdit() self.relative_dZPVE.setReadOnly(True) relative_layout.addWidget(self.relative_dZPVE, 5, 1, Qt.AlignTop | Qt.AlignVCenter) relative_layout.addWidget(QLabel("ΔH<sub>RRHO</sub>"), 4, 2, Qt.AlignTop | Qt.AlignVCenter) self.relative_dH = QLineEdit() self.relative_dH.setReadOnly(True) relative_layout.addWidget(self.relative_dH, 5, 2, Qt.AlignTop | Qt.AlignVCenter) relative_layout.addWidget(QLabel("ΔG<sub>RRHO</sub>"), 4, 3, Qt.AlignTop | Qt.AlignVCenter) self.relative_dG = QLineEdit() self.relative_dG.setReadOnly(True) relative_layout.addWidget(self.relative_dG, 5, 3, Qt.AlignTop | Qt.AlignVCenter) relative_layout.addWidget(QLabel("ΔG<sub>Quasi-RRHO</sub>"), 4, 4, Qt.AlignTop | Qt.AlignVCenter) self.relative_dQRRHOG = QLineEdit() self.relative_dQRRHOG.setReadOnly(True) relative_layout.addWidget(self.relative_dQRRHOG, 5, 4, Qt.AlignTop | Qt.AlignVCenter) relative_layout.addWidget(QLabel("ΔG<sub>Quasi-Harmonic</sub>"), 4, 5, Qt.AlignTop | Qt.AlignVCenter) self.relative_dQHARMG = QLineEdit() self.relative_dQHARMG.setReadOnly(True) relative_layout.addWidget(self.relative_dQHARMG, 5, 5, Qt.AlignTop | Qt.AlignVCenter) relative_layout.setRowStretch(0, 1) relative_layout.setRowStretch(1, 0) relative_layout.setRowStretch(2, 0) relative_layout.setRowStretch(3, 0) relative_layout.setRowStretch(4, 0) relative_layout.setRowStretch(5, 0) self.tab_widget.addTab(relative_widget, "relative") #menu stuff menu = QMenuBar() export = menu.addMenu("&Export") copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area) copy.triggered.connect(self.copy_csv) shortcut = QKeySequence(Qt.CTRL + Qt.Key_C) copy.setShortcut(shortcut) export.addAction(copy) self.copy = copy save = QAction("&Save CSV...", self.tool_window.ui_area) save.triggered.connect(self.save_csv) #this shortcut interferes with main window's save shortcut #I've tried different shortcut contexts to no avail #thanks Qt... #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S) #save.setShortcut(shortcut) #save.setShortcutContext(Qt.WidgetShortcut) export.addAction(save) delimiter = export.addMenu("Delimiter") comma = QAction("comma", self.tool_window.ui_area, checkable=True) comma.setChecked(self.settings.delimiter == "comma") comma.triggered.connect(lambda *args, delim="comma": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(comma) tab = QAction("tab", self.tool_window.ui_area, checkable=True) tab.setChecked(self.settings.delimiter == "tab") tab.triggered.connect(lambda *args, delim="tab": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(tab) space = QAction("space", self.tool_window.ui_area, checkable=True) space.setChecked(self.settings.delimiter == "space") space.triggered.connect(lambda *args, delim="space": self.settings. __setattr__("delimiter", delim)) delimiter.addAction(space) semicolon = QAction("semicolon", self.tool_window.ui_area, checkable=True) semicolon.setChecked(self.settings.delimiter == "semicolon") semicolon.triggered.connect(lambda *args, delim="semicolon": self. settings.__setattr__("delimiter", delim)) delimiter.addAction(semicolon) add_header = QAction("&Include CSV header", self.tool_window.ui_area, checkable=True) add_header.setChecked(self.settings.include_header) add_header.triggered.connect(self.header_check) export.addAction(add_header) comma.triggered.connect( lambda *args, action=tab: action.setChecked(False)) comma.triggered.connect( lambda *args, action=space: action.setChecked(False)) comma.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) tab.triggered.connect( lambda *args, action=comma: action.setChecked(False)) tab.triggered.connect( lambda *args, action=space: action.setChecked(False)) tab.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) space.triggered.connect( lambda *args, action=comma: action.setChecked(False)) space.triggered.connect( lambda *args, action=tab: action.setChecked(False)) space.triggered.connect( lambda *args, action=semicolon: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=comma: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=tab: action.setChecked(False)) semicolon.triggered.connect( lambda *args, action=space: action.setChecked(False)) menu.setNativeMenuBar(False) self._menu = menu layout.setMenuBar(menu) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def calc_relative_thermo(self, *args): """changes the values on the 'relative' tab called when the tool is opened and whenever something changes on the relative tab""" def calc_free_energies(nrg_list, co_list, T, w0): """returns lists for ZPVE, H, RRHO G, QRRHO G, and QHARM G for the items in nrg_list and co_list at temperature T and frequency parameter w0""" ZPVEs = [] Hs = [] Gs = [] RRHOG = [] QHARMG = [] for i in range(0, len(nrg_list)): ZPVEs.append([]) Hs.append([]) Gs.append([]) RRHOG.append([]) QHARMG.append([]) for nrg, co in zip(nrg_list[i], co_list[i]): ZPVE = co.ZPVE ZPVEs[-1].append(nrg + ZPVE) dH = co.therm_corr(T, w0, CompOutput.RRHO)[1] Hs[-1].append(nrg + dH) dG = co.calc_G_corr(temperature=T, v0=w0, method=CompOutput.RRHO) Gs[-1].append(nrg + dG) dQRRHOG = co.calc_G_corr(temperature=T, v0=w0, method=CompOutput.QUASI_RRHO) RRHOG[-1].append(nrg + dQRRHOG) dQHARMG = co.calc_G_corr(temperature=T, v0=w0, method=CompOutput.QUASI_HARMONIC) QHARMG[-1].append(nrg + dQHARMG) return ZPVEs, Hs, Gs, RRHOG, QHARMG def boltzmann_weight(energies1, energies2, T): """ energies - list of lists list axis 0 - molecule groups axis 1 - energies of conformers boltzmann weight energies for conformers combine energies for molecule groups return the difference""" totals1 = [] totals2 = [] beta = UNIT.HART_TO_JOULE / (PHYSICAL.KB * T) for energy_group in energies1: if len(energy_group) == 0: continue rezero = min(energy_group) rel_nrgs = [(x - rezero) for x in energy_group] weights = [np.exp(-beta * nrg) for nrg in rel_nrgs] totals1.append(-PHYSICAL.BOLTZMANN * T * np.log(sum(weights)) + rezero * UNIT.HART_TO_KCAL) for energy_group in energies2: if len(energy_group) == 0: continue rezero = min(energy_group) rel_nrgs = [(x - rezero) for x in energy_group] weights = [np.exp(-beta * nrg) for nrg in rel_nrgs] totals2.append(-PHYSICAL.BOLTZMANN * T * np.log(sum(weights)) + rezero * UNIT.HART_TO_KCAL) return sum(totals2) - sum(totals1) ref_Es = self.ref_group.energies() ref_cos = self.ref_group.compOutputs() empty_groups = [] for i, group in enumerate(ref_cos): if len(group) == 0: empty_groups.append(i) for ndx in empty_groups[::-1]: ref_Es.pop(ndx) ref_cos.pop(ndx) other_Es = self.other_group.energies() other_cos = self.other_group.compOutputs() empty_groups = [] for i, group in enumerate(other_cos): if len(group) == 0: empty_groups.append(i) for ndx in empty_groups[::-1]: other_Es.pop(ndx) other_cos.pop(ndx) if any( len(x) == 0 or all(len(x) == 0 for y in x) for x in [ref_Es, other_Es, ref_cos, other_cos]): self.relative_dE.setText("") self.relative_dZPVE.setText("") self.relative_dH.setText("") self.relative_dG.setText("") self.relative_dQRRHOG.setText("") self.relative_dQHARMG.setText("") return T = self.relative_temperature.value() self.settings.rel_temp = T w0 = self.relative_v0.value() if w0 != self.settings.w0: self.settings.w0 = w0 ref_ZPVEs, ref_Hs, ref_Gs, ref_QRRHOGs, ref_QHARMGs = calc_free_energies( ref_Es, ref_cos, T, w0) other_ZPVEs, other_Hs, other_Gs, other_QRRHOGs, other_QHARMGs = calc_free_energies( other_Es, other_cos, T, w0) rel_E = boltzmann_weight(ref_Es, other_Es, T) rel_ZPVE = boltzmann_weight(ref_ZPVEs, other_ZPVEs, T) rel_H = boltzmann_weight(ref_Hs, other_Hs, T) rel_G = boltzmann_weight(ref_Gs, other_Gs, T) rel_QRRHOG = boltzmann_weight(ref_QRRHOGs, other_QRRHOGs, T) rel_QHARMG = boltzmann_weight(ref_QHARMGs, other_QHARMGs, T) self.relative_dE.setText("%.1f" % rel_E) self.relative_dZPVE.setText("%.1f" % rel_ZPVE) self.relative_dH.setText("%.1f" % rel_H) self.relative_dG.setText("%.1f" % rel_G) self.relative_dQRRHOG.setText("%.1f" % rel_QRRHOG) self.relative_dQHARMG.setText("%.1f" % rel_QHARMG) def open_link(self, theory): """open the oft-cited QRRHO or QHARM reference""" link = self.theory_helper[theory] run(self.session, "open %s" % link) def save_csv(self): """save data on current tab to CSV file""" filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)") if filename: s = self.get_csv() with open(filename, 'w') as f: f.write(s.strip()) self.session.logger.status("saved to %s" % filename) def copy_csv(self): """put CSV data for current tab on the clipboard""" app = QApplication.instance() clipboard = app.clipboard() csv = self.get_csv() clipboard.setText(csv) self.session.logger.status("copied to clipboard") def get_csv(self): """get CSV data for the current tab""" if self.settings.delimiter == "comma": delim = "," elif self.settings.delimiter == "space": delim = " " elif self.settings.delimiter == "tab": delim = "\t" elif self.settings.delimiter == "semicolon": delim = ";" if self.tab_widget.currentIndex() == 0: if self.settings.include_header: s = delim.join(["E" , "ZPE", "H(RRHO)", "G(RRHO)", "G(Quasi-RRHO)", "G(Quasi-harmonic)", \ "dZPE", "dH(RRHO)", "dG(RRHO)", "dG(Quasi-RRHO)", "dG(Quasi-harmonic)", \ "SP File", "Thermo File"]) s += "\n" else: s = "" float_fmt = "%.12f" + delim str_dmt = "%s" + delim + "%s" fmt = 11 * float_fmt + str_dmt + "\n" E = float(self.sp_nrg_line.text()) dZPE = float(self.zpe_line.text()) dH = float(self.enthalpy_line.text()) rrho_dG = float(self.rrho_g_line.text()) qrrho_dG = float(self.qrrho_g_line.text()) qharm_dG = float(self.qharm_g_line.text()) ZPE = float(self.zpe_sum_line.text()) H = float(self.h_sum_line.text()) rrho_G = float(self.rrho_g_sum_line.text()) qrrho_G = float(self.qrrho_g_sum_line.text()) qharm_G = float(self.qharm_g_sum_line.text()) sp_mdl = self.sp_selector.currentData() sp_name = sp_mdl.name therm_mdl = self.thermo_selector.currentData() therm_name = therm_mdl.name s += fmt % (E, ZPE, H, rrho_G, qrrho_G, qharm_G, dZPE, dH, rrho_dG, qrrho_dG, qharm_dG, sp_name, therm_name) elif self.tab_widget.currentIndex() == 1: if self.settings.include_header: s = delim.join([ "ΔE", "ΔZPE", "ΔH(RRHO)", "ΔG(RRHO)", "ΔG(Quasi-RRHO)", "ΔG(Quasi-Harmonic)" ]) s += "\n" else: s = "" float_fmt = "%.1f" + delim fmt = 6 * float_fmt try: dE = float(self.relative_dE.text()) dZPVE = float(self.relative_dZPVE.text()) dH = float(self.relative_dH.text()) dG = float(self.relative_dG.text()) dQRRHOG = float(self.relative_dQRRHOG.text()) dQHARMG = float(self.relative_dQHARMG.text()) s += fmt % (dE, dZPVE, dH, dG, dQRRHOG, dQHARMG) except ValueError: pass return s def header_check(self, state): """user has [un]checked the 'include header' option on the menu""" if state: self.settings.include_header = True else: self.settings.include_header = False def set_sp(self): """set energy entry for when sp model changes""" if self.sp_selector.currentIndex() >= 0: fr = self.sp_selector.currentData() self.check_geometry_rmsd("SP") self.sp_nrg_line.setText("%.6f" % fr.other['energy']) else: self.sp_nrg_line.setText("") self.update_sum() def set_thermo_mdl(self): """frequencies filereader has changed on the absolute tab also changes the temperature option (on the absolute tab only)""" if self.thermo_selector.currentIndex() >= 0: fr = self.thermo_selector.currentData() self.check_geometry_rmsd("THERM") if 'temperature' in fr.other: self.temperature_line.setValue(fr.other['temperature']) self.set_thermo() def check_geometry_rmsd(self, *args): """check RMSD between energy and frequency filereader on the absolute tab if the RMSD is > 10^-5 or the number of atoms is different, put a warning in the status bar""" if self.thermo_selector.currentIndex( ) >= 0 and self.sp_selector.currentIndex() >= 0: fr = self.sp_selector.currentData() fr2 = self.thermo_selector.currentData() if len(fr.atoms) != len(fr2.atoms): self.status.showMessage( "structures are not the same: different number of atoms") return geom = Geometry(fr) geom2 = Geometry(fr2) rmsd = geom.RMSD(geom2) if not isclose(rmsd, 0, atol=10**-5): rmsd = geom.RMSD(geom2, sort=True) if not isclose(rmsd, 0, atol=10**-5): self.status.showMessage( "structures might not be the same - RMSD = %.4f" % rmsd) else: self.status.showMessage("") def set_thermo(self): """computes thermo corrections and sets thermo entries for when thermo model changes""" #index of combobox is -1 when combobox has no entries if self.thermo_selector.currentIndex() >= 0: fr = self.thermo_selector.currentData() if fr not in self.thermo_co: self.thermo_co[fr] = CompOutput(fr) co = self.thermo_co[fr] v0 = self.v0_edit.value() if v0 != self.settings.w0: self.settings.w0 = v0 T = self.temperature_line.value() if not T: return dZPE = co.ZPVE #compute enthalpy and entropy at this temperature #AaronTools uses Grimme's Quasi-RRHO, but this is the same as RRHO when w0=0 dE, dH, s = co.therm_corr(temperature=T, v0=0, method="RRHO") rrho_dg = dH - T * s #compute G with quasi entropy treatments qrrho_dg = co.calc_G_corr(v0=v0, temperature=T, method="QRRHO") qharm_dg = co.calc_G_corr(v0=v0, temperature=T, method="QHARM") self.zpe_line.setText("%.6f" % dZPE) self.enthalpy_line.setText("%.6f" % dH) self.rrho_g_line.setText("%.6f" % rrho_dg) self.qrrho_g_line.setText("%.6f" % qrrho_dg) self.qharm_g_line.setText("%.6f" % qharm_dg) else: self.zpe_line.setText("") self.enthalpy_line.setText("") self.rrho_g_line.setText("") self.qrrho_g_line.setText("") self.qharm_g_line.setText("") self.update_sum() def update_sum(self): """updates the sum of energy and thermo corrections""" dZPE = self.zpe_line.text() dH = self.enthalpy_line.text() dG = self.rrho_g_line.text() dG_qrrho = self.qrrho_g_line.text() dG_qharm = self.qharm_g_line.text() nrg = self.sp_nrg_line.text() if len(dH) == 0 or len(nrg) == 0: self.zpe_sum_line.setText("") self.h_sum_line.setText("") self.rrho_g_sum_line.setText("") self.qrrho_g_sum_line.setText("") self.qharm_g_sum_line.setText("") return else: dZPE = float(dZPE) dH = float(dH) dG = float(dG) dG_qrrho = float(dG_qrrho) dG_qharm = float(dG_qharm) nrg = float(nrg) zpe = nrg + dZPE enthalpy = nrg + dH rrho_g = nrg + dG qrrho_g = nrg + dG_qrrho qharm_g = nrg + dG_qharm self.zpe_sum_line.setText("%.6f" % zpe) self.h_sum_line.setText("%.6f" % enthalpy) self.rrho_g_sum_line.setText("%.6f" % rrho_g) self.qrrho_g_sum_line.setText("%.6f" % qrrho_g) self.qharm_g_sum_line.setText("%.6f" % qharm_g) def display_help(self): """Show the help for this tool in the help viewer.""" from chimerax.core.commands import run run(self.session, 'open %s' % self.help if self.help is not None else "") def delete(self): #overload because closing a tool window doesn't destroy any widgets on it self.settings.ref_col_1 = self.ref_group.tree.columnWidth(0) self.settings.ref_col_2 = self.ref_group.tree.columnWidth(1) self.settings.other_col_1 = self.other_group.tree.columnWidth(0) self.settings.other_col_2 = self.other_group.tree.columnWidth(1) self.sp_selector.deleteLater() self.thermo_selector.deleteLater() self.ref_group.deleteLater() self.other_group.deleteLater() return super().delete() def close(self): #overload because closing a tool window doesn't destroy any widgets on it self.settings.ref_col_1 = self.ref_group.tree.columnWidth(0) self.settings.ref_col_2 = self.ref_group.tree.columnWidth(1) self.settings.other_col_1 = self.other_group.tree.columnWidth(0) self.settings.other_col_2 = self.other_group.tree.columnWidth(1) self.sp_selector.deleteLater() self.thermo_selector.deleteLater() self.ref_group.deleteLater() self.other_group.deleteLater() return super().close()