commit ad5773abfc53b129c1c36045fbe2b601301d055d Author: Thomas Vogl Date: Sat Dec 17 00:35:20 2022 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbfa7d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/*.pyc diff --git a/appdirs.py b/appdirs.py new file mode 100644 index 0000000..975e9bf --- /dev/null +++ b/appdirs.py @@ -0,0 +1,616 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version__ = "1.4.4" +__version_info__ = tuple(int(segment) for segment in __version__.split(".")) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: ~/Library/Preferences/ + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system == "win32": + path = user_data_dir(appname, appauthor, None, roaming) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Preferences/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == 'win32': + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + elif system == 'darwin': + path = os.path.expanduser('/Library/Preferences') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + import win32com.shell + _get_win_folder = _get_win_folder_with_pywin32 + except ImportError: + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/calendar_main.py b/calendar_main.py new file mode 100644 index 0000000..d9519a5 --- /dev/null +++ b/calendar_main.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import os + + +try: + import scribus +except ImportError: + print("This Python script is written for the Scribus scripting interface.") + print("It can only be run from within Scribus.") + sys.exit(1) + +from scribus_calendar.config_parser import ConfigParser +from scribus_calendar.calendars import MonthlyCalendar +from scribus_calendar.monthly_objects import * + + +def class_from_name(className): + return globals()[className] + + +def main(argv): + objects = [] + + #compute config file path + thisDir = os.path.dirname(argv[0]) + defaultConfigFile = os.path.join(thisDir, "test.json") + configParser = ConfigParser(defaultConfigFile) + + + #create objects from config file + strListModuleObjects = configParser.modules.keys() + for module in strListModuleObjects: + obj = class_from_name(module)() + obj.configure(configParser) + objects.append(obj) + + #create calendar object from defined class in config + calendar = class_from_name(configParser.settings["calendarClass"])(objects, configParser.settings["year"]) + + calendar.createStyles() + calendar.createLayers() + + if calendar.plotStubs(): + scribus.messageBox('created objects','created objects. Please adjust its position and re-run script.' ,scribus.ICON_NONE, scribus.BUTTON_OK) + return + + calendar.readFromStubs() + calendar.plotObjects() + + + + +def main_wrapper(argv): + """The main_wrapper() function disables redrawing, sets a sensible generic + status bar message, and optionally sets up the progress bar. It then runs + the main() function. Once everything finishes it cleans up after the main() + function, making sure everything is sane before the script terminates.""" + try: + scribus.statusMessage("Running script...") + scribus.progressReset() + main(argv) + finally: + # Exit neatly even if the script terminated with an exception, + # so we leave the progress bar and status bar blank and make sure + # drawing is enabled. + if scribus.haveDoc(): + scribus.setRedraw(True) + scribus.statusMessage("") + scribus.progressReset() + +# This code detects if the script is being run as a script, or imported as a module. +# It only runs main() if being run as a script. This permits you to import your script +# and control it manually for debugging. +if __name__ == '__main__': + main_wrapper(sys.argv) \ No newline at end of file diff --git a/scribus_calendar/__init__.py b/scribus_calendar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scribus_calendar/base_objects.py b/scribus_calendar/base_objects.py new file mode 100644 index 0000000..999a9a4 --- /dev/null +++ b/scribus_calendar/base_objects.py @@ -0,0 +1,167 @@ + +import scribus +import datetime +import calendar + +class ScribusCalendar(object): + def __init__(self, objects, year): + self.objects = objects + self.year = year + + + def createStyles(self): + existing_styles = scribus.getAllStyles() + for obj in self.objects: + obj.createStyles(existing_styles) + + + def createLayers(self): + existingLayers = scribus.getLayers() + for obj in self.objects: + obj.createLayer(existingLayers) + + def plotStubs(self): + created = False + + for obj in self.objects: + created = obj.plotStub() or created + + return created + + def plotObjects(self): + pass + + def readFromStubs(self): + for obj in self.objects: + obj.readFromStub() + + +class CalendarObject(object): + def __init__(self, name): + self.name = name + + self.position = (0,0) + self.size = (100,100) + + self.paragraphStyle = "DefaultCalenderParagraphStyle" + self.charStyle = "DefaultCalenderCharStyle" + + self.layer = "CalendarLayer" + + self.date = datetime.date.today() + + def setDate(self, date): + self.date = date + + def setDimension(self, pos, size): + self.position = pos + self.size = size + + def createLayer(self, existingLayers): + if self.layer not in existingLayers: + scribus.createLayer(self.layer) + existingLayers.append(self.layer) + + def createStyles(self, existingStyles): + if self.charStyle not in existingStyles: + scribus.createCharStyle(self.charStyle, "DejaVu Sans", 8) + existingStyles.append(self.charStyle) + + if self.paragraphStyle not in existingStyles: + scribus.createParagraphStyle(self.paragraphStyle ,1,0,0,0,0,0,0,0,0,0,0,self.charStyle) + existingStyles.append(self.paragraphStyle) + + def plotStub(self): + if not scribus.objectExists(self.name): + scribus.createText(self.position[0],self.position[1], self.size[0],self.size[1], self.name) + scribus.insertText(self.name, 0, self.name) + scribus.sentToLayer(self.layer, self.name) + return True + return False + + def readFromStub(self): + p = scribus.getPosition(self.name) + s = scribus.getSize(self.name) + + self.setDimension(p, s) + + def plotObject(self): + pass + + + def configure(self, configParser): + className = self.__class__.__name__ + settings = dict() + + try: + settings = configParser.modules[className] + except KeyError: + return + + for key, val in settings.items(): + setattr(self,key, val) + + + + +class WeekDayNameObject(CalendarObject): + def __init__(self, name): + CalendarObject.__init__(self,name) + self.weekday = datetime.date.today().weekday() + + def getName(self): + return calendar.day_name[self.date.weekday()] + + def getNameAbbrev(self): + return calendar.day_abbr[self.date.weekday()] + +class WeekNameObject(CalendarObject): + def __init__(self, name): + CalendarObject.__init__(self,name) + self.weeknumber = 1 + + def setDate(self, date): + CalendarObject.setDate(self, date) + self.weeknumber = self.date.isocalendar()[1] + + def getName(self): + return str(self.weeknumber) + + + def getNameAbbrev(self): + return self.getName() + + + + +class DailyCalendarObject(CalendarObject): + def __init__(self, name): + CalendarObject.__init__(self,name) + self.isActive = True + + def getName(self): + return str(self.date.day) + + def setActive(self, state=True): + self.isActive = state + + +class MonthlyCalendarObject(CalendarObject): + def __init__(self, name): + CalendarObject.__init__(self,name) + + def setDate(self, date): + CalendarObject.setDate(self, date) + self.month = self.date.month + + + def getName(self): + return calendar.month_name[self.month] + + def getNameAbbrev(self): + return calendar.month_abbr[self.month] + + + + + diff --git a/scribus_calendar/calendar_helpers.py b/scribus_calendar/calendar_helpers.py new file mode 100644 index 0000000..53a79df --- /dev/null +++ b/scribus_calendar/calendar_helpers.py @@ -0,0 +1,51 @@ +import datetime +import calendar + +class MonthCalendarMatrix(object): + def __init__(self, year, month, weeksPerRow, fixedRows=True): + self.weeksPerRow = weeksPerRow + self.firstDayInMonth = datetime.date(year,month,1) + maxDay = calendar.monthrange(self.firstDayInMonth.year, self.firstDayInMonth.month)[1] + self.lastDayInMonth = datetime.date(self.firstDayInMonth.year, self.firstDayInMonth.month, maxDay) + + self.firstDayInMonth = self.firstDayInMonth - datetime.timedelta(days=self.firstDayInMonth.weekday()) + self.lastDayInMonth = self.lastDayInMonth + datetime.timedelta(days=(6-self.lastDayInMonth.weekday())) + + self.numDays = (self.lastDayInMonth - self.firstDayInMonth + datetime.timedelta(days=1)).days + self.numRows = int(self.numDays / ( 7 * self.weeksPerRow )) + + if fixedRows: + if ( self.numRows < ( 6 / self.weeksPerRow) ): + self.firstDayInMonth = self.firstDayInMonth - datetime.timedelta(days=7) + self.numDays = (self.lastDayInMonth - self.firstDayInMonth + datetime.timedelta(days=1)).days + self.numRows = int(self.numDays / ( 7 * self.weeksPerRow )) + + if ( self.numRows < ( 6 / self.weeksPerRow) ): + self.lastDayInMonth = self.lastDayInMonth + datetime.timedelta(days=7) + self.numDays = (self.lastDayInMonth - self.firstDayInMonth + datetime.timedelta(days=1)).days + self.numRows = int(self.numDays / ( 7 * self.weeksPerRow )) + + + + def get(self): + out = [] + tmp = [] + num_rows = 0 + for i in range(0, self.numDays): + tmp.append(self.firstDayInMonth + datetime.timedelta(days=i)) + if i % ( 7 * self.weeksPerRow ) == ( 7 * self.weeksPerRow ) - 1: + out.append(tmp) + tmp = [] + + return out + + + + + + +if __name__ == "__main__": + x = MonthCalendarMatrix(2020,1,1,True) + for i in x.get(): + print(len(i)) + diff --git a/scribus_calendar/calendars.py b/scribus_calendar/calendars.py new file mode 100644 index 0000000..ae646c1 --- /dev/null +++ b/scribus_calendar/calendars.py @@ -0,0 +1,23 @@ +import scribus +import datetime +from .base_objects import ScribusCalendar + +class MonthlyCalendar(ScribusCalendar): + def __init__(self, objects, year): + ScribusCalendar.__init__(self, objects, year) + + + def plotObjects(self): + numPages = scribus.pageCount() + if numPages > 1: + for i in range(1, numPages): + scribus.deletePage(i) + + + for i in range(1,2): + scribus.newPage(-1) + for obj in self.objects: + obj.setDate(datetime.date(self.year, i, 1)) + obj.plotObject() + + diff --git a/scribus_calendar/config_parser.py b/scribus_calendar/config_parser.py new file mode 100644 index 0000000..8aa6468 --- /dev/null +++ b/scribus_calendar/config_parser.py @@ -0,0 +1,82 @@ + +import json +import datetime +class ConfigParser(object): + def __init__(self, filename): + with open(filename, "r") as f: + self.jsonContent = json.load(f) + self.events = dict() + self.settings = dict() + + self.__parseSettings() + self.__parseEvents() + self.__parseModuleSettings() + + def __parseModuleSettings(self): + self.modules = self.jsonContent["modules"] + + def __parseSettings(self): + self.settings = self.jsonContent["settings"] + + def __parseDateRange(self, val ): + _from = self.__fromisoformat(val["from"]) + _to = self.__fromisoformat(val["to"]) + + val["from"] = _from + val["to"] = _to + + def __fromisoformat(self, val): + d = val.split("-") + if len(d) == 3: + return datetime.date(year=int(d[0]), month=int(d[1]), day=int(d[2])) + elif len(d) == 2: + return datetime.date(year=self.settings["year"], month=int(d[0]), day=int(d[1])) + + + def __parseEvents(self): + self.events = self.jsonContent["events"] + for key, val in self.events.items(): + dates = val["dates"] + for ev in dates: + self.__parseDateRange(ev) + + def printEvents(self): + for key, val in self.events.items(): + dates = val["dates"] + print("%s\n================" % (val["name"]) ) + for ev in dates: + print("from: %s until: %s ---> %s" % (ev["from"], ev["to"], ev["name"]) ) + + + def getEvent(self, date, eventName=None): + for key,val in self.events.items(): + if eventName: + if key == eventName: + for d in val["dates"]: + _from = d["from"] + _to = d["to"] + if date >= _from and date <= _to: + return (key, d) + else: + for d in val["dates"]: + _from = d["from"] + _to = d["to"] + if date >= _from and date <= _to: + return (key, d) + + return None + + +if __name__ == "__main__": + c = ConfigParser("test.json") + startDate = datetime.date(2020,1,1) + for i in range(0,366): + d = startDate + datetime.timedelta(days=i) + ev = c.getEvent(d) + if ev: print(d, ev[0], ev[1]["name"]) + + + + + + \ No newline at end of file diff --git a/scribus_calendar/monthly_objects.py b/scribus_calendar/monthly_objects.py new file mode 100644 index 0000000..958a73d --- /dev/null +++ b/scribus_calendar/monthly_objects.py @@ -0,0 +1,172 @@ +from .base_objects import MonthlyCalendarObject, WeekDayNameObject, DailyCalendarObject, WeekNameObject +from .calendar_helpers import MonthCalendarMatrix +import calendar +import math +import copy +import datetime +import scribus +import json + +def class_from_name(className): + return globals()[className] + +class SimpleWeekDayNameObject(WeekDayNameObject): + def __init__(self): + WeekDayNameObject.__init__(self, "SimpleWeekDayNameObject") + + def plotObject(self): + textBox = scribus.createText(self.position[0],self.position[1], self.size[0],self.size[1]) + scribus.insertText(self.getNameAbbrev(), 0, textBox ) + scribus.sentToLayer(self.layer, textBox) + +class SimpleWeekNameObject(WeekNameObject): + def __init__(self): + WeekNameObject.__init__(self, "SimpleWeekNameObject") + + def plotObject(self): + textBox = scribus.createText(self.position[0],self.position[1], self.size[0],self.size[1]) + scribus.insertText(self.getNameAbbrev(), 0, textBox ) + scribus.sentToLayer(self.layer, textBox) + +class FormattedDayObject(DailyCalendarObject): + def __init__(self): + DailyCalendarObject.__init__(self,"FormattedDayObject") + + self.paragraphStyle = "S" + + def plotObject(self): + if self.isActive: + textBox = scribus.createText(self.position[0],self.position[1], self.size[0],self.size[1]) + scribus.insertText(self.getName(), 0, textBox ) + scribus.sentToLayer(self.layer, textBox) + + +class DayGridObject(DailyCalendarObject): + def __init__(self): + DailyCalendarObject.__init__(self,"DayGridObject") + + def plotObject(self): + if self.isActive: + textBox = scribus.createText(self.position[0],self.position[1], self.size[0],self.size[1]) + scribus.insertText(self.getName(), 0, textBox ) + scribus.sentToLayer(self.layer, textBox) + + +class MonthlyGrid(MonthlyCalendarObject): + def __init__(self): + MonthlyCalendarObject.__init__(self, "MonthlyGrid") + self.numberOfWeeksPerLine = 1 + self.oneLineOnly = False + self.heightColWeekNumber = 0.3 + self.widthRowWeekName = 0.3 + self.sizeCellDay = (1.0, 1.0) + + self.scaleFactor = [1,1] + + self.calendarWeekClass = None + self.weekDayClass = None + self.dayClass = None + + + self.createdObjects = [] + self.setDate(datetime.date.today()) + + def setDimension(self, pos, size): + MonthlyCalendarObject.setDimension(self,pos,size) + self.computeScaleFactor() + + def computeScaleFactor(self): + self.numCols = len(self.calendarMatrix) + unitsX = 1 * self.widthRowWeekName + len(self.calendarMatrix[0]) * self.sizeCellDay[0] + unitsY = ( 1 * self.heightColWeekNumber) + ( self.numCols * self.sizeCellDay[1] ) + + self.scaleFactor[0] = self.size[0] / unitsX + self.scaleFactor[1] = self.size[1] / unitsY + + def setDate(self, date): + MonthlyCalendarObject.setDate(self, date) + if self.oneLineOnly: + self.calendarMatrix = MonthCalendarMatrix(self.date.year, self.date.month, 1, False).get() + + t = [[]] + for i in self.calendarMatrix: + t[0] += i + self.calendarMatrix = t + self.numberOfWeeksPerLine = 1 + + else: + self.calendarMatrix = MonthCalendarMatrix(self.date.year, self.date.month, self.numberOfWeeksPerLine, True).get() + + self.computeScaleFactor() + + def plotObject(self): + self.createObjects() + + for obj in self.createdObjects: + obj.plotObject() + + def createObjects(self): + if type(self.weekDayClass) == unicode : + self.weekDayClass = class_from_name(self.weekDayClass) + + if type(self.dayClass) == unicode: + self.dayClass = class_from_name(self.dayClass) + + if type(self.calendarWeekClass) == unicode: + self.calendarWeekClass = class_from_name(self.calendarWeekClass) + + self.createdObjects = [] + w = self.position[0] + h = self.position[1] + + for i in range(0, len(self.calendarMatrix[0])): + if self.calendarMatrix[0][i].weekday() == 0: + x = self.widthRowWeekName * self.scaleFactor[0] + w += x + + weekDayObj = self.weekDayClass() + weekDayObj.setDate(self.calendarMatrix[0][i]) + self.createdObjects.append(weekDayObj) + x = self.sizeCellDay[0] * self.scaleFactor[0] + y = self.heightColWeekNumber * self.scaleFactor[1] + weekDayObj.setDimension((w,h), (x,y)) + w += x + + w = self.position[0] + h += self.heightColWeekNumber * self.scaleFactor[1] + + for i in self.calendarMatrix: + for j in range(0,len(i)): + if i[j].weekday() == 0: + calendarWeekObj = self.calendarWeekClass() + calendarWeekObj.setDate(i[j]) + x = self.widthRowWeekName * self.scaleFactor[0] + y = self.sizeCellDay[1] * self.scaleFactor[1] + calendarWeekObj.setDimension((w,h), (x, y) ) + w += x + self.createdObjects.append(calendarWeekObj) + + dailyObj = self.dayClass() + dailyObj.setDate(i[j]) + dailyObj.setActive(i[j].month == self.date.month) + + x = self.sizeCellDay[0] * self.scaleFactor[0] + y = self.sizeCellDay[1] * self.scaleFactor[1] + dailyObj.setDimension((w, h), (x, y)) + w += x + self.createdObjects.append(dailyObj) + + + h += self.sizeCellDay[1] * self.scaleFactor[1] + w = self.position[0] + + + + + + + + + + + diff --git a/test.json b/test.json new file mode 100644 index 0000000..8776a77 --- /dev/null +++ b/test.json @@ -0,0 +1,85 @@ +{ + "settings": { + "year": 2020, + "calendarClass": "MonthlyCalendar" + }, + "modules": { + "MonthlyGrid": { + "numberOfWeeksPerLine": 1, + "oneLineOnly": true, + "heightColWeekNumber": 1.0, + "widthRowWeekName": 1.0, + "sizeCellDay": [1.0, 1.0], + "calendarWeekClass": "SimpleWeekNameObject", + "weekDayClass": "SimpleWeekDayNameObject", + "dayClass": "DayGridObject" + } + }, + "events": { + "bankHolidays": { + "name": "Feiertage (Bayern)", + "format": { + "type": "style", + "charStyle": "FeiertageCharStyle", + "paragraphStyle": "FeiertageParagraphStyle" + }, + "dates": [ + { + "from": "01-01", + "to": "01-01", + "name": "Neujahr" + }, + { + "from": "10-03", + "to": "10-03", + "name": "Tag der deutschen Einheit" + }, + { + "from": "12-24", + "to": "12-24", + "name": "Heilig Abend" + }, + { + "from": "12-25", + "to": "12-25", + "name": "1. Weihnachtsfeiertag" + }, + { + "from": "12-26", + "to": "12-26", + "name": "2. Weihnachtsfeiertag" + } + ] + }, + "birthdays": { + "name": "Geburtstage", + "highlight": { + "type": "marker", + "charStyle": "FeiertageCharStyle", + "paragraphStyle": "FeiertageParagraphStyle" + }, + "dates": [ + { + "from": "2020-12-30", + "to": "2020-12-30", + "name": "Geburtstag Papa" + }, + { + "from": "2020-11-08", + "to": "2020-11-08", + "name": "Geburtstag Mama" + } + ] + }, + "schoolHolidays": { + "name": "Schulferien", + "dates": [ + { + "name": "Weihnachtsferien", + "from": "2020-12-21", + "to": "2021-01-07" + } + ] + } + } +}