#!/bin/sh
#\
exec tclsh9.0 "$0" "$@"

# Mosake v0.1 ################################################################################################
# automatically generated file :: created by the Mosake build system
# mosake v0.1 on freya :: 20251101-14:27 :: freebsd 14.3-release-p4 (amd64)
# ############################################################################################################

set sProgName    "TCalView"
set sProgTech    "tcalview"
set sProgGeneric "Calendar"
set sDescShort   "fetches webcal URIs and displays them as a listing type calendar"
set sDescLong    "Fetches Webcal URIs and displays them as a listing type calendar."
set sVersion     "0.6"
set sAuthor      "Joachim Moskalewski"
set sProgYear    "2024 - 2025"
set sUrl         "http://jmos.net/software/"
set sEmail       "code@jmos.net"
set sCompileDate "20251101"

# Lizence ####################################################################################################
# Copyright (C) 2024-2025 Joachim Moskalewski <code@jmos.net>.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ############################################################################################################

package require Tk
tk app        $sProgName
wm title    . $sProgName
wm iconname . $sProgName

set sThemeExtender [ file join [ file home ] ".local" "share" "tk9-themes" "theme_extender.tcl" ]
if { [ file exists $sThemeExtender ] } { source $sThemeExtender }

# FILE: 01_vars.tcl ##########################################################################################

################################################################################
# Startsettings + Variablen
################################################################################

set iTimeStart     [ clock seconds ]
set iOldestFetch   $iTimeStart
set lIcals         [ list ]
set iIcals         12
# 28 bis 365 Tage
set iViewDays      28
set sTheme         "default"
set sFontName      ""
set iFontSize      0
set sLanguage      "en"
set sSearch        ""
set bCompleteList  true
set bOnceRaised    false
set bEntriesToday  false
set lNotifications [ list ]
set sSelfExec      [ info script ]
set bTrayMode      false

# verfuegbare Sprachen
set lLanguages [ list ]
lappend lLanguages [ list "english" "en" "Joachim Moskalewski" ]
lappend lLanguages [ list "deutsch" "de" "Joachim Moskalewski" ]

# Hintergrundfarben fuer !TTK-Widgets
set sBgDark      "#ababab"
set sBgNormal    "#d5d5d5"
set sBgLight     "#eaeaea"
set sBgBright    "#ffffff"

# Variablen fuer die Konfigdatei
set lConfigFileVars [ list ]
lappend lConfigFileVars "iViewDays"
lappend lConfigFileVars "sTheme"
lappend lConfigFileVars "sFontName"
lappend lConfigFileVars "iFontSize"
lappend lConfigFileVars "sLanguage"
lappend lConfigFileVars "bCompleteList"
lappend lConfigFileVars "bTrayMode"

# FILE: 02_packages.tcl ######################################################################################

################################################################################
# Packages
################################################################################

package require md5
package require msgcat
namespace import msgcat::mc
namespace import msgcat::mcset

# FILE: 03_environment.tcl ###################################################################################

################################################################################
# Verzeichnisse fuer Konfiguration und Daten erstellen
################################################################################

if { ! [ file isdir [ file join [ file home ] ".config" ] ] } {
	file mkdir [ file join [ file home ] ".config" ]
}
if { ! [ file isdir [ file join [ file home ] ".config" $sProgTech ] ] } {
	file mkdir [ file join [ file home ] ".config" $sProgTech ]
}
if { ! [ file isdir [ file join [ file home ] ".local" "share" ] ] } {
	file mkdir [ file join [ file home ] ".local" "share" ]
}
if { ! [ file isdir [ file join [ file home ] ".local" ] ] } {
	file mkdir [ file join [ file home ] ".local" ]
}
if { ! [ file isdir [ file join [ file home ] ".local" "share" $sProgTech ] ] } {
	file mkdir [ file join [ file home ] ".local" "share" $sProgTech ]
}

################################################################################
# Konfiguration lesen + schreiben
################################################################################

set sConfigFile [ file join [ file home ] ".config" $sProgTech "config.list" ]

# Config schreiben
proc writeconfig {} {
	global lConfigFileVars sConfigFile
	set lConfig [ list ]
	foreach sConfig $lConfigFileVars {
		global $sConfig
		lappend lConfig [ list $sConfig [ expr $$sConfig ] ]
	}
	set oFile [ open $sConfigFile w ]
	puts $oFile $lConfig
	close $oFile
}

# Sprache kann erst jetzt vorbelegt werden
if { [ string length [ msgcat::mclocale ] ] > 1 } {
	set sLanguage [ string range [ msgcat::mclocale ] 0 1 ]
}

# Konfiguration ggf. erstellen
if { ! [ file exists $sConfigFile ] } {
	writeconfig
}

# Konfiguration auslesen
set oFile [ open $sConfigFile r ]
set lConfig [ read $oFile ]
close $oFile
foreach lKeyValue $lConfig {
	if { [ llength $lKeyValue ] == 2 } {
		set [ lindex $lKeyValue 0 ] [ lindex $lKeyValue 1 ]
	}
}

# Commandline-Parameter auswerten
foreach sParameter [ split $argv " " ] {
	if { $sParameter == "--help" || $sParameter == "-help" || $sParameter == "--h" || $sParameter == "-h" } {
		# Hilfe + beenden
		puts "${sProgName} ${sVersion}, ${sProgYear}"
		puts ""
		puts "  --help        print this message and exit"
		puts "  --notraymode  disables an activated tray mode"
		puts ""
		exit
	} elseif { $sParameter == "--notraymode" } {
		# kein Systray-Modus
		set bTrayMode false
	}
}

# Kalender einlesen
set sIcalFile [ file join [ file home ] ".config" $sProgTech "icals.list" ]
if { [ file exists $sIcalFile ] } {
	set oFile [ open $sIcalFile r ]
	set lIcals [ read $oFile ]
	close $oFile
}

# Konfigsettings on the fly anwenden
proc setmyconfig { sSetVar } {
	global lConfigFileVars lLanguages
	foreach sConfig $lConfigFileVars {
		global $sConfig

		if { $sConfig == "sTheme" } {
			set lThemes [ ttk::style theme names ]
			if { [ lsearch $lThemes $sTheme ] >= 0 } {
				ttk::style theme use $sTheme
			}

		} elseif { $sConfig == "sFontName" } {
			setmyfonts

		} elseif { $sConfig == "iFontSize" } {
			setmyfonts

		} elseif { $sConfig == "sLanguage" } {
			foreach lLang $lLanguages {
				if { [ lindex $lLang 1 ] == $sLanguage } {
					msgcat::mclocale $sLanguage
				}
			}
		}

	}

}

################################################################################
# Sprachen
################################################################################

# Sprache setzen (sofern vorhanden)
foreach lLang $lLanguages {
	if { [ lindex $lLang 1 ] == $sLanguage } {
		msgcat::mclocale $sLanguage
	}
}
namespace import -force msgcat::mcset

# EN
mcset en "DAY1" "Mo."
mcset en "DAY2" "Tu."
mcset en "DAY3" "We."
mcset en "DAY4" "Th."
mcset en "DAY5" "Fr."
mcset en "DAY6" "Sa."
mcset en "DAY7" "Su."

# DE
mcset de "Search" "Suche"
mcset de "Calendars" "Kalender"
mcset de "Update" "Aktualisieren"
mcset de "System Tray" "System Tray"
mcset de "last" "zuletzt"
mcset de "not an hour ago" "vor keiner Stunde"
mcset de "1 hour" "1ne Stunde"
mcset de "hours" "Stunden"
mcset de "please wait" "bitte warten"
mcset de "DAY1" "Mo."
mcset de "DAY2" "Di."
mcset de "DAY3" "Mi."
mcset de "DAY4" "Do."
mcset de "DAY5" "Fr."
mcset de "DAY6" "Sa."
mcset de "DAY7" "So."
mcset de "o'clock" "Uhr"
mcset de "from" "ab"
mcset de "till" "bis"
mcset de "There are entries in your calendar today." "Es gibt heute Eintr\u00e4ge in deinem Kalender."
mcset de "The calendar should be synchronized again!" "Der Kalender sollte wieder synchronisiert werden!"
mcset de "Really quit?" "Wirklich beenden?"
mcset de "You are about to finish the application - not minimize it." "Du bist dabei die Anwendung zu beenden - nicht zu minimieren."
mcset de "Settings" "Einstellungen"
mcset de "Language" "Sprache"
mcset de "List View" "Listenansicht"
mcset de "show days without entries" "Tage ohne Eintr\u00e4ge zeigen"
mcset de "Days" "Tage"
mcset de "Theme" "Theme"
mcset de "Font Style" "Schriftart"
mcset de "Font Size" "Schriftgröße"
mcset de "Save" "Speichern"
mcset de "Restart recommended" "Neustart empfohlen"
mcset de "Some settings are only applied after a restart." "Manche Einstellungen werden erst nach einem Neustart \u00fcbernommen."
mcset de "Exit program?" "Programm beenden?"
mcset de "General" "Allgemein"
mcset de "Name" "Name"
mcset de "Color" "Farbe"
mcset de "URI" "URI"
mcset de "Abort" "Abbrechen"
mcset de "Version" "Versionn"
mcset de "Fetches Webcal URIs and displays them as a listing type calendar." "Holt Webcal-URIs ab und zeigt sie als Listenkalender an."
mcset de "Available Languages" "Verf\u00fcgbare Sprachen"
mcset de "About" "\u00dcber"
mcset de "License" "Lizenz"
mcset de "Close" "Schliessen"
mcset de "Tray Mode" "Tray Modus"
mcset de "minimized only in the system tray (experimentally)" "minimiert nur im System Tray (experimentell)"
mcset de "Minimize" "Minimieren"

################################################################################
# Themes
################################################################################

if { $sTheme != "" } {
	set lThemes [ ttk::style theme names ]
	if { [ lsearch $lThemes $sTheme ] >= 0 } {
		ttk::style theme use $sTheme
	}
}

################################################################################
# benoetigte Schriftarten setzen bzw. konfigurieren
################################################################################

set sMyFontName [ font actual TkDefaultFont -family ]
set iMyFontSize [ font actual TkDefaultFont -size ]
if { $sFontName == "" } {
	set sFontName $sMyFontName
}
if { $iFontSize < 4 } {
	set iFontSize $iMyFontSize
}
set iBiggerSize [ expr $iFontSize + 1 ]
set iSmallerSize [ expr $iFontSize - 2 ]
if { $iSmallerSize < 4 } {
	set iSmallerSize 4
}
if { $iSmallerSize > $iFontSize } {
	set iSmallerSize $iFontSize
}
set bFontInit true
proc setmyfonts {} {
	global bFontInit sFontName iFontSize iBiggerSize iSmallerSize
	font configure TkDefaultFont -family $sFontName -size $iFontSize
	font configure TkTextFont    -family $sFontName -size $iFontSize
	font configure TkCaptionFont -family $sFontName -size $iFontSize
	if { $bFontInit } {
		font create MyFontBigBold -family $sFontName -size $iBiggerSize  -weight bold
		font create MyFontBold    -family $sFontName -size $iFontSize    -weight bold
		font create MyFontSmaller -family $sFontName -size $iSmallerSize
	} else {
		font configure MyFontBigBold -family $sFontName -size $iBiggerSize  -weight bold
		font configure MyFontBold    -family $sFontName -size $iFontSize    -weight bold
		font configure MyFontSmaller -family $sFontName -size $iSmallerSize
	}
	set bFontInit false
}
setmyfonts

# FILE: 04_engine.tcl ########################################################################################

################################################################################
# valide Tagesliste fuer unseren Kalender - minimal 28, maximal 365 Tage
# Hinweis: nicht alle Tage haben 86400 Sekunden; wir nehmen daher weniger,
# und kuerzen anschliessend Doppelungen aus unserer Liste
################################################################################

if { $iViewDays < 28 } {
	set iViewDays 28
} elseif { $iViewDays > 365 } {
	set iViewDays 365
}
set lValidDates [ list ]
lappend lValidDates [ clock format $iTimeStart -format %Y%m%d ]
set iCount 0
set iStampNow $iTimeStart
set iCountTill [ expr $iViewDays - 1 ]
while { $iCount < $iCountTill } {
	set iStampNow [ expr $iStampNow + 80000 ]
	lappend lValidDates [ clock format $iStampNow -format %Y%m%d ]
	incr iCount
}
set lValidDates [ lsort -unique $lValidDates ]

################################################################################
# Daten ggf. abholen, und vorhandenes einlesen
################################################################################

set lCalendar [ list ]
# ein paar Variablen ;)
set iThisYear [ clock format $iTimeStart -format %Y ]
set iNextYear [ expr $iThisYear + 1 ]
set lValidYears [ list $iThisYear $iNextYear ]
set sMonth [ clock format $iTimeStart -format %m ]
set iMonth [ string trimleft $sMonth "0" ]
incr iMonth
set sNextMonth $iMonth
if { [ string length $sNextMonth ] == 1 } {
	set sNextMonth "0${sNextMonth}"
}
if { $sNextMonth == "13" } {
	set lValidMonths [ list "12" "01" ]
	set lValidYearMonth [ list "${iThisYear}12" "${iNextYear}01" ]
} else {
	set lValidMonths [ list $sMonth $sNextMonth ]
	set lValidYearMonth [ list "${iThisYear}${sMonth}" "${iThisYear}${sNextMonth}" ]
}

# dieses und kommendes Jahr auswerten
# Hinweis: wegen Zeitumstellungen koennen *leider* nicht Berechnungen mit "86400 == 1 Tag" vorgenommen werden - aber 12 Uhr Mittags schon ;)
proc nextday { sYearMonthDay } {
	return [ clock format [ expr [ clock scan "${sYearMonthDay}12" -format "%Y%m%d%H" ] + 86400 ] -format "%Y%m%d" ]
}
proc nextweek { sYearMonthDay } {
	return [ clock format [ expr [ clock scan "${sYearMonthDay}12" -format "%Y%m%d%H" ] + (7 * 86400) ] -format "%Y%m%d" ]
}
proc nextmonth { sYearMonthDay } {
	set sDay [ string range $sYearMonthDay 6 7 ]
	set iMonth [ string trimleft [ string range $sYearMonthDay 4 5 ] "0" ]
	set iYear [ string range $sYearMonthDay 0 3 ]
	incr iMonth
	if { $iMonth > 12 } {
		set iMonth 1
		incr iYear
	}
	if { [ string length $iMonth ] == 1 } {
		set iMonth "0${iMonth}"
	}
	return "${iYear}${iMonth}${sDay}"
}
proc nextyear { sYearMonthDay } {
	set iYear [ string range $sYearMonthDay 0 3 ]
	incr iYear
	return "${iYear}[ string range $sYearMonthDay 4 end ]"
}
proc parserules { sDate sRule sDateEnd sUntil sStartTime sEndTime } {
	global iTimeStart

	# Eintraege erstellen - fuer jeden Tag einen Listeneintrag mit "Tag Startuhrzeit Enduhrzeit"
	set lMainDates [ list ]
	set lTmp [ list $sDate $sStartTime "" ]
	lappend lMainDates $lTmp
	set sNextDay [ nextday $sDate ]
	set iCount 0
	if { $sNextDay != $sDateEnd } {
		while { $sNextDay <= $sDateEnd } {
			set lTmp [ list $sNextDay "" "" ]
			lappend lMainDates $lTmp
			set sNextDay [ nextday $sNextDay ]
			incr iCount
		}
	}

	# bei exakt 2 Tagen fehlt uns ser zweite...
	set sSecondDay [ nextday $sDate ]
	if { $sSecondDay == $sDateEnd } {
		set lTmp [ list $sSecondDay "" "" ]
		lappend lMainDates $lTmp
		incr iCount
	}

	# dem letzten Eintrag noch die End-Uhrzeit spendieren
	if { $sEndTime != "" } {
		set lMainDates [ lreplace $lMainDates $iCount $iCount [ lreplace [ lindex $lMainDates $iCount ] 2 2 $sEndTime ] ]
	}

	# Auslesen der Wiederholungsregeln vereinfachen: alle Monate und Jahre
	set lAllYears [ list ]
	set iYear [ clock format $iTimeStart -format "%Y" ]
	lappend lAllYears $iYear
	incr iYear
	lappend lAllYears $iYear
	set iLastYear $iYear
	set sLastDate "${iLastYear}1231"
	set lAllMonths [ list "01" "02" "03" "04" "05" "06" "07" "08" "09" "10" "11" "12" ]

	# Wiederholungen - uns interesiert nur dieses und naechstes Jahr
	set lRepeat [ list ]
	set lRule [ list ]
	set iPos [ expr [ string first "UNTIL=" $sRule ] - 2 ]
	if { $iPos > -1 }	{
		set sRule [ string range $sRule 0 $iPos ]
	}
	if { $sRule != "" } {
		set lRules [ split $sRule ";" ]

		if { $sRule == "FREQ=YEARLY" } {
			# jaehrlich
			foreach lMainDate $lMainDates {
				set sNextdate [ nextyear [ lindex $lMainDate 0 ] ]
				while { $sNextdate <= $sLastDate } {
					set lTmp [ list $sNextdate [ lindex $lMainDate 1 ] [ lindex $lMainDate 2 ] ]
					lappend lRepeat $lTmp
					set sNextdate [ nextyear $sNextdate ]
				}
			}

		} elseif { $sRule == "FREQ=MONTHLY" } {
			# monatlich
			foreach lMainDate $lMainDates {
				set sNextdate [ nextmonth [ lindex $lMainDate 0 ] ]
				while { $sNextdate <= $sLastDate } {
					set lTmp [ list $sNextdate [ lindex $lMainDate 1 ] [ lindex $lMainDate 2 ] ]
					lappend lRepeat $lTmp
					set sNextdate [ nextmonth $sNextdate ]
				}
			}

		} elseif { $sRule == "FREQ=WEEKLY" } {
			# woechentlich
			foreach lMainDate $lMainDates {
				set sNextdate [ nextweek [ lindex $lMainDate 0 ] ]
				while { $sNextdate <= $sLastDate } {
					set lTmp [ list $sNextdate [ lindex $lMainDate 1 ] [ lindex $lMainDate 2 ] ]
					lappend lRepeat $lTmp
					set sNextdate [ nextweek $sNextdate ]
				}
			}

		} elseif { [ llength $lRules ] > 1 && [ llength $lRules ] < 4 } {
			set sMyRule1 ""
			set sMyRule2 ""
			set lMyRule2 ""
			set sMyRule3 ""
			set lMyRule3 ""
			set iCount
			foreach sTmp $lRules {
				if { $iCount == 0 } {
					set sMyRule1 $sTmp
				} elseif { $iCount == 1 } {
					set sMyRule2 $sTmp
					set lMyRule2 [ split $sTmp "=" ]
				} elseif { $iCount == 2 } {
					set sMyRule3 $sTmp
					set lMyRule3 [ split $sTmp "=" ]
				}
				incr iCount
			}
			if { $sMyRule1 == "FREQ=YEARLY" } {

				if { [ lindex $lMyRule2 0 ] == "BYMONTH" } {
					# jaehrlich an bestimmten Monaten
					foreach lMainDate $lMainDates {
						set lDays [ list ]
						if { [ lindex $lMyRule3 0 ] == "BYDAY" } {
							set lDayDefinition [ split [ lindex $lMyRule3 1 ] "," ]
							foreach sDayDefinition $lDayDefinition {
								if { [ string length $sDayDefinition ] == 3 } {
									# Beispiel: 4TH - 4. Thursday im Monat...
									set iI [ string range $sDayDefinition 0 0 ]
									set sD [ string tolower [ string range $sDayDefinition 1 end ] ]
									if { ($iI == 1 || $iI == 2 || $iI == 3 || $iI == 4) && ($sD == "mo" || $sD == "tu" || $sD == "we" || $sD == "th" || $sD == "fr" || $sD == "sa" || $sD == "so") } {
										if { $sD == "mo" } {
											set iDay 1
										} elseif { $sD == "tu" } {
											set iDay 2
										} elseif { $sD == "we" } {
											set iDay 3
										} elseif { $sD == "th" } {
											set iDay 4
										} elseif { $sD == "fr" } {
											set iDay 5
										} elseif { $sD == "sa" } {
											set iDay 6
										} elseif { $sD == "su" } {
											set iDay 7
										}
										set sNextDay "[ string range $lMainDate 0 5 ]01"
										set iNextDay [ clock format [ clock scan $sNextDay -format "%Y%m%d" ] -format "%u" ]
										while { $iNextDay != $iDay } {
											set sNextDay [ nextday $sNextDay ]
											set iNextDay [ clock format [ clock scan $sNextDay -format "%Y%m%d" ] -format "%u" ]
										}
										set sNextDay [ expr $sNextDay + (($iI - 1) * 7) ]
										lappend lDays [ string range $sNextDay 6 7 ]
									} else {
										puts "ERROR 6: missing parser rule: ${sRule}"
									}
								} else {
									puts "ERROR 5: missing parser rule: ${sRule}"
								}
							}
						} else {
							lappend lDays [ string range [ lindex $lMainDate 0 ] 6 7 ]
						}
						foreach iYear $lAllYears {
							foreach iMonth [ split [ lindex $lMyRule2 1 ] "," ] {
								if { [ string length $iMonth ] == 1 } {
									set iMonth "0${iMonth}"
								}
								foreach iDay $lDays {
									if { [ string length $iDay ] == 1 } {
										set iDay "0${iDay}"
									}
									set sNextdate "${iYear}${iMonth}${iDay}"
									if { $sNextdate != [ lindex $lMainDate 0 ] && $sNextdate > $sDate } {
										set lTmp [ list $sNextdate [ lindex $lMainDate 1 ] [ lindex $lMainDate 2 ] ]
										lappend lRepeat $lTmp
									}
								}
							}
						}
					}
				} else {
					puts "ERROR 5: missing parser rule: ${sRule}"
				}

			} elseif { $sMyRule1 == "FREQ=MONTHLY" } {

				if { [ lindex $lMyRule2 0 ] == "BYMONTHDAY" && [ lindex $lMyRule3 0 ] == "" } {

					# monatlich an bestimmten Tagen
					foreach lMainDate $lMainDates {
						foreach iYear $lAllYears {
							foreach sMonth $lAllMonths {
								foreach iDay [ split [ lindex $lMyRule2 1 ] "," ] {
									if { [ string length $iDay ] == 1 } {
										set iDay "0${iDay}"
									}
									set sNextdate "${iYear}${sMonth}${iDay}"
									if { $sNextdate != [ lindex $lMainDate 0 ] && $sNextdate > $sDate } {
										set lTmp [ list $sNextdate [ lindex $lMainDate 1 ] [ lindex $lMainDate 2 ] ]
										lappend lRepeat $lTmp
									}
								}
							}
						}
					}

				} else {
					puts "ERROR 4: missing parser rule: ${sRule}"
				}

			} elseif { $sMyRule1 == "FREQ=WEEKLY" } {

				# alle X Wochen
				if { [ lindex $lMyRule2 0 ] == "INTERVAL" && [ lindex $lMyRule3 0 ] == "" } {
					# unseren ersten Eintrag haben wir schon - daher mit 1 starten
					set iCount 1
					foreach lMainDate $lMainDates {
						set sNextdate [ nextweek [ lindex $lMainDate 0 ] ]
						while { $sNextdate <= $sLastDate } {
							if { $iCount == 0 } {
								set lTmp [ list $sNextdate [ lindex $lMainDate 1 ] [ lindex $lMainDate 2 ] ]
								lappend lRepeat $lTmp
							}
							set sNextdate [ nextweek $sNextdate ]
							incr iCount
							if { $iCount >= [ lindex $lMyRule2 1 ] } {
								# Zaehler hat INTERVAL erreicht - Reset auf 0, so dass der neachste Durchlauf wieder valide ist
								set iCount 0
							}
						}
					}

				} else {
					puts "ERROR 3: missing parser rule: ${sRule}"
				}

			} else {
				puts "ERROR 2: missing parser rule: ${sRule}"
			}
		} else {
			puts "ERROR 1: missing parser rule: ${sRule}"
		}
	}

	# Listen zusammenfuehren
	foreach lTmp $lRepeat {
		lappend lMainDates $lTmp
	}

	# ggf. Until filtern
	if { $sUntil != "" } {
		set lNew [ list ]
		foreach lTmp $lMainDates {
			if { [ lindex $lTmp 0 ] <= $sUntil } {
				lappend lNew $lTmp
			}
		}
		set lMainDates $lNew
	}

	# Liste unique sortieren
	set lMainDates [ lsort -index 0 -unique $lMainDates ]

	return $lMainDates
}

set lAllUids [ list ]
set lDuplicateUids [ list ]
set lSkipEntry [ list ]
proc examine_entry { lEntry sCalendar sColor } {
	global iTimeStart lValidDates lCalendar lValidYears lValidMonths lValidYearMonth lAllUids lDuplicateUids lSkipEntry

	# grundlegend die relevanten Zeilen herausfischen
	set sDate        ""
	set sStartTime   ""
	set iDateEnd     0
	set sDateEnd     0
	set sDateEndReal 0
	set sEndTime     ""
	set sRule        ""
	set sTitle       ""
	set sText        ""
	set sUid         ""
	set lExclude     [ list ]
	set sDropDate    ""
	foreach sLine $lEntry {
		set lLine [ split $sLine ":" ]
		if { [ llength $lLine ] > 1 } {
			set sKey   [ lindex $lLine 0 ]
			set sValue [ string map {"\\n" "[[NL]]"} [ string trimleft [ string range $sLine [ string length $sKey ] end ] ":" ] ]
			set sValue [ string map {"\\" ""} $sValue ]
			set sValue [ string map {"[[NL]]" "\n"} $sValue ]

			# Timezone ID: ggf. anpassen
			set sTimezone ""
			set iTmp [ string first ";TZID=" $sKey ]
			if { $iTmp >= 0 } {
				incr iTmp 6
				set sTimezone [ string range $sKey $iTmp end ]
			}

			# Beginn: Datum ohne Uhrzeit
			if { $sKey == "DTSTART;VALUE=DATE" } {
				set sDate $sValue
			# Beginn: Datum mit Uhrzeit
			} elseif { $sKey == "DTSTART;TZID=${sTimezone}" } {
				set sDate [ string range $sValue 0 7 ]
				set sStartTime [ clock format [ clock scan $sValue -format "%Y%m%dT%H%M%S" -timezone ":${sTimezone}" ] -format "%H:%M" ]
			# Ende: Datum ohne Uhrzeit
			} elseif { $sKey == "DTEND;VALUE=DATE" } {
				set iDateEnd [ clock scan "${sValue}2359" -format %Y%m%d%H%M ]
				set sDateEnd $sValue
				set sDateEndReal [ clock format [ expr [ clock scan "${sValue}12" -format "%Y%m%d%H" ] - 86400 ] -format "%Y%m%d" ]
			# Ende: Datum mit Uhrzeit
			} elseif { $sKey == "DTEND;TZID=${sTimezone}" } {
				set iDateEnd [ clock scan $sValue -format "%Y%m%dT%H%M%S" -timezone ":${sTimezone}" ]
				set sDateEnd [ string range $sValue 0 7 ]
				set sEndTime [ clock format $iDateEnd -format "%H:%M" ]
				set sDateEndReal $sDateEnd
			# Wiederholungsregel
			} elseif { $sKey == "RRULE" } {
				set sRule $sValue
			# Titel / Summary
			} elseif { $sKey == "SUMMARY" } {
				set sTitle $sValue
			# Text / Description
			} elseif { $sKey == "DESCRIPTION" } {
				set sText $sValue
			# UID (ist gar nicht unique - was ein daemliches Design...)
			} elseif { $sKey == "UID" } {
				set sUid $sValue
			# Exclude-Tag
			} elseif { $sKey == "EXDATE;TZID=${sTimezone}" || $sKey == "EXDATE;VALUE=DATE" } {
				lappend lExclude [ string range $sValue 0 7 ]
			# ggf. einen anderen Eintrag ersetzen
			} elseif { $sKey == "RECURRENCE-ID;VALUE=DATE" } {
				set sDropDate $sValue
			}
		}
	}

	# Das Ende-Datum kann im Key auch ein "...;UNTIL=" sein...
	set sUntil ""
	foreach sLine $lEntry {
		set iPos [ string first ";UNTIL=" $sLine ]
		if { $iPos > 0 } {
			set sTmp [ string range $sLine [ expr $iPos + 7 ] end ]
			set lTmp [ split $sTmp ";" ]
			set sUntil [ lindex $lTmp 0 ]
			set sDateTmp [ lindex $lTmp 0 ]
			set iDateTmp [ clock scan "${sDateEnd}2359" -format "%Y%m%d%H%M" ]
			if { $iDateTmp < $iDateEnd || $iDateEnd == 0 } {
				set iDateEnd $iDateTmp
				set sDateEnd $sDateTmp
			}
			# wenn UNTIL in der Vergangenheit liegt gibt es auch keine Wiederholung
			set iUntil $iTimeStart
			if { [ string length $sUntil ] == 8 } {
				set iUntil [ clock scan "${sUntil}2359" -format "%Y%m%d%H%M" ]
			} elseif { [ string length $sUntil ] == 16 } {
				set iUntil [ clock scan $sUntil -format "%Y%m%dT%H%M%SZ" ]
			} else {
				puts "ERROR: missing parser rule for clock format: ${sUntil}"
			}
			if { $iUntil < $iTimeStart } {
				set sRule ""
			}
		}
	}

	# Thematik: besteht keine Regel, so kann dieser Eintrag einen Eintrag mit Regel und gleicher UID (!) an seinen Tagen uebersteuern
	#           daher muessen wir spaeter ("readcalendars" - nachdem alle Regeln ausgewertet wurden) auch doppelte UIDs kennen
	# bOverwritesSameUid: Info in der Kalenderliste
	# lDuplicateUids ...: benoetigt "readcalendars"
	# lAllUids .........: wird nur in dieser zu Erkennung proc genutzt
	set bOverwritesSameUid false
	if { $sRule == "" } {
		set bOverwritesSameUid true
	}
	if { [ lsearch $lAllUids $sUid ] < 0 } {
		lappend lAllUids $sUid
	} else {
		lappend lDuplicateUids $sUid
	}

	# auswerten
	set lDates [ list ]
	if { $sDate != "" } {
		set lDates [ parserules $sDate $sRule $sDateEndReal $sUntil $sStartTime $sEndTime ]
	}
	set bAdded false
	foreach lDate $lDates {
		set sDate [ lindex $lDate 0 ]
		if { [ lsearch $lExclude $sDate ] == -1 } {
			set sStart [ lindex $lDate 1 ]
			set sEnd   [ lindex $lDate 2 ]
			set sTime ""
			if { $sStart != "" && $sEnd != "" } {
				set sTime "${sStart}-${sEnd}"
			} elseif { $sStart != "" } {
				set sTime ">${sStart}"
			} elseif { $sEnd != "" } {
				set sTime "<${sEnd}"
			}
			lappend lCalendar [ list $sCalendar $sColor $sDate $sTime $sTitle $sText $sUid $bOverwritesSameUid ]
			set bAdded true
		}
	}

	# falls ein anderer Tag ersetzt wird: merken
	if { $sDropDate != "" } {
		lappend lSkipEntry [ list $sUid $sDropDate ]
	}

}

proc readcalendars { lIcals bForceFetch } {
	global sProgTech iOldestFetch iTimeStart lCalendar lDuplicateUids lSkipEntry
	set iOldestFetch 0
	if { [ llength $lIcals ] > 0 } {
		set iOldestFetch $iTimeStart
	}
	set iMyIcals [ llength $lIcals ]
	if { $iMyIcals > 0 } {
		incr iMyIcals
		incr iMyIcals $iMyIcals
		.l.update.progress configure -value 0 -maximum $iMyIcals
		update
	}
	set iProgress 0
	foreach lWebcal $lIcals {
		incr iProgress
		.l.update.progress configure -value $iProgress
		update
		if { [ lindex $lWebcal 3 ] } {
			set sName [ lindex $lWebcal 0 ]
			set sUrl [ string map {"webcal" "https"} [ lindex $lWebcal 1 ] ]
			set sColor [ lindex $lWebcal 2 ]
			set sMd5 [ string tolower [ ::md5::md5 -hex $sUrl ] ]
			set sFile [ file join [ file home ] ".local" "share" $sProgTech "${sMd5}.ical" ]
			if { $bForceFetch || ! [ file exists $sFile ] } {
				set sIcal [ exec "/usr/local/bin/curl" "-s" $sUrl ]
				# Check: Haben wir validen Content erhalten? -> beginnt mit "BEGIN:VCALENDAR", und hoert mit "END:VCALENDAR" auf
				set sFirstLine ""
				set sLastLine  ""
				foreach sTmp [ split $sIcal "\n" ] {
					set sTmp [ string trim $sTmp ]
					if { $sTmp != "" } {
						if { $sFirstLine == "" } {
							set sFirstLine $sTmp
						}
						set sLastLine $sTmp
					}
				}
				if { [ string toupper $sFirstLine ] == "BEGIN:VCALENDAR" && [ string toupper $sLastLine ] == "END:VCALENDAR"} {
					# OK - valider Content und bis zum Ende erhalten
					set oFile [ open $sFile w ]
					puts $oFile $sIcal
					close $oFile
				}
			}
			incr iProgress
			.l.update.progress configure -value $iProgress
			update
			if { [ file exists $sFile ] } {
				set iTmp [ file mtime $sFile ]
				if { $iTmp < $iOldestFetch } {
					set iOldestFetch $iTmp
				}
				set oFile [ open $sFile r ]
				set sIcal [ read $oFile ]
				close $oFile

				set lLines [ split $sIcal "\n" ]
				set lEntry [ list ]
				set bEntryStart false
				set bEntryStop true
				foreach sLine $lLines {
					set sLine [ string trim $sLine ]
					if { $sLine == "BEGIN:VEVENT" } {
						set bEntryStart true
						set bEntryStop false
					} elseif { $sLine == "END:VEVENT" } {
						set bEntryStart false
						set bEntryStop true
						examine_entry $lEntry $sName $sColor
						set lEntry [ list ]
					}
					if { $bEntryStart && !$bEntryStop } {
						lappend lEntry $sLine
					}

				}
			}
		}
		incr iProgress
		.l.update.progress configure -value $iProgress
		update
	}

	# UIDs koennen mehrfach vorkommen (unsere Logik wertet aber Eintraege bislang separiert aus...)
	# bei mehrfach verwendeten UIDs wird eine Regel durch einen Eintrag ohne Regel uebersteuert
	# wir muessen daher die Liste Filtern: Regeleintraege an indentischen Tagen muessen geloescht werden
	# Index 6: UID
	# Index 7: false=Regel, true=Einzeleintrag (uebersteuert)
	set lDelete [ list ]
	foreach sUid [ lsort -unique $lDuplicateUids ] {
		set lOverwritesIndices [ list ]
		set lRuleIndices [ list ]
		set iIndex 0
		foreach lEntry $lCalendar {
			if { [ lindex $lEntry 6 ] == $sUid } {
				lappend lIndices $iIndex
				if { [ lindex $lEntry 7 ] } {
					lappend lOverwritesIndices $iIndex
				} else {
					lappend lRuleIndices $iIndex
				}
			}
			incr iIndex
		}
		# Indices ermitteln, zu denen ein identischer Tag in $lOverwritesIndices existiert
		foreach iIndex $lOverwritesIndices {
			set sDay [ lindex [ lindex $lCalendar $iIndex ] 2 ]
			foreach iIndex2 $lRuleIndices {
				if { $sDay == [ lindex [ lindex $lCalendar $iIndex2 ] 2 ] } {
					lappend lDelete $iIndex2
				}
			}
		}
	}
	# Step #2: wir mussen auch wegen Ersetzungen Eintraege loschen
	foreach lSkip $lSkipEntry {
		# wir schreiben das umstaendlicher, dafuer aber hoffentlich nachvollziehbar...
		set sSkipUid  [ lindex $lSkip 0 ]
		set sSkipDate [ lindex $lSkip 1 ]
		set iIndex 0
		foreach lEntry $lCalendar {
			if { [ lindex $lEntry 6 ] == $sSkipUid && [ lindex $lEntry 2 ] == $sSkipDate } {
				lappend lDelete $iIndex
			}
			incr iIndex
		}
	}
	# die ermittelten Indices loeschen - wir machen das bewusst mit einer neuen Liste, und nicht mit lreplace (da in diesem Fall buggy)
	if { [ llength $lDelete ] > 0 } {
		set lTmp [ list ]
		set iIndex 0
		foreach lEntry $lCalendar {
			if { [ lsearch $lDelete $iIndex ] == -1 } {
				lappend lTmp $lEntry
			}
			incr iIndex
		}
		set lCalendar $lTmp
	}

}

# Kalender zu- und abschalten
proc icalsactive { iIcal bView } {
	global lIcals sProgTech sBgBright
	set wWidget ".l.calendars.color${iIcal}"
	set lIcal [ lindex $lIcals $iIcal ]
	if { $bView } {
		$wWidget configure -text " \u2713 " -background "#[ lindex $lIcal 2 ]" -command "icalsactive ${iIcal} false"
	} else {
		$wWidget configure -text " \u2013 " -background $sBgBright -command "icalsactive ${iIcal} true"
	}
	set lIcals [ lreplace $lIcals $iIcal $iIcal [ lreplace $lIcal 3 3 $bView ] ]

	set sFile [ file join [ file home ] ".config" $sProgTech "icals.list" ]
	set oFile [ open $sFile w ]
	puts $oFile $lIcals
	close $oFile

	mkupdate false
}

# FILE: 05_icons.tcl #########################################################################################

################################################################################
# Icons
################################################################################

# Logo 48px
set gLogo [ image create photo -format "png -alpha 1" -data "\
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
V7XF9F+aLpcMKmw5gwAAAABJRU5ErkJggg==" ]

set gLogo32 [ image create photo -format "png -alpha 1" -data "\
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" ]

set gLogo24 [ image create photo -format "png -alpha 1" -data "\
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" ]

set gUpdate [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QwSByYRgg2lwQAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
YXRlZCB3aXRoIEdJTVBkLmUHAAACzElEQVQ4y5WRT2gdVRTGvzN/3rzqm9f3x9RGrSUibQmF+I9G
0qAbcfF0IbioGxdDydWF6FrsQrSCUBeCi8rEVNSNuq0oFGIVpIjNW7hSd1rb17wkLebduXfm3jsz
x0WJiEjQA9/qfOf3wXeAXUYI8YgQ4qXdPN5uS2b+nJnfE0Ic/98AIcQbPvkz3WAKzHxeCBH+Z4AQ
L4SmLl59cd9pfmX/WzgQ3d9lZvFvXvpH6u0A7qu4fG0hHpx4sv0sXF2jhON31l8mAMcA/Jimqf0L
kCTJoTAME2Y+GVJjyicfM81ZPNNZYptbKrRBa28LP5gLfEl+RRVXKNldB/AxEX1ES0tL6wDufKr7
PI7ueZQ9+MQlQysNU1hYY+FMiU5/L9q9Fhw73q5u0Mrmm6hRIwBwAoRvZqJZDimkbKJQ5AbWWFjj
YAsHawxubNyEyQ0eXJyjO5rTHFCDLBfnvOXl5W/rqn7u7PgU6VpxVdVQUkNNcqiJhpIKaqKhZQ5r
HKIwwoXtT8nU+lKapic9AFhZWfmsRn367MYp6nZjZJPs1qHUt5RpZBOFufmjWDOrWFMX14m8xwDA
32lzOBxePPLAoScONA/fW449ZH9kyLVBoQsUqoCSGscW5vD+1uvm3AcfdofDIQNAsAMYDAY+o+53
gh5+uXkFcjtDrgo462BNCZ1pOAd0gqno768PkiSZbTQa/X6/37+2+ft0TH08tNjF6NcxDh6+B/Br
XP15DHiMPS3gNhkjSZIjvu+PPc9Twfz8/PEoinpRFDVHX19tjd0V7Ivuhjx4DWfW3+aC82px/9PB
YjyArQBdSfR6vSTLsu+YeSuQUn4vpZwuy/KuhtcMPtk6Aw8+Srifet3elzHHdk2uLlxWq4978ODY
giRtpGl6HgCC0WjEjUYjJ6JNIvotDMPVdrv9RRzH14ko0lp3fN+/7Jx7Vyn1MFWUABjvdPAn5kJq
Um6bCWMAAAAASUVORK5CYII=" ]

set gConfig [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAMwA
zABm6cIV7AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wEDxQsC7EHeegAAAAZdEVYdENv
bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAACxUlEQVQ4y1WTz2tcVRTHP/fNCzErrVUziuOo
ULtqZpcpdFMQms5GF/0ThC4sttAWFUxXxWZiBaEoSqFgQVy4cxXsqjtnQkGnIbgwaaZVGgkmTF7y
7nv3nnvuczGD6IEvZ3F+fDl8OAYAuj2wbVBAgAqILXAPWfoSDHMIA8pJ2QMpfT7nZApLvXr9yPy1
ax/hHGiEzc0Nvvl6aQCmTQQK+pcuXKJZb6IoCQnL15fnd87v9BLYbX+yeN7s7ZVkBznZ/ojnjzzL
6dNnANfB0Vl4e4Gjzx3l4DAj2z9gb3eXi5c/MPxNO4UCm0dESoIGoiqqgnMZkEMOLnNIGVANxKBE
iVQBKCCFHO9LvC+JUdEghCioFoCFArRUxHmiKBoiGiJRK7CQgGdzc32ypMBLSWIShsNfKshnsMxs
/LpR1UyCL2Uix+P1xyBg4NwcHA5OnXoHkYIYhSdP1qqdnUdr0GsBcI7BbH32RPPNpokhUjMJ/e9X
4RlaBt4F7DxoZ8woADIDDz7mv3GGLp6CMMEYWCFlNe12f/y3RwVEwQvc+PR/43TvjbOfWASgBEy3
y5zNGSycPUtZCkEi67/9Xg23/ly7dYsWwDIMZhuNE68fP25iVVGlNX746R41aJnFRar3L1zkxRdm
EVFElDSd4ubNr6rhcPuzt76FlxqND9+7ctWozQmq+BD4Y3ubG7dvk2T78HL9NZwLuFJwzjMaHfBq
4w2TW4pDKBrHjpki28c6h/WeUoRX6nUyIM0tiAilE4IEvFdCCBRWsTlYwKpSek+IEdFA0EiIkRxI
D3OIscKVMj4hBMQrqgm5hRwQY7DeoxoJURFVEmPIgYSK/tUrX1TT01PUEkNiDFtbf3H37s+4khUP
K9/dv8+jp0+pzPhPp2opl+/cqYC+Aeh06I1GtEMAkTGuWkIrTXnYXIUC5iwM7ASjB6ah/wBO/gPT
urYYYYkqtAAAAABJRU5ErkJggg==" ]

set gHide [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QEFCS0sBlG2hgAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
YXRlZCB3aXRoIEdJTVBkLmUHAAACr0lEQVQ4y41TO2hUURA9c999u8+3iZ81CcaYqFEEkyCK0YAK
VqJFwM7GJrCfgCBibWVhYWOpZBdJxF/hp4ydjT8sjEFQSVSMf81n3ezu3c27XwujhCSIp5iB4ZzD
zDBDWIRMNht3Vm2HtRxEAIDfEQ7Mf5vP52cX8vliA6equxJtBx+E63u4cwbkAEcEXfqA0rt7fQCu
/NOAPJ8l1u/VtLqDSxXBKANlNOqbGuB9fkiL+Us7cADBojQ7C60iKK2hlEZ8pcFy4MsViQGVsoDR
ct5AYW0iBgL9nwEjhoqowBoFpTSkVLANHMvowTKZzKIZrCMiCFFFRVTns4CxFnDOLaT29/eD5/N5
pNPpRsbYGSsrlvlhC+OBL0QBRmtorRFFEsYyENHxdKpvB2M8BLFrAwMDjzgAMMZarNWnkruyiCW3
YezLrBMVAWMNlNbQUuHF2Ffs7z55aJUuHpp+dhFw9juARwwAcrncKIzqjWbGoP1mvHo5TrW5GoSo
QlQERK2K8fE3+FEk55yCM9GFXG7gLAB4AJDNZpG/PPimszU+yZ047K3pZO8nJjAXRYgiCSEE2jZu
QtcGSVOPz10PGved2NnZ4kZGRpbuNZtJpROtBy5N1x3hd2/dhDYau3fvcQc6V1D55dXc1OTPE7fv
3Ph7FJRKpeB5ng8gjMVifl19/dZaaeY0a+o5NlbtwvuPn3D04BYUnp4faWrZeqZQKIxaayNjTI2I
Iq+7uxsAEoyx5mQyuTERhptjQUIUv72eXpf0uzo2JzH1fPBJ3arG4TAMEYahL6WMSSkjz/Nq3Pd9
SCklERXj8Tj5vu/K5fKcI69YeHe/5qxuBguGlZIzAL4FQTDNOf9pra0t+NTf6O3tpfb2do+I6oMg
aDTGJJ1zjHNeklJOGmOKpVJJDw0N2T+aX/CSWOH+wYrMAAAAAElFTkSuQmCC" ]

set gScrolltop [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QEFCS0ezobnBgAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
YXRlZCB3aXRoIEdJTVBkLmUHAAACw0lEQVQ4y42SzUtUURjGn3PuHWe8Y01lmhpRSoVmERUV9rV2
obsokL6cudfAIYhWRQRR0aqQtKA8k0iL6h8IXBsELTKJNn0tpMhIx49p7p17z2eLMRqrRQ+8vJvn
/fGe5z3AP+T1eeWePtXkpk+3AYCbOYP/knf2LADAPXOiuf/8lalzF28rN3PqKAC46ZN/+a1lw54L
NszQ29Pdmqhrf1l/4FJDTfNhqOLssfZNztTD0ceTnpvGxMTrvwGe54KxHHp7ultqNhwZX7v/Qp0Q
QMkvkeqGPSYWT3RtW08ncyOP3ldCLADo6+sDYwy9Pd0tTuO+56t3ZRuLJYEwDBFxjkKhQBJr2whV
iz07WlITlRDL8zwwxpA5fbw1ub5jfPXubONCkSMMQ/AoQhhxCM4xv7hIapsPmUTCOda+Mf6ZPRyd
9DwPpLy+VwejPtR3XEzFUhsx/S0P3/ehlISQClJKUEKwdUsz4pRjevwqoEXX8PCDZ/ZSBKtArM+z
r+59tKqSNfU7M1vefg+oUhJSSnAh0NrSBP/N0NRCMD8LHVUbgwYAsJee8AHAjvK9e3dZxn/h+6WE
0gJKSkRcgOgAOipcun/31pPfwXuwGWN/nraaUAvFwIdRElIqRFxAaw0A8UojYwzUdd1l08YYQgmB
X/ThB8FSFaHKgGVKp9Owc7kcAKC/v5+uqa11Fuby6wgBDYIAWitIpRCFEbTSSNhk3fUbN1Nz+Zlg
YGBAjIyMlDMAsEIp1aSkbHCSznZCKY24gBB8KQMOSi1UVcU2Cx7t5ZzPZDKZL5TSObtiI2GMmZeC
hwtf31lHuw6iFPgwMKDEwkr7B6IolHG7egZASAgxAMr/oFLZbLbDaHVNw14hlYQxALUobGgTq4rd
GRwcelrpt/8ExOPxT47jXA4CP5nPF4xWGk7SwcrUKglCpjo7O8nY2Jj55f8JtOlgc32DxxUAAAAA
SUVORK5CYII=" ]

set gSearchReset [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAjgD/AD//a6xcAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QEQDCQvL5Y5MwAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
YXRlZCB3aXRoIEdJTVBkLmUHAAAC0UlEQVQ4y32TzW5bVRSFv3PuubYcx3HqIFMqlRolKB70xyEI
VR2gwqgi8yJ15LrKfYxOeIciWYrjTiKRB6AzQCBQEEpxCaVOgkuqhkahtev8OLZz7zmbQeIqyqBr
vLS1v7X29jihIAhYXl4GoFgqTl4pFD4uTBUmpj4qmNrvteZpD4DilIql2zczw6ML2fSYl06mUEC7
s8t/Oy3b2m/fqlbmF0/63wwo3SmZuB+vX50sjKeHUoRhiPY0nvYwWgOa5v5rllZrjX7Yz1fmKhGA
DoIAgLgfr89MXx/30HR7PQRBodAoUAorlpGhJDPT18fjfrw+wFGDtT+7ePUbsQ5Pa7T2MJ6H8Q2+
MShRRBJhrSDOcRD2+H5l6ctqZX5RA4ylRhfOJNPYKCKMIpyzxGIxVp6t8bDxF9Y5bORwNsI5x2gi
RWZ4dAFABUGQjxn/yeULeaw4AESEP57VH/QOe6VjvLkbU59+0e33QAQnwmZrm/q/f+cNUO9H4cxP
T37bHQSqldb356s/Djj7Yf/Oo4361kT2PIIgAunkMCLkFMDs7Oy1RDzx80hi+KgaBS93WjhxX5XL
5bvFUvFsLnt+K5d5D0EBQs8esrRWu2EADqMw+fmla3QPOjgRkokhHtmIVzutX4ql4tm4H5+7fCFP
s91EK1Bodg72UYoNM1g7shHtvV1EHK9eN5n+4CKPN9cfAFx6f5Ltl9torRDtkRkZ4c8XDVutVFfN
yaty1uKOGdf/WSfpxzDGY3PrOdozeKLxgM5hl9Z++xaAPgpN0ekfMJEbR+Soa6UUIoJzDhHACYJw
LnuOpbVao1qZXwyCAAPgG98uP32M75n6Jx9eyRtRPN9+gYjDOYXWkB17h54N+fbhD41+2M8DlMtl
BgjfKThz7+t77YPS7ZuZ1OjCu+kxL5UYBoS9bodfn67Y1t5bnmnQeblcfvPOIuSOa92oVqqrpz0A
/wMQlFoYwWhRigAAAABJRU5ErkJggg==" ]

set gSave [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QEFCTEXUS0C/wAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
YXRlZCB3aXRoIEdJTVBkLmUHAAAC/0lEQVQ4y72TX0hTcRTHz+/eXeeum1M3tWlzudTZ1JloEP2T
6N+oHkqtHlKnTIUehBgEhQ/Rn5eop0ERaTpfTIRCrIdMKguhIP+UqE2zPxvin7ld23a9u7u7f3qQ
iFEP9dJ5O4fD5wvn+z0A/7OabfbfZuhfAPZqmxZLwi3it8hrlKvgMbUc8L9SbmxCFjAq5ZXpb5Na
TQ7eQxva77X3lybnbwCabXYY/zABDWXVquJMU2xyxR0HKBFzlYqazaPkxUITSicAL1Btt0QMpcLI
Wh9qbrBDu+s+2I/Wmslm4xP28eIkMxeqJvNUAiCEuDEqSdWUNwb1WQVBPgwCx4NKlyLJB8MB+qbb
jI+/nwD78Vqz/ET2NF+lTU3YqSkkPNFk//DCkGwlRpKnc8a4eq3p08JnCAZDICIJNNM40Ndmrne9
fPBMZiuqSiIbjQPhQ4nwZeYjIAyg2FFwXrPKlhP5yZl0U1rBxMwoxLgYpGSkSSVLOSjS+bWma7j3
0dncY4CXyI084mAfUZFqdi/PQ8DvB/fiPBhbdhj8RTHN0xdDEAwFQUGSUClZEHNrtrWjz9Xd0tIC
rue9GzbW7q9GSkvmDWjVX3AN90KMi4EoCiBJACBJkF9SCIexCinW5a265+ro/3lcq9X6Kwe7t+xA
RXssD2WX8052vuoFlokAiCJsys6CqoyDYvSK++rexiN3owyrjkQiIYqi1mia5pDD4cDNZrNaqVSm
yjA8YWTszWWuKePMncEu0GVnSac0BxA5ELpUXGZ5p0hU6GUyGRkOh5d8Pt+Ux+Px4uXl5YRKpdIT
BFEsiIIpVZky7+ubzdl9zqozczkIdy33GLZtncMxzKBQKBCO46uiKPpYlqUCgUBIxrIszzCMnyTJ
eUEQtOvr61p9vqHz++2VXZiEBdWF+tF1mqYQwFQ0GvVTFOWTJCnMMAzvdDqluF9wOByJOp0uBQAy
EEJqhJAgiuIaQsjn9XqDTqeT/2PWbTZbXN/W1ob19PQQ3d3dRF1dXZyI1WqN2/0BN4xArVmEp+sA
AAAASUVORK5CYII=" ]

set gExit [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QEEDi8BzEv5kQAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
YXRlZCB3aXRoIEdJTVBkLmUHAAADYElEQVQ4y12SX2gcVRTGvzt3dnamu5ukSYxkIX+2ZZtURJM1
ta1FKSJUsYjSIGqaliwzk6AvgigI/kEhPiktCEJ3N00eLFRRH3yxDxX7UJFCDPFP0WLEKs2m2bQm
2WR3Zu/cc68PaRD6Pf/O93HOd4C7dML1M6OenwCAvOsBAFx/HMfyeXbC9ffczRsA8LK/BR5zx4/v
MtXvzUxfODlykp8tFZH3fJQKZ9DKzTeypr466nnFO0EAALbt9JLrP/tEnL4ccwQWJDdO12OzK4o9
+XmpcHvU8997Jk7vDPNNfQkJdi4wJ0uFwlsAwAHgBXd85IhN51+0QqMRNlhChDiYjKV/jIwD/bmh
3ESCXn+4VmHXl26yjkYN3S1Nj+kHhrp+mpv7mgPA0EO5j4cdmdkpGgiFgCAJI6hhMBHrSXPsH9is
sBvLKyBSaIQNZNt3YlaaTbsG931qAEBNGU/P1GOX1+I2OAMiEaEeNGBVFnFf5S+UV25BaQVBEpnd
GXwS2rcrih09VzqzysY8DxoMgWI7dhj6ygeJ8H66tYwgDCCJEEkJkgpKSezJZvGRbP7354jt/2qq
sDDqev8fcVtvTvjlV+Ry5/pKBSQJERFkFKEplcKFzn583zAGvzhbnB9zXUyXSls15r2tSo57/uS+
GHWqeg1RJCGkRBRFiEiiWq3iKR6iw2SF4bzbOl0qIe+6YGOej+liAaOe//5EUr2dXS1jcbmCaHtY
ErQiCCLEOUffwUOY3LBmA7F+YGrmM2IAMOL6rx116MPH18u4vlQGtAYAtKe7oGIxLP9xDavVKogI
jmWh69BhnA7ilzY1jhh3Vr/HASAiASEkIkno7+vD+UR6sRjr+KF3MIeWZBJhQ0ASQTEGvfVEjOfz
eZZOJa/Mhbr/wfbmvfcqgZ6ebpxSrcGiab9LBv/uYmg+8lxXa4o2qtj76GGcqlvX7LjzvKZojQ8M
DDAJ2Mlk4urlmuplqebd3yinfgNmKQatSco2IRq/zMPOdPb0Ns/U+Z+wnVcV0d9SUo3ncjnDsqw2
x7bTTtxa+DWQrMaMby1uLAHY1FrXtdZqU0S/zQsWxQw25dj2Oue8JoTYMAGAMSa01utE1JIw+UUA
ddM0/zEMowKApJRtQRB0cyFuaphrSqlVxlidMab/Aw2lo/jH7vQWAAAAAElFTkSuQmCC" ]

set gInfo [ image create photo -format "png -alpha 1" -data "\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAGYA
ZgDMMbkAmwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wEDxQQASIh6QkAAAAZdEVYdENv
bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAClUlEQVQ4y22TTWidRRSGn/m+e5NrhFIUQwgK
CelNiyYWjIIYCY0hkCxKsqlm0S7cRCgoXaQridCF9YeIGxNMDKJ0oV10ITHgqqmILtokJOQmNQYx
LRUxULDVm9z7zZxzXEzz48/AMIs573new8zr+M96twiMQNYNWTMEBW4Bc6Bj8P4G/yPaPSfhohVe
vKOvfFi2t2fFLsyIDb533wrPlxTOGbwxGWuHAXD7TS6uuMbjbW+N9/HwQw4vRvAQBMQZCNzbFibO
f4ptLZRguh0g3SW7xqf7P/i8H+fAS9xPNMCzRVj9RVEzEqBroIPvr96qZ7u+EZa/dnHm7KfRK29S
UwsaIADijd4Ox4nmhLOfZdTkQNQIAuWKcOnsqwCtKXS9U3jh9DM9Lx12XiCYIWqowMqm8tWCkM9B
ECNIvMuljtLtQya/LxRyUOnuH3zMVX2cWdRoaoCWRkdQRy6By9c8qTOCgll0cbSzw60sf9SdQNZ0
rKmAzwwvhg9GcJAJtDzq6D2SUtlW/AN61RtBlNZiHVBpSiDDhygOIRbe/FmY/cHz45YC7DXOvKKq
BFFUFAgkoJtLa3/tNfEhkrwYavGN9gCie6D5UgXQzQSY++6bdRONYlUlC/uWAYJEqmgUixq/3Zg1
YC6BMFad/8TdK0t0IEbL4yknO/M82ZAAcKq3lqGeAjtlQarCn/c9YXXKQRhL4OMN0KlLo+PkU0fV
G+IifflX4YulKkkCatFJmqbMjY8CTMH1jQNf+eUVHmlrGzj/OrV1abQshmQGpqhBecfz7cQo/LFY
gsX2A1kYAr4EBichG84/dcYanjvh2lvyiBpL6xXuLs1YWJt2kEzB/GvQASwcDNPu6iuCjsD2v+Kc
PIjz4j/i/Ddupp4/fq3sSgAAAABJRU5ErkJggg==" ]

# FILE: 06_procs.tcl #########################################################################################

################################################################################
# sonstige Procs
################################################################################

# alle X Sekunden etwas ausfuehren
proc everysecond { iSeconds sAction } {
	set iSeconds [ expr $iSeconds * 1000 ]
	uplevel #0 $sAction
	after $iSeconds [ info level 0 ]
}

# Listenalter eintragen
proc updatelistdate {} {
	global iOldestFetch
	# .l.update.text configure -text "[::msgcat::mc {last}]: [ clock format $iOldestFetch -format "%d.%m.%Y %H:%M" ]"
	if { $iOldestFetch > 0 } {
		set iHoursAgo [ expr ([ clock seconds ] - $iOldestFetch) / 3600 ]
		set sText ""
		if { $iHoursAgo == 0 } {
			append sText "[::msgcat::mc {last}]: [::msgcat::mc {not an hour ago}]"
		} elseif { $iHoursAgo == 1 } {
			append sText "[::msgcat::mc {last}]: [::msgcat::mc {1 hour}]"
		} else {
			append sText "[::msgcat::mc {last}]: ${iHoursAgo} [::msgcat::mc {hours}]"
		}
		append sText "\n[ clock format $iOldestFetch -format "%d.%m.%Y %H:%M" ] [::msgcat::mc {o'clock}]"
		if { $sText != [ .l.update.text cget -text ] } {
			.l.update.text configure -text $sText
			if { [ clock format $iOldestFetch -format "%Y%m%d" ] !=  [ clock format [ clock seconds ] -format "%Y%m%d" ] } {
				.l.update.text configure -foreground "#cc0000"
			} else {
				.l.update.text configure -foreground ""
			}
		}
	} else {
		.l.update.text configure -text ""
	}
}

proc shownotifications {} {
	global sProgName lNotifications bTrayMode
	if { $bTrayMode } {
		if { [ llength $lNotifications ] > 0 } {
			set lNotifications [ lsort -unique $lNotifications ]
			set sText ""
			set sSeparator ""
			foreach sNotification $lNotifications {
				append sText $sSeparator
				append sText $sNotification
				set sSeparator "\n"
			}
			if { $sText != "" } {
				tk sysnotify $sProgName $sText
			}
			set lNotifications [ list ]
		}
	}
}

proc notifyoldlists {} {
	global iOldestFetch lNotifications
	if { [ clock format $iOldestFetch -format "%Y%m%d" ] != [ clock format [ clock seconds ] -format "%Y%m%d" ] } {
		lappend lNotifications [::msgcat::mc {The calendar should be synchronized again!}]
	}
}

proc checkentriestoday {} {
	global bOnceRaised bEntriesToday lNotifications
	if { ! $bOnceRaised && $bEntriesToday } {
		lappend lNotifications [::msgcat::mc {There are entries in your calendar today.}]
	}
}

# iCals Datei schreiben - wird aus Config-Maske ausgelesen
proc writeicals {} {
	global iIcals sIcalFile
	set lIcals [ list ]
	for { set iRow 1 } { $iRow <= $iIcals } { incr iRow } {
		set sName  [ .conf.f.tabs.cal.name${iRow} get ]
		set sColor [ string map {"#" ""} [ .conf.f.tabs.cal.color${iRow} cget -background ] ]
		set sUri   [ .conf.f.tabs.cal.webcal${iRow} get ]
		if { $sName != "" && $sUri != "" && $sColor != "" } {
			lappend lIcals [ list $sName $sUri $sColor "true" ]
		}
	}
	set oFile [ open $sIcalFile w ]
	puts $oFile $lIcals
	close $oFile
}

# wir benoetigen 2stellig Hex
proc gethexcolor { iInt } {
	if { $iInt < 0 } {
		set iInt 0
	} elseif { $iInt > 255 } {
		set iInt 255
	}
	set sHex [ format "%#010X" [ expr $iInt ] ]
	set sHex [ string trimleft [ string range $sHex 2 9 ] "0" ]
	set sHex [ string tolower $sHex ]
	set iHex [ string length $sHex ]
	if { $iHex == 0 } {
		return "00"
	} elseif { $iHex == 1 } {
		return "0${sHex}"
	} else {
		return $sHex
	}
}

# Farbwahl
proc colorselector { wWidget iNumber } {
	global sProgName gLogo32 gSave gExit
	set wSelf ".colorsel{iNumber}"
	if { [ winfo exists $wSelf ] } {
		raise $wSelf
	} else {

		# Fenster initalisieren
		toplevel     $wSelf
		wm title     $wSelf "${sProgName}: [::msgcat::mc {Color}] #${iNumber}"
		wm resizable $wSelf false false
		wm transient $wSelf .conf
		focus        $wSelf

		bind $wSelf <Escape> { destroy $wSelf }

		# wir wollen TTK als Hintergrund
		ttk::frame $wSelf.f -padding 10
		pack $wSelf.f -side top

		# Kopf
		ttk::label $wSelf.f.title -text " [::msgcat::mc {Color}] #${iNumber}" -font MyFontBigBold -image $gLogo32 -compound left
		pack $wSelf.f.title -side top -fill x

		ttk::frame $wSelf.f.c
		pack $wSelf.f.c -side top -pady 10

		set lColorPalette [ list ]
		set iFarbabstand 0
		set iFarbabstandRaise 3
		set iColor 0
		while { $iColor <= 128 } {
			set iColor [ expr $iColor + $iFarbabstand ]
			set sColor1 [ gethexcolor [ expr 255 - $iColor ] ]
			set sColor2 [ gethexcolor [ expr 255 - $iColor - $iColor ] ]
			if { $iColor > 28 } {
				lappend lColorPalette [ list $sColor2 $sColor1 "ff" ]
			}
			set iFarbabstand [ expr $iFarbabstand + $iFarbabstandRaise ]
		}

		set iCount 0
		set iCol 0
		set lPadx [ list 0 0 ]
		set lPady [ list 0 0 ]
		foreach lColorSet $lColorPalette {
			set sColor1 [ lindex $lColorSet 0 ]
			set sColor2 [ lindex $lColorSet 1 ]
			set sColor3 [ lindex $lColorSet 2 ]

			set lColors [ list ]
			lappend lColors "#${sColor2}${sColor2}${sColor2}"
			lappend lColors "#${sColor3}${sColor1}${sColor3}"
			lappend lColors "#${sColor3}${sColor1}${sColor2}"
			lappend lColors "#${sColor3}${sColor1}${sColor1}"
			lappend lColors "#${sColor3}${sColor2}${sColor1}"
			lappend lColors "#${sColor3}${sColor3}${sColor1}"
			lappend lColors "#${sColor2}${sColor3}${sColor1}"
			lappend lColors "#${sColor1}${sColor3}${sColor1}"
			lappend lColors "#${sColor1}${sColor3}${sColor2}"
			lappend lColors "#${sColor1}${sColor3}${sColor3}"
			lappend lColors "#${sColor1}${sColor2}${sColor3}"
			lappend lColors "#${sColor1}${sColor1}${sColor3}"
			lappend lColors "#${sColor2}${sColor1}${sColor3}"

			set iRow 0
			foreach sColor $lColors {
				label $wSelf.f.c.color${iCount} -text " " -background $sColor -relief flat -borderwidth 0 -padx 20 -pady 3 -highlightthickness 0
				grid $wSelf.f.c.color${iCount} -row $iRow -column $iCol -padx $lPadx -pady $lPady
				bind $wSelf.f.c.color${iCount} <Button> "
					${wWidget} configure -background ${sColor}
					destroy $wSelf
				"
				incr iRow
				incr iCount
				set lPadx [ list 2 0 ]
				set lPady [ list 2 0 ]
			}

			incr iCol

		}

		# Abbrechen
		ttk::button $wSelf.f.close -text [::msgcat::mc {Abort}] -image $gExit -compound left -command "destroy ${wSelf}"
		pack $wSelf.f.close -side top -fill x

	}
}

################################################################################
# Daten holen + Ausgabe
################################################################################

proc viewlist { bReconfWinsize bShowNotifications } {
	global iViewDays lValidDates lCalendar iOldestFetch iTimeStart sBgDark sBgNormal sBgLight sBgBright sSearch bCompleteList lNotifications bEntriesToday

	# Suche in Lowercase entkoppeln
	set sSearchReal [ string tolower $sSearch ]

	# vollstaendige Tagesliste - oder nur jene mit Eintraegen?
	set iSeparatorThickness 2
	set lViewTheseDays $lValidDates

	# Suche als Filter anwenden (der aktuelle Tag wird aber immer ausgegeben)
	# gesucht wird auf die Listen-Indices 4 und 5
	if { $sSearchReal != "" } {
		set lTmpCalendar [ list ]
		set lViewTheseDays [ list ]
		foreach lEntry $lCalendar {
			if { [ lsearch $lValidDates [ lindex $lEntry 2 ] ] > -1 } {
				set bFound false
				foreach iSearch [ list 4 5 ] {
					if { [ string first $sSearchReal [ string tolower [ lindex $lEntry $iSearch ] ] ] > -1 } {
						set bFound true
					}
				}
				if { [ clock format $iTimeStart -format %Y%m%d ] == [ lindex $lEntry 2 ] } {
					# den heutigen Tag immer ausgeben
					set bFound true
				}
				if { $bFound } {
					lappend lTmpCalendar $lEntry
					lappend lViewTheseDays [ lindex $lEntry 2 ]
				}
			}
		}
		# den heutigen Tag immer ausgeben
		lappend lViewTheseDays [ clock format $iTimeStart -format "%Y%m%d" ]
		set lViewTheseDays [ lsort -unique $lViewTheseDays ]
		set lCalendar $lTmpCalendar
	}

	# ggf. nur Tage mit Eintraegen anzeigen (Modus bei Suche ihnehin aktiv - daher laufen wir bei der Suche nicht nochmal rein)
	if { $sSearchReal == "" && ! $bCompleteList } {
		set lTmpCalendar [ list ]
		set lViewTheseDays [ list ]
		foreach lEntry $lCalendar {
			if { [ lsearch $lValidDates [ lindex $lEntry 2 ] ] > -1 } {
				lappend lTmpCalendar $lEntry
				lappend lViewTheseDays [ lindex $lEntry 2 ]
			}
		}
		# den heutigen Tag immer ausgeben
		lappend lViewTheseDays [ clock format $iTimeStart -format "%Y%m%d" ]
		set lViewTheseDays [ lsort -unique $lViewTheseDays ]
		set lCalendar $lTmpCalendar
	}

	# ein paar Variablen ;)
	set iRow 0
	set iCountDays 0
	set iLineHeight 0
	set wWidgetForColWidth ""
	set sBgBright $sBgBright
# Bug / Ungenauigkeit: bei Umstellung zur Sommerzeit (oder Winterzeit?) sollte jene Stunde nicht getroffen werden...
	set sLastDay [ clock format [ expr $iTimeStart - 86400 ] -format "%Y%m%d" ]
	set bToday false
	foreach sDay $lViewTheseDays {
		incr iCountDays

		# keine korrekte Tagesabfolge - Separator erhoehen
		set iSeparatorThickness 2
		if { [ nextday $sLastDay ] != $sDay } {
			set iSeparatorThickness 4
		}
		set sLastDay $sDay
		# zweiter Tag: bToday ist noch vom vorige Durchlauf gesetzt ;)
		if { $bToday } {
			.r.s configure -height $iSeparatorThickness
		}

		# ist es der heutige Tag?
		set bToday false
		if { [ clock format $iTimeStart -format %Y%m%d ] == $sDay } {
			set bToday true
		}

		if { $iCountDays > 2 } {
			incr iRow
			# wir nutzen Frame (da ohne TTK)
			frame .r.c.frame.separator${iRow} -relief flat -borderwidth 0 -height $iSeparatorThickness -background $sBgDark
			grid .r.c.frame.separator${iRow} -row $iRow -column 0 -columnspan 4 -sticky ew
		}

		incr iRow
		set iRowOfDay $iRow
		set iStamp [ clock scan $sDay -format %Y%m%d ]
		set iDayOfWeek [ clock format $iStamp -format %u ]
		set sDayofWeek ""
		if { $iDayOfWeek == 1 } {
			set sDayofWeek "Mo."
		} elseif { $iDayOfWeek == 2 } {
			set sDayofWeek "Di."
		} elseif { $iDayOfWeek == 3 } {
			set sDayofWeek "Mi."
		} elseif { $iDayOfWeek == 4 } {
			set sDayofWeek "Do."
		} elseif { $iDayOfWeek == 5 } {
			set sDayofWeek "Fr."
		} elseif { $iDayOfWeek == 6 } {
			set sDayofWeek "Sa."
		} elseif { $iDayOfWeek == 7 } {
			set sDayofWeek "So."
		}

		set wPath ".r.c.frame"
		if { $bToday } {
			set wPath ".r.today"
		}

		# Wochentag
		label ${wPath}.${iRow}weekday -pady 0 -width 3 -justify center -text [::msgcat::mc "DAY${iDayOfWeek}"] -background $sBgNormal
		grid ${wPath}.${iRow}weekday -row $iRow -column 0 -sticky wen
		if { $iLineHeight == 0 } {
			set iLineHeight [ winfo reqheight ${wPath}.${iRow}weekday ]
		}
		set wWidgetForColWidth "${wPath}.${iRow}weekday"

		# Datum
		label ${wPath}.${iRow}date -pady 0 -text "[ clock format $iStamp -format "%d.%m.%Y" ] " -background $sBgNormal
		grid ${wPath}.${iRow}date -row $iRow -column 1 -sticky wn

		# Wochentag + Datum an Sams- und Sonntagen hervorheben
		if { $iDayOfWeek == 6 } {
			${wPath}.${iRow}weekday configure -background $sBgLight
			${wPath}.${iRow}date    configure -background $sBgLight
		} elseif { $iDayOfWeek == 7 } {
			${wPath}.${iRow}weekday configure -background $sBgBright
			${wPath}.${iRow}date    configure -background $sBgBright
		}

		# falls es der aktuelle Tag ist: kennzeichnen
		if { $bToday } {
			${wPath}.${iRow}weekday configure -font MyFontBold
			${wPath}.${iRow}date    configure -font MyFontBold
		}

		set iDayEntries 0
		set sOldColor ""
		foreach lEntry $lCalendar {
			if { [ lindex $lEntry 2 ] == $sDay } {

				set sColor [ lindex $lEntry 1 ]
				set sText ""
				set sTime [ lindex $lEntry 3 ]
				if { $sTime != "" } {
					set sTime [ string map {"-" " - "} $sTime ]
					set sTime [ string map {">" "> "} $sTime ]
					set sTime [ string map {"<" "< "} $sTime ]
					set sTime [ string map "{>} [::msgcat::mc {from}]" $sTime ]
					set sTime [ string map "{<} [::msgcat::mc {till}]" $sTime ]
					append sText "${sTime} [::msgcat::mc {o'clock}]: "
				}
				append sText [ lindex $lEntry 4 ]

				incr iDayEntries

				if { $sColor != $sOldColor } {
					if { $sOldColor != "" } {
						# wir nutzen Frame (da ohne TTK)
						frame .r.c.frame.separator${iRow} -relief flat -borderwidth 0 -height 1 -background $sBgNormal
						grid .r.c.frame.separator${iRow} -row $iRow -column 2 -columnspan 2 -sticky ew
						incr iRow
						incr iDayEntries
					}
					set sOldColor $sColor
				}

				label ${wPath}.${iRow}color -pady 0 -text "   " -background "#${sColor}"
				grid ${wPath}.${iRow}color -row $iRow -column 2 -sticky wns

				label ${wPath}.${iRow}entry -padx 10 -pady 0 -anchor w -justify left -text $sText -background $sBgBright
				grid ${wPath}.${iRow}entry -row $iRow -column 3 -sticky nwe

				set sDescription [ lindex $lEntry 5 ]
				if { $sDescription != "" } {
					incr iRow

					set sText ""
					set lDescription [ split $sDescription "\n" ]
					foreach sTmp $lDescription {
						append sText "\n     ${sTmp}"
					}
					set sText [ string trimleft $sText "\n" ]

					incr iDayEntries

					label ${wPath}.${iRow}color -pady 0 -text "   " -background "#${sColor}" -font MyFontSmaller
					grid ${wPath}.${iRow}color -row $iRow -column 2 -sticky wens

					label ${wPath}.${iRow}entry -padx 10 -pady 0 -anchor w -justify left -text $sText -background $sBgBright -font MyFontSmaller
					grid ${wPath}.${iRow}entry -row $iRow -column 3 -sticky nwe
				}

				incr iRow
			}
		}

		if { $iDayEntries > 1 } {
			grid configure ${wPath}.${iRowOfDay}weekday -sticky wens -rowspan $iDayEntries
			grid configure ${wPath}.${iRowOfDay}date -sticky wens -rowspan $iDayEntries
		}

		if { $iDayEntries > 0 && $sDay == [ clock format $iTimeStart -format "%Y%m%d" ] } {
			set bEntriesToday true
			if { $bShowNotifications && $sSearch == "" && [ wm state . ] != "normal" } {
				lappend lNotifications [::msgcat::mc {There are entries in your calendar today.}]
			}
		}

	}
	foreach wWidget [ winfo children .r.c.frame ] {
		bind $wWidget <MouseWheel> {
			if { %D > 0 } {
				.r.c yview scroll -1 units
			} elseif { %D < 0 } {
				.r.c yview scroll  1 units
			}
		}
	}
	update
set bReconfWinsize true
	if { $bReconfWinsize } {
		# Hoehe: wir bleiben hier fest auf einem Maximum von 27 Tageszeilen (1 Tag "heute" == 4 Wochen)
		set iLines $iViewDays
		if { $iLines > 27 } {
			set iLines 27
		}
		if { $iLineHeight == 0 } {
			set iLineHeight 10
		}
		set iHeight [ expr ($iLineHeight + [ winfo reqheight .r.s ]) * $iLines ]
		.r.c configure -width [ winfo reqwidth .r.c.frame ] -height $iHeight
		.r.c configure -scrollregion "0 0 [ winfo reqwidth .r.c.frame ] [ winfo reqheight .r.c.frame ]"
		update
		wm minsize . [ winfo reqwidth . ] [ winfo reqheight . ]
	}
	.r.scrollbar fraction 0.0 0.0
	updatelistdate
	.l.update.progress configure -value 0
}

proc mkupdate { bRefetch } {
	global lIcals lCalendar
	.l.update.text configure -text "\u2026 [::msgcat::mc {please wait}] \u2026\n " -foreground ""
	foreach wWidget [ winfo children .r.c.frame ] {
		destroy $wWidget
	}
	foreach wWidget [ winfo children .r.today ] {
		destroy $wWidget
	}
	update
	set lCalendar [ list ]
	readcalendars $lIcals $bRefetch
	viewlist false $bRefetch
	update
}

# FILE: 07_gui.tcl ###########################################################################################

################################################################################
# GUI: Main Window
################################################################################

# Allgemeines
if { $bTrayMode } {
	wm withdraw .
}
wm title     . $sProgName
wm iconname  . $sProgName
wm resizable . false true
catch { wm iconphoto . -default $gLogo $gLogo32 }
# window maker...
wm command . [ concat $argv0 $argv ]
wm group . .

# kein Beenden beim Schliessen des Fensters
wm protocol . WM_DELETE_WINDOW {
	if { $bTrayMode } {
		wm withdraw .
		update
	} else {
		exit
	}
}

# linker Frame
ttk::frame .l -padding {10 0}
grid .l -row 0 -column 0 -sticky ns

ttk::label .l.title -font MyFontBigBold -image $gLogo32 -compound left
pack .l.title -side top -fill x -pady {10 0}

# obere Leiste mit Systray + Scrolltop
ttk::frame .l.topbar
pack .l.topbar -side top -fill x -pady {10 0}

# Systray #1
if { $bTrayMode } {
	set sCloseText " [::msgcat::mc {System Tray}]"
} else {
	set sCloseText " [::msgcat::mc {Minimize}]"
}
ttk::button .l.topbar.systray -text $sCloseText -image $gHide -compound left -command {
	if { $bTrayMode } {
		wm withdraw .
	} else {
		wm iconify .
	}
	update
}
pack .l.topbar.systray -side left -fill x -expand true

# Scrolltop
ttk::button .l.topbar.scrolltop -image $gScrolltop -state disabled -command {.r.c yview moveto 0.0}
pack .l.topbar.scrolltop -side left -fill y -padx {10 0}

# Suche
ttk::labelframe .l.search -text [::msgcat::mc {Search}] -padding {10 5 10 10}
pack .l.search -side top -fill x -pady {10 0}

# Such: Eingabefeld
ttk::entry .l.search.search -textvariable sSearch -font TkDefaultFont
pack .l.search.search -side left -fill x -expand true
proc dosearch { args } { mkupdate false }
trace add variable sSearch write dosearch

# Such: Reset
ttk::button .l.search.reset -image $gSearchReset -command "set sSearch {}"
pack .l.search.reset -side left -fill y -padx {10 0}

# Kalender
set lIcalsChecked [ list ]
if { [ llength $lIcals ] > 0 } {
	ttk::labelframe .l.calendars -text [::msgcat::mc {Calendars}] -padding {10 4 10 10}
	pack .l.calendars -side top -fill x -pady {10 0}

	set iRow 0
	foreach lIcal $lIcals {
		set wWidget "color${iRow}"
		if { [ lindex $lIcal 3 ] } {
			button .l.calendars.${wWidget} -text " \u2713 " -background "#[ lindex $lIcal 2 ]" -relief flat -borderwidth 0 -padx 0 -pady 0 -highlightthickness 0 -command "icalsactive ${iRow} false"
		} else {
			button .l.calendars.${wWidget} -text " \u2013 " -background $sBgBright -relief flat -borderwidth 0 -padx 0 -pady 0 -highlightthickness 0 -command "icalsactive ${iRow} true"
		}
		grid .l.calendars.${wWidget} -row $iRow -column 0 -sticky nsew -pady 1
		set wWidget "text${iRow}"
		ttk::label .l.calendars.${wWidget} -text " [ lindex $lIcal 0 ]"
		grid .l.calendars.${wWidget} -row $iRow -column 1 -sticky wns
		incr iRow
	}

}

# Rescan-Frame
ttk::labelframe .l.update -text [::msgcat::mc {Update}] -padding {10 5 10 10}
pack .l.update -side top -fill x -pady {10 0}

# Textinfo
ttk::label .l.update.text -font MyFontSmaller

# Rescan
ttk::button .l.update.button -text " [::msgcat::mc {Update}]" -image $gUpdate -compound left -command "mkupdate true"

# Progressbar
ttk::progressbar .l.update.progress -mode determinate -value 0 -orient vertical -length 10

grid .l.update.text     -sticky we -row 0 -column 0
grid .l.update.button   -sticky we -row 1 -column 0 -pady {10 0}
grid .l.update.progress -sticky ns -row 0 -column 1 -rowspan 2 -padx {10 0}
grid columnconfigure .l.update 0 -weight 1

# Systray #2
ttk::button .l.systray -text $sCloseText -image $gHide -compound left -command {
	if { $bTrayMode } {
		wm withdraw .
	} else {
		wm iconify .
	}
	update
}
pack .l.systray -side bottom -fill x -pady 10

# Leiste mit About, Config + Exit
ttk::frame .l.toolbar
pack .l.toolbar -side bottom -fill x -pady {10 0}

# Config
ttk::button .l.toolbar.config -text [::msgcat::mc {Settings}] -image $gConfig -compound left -command window_settings
pack .l.toolbar.config -side left -fill both -expand true

# About
ttk::button .l.toolbar.about -image $gInfo -command window_about
pack .l.toolbar.about -side left -padx 10 -fill y

# Exit
ttk::button .l.toolbar.exit -image $gExit -command {
	if { $bTrayMode } {
		set sDetail [::msgcat::mc {You are about to finish the application - not minimize it.}]
		set sDetail [ string map {" - " "\u00a0\u2013 "} $sDetail ]
		set iDetail [ expr [ string length $sDetail ] - 1 ]
		if { [ string range $sDetail $iDetail end ] == "." } {
			set iDetail [ expr $iDetail - 1 ]
			set sDetail "[ string range $sDetail 0 $iDetail ]\u2026"
		}
		if { [ tk_messageBox -parent . -type yesno -icon question -title $sProgName -message [::msgcat::mc {Really quit?}] -detail $sDetail ] == "yes" } {
			exit
		}
	} else {
		exit
	}
}
pack .l.toolbar.exit -side left -fill y

# Separator zwischen linkem und rechten Frame
frame .s -relief flat -borderwidth 0 -width 1 -background $sBgDark
grid .s -row 0 -column 1 -sticky ns

# rechter Frame
frame .r -background $sBgNormal
grid .r -row 0 -column 2 -sticky ewns

# heutiger Tag
frame .r.today -background $sBgNormal
grid .r.today -row 0 -column 0 -columnspan 2 -sticky ew
grid columnconfigure .r.today 3 -weight 1

# Separator - wir nutzen Frame (da ohne TTK)
frame .r.s -relief flat -borderwidth 0 -height 2 -background $sBgDark
grid .r.s -row 1 -column 0 -columnspan 2 -sticky ew

# Canvas mit Scrollrad fuer Content
proc tracemyscrollbar { args } {
	# Hint: "args" kann nicht umbenannt werden...
	.r.scrollbar set [ lindex $args 0 ] [ lindex $args 1 ]
	if { [ lindex $args 0 ] == "0.0" } {
		.l.topbar.scrolltop configure -state disabled
	} else {
		.l.topbar.scrolltop configure -state normal
	}
}
canvas .r.c -highlightthickness 0 -background $sBgNormal -yscrollcommand tracemyscrollbar
grid .r.c -row 2 -column 0 -sticky ewns
# ttk::scrollbar .r.scrollbar -command ".r.c yview"
#ttk::scrollbar .r.scrollbar -command [ list .r.c yview ]
ttk::scrollbar .r.scrollbar -command ".r.c yview"
grid .r.scrollbar -row 2 -column 1 -sticky ns

# Content-Resize des rechten Frames
grid rowconfigure .r 2 -weight 1
grid columnconfigure .r 0 -weight 1

# Contentframe
frame .r.c.frame -width 200 -height 200 -background $sBgNormal
.r.c create window 0 0 -anchor nw -window .r.c.frame
.r.c configure -scrollregion [ .r.c bbox all ]
bind .r.c.frame <MouseWheel> {
	if { %D > 0 } {
		.r.c yview scroll -1 units
	} elseif { %D < 0 } {
		.r.c yview scroll  1 units
	}
}
bind .r.c <MouseWheel> {
	if { %D > 0 } {
		.r.c yview scroll -1 units
	} elseif { %D < 0 } {
		.r.c yview scroll  1 units
	}
}

# Resize des linken + rechten Frames
grid rowconfigure . 0 -weight 1
grid columnconfigure . 2 -weight 1

# System Tray
proc systray_left {} {
	global bOnceRaised
	set sWmState [ wm state . ]
	if { $sWmState == "withdrawn" || $sWmState == "iconic" } {
		.r.c yview moveto 0.0
		wm deiconify .
		wm attributes . -topmost
		raise .
		update
		set bOnceRaised true
	} else {
		wm withdraw .
		update
	}
}
proc systray_right {} {
	global bOnceRaised
	.r.c yview moveto 0.0
	wm deiconify .
	wm attributes . -topmost
	raise .
	update
	set bOnceRaised true
}
tk systray create -image $gLogo24 -text $sProgName -button1 systray_left -button3 systray_right
update
if { [ winfo exists ._tray._tray ] } {
	wm title ._tray._tray $sProgName
}

# Tray Bug...
bind ._tray <Enter> systray_left

################################################################################
# Ueber + Lizenz
################################################################################

proc window_about {} {
	global sProgName gLogo32 gExit sVersion sProgYear sDescLong sAuthor sEmail sUrl lLanguages
	if { [ winfo exists .info ] } {
		raise .info
	} else {

		# Fenster initalisieren
		toplevel     .info
		wm title     .info "${sProgName}: [::msgcat::mc {About}] & [::msgcat::mc {License}]"
		wm resizable .info false false
		wm transient .info .
		focus        .info

		bind .info <Escape> {
			destroy .info
		}

		# wir wollen TTK als Hintergrund
		ttk::frame .info.f -padding {10 0}
		pack .info.f -side top -fill both -expand true

		# Kopf
		ttk::label .info.f.title -text " [::msgcat::mc {About}] & [::msgcat::mc {License}]" -font MyFontBigBold -image $gLogo32 -compound left
		pack .info.f.title -side top -fill x -pady 10

		# Tabs
		ttk::notebook .info.f.tabs
		pack .info.f.tabs -side top -fill x -expand true
		.info.f.tabs add [ ttk::frame .info.f.tabs.about   -padding 5 ] -text [::msgcat::mc {About}]
		.info.f.tabs add [ ttk::frame .info.f.tabs.license -padding 5 ] -text [::msgcat::mc {License}]
		ttk::notebook::enableTraversal .info.f.tabs

		# Ueber
		text .info.f.tabs.about.text -wrap word -width 55 -height 15 -padx 5 -pady 5 -yscrollcommand { .info.f.tabs.about.scroll set }
		ttk::scrollbar .info.f.tabs.about.scroll -command { .info.f.tabs.about.text yview }
		pack .info.f.tabs.about.text   -side left -fill both -expand true
		pack .info.f.tabs.about.scroll -side left -fill y
		.info.f.tabs.about.text tag configure ThisFontBold -font MyFontBold
		.info.f.tabs.about.text tag configure ThisFont  -font TkDefaultFont
		.info.f.tabs.about.text insert 0.0 "[::msgcat::mc {Version}] ${sVersion}, ${sProgYear}" ThisFontBold
		.info.f.tabs.about.text insert end "\n" TkDefaultFont
		.info.f.tabs.about.text insert end "\n[::msgcat::mc ${sDescLong} ]" ThisFont
		.info.f.tabs.about.text insert end "\n" TkDefaultFont
		.info.f.tabs.about.text insert end "\n[::msgcat::mc {Author}]" ThisFontBold
		.info.f.tabs.about.text insert end "\n  ${sAuthor}" ThisFont
		.info.f.tabs.about.text insert end "\n  ${sEmail}" ThisFont
		.info.f.tabs.about.text insert end "\n  ${sUrl}" ThisFont
		.info.f.tabs.about.text insert end "\n" TkDefaultFont
		.info.f.tabs.about.text insert end "\n[::msgcat::mc {Available Languages} ]" ThisFontBold
		foreach sLang [ lsort -dictionary -index 1 $lLanguages ] {
			.info.f.tabs.about.text insert end "\n  \u2022 [ lindex ${sLang} 0 ] ([ lindex ${sLang} 2 ])" ThisFont
		}
		.info.f.tabs.about.text insert end "\n" ThisFont
		.info.f.tabs.about.text configure -state disabled

		# Lizenz
		text .info.f.tabs.license.text -wrap word -width 55 -height 15 -padx 5 -pady 5 -yscrollcommand { .info.f.tabs.license.scroll set }
		ttk::scrollbar .info.f.tabs.license.scroll -command { .info.f.tabs.license.text yview }
		pack .info.f.tabs.license.text   -side left -fill both -expand true
		pack .info.f.tabs.license.scroll -side left -fill y
		.info.f.tabs.license.text tag configure ThisFontBold -font MyFontBold
		.info.f.tabs.license.text tag configure ThisFont  -font TkDefaultFont
		.info.f.tabs.license.text insert 0.0 "[::msgcat::mc {License}]: BSD 2-clause" ThisFontBold
		.info.f.tabs.license.text insert end "\n\nCopyright \u00a9 ${sProgYear} ${sAuthor} <${sEmail}>." ThisFont
		.info.f.tabs.license.text insert end "\nAll rights reserved." ThisFont
		.info.f.tabs.license.text insert end "\n" ThisFont
		.info.f.tabs.license.text insert end "\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:" ThisFont
		.info.f.tabs.license.text insert end "\n" ThisFont
		.info.f.tabs.license.text insert end "\n \u2022 Redistributions of source code must retain the above" ThisFont
		.info.f.tabs.license.text insert end "\n   copyright notice, this list of conditions and the" ThisFont
		.info.f.tabs.license.text insert end "\n   following disclaimer." ThisFont
		.info.f.tabs.license.text insert end "\n" ThisFont
		.info.f.tabs.license.text insert end "\n \u2022 Redistributions in binary form must reproduce the" ThisFont
		.info.f.tabs.license.text insert end "\n   above copyright notice, this list of conditions and" ThisFont
		.info.f.tabs.license.text insert end "\n   the following disclaimer in the documentation and/or" ThisFont
		.info.f.tabs.license.text insert end "\n   other materials provided with the distribution." ThisFont
		.info.f.tabs.license.text insert end "\n" ThisFont
		.info.f.tabs.license.text insert end "\nTHIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ThisFont
		.info.f.tabs.license.text insert end "\n" ThisFont
		.info.f.tabs.license.text configure -state disabled

		# Cancel
		ttk::button .info.f.close -text [::msgcat::mc {Close}] -image $gExit -compound left -command { destroy .info }
		pack .info.f.close -side top -fill x -expand true -pady 10

	}
}

################################################################################
# Config
################################################################################

proc window_settings {} {
	global sProgName gLogo32 lConfigFileVars sConfigFile lLanguages sSelfExec gSave gExit lIcals iIcals

	if { [ winfo exists .conf ] } {
		raise .conf
	} else {

		# globale Variablen nachholen
		set lConfig [ list ]
		foreach sConfig $lConfigFileVars {
			global $sConfig
			lappend lConfig [ list $sConfig [ expr $$sConfig ] ]
		}

		# Liste der Schriften wird mehrfach benoetigt, daher einmal holen
		set lAllFonts [ lsort -unique -dictionary [ font families ] ]

		##############################################################################
		# Fenster initalisieren
		toplevel     .conf
		wm title     .conf "${sProgName}: [::msgcat::mc {Settings}]"
		wm resizable .conf true false
		wm transient .conf .
		focus        .conf

		bind .conf <Escape> {
			destroy .conf
		}

		# wir wollen TTK als Hintergrund
		ttk::frame .conf.f -padding {10 0}
		pack .conf.f -side top -fill both -expand true

		# Kopf
		ttk::label .conf.f.title -text " [::msgcat::mc {Settings}]" -font MyFontBigBold -image $gLogo32 -compound left
		pack .conf.f.title -side top -fill x -pady 10

		# Tabs
		ttk::notebook .conf.f.tabs
		pack .conf.f.tabs -side top -fill x -expand true
		.conf.f.tabs add [ ttk::frame .conf.f.tabs.prog -padding {10 0} ] -text [::msgcat::mc {General}]
		.conf.f.tabs add [ ttk::frame .conf.f.tabs.cal  -padding {10 0} ] -text [::msgcat::mc {Calendars}]
		ttk::notebook::enableTraversal .conf.f.tabs

		##############################################################################
		# Tab: Allgemein
		set iRow 0

		# Sprache
		ttk::label      .conf.f.tabs.prog.lang_text -text [::msgcat::mc {Language}]
		ttk::menubutton .conf.f.tabs.prog.lang_edit -menu .conf.f.tabs.prog.lang_edit.menu -text [ lindex [ lindex $lLanguages [ lsearch -exact -index 1 $lLanguages $sLanguage ] ] 0 ]
		menu .conf.f.tabs.prog.lang_edit.menu -tearoff 0 -font TkDefaultFont
		foreach lTmp [ lsort -dictionary $lLanguages ] {
			.conf.f.tabs.prog.lang_edit.menu add radiobutton -label [ lindex $lTmp 0 ] -value [ lindex $lTmp 1 ] -variable sLanguage -command ".conf.f.tabs.prog.lang_edit configure -text [ lindex ${lTmp} 0 ]"
		}
		grid .conf.f.tabs.prog.lang_text -row $iRow -column 0 -sticky e -pady 10 -padx {0 10}
		grid .conf.f.tabs.prog.lang_edit -row $iRow -column 1 -sticky w -pady 10 -columnspan 2
		incr iRow

		# Separator
		ttk::separator .conf.f.tabs.prog.sep${iRow} -orient horizontal
		grid .conf.f.tabs.prog.sep${iRow} -row $iRow -column 0 -columnspan 3 -sticky ew -pady {0 10}
		incr iRow

		# Tray-Modus
		ttk::label       .conf.f.tabs.prog.traymodus_text  -text [::msgcat::mc {Tray Mode}]
		ttk::checkbutton .conf.f.tabs.prog.traymodus_check -text [::msgcat::mc {minimized only in the system tray (experimentally)}] -variable bTrayMode -onvalue true -offvalue false
		grid .conf.f.tabs.prog.traymodus_text  -row $iRow -column 0 -sticky e -pady {0 10} -padx {0 10}
		grid .conf.f.tabs.prog.traymodus_check -row $iRow -column 1 -sticky w -pady {0 10} -columnspan 2
		incr iRow

		# komplette / gekuerzte Liste
		ttk::label       .conf.f.tabs.prog.listtype_text  -text [::msgcat::mc {List View}]
		ttk::checkbutton .conf.f.tabs.prog.listtype_check -text [::msgcat::mc {show days without entries}] -variable bCompleteList -onvalue true -offvalue false
		grid .conf.f.tabs.prog.listtype_text  -row $iRow -column 0 -sticky e -pady {0 10} -padx {0 10}
		grid .conf.f.tabs.prog.listtype_check -row $iRow -column 1 -sticky w -pady {0 10} -columnspan 2
		incr iRow

		# Anzahl Tage
		ttk::label   .conf.f.tabs.prog.days_text -text [::msgcat::mc {Days}]
		ttk::spinbox .conf.f.tabs.prog.days_sel  -state readonly -textvariable iViewDays -from 28 -to 365 -width 4 -justify center -takefocus 0 -font TkDefaultFont
		grid .conf.f.tabs.prog.days_text -row $iRow -column 0 -sticky e -pady {0 10} -padx {0 10}
		grid .conf.f.tabs.prog.days_sel  -row $iRow -column 1 -sticky w -pady {0 10} -columnspan 2
		incr iRow

		# Separator
		ttk::separator .conf.f.tabs.prog.sep${iRow} -orient horizontal
		grid .conf.f.tabs.prog.sep${iRow} -row $iRow -column 0 -columnspan 3 -sticky ew -pady {0 10}
		incr iRow

		# Ttk Themes
		ttk::label      .conf.f.tabs.prog.theme_text -text [::msgcat::mc {Theme}]
		ttk::menubutton .conf.f.tabs.prog.theme_menu -menu .conf.f.tabs.prog.theme_menu.menu -text $sTheme
		menu .conf.f.tabs.prog.theme_menu.menu -tearoff 0 -font TkDefaultFont
		foreach sTmpTheme [ lsort -dictionary [ ttk::themes ] ] {
			.conf.f.tabs.prog.theme_menu.menu add radiobutton -label $sTmpTheme -value $sTmpTheme -variable sTheme -command ".conf.f.tabs.prog.theme_menu configure -text ${sTmpTheme}"
		}
		grid .conf.f.tabs.prog.theme_text -row $iRow -column 0 -sticky e -pady {0 10} -padx {0 10}
		grid .conf.f.tabs.prog.theme_menu -row $iRow -column 1 -sticky w -pady {0 10} -columnspan 2
		incr iRow

		# Schriftart
		ttk::label     .conf.f.tabs.prog.fontstyle_text -text [::msgcat::mc {Font Style}]
		listbox        .conf.f.tabs.prog.fontstyle_sel  -width 35 -height 9 -selectmode single -exportselection false -activestyle none -highlightthickness 0 -yscrollcommand { .conf.f.tabs.prog.fontstyle_scroll set }
		ttk::scrollbar .conf.f.tabs.prog.fontstyle_scroll -orient vertical -command { .conf.f.tabs.prog.fontstyle_sel yview }
		foreach sTmp $lAllFonts {
			.conf.f.tabs.prog.fontstyle_sel insert end $sTmp
			if { $sTmp == $sFontName } {
				.conf.f.tabs.prog.fontstyle_sel selection set end
				.conf.f.tabs.prog.fontstyle_sel see end
			}
		}
		bind .conf.f.tabs.prog.fontstyle_sel <ButtonRelease-1> { set sFontName [ %W get [ %W curselection ] ] }
		grid .conf.f.tabs.prog.fontstyle_text   -row $iRow -column 0 -sticky e    -pady {0 10} -padx {0 10}
		grid .conf.f.tabs.prog.fontstyle_sel    -row $iRow -column 1 -sticky nsew -pady {0 10}
		grid .conf.f.tabs.prog.fontstyle_scroll -row $iRow -column 2 -sticky ns   -pady {0 10}
		incr iRow

		# Schriftgroesse
		ttk::label   .conf.f.tabs.prog.fontsize_text -text [::msgcat::mc {Font Size}]
		ttk::spinbox .conf.f.tabs.prog.fontsize_sel  -state readonly -textvariable iFontSize -from 4 -to 30 -width 3 -justify center -takefocus 0 -font TkDefaultFont
		grid .conf.f.tabs.prog.fontsize_text -row $iRow -column 0 -sticky e -pady {0 10} -padx {0 10}
		grid .conf.f.tabs.prog.fontsize_sel  -row $iRow -column 1 -sticky w -pady {0 10} -columnspan 2
		incr iRow

		grid columnconfigure .conf.f.tabs.prog 1 -weight 1

		##############################################################################
		# Tab: Kalender

		ttk::label .conf.f.tabs.cal.name   -text [::msgcat::mc {Name}]
		ttk::label .conf.f.tabs.cal.color  -text [::msgcat::mc {Color}]
		ttk::label .conf.f.tabs.cal.webcal -text [::msgcat::mc {URI}]
		grid .conf.f.tabs.cal.name   -sticky w -row 0 -column 1 -pady 10 -padx 10
		grid .conf.f.tabs.cal.color  -sticky w -row 0 -column 2 -pady 10
		grid .conf.f.tabs.cal.webcal -sticky w -row 0 -column 3 -pady 10 -padx {10 0}

		for { set iRow 1 } { $iRow <= $iIcals } { incr iRow } {
			ttk::label .conf.f.tabs.cal.count${iRow}  -text "#${iRow}"
			ttk::entry .conf.f.tabs.cal.name${iRow} -width 18
			button .conf.f.tabs.cal.color${iRow} -text "   " -background "#ffffff" -relief solid -borderwidth 1 -padx 0 -pady 0 -highlightthickness 0 -command "colorselector .conf.f.tabs.cal.color${iRow} ${iRow}"
			ttk::entry .conf.f.tabs.cal.webcal${iRow} -width 50
			grid .conf.f.tabs.cal.count${iRow}  -sticky e -row $iRow -column 0 -pady {0 10}
			grid .conf.f.tabs.cal.name${iRow}   -sticky w -row $iRow -column 1 -pady {0 10} -padx 10
			grid .conf.f.tabs.cal.color${iRow}  -sticky w -row $iRow -column 2 -pady {0 10} -sticky wens
			grid .conf.f.tabs.cal.webcal${iRow} -sticky w -row $iRow -column 3 -pady {0 10} -padx {10 0} -sticky we

			if { [ llength $lIcals ] >= $iRow } {
				set iIndex [ expr $iRow - 1 ]
				set lTmp [ lindex $lIcals $iIndex ]
				.conf.f.tabs.cal.name${iRow}   insert 0 [ lindex $lTmp 0 ]
				.conf.f.tabs.cal.color${iRow}  configure -background "#[ lindex $lTmp 2 ]"
				.conf.f.tabs.cal.webcal${iRow} insert 0 [ lindex $lTmp 1 ]
			}

		}

		grid columnconfigure .conf.f.tabs.cal 3 -weight 1

		##############################################################################
		# Frame: Save + Cancel
		ttk::frame .conf.f.close
		pack .conf.f.close -side top -fill x -expand true -pady 10

		# Save
		ttk::button .conf.f.close.save -text [::msgcat::mc {Save}] -image $gSave -compound left -command {
			writeicals
			writeconfig
			destroy .conf
			update
			if { [ tk_messageBox -parent . -type okcancel -icon info -title $sProgName -message [::msgcat::mc {Restart recommended}] -detail "[::msgcat::mc {Some settings are only applied after a restart.}]\n\n[::msgcat::mc {Exit program?}]" ] == "ok" } {
				exit
			}
		}
		pack .conf.f.close.save -side left -fill x -expand true -padx {0 10}

		# Cancel
		ttk::button .conf.f.close.cancel -image $gExit -command { destroy .conf }
		pack .conf.f.close.cancel -side left -fill y

	}
}

# FILE: 08_action.tcl ########################################################################################

# initial Liste aufbauen und ausgeben
update
readcalendars $lIcals false
viewlist true true
notifyoldlists

# Uhr aktuell halten
everysecond 1 {
	.l.title configure -text " [ clock format [ clock seconds ] -format "%H:%M" ] [::msgcat::mc {o'clock}]"
}

# Info zum alter der Liste aktuell halten (jede Minute)
everysecond 60 {
	updatelistdate
}

# Info zum Listenalter ggf. alle 90 Minuten als Notify
everysecond 5400 {
	notifyoldlists
}

# Info zu heutigen Eintragungen ggf wiederholen (30 Minuten)
everysecond 1800 {
	checkentriestoday
}

# Notifications - unser Daemon ;)
everysecond 30 {
	shownotifications
}
shownotifications

# Tracings auf Config-Vars
trace add variable sTheme        write "setmyconfig sTheme;#"
trace add variable sLanguage     write "setmyconfig sLanguage;#"
trace add variable sFontName     write "setmyconfig sFontName;#"
trace add variable iFontSize     write "setmyconfig iFontSize;#"
trace add variable iViewDays     write "mkupdate false;#"
trace add variable bCompleteList write "mkupdate false;#"

################################################################################
# Bindings
################################################################################

bind all <Control-q> {
	wm withdraw .
	update
}
bind . <Escape> {
	wm withdraw .
	update
}

