# UI Framework and PyQt6 CppLab IDE is built with **PyQt6**, a comprehensive Python binding for the Qt 6 application framework. I used it because I have worked with it before and I know my way around it a bit, and it's really nice. Apart from the pretty decision matrix below, the ease of the tool is its greatest advantage and what more would I want as a dev? Now, the irony of this is, that Qt is written in C++ and my initial idea was to develop this project in C++, which can be witnessed in the multiple repos I made earlier and then made private (:sweatdrop:). So, yeah. Bundling and developing with Qt in C++ is a bit of a headache if you ask me. Is it a skill issue? Most probably. Does that mean the project will forever be in python? Unlikely. But, for now, we have Python to thank for this project to have come together so nicely. ## Why PyQt6? ### Decision Matrix | Framework | Pros | Cons | Score | |-----------|------|------|-------| | **PyQt6** | Native look, rich widgets, cross-platform, mature | GPL/Commercial license, learning curve | ⭐⭐⭐⭐⭐ | | Tkinter | Built-in, simple | Limited widgets, outdated look | ⭐⭐ | | wxPython | Native widgets | Less documentation, smaller community | ⭐⭐⭐ | | Kivy | Modern, touch-friendly | Not for desktop IDEs | ⭐⭐ | | Dear ImGui | Game dev, fast | C++ binding, immediate mode | ⭐⭐ | | Web (Electron) | Familiar (HTML/CSS/JS) | Heavy, memory hungry | ⭐⭐⭐ | ### Key Advantages **1. Native Performance** - Written in C++ (Qt framework) - Hardware-accelerated rendering - Low memory footprint (~50-80 MB) - Fast startup time (~1-2 seconds) **2. Rich Widget Library** ```python # Available out-of-the-box QMainWindow # Main application window QTextEdit # Code editor with syntax highlighting QTreeView # File browser QDockWidget # Dockable panels QToolBar # Toolbar with actions QStatusBar # Status bar with labels QTabWidget # Tabbed panels QComboBox # Dropdown selectors QFileDialog # File/folder pickers QMessageBox # Dialogs QSplitter # Resizable panes ``` **3. Cross-Platform** - Windows (native) - macOS (native) - Linux (native) - Same codebase, native look on each platform **4. Qt Designer Integration** - Visual UI design - `.ui` files (XML-based) - Load at runtime with `uic.loadUi()` - WYSIWYG editor **5. Signals & Slots** - Type-safe event system - Decoupled components - Thread-safe communication **6. Threading Support** - `QThread` for background work - `QObject.moveToThread()` pattern - Thread-safe signals - No GIL issues for UI updates **7. Mature Ecosystem** - 20+ years of Qt development - Extensive documentation - Large community - Proven in production (Autodesk Maya, Blender, etc.) ## Architecture ### Component Hierarchy ``` Application (QApplication) └── MainWindow (QMainWindow) ├── MenuBar (QMenuBar) │ ├── File Menu │ ├── Edit Menu │ ├── Build Menu │ └── Run Menu ├── ToolBar (QToolBar) │ ├── New File Action │ ├── Open File Action │ ├── Save Action │ ├── Build Action │ └── Run Action ├── Central Widget │ ├── Splitter (QSplitter) │ │ ├── File Tree (QTreeView) [Left] │ │ └── Editor (QTextEdit) [Right] ├── Dock Widgets │ └── Bottom Panel (QDockWidget) │ └── Tab Widget (QTabWidget) │ ├── Build Tab (QTextEdit) │ ├── Problems Tab (QListWidget) │ └── Console Tab (QTextEdit) └── Status Bar (QStatusBar) ├── Mode Label ├── Build Status Label ├── Toolchain Label └── Standard Label ``` ### UI Definition **File**: `src/cpplab/ui/MainWindow.ui` ```xml MainWindow 0 0 1200 800 CppLab IDE Qt::Horizontal File Output ``` ### Loading UI at Runtime **File**: `src/cpplab/app.py` ```python from PyQt6 import uic from PyQt6.QtWidgets import QMainWindow class MainWindow(QMainWindow): def __init__(self): super().__init__() # Load UI from .ui file ui_path = Path(__file__).parent / "ui" / "MainWindow.ui" uic.loadUi(ui_path, self) # Setup additional components self._setup_widgets() self._connect_signals() ``` ## Signals & Slots ### Event System **Qt's Signal/Slot Mechanism**: - Type-safe callbacks - One-to-many connections - Automatic disconnection when objects deleted - Thread-safe (with queued connections) ### Example: Build Button **Signal Definition** (built into QPushButton): ```python # QPushButton has a 'clicked' signal button.clicked # Signal[bool] ``` **Connection**: ```python def __init__(self): # Connect signal to slot self.buildButton.clicked.connect(self.on_build_clicked) def on_build_clicked(self): """Slot called when build button clicked.""" print("Building...") self.build_current() ``` ### Custom Signals **File**: `src/cpplab/app.py` ```python from PyQt6.QtCore import QObject, pyqtSignal class BuildWorker(QObject): # Define custom signals started = pyqtSignal() # No arguments finished = pyqtSignal(object, int) # BuildResult, elapsed_ms error = pyqtSignal(str) # Error message def run(self): """Background build task.""" self.started.emit() # Emit started signal try: result = self.builder.build_project(...) elapsed_ms = ... self.finished.emit(result, elapsed_ms) # Emit finished except Exception as e: self.error.emit(str(e)) # Emit error ``` **Connection**: ```python # Create worker worker = BuildWorker(...) # Connect signals worker.started.connect(self.on_build_started) worker.finished.connect(self.on_build_finished) worker.error.connect(self.on_build_error) # Start thread thread = QThread() worker.moveToThread(thread) thread.started.connect(worker.run) thread.start() ``` ### Signal Types | Signal Type | Use Case | Example | |-------------|----------|---------| | `pyqtSignal()` | No data | `started` | | `pyqtSignal(str)` | String data | `error(message)` | | `pyqtSignal(int, int)` | Multiple args | `progress(current, total)` | | `pyqtSignal(object)` | Complex data | `finished(result)` | ## Threading ### QThread Pattern **Modern Approach** (used in CppLab): ```python class BuildWorker(QObject): """Worker object for background builds.""" finished = pyqtSignal(object, int) def __init__(self, builder): super().__init__() self.builder = builder def run(self): """Run in background thread.""" result = self.builder.build_project(...) self.finished.emit(result, elapsed_ms) # Usage worker = BuildWorker(builder) thread = QThread() worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(on_finished) thread.start() ``` **Old Approach** (don't use): ```python # ❌ Don't subclass QThread class BuildThread(QThread): def run(self): # This is harder to maintain pass ``` **Why Modern Approach is Better**: - [x] Worker is reusable (not tied to thread) - [x] Easier to test (just call `worker.run()`) - [x] Cleaner separation of concerns - [x] Recommended by Qt documentation ### Thread Safety **UI Updates Must Be on Main Thread**: ```python # [x] Safe: Use signal to update UI class BuildWorker(QObject): finished = pyqtSignal(str) # Signal emits to main thread def run(self): result = "Build succeeded" self.finished.emit(result) # Thread-safe # ❌ Unsafe: Direct UI update from thread class BuildThread(QThread): def run(self): # This will crash or corrupt UI self.text_edit.append("Build succeeded") ``` ### Thread Cleanup ```python def start_build_task(self, action): """Start build in background thread with proper cleanup.""" # Create worker and thread worker = BuildWorker(...) thread = QThread() # Setup cleanup worker.finished.connect(thread.quit) worker.finished.connect(worker.deleteLater) thread.finished.connect(thread.deleteLater) # Move worker to thread worker.moveToThread(thread) # Start thread.started.connect(worker.run) thread.start() # Store references (prevent premature GC) self.current_build_thread = thread self.current_build_worker = worker ``` ## Styling ### Qt Style Sheets (QSS) **Similar to CSS**: ```python # Apply stylesheet self.setStyleSheet(""" QMainWindow { background-color: #2b2b2b; } QTextEdit { background-color: #1e1e1e; color: #d4d4d4; font-family: Consolas; font-size: 10pt; } QPushButton { background-color: #0e639c; color: white; border: none; padding: 5px 10px; border-radius: 3px; } QPushButton:hover { background-color: #1177bb; } """) ``` ### Theme System **File**: `src/cpplab/settings.py` ```python THEMES = { "classic": """ QMainWindow { background-color: #f0f0f0; } QTextEdit { background-color: white; color: black; } """, "sky_blue": """ QMainWindow { background-color: #e6f2ff; } QTextEdit { background-color: #f0f8ff; color: #003366; } """ } ``` **Application**: ```python def apply_settings(self): """Apply user settings.""" settings = load_settings() # Apply theme theme = settings.theme if theme in THEMES: self.setStyleSheet(THEMES[theme]) # Apply font font = QFont("Consolas", settings.font_size) if settings.bold_font: font.setBold(True) self.buildOutputEdit.setFont(font) ``` ## Widgets in Detail ### QTextEdit (Code Editor) **Features**: - Multi-line text editing - Syntax highlighting (via QSyntaxHighlighter) - Line numbers (custom implementation) - Find/replace - Undo/redo - Read-only mode **Usage**: ```python from PyQt6.QtWidgets import QTextEdit editor = QTextEdit() editor.setPlainText("#include \n\nint main() {\n return 0;\n}") editor.setFont(QFont("Consolas", 10)) editor.setTabStopDistance(40) # 4-space tabs ``` ### QTreeView (File Browser) **Features**: - Hierarchical data display - File system model - Icons - Expand/collapse - Selection **Usage**: ```python from PyQt6.QtWidgets import QTreeView from PyQt6.QtGui import QFileSystemModel tree = QTreeView() model = QFileSystemModel() model.setRootPath("C:/Projects/MyApp") tree.setModel(model) tree.setRootIndex(model.index("C:/Projects/MyApp")) ``` ### QDockWidget (Bottom Panel) **Features**: - Dockable/floating - Resizable - Closeable - Multiple dock areas **Usage**: ```python from PyQt6.QtWidgets import QDockWidget, QTextEdit from PyQt6.QtCore import Qt dock = QDockWidget("Output", self) dock.setWidget(QTextEdit()) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, dock) ``` ### QTabWidget (Tabbed Interface) **Features**: - Multiple tabs - Tab switching - Tab icons - Closeable tabs **Usage**: ```python from PyQt6.QtWidgets import QTabWidget, QTextEdit tabs = QTabWidget() tabs.addTab(QTextEdit(), "Build") tabs.addTab(QTextEdit(), "Problems") tabs.addTab(QTextEdit(), "Console") ``` ### QStatusBar (Status Bar) **Features**: - Permanent and temporary messages - Multiple widgets (labels, progress bars) - Automatic layout **Usage**: ```python from PyQt6.QtWidgets import QLabel # Temporary message (disappears after 3 seconds) self.statusBar().showMessage("File saved", 3000) # Permanent widgets mode_label = QLabel("Mode: Project") self.statusBar().addPermanentWidget(mode_label) ``` ## Dialogs ### Standard Dialogs **File Dialog**: ```python from PyQt6.QtWidgets import QFileDialog file_path, _ = QFileDialog.getOpenFileName( self, "Open File", "", "C++ Files (*.cpp *.cc);;C Files (*.c);;All Files (*)" ) ``` **Message Box**: ```python from PyQt6.QtWidgets import QMessageBox QMessageBox.information(self, "Success", "Build completed!") QMessageBox.warning(self, "Warning", "Unsaved changes") QMessageBox.critical(self, "Error", "Build failed") ``` **Input Dialog**: ```python from PyQt6.QtWidgets import QInputDialog text, ok = QInputDialog.getText(self, "Input", "Enter project name:") if ok: print(f"Project name: {text}") ``` ### Custom Dialogs **File**: `src/cpplab/settings_dialog.py` ```python from PyQt6.QtWidgets import QDialog, QTabWidget, QVBoxLayout class SettingsDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Settings") self.resize(500, 400) # Create layout layout = QVBoxLayout() # Add tabs tabs = QTabWidget() tabs.addTab(self._create_appearance_tab(), "Appearance") tabs.addTab(self._create_build_tab(), "Build") layout.addWidget(tabs) self.setLayout(layout) def _create_appearance_tab(self): # Create appearance settings UI pass ``` ## Performance Considerations ### Memory Usage **CppLab IDE** (measured): - Startup: ~50 MB - With project open: ~80 MB - During build: ~100 MB **Comparison**: - VS Code: ~300-500 MB - CLion: ~800-1200 MB - Visual Studio: ~1000-2000 MB ### Startup Time **CppLab IDE** (measured): - Cold start: ~1.5 seconds - Warm start: ~0.8 seconds **Comparison**: - VS Code: ~2-3 seconds - CLion: ~5-8 seconds - Visual Studio: ~10-15 seconds ### Rendering Performance **PyQt6 Advantages**: - Hardware-accelerated (OpenGL/DirectX) - Efficient text rendering - Lazy loading of UI elements - Virtual scrolling for large lists ## Development Workflow ### UI Design Process **1. Design in Qt Designer** ``` Open Qt Designer → Create .ui file → Save in src/cpplab/ui/ ``` **2. Load in Python** ```python from PyQt6 import uic uic.loadUi("ui/MainWindow.ui", self) ``` **3. Access widgets** ```python # Widgets are accessible as attributes self.buildButton.clicked.connect(self.on_build) self.editor.textChanged.connect(self.on_text_changed) ``` ### Hot Reload (Development) ```python import sys from PyQt6.QtWidgets import QApplication def main(): app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec()) if __name__ == "__main__": main() ``` **Restart to see changes** (no hot reload by default) ### Debugging **Print statements**: ```python print("Build clicked") # Shows in terminal ``` **Qt debugging**: ```python from PyQt6.QtCore import qDebug, qWarning, qCritical qDebug("Debug message") qWarning("Warning message") qCritical("Critical error") ``` **Visual debugging**: ```python # Show widget boundaries self.setStyleSheet("* { border: 1px solid red; }") ``` ## Packaging ### PyInstaller **Command**: ```bash pyinstaller --onefile --windowed --icon=icon.ico ^ --add-data "ui;ui" ^ --add-data "compilers;compilers" ^ src/cpplab/__main__.py ``` **Includes**: - PyQt6 DLLs (~80 MB) - Python runtime (~20 MB) - UI files - Resources **Output**: - Single `.exe` file (~100-120 MB) - No dependencies required --- **Next**: [Settings and Configuration](Settings-And-Configuration.md) **Previous**: [Build System Details](Build-System-Details.md)