コード例 #1
0
ファイル: flowsolverapp.py プロジェクト: anescient/flowsolver
    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)
コード例 #2
0
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)
コード例 #3
0
	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)
コード例 #4
0
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)
コード例 #5
0
ファイル: foctviewer.py プロジェクト: arrrobase/foct-viewer
    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
コード例 #6
0
ファイル: settings.py プロジェクト: WarshipGirls/WGViewer
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
コード例 #7
0
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
コード例 #8
0
ファイル: node_properties.py プロジェクト: UAVCAN/gui_tool
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
コード例 #9
0
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)
コード例 #10
0
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()}")
コード例 #11
0
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()
コード例 #12
0
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
コード例 #13
0
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')
コード例 #14
0
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
コード例 #15
0
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...')
コード例 #16
0
ファイル: mainwindow.py プロジェクト: NeVerTools/NeVer2
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()
コード例 #17
0
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()
コード例 #18
0
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)
コード例 #19
0
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)
コード例 #20
0
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")
コード例 #21
0
ファイル: result_window.py プロジェクト: baykelper/dupeguru
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()
コード例 #22
0
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()
コード例 #23
0
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_()
コード例 #24
0
ファイル: node_properties.py プロジェクト: UAVCAN/gui_tool
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")
コード例 #25
0
ファイル: result_window.py プロジェクト: Beyond82/dupeguru
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())
コード例 #26
0
ファイル: compute_thermo.py プロジェクト: pk-organics/SEQCROW
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()