Signal Communication¶
Qt Signal/Slot Pattern¶
codemol uses Qt's signal/slot mechanism for decoupled communication between components. This avoids tight coupling — components emit signals without knowing who listens.
Key Signal Flows¶
Console → Window¶
The console emits a signal when the user submits a command:
# In Console
class Console(QTextEdit):
command_submitted = pyqtSignal(str) # emitted on Enter
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return:
self.command_submitted.emit(self.current_command())
Worker Signals¶
Long-running operations (API calls, agent runs) use QThread workers with signals for progress and completion:
sequenceDiagram
participant Main as Main Thread
participant Worker as QThread Worker
participant UI as Console/Panels
Main->>Worker: worker.start()
Worker->>Worker: run() — blocking work
Worker-->>Main: signal: finished(result)
Main->>UI: update display
Worker-->>Main: signal: error(message)
Main->>UI: show error
Example — AgentWorker:
class AgentWorker(QThread):
finished = pyqtSignal(str) # agent response text
error = pyqtSignal(str) # error message
tool_called = pyqtSignal(str) # tool execution notification
def run(self):
try:
result = run_agent(self.tools, self.cmd, self.message, ...)
self.finished.emit(result)
except Exception as e:
self.error.emit(str(e))
Structure Activation¶
When the user clicks a structure in the sidebar:
graph LR
A[Sidebar Click] -->|signal| B[StructureManager]
B -->|activate| C[StructureSession]
C -->|cmd.disable/enable| D[Engine]
B -->|signal| E[Window]
E -->|update| F[Scope + Title]
Atom Picking¶
Interactive picking (for distance, angle measurements) uses a callback chain:
- User clicks "pick distance" → sets picking mode
- The viewer reports atom click →
_on_atom_picked()fires - After enough atoms collected → measurement tool runs
- Result displayed in console
Timer-Based Updates¶
Some state requires periodic polling rather than event-driven updates:
- Viewer refresh — Viewer rendering syncs on a timer
- Trajectory playback — Frame advancement at controlled FPS
- Share session heartbeat — WebSocket keep-alive
# Example: trajectory playback timer
self._traj_timer = QTimer()
self._traj_timer.timeout.connect(self._advance_frame)
self._traj_timer.start(33) # ~30 FPS
Design Benefits¶
| Pattern | Benefit |
|---|---|
| Signals for commands | Console doesn't know about window internals |
| Worker threads | UI stays responsive during API calls |
| Protocol-based access | Managers decouple from window implementation |
| Timer-based polling | Smooth animations without blocking the event loop |