|
1 | | -from PyQt5.QtWidgets import ( |
2 | | - QGraphicsEllipseItem, QGraphicsTextItem, QToolTip, |
3 | | - QDialog, QVBoxLayout, QPushButton, QHBoxLayout |
4 | | -) |
5 | | -from PyQt5.QtGui import QBrush, QColor, QFont |
6 | | -from PyQt5.QtCore import Qt |
| 1 | +# node_item.py |
| 2 | +from PyQt5.QtWidgets import QGraphicsEllipseItem, QGraphicsTextItem |
| 3 | +from PyQt5.QtGui import QBrush, QColor, QFont, QPen |
| 4 | +from PyQt5.QtCore import Qt, QPointF, QPropertyAnimation |
7 | 5 |
|
8 | 6 | class NodeItem(QGraphicsEllipseItem): |
9 | | - def __init__(self, node_id, pos, parent_level): |
| 7 | + def __init__(self, node_id, pos, parent, label=None): |
10 | 8 | super().__init__(-20, -20, 40, 40) |
11 | 9 | self.node_id = node_id |
12 | | - self.parent_level = parent_level |
| 10 | + self.parent = parent |
13 | 11 | self.setBrush(QBrush(Qt.gray)) |
| 12 | + self.setPen(QPen(Qt.black, 2)) |
| 13 | + self.setZValue(1) |
| 14 | + |
14 | 15 | self.setFlag(QGraphicsEllipseItem.ItemIsSelectable) |
15 | 16 | self.setAcceptHoverEvents(True) |
16 | 17 | self.setPos(pos) |
| 18 | + |
17 | 19 | self.locked = False |
| 20 | + self.role = None |
| 21 | + self.label = label if label else f"Node {node_id}" |
| 22 | + self.hash_display = None |
| 23 | + |
| 24 | + # Label below node |
| 25 | + self.text_item = QGraphicsTextItem(self.label, self) |
| 26 | + self.text_item.setFont(QFont("Arial", 9)) |
| 27 | + self.text_item.setDefaultTextColor(Qt.white) |
| 28 | + self.text_item.setPos(-self.text_item.boundingRect().width() / 2, 22) |
18 | 29 |
|
19 | | - self.text = QGraphicsTextItem(str(node_id)) |
20 | | - self.text.setDefaultTextColor(Qt.white) |
21 | | - self.text.setFont(QFont("Arial", 10, QFont.Bold)) |
22 | | - self.text.setParentItem(self) |
23 | | - self.text.setPos(-8, -10) |
| 30 | + # Lock visual indicator |
| 31 | + self.lock_icon = QGraphicsTextItem("🔒", self) |
| 32 | + self.lock_icon.setFont(QFont("Arial", 12)) |
| 33 | + self.lock_icon.setDefaultTextColor(Qt.darkGray) |
| 34 | + self.lock_icon.setPos(-10, -35) |
| 35 | + self.lock_icon.setVisible(False) |
| 36 | + def set_label(self, label_text): |
| 37 | + if hasattr(self, 'label'): |
| 38 | + self.scene().removeItem(self.label) |
24 | 39 |
|
25 | | - # Define the allowed color palette |
26 | | - self.allowed_colors = { |
27 | | - "Red": "#E74C3C", |
28 | | - "Green": "#2ECC71", |
29 | | - " Blue": "#3498DB", |
30 | | - "Yellow": "#F1C40F" |
31 | | - } |
| 40 | + from PyQt5.QtWidgets import QGraphicsTextItem |
| 41 | + from PyQt5.QtCore import Qt |
32 | 42 |
|
| 43 | + self.label = QGraphicsTextItem(label_text) |
| 44 | + self.label.setDefaultTextColor(Qt.white) |
| 45 | + self.label.setParentItem(self) |
| 46 | + self.label.setPos(-20, -35) |
| 47 | + |
| 48 | + def set_color(self, color): |
| 49 | + if not self.locked: |
| 50 | + self.setBrush(QBrush(QColor(color))) |
| 51 | + def change_color(self): |
| 52 | + current_color = self.brush().color() |
| 53 | + next_color = self.get_next_color(current_color) |
| 54 | + self.setBrush(QBrush(next_color)) |
| 55 | + self.parent.node_colors[self.node_id] = next_color |
33 | 56 | def mousePressEvent(self, event): |
34 | | - if not self.locked and not self.parent_level.committed: |
35 | | - self.show_color_selection_dialog() |
36 | | - super().mousePressEvent(event) |
| 57 | + self.change_color() |
| 58 | + def get_next_color(self, current_color): |
| 59 | + color_cycle = [Qt.red, Qt.green, Qt.blue] # Or your fixed palette |
| 60 | + try: |
| 61 | + i = color_cycle.index(current_color) |
| 62 | + return color_cycle[(i + 1) % len(color_cycle)] |
| 63 | + except ValueError: |
| 64 | + return color_cycle[0] |
| 65 | + def lock(self): |
| 66 | + self.locked = True |
| 67 | + self.setBrush(QBrush(Qt.gray)) |
| 68 | + self.setPen(QPen(Qt.darkGray, 2)) |
| 69 | + self.lock_icon.setVisible(True) |
| 70 | + self.update() |
37 | 71 |
|
38 | | - def show_color_selection_dialog(self): |
39 | | - dialog = QDialog() |
40 | | - dialog.setWindowTitle(f"Select a color for Node {self.node_id}") |
41 | | - layout = QVBoxLayout() |
| 72 | + def unlock(self): |
| 73 | + self.locked = False |
| 74 | + self.setPen(QPen(Qt.black, 2)) |
| 75 | + self.setBrush(QBrush(Qt.gray)) |
| 76 | + self.lock_icon.setVisible(False) |
| 77 | + self.update() |
42 | 78 |
|
43 | | - button_row = QHBoxLayout() |
44 | | - for label, hex_code in self.allowed_colors.items(): |
45 | | - btn = QPushButton(label) |
46 | | - btn.setStyleSheet(f"background-color: {hex_code}; color: black; font-weight: bold;") |
47 | | - btn.clicked.connect(lambda _, c=hex_code: self.select_color(dialog, c)) |
48 | | - button_row.addWidget(btn) |
| 79 | + def show_hash(self, short_hash): |
| 80 | + if not self.hash_display: |
| 81 | + self.hash_display = QGraphicsTextItem(short_hash, self) |
| 82 | + self.hash_display.setFont(QFont("Courier", 7)) |
| 83 | + self.hash_display.setDefaultTextColor(Qt.lightGray) |
| 84 | + self.hash_display.setPos(-self.hash_display.boundingRect().width() / 2, 35) |
| 85 | + else: |
| 86 | + self.hash_display.setPlainText(short_hash) |
49 | 87 |
|
50 | | - layout.addLayout(button_row) |
51 | | - dialog.setLayout(layout) |
52 | | - dialog.exec_() |
| 88 | + def hide_hash(self): |
| 89 | + if self.hash_display: |
| 90 | + self.scene().removeItem(self.hash_display) |
| 91 | + self.hash_display = None |
53 | 92 |
|
54 | | - def select_color(self, dialog, color_hex): |
55 | | - self.setBrush(QBrush(QColor(color_hex))) |
56 | | - self.parent_level.node_colors[self.node_id] = color_hex |
57 | | - self.parent_level.update_narration(f"🎨 Prover colored node {self.node_id} with color.") |
58 | | - dialog.accept() |
| 93 | + def set_label(self, label): |
| 94 | + self.label = label |
| 95 | + self.text_item.setPlainText(label) |
59 | 96 |
|
60 | | - def hoverEnterEvent(self, event): |
61 | | - if self.locked: |
62 | | - QToolTip.showText(event.screenPos(), "🔒 This node is committed", self.parent_level) |
63 | | - else: |
64 | | - QToolTip.showText(event.screenPos(), f" Node {self.node_id}: Click to choose a color", self.parent_level) |
| 97 | + def pulse_red(self): |
| 98 | + # Animate node glow red for non-repudiation breach |
| 99 | + animation = QPropertyAnimation(self, b"opacity") |
| 100 | + animation.setDuration(800) |
| 101 | + animation.setKeyValueAt(0, 1.0) |
| 102 | + animation.setKeyValueAt(0.5, 0.3) |
| 103 | + animation.setKeyValueAt(1, 1.0) |
| 104 | + animation.start() |
| 105 | + self.setBrush(QBrush(QColor("red"))) |
| 106 | + def set_label(self, text): |
| 107 | + label = QGraphicsTextItem(text, self) |
| 108 | + label.setDefaultTextColor(Qt.black) |
| 109 | + label.setFont(QFont("Arial", 10)) |
| 110 | + label.setPos(-10, -10) # Adjust position if needed |
0 commit comments