Skip to content

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())
# In MolbotWindow.__init__
self.console.command_submitted.connect(self._handle_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:

  1. User clicks "pick distance" → sets picking mode
  2. The viewer reports atom click → _on_atom_picked() fires
  3. After enough atoms collected → measurement tool runs
  4. 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