Source code for vibegui

"""
vibegui - A Python library for creating GUI applications from JSON configuration files.

This library allows you to define GUI layouts, widgets, and their properties in JSON format
and automatically generate the corresponding interface using either Qt (PySide6/PyQt6) or wxPython.

Backend Support:
- Qt backend via qtpy (supports PySide6/PyQt6)
- wxPython backend as an alternative
- tkinter backend (built into Python)

Usage:
    # Simplest approach - using the unified interface
    from vibegui import GuiBuilder
    GuiBuilder.create_and_run('config.json')

    # Explicit backend selection
    from vibegui import set_backend, GuiBuilder
    set_backend('tk')  # or 'qt' or 'wx' or 'gtk'
    GuiBuilder.create_and_run('config.json')

    # Advanced: Manual application lifecycle (Qt example)
    from vibegui import GuiBuilder
    from qtpy.QtWidgets import QApplication
    import sys
    app = QApplication(sys.argv)
    gui = GuiBuilder('config.json')
    gui.show()
    app.exec()
"""

from __future__ import annotations
from typing import Callable, Any, Dict, Optional

# Don't import GUI backends immediately - use lazy loading instead
# This prevents conflicts between different GUI frameworks

def _lazy_import_qt() -> tuple:
    """Lazy import Qt backend."""
    from .qt.qt_gui_builder import GuiBuilder as QtGuiBuilder
    from .qt.qt_widget_factory import WidgetFactory
    return QtGuiBuilder, WidgetFactory

def _lazy_import_wx() -> tuple:
    """Lazy import wxPython backend."""
    from .wx.wx_gui_builder import WxGuiBuilder
    from .wx.wx_widget_factory import WxWidgetFactory
    return WxGuiBuilder, WxWidgetFactory

def _lazy_import_tk() -> tuple:
    """Lazy import tkinter backend."""
    from .tk.tk_gui_builder import TkGuiBuilder
    from .tk.tk_widget_factory import TkWidgetFactory
    return TkGuiBuilder, TkWidgetFactory

def _lazy_import_gtk() -> tuple:
    """Lazy import GTK backend."""
    from .gtk.gtk_gui_builder import GtkGuiBuilder
    from .gtk.gtk_widget_factory import GtkWidgetFactory
    return GtkGuiBuilder, GtkWidgetFactory

def _lazy_import_flet() -> tuple:
    """Lazy import Flet backend."""
    from .flet.flet_gui_builder import FletGuiBuilder
    from .flet.flet_widget_factory import FletWidgetFactory
    return FletGuiBuilder, FletWidgetFactory

from .config_loader import ConfigLoader, CustomButtonConfig
from .backend import (
    get_backend, set_backend, get_available_backends,
    get_backend_info, is_backend_available
)
from .exceptions import (
    BackendError, ConfigurationError
)
from typing import Dict, Any, Optional, Union


[docs] class GuiBuilder: """ Unified GUI builder that automatically selects the appropriate backend. This class provides a consistent interface that works with Qt, wxPython, tkinter, and GTK backends, automatically selecting the available backend or using the one specified via set_backend(). Attributes: backend (str): The currently active backend name builder: The underlying backend-specific builder instance Example: Simplest usage - create and run immediately: >>> from vibegui import GuiBuilder >>> GuiBuilder.create_and_run('config.json') Force a specific backend: >>> from vibegui import set_backend, GuiBuilder >>> set_backend('tk') >>> GuiBuilder.create_and_run('config.json') For Qt backend, create QApplication first: >>> from vibegui import GuiBuilder >>> from qtpy.QtWidgets import QApplication >>> import sys >>> app = QApplication(sys.argv) >>> gui = GuiBuilder('config.json') >>> gui.show() >>> app.exec() For tkinter backend: >>> from vibegui import set_backend, GuiBuilder >>> set_backend('tk') >>> gui = GuiBuilder('config.json') >>> gui.show() >>> gui.run() Use with configuration dictionary: >>> config = {"window": {"title": "My App"}, "fields": [...]} >>> GuiBuilder.create_and_run(config_dict=config) """ def __init__(self, config_path: Optional[str] = None, config_dict: Optional[Dict[str, Any]] = None, backend: Optional[str] = None): """ Initialize the GUI builder with automatic backend selection. Args: config_path: Path to JSON configuration file config_dict: Configuration dictionary (alternative to config_path) backend: Force a specific backend ('qt', 'wx', 'tk', 'gtk', 'flet'). If None, auto-detects. """ self._backend = None self._builder = None # Set backend if specified if backend: if not is_backend_available(backend): raise BackendError(f"Backend '{backend}' is not available") set_backend(backend) # Get current backend self._backend = get_backend() # Create appropriate builder using lazy imports if self._backend == 'qt': QtGuiBuilder, _ = _lazy_import_qt() self._builder = QtGuiBuilder(config_path, config_dict) elif self._backend == 'wx': WxGuiBuilder, _ = _lazy_import_wx() self._builder = WxGuiBuilder(config_path, config_dict) elif self._backend == 'tk': TkGuiBuilder, _ = _lazy_import_tk() self._builder = TkGuiBuilder(config_path, config_dict) elif self._backend == 'gtk': try: GtkGuiBuilder, _ = _lazy_import_gtk() self._builder = GtkGuiBuilder(config_path, config_dict) except ImportError: raise BackendError("GTK backend is not available (missing dependencies)") elif self._backend == 'flet': try: FletGuiBuilder, _ = _lazy_import_flet() self._builder = FletGuiBuilder(config_path, config_dict) except ImportError: raise BackendError("Flet backend is not available (missing dependencies)") else: raise BackendError(f"Unsupported backend: {self._backend}") @property def backend(self) -> str: """Get the current backend being used.""" return self._backend @property def builder(self) -> Any: # Use Any to avoid import issues with type hints """Get the underlying builder instance.""" return self._builder
[docs] def show(self) -> None: """Show the GUI window.""" if hasattr(self._builder, 'show'): self._builder.show() elif hasattr(self._builder, 'Show'): self._builder.Show()
[docs] def hide(self) -> None: """Hide the GUI window.""" if hasattr(self._builder, 'hide'): self._builder.hide() elif hasattr(self._builder, 'Hide'): self._builder.Hide()
[docs] def get_form_data(self) -> Dict[str, Any]: """Get all form data as a dictionary.""" return self._builder.get_form_data()
[docs] def set_form_data(self, data: Dict[str, Any]) -> None: """Set form data from a dictionary.""" self._builder.set_form_data(data)
[docs] def clear_form(self) -> None: """Clear all form fields.""" self._builder.clear_form()
[docs] def get_field_value(self, field_name: str) -> Any: """ Get the value of a specific field. Args: field_name: Name of the field to get the value from Returns: The current value of the field, or None if field doesn't exist """ return self._builder.get_field_value(field_name)
[docs] def set_field_value(self, field_name: str, value: Any) -> bool: """ Set the value of a specific field. Args: field_name: Name of the field to set value: Value to set for the field Returns: True if the value was set successfully, False otherwise """ return self._builder.set_field_value(field_name, value)
[docs] def set_submit_callback(self, callback: Callable) -> None: """Set a callback function to be called when the form is submitted.""" self._builder.set_submit_callback(callback)
[docs] def set_cancel_callback(self, callback: Callable) -> None: """Set a callback function to be called when the form is cancelled.""" self._builder.set_cancel_callback(callback)
[docs] def set_custom_button_callback(self, button_name: str, callback: Callable) -> None: """Set a callback function to be called when a custom button is clicked.""" self._builder.set_custom_button_callback(button_name, callback)
[docs] def remove_custom_button_callback(self, button_name: str) -> None: """Remove a custom button callback.""" self._builder.remove_custom_button_callback(button_name)
[docs] def get_custom_button_names(self) -> list[str]: """Get a list of all custom button names from the configuration.""" return self._builder.get_custom_button_names()
[docs] def enable_field(self, field_name: str, enabled: bool = True) -> None: """Enable or disable a specific field.""" self._builder.enable_field(field_name, enabled)
[docs] def show_field(self, field_name: str, visible: bool = True) -> None: """Show or hide a specific field.""" self._builder.show_field(field_name, visible)
[docs] def load_data_from_file(self, data_file_path: str) -> bool: """Load form data from a JSON file and populate the GUI.""" return self._builder.load_data_from_file(data_file_path)
[docs] def load_data_from_dict(self, data: Dict[str, Any]) -> bool: """Load form data from a dictionary and populate the GUI.""" return self._builder.load_data_from_dict(data)
[docs] def save_data_to_file(self, data_file_path: str, include_empty: bool = True) -> bool: """Save current form data to a JSON file.""" return self._builder.save_data_to_file(data_file_path, include_empty)
[docs] def run(self) -> None: """Run the GUI application (start the main loop).""" if hasattr(self._builder, 'run'): self._builder.run() else: # For backends that don't have a run method, try to show and start mainloop self.show() if hasattr(self._builder, 'mainloop'): self._builder.mainloop()
[docs] def close(self) -> None: """Close the GUI application.""" if hasattr(self._builder, 'close'): self._builder.close()
[docs] def __getattr__(self, name: str) -> Any: """ Forward attribute access to the underlying builder. This allows access to backend-specific attributes like Qt Signals (fieldChanged, formSubmitted, formCancelled). """ return getattr(self._builder, name)
[docs] @staticmethod def create_and_run(config_path: Optional[str] = None, config_dict: Optional[Dict[str, Any]] = None, backend: Optional[str] = None) -> 'GuiBuilder': """ Create and run a GUI application with automatic backend detection. Args: config_path: Path to JSON configuration file config_dict: Configuration dictionary (alternative to config_path) backend: Force a specific backend ('qt', 'wx', or 'tk'). If None, auto-detects. Returns: GuiBuilder instance """ # Set backend if specified if backend: set_backend(backend) current_backend = get_backend() if current_backend == 'qt': QtGuiBuilder, _ = _lazy_import_qt() return QtGuiBuilder.create_and_run(config_path, config_dict) elif current_backend == 'wx': WxGuiBuilder, _ = _lazy_import_wx() return WxGuiBuilder.create_and_run(config_path, config_dict) elif current_backend == 'tk': TkGuiBuilder, _ = _lazy_import_tk() return TkGuiBuilder.create_and_run(config_path, config_dict) elif current_backend == 'gtk': GtkGuiBuilder, _ = _lazy_import_gtk() return GtkGuiBuilder.create_and_run(config_path, config_dict) elif current_backend == 'flet': FletGuiBuilder, _ = _lazy_import_flet() return FletGuiBuilder.create_and_run(config_path, config_dict) else: raise BackendError(f"Unsupported backend: {current_backend}")
__version__ = "1.0.4" __author__ = "Jacob Williams" __all__ = [ "GuiBuilder", "QtGuiBuilder", "WxGuiBuilder", "TkGuiBuilder", "GtkGuiBuilder", "ConfigLoader", "WidgetFactory", "WxWidgetFactory", "TkWidgetFactory", "GtkWidgetFactory", "CustomButtonConfig", "get_backend", "set_backend", "get_available_backends", "get_backend_info", "is_backend_available", "BackendError", "ConfigurationError", "ConfigValidator" ] # Make backend-specific classes available for direct import def __getattr__(name: str) -> Any: """Lazy import backend classes when accessed directly.""" if name == 'QtGuiBuilder': QtGuiBuilder, _ = _lazy_import_qt() return QtGuiBuilder elif name == 'WxGuiBuilder': WxGuiBuilder, _ = _lazy_import_wx() return WxGuiBuilder elif name == 'TkGuiBuilder': TkGuiBuilder, _ = _lazy_import_tk() return TkGuiBuilder elif name == 'GtkGuiBuilder': GtkGuiBuilder, _ = _lazy_import_gtk() return GtkGuiBuilder elif name == 'WidgetFactory': _, WidgetFactory = _lazy_import_qt() return WidgetFactory elif name == 'WxWidgetFactory': _, WxWidgetFactory = _lazy_import_wx() return WxWidgetFactory elif name == 'TkWidgetFactory': _, TkWidgetFactory = _lazy_import_tk() return TkWidgetFactory elif name == 'GtkWidgetFactory': _, GtkWidgetFactory = _lazy_import_gtk() return GtkWidgetFactory elif name == 'ConfigValidator': from .config_validator import ConfigValidator return ConfigValidator else: raise AttributeError(f"module '{__name__}' has no attribute '{name}'")