"""SaraView's desktop client: browse and edit files in a local root directory, or on a SaraView server (the one on SpineS) through its API. Laid out like SaraFem: a directory tree on the left, the file list (or an open file) on the right.""" import os import subprocess import sys from typing import Optional import flet as ft from App import App from Browser import Browser from Core import ( AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, EffectiveSettings, LoadConfig, Palette, ResizeHandle, ) from Filetree import Filetree from Settings import ShowSettingsDialog def _SetCursorThemeFallback() -> None: """GTK reads XCURSOR_THEME directly and doesn't always inherit it (shows as 'Unable to load ... from the cursor theme'). Same fix as SaraFem's.""" if os.environ.get("XCURSOR_THEME") or sys.platform == "win32": # GTK/GNOME only return try: Result = subprocess.run(["gsettings", "get", "org.gnome.desktop.interface", "cursor-theme"], capture_output=True, text=True, timeout=2) Theme = Result.stdout.strip().strip("'") except (OSError, subprocess.SubprocessError): Theme = "" os.environ["XCURSOR_THEME"] = Theme or "Adwaita" def Main(Page: ft.Page) -> None: Config = LoadConfig() Appearance = EffectiveSettings(Config) ApplyAppearance(Appearance) # before anything reads Palette.X Page.title = AppName Page.theme_mode = ft.ThemeMode.DARK Page.theme = BuildTheme() Page.dark_theme = BuildTheme() Page.fonts = {BundledFontFamily: "fonts/Fixedsys_Excelsior_Regular.ttf"} Page.window.title_bar_hidden = Appearance.HideWindowTitleBar Page.padding = 0 Page.bgcolor = Palette.Base Page.window.width = 1180 Page.window.height = 760 if sys.platform == "win32": Page.window.icon = str(AssetsDir / "Icon.ico") # taskbar/title bar (Linux: the desktop entry) Page.window.min_width = 720 Page.window.min_height = 480 TheApp = App(Page, Config) def OnPathChanged(Root: Optional[str], RelativePath: str, Reload: bool) -> None: if Reload: # directories may have moved, so the tree starts over Tree.Reload() Tree.RevealPath(Root, RelativePath) def ShowSettings(Category: str = "General") -> None: ShowSettingsDialog(TheApp, OnSettingsSaved, Category) def OnSettingsSaved(Reconnect: bool) -> None: Tree.visible = Divider.visible = Config.ShowSidebar Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False) if Reconnect: TheApp.Connect() # its listeners reload the tree and the browser else: Body.update() Tree.Reload() # "Show files in the tree" may have changed TheBrowser.Refresh() Tree.RevealPath(TheBrowser.Root, TheBrowser.Path) def ToggleSidebar() -> None: Config.ShowSidebar = not Config.ShowSidebar Config.Save() Tree.visible = Divider.visible = Config.ShowSidebar Body.update() def OnDrag(Delta: float) -> None: Tree.width = max(140, (Tree.width or Config.SidebarWidth) + Delta) Tree.update() def OnDragEnd() -> None: Config.SidebarWidth = int(Tree.width) Config.Save() TheBrowser = Browser(TheApp, OnPathChanged, lambda Category=None: ShowSettings(Category or ("Connection" if TheApp.ConnectError else "General")), ToggleSidebar) Tree = Filetree(TheApp, TheBrowser.OpenDirectory, TheBrowser.OpenFile) Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False) Divider = ResizeHandle(OnDrag, OnDragEnd) Tree.visible = Divider.visible = Config.ShowSidebar # STRETCH so the divider gets the full height to be dragged along. Body = ft.Row([Tree, Divider, TheBrowser], expand=True, spacing=0, vertical_alignment=ft.CrossAxisAlignment.STRETCH) Page.add(Body) def OnSourceChanged() -> None: Tree.Reload() TheBrowser.SourceChanged() TheApp.Listeners.append(OnSourceChanged) TheApp.Connect() if __name__ == "__main__": _SetCursorThemeFallback() ft.run(Main, assets_dir=str(AssetsDir))