feat: initialize CCMA member administration

This commit is contained in:
Marcel Peterkau
2026-06-21 16:46:15 +02:00
parent 4c6a1191ee
commit dfd5b1192b
184 changed files with 5051 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
__pycache__/
*.py[cod]
*.egg-info/
.pytest_cache/
.ruff_cache/
.venv/
build/
dist/
.coverage
htmlcov/
.idea/
.vscode/
# Local member stores must never be committed.
member-store/
*.ccma-lock
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Chaos Computer Club Mannheim e.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -0,0 +1 @@
include VERSION
+1
View File
@@ -0,0 +1 @@
0.0.1-dev0
+47
View File
@@ -0,0 +1,47 @@
[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"
[project]
name = "ccma"
dynamic = ["version"]
description = "Chaotic Creature Member Administration for Chaos Computer Club Mannheim e.V."
readme = "README.md"
requires-python = ">=3.11"
license = "MIT"
authors = [{name = "Chaos Computer Club Mannheim e.V."}]
dependencies = [
"screeninfo>=0.8.1,<1",
"ttkbootstrap-icons",
"ttkbootstrap-icons-mat",
]
[project.optional-dependencies]
dev = [
"pytest>=8",
"ruff>=0.6",
]
[project.scripts]
ccma = "ccma.app:main"
[tool.setuptools]
package-dir = {"" = "src"}
include-package-data = true
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
ccma = ["VERSION", "assets/CHANGELOG.json", "assets/themes/forest/**/*", "assets/themes/forest/*"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
[tool.ruff]
line-length = 110
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
+2
View File
@@ -0,0 +1,2 @@
# Install CCMA and its runtime dependencies from pyproject.toml.
-e .
+21
View File
@@ -0,0 +1,21 @@
from pathlib import Path
from shutil import copyfile
from setuptools import setup
from setuptools.command.build_py import build_py
ROOT = Path(__file__).resolve().parent
class BuildPyWithVersion(build_py):
def run(self) -> None:
super().run()
target = Path(self.build_lib) / "ccma" / "VERSION"
target.parent.mkdir(parents=True, exist_ok=True)
copyfile(ROOT / "VERSION", target)
setup(
version=(ROOT / "VERSION").read_text(encoding="utf-8").strip(),
cmdclass={"build_py": BuildPyWithVersion},
)
+5
View File
@@ -0,0 +1,5 @@
from ccma.version import get_version
__version__ = get_version()
__all__ = ["__version__"]
+4
View File
@@ -0,0 +1,4 @@
from ccma.app import main
if __name__ == "__main__":
main()
+134
View File
@@ -0,0 +1,134 @@
from __future__ import annotations
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox
from ccma import __version__
from ccma.config import load_config
from ccma.domain.dates import setup_system_locale
from ccma.storage.repository import MemberRepository
from ccma.ui.main_window import MainWindow
from ccma.ui.monitors import (
centered_geometry,
ensure_visible_geometry,
monitor_for_geometry,
preferred_monitor,
)
from ccma.ui.splash import SplashScreen, StartupResult
from ccma.ui.theme import load_theme
from ccma.ui.window_state import is_maximized, maximize
class CCMAApp(tk.Tk):
def __init__(self):
super().__init__()
setup_system_locale()
self.config_obj = load_config()
self.title(f"CCMA · v{__version__}")
self.minsize(1050, 650)
load_theme(self, self.config_obj.theme_mode)
self.startup_monitor = preferred_monitor(self, self.config_obj.monitor_bounds)
initial_geometry = self.config_obj.window_geometry or centered_geometry(
1500, 860, self.startup_monitor
)
self.geometry(ensure_visible_geometry(initial_geometry, self.startup_monitor))
self._last_normal_geometry = self.geometry()
self._geometry_capture_job: str | None = None
self.bind("<Configure>", self._remember_window_geometry)
self.protocol("WM_DELETE_WINDOW", self.close)
self.withdraw()
self.after_idle(self._start)
def _start(self) -> None:
store_path = self._resolve_store()
if not store_path:
self.destroy()
return
repository = MemberRepository(store_path)
try:
SplashScreen(
self,
repository,
self._startup_complete,
self._startup_failed,
run_housekeeper=self.config_obj.run_housekeeper_on_startup,
monitor=self.startup_monitor,
housekeeper_settings=self.config_obj.housekeeper_settings(),
)
except Exception as exc:
self._startup_failed(exc)
def _resolve_store(self) -> Path | None:
configured = self.config_obj.store_path.strip()
if configured:
return Path(configured).expanduser()
selected = filedialog.askdirectory(
parent=self,
title="Zentrales CCMA-Mitgliederverzeichnis auswählen oder anlegen",
initialdir=str(Path.home()),
mustexist=False,
)
if not selected:
return None
self.config_obj.store_path = selected
self.config_obj.save()
return Path(selected)
def _startup_complete(self, result: StartupResult) -> None:
self.deiconify()
main = MainWindow(
self,
result.repository,
self.config_obj,
result.findings,
result.validation_errors,
)
main.pack(fill="both", expand=True)
if self.config_obj.window_state in {"maximized", "zoomed"}:
self.after_idle(lambda: maximize(self))
def _startup_failed(self, error: Exception) -> None:
self.deiconify()
messagebox.showerror("CCMA konnte nicht gestartet werden", str(error), parent=self)
self.destroy()
def _remember_window_geometry(self, _event: tk.Event | None = None) -> None:
if self._geometry_capture_job:
try:
self.after_cancel(self._geometry_capture_job)
except tk.TclError:
pass
self._geometry_capture_job = self.after(150, self._capture_normal_geometry)
def _capture_normal_geometry(self) -> None:
self._geometry_capture_job = None
try:
if self.state() == "normal" and not is_maximized(self) and self.winfo_viewable():
self._last_normal_geometry = self.winfo_geometry()
except tk.TclError:
return
def close(self) -> None:
try:
maximized = is_maximized(self)
current_geometry = self.winfo_geometry()
if not maximized:
self._last_normal_geometry = current_geometry
self.config_obj.window_geometry = self._last_normal_geometry
self.config_obj.window_state = "maximized" if maximized else "normal"
monitor = monitor_for_geometry(self, current_geometry)
self.config_obj.monitor_bounds = monitor.as_tuple()
self.config_obj.save()
except (OSError, tk.TclError):
pass
self.destroy()
def main() -> None:
app = CCMAApp()
app.mainloop()
if __name__ == "__main__":
main()
+25
View File
@@ -0,0 +1,25 @@
[
{
"version": "0.0.1-dev0",
"date": "2026-06-21",
"changes": [
"Projektgrundlage für CCMA Chaotic Creature Member Administration geschaffen.",
"Dateibasierten Mitglieder-Store mit transparenten JSON-Dateien, atomarem Speichern und separaten Dokumentordnern eingeführt.",
"Append-only Eventlog pro Mitglied für Systemereignisse, Zahlungen, Mahnungen und Vorstandskommentare ergänzt.",
"Universelle Mitgliedersuche, parallele Mitglied-Tabs, Dashboard und ersten prüfenden Hausmeisterlauf umgesetzt.",
"Forest Light/Dark, Material-Design-Icons und einen eigenen Splash-Screen integriert.",
"Optionen für Mitglieder-Store, zukünftige GnuCash-Datei, Darstellung und Startautomatisierung ergänzt.",
"Integrierten Changelog im Optionen-Dialog mit scrollbaren Release-Karten ergänzt.",
"Ribbon-Suche und Aktionsbuttons für ein klar getrenntes, responsives Layout überarbeitet.",
"Konfigurierbare automatische oder manuelle Mitgliedsnummern mit Pattern, Vorschau und Kollisionsschutz eingeführt.",
"Strikte Datumsvalidierung und Live-Altersanzeige für Mitgliedsdaten ergänzt.",
"Datumseingabe und -anzeige an das Systemformat angepasst; gespeichert wird weiterhin portabel im ISO-Format.",
"Eine ribbonweite Mitgliederliste mit direktem Zugriff auf alle Akten ergänzt.",
"Texthintergründe der Dashboard-Karten an die Kartenflächen angeglichen.",
"Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.",
"Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.",
"Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.",
"Anwendungsname und technische Paketbezeichnung auf CCMA vereinheitlicht."
]
}
]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 rdbende
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,534 @@
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
# The Forest theme is a beautiful and modern ttk theme inspired by Excel.
package require Tk 8.6
namespace eval ttk::theme::forest-dark {
variable version 1.0
package provide ttk::theme::forest-dark $version
variable colors
array set colors {
-fg "#eeeeee"
-bg "#313131"
-disabledfg "#595959"
-disabledbg "#ffffff"
-selectfg "#ffffff"
-selectbg "#217346"
}
proc LoadImages {imgdir} {
variable I
foreach file [glob -directory $imgdir *.png] {
set img [file tail [file rootname $file]]
set I($img) [image create photo -file $file -format png]
}
}
LoadImages [file join [file dirname [info script]] forest-dark]
# Settings
ttk::style theme create forest-dark -parent default -settings {
ttk::style configure . \
-background $colors(-bg) \
-foreground $colors(-fg) \
-troughcolor $colors(-bg) \
-focuscolor $colors(-selectbg) \
-selectbackground $colors(-selectbg) \
-selectforeground $colors(-selectfg) \
-insertwidth 1 \
-insertcolor $colors(-fg) \
-fieldbackground $colors(-selectbg) \
-font {TkDefaultFont 10} \
-borderwidth 1 \
-relief flat
ttk::style map . -foreground [list disabled $colors(-disabledfg)]
tk_setPalette background [ttk::style lookup . -background] \
foreground [ttk::style lookup . -foreground] \
highlightColor [ttk::style lookup . -focuscolor] \
selectBackground [ttk::style lookup . -selectbackground] \
selectForeground [ttk::style lookup . -selectforeground] \
activeBackground [ttk::style lookup . -selectbackground] \
activeForeground [ttk::style lookup . -selectforeground]
option add *font [ttk::style lookup . -font]
# Layouts
ttk::style layout TButton {
Button.button -children {
Button.padding -children {
Button.label -side left -expand true
}
}
}
ttk::style layout Toolbutton {
Toolbutton.button -children {
Toolbutton.padding -children {
Toolbutton.label -side left -expand true
}
}
}
ttk::style layout TMenubutton {
Menubutton.button -children {
Menubutton.padding -children {
Menubutton.indicator -side right
Menubutton.label -side right -expand true
}
}
}
ttk::style layout TOptionMenu {
OptionMenu.button -children {
OptionMenu.padding -children {
OptionMenu.indicator -side right
OptionMenu.label -side right -expand true
}
}
}
ttk::style layout Accent.TButton {
AccentButton.button -children {
AccentButton.padding -children {
AccentButton.label -side left -expand true
}
}
}
ttk::style layout TCheckbutton {
Checkbutton.button -children {
Checkbutton.padding -children {
Checkbutton.indicator -side left
Checkbutton.label -side right -expand true
}
}
}
ttk::style layout Switch {
Switch.button -children {
Switch.padding -children {
Switch.indicator -side left
Switch.label -side right -expand true
}
}
}
ttk::style layout ToggleButton {
ToggleButton.button -children {
ToggleButton.padding -children {
ToggleButton.label -side left -expand true
}
}
}
ttk::style layout TRadiobutton {
Radiobutton.button -children {
Radiobutton.padding -children {
Radiobutton.indicator -side left
Radiobutton.label -side right -expand true
}
}
}
ttk::style layout Vertical.TScrollbar {
Vertical.Scrollbar.trough -sticky ns -children {
Vertical.Scrollbar.thumb -expand true
}
}
ttk::style layout Horizontal.TScrollbar {
Horizontal.Scrollbar.trough -sticky ew -children {
Horizontal.Scrollbar.thumb -expand true
}
}
ttk::style layout TCombobox {
Combobox.field -sticky nswe -children {
Combobox.padding -expand true -sticky nswe -children {
Combobox.textarea -sticky nswe
}
}
Combobox.button -side right -sticky ns -children {
Combobox.arrow -sticky nsew
}
}
ttk::style layout TSpinbox {
Spinbox.field -sticky nsew -children {
Spinbox.padding -expand true -sticky nswe -children {
Spinbox.textarea -sticky nsew
}
}
null -side right -sticky nsew -children {
Spinbox.uparrow -side right -sticky nsew -children {
Spinbox.symuparrow
}
Spinbox.downarrow -side left -sticky nsew -children {
Spinbox.symdownarrow
}
}
}
ttk::style layout Horizontal.TSeparator {
Horizontal.separator -sticky nswe
}
ttk::style layout Vertical.TSeparator {
Vertical.separator -sticky nswe
}
ttk::style layout Card {
Card.field {
Card.padding -expand 1
}
}
ttk::style layout TLabelframe {
Labelframe.border {
Labelframe.padding -expand 1 -children {
Labelframe.label -side left
}
}
}
ttk::style layout TNotebook {
Notebook.border -children {
TNotebook.Tab -expand 1 -side top
Notebook.client -sticky nsew
}
}
ttk::style layout TNotebook.Tab {
Notebook.tab -children {
Notebook.padding -side top -children {
Notebook.label
}
}
}
ttk::style layout Treeview.Item {
Treeitem.padding -sticky nswe -children {
Treeitem.indicator -side left -sticky {}
Treeitem.image -side left -sticky {}
Treeitem.text -side left -sticky {}
}
}
# Elements
# Button
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create Button.button image \
[list $I(rect-basic) \
{selected disabled} $I(rect-basic) \
disabled $I(rect-basic) \
selected $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
# Toolbutton
ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create Toolbutton.button image \
[list $I(empty) \
{selected disabled} $I(empty) \
disabled $I(empty) \
selected $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-basic) \
] -border 4 -sticky nsew
# Menubutton
ttk::style configure TMenubutton -padding {8 4 4 4}
ttk::style element create Menubutton.button image \
[list $I(rect-basic) \
disabled $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
ttk::style element create Menubutton.indicator image \
[list $I(down) \
active $I(down) \
pressed $I(down) \
disabled $I(down) \
] -width 15 -sticky e
# OptionMenu
ttk::style configure TOptionMenu -padding {8 4 4 4}
ttk::style element create OptionMenu.button image \
[list $I(rect-basic) \
disabled $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
ttk::style element create OptionMenu.indicator image \
[list $I(down) \
active $I(down) \
pressed $I(down) \
disabled $I(down) \
] -width 15 -sticky e
# AccentButton
ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center -foreground #eeeeee
ttk::style element create AccentButton.button image \
[list $I(rect-accent) \
{selected disabled} $I(rect-accent-hover) \
disabled $I(rect-accent-hover) \
selected $I(rect-accent) \
pressed $I(rect-accent) \
active $I(rect-accent-hover) \
] -border 4 -sticky nsew
# Checkbutton
ttk::style configure TCheckbutton -padding 4
ttk::style element create Checkbutton.indicator image \
[list $I(check-unsel-accent) \
{alternate disabled} $I(check-tri-basic) \
{selected disabled} $I(check-basic) \
disabled $I(check-unsel-basic) \
{pressed alternate} $I(check-tri-hover) \
{active alternate} $I(check-tri-hover) \
alternate $I(check-tri-accent) \
{pressed selected} $I(check-hover) \
{active selected} $I(check-hover) \
selected $I(check-accent) \
{pressed !selected} $I(check-unsel-pressed) \
active $I(check-unsel-hover) \
] -width 26 -sticky w
# Switch
ttk::style element create Switch.indicator image \
[list $I(off-accent) \
{selected disabled} $I(on-basic) \
disabled $I(off-basic) \
{pressed selected} $I(on-accent) \
{active selected} $I(on-hover) \
selected $I(on-accent) \
{pressed !selected} $I(off-accent) \
active $I(off-hover) \
] -width 46 -sticky w
# ToggleButton
ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create ToggleButton.button image \
[list $I(rect-basic) \
{selected disabled} $I(rect-accent-hover) \
disabled $I(rect-basic) \
{pressed selected} $I(rect-basic) \
{active selected} $I(rect-accent-hover) \
selected $I(rect-accent) \
{pressed !selected} $I(rect-accent) \
active $I(rect-hover) \
] -border 4 -sticky nsew
# Radiobutton
ttk::style configure TRadiobutton -padding 4
ttk::style element create Radiobutton.indicator image \
[list $I(radio-unsel-accent) \
{alternate disabled} $I(radio-tri-basic) \
{selected disabled} $I(radio-basic) \
disabled $I(radio-unsel-basic) \
{pressed alternate} $I(radio-tri-hover) \
{active alternate} $I(radio-tri-hover) \
alternate $I(radio-tri-accent) \
{pressed selected} $I(radio-hover) \
{active selected} $I(radio-hover) \
selected $I(radio-accent) \
{pressed !selected} $I(radio-unsel-pressed) \
active $I(radio-unsel-hover) \
] -width 26 -sticky w
# Scrollbar
ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \
-sticky ew
ttk::style element create Horizontal.Scrollbar.thumb image \
[list $I(hor-accent) \
disabled $I(hor-basic) \
pressed $I(hor-hover) \
active $I(hor-hover) \
] -sticky ew
ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \
-sticky ns
ttk::style element create Vertical.Scrollbar.thumb image \
[list $I(vert-accent) \
disabled $I(vert-basic) \
pressed $I(vert-hover) \
active $I(vert-hover) \
] -sticky ns
# Scale
ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \
-border 5 -padding 0
ttk::style element create Horizontal.Scale.slider image \
[list $I(thumb-hor-accent) \
disabled $I(thumb-hor-basic) \
pressed $I(thumb-hor-hover) \
active $I(thumb-hor-hover) \
] -sticky {}
ttk::style element create Vertical.Scale.trough image $I(scale-vert) \
-border 5 -padding 0
ttk::style element create Vertical.Scale.slider image \
[list $I(thumb-vert-accent) \
disabled $I(thumb-vert-basic) \
pressed $I(thumb-vert-hover) \
active $I(thumb-vert-hover) \
] -sticky {}
# Progressbar
ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \
-sticky ew
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \
-sticky ew
ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \
-sticky ns
ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \
-sticky ns
# Entry
ttk::style element create Entry.field image \
[list $I(border-basic) \
{focus hover} $I(border-accent) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8} -sticky nsew
# Combobox
ttk::style map TCombobox -selectbackground [list \
{!focus} $colors(-selectbg) \
{readonly hover} $colors(-selectbg) \
{readonly focus} $colors(-selectbg) \
]
ttk::style map TCombobox -selectforeground [list \
{!focus} $colors(-selectfg) \
{readonly hover} $colors(-selectfg) \
{readonly focus} $colors(-selectfg) \
]
ttk::style element create Combobox.field image \
[list $I(border-basic) \
{readonly disabled} $I(rect-basic) \
{readonly pressed} $I(rect-basic) \
{readonly focus hover} $I(rect-hover) \
{readonly focus} $I(rect-hover) \
{readonly hover} $I(rect-hover) \
{focus hover} $I(border-accent) \
readonly $I(rect-basic) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8 8 28 8}
ttk::style element create Combobox.button image \
[list $I(combo-button-basic) \
{!readonly focus} $I(combo-button-focus) \
{readonly focus} $I(combo-button-hover) \
{readonly hover} $I(combo-button-hover)
] -border 5 -padding {2 6 6 6}
ttk::style element create Combobox.arrow image $I(down) -width 15 -sticky e
# Spinbox
ttk::style element create Spinbox.field image \
[list $I(border-basic) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8 8 54 8} -sticky nsew
ttk::style element create Spinbox.uparrow image $I(spin-button-up) -border 4 -sticky nsew
ttk::style element create Spinbox.downarrow image \
[list $I(spin-button-down-basic) \
focus $I(spin-button-down-focus) \
] -border 4 -sticky nsew
ttk::style element create Spinbox.symuparrow image $I(up) -width 15 -sticky {}
ttk::style element create Spinbox.symdownarrow image $I(down) -width 17 -sticky {}
# Sizegrip
ttk::style element create Sizegrip.sizegrip image $I(sizegrip) \
-sticky nsew
# Separator
ttk::style element create Horizontal.separator image $I(separator)
ttk::style element create Vertical.separator image $I(separator)
# Card
ttk::style element create Card.field image $I(card) \
-border 10 -padding 4 -sticky nsew
# Labelframe
ttk::style element create Labelframe.border image $I(card) \
-border 5 -padding 4 -sticky nsew
# Notebook
ttk::style configure TNotebook -padding 2
ttk::style element create Notebook.border image $I(card) -border 5
ttk::style element create Notebook.client image $I(notebook) -border 5
ttk::style element create Notebook.tab image \
[list $I(tab-basic) \
selected $I(tab-accent) \
active $I(tab-hover) \
] -border 5 -padding {14 4}
# Treeview
ttk::style element create Treeview.field image $I(card) \
-border 5
ttk::style element create Treeheading.cell image \
[list $I(tree-basic) \
pressed $I(tree-pressed)
] -border 5 -padding 6 -sticky nsew
ttk::style element create Treeitem.indicator image \
[list $I(right) \
user2 $I(empty) \
user1 $I(down) \
] -width 17 -sticky {}
ttk::style configure Treeview -background $colors(-bg)
ttk::style configure Treeview.Item -padding {2 0 0 0}
ttk::style map Treeview \
-background [list selected $colors(-selectbg)] \
-foreground [list selected $colors(-selectfg)]
# Sashes
#ttk::style map TPanedwindow -background [list hover $colors(-bg)]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

@@ -0,0 +1,541 @@
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
# The Forest theme is a beautiful and modern ttk theme inspired by Excel.
package require Tk 8.6
namespace eval ttk::theme::forest-light {
variable version 1.0
package provide ttk::theme::forest-light $version
variable colors
array set colors {
-fg "#313131"
-bg "#ffffff"
-disabledfg "#595959"
-disabledbg "#ffffff"
-selectfg "#ffffff"
-selectbg "#217346"
}
proc LoadImages {imgdir} {
variable I
foreach file [glob -directory $imgdir *.png] {
set img [file tail [file rootname $file]]
set I($img) [image create photo -file $file -format png]
}
}
LoadImages [file join [file dirname [info script]] forest-light]
# Settings
ttk::style theme create forest-light -parent default -settings {
ttk::style configure . \
-background $colors(-bg) \
-foreground $colors(-fg) \
-troughcolor $colors(-bg) \
-focuscolor $colors(-selectbg) \
-selectbackground $colors(-selectbg) \
-selectforeground $colors(-selectfg) \
-insertwidth 1 \
-insertcolor $colors(-fg) \
-fieldbackground $colors(-selectbg) \
-font {TkDefaultFont 10} \
-borderwidth 1 \
-relief flat
ttk::style map . -foreground [list disabled $colors(-disabledfg)]
tk_setPalette background [ttk::style lookup . -background] \
foreground [ttk::style lookup . -foreground] \
highlightColor [ttk::style lookup . -focuscolor] \
selectBackground [ttk::style lookup . -selectbackground] \
selectForeground [ttk::style lookup . -selectforeground] \
activeBackground [ttk::style lookup . -selectbackground] \
activeForeground [ttk::style lookup . -selectforeground]
option add *font [ttk::style lookup . -font]
# Layouts
ttk::style layout TButton {
Button.button -children {
Button.padding -children {
Button.label -side left -expand true
}
}
}
ttk::style layout Toolbutton {
Toolbutton.button -children {
Toolbutton.padding -children {
Toolbutton.label -side left -expand true
}
}
}
ttk::style layout TMenubutton {
Menubutton.button -children {
Menubutton.padding -children {
Menubutton.indicator -side right
Menubutton.label -side right -expand true
}
}
}
ttk::style layout TOptionMenu {
OptionMenu.button -children {
OptionMenu.padding -children {
OptionMenu.indicator -side right
OptionMenu.label -side right -expand true
}
}
}
ttk::style layout Accent.TButton {
AccentButton.button -children {
AccentButton.padding -children {
AccentButton.label -side left -expand true
}
}
}
ttk::style layout TCheckbutton {
Checkbutton.button -children {
Checkbutton.padding -children {
Checkbutton.indicator -side left
Checkbutton.label -side right -expand true
}
}
}
ttk::style layout Switch {
Switch.button -children {
Switch.padding -children {
Switch.indicator -side left
Switch.label -side right -expand true
}
}
}
ttk::style layout ToggleButton {
ToggleButton.button -children {
ToggleButton.padding -children {
ToggleButton.label -side left -expand true
}
}
}
ttk::style layout TRadiobutton {
Radiobutton.button -children {
Radiobutton.padding -children {
Radiobutton.indicator -side left
Radiobutton.label -side right -expand true
}
}
}
ttk::style layout Vertical.TScrollbar {
Vertical.Scrollbar.trough -sticky ns -children {
Vertical.Scrollbar.thumb -expand true
}
}
ttk::style layout Horizontal.TScrollbar {
Horizontal.Scrollbar.trough -sticky ew -children {
Horizontal.Scrollbar.thumb -expand true
}
}
ttk::style layout TCombobox {
Combobox.field -sticky nswe -children {
Combobox.padding -expand true -sticky nswe -children {
Combobox.textarea -sticky nswe
}
}
Combobox.button -side right -sticky ns -children {
Combobox.arrow -sticky nsew
}
}
ttk::style layout TSpinbox {
Spinbox.field -sticky nsew -children {
Spinbox.padding -expand true -sticky nswe -children {
Spinbox.textarea -sticky nsew
}
}
null -side right -sticky nsew -children {
Spinbox.uparrow -side right -sticky nsew -children {
Spinbox.symuparrow
}
Spinbox.downarrow -side left -sticky nsew -children {
Spinbox.symdownarrow
}
}
}
ttk::style layout Horizontal.TSeparator {
Horizontal.separator -sticky nswe
}
ttk::style layout Vertical.TSeparator {
Vertical.separator -sticky nswe
}
ttk::style layout Card {
Card.field {
Card.padding -expand 1
}
}
ttk::style layout TLabelframe {
Labelframe.border {
Labelframe.padding -expand 1 -children {
Labelframe.label -side left
}
}
}
ttk::style layout TNotebook {
Notebook.border -children {
TNotebook.Tab -expand 1 -side top
Notebook.client -sticky nsew
}
}
ttk::style layout TNotebook.Tab {
Notebook.tab -children {
Notebook.padding -side top -children {
Notebook.label
}
}
}
ttk::style layout Treeview.Item {
Treeitem.padding -sticky nswe -children {
Treeitem.indicator -side left -sticky {}
Treeitem.image -side left -sticky {}
Treeitem.text -side left -sticky {}
}
}
# Elements
# Button
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create Button.button image \
[list $I(rect-basic) \
{selected disabled} $I(rect-basic) \
disabled $I(rect-basic) \
selected $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
# Toolbutton
ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create Toolbutton.button image \
[list $I(empty) \
{selected disabled} $I(empty) \
disabled $I(empty) \
selected $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-basic) \
] -border 4 -sticky nsew
# Menubutton
ttk::style configure TMenubutton -padding {8 4 4 4}
ttk::style element create Menubutton.button image \
[list $I(rect-basic) \
disabled $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
ttk::style element create Menubutton.indicator image \
[list $I(down) \
active $I(down) \
pressed $I(down) \
disabled $I(down) \
] -width 15 -sticky e
# OptionMenu
ttk::style configure TOptionMenu -padding {8 4 4 4}
ttk::style element create OptionMenu.button image \
[list $I(rect-basic) \
disabled $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
ttk::style element create OptionMenu.indicator image \
[list $I(down) \
active $I(down) \
pressed $I(down) \
disabled $I(down) \
] -width 15 -sticky e
# AccentButton
ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center -foreground #eeeeee
ttk::style element create AccentButton.button image \
[list $I(rect-accent) \
{selected disabled} $I(rect-accent-hover) \
disabled $I(rect-accent-hover) \
selected $I(rect-accent) \
pressed $I(rect-accent) \
active $I(rect-accent-hover) \
] -border 4 -sticky nsew
# Checkbutton
ttk::style configure TCheckbutton -padding 4
ttk::style element create Checkbutton.indicator image \
[list $I(check-unsel-accent) \
{alternate disabled} $I(check-tri-basic) \
{selected disabled} $I(check-basic) \
disabled $I(check-unsel-basic) \
{pressed alternate} $I(check-tri-hover) \
{active alternate} $I(check-tri-hover) \
alternate $I(check-tri-accent) \
{pressed selected} $I(check-hover) \
{active selected} $I(check-hover) \
selected $I(check-accent) \
{pressed !selected} $I(check-unsel-pressed) \
active $I(check-unsel-hover) \
] -width 26 -sticky w
# Switch
ttk::style element create Switch.indicator image \
[list $I(off-accent) \
{selected disabled} $I(on-basic) \
disabled $I(off-basic) \
{pressed selected} $I(on-accent) \
{active selected} $I(on-hover) \
selected $I(on-accent) \
{pressed !selected} $I(off-accent) \
active $I(off-hover) \
] -width 46 -sticky w
# ToggleButton
ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center -foregound $colors(-fg)
ttk::style map ToggleButton -foreground \
[list {pressed selected} $colors(-fg) \
{pressed !selected} #ffffff \
selected #ffffff]
ttk::style element create ToggleButton.button image \
[list $I(rect-basic) \
{selected disabled} $I(rect-accent-hover) \
disabled $I(rect-basic) \
{pressed selected} $I(rect-basic) \
{active selected} $I(rect-accent-hover) \
selected $I(rect-accent) \
{pressed !selected} $I(rect-accent) \
active $I(rect-hover) \
] -border 4 -sticky nsew
# Radiobutton
ttk::style configure TRadiobutton -padding 4
ttk::style element create Radiobutton.indicator image \
[list $I(radio-unsel-accent) \
{alternate disabled} $I(radio-tri-basic) \
{selected disabled} $I(radio-basic) \
disabled $I(radio-unsel-basic) \
{pressed alternate} $I(radio-tri-hover) \
{active alternate} $I(radio-tri-hover) \
alternate $I(radio-tri-accent) \
{pressed selected} $I(radio-hover) \
{active selected} $I(radio-hover) \
selected $I(radio-accent) \
{pressed !selected} $I(radio-unsel-pressed) \
active $I(radio-unsel-hover) \
] -width 26 -sticky w
# Scrollbar
ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \
-sticky ew
ttk::style element create Horizontal.Scrollbar.thumb image \
[list $I(hor-accent) \
disabled $I(hor-basic) \
pressed $I(hor-hover) \
active $I(hor-hover) \
] -sticky ew
ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \
-sticky ns
ttk::style element create Vertical.Scrollbar.thumb image \
[list $I(vert-accent) \
disabled $I(vert-basic) \
pressed $I(vert-hover) \
active $I(vert-hover) \
] -sticky ns
# Scale
ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \
-border 5 -padding 0
ttk::style element create Horizontal.Scale.slider image \
[list $I(thumb-hor-accent) \
disabled $I(thumb-hor-basic) \
pressed $I(thumb-hor-hover) \
active $I(thumb-hor-hover) \
] -sticky {}
ttk::style element create Vertical.Scale.trough image $I(scale-vert) \
-border 5 -padding 0
ttk::style element create Vertical.Scale.slider image \
[list $I(thumb-vert-accent) \
disabled $I(thumb-vert-basic) \
pressed $I(thumb-vert-hover) \
active $I(thumb-vert-hover) \
] -sticky {}
# Progressbar
ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \
-sticky ew
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \
-sticky ew
ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \
-sticky ns
ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \
-sticky ns
# Entry
ttk::style element create Entry.field image \
[list $I(border-basic) \
{focus hover} $I(border-accent) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8} -sticky nsew
# Combobox
ttk::style map TCombobox -selectbackground [list \
{!focus} $colors(-selectbg) \
{readonly hover} $colors(-selectbg) \
{readonly focus} $colors(-selectbg) \
]
ttk::style map TCombobox -selectforeground [list \
{!focus} $colors(-selectfg) \
{readonly hover} $colors(-selectfg) \
{readonly focus} $colors(-selectfg) \
]
ttk::style element create Combobox.field image \
[list $I(border-basic) \
{readonly disabled} $I(rect-basic) \
{readonly pressed} $I(rect-basic) \
{readonly focus hover} $I(rect-hover) \
{readonly focus} $I(rect-hover) \
{readonly hover} $I(rect-hover) \
{focus hover} $I(border-accent) \
readonly $I(rect-basic) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8 8 28 8}
ttk::style element create Combobox.button image \
[list $I(combo-button-basic) \
{!readonly focus} $I(combo-button-focus) \
{readonly focus} $I(combo-button-hover) \
{readonly hover} $I(combo-button-hover)
] -border 5 -padding {2 6 6 6}
ttk::style element create Combobox.arrow image $I(down) -width 15 -sticky e
# Spinbox
ttk::style element create Spinbox.field image \
[list $I(border-basic) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8 8 54 8} -sticky nsew
ttk::style element create Spinbox.uparrow image $I(spin-button-up) -border 4 -sticky nsew
ttk::style element create Spinbox.downarrow image \
[list $I(spin-button-down-basic) \
focus $I(spin-button-down-focus) \
] -border 4 -sticky nsew
ttk::style element create Spinbox.symuparrow image $I(up) -width 15 -sticky {}
ttk::style element create Spinbox.symdownarrow image $I(down) -width 17 -sticky {}
# Sizegrip
ttk::style element create Sizegrip.sizegrip image $I(sizegrip) \
-sticky nsew
# Separator
ttk::style element create Horizontal.separator image $I(separator)
ttk::style element create Vertical.separator image $I(separator)
# Card
ttk::style element create Card.field image $I(card) \
-border 10 -padding 4 -sticky nsew
# Labelframe
ttk::style element create Labelframe.border image $I(card) \
-border 5 -padding 4 -sticky nsew
# Notebook
ttk::style configure TNotebook -padding 2
ttk::style element create Notebook.border image $I(card) -border 5
ttk::style element create Notebook.client image $I(notebook) -border 5
ttk::style element create Notebook.tab image \
[list $I(tab-basic) \
selected $I(tab-accent) \
active $I(tab-hover) \
] -border 5 -padding {14 4}
# Treeview
ttk::style element create Treeview.field image $I(card) \
-border 5
ttk::style element create Treeheading.cell image \
[list $I(tree-basic) \
pressed $I(tree-pressed)
] -border 5 -padding 6 -sticky nsew
ttk::style element create Treeitem.indicator image \
[list $I(right) \
user2 $I(empty) \
{user1 focus} $I(down-focus) \
focus $I(right-focus) \
user1 $I(down) \
] -width 17 -sticky {}
ttk::style configure Treeview -background $colors(-bg)
ttk::style configure Treeview.Item -padding {2 0 0 0}
ttk::style map Treeview \
-background [list selected $colors(-selectbg)] \
-foreground [list selected $colors(-selectfg)]
# Sashes
#ttk::style map TPanedwindow -background [list hover $colors(-bg)]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Some files were not shown because too many files have changed in this diff Show More