Initial commit: I had claude do some AI slop to add qt qui
This commit is contained in:
parent
a5a958861e
commit
687edd639b
7 changed files with 495 additions and 4513 deletions
9
requirements.txt
Normal file
9
requirements.txt
Normal 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"
|
||||
4583
src/flatpost.py
4583
src/flatpost.py
File diff suppressed because it is too large
Load diff
4
src/gui/__init__.py
Normal file
4
src/gui/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Flatpost GUI package.
|
||||
This package contains the GUI implementations for Flatpost.
|
||||
"""
|
||||
101
src/gui/base.py
Normal file
101
src/gui/base.py
Normal 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
49
src/gui/factory.py
Normal 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
135
src/gui/gtk_gui.py
Normal 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
127
src/gui/qt_gui.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue