MartinThoma / flitz

Fast Location / Indexing / Traversal Zone. A file explorer / finder with a tkinter GUI.
MIT License
0 stars 0 forks source link

Allow alternative frontends #5

Open MartinThoma opened 4 months ago

MartinThoma commented 4 months ago

I'm uncertain if this makes sense, but maximum flexibility would be to have the option to implement alternative frontends.

Requirements

MartinThoma commented 4 months ago

Looking at https://www.pythonguis.com/faq/which-python-gui-library/, it seems that PyQt6 or PySide6.

https://www.pythonguis.com/faq/pyqt6-vs-pyside6/ : I would need to use GPL if I use PyQt6.

https://stackoverflow.com/questions/6888750/pyqt-or-pyside-which-one-to-use

Pyside6 seems a bit more pythonic

PyInstaller supports PyQt

Other interesting frontends:

MartinThoma commented 4 months ago

We could have a base class:

from abc import ABC, abstractmethod

class Frontend(ABC):
    def __init__(self, cfg):
        self.cfg = cfg
        self.width = None
        self.height = None
        self.x = None
        self.y = None

    def set_geometry(self, width: int, height: int, x=0, y=0) -> None:
        self.width = width
        self.height = height
        self.x = x
        self.y = y

    def set_title(self, title: str) -> None:
        self.title = title

    def bind(self, event, callback) -> None:
        self.root.bind(event, callback)

    @abstractmethod
    def run(self):
        pass

and classes implementing it:

from tkinter import Tk, Label
import tkinter as tk
from tkinter import messagebox, ttk
from tkinter.simpledialog import askstring
from flitz.frontends.base import Frontend
from flitz.config import Config
from pathlib import Path

class TkinterFrontend(Frontend):
    def __init__(self, cfg: Config):
        self.cfg = cfg

        self.root = Tk()
        self.set_geometry(width=self.cfg.window.width, height=self.cfg.window.height)
        self.root.configure(background=self.cfg.background_color)

        self.root.rowconfigure(0, weight=0, minsize=45)
        self.root.rowconfigure(1, weight=1)
        self.root.columnconfigure(0, weight=0, minsize=80, uniform="group1")
        self.root.columnconfigure(1, weight=85, uniform="group1")
        self.root.columnconfigure(2, weight=5, uniform="group1")

        self.style = ttk.Style()
        self.style.theme_use("clam")  # necessary to get the selection highlight
        self.style.configure(
            "Treeview.Heading",
            font=(self.cfg.font, self.cfg.font_size),
        )
        self.style.map(
            "Treeview",
            foreground=[
                ("selected", self.cfg.selection.text_color),
                (None, self.cfg.text_color),
            ],
            background=[
                # Adding `(None, self.cfg.background_color)` here makes the
                # selection not work anymore
                ("selected", self.cfg.selection.background_color),
            ],
            fieldbackground=self.cfg.background_color,
        )

        # Set window icon (you need to provide a suitable icon file)
        icon_path = str(Path(__file__).resolve().parent / "../icon.gif")
        img = tk.Image("photo", file=icon_path)
        self.root.tk.call("wm", "iconphoto", self.root._w, img)  # type: ignore[attr-defined]

    def set_geometry(self, width, height, x=0, y=0):
        self.width = width
        self.height = height
        self.x = x
        self.y = y
        self.root.geometry(f"{self.width}x{self.height}")

    def set_title(self, title: str) -> None:
        self.title = title
        self.root.title(self.title)

    def bind(self, event, callback) -> None:
        pass

    def run(self):
        label = Label(self.root, text="Tkinter GUI")
        label.pack()
        self.root.mainloop()

The problem is how to set the layout. The tk.Frame / pack concept of tkinter likely doesn't translate properly to other GUI frameworks

MartinThoma commented 4 months ago

The next issue is that some front-end elements have direct connections to the application state which again should trigger other front-end components to change:

I've introduced a on_current_path_change to trigger all changes, but that currently is in the main application. I could pass a reference to the main application to the frontend... I'm not sure if that is a good idea.

Instead, I could introduce a "controller".