Initial commit: I had claude do some AI slop to add qt qui
Some checks failed
Build and upload development artifacts / Describe (push) Has been cancelled
Build and upload development artifacts / Fedora 41 Build (push) Has been cancelled

This commit is contained in:
robojerk 2025-06-09 20:07:58 -07:00
parent a5a958861e
commit 687edd639b
7 changed files with 495 additions and 4513 deletions

9
requirements.txt Normal file
View file

@ -0,0 +1,9 @@
# Common dependencies
requests>=2.28.0
# Platform-specific dependencies
# Windows
PySide6>=6.5.0; platform_system=="Windows"
# Linux/Unix
PyGObject>=3.42.0; platform_system!="Windows"

File diff suppressed because it is too large Load diff

4
src/gui/__init__.py Normal file
View file

@ -0,0 +1,4 @@
"""
Flatpost GUI package.
This package contains the GUI implementations for Flatpost.
"""

101
src/gui/base.py Normal file
View file

@ -0,0 +1,101 @@
from abc import ABC, abstractmethod
from typing import List, Dict, Any, Optional
class BaseGUI(ABC):
"""Abstract base class for GUI implementations."""
@abstractmethod
def __init__(self, system_mode: bool = False, system_only_mode: bool = False):
"""Initialize the GUI with system mode settings."""
pass
@abstractmethod
def create_window(self):
"""Create the main application window."""
pass
@abstractmethod
def create_header_bar(self):
"""Create the header bar with controls."""
pass
@abstractmethod
def create_panels(self):
"""Create the main panels for the application."""
pass
@abstractmethod
def create_applications_panel(self, title: str):
"""Create the applications panel."""
pass
@abstractmethod
def display_apps(self, apps: List[Dict[str, Any]]):
"""Display the list of applications."""
pass
@abstractmethod
def show_waiting_dialog(self, message: str = "Please wait while task is running..."):
"""Show a waiting dialog."""
pass
@abstractmethod
def show_error_dialog(self, message: str):
"""Show an error dialog."""
pass
@abstractmethod
def show_info_dialog(self, message: str):
"""Show an information dialog."""
pass
@abstractmethod
def create_button(self, callback, app: Optional[Dict[str, Any]] = None,
label: Optional[str] = None, condition: Optional[bool] = None):
"""Create a button with the given properties."""
pass
@abstractmethod
def create_search_entry(self):
"""Create a search entry widget."""
pass
@abstractmethod
def create_dropdown(self, items: List[str]):
"""Create a dropdown menu with the given items."""
pass
@abstractmethod
def create_switch(self, label: str, active: bool = False):
"""Create a switch widget."""
pass
@abstractmethod
def create_label(self, text: str):
"""Create a label widget."""
pass
@abstractmethod
def create_image(self, path: str, size: int):
"""Create an image widget."""
pass
@abstractmethod
def create_scrollable_container(self):
"""Create a scrollable container."""
pass
@abstractmethod
def create_box(self, orientation: str = "vertical"):
"""Create a box container."""
pass
@abstractmethod
def create_list_box(self):
"""Create a list box widget."""
pass
@abstractmethod
def run(self):
"""Run the main application loop."""
pass

49
src/gui/factory.py Normal file
View file

@ -0,0 +1,49 @@
import platform
from typing import Optional
from .base import BaseGUI
from .gtk_gui import GTKGUI
from .qt_gui import QtGUI
class GUIFactory:
"""Factory class for creating GUI backends."""
@staticmethod
def create_gui(backend: str = None, system_mode: bool = False, system_only_mode: bool = False) -> BaseGUI:
"""
Create a GUI instance with the specified backend.
Args:
backend: The GUI backend to use ("gtk" or "qt"). If None, will use platform default.
system_mode: Whether to run in system mode
system_only_mode: Whether to run in system-only mode
Returns:
A GUI instance implementing the BaseGUI interface
Raises:
ValueError: If an invalid backend is specified
"""
if backend is None:
# Use platform-specific default
if platform.system() == "Windows":
backend = "qt"
else:
backend = "gtk"
backend = backend.lower()
if backend == "gtk":
if platform.system() == "Windows":
raise ValueError("GTK backend is not supported on Windows. Please use Qt backend.")
return GTKGUI(system_mode, system_only_mode)
elif backend == "qt":
return QtGUI(system_mode, system_only_mode)
else:
raise ValueError(f"Invalid GUI backend: {backend}. Must be 'gtk' or 'qt'")
@staticmethod
def get_available_backends() -> list[str]:
"""Get a list of available GUI backends for the current platform."""
if platform.system() == "Windows":
return ["qt"]
return ["gtk", "qt"]

135
src/gui/gtk_gui.py Normal file
View file

@ -0,0 +1,135 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("GLib", "2.0")
gi.require_version("Flatpak", "1.0")
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gtk, Gio, Gdk, GLib, GdkPixbuf
from typing import List, Dict, Any, Optional, Callable
from .base import BaseGUI
class GTKGUI(BaseGUI):
def __init__(self, system_mode: bool = False, system_only_mode: bool = False):
self.system_mode = system_mode
self.system_only_mode = system_only_mode
self.window = None
self.create_window()
def create_window(self):
app_title = "Flatpost (user mode)"
if self.system_only_mode:
app_title = "Flatpost (system-only mode)"
elif self.system_mode:
app_title = "Flatpost (system mode)"
self.window = Gtk.Window(title=app_title)
self.window.set_default_size(1280, 720)
# Enable drag and drop
self.window.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
self.window.drag_dest_add_uri_targets()
def create_header_bar(self):
header = Gtk.HeaderBar()
header.set_show_close_button(True)
self.window.set_titlebar(header)
return header
def create_panels(self):
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.window.add(main_box)
return main_box
def create_applications_panel(self, title: str):
panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
label = Gtk.Label(label=title)
panel.pack_start(label, False, False, 0)
return panel
def display_apps(self, apps: List[Dict[str, Any]]):
# Implementation will be similar to the original GTK code
pass
def show_waiting_dialog(self, message: str = "Please wait while task is running..."):
dialog = Gtk.Dialog(title="Please Wait", parent=self.window, flags=0)
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
content_area = dialog.get_content_area()
label = Gtk.Label(label=message)
content_area.add(label)
dialog.show_all()
return dialog
def show_error_dialog(self, message: str):
dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text=message
)
dialog.run()
dialog.destroy()
def show_info_dialog(self, message: str):
dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
text=message
)
dialog.run()
dialog.destroy()
def create_button(self, callback: Callable, app: Optional[Dict[str, Any]] = None,
label: Optional[str] = None, condition: Optional[bool] = None):
button = Gtk.Button(label=label if label else "")
if callback:
if app:
button.connect("clicked", callback, app)
else:
button.connect("clicked", callback)
if condition is not None:
button.set_sensitive(condition)
return button
def create_search_entry(self):
return Gtk.SearchEntry()
def create_dropdown(self, items: List[str]):
combo = Gtk.ComboBoxText()
for item in items:
combo.append_text(item)
return combo
def create_switch(self, label: str, active: bool = False):
switch = Gtk.Switch()
switch.set_active(active)
return switch
def create_label(self, text: str):
return Gtk.Label(label=text)
def create_image(self, path: str, size: int):
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, size, size, True)
image = Gtk.Image.new_from_pixbuf(pixbuf)
return image
def create_scrollable_container(self):
scrolled = Gtk.ScrolledWindow()
scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
return scrolled
def create_box(self, orientation: str = "vertical"):
if orientation == "vertical":
return Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
return Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
def create_list_box(self):
return Gtk.ListBox()
def run(self):
self.window.connect("destroy", Gtk.main_quit)
self.window.show_all()
Gtk.main()

127
src/gui/qt_gui.py Normal file
View file

@ -0,0 +1,127 @@
from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QLabel, QLineEdit, QComboBox, QScrollArea, QListWidget,
QMessageBox, QDialog, QProgressBar, QSwitch, QFrame
)
from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtGui import QPixmap, QImage
from typing import List, Dict, Any, Optional, Callable
from .base import BaseGUI
class QtGUI(BaseGUI):
def __init__(self, system_mode: bool = False, system_only_mode: bool = False):
self.system_mode = system_mode
self.system_only_mode = system_only_mode
self.window = None
self.create_window()
def create_window(self):
app_title = "Flatpost (user mode)"
if self.system_only_mode:
app_title = "Flatpost (system-only mode)"
elif self.system_mode:
app_title = "Flatpost (system mode)"
self.window = QMainWindow()
self.window.setWindowTitle(app_title)
self.window.resize(1280, 720)
def create_header_bar(self):
header = QWidget()
layout = QHBoxLayout(header)
header.setLayout(layout)
return header
def create_panels(self):
central_widget = QWidget()
layout = QHBoxLayout(central_widget)
self.window.setCentralWidget(central_widget)
return layout
def create_applications_panel(self, title: str):
panel = QWidget()
layout = QVBoxLayout(panel)
label = QLabel(title)
layout.addWidget(label)
return panel
def display_apps(self, apps: List[Dict[str, Any]]):
# Implementation will be similar to the GTK version but using Qt widgets
pass
def show_waiting_dialog(self, message: str = "Please wait while task is running..."):
dialog = QDialog(self.window)
dialog.setWindowTitle("Please Wait")
layout = QVBoxLayout(dialog)
label = QLabel(message)
progress = QProgressBar()
progress.setRange(0, 0) # Indeterminate progress
layout.addWidget(label)
layout.addWidget(progress)
dialog.show()
return dialog
def show_error_dialog(self, message: str):
QMessageBox.critical(self.window, "Error", message)
def show_info_dialog(self, message: str):
QMessageBox.information(self.window, "Information", message)
def create_button(self, callback: Callable, app: Optional[Dict[str, Any]] = None,
label: Optional[str] = None, condition: Optional[bool] = None):
button = QPushButton(label if label else "")
if callback:
if app:
button.clicked.connect(lambda: callback(app))
else:
button.clicked.connect(callback)
if condition is not None:
button.setEnabled(condition)
return button
def create_search_entry(self):
return QLineEdit()
def create_dropdown(self, items: List[str]):
combo = QComboBox()
combo.addItems(items)
return combo
def create_switch(self, label: str, active: bool = False):
switch = QSwitch()
switch.setChecked(active)
return switch
def create_label(self, text: str):
return QLabel(text)
def create_image(self, path: str, size: int):
pixmap = QPixmap(path)
pixmap = pixmap.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
label = QLabel()
label.setPixmap(pixmap)
return label
def create_scrollable_container(self):
scroll = QScrollArea()
scroll.setWidgetResizable(True)
return scroll
def create_box(self, orientation: str = "vertical"):
widget = QWidget()
if orientation == "vertical":
layout = QVBoxLayout(widget)
else:
layout = QHBoxLayout(widget)
widget.setLayout(layout)
return widget
def create_list_box(self):
return QListWidget()
def run(self):
self.window.show()
# Note: The Qt event loop is typically started in the main application