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)
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()