""" ======================================================================== COMMON NAMES (part of Frigcal) Global names, imported or defined once here and shared by all modules ======================================================================== """ #======================================================================= # IMPORTS #======================================================================= # Python import sys, os import platform, webbrowser, json, pickle, glob, time, datetime import re, threading, queue, fnmatch, traceback, shutil, math osjoin, osexists = os.path.join, os.path.exists # Require Python 3.X+ assert(int(sys.version[0]) >= 3) # Platform RunningOnAndroid = hasattr(sys, 'getandroidapilevel') # py 3.7+ (or newer sys.platform) RunningOnMacOS = sys.platform.startswith('darwin') # intel and apple m (rosetta?) RunningOnWindows = sys.platform.startswith('win') # Windows py, may be run by Cygwin RunningOnLinux = sys.platform.startswith('linux') # native, Windows WSL, Android RunningOnLinux = RunningOnLinux and not RunningOnAndroid # Linux ONLY, else Android too py3.8 wsl2sig = 'microsoft-standard-WSL2' # Windows WSL2 Linux ONLY RunningOnWSL2 = RunningOnLinux and platform.uname().release.endswith(wsl2sig) RunningOnCygwin = sys.platform.startswith('cygwin') # Cygwin's own py, run on Windows # Run early tweaks on PCs now, before Kivy imports import prestart_pc_tweaks # Kivy: GUI core (some may be unused) import kivy.metrics from kivy.lang import Builder from kivymd.app import MDApp from kivy.clock import mainthread, Clock from kivy.metrics import dp, sp from kivy.properties import StringProperty, ListProperty, NumericProperty from kivy.properties import ObjectProperty, OptionProperty from kivy.config import Config from kivy.core.window import Window # instance is app.root_window from kivy.graphics import Color, Rectangle, Line from kivy.effects.scroll import ScrollEffect from kivy.animation import Animation from kivy.factory import Factory # for classes defined in .kv from kivy.utils import colormap, get_color_from_hex from kivy.uix.popup import Popup from kivy.uix.boxlayout import BoxLayout from kivy.uix.gridlayout import GridLayout from kivy.uix.floatlayout import FloatLayout from kivy.uix.checkbox import CheckBox from kivy.uix.textinput import TextInput from kivy.uix.label import Label from kivy.uix.filechooser import FileSystemLocal # else hangware (in PPUS) from kivy.uix.behaviors import ButtonBehavior from kivy.uix.scrollview import ScrollView from kivy.uix.widget import Widget # KivyMD: GUI (nav drawer, screens, hamburger menu, etc., some may be unused) from kivymd.uix.screen import MDScreen from kivymd.uix.textfield import MDTextField from kivymd.uix.label import MDLabel from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.anchorlayout import MDAnchorLayout from kivymd.uix.gridlayout import MDGridLayout from kivymd.uix.behaviors.backgroundcolor_behavior import BackgroundColorBehavior from kivymd.uix.scrollview import MDScrollView from kivymd.uix.navigationdrawer import MDNavigationDrawer from kivymd.uix.list import MDList from kivymd.uix.list import OneLineListItem from kivymd.uix.filemanager import MDFileManager # or Kivy chooser, plyer, tkinter? from kivymd.uix.pickers import MDDatePicker from kivymd.uix.menu import MDDropdownMenu from kivymd.app import MDApp # pyjnius: Android Java interface if RunningOnAndroid: from jnius import autoclass, cast # python-for-android (p4a): helpers if RunningOnAndroid: import android.storage from android.runnable import run_on_ui_thread from android.config import ACTIVITY_CLASS_NAME #from android.storage import app_storage_path #from android.storage import primary_external_storage_path #from android.broadcast import BroadcastReceiver else: run_on_ui_thread = lambda func: func # no-op decorator for PCs #======================================================================= # GLOBALS #======================================================================= # logistics APPNAME = 'Frigcal' # used for assorted folder names, etc. VERSION = '4.0.0' # versionName in manifest, inserted in About PUBDATE = 'January 2026' # inserted in About by on_start() too # trace iff false RELEASE_VERSION = False # set to True for Android release builds: don't trace # font size before user mods (or 0 to skip settings) # kivy label+text default=15sp, scaled for device+user per kivy.metrics # _linuxinc was dropped: +3 seems too large on Linux (was +5 in PPUS's older Kivy) # caveat: Windows/WSL2 settings should vary but won't if use same source-code _linuxinc = 0 if RunningOnLinux else 0 FONT_SIZE_DEFAULT = int(sp(15 + _linuxinc)) # font family before user mods (or '' to skip settings) # kivy default=Robot-Regular.ttf per ~/.kivy/config.ini; see also settingsgui.py picks # now unused: see common.kv for cut's rationale FONT_NAME_DEFAULT = 'Roboto' # or other kivy builtins: DejaVuSans, Roboto-Mono, etc. # app private+transient resources # on Android: in app-install (not app-private) folder, no user access # on Windows and Linux: in '.' for source code, Pyinstaller temp unzip for exes # on macOS: in '.' for source code, .app/Contents/Resources (and MacOS links) for app HELP_FILE = 'help-message.txt' ABOUT_FILE = 'about-template.txt' TERMS_OF_USE_FILE = 'terms-of-use.txt' # app public+persistent resources # on Android: in app-private (not app-install) folder, backed up, no user access # on Windows and Linux: in '.' for source code, install/unzip (exe) folder for exes # on macOS: in ~/Library/Frigcal for source code and app SETTINGS_FILE = 'settings.json' # was .pkl RUNCOUNT_FILE = 'runcounter.txt' # calendar files (user-selected folder) ICS_FILE_EXTENSION = '.ics' CALENDARS_FOLDER_DEFAULT_NAME = APPNAME BACKUPS_SUBDIR_NAME = '_Backups' # Folders - run counts + config settings: see storage_path_app_private() # Folders - suggested calendars default: see default_calendar_folder_path() #======================================================================= # GLOBAL UTILITIES #======================================================================= def trace(*args, **kargs): """ ----------------------------------------------------------- Don't print trace messages in release builds for Android. That means there's nothing to go on for errors, but most users won't be able to run logcat anyhow, and Google Play chides about trace messages (and is douchefully dogmatic). ----------------------------------------------------------- """ if RELEASE_VERSION: return # if not THREAD_RUNNING: # moot in this app? print('(FRIGCAL)', *args, **kargs, flush=True) def traces(*args, **kargs): for arg in args: trace(arg, **kargs) # '\n' doesn't work in logcat class PopupStack: """ ----------------------------------------------------------- A simple stack for popup dialogs that may nest. Kivy coding structure makes it easiest to save Popups for later closes, but Popups may nest here (e.g., an info_message while a folderchooser is open, though this may have been impossible in earlier code). UPDATE: Button double-tap nonsense: 1) Pops of _empty_ stack triggered excs and app aborts when dialog close buttons were double tapped on all platforms. This specific issue is fixed by checking for empty here, but double taps create other issues. 2) For one, double taps can close _two_ dialogs stacked atop each other at once. To avoid this, all dismiss calls now pass the Popup's content, and dismiss here skips the call if passed contents do not match that of the stacked Popup. This avoids closing overlaid Popup on double taps. 3) Now also uses auto_dismiss=False on all Popup() so Popup not dismissed without a pop() here. Else, bogus auto-dismissed entries remain stacked and are dismissed on later dbl taps (though this seems to be harmless). Could keep auto-dismiss by catching on_dismiss and doing pop() in catcher, but taps outside multi-button popups are ambiguous and seem error-prone in practice anyhow. More generally, a double tap on _any_ button may trigger its callback twice... This seems a Kivy misfeature (bug!), but hasn't been an issue in testing. If can only matter if event is fired because the widget still active; the Popup case is probably timing - Button event beats the dismiss() event. Alt: disable Button double-tap instead? Later: PPUS also had to handle double taps of Yes in action-confirm dialogs, else crashed or ran action*2. Multiple taps are a Kivy issue but may be touch related. ----------------------------------------------------------- """ def __init__(self): self.popups = [] def push(self, popup): self.popups.append(popup) # stack Popup object def empty(self): return not self.popups def pop(self): if not self.empty(): return self.popups.pop(-1) # remove+return top def top(self): if not self.empty(): # avoid dbl-tap excs return self.popups[-1] def open(self): if not self.empty(): self.top().open() # display topmost Popup def dismiss(self, content): if not self.empty(): if self.top().content == content: # avoid dbl-tap closes self.pop().dismiss() # close topmost Popup # One self.popupStack instance is created in App # See also: App().dismiss_popup(popupcontent)