class MainWidget(QWidget):
    data_directory_group_box: DataDirectoryBox
    error_message: QErrorMessage
    grid: QVBoxLayout
    network_grid: Tabs
    network_widget: NetworkWidget
    settings_tab: SettingsTabDialog

    def __init__(self):
        super().__init__()
        self.setWindowTitle('Node Launcher')
        self.error_message = QErrorMessage(self)
        try:
            self.network_widget = NetworkWidget()
        except ZmqPortsNotOpenError as e:
            self.error_message.showMessage(str(e))
            self.error_message.exec_()
            sys.exit(0)

        self.network_grid = Tabs(self, self.network_widget)
        self.network_grid.currentChanged.connect(self.on_tab_change)

        self.grid = QVBoxLayout()

        self.settings_tab = SettingsTabDialog(
            node_set=self.network_widget.node_set)

        settings_action = QAction('&Settings', self)
        settings_action.setShortcut(QKeySequence.Preferences)
        settings_action.triggered.connect(self.settings_tab.show)

        self.menubar = QMenuBar()
        self.menubar.setMinimumWidth(self.menubar.sizeHint().width())
        file_menu = self.menubar.addMenu('&File')
        file_menu.addAction(settings_action)
        self.grid.addWidget(self.menubar)

        self.grid.addStretch()
        self.grid.addWidget(self.network_grid)
        self.setLayout(self.grid)

        self.settings_tab.bitcoin_tab.change_network.connect(
            self.change_network)

        timer = QTimer()
        timer.singleShot(1000, self.check_version)

    def on_tab_change(self, index: int):
        log.info('on_tab_change', index=index)
        if index == 1:
            self.network_grid.lncli_widget.input.setFocus()
        elif index == 2:
            self.network_grid.bitcoin_cli_widget.input.setFocus()

    def check_version(self):
        latest_version = LauncherSoftware().get_latest_release_version()
        if latest_version is None:
            return
        latest_major, latest_minor, latest_bugfix = latest_version.split('.')
        major, minor, bugfix = NODE_LAUNCHER_RELEASE.split('.')

        major_upgrade = latest_major > major

        minor_upgrade = (latest_major == major and latest_minor > minor)

        bugfix_upgrade = (latest_major == major and latest_minor == minor
                          and latest_bugfix > bugfix)

        if major_upgrade or minor_upgrade or bugfix_upgrade:
            message_box = QMessageBox(self)
            message_box.setTextFormat(Qt.RichText)
            message_box.setText(UPGRADE)
            message_box.setInformativeText(
                f'Your version: {NODE_LAUNCHER_RELEASE}\n'
                f'New version: {latest_version}')
            message_box.exec_()

    def change_network(self, network: Network):
        self.network_widget.nodes_layout.image_label.set_image(
            f'bitcoin-{network}.png')

    def mousePressEvent(self, event):
        focused_widget = QApplication.focusWidget()
        if isinstance(focused_widget, QLineEdit):
            focused_widget.clearFocus()
        QMainWindow.mousePressEvent(self, event)
Exemple #2
0
class GraphEditor(QWidget):
    _get_state_cb: typing.Callable
    _dispatch_cb: typing.Callable
    _menu_bar: QMenuBar

    _node_width: int = 120
    _node_height: int = 80

    def __init__(self,
                 get_state_cb: typing.Callable,
                 dispatch_cb: typing.Callable,
                 parent: QWidget = None):
        super().__init__(parent)
        self.setMouseTracking(True)

        self._get_state_cb = get_state_cb
        self._dispatch_cb = dispatch_cb
        self._menu_bar = QMenuBar(self)

        self.create_node_action = QAction('Create node', self)
        QObject.connect(
            self.create_node_action, SIGNAL("triggered()"),
            lambda: self._dispatch_cb({
                'Type': 'CreateNode',
                'NodeModel': 'DummyNode',
                'NodeId': 1,
                'Metadata': {
                    'Name': 'New dummy node',
                    'PositionX': 200,
                    'PositionY': 120
                }
            }))

        self.node_menu = QMenu('Node')
        self.node_menu.addAction(self.create_node_action)

        self._menu_bar.addMenu(self.node_menu)

        self._focused_node_id = None
        self._clicked_node_id = None
        self._selected_node_id = None
        self._last_click_pos = None

    def sizeHint(self):
        return QSize(800, 600)

    def resizeEvent(self, event: QResizeEvent):
        self._menu_bar.setGeometry(0, 0,
                                   event.size().width(),
                                   self._menu_bar.sizeHint().height())

    def paintEvent(self, event: QPaintEvent):
        if self._get_state_cb is None:
            print(
                "GraphEditor: state callback is not specified. Ignoring paint event."
            )
            return

        state = self._get_state_cb()

        painter = QPainter(self)
        painter.fillRect(self.rect(), QColor.fromRgb(58, 58, 58))

        for node_id, node in state.items():
            if node_id == self._selected_node_id:
                state = NodeUiState.Selected
            elif node_id == self._focused_node_id:
                state = NodeUiState.Focused
            else:
                state = NodeUiState.Normal

            draw_node(node, state, painter)

    def mouseMoveEvent(self, event: QMouseEvent):
        state = self._get_state_cb()

        if self._clicked_node_id is not None and self._clicked_node_id in state.keys(
        ):
            self._handle_node_grab(event, state)
        else:
            self._handle_mouse_move(event, state)

    def _handle_mouse_move(self, event, state):
        prev_focused_node_id = self._focused_node_id
        self._focused_node_id = None
        for node_id, node in state.items():
            if intersect_node(node, event.pos()):
                self._focused_node_id = node_id
        if self._focused_node_id != prev_focused_node_id:
            self.update()

    def _handle_node_grab(self, event, state):
        drag_delta = event.pos() - self._last_click_pos
        new_x = state[
            self._clicked_node_id].metadata['PositionX'] + drag_delta.x()
        new_y = state[
            self._clicked_node_id].metadata['PositionY'] + drag_delta.y()

        self._last_click_pos = event.pos()
        self._dispatch_cb({
            'Type': 'UpdateNodeMetadata',
            'NodeId': self._clicked_node_id,
            'Metadata': {
                'PositionX': new_x,
                'PositionY': new_y
            }
        })

    def mousePressEvent(self, event: QMouseEvent):
        state = self._get_state_cb()

        for node_id, node in state.items():
            if intersect_node(node, event.pos()):
                self._clicked_node_id = node_id
                self._last_click_pos = event.pos()

    def mouseReleaseEvent(self, event: QMouseEvent):
        self._selected_node_id = self._clicked_node_id
        self._clicked_node_id = None
        self._last_click_pos = None
        self.update()