Files
SaraFem/Settings.py
T

701 lines
31 KiB
Python

"""In-app preferences dialog (Features/Major: "Edit preferences in SaraFem").
Laid out as a category sidebar + detail panel (Features/Minor: "Make settings
look good... sidebar with areas, then a much bigger main box"), with the
left/right/top/bottom-style choices as icon-button pickers instead of text
dropdowns ("some way to choose layout in a more graphical... way")."""
from pathlib import Path
from typing import Callable
import flet as ft
from Core import (
AppConfig, AvailableThemes, ChipButtonStyle, FolderIconBand, InstalledFontFamilies, LoadSharedSettings, Palette,
SharedSettings,
)
from FolderIcons import FolderIconChoices, FolderIconLabelOverrides
# Settings Browser can already hot-apply live (dotfiles, breadcrumbs, preview, tags).
# The window title bar, sidebar (bookmarks/filetree) layout, theme, and SaraTag's
# dynamically-loaded module need a restart to take effect.
RestartRequiredNote = "Window title bar, Theme, Font and SaraTag settings need a restart to take effect."
DialogWidth = 700
DialogHeight = 520
CategoryListWidth = 150
def _MinMaxRow(Label: str, MinValue: int, MaxValue: int) -> tuple[ft.Row, ft.TextField, ft.TextField]:
MinField = ft.TextField(value=str(MinValue), label=f"{Label} min", keyboard_type=ft.KeyboardType.NUMBER, expand=True)
MaxField = ft.TextField(value=str(MaxValue), label=f"{Label} max", keyboard_type=ft.KeyboardType.NUMBER, expand=True)
return ft.Row([MinField, MaxField]), MinField, MaxField
def _SectionLabel(Text: str) -> ft.Text:
return ft.Text(Text, color=Palette.Overlay0, size=12)
def _UnselectedPickerStyle() -> ft.ButtonStyle:
return ft.ButtonStyle(color=Palette.Overlay0, shape=ft.RoundedRectangleBorder(radius=4))
def _OptionPicker(Options: list[tuple[str, str, str]], Initial: str) -> tuple[ft.Control, Callable[[], str]]:
"""A row of icon buttons standing in for a text dropdown — Options is a
list of (key, label, Icons-attribute-name). Returns (control, get_value)."""
State = {"value": Initial}
Buttons: dict[str, ft.OutlinedButton] = {}
def Select(Key: str) -> None:
State["value"] = Key
for OptionKey, Button in Buttons.items():
Button.style = ChipButtonStyle() if OptionKey == Key else _UnselectedPickerStyle()
Row.update()
for Key, Label, IconName in Options:
Buttons[Key] = ft.OutlinedButton(
content=Label,
icon=getattr(ft.Icons, IconName),
style=ChipButtonStyle() if Key == Initial else _UnselectedPickerStyle(),
on_click=lambda e, k=Key: Select(k),
)
Row = ft.Row(list(Buttons.values()), wrap=True)
return Row, lambda: State["value"]
def BuildSettingsDialog(
Config: AppConfig,
OnSave: Callable[[], None],
OnDismiss: Callable[[], None],
OnExport: Callable[[str], None],
OnImport: Callable[[str], None],
) -> ft.AlertDialog:
Themes = AvailableThemes()
Fonts = InstalledFontFamilies()
def ThemePicker(Value: str) -> ft.Dropdown:
return ft.Dropdown(
value=Value, label="Theme", options=[ft.DropdownOption(key=Name, text=Name) for Name in Themes],
)
def FontPicker(Value: str) -> ft.Dropdown:
# Installed fonts plus the bundled one; type to filter the long list.
Names = Fonts if Value in Fonts else [Value] + Fonts
return ft.Dropdown(
value=Value, label="Font", editable=True, enable_filter=True, menu_height=320,
options=[ft.DropdownOption(key=Name, text=Name) for Name in Names],
)
# "SaraSuite" tab: the shared settings every SaraSuite program reads from
# ~/.config/SaraSuite/Config.toml, and whether SaraFem follows them.
Shared = LoadSharedSettings(Config)
UseSharedSwitch = ft.Switch(label="SaraFem uses these shared settings", value=Config.UseSharedSettings)
SharedThemeDropdown = ThemePicker(Shared.Theme)
SharedFontDropdown = FontPicker(Shared.FontFamily)
SharedTitleBarSwitch = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar)
# Export/import as one portable file (Features/Major) — separate from
# the shared settings above: that's the always-on, automatic sync every
# SaraSuite program reads on startup, this is a one-off snapshot you
# choose a path for, to back up or carry to another machine.
SettingsFileField = ft.TextField(
value=str(Path.home() / "SaraFem-Settings.toml"), label="Settings file path"
)
# "Middle" tab: the main file-list pane itself, not the panels around it.
ViewModePicker, GetViewMode = _OptionPicker(
[("list", "List", "VIEW_LIST_OUTLINED"), ("grid", "Grid", "GRID_VIEW_OUTLINED")], Config.ViewMode
)
ListDescriptionSwitch = ft.Switch(label="Description", value=Config.ListShowDescription)
ListSizeSwitch = ft.Switch(label="Size", value=Config.ListShowSize)
ListModifiedSwitch = ft.Switch(label="Modified", value=Config.ListShowModified)
ListOwnerSwitch = ft.Switch(label="Owner", value=Config.ListShowOwner)
ListCreatedSwitch = ft.Switch(label="Created", value=Config.ListShowCreated)
ListContentSwitch = ft.Switch(
label="Content (item count / line count / image size)", value=Config.ListShowContent
)
ListAccessSwitch = ft.Switch(label="Access (rwx)", value=Config.ListShowAccess)
GridIconSizeField = ft.TextField(
value=str(Config.GridIconSize), label="Grid icon size", keyboard_type=ft.KeyboardType.NUMBER
)
GridNamePositionPicker, GetGridNamePosition = _OptionPicker(
[("below", "Below", "SOUTH"), ("above", "Above", "NORTH")],
"below" if Config.GridNameBelowIcon else "above",
)
ListIconSizeField = ft.TextField(
value=str(Config.ListIconSize), label="List icon size", keyboard_type=ft.KeyboardType.NUMBER
)
FontSizeField = ft.TextField(
value=str(Config.FontSize), label="Name font size", keyboard_type=ft.KeyboardType.NUMBER
)
ToolbarHamburgerSwitch = ft.Switch(
label="Collapse toolbar buttons into one menu", value=Config.ToolbarHamburger
)
# Folder Icons (Features: "turning on different variants depending on
# how full a directory is... add more... choose icons (or custom
# images) for each"). WorkingFolderBands is edited live in the dialog;
# only committed to Config.FolderIconBands in DoSave, same as every
# other field here. One band (the default) behaves exactly like
# today's plain folder icon everywhere — nothing changes until a
# second variant gets added.
FolderThresholdField = ft.TextField(
value=str(Config.FolderFullnessThreshold),
label="Items counted as 100% full",
keyboard_type=ft.KeyboardType.NUMBER,
)
WorkingFolderBands: list[dict] = [
{"Icon": B.Icon, "ImagePath": B.ImagePath} for B in (Config.FolderIconBands or [FolderIconBand()])
]
# Live widget refs, one (IconDropdown, ImageField) pair per currently-
# displayed row — read from directly at save time (and before any
# Add/Remove rebuild) rather than syncing via on_change, since Dropdown
# only exposes on_select (a different event) and TextField already
# keeps its own .value current without any handler needed.
FolderBandWidgets: list[tuple[ft.Dropdown, ft.TextField]] = []
FolderBandsColumn = ft.Column(spacing=8)
def _FolderIconLabel(Name: str) -> str:
return FolderIconLabelOverrides.get(Name, Name.split("_")[0].title())
def _SyncWorkingFolderBands() -> None:
for Index, (IconDropdown, ImageField) in enumerate(FolderBandWidgets):
WorkingFolderBands[Index] = {
"Icon": IconDropdown.value or "FOLDER_OUTLINED", "ImagePath": (ImageField.value or "").strip(),
}
def RebuildFolderBandsColumn(DoUpdate: bool = True) -> None:
FolderBandWidgets.clear()
Count = len(WorkingFolderBands)
Rows: list[ft.Control] = []
for Index, BandData in enumerate(WorkingFolderBands):
Low = round(Index / Count * 100)
High = round((Index + 1) / Count * 100)
def OnRemove(e, I=Index) -> None:
if len(WorkingFolderBands) > 1:
_SyncWorkingFolderBands()
WorkingFolderBands.pop(I)
RebuildFolderBandsColumn()
IconDropdown = ft.Dropdown(
value=BandData["Icon"],
options=[
ft.DropdownOption(key=Name, text=_FolderIconLabel(Name), leading_icon=getattr(ft.Icons, Name))
for Name in FolderIconChoices
],
width=150,
)
ImageField = ft.TextField(value=BandData["ImagePath"], label="Or custom image path", expand=True)
FolderBandWidgets.append((IconDropdown, ImageField))
RowItems: list[ft.Control] = [
ft.Text(f"{Low}-{High}%", width=55, color=Palette.Overlay0), IconDropdown, ImageField,
]
if Count > 1:
RowItems.append(
ft.IconButton(icon=ft.Icons.DELETE_OUTLINE, tooltip="Remove this variant", on_click=OnRemove)
)
Rows.append(ft.Row(RowItems, spacing=8))
FolderBandsColumn.controls = Rows
if DoUpdate:
FolderBandsColumn.update()
def AddFolderBand(e=None) -> None:
_SyncWorkingFolderBands()
WorkingFolderBands.append({"Icon": "FOLDER_OUTLINED", "ImagePath": ""})
RebuildFolderBandsColumn()
RebuildFolderBandsColumn(DoUpdate=False) # dialog isn't shown yet
AddFolderBandButton = ft.OutlinedButton(
content="Add Variant", icon=ft.Icons.ADD, style=ChipButtonStyle(), on_click=AddFolderBand,
)
# SaraFem's own values ("General" tab), only used with shared settings off.
ThemeDropdown = ThemePicker(Config.Theme)
FontDropdown = FontPicker(Config.FontFamily)
TitleBarSwitch = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar)
OwnAppearance = ft.Column(
[
_SectionLabel("SaraFem's own appearance, used when shared settings are off (SaraSuite tab)"),
ThemeDropdown,
FontDropdown,
TitleBarSwitch,
],
spacing=12,
disabled=Config.UseSharedSettings,
opacity=0.5 if Config.UseSharedSettings else 1,
)
def OnUseSharedChanged(e) -> None:
OwnAppearance.disabled = UseSharedSwitch.value
OwnAppearance.opacity = 0.5 if UseSharedSwitch.value else 1
OwnAppearance.update()
UseSharedSwitch.on_change = OnUseSharedChanged
DotfilesSwitch = ft.Switch(label="Show dotfiles", value=Config.ShowDotfiles)
OpenFilesInAppSwitch = ft.Switch(
label="Open text/config/markup files in SaraFem's own viewer, not your default app",
value=Config.OpenFilesInApp,
)
ShowSearchSwitch = ft.Switch(label="Enable search (Ctrl+F)", value=Config.ShowSearch)
SearchAlwaysVisibleSwitch = ft.Switch(
label="Always show the search bar (off = only on Ctrl+F, Escape to dismiss)",
value=Config.SearchAlwaysVisible,
)
SearchResetOnNavigateSwitch = ft.Switch(
label="Clear search when changing directory", value=Config.SearchResetOnNavigate
)
ShowSortSwitch = ft.Switch(label="Enable sort control in the sidebar", value=Config.ShowSort)
BatchRenamePatternField = ft.TextField(
value=Config.BatchRenamePattern,
label="Batch rename pattern (~ = name, ! = counter, ^ = extension)",
)
DeleteZipSwitch = ft.Switch(
label="Delete zip after extracting, by default", value=Config.DeleteZipAfterExtract
)
ArchiveFormatPicker, GetArchiveFormat = _OptionPicker(
[("zip", "Zip", "FOLDER_ZIP_OUTLINED"), ("targz", "Tar.gz", "ARCHIVE_OUTLINED")],
Config.DefaultArchiveFormat,
)
NewDirectoryButtonSwitch = ft.Switch(label="Show New Directory button", value=Config.ShowNewDirectoryButton)
PasteButtonSwitch = ft.Switch(label="Show Paste button", value=Config.ShowPasteButton)
RefreshButtonSwitch = ft.Switch(label="Show Refresh button", value=Config.ShowRefreshButton)
ViewToggleButtonSwitch = ft.Switch(label="Show Grid/List button", value=Config.ShowViewToggleButton)
OptionsButtonSwitch = ft.Switch(label="Show Options button", value=Config.ShowOptionsButton)
ToolbarSwitch = ft.Switch(label="Show toolbar (breadcrumbs + buttons bar)", value=Config.ShowToolbar)
BreadcrumbsSwitch = ft.Switch(label="Show breadcrumbs", value=Config.ShowBreadcrumbs)
BreadcrumbsPositionPicker, GetBreadcrumbsPosition = _OptionPicker(
[("top", "Top", "NORTH"), ("bottom", "Bottom", "SOUTH")], Config.BreadcrumbsPosition
)
SidebarSwitch = ft.Switch(label="Show sidebar", value=Config.ShowSidebar)
SidebarSidePicker, GetSidebarSide = _OptionPicker(
[("left", "Left", "WEST"), ("right", "Right", "EAST")], Config.SidebarSide
)
SidebarWidthField = ft.TextField(
value=str(Config.SidebarWidth), label="Sidebar width", keyboard_type=ft.KeyboardType.NUMBER
)
BookmarksSwitch = ft.Switch(label="Show bookmarks section", value=Config.ShowBookmarks)
BookmarksHeightRow, BookmarksMinHeightField, BookmarksMaxHeightField = _MinMaxRow(
"Bookmarks height", Config.BookmarksMinHeight, Config.BookmarksMaxHeight
)
DevicesSwitch = ft.Switch(label="Show devices section", value=Config.ShowDevices)
AutoMountSwitch = ft.Switch(label="Mount devices when plugged in", value=Config.AutoMountDevices)
InternalDrivesSwitch = ft.Switch(label="Also show internal drives", value=Config.ShowInternalDrives)
SystemDriveSwitch = ft.Switch(
label="Include the main drive (/) too (never ejectable)", value=Config.ShowSystemDrive
)
AutoHideEmptyDevicesSwitch = ft.Switch(
label="Hide the devices section when there's nothing plugged in", value=Config.AutoHideEmptyDevices
)
DevicesHeightRow, DevicesMinHeightField, DevicesMaxHeightField = _MinMaxRow(
"Devices height", Config.DevicesMinHeight, Config.DevicesMaxHeight
)
FiletreeSwitch = ft.Switch(label="Show filetree section", value=Config.ShowFiletree)
FiletreeRootField = ft.TextField(value=Config.FiletreeRoot, label="Filetree root", hint_text="/")
FiletreeHeightRow, FiletreeMinHeightField, FiletreeMaxHeightField = _MinMaxRow(
"Filetree height", Config.FiletreeMinHeight, Config.FiletreeMaxHeight
)
SplitSidebarEvenlySwitch = ft.Switch(
label="Split Bookmarks/Filetree evenly (overrides the heights above)",
value=Config.SplitBookmarksFiletreeEvenly,
)
PreviewSwitch = ft.Switch(label="Show preview pane", value=Config.ShowPreview)
PreviewSidePicker, GetPreviewSide = _OptionPicker(
[("left", "Left", "WEST"), ("right", "Right", "EAST")], Config.PreviewSide
)
PreviewWidthField = ft.TextField(
value=str(Config.PreviewWidth), label="Preview width", keyboard_type=ft.KeyboardType.NUMBER
)
PreviewHeightRow, PreviewMinHeightField, PreviewMaxHeightField = _MinMaxRow(
"Preview height (only when Tags is under preview)", Config.PreviewMinHeight, Config.PreviewMaxHeight
)
SplitEvenlySwitch = ft.Switch(
label="Split Preview/Tags evenly (overrides the heights above)",
value=Config.SplitPreviewTagsEvenly,
)
TagsSwitch = ft.Switch(label="Show SaraTag tags", value=Config.ShowTags)
TagsPositionPicker, GetTagsPosition = _OptionPicker(
[
("under_preview", "Under Preview", "VIEW_AGENDA_OUTLINED"),
("top", "Top", "NORTH"),
("bottom", "Bottom", "SOUTH"),
],
Config.TagsPosition,
)
TagsLayoutPicker, GetTagsLayout = _OptionPicker(
[("flex", "Flex", "VIEW_STREAM_OUTLINED"), ("vertical", "Vertical", "VIEW_COLUMN_OUTLINED")],
Config.TagsLayout,
)
TagMinWidthField = ft.TextField(
value=str(Config.TagMinWidth), label="Tag min width (0 = none)", keyboard_type=ft.KeyboardType.NUMBER
)
TagMaxWidthField = ft.TextField(
value=str(Config.TagMaxWidth), label="Tag max width (0 = none)", keyboard_type=ft.KeyboardType.NUMBER
)
TagPanelHeightRow, TagPanelMinHeightField, TagPanelMaxHeightField = _MinMaxRow(
"Tags box height", Config.TagPanelMinHeight, Config.TagPanelMaxHeight
)
SaraTagSwitch = ft.Switch(label="Enable SaraTag integration", value=Config.SaraTag.Enabled)
SaraTagPathField = ft.TextField(
value=Config.SaraTag.CoreModulePath,
label="SaraTag .saratag path",
hint_text="/home/sarah/Applications/SaraTag/.saratag",
)
SaraSortdSwitch = ft.Switch(label="Enable SaraSortd integration", value=Config.SaraSortd.Enabled)
SaraSortdPathField = ft.TextField(
value=Config.SaraSortd.GlobalConfPath,
label="SaraSortd GlobalConf.toml path",
hint_text="/home/sarah/Applications/SaraSortd/GlobalConf.toml",
)
SaraSortdShowBannerSwitch = ft.Switch(
label="Show the SaraSortd info banner above the file list", value=Config.SaraSortd.ShowBanner
)
SaraSortdCondenseBannerSwitch = ft.Switch(
label="Condense multiple patterns per destination (collapsible)", value=Config.SaraSortd.CondenseBanner
)
Categories: dict[str, ft.Control] = {
"SaraSuite": ft.Column(
[
_SectionLabel("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml). "
"Other programs pick changes up the next time they start."),
UseSharedSwitch,
ft.Divider(height=1, color=Palette.Surface1),
SharedThemeDropdown,
SharedFontDropdown,
SharedTitleBarSwitch,
ft.Divider(height=1, color=Palette.Surface1),
_SectionLabel("Export/import settings as one file — this tab's shared "
"settings plus everything else in Options"),
SettingsFileField,
ft.Row(
[
ft.OutlinedButton(
content="Export", style=ChipButtonStyle(),
on_click=lambda e: OnExport(SettingsFileField.value.strip()),
),
ft.OutlinedButton(
content="Import", style=ChipButtonStyle(),
on_click=lambda e: OnImport(SettingsFileField.value.strip()),
),
]
),
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"General": ft.Column(
[
DotfilesSwitch,
OpenFilesInAppSwitch,
BatchRenamePatternField,
DeleteZipSwitch,
_SectionLabel("Default compress format"),
ArchiveFormatPicker,
ft.Divider(height=1, color=Palette.Surface1),
OwnAppearance,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Middle": ft.Column(
[
_SectionLabel("View"),
ViewModePicker,
ft.Divider(height=1, color=Palette.Surface1),
_SectionLabel("List columns (besides Name)"),
ListDescriptionSwitch,
ListSizeSwitch,
ListModifiedSwitch,
ListOwnerSwitch,
ListCreatedSwitch,
ListContentSwitch,
ListAccessSwitch,
ft.Divider(height=1, color=Palette.Surface1),
_SectionLabel("Font and icon sizes"),
FontSizeField,
ListIconSizeField,
ft.Divider(height=1, color=Palette.Surface1),
_SectionLabel("Grid"),
GridIconSizeField,
_SectionLabel("Name position"),
GridNamePositionPicker,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Folder Icons": ft.Column(
[
_SectionLabel(
"One variant always shows the same icon (today's plain folder, unless changed "
"below). Add more to vary by how full a directory is, like Windows' folder fill "
"levels — item count only, never a slow recursive size walk."
),
FolderThresholdField,
FolderBandsColumn,
AddFolderBandButton,
ft.Divider(height=1, color=Palette.Surface1),
_SectionLabel(
'Per-directory overrides (right-click a folder → Properties → "Save Icon") '
"always win over these."
),
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Toolbar": ft.Column(
[
ToolbarSwitch,
BreadcrumbsSwitch,
_SectionLabel("Position"),
BreadcrumbsPositionPicker,
ft.Divider(height=1, color=Palette.Surface1),
NewDirectoryButtonSwitch,
PasteButtonSwitch,
RefreshButtonSwitch,
ViewToggleButtonSwitch,
OptionsButtonSwitch,
ft.Divider(height=1, color=Palette.Surface1),
ToolbarHamburgerSwitch,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Sidebar": ft.Column(
[
SidebarSwitch,
_SectionLabel("Side"),
SidebarSidePicker,
SidebarWidthField,
ft.Divider(height=1, color=Palette.Surface1),
BookmarksSwitch,
BookmarksHeightRow,
ft.Divider(height=1, color=Palette.Surface1),
DevicesSwitch,
AutoMountSwitch,
InternalDrivesSwitch,
SystemDriveSwitch,
AutoHideEmptyDevicesSwitch,
DevicesHeightRow,
ft.Divider(height=1, color=Palette.Surface1),
FiletreeSwitch,
FiletreeRootField,
FiletreeHeightRow,
ft.Divider(height=1, color=Palette.Surface1),
SplitSidebarEvenlySwitch,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Preview": ft.Column(
[
PreviewSwitch,
_SectionLabel("Side"),
PreviewSidePicker,
PreviewWidthField,
PreviewHeightRow,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Tags": ft.Column(
[
TagsSwitch,
_SectionLabel("Position"),
TagsPositionPicker,
_SectionLabel("Layout"),
TagsLayoutPicker,
TagMinWidthField,
TagMaxWidthField,
TagPanelHeightRow,
SplitEvenlySwitch,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Search & Sort": ft.Column(
[
ShowSearchSwitch,
SearchAlwaysVisibleSwitch,
SearchResetOnNavigateSwitch,
ft.Divider(height=1, color=Palette.Surface1),
ShowSortSwitch,
_SectionLabel('The sort control ("[Sort: Name] v") sits above Bookmarks in the sidebar.'),
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"SaraTag": ft.Column([SaraTagSwitch, SaraTagPathField], spacing=12, scroll=ft.ScrollMode.AUTO),
"SaraSortd": ft.Column(
[
SaraSortdSwitch,
SaraSortdPathField,
_SectionLabel(
'Shows what SaraSortd does with the directory you\'re browsing, e.g. '
'"*.png -> ~/Pictures" for a watched input directory, or '
'"*.png <- ~/Downloads" for one of its output directories.'
),
ft.Divider(height=1, color=Palette.Surface1),
SaraSortdShowBannerSwitch,
SaraSortdCondenseBannerSwitch,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
}
DetailArea = ft.Container(Categories["General"], expand=True, padding=ft.Padding.only(left=12))
CategoryTiles: dict[str, ft.ListTile] = {}
def SelectCategory(Name: str) -> None:
for CategoryName, Tile in CategoryTiles.items():
Tile.selected = CategoryName == Name
DetailArea.content = Categories[Name]
DetailArea.update()
CategoryListView.update()
for Name in Categories:
CategoryTiles[Name] = ft.ListTile(
title=Name,
selected=Name == "General",
selected_tile_color=Palette.Surface1,
selected_color=Palette.Mauve,
on_click=lambda e, n=Name: SelectCategory(n),
)
CategoryListView = ft.Column(list(CategoryTiles.values()), spacing=0, scroll=ft.ScrollMode.AUTO)
CategorySidebar = ft.Container(
CategoryListView, width=CategoryListWidth, bgcolor=Palette.Surface0, border_radius=4, padding=4
)
def DoSave(e=None) -> None:
Config.ViewMode = GetViewMode()
Config.ListShowDescription = ListDescriptionSwitch.value
Config.ListShowSize = ListSizeSwitch.value
Config.ListShowModified = ListModifiedSwitch.value
Config.ListShowOwner = ListOwnerSwitch.value
Config.ListShowCreated = ListCreatedSwitch.value
Config.ListShowContent = ListContentSwitch.value
Config.ListShowAccess = ListAccessSwitch.value
Config.GridNameBelowIcon = GetGridNamePosition() == "below"
Config.ToolbarHamburger = ToolbarHamburgerSwitch.value
Config.UseSharedSettings = UseSharedSwitch.value
Config.Theme = ThemeDropdown.value
Config.FontFamily = FontDropdown.value or Config.FontFamily
Config.HideWindowTitleBar = TitleBarSwitch.value
SharedSettings(
Theme=SharedThemeDropdown.value,
FontFamily=SharedFontDropdown.value or Shared.FontFamily,
HideWindowTitleBar=SharedTitleBarSwitch.value,
).Save()
Config.ShowDotfiles = DotfilesSwitch.value
Config.OpenFilesInApp = OpenFilesInAppSwitch.value
Config.ShowSearch = ShowSearchSwitch.value
Config.SearchAlwaysVisible = SearchAlwaysVisibleSwitch.value
Config.SearchResetOnNavigate = SearchResetOnNavigateSwitch.value
Config.ShowSort = ShowSortSwitch.value
Config.BatchRenamePattern = BatchRenamePatternField.value.strip() or "~!^"
Config.DeleteZipAfterExtract = DeleteZipSwitch.value
Config.DefaultArchiveFormat = GetArchiveFormat()
_SyncWorkingFolderBands()
Config.FolderIconBands = [
FolderIconBand(Icon=B["Icon"] or "FOLDER_OUTLINED", ImagePath=B["ImagePath"])
for B in WorkingFolderBands
]
Config.ShowNewDirectoryButton = NewDirectoryButtonSwitch.value
Config.ShowPasteButton = PasteButtonSwitch.value
Config.ShowRefreshButton = RefreshButtonSwitch.value
Config.ShowViewToggleButton = ViewToggleButtonSwitch.value
Config.ShowOptionsButton = OptionsButtonSwitch.value
Config.ShowToolbar = ToolbarSwitch.value
Config.ShowBreadcrumbs = BreadcrumbsSwitch.value
Config.BreadcrumbsPosition = GetBreadcrumbsPosition()
Config.ShowSidebar = SidebarSwitch.value
Config.SidebarSide = GetSidebarSide()
Config.ShowBookmarks = BookmarksSwitch.value
Config.ShowDevices = DevicesSwitch.value
Config.AutoMountDevices = AutoMountSwitch.value
Config.ShowInternalDrives = InternalDrivesSwitch.value
Config.ShowSystemDrive = SystemDriveSwitch.value
Config.AutoHideEmptyDevices = AutoHideEmptyDevicesSwitch.value
Config.ShowFiletree = FiletreeSwitch.value
Config.SplitBookmarksFiletreeEvenly = SplitSidebarEvenlySwitch.value
Config.FiletreeRoot = FiletreeRootField.value.strip() or "/"
Config.PreviewSide = GetPreviewSide()
Config.ShowPreview = PreviewSwitch.value
Config.SplitPreviewTagsEvenly = SplitEvenlySwitch.value
Config.TagsPosition = GetTagsPosition()
Config.TagsLayout = GetTagsLayout()
Config.ShowTags = TagsSwitch.value
Config.SaraTag.Enabled = SaraTagSwitch.value
Config.SaraTag.CoreModulePath = SaraTagPathField.value.strip()
Config.SaraSortd.Enabled = SaraSortdSwitch.value
Config.SaraSortd.GlobalConfPath = SaraSortdPathField.value.strip()
Config.SaraSortd.ShowBanner = SaraSortdShowBannerSwitch.value
Config.SaraSortd.CondenseBanner = SaraSortdCondenseBannerSwitch.value
for Field, TargetAttr in (
(SidebarWidthField, "SidebarWidth"),
(BookmarksMinHeightField, "BookmarksMinHeight"),
(BookmarksMaxHeightField, "BookmarksMaxHeight"),
(DevicesMinHeightField, "DevicesMinHeight"),
(DevicesMaxHeightField, "DevicesMaxHeight"),
(FiletreeMinHeightField, "FiletreeMinHeight"),
(FiletreeMaxHeightField, "FiletreeMaxHeight"),
(PreviewWidthField, "PreviewWidth"),
(PreviewMinHeightField, "PreviewMinHeight"),
(PreviewMaxHeightField, "PreviewMaxHeight"),
(TagMinWidthField, "TagMinWidth"),
(TagMaxWidthField, "TagMaxWidth"),
(TagPanelMinHeightField, "TagPanelMinHeight"),
(TagPanelMaxHeightField, "TagPanelMaxHeight"),
(GridIconSizeField, "GridIconSize"),
(ListIconSizeField, "ListIconSize"),
(FontSizeField, "FontSize"),
(FolderThresholdField, "FolderFullnessThreshold"),
):
try:
setattr(Config, TargetAttr, int(Field.value))
except ValueError:
pass # keep the previous value rather than crash on bad input
Config.Save()
OnSave()
return ft.AlertDialog(
title="Options",
content=ft.Column(
[
# STRETCH confirmed necessary, not assumed: without it, Row
# doesn't bound CategorySidebar/DetailArea's height to the
# dialog's own fixed height, so their scroll=AUTO had nothing
# to actually scroll within — content just overflowed instead
# (Bugs/Minor: "Settings should be scrollable... too many for
# one window").
ft.Row([CategorySidebar, DetailArea], expand=True, vertical_alignment=ft.CrossAxisAlignment.STRETCH),
ft.Divider(height=1, color=Palette.Surface1),
ft.Text(RestartRequiredNote, color=Palette.Overlay0, size=12),
],
width=DialogWidth,
height=DialogHeight,
),
actions=[
ft.OutlinedButton(content="Cancel", style=ChipButtonStyle(), on_click=lambda e: OnDismiss()),
ft.OutlinedButton(content="Save", style=ChipButtonStyle(), on_click=DoSave),
],
)