frigcal — a "Refrigerator"-Style Desktop GUI Calendar

Quick Start:

Version 1.7, August 27, 2016
Summary A stand-alone desktop GUI with basic calendar functionality, using portable iCalendar event files.
Requires Python 3.X and its tkinter standard library.
Examples [Win7, Win7, Win7]  [Linux, Linux, Linux]  [Win8, Win8, Win10more...
Download http://learning-python.com/downloads/frigcal.zip
Launch To start the program, extract the zip file, and run its frigcal.py, the main script (or frigcal-noconsole.pyw on Windows).
Author M. Lutz (http://learning-python.com), © 2014-2016
License Provided freely, but with no warranties of any kind. Redistributions are welcome, but must include the package's "Readme-frigcal.html" file and "docetc" and "screenshots" folders, in full and unaltered form.


This document describes the frigcal calendar GUI program. In this document:

  1. Overview
  2. Using the Program
  3. Using Calendars
  4. Using the GUI
  5. iCalendar Implementation
  6. Release History
  7. Project Goals and Review



Overview


This section provides a quick first look at frigcal and its startup details. In this section:

 

Features

The frigcal program implements basic personal calendar functionality, in a standalone desktop GUI. Among its features:

General

GUI

This program does not implement more advanced calendaring tools such as task scheduling, journaling, alarms, event invitations, or recurring or multiday events, though some can be emulated manually (e.g., copy-and-paste for recurring and multiday events, and event description text for date-based journal entries).

In exchange for this deliberately restricted utility, this program minimizes both code and GUI complexity; reduces the risk of data loss; and provides an open and portable interface sufficient for most personal calendar needs, which does not require a network connection, account login, or proprietary system or device.

 

Screenshots

This section gives the complete set of examples of this program in action on Windows and Linux, also available in this package's screenshots folder. This folder and its subfolders also have newer thumbnail index pages displayed automatically on a server; click the "index.html" files along the way when viewing these offline.

The list and tables below itemize the salient images in the set. Many are from the prior 1.2 release, but are unchanged in 1.3 and later apart from updates captured in the second table below. The first table below gives examples of core behavior, mostly in Version 1.2. The second table's pictures were largely captured on Windows 7. As usual with GUIs, install and run live to get a better feel for the dynamic interaction absent here.

The Latest

Basic Usage

     
Windows 7 Ultrabook main | clones+images | edits | create | added | menu | close
Linux [Fedora+Gnome] main | edits | clones+close
Windows 8.1 tablet main | clones+create+images | close | portrait
Half a year, Windows 7 months | month+edits
Earlier releases version 1.1 | version 1.0

Recent Changes

     
Version 1.7 foreground colors | Unicode scene | Unicode event | encoding utility | color utility | more...
Version 1.6 image errors | image placeholder | Windows 7 | Windows 10 | Ubuntu Linux | Linux icons
Version 1.5 load errors | config errors | lib errors | pruning errors | image errors | image errors | default file
Version 1.4 cancel verify + month images | Windows launcher | backup errors | save errors
Version 1.3 event select list | right-click dialog | right-click menu | paste | create | view/edit

 

Startup

The upcoming sections cover program, calendar, and GUI usage in detail, but here's a quick summary of startup fundamentals:

To use this program, you'll perform operations in the GUI, maintain one or more calendar files, and configure appearance and behavior settings as desired. The following sections document these topics.



Using the Program


This section gives basic program usage details, covering its important files and user-visible aspects of its operation. For information on calendar files, see Using Calendars; for help with using the GUI itself, see Using the GUI (both ahead).

In this section:

 

Program Files

This program's source files reside in its zip file, available at this index page. You can view them and other package content both on your computer after extracting the zip file, and in an unzipped online copy maintained at this site.

To start the program, click or otherwise run either of the following in the top-level folder (a.k.a. directory) of your copy's unzipped content:

Windows users:
You can launch either of these scripts by simply clicking their icons. If desired, drag them out to desktop shortcuts to make them easier to start, and set their shortcuts' icon to "frigcalset.ico" in the top-level folder (right-click on the shortcut to Properties). You can also launch from command lines in frigcal's folder: "frigcal.py", "py frigcal.py", or "py -3 frigcal.py".

Linux users:
A "python3 frigcal.py" command line in frigcal's folder will likely launch the program as is, but you may wish to edit the script's initial "#!" line for your system's install paths. Some Linux systems may require an extra install for tkinter support: see ahead.

Mac users:
Testing resources are limited, but feedback is welcome. Initial reports suggest that frigcal runs correctly on your platform (after a minor Jan-2016 patch for Tk version testing), but startup pragmatics are TBD; see the Linux pointers above and the Mac tkinter link ahead.

Other user-related source files located in this program's top-level directory and covered in full later:

The first of these two is user-editable and described ahead. Because the program creates a default calendar automatically, the second of these is an optional step. The last three files listed above are described in version 1.7 release notes.

In addition, files icsfiletools.py, sharednames.py, attributeproxy.py, and scrolledlist.py are modules used only by the system itself, not its users; __sloc.py__ is used for metrics only; and other ".py" files in the docetc folder are supplemental examples not used by the frigcal program. Browse the top-level folder for more details.

A note for programmers: by code structure, the program consists of a primary module with classes that mirror main GUI windows (months and dialogs), plus a module for non-GUI calendar file access, and a set of smaller utility and configuration modules and scripts. See its code for more details.

 

Data Files

Calendar files and month image files are stored in folders, which by default are located in this program's top-level directory. These folders may be located elsewhere via configurations file path settings. Calendar and backup folders are fully described ahead, in Using Calendars; month image usage is also covered ahead in Using the GUI. In brief:

You can store arbitrary unrelated subfolders and files in any of these three folders. Specifically, the program uses only ".ics" files in the first and third of these, and only image files (of any type) in the second, and ignores any other items present. In the images folder, non-file items are skipped as of version 1.4, and non-image files as of 1.5.

 

Configurations File

To configure this program's appearance and behavior to suit your tastes and usage, edit source-code file frigcal_configs.py in the program's top-level folder.

This file supports a wide variety of user customizations—fonts, colors, window size, click modes, user directories for calendars and month images, and more. These settings, coded as Python assignments and loaded at program start-up, are described in full in the file's comments; are shipped preset to example values that demonstrate usage; and may be freely changed by users. Whenever possible, this system employs these Python-coded configurations (a.k.a. configs) instead of GUI devices, for both flexibility and code and interface simplicity.

There are more details on the configuration process in the configs file itself, but a few general usage points are worth noting here.

Edit with care:
Errors in this file don't terminate the GUI (you'll get a pop-up message with error details), and omissions in this file apply defaults and produce detailed console messages. Still, users are advised to take care with edits: import the file separately to check it for errors, and make a backup of its shipped version.

Unicode encoding and text editors:
The configs file is shipped in UTF-8 Unicode encoding form as of version 1.7, to both support and demonstrate non-English settings text. Most modern text editors—including Python's IDLE, Notepad on Windows, and gedit and vim on Linux—support this common text file format, and usually automatically. The PyEdit text editor program from book PP4E also uses UTF-8 as its default encoding. See version 1.7 release notes for background details on the encoding change.

As fallback option, in the very unlikely event that users cannot edit the config file in its shipped UTF-8 form, there is a plain ASCII equivalent in the docetc folder here. This file is identical to that in the script's main folder, sans three non-English category names which cannot be converted to narrower encodings like ASCII or Latin-1. If required for edits, copy it up to '..' and rename without its "--ascii" suffix.

You can change this file's content and encoding declaration to use Latin-1 instead, but the shipped UTF-8 version supports a much broader character range. Also note that the included unicodemod.py utility can convert the shipped UTF-8 configs file to a similarly-general encoding like UTF-16, but the file's content precludes automatic conversion to narrower schemes like ASCII.

Linux end-line conversion options:
Like all text files in the frigcal package, the configs file is shipped in Windows (a.k.a. DOS) end-line format, which may be inconvenient for some users on Linux and other Unix-like platforms. The gedit text editor on Linux handles Windows end-lines automatically, and there are a variety of well-known Linux conversion techniques, including the dos2unix shell command, and vim text-editor commands such as ":e ++ff=dos" and ":set ff=dos". Some commands may require an install, however, and editor techniques can be complex.

To address this, as of version 1.7 frigcal ships with a Python-coded end-line conversion script, fixeoln.py, which can be used to convert text files in any Unicode encoding—ASCII, UTF-8, Latin-1, UTF-16, UTF-32, and others—when no other tools are available or convenient. The configs file can be converted from Windows to Linux end-line format by this script with a simple console/shell command of this form (the "utf8" at the end of this is the optional default encoding, which works for ASCII files too):
~/Downloads/frigcal/docetc$ python fixeoln.py tounix ../frigcal_configs.py utf8
For more details, see version 1.7 release notes, the script's docstring, and its usage screenshot and test run log.

 

Portability

Because of the language used for its implementation, this program is generally as portable as Python 3.X and its standard tkinter GUI toolkit—which run on most desktop-metaphor computers. In more detail regarding platform, language, and data portability, this program:

Futurism: it's not impossible that tkinter, and hence this program, may someday also work on Android (and other) devices; see ahead for more details. This a desktop-based program, but its interaction modes support touch-enabled devices such as tablets (see Using the GUI ahead).

 

Dependencies

As shipped, this program works if you install just Python 3.X. In full detail, it relies on the following components:

  1. [Required] Python 3.X, as well as its standard library's tkinter module for its GUI.

    This program is written entirely in the Python programming language—specifically Python 3.X, where "X" means the latest in the version 3 line. If it's not already installed on your computer, Python 3.X can be fetched from https://www.python.org/downloads; for Windows, simply get and run the latest 3.X self-installing executable there.

    tkinter is included with most Pythons (and always with the python.org Windows installers), so this is not normally a separate fetch/install step. If tkinter doesn't work for you on Ubuntu Linux, try a "sudo apt-get install python3-tk tk"; Mac OS X users may wish to read this.

    To date, this program has been developed and tested under Python 3.3, 3.4, and 3.5; while compatibility is expected for future 3.X releases (and perhaps beyond the 3.X line), it naturally cannot be guaranteed. If you experience anomalies in later releases, try using 3.3 through 3.5.

  2. [Included] The 3rd-party icalendar parser/generator package, which in turn requires pytz.

    These platform-neutral third-party packages are both required to load and save calendar files. For convenience, open source versions of both are included with this program and used automatically, and need not be fetched or installed separately. If ever required, you can see and fetch these packages at:


  3. [Optional] The 3rd-party Pillow (formerly known as PIL) package.

    This image-processing package is required only if you wish to use the program's optional month Images feature for some image types. frigcal still works without Pillow; if not installed, unsupported month images are simply disabled in the GUI (and you'll get a popup message if you select them). Because Pillow's installed code may vary per platform and Python version, it must be installed separately if some image types are used. Commonly-used Pillow self-installers for Windows and Python 3.X are included and documented in frigcal's dependencies folder for convenience (click to run), but see the following if these don't apply to your platform or version, or fail on your machine:


    Update: as of frigcal version 1.6, depending on your image file types and Python version, a Pillow install may no longer be required to use the optional month images display feature. Month images now display with just Python itself if you use either PNG images and a Python that uses Tk 8.6 or later (including Python 3.4 and later with the standard Windows installer at python.org), or GIF or PPM/PPG images and any Python 3.X. For all other combinations of image type and Python release, Pillow is still required for the image display feature, and you'll still get a popup if images are enabled and a month image cannot be displayed. A new popup per image failure replaces the former popup on Images toggle-on. Using PNG images and Tk 8.6+ (which is standard in Python 3.4+ on Windows) is generally recommended, as it provides both image quality and install simplicity.

This code may also work on Python 2.X with minor changes (e.g., tkinter imports would use Tkinter), but cross-version compatibility was dropped after grappling with it on the recent mergeall archive synchronization project—an admittedly more system-intensive program, which may represent a worst case for dual-version complexity (see mergeall's main page).

Futurism: a frozen executable option could obviate some of the dependencies above—including the Python 3.X install—though source-code distributions could not, and this option has not been explored to date (see future ideas ahead).



Using Calendars


This section lists calendar file usage details. Because the system makes a new calendar for you the first time you run it, most of the following items can be skipped or skimmed if you do not have existing calendar files you wish to use, and a single default and automatic calendar suffices for your goals.

Format:
Calendar event data is stored in portable, program-neutral iCalendar ".ics" text files, using a text format defined by the iCalendar standard. This is the calendar world's equivalent to CSV or JSON files for data exchange, and uses an application-specific format similar in spirit to GEDCOM files in genealogy data. This format provides calendar data portability both to and from other calendar programs.

Additionally, frigcal both loads and saves calendar files using the common and general UTF-8 Unicode encoding scheme. This encoding handles simple ASCII text, but also allows calendar event descriptions to contain both non-English and special-character Unicode text, and is similarly portable among calendar programs—frigcal can use any existing UTF-8 iCalendar file, and creates UTF-8 iCalendar files which can be used by any other program that supports this scheme. Frigcal also provides an encoding converter, and can display any Unicode character supported by its underlying GUI library. See version 1.7 release notes for the full story on Unicode files and support.

Location:
Your calendars are stored as text data files, one per calendar, in your calendars folder. This folder is either the Calendars folder where this program resides (the default), or a folder whose path you name in frigcal_configs.py (see its setting "icspath"). For instance, a travel calendar's data is stored in a file of this form, where calendars refers to your default or configured calendar files folder:
    calendars\travel.ics
Calendar files managed by frigcal in this folder should be usable in other calendar programs that support the iCalendar standard. Conversely, frigcal can use iCalendar files created by other programs if they are placed in this folder.

Multiple Calendars:
This program supports viewing and editing multiple calendar ".ics" files. Each ".ics" file present in the calendars folder on startup is assumed to be a user calendar; the combination of all their events is displayed in the GUI. Event summaries displayed on days are sorted (i.e., ordered and grouped) by calendar filename first, and then creation date within calendar. If you use multiple calendars, you can choose calendar filenames to group day events as desired (e.g., the first filename by sort order gets precedence when screen space is limited, though the 1.3 event selection list displays all).

Save and Backup:
A calendar's data is saved to its ".ics" file only under four conditions— on program quit requests; if the data has changed; if the save is verified by the user; and after a successful and automatic backup copy is made of the file's prior version. Files are not updated after each change. Rather, for speed and accuracy, calendar data is stored and updated in memory only, and saved to files later on program quit requests. On a quit (the main window's "X" close button), a two-step dialog sequence confirms a save and then an exit, and either step can be confirmed or cancelled. Hence, you can backup and save at any time without exiting—click the main window's close button, and confirm the save dialog but cancel the exit dialog. If no data has changed, the save dialog does not appear on quit requests.

Backup Model:
The system maintains multiple backup copies of each calendar file, created when changed data is saved. Backups files show up in the automatically-created Backups subfolder of your calendars folder, with date/time name prefixes to make them unique. For instance, a backup of a travel.ics calendar is stored on saves in a file of this form:
    calendars\Backups\date150405-time142907--travel.ics
Backup files are simply copies of iCalendar text files, and can be used directly as calendars; rename to strip their date/time prefixes if desired. To change the maximum number of backups retained for each ".ics" file, set its variable in frigcal_configs.py (the current default is 10)—the system will keep up to that many most recent backup copies per file in Backups subfolder. Older backups are pruned as needed when new backups are written, and saves overwrite calendar files only if a backup succeeds.

Default Calendar:
By default, the program creates and uses one ".ics" file if none are present on initial (or later) startup. If you are starting from scratch and can group and colorize your events by category name instead of calendar file, this single, default, and automatic calendar will probably suffice. frigcal creates your default calendar in your calendars folder, at:
    calendars\frigcal-default-calendar.ics
You can remove this calendar if you add others later and don't wish to use the default (see Removing Calendars ahead). frigcal also generates a required initial event in the default calendar that looks like this, which you may also later remove.

Making New Calendars:
To make new calendars manually, run the makenewcalendar.py script to make any number of new ".ics" calendar files in your calendars folder. You can run this script from a command line, or by simply clicking its filename/icon on Windows like this. All calendars you create this way will be loaded on startup. This script respects any calendar folder settings in frigcal_configs.py; new calendars created this way are stored wherever your calendar folder is located when the script is run. frigcal generates a required initial event in new calendars that looks like this, which you may later remove.

Using Existing Calendars:
To use existing calendars, simply copy any number of existing ".ics" files to your calendars folder. All calendars you copy there will be loaded on program startup, and backed up on saves in the Backups subfolder. As noted earlier, frigcal fully supports non-English and special-character Unicode text in calendar files and displays, and uses the UTF-8 encoding to load and save calendar files. No special handling is required for existing calendar files already in UTF-8 or ASCII encoding forms. Existing calendar files not in UTF-8 format may require a one-time conversion; see version 1.7 release notes for options for using existing non-UTF-8 files, including a provided encoding-converter script.

Removing Calendars:
To remove a calendar, simply delete its ".ics" file from the calendars folder. All remaining ".ics" files in that folder will be used on the next run. Note: if you remove a calendar file from your calendars folder, frigcal will not automatically remove any backups of that calendar from the Backups subfolder. This is by design—backup files are small, may be useful, and are your property. If you're sure you'll never need them, also manually remove any backup files of a removed calendar when removing the calendar's main file.

Empty Calendars:
If you attempt to save a calendar after removing its last event, frigcal will create a required sole event in the calendar that looks like this, because empty calendar files aren't allowed. This keeps the calendar active in the next session, and you may remove the added event later. If you wish to instead remove a calendar altogether, delete its file per the prior bullet.

Using Outlook Calendars:
To view Outlook (or other calendar program) events: export calendars to ".ics" files if needed, and copy/move them to your calendars folder. Event text may be viewed and edited and all existing calendar data is retained on saves. For simplicity, though, not all Outlook functionality is supported by this program; see the Overview earlier and Why frigcal? ahead for more on program features and limitations.

Performance:
Despite its text-based storage medium, this program performs well by most measures. As an example: for a very large ".ics" file used in testing (currently 876K bytes, 35K lines, and 12 years of events), the GUI can take from 5 to 10 seconds to appear on a Windows 7 Ultrabook and Window 8.1 tablet, due to the initial parsing step. Once the file is loaded, though, this program runs very quickly thereafter, except for a generally shorter multi-second delay to generate and write the file on exit. Memory usage is also reasonable by modern standards of reasonable—with over a decade of calendar data, this program takes no more space than a typical web page viewed in a typical browser. Update: Python 3.5 now runs the initial calendar load almost twice as fast in use cases tested—see version 1.6 notes.

Handling Backup/Save Errors:
File backup and save errors should rarely (if ever) occur, and the system goes to great lengths to maintain multiple backups as a safeguard, as described above. However, if you get an error message dialog that begins with "Error while backing up..." (like this), it means that your calendar file was not overwritten in any way, as saves are cancelled on backup failure. If you instead receive an error message dialog that begins with "Error while generating..." (like this), the system failed while trying to either generate your calendar's text, or write it to a file. Your simplest recourse in both cases is to simply try again; calendar data is saved intact in memory until program exit (and changed calendars are still marked as changed after a backup or save failure), so a successful later save implies that a temporary system glitch was the error's likely cause.

If your calendars backup and save correctly on a retry, you can safely ignore a prior error. Otherwise, an error during backups warrants closing without a save and restarting, as your data file is unchanged. An error during generation instead may also be harmless if your calendar file is unmodified (per its mod time); this implies an icalendar library failure prior to opening or writing the file. In the worst case, a failure while writing to the calendar file may corrupt its data; in this unlikely event, copy the most recent version of the failed file from your Backups folder (stripping date and time from its name) up to your Calendars folder, and restart. You'll lose any updates in the failed session, but your prior calendar version's data should be intact. Repeat as needed with older versions in Backups, if the most recent is invalid. See also the new startup and pruning failure error handling in version 1.5

For more technical details on this program's usage of the iCalendar standard, see iCalendar implementation ahead.



Using the GUI


This section provides GUI usage details. It may help to refer to a screenshot or live window while reading this section. One main-window image is included inline below for reference, but there are both clickable thumbnails and links to relevant examples along the way—open these in a new window for easier viewing. Also keep in mind that this GUI was designed to be used on both touch and mouse/keyboard devices, and this influences both its layout and its action triggers. The GUI's operation, for example, includes interactions both common to and differing between selectable usage modes.

In this section:


 

Window Layout

The GUI's top level is composed of one or more month view windows, which may be opened on the same or differing months, and moved to new months and years either independently or in tandem. Month windows in turn open dialogs for event view/edit, creation, list selection, cut/paste, and so on, as well as associated images.

Month windows use a resizable display that always shows 6 weeks for consistency and clarity, with event summary text widgets positioned on days arranged in a grid, and control widgets placed around window borders. Both border controls and day-grid days, numbers, and events invoke actions when selected. An optional footer display in the lower portion of the window gives event details on hovers or clicks. Event summaries—single-line text that provides short captions—appear on days, in the footer, in event dialogs, and in the event selection list dialog. Event descriptions—multiline text large enough for full details and journal entries—appear in footers, and in event dialogs.

For ease of use on touch interfaces, there is no top-of-window menu (which takes space and can be cramped), or event drag-and-drop (which can be nearly nondeterministic on a Windows tablet). Instead, controls appear on window borders, and event cut/copy/paste is fully supported as an alternative to drag-and-drop (see cut/paste note ahead). For keyboard use, whether physical or on-screen, a set of key bindings are equivalent to date navigation buttons (see Date Navigation ahead). For more on the choice of cut/paste over drag-and-drop, see also this futurism.

Both month windows and event dialog windows may be freely resized and moved in the GUI. Month windows are non-modal (non-blocking); their initial size may be tailored by either absolute pixels or percentage of full screen size, and their initial position may be preset (see Configurations File). Event Create and Paste, View/Edit, Cut/Copy/Open, and Selection List dialog windows are all modal (blocking), and opened as needed in response to GUI actions described ahead.

 

Border Control Actions

   

The GUI operations in the table below—invoked by controls arranged around month window borders—are always available, regardless of your click mode setting in frigcal_configs.py.

        


Control Action Equivalent Key
PrevYr Move to previous year in this window, or in all windows if Tandem. Shift+Up-arrow
NextYr Move to next year in this window, or in all windows if Tandem. Shift+Down-arrow
PrevMo Move to previous month in this window, or in all windows if Tandem. Up-arrow
NextMo Move to next month in this window, or in all windows if Tandem. Down-arrow
Today Move to current date in this window only. Escape (Esc)
GoTo Move to entered mm/dd/yyyy date in this window only. Enter in text
Clone Open a new month view window. N/A
Tandem Enable/disable synchronized moves for all month windows. N/A
Images Open/close month image associated with this window's month. N/A
Footer Open/close event summary text display at bottom of this window. N/A
? Open this help document in a web-browser window. N/A
Window quit Close clone, or backup/save calendar files and/or exit program. N/A
Window minimize Minimize this month window and its image, later restored as a pair. N/A


The following list provides more details on border control actions.

Quit—Save, Exit:
The main window's top-right quit button (a.k.a. close, and rendered as an X on most platforms) is used for both kicking-off a calendar files backup/save operation, and ending the program. A quit button on a Clone popup window, described ahead in this list, silently closes its window only. A quit button in the main window (denoted by the "Main" in its title text) invokes two dialogs:

  1. The first verifies ".ics" calendar files backup and save (and is skipped if no calendars have been changed).
  2. The second verifies program exit.

Hence, to backup and save without exiting, confirm the first dialog and cancel the second; to exit without saving, cancel the first then verify the second; and to both save and exit, confirm both dialogs. See Using Calendars for more on saves and backups.

Date Navigation—Buttons and Keys:
These widgets appear at the top and bottom of the month window. They move the window's display (and its open image) to:

The keys Up-arrow/Down-arrow and Shift+Up-arrow/Shift+Down-arrow also go to previous/next month and year, respectively, but are simply redundant with navigation buttons at window bottom, and not required of touch users, whose on-screen keyboards may be incompatibly large. (Left and right arrow keys are reserved for text edits in event summary fields.) Whether invoked by buttons or keys, previous/next month and year navigations are applied to all open month windows (and their images) if Tandem is enabled, per the next bullet item.

Also as conveniences for keyboard users: an Enter key press in the GoTo date input field is the same as pressing the GoTo button—moving to the entered date; and as of version 1.5 the Escape key (a.k.a. "Esc") is the same as pressing the Today button—moving to the current date. Whether invoked by buttons or keys, both of these operations always effect the currently active window only.

Clone Button and Tandem Checkmark:
The Clone button opens a new, independent month view window, and selecting Tandem causes all open month windows (and their open images) to navigate in sync on a previous/next month or year action (only) in any open month window. The Clone option supports multiple month views. Each month window can be open on a different month—open 2 to view the current and next or prior months together; open 3 to view the current, prior, and next months at once; open 6 to see half a year if your screen is large enough.

Month windows move through months either independently or in synch. If Tandem is toggled on, all open windows respond to any previous/next month or year navigation, and thus move together. If Tandem is off, only a single window is moved when its navigation controls are used. A Tandem setting in any window is GUI-wide, and automatically propagated to all month windows. All open month windows also reflect updates made in any: if more than one are viewing the same month, all are updated immediately for any change made to their month in any window. As mentioned earlier, only a quit in the main window (denoted by the "Main" in its title text) ends the program; other windows close themselves only.

Footer Checkmark:
This toggle shows/hides the event overview-text display area near the bottom of the window. This display is filled with event date, summary, and description when the mouse cursor enters an event's summary field, or the field is single-clicked/tapped (see click mode actions ahead in this section). Its text is not erased on mouse exit, as some text requires scrolling. Its text is also not erased on month changes, as its prior content may be useful (this is configurable as of version 1.2.3). Footer text is per-window: each open month window can display a different event's data in its footer.

Because it gives more details than an event's summary widget, but less than the full View/Edit event dialog, the footer display is mostly a convenience, designed to minimize full dialog opens. As of version 1.2, the footer's text is read-only, as no changes to it would be applied; you can still copy its text to paste elsewhere (via mouse highlighting, plus Ctrl-C on Windows), but no edits are allowed.

Note that the footer's scrollbar may be difficult to reach without entering another event, and, due to month window size constraints, footer text may not be displayed at all both on small screens and in months with many events in any given day. To see more footer text, try expanding the month window, or changing the footer's font, size, and resizing options in the configurations file; or simply left-click an event to open the full event View/Edit dialog as needed.

Images Checkmark:
This toggle shows/hides an image file associated with the window's displayed month in a separate window. Month images change automatically as you navigate through months, and are automatically minimized and restored with their month windows. Like footers, images are per-window: each open month window can display a different month image in its own image popup. As noted earlier, selecting Tandem causes all open image windows to be updated simultaneously on month moves (i.e., previous/next month and year navigations) along with their month windows.

Images are loaded from the images folder, in which an image filename's relative sort order among the 12 image files present gives the image's relative month number (e.g., the first image file in the folder by ascending sort order is used for January). The image files folder's location defaults to MonthImages in the program's folder, and may be customized in the configurations file (see its setting "imgpath"). To use images of your own, either change the default MonthImages folder's content in-place, or change the configurations file to point to a different folder where your images are stored.

To enable the optional images feature you must install the Pillow extension unless you are using PNG images with a Python using Tk 8.6+ (including standard Python 3.4+ on Windows), or GIF or PPM/PGM images with any Python 3.X (see Dependencies). This feature is basic by design in this release; images are simply displayed in a separate window, synchronized with the associated month window. For speed no auto-resizing is performed, so this works best if images are all the same size, and small enough to display well in a popup window. See also Future Directions? for ideas on this topic, and version 1.4, 1.5, and 1.6 changes which make this feature more usable.

Help Button:
As of version 1.2, the "?" button at top right in month windows opens this document in a web browser.

Additional GUI actions span click modes but are invoked by clicks in the day grid, and described in the next three sections. Among these, cut and paste operations invoked by right-clicks on events and days may be used to move or copy events to other days and month windows; and the event selection list dialog opened by left-clicks on day-number areas above events is useful when a day has too many events to display. See the next three sections for action triggers, and Other GUI Actions and Usage Notes for more on cut-and-paste and event selection lists.

 

Day-Grid Actions in 'mouse' Click Mode

   

The additional event triggers and actions in the table below are defined for calendar days and events in the GUI's day-grid area, when your "clickmode" setting is 'mouse' in frigcal_configs.py. This multi-click mode is generally suitable for devices with a mouse and keyboard.

        


Trigger Action
Single left-click (tap) event Select event field for summary text edits (not updated until Enter pressed).
Enter key in event summary text Update event's possibly-edited summary text only.
Single left-click (tap) day or day-number Move the current-day shading to the selected day (other events move shading too).
Double left-click (tap) day Open Create event dialog for new event on day.
Double left-click (tap) day-number Open event selection list dialog for all events in day [see ahead for actions].
Double left-click (tap) event Open View/Edit dialog for event.
Single right-click (tap+hold) event Open Cut/Copy/Open options menu for event.
Single right-click (tap+hold) day or day-number Paste the latest cut/copy event in this day, by opening a prefilled Create event dialog.
Mouse hover-in on event Display footer text if enabled. On tablets, single-press or stylus may activate this.

 

Day-Grid Actions in 'touch' Click Mode

   

The additional event triggers and actions in the table below are defined for calendar days and events in the GUI's day-grid area, when your "clickmode" setting is 'touch' in frigcal_configs.py. This single-click mode is generally suitable for devices with only touch screens.

        


Trigger Action
N/A There is no in-line edit of displayed summary text: use left-click View/Edit dialog.
Single left-click (tap) day Open Create event dialog for new event on day.
Single left-click (tap) day-number Open event selection list dialog for all events in day [see ahead for actions].
Single left-click (tap) event Open View/Edit dialog for event, and display its footer text if enabled.
Single right-click (tap+hold) event Open Cut/Copy/Open options menu for event.
Single right-click (tap+hold) day or day-number Paste the latest cut/copy event in this day, by opening a prefilled Create event dialog.
Mouse hover-in on event Display footer text if enabled, and if you have a hover action (e.g., stylus or laptop).

 

Selection List Actions in Both Modes

   

The actions in the table below, repeated here for quick reference, are available in the event selection lists popped up in response to day-number left-click actions in the preceding two sections. This popup mimics a single, selected day in a day grid. For more details, see the next section's first item, and 1.3 notes.

        


Trigger Action
Single left-click (tap) on event list item Open View/Edit dialog for the event clicked.
Single right-click (tap+hold) on event list item Open Cut/Copy/Open options menu for the event clicked.
Create button Open Create event dialog for new event on day.

 

Other GUI Actions and Usage Notes

   

This section provides additional GUI-related interaction and usage details. Refer to the preceding sections for more details on the windows and actions described.

        

Event Selection Lists—Viewing More Events:
The physical size of day frames imposes a limit on the number of events displayed in month windows. As of version 1.3, you can view, edit, or cut/copy all events for a day—including any events off-screen—by left-clicking in the day-number area above events at the top of any day frame, to open the event selection list dialog. This list's items look and act nearly the same as events in a day frame in a month window: single-left and single-right clicks on an event in the list open View/Edit and Cut/Copy dialogs for the event, respectively, and a Create button opens the Create event dialog (like a day left-click). For more details, see this dialog's full documentation in the release notes for version 1.3, where it was introduced. The former description that follows is retained, as it contains still-valid alternatives and hints.

Prior description: The uniform size of each day's tile imposes a practical limit on the number of events that can be displayed per day (just as in a real "refrigerator" calendar, this program's metaphor). To see more events, try expanding the window, or reducing event summary and other font sizes in frigcal_configs.py. This limitation may be (has been) resolved in a future release. As is, if you really have too many events to display on a day even after expanding the window and reducing summary font sizes, the recommended workaround is to combine multiple events into one, with individual events listed in the combined event's multiline description text. A unique title and event category/color for such aggregates would help make them distinct.

Cut/Copy/Paste—Moving Events:
Events may be moved and copied to other days and months as follows: right-click on an event to cut or copy (via a pop-up actions menu); then right-click on any day or day number to paste the most recently cut/copy event on that day, via a prefilled Create dialog, which allows changes prior to pasting. The event right-click Cut/Copy menu dialog and menu appear at the spot clicked, and are captured here and here. Per the preceding note, right-clicks on event items in the event selection list dialog open the Cut/Copy menu too, just as they do on events in day frames (though pastes are invoked only on day frames).

More on Pastes:
A cut/copy event can be pasted to a day in any open window, and as many times as you wish. That is, you can paste a cut or copied event multiple times to add it to many days—for instance, to manually propagate a recurring or multiday event. Right-click the event to copy it once, then right-click days to paste it on as many days as desired. As for all updates, pastes show up immediately in all windows open on the target month.

Open on Right-Click:
For convenience, the Cut/Copy menu opened by event right-clicks of the prior paragraphs also has an Open option, which is equivalent to an event left-click, and pops up the full event View/Edit dialog. Hence, most work can be done with right-clicks alone (e.g., cut/copy/open in event, paste in day), plus day left-clicks to add new events.

Event Changes and Deletes:
The event View/Edit dialog, opened on event left-clicks, allows you to view full event details; remove the event with Delete; or change most event fields and update the calendar for the new data with Update. You can also delete events with a Cut in the Cut/Copy event right-click menu; a Cut also saves the event for a possible later paste, while a View/Edit dialog's Delete does not. As of version 1.4, the event edit dialog's Cancel verifies the window close if you've made any changes; Delete and Update do not (and neither does a right-click Cut), but these operations can't discard user inputs, and do not update calendar files immediately. To back out unintended deletes or updates, click the main month window's "X" close button and opt to not save changes.

Changing an Event's Calendar:
The View/Edit dialog for an existing event lets you change most event details, but for implementation reasons does not allow you to change the calendar to which the event belongs. However, you can move an event to a different calendar via cut/paste—cut the event via event right-click, then paste it back to where it was via day right-click, changing its calendar in the Paste (prefilled Create) dialog that is issued on the paste.

Event Colorization:
Event summary widget background color defaults to white, but can be configured in frigcal_configs.py—both for all events in a calendar, and for all events sharing a category name. Event category colors span calendars, and override any calendar color settings. To colorize events:

New in version 1.7: both the widget background and foreground text can now be colorized, and the pickcolor.py utility script can be used for custom color selections; see 1.7 release notes for details.

Summary Text Visibility:
Event summary widget text displayed on days is intended for brief captions only. In-depth notations are instead entered in an event's multiline description field. To see more event summary text, try expanding the window and/or changing the day label and other fonts in the previously described frigcal_configs.py. You can also see the full summary text in the Footer display (see earlier), as well as in the full event View/Edit dialog window opened on event left-clicks (see click mode actions earlier in this section). The 1.3 event selection list dialog also shows more of the summary (see earlier in this list).

Other Visibility Notes:
This GUI may sometimes resize itself based on its content. You can resize any month window manually if its automatic sizing changes undesirably as you navigate to months with more or fewer events displayed. As mentioned earlier, many events per day may also preclude footer visibility in a given month, though the full View/Edit dialog shows complete details on demand (see above).

Trace Messages:
Program messages are printed to the console; if you don't care to see these, change the script's trace variable; redirect stdout in your shell; or rename the main script to "frigcal.pyw" or set a shortcut to it to be Run/Minimized to suppress the console window altogether. Update: as of version 1.4, you may also run the new frigcal-noconsole.pyw script to suppress the console on Windows automatically; see the 1.4 change note.

Run the GUI live for more usage details; see the start-up overview for pointers.



iCalendar Implementation


This section is probably of more interest to developers than users. The frigcal program records calendar events in portable, vendor-neutral iCalendar (".ics") text files. These files are loaded in full in startup, and regenerated in full on quit requests (and only after verification and a successful automatic backup).

The iCalendar files created by this program are standard-conforming, and should be useable in other calendar programs directly. Some other programs may require an initial import to use iCalendar files created here, and an export to create files usable here.

For more technical readers already familiar with the iCalendar standard, here is a summary of its usage in frigcal:

  1. This program can use iCalendar files created by other programs. This program also creates iCalendar files having one calendar object per file, and one or more nested event components per calendar.

  2. This program recognizes and creates the following calendar component type to represent its calendar event entries:


  3. This program ignores and does not create these component types:


    However, this program retains these component types in the iCalendar file if already present, for use by other programs.

  4. For VEVENT components in files, this program uses and generates the following properties:

    Property Role Type
    UID Unique id, for searches Required
    DTSTAMP Last modified, type=date-time Required
    DTSTART Event's date, type=date Required, if no calendar 'method'
    SUMMARY Label text (one line) Optional
    DESCRIPTION Extra text (multiline) Optional
    CATEGORIES Color map text: uses first only Optional
    CREATED To sort events, type=date-time Optional

    Notably, this program does not use or generate DTEND, VALARM, or RRULE specifications for events, which precludes events spanning dates, reminders, and recurring events. You can simulate the spans and recurrence by simply copying and pasting an event on multiple consecutive or recurring days via right-clicks; for simplicity, alarms are outside this program's scope. See also the suggestions for recurrence implementation ahead. Journaling is also not supported, though event descriptions support arbitrarily long date-based notations.

    Subtle point: strangely, the UID field's value is not always unique—some calendar programs may reuse the same UID for each instance of a recurring event. Although frigcal doesn't support recurring events directly, this won't generally cause problems: because frigcal indexes and locates events by UID within dates, events with duplicate UIDs display individually and work properly as long as events don't recur on the same day.

  5. Though both rare and configurable, when some other programs use the calendar files this program creates, you may see quoted-printable escapes for literal backslashes embedded in an event's text fields. See this note for details.

For details on the iCalendar standard, search the web or see:

Less formal sources:

The following is an example of the iCalendar files this program creates, as of this copy (see also the version 1.7 screenshot of such a file with non-English Unicode content):

BEGIN:VCALENDAR
VERSION:2.0
PRODID:frigcal 1.5
BEGIN:VEVENT
SUMMARY:frigcal ics file created
DTSTART;VALUE=DATE:20150406
DTSTAMP:20150406T091225
UID:frigcal-date150406-time091225-pid6604
CATEGORIES:system internal
CREATED:20150406T091225
DESCRIPTION:Your default iCalendar file was generated.
X-FRIGCAL-VERSION:1.5
END:VEVENT
BEGIN:VEVENT
SUMMARY:fetch latest Windows 10 preview
DTSTART;VALUE=DATE:20150408
DTSTAMP;VALUE=DATE-TIME:20150406T091626Z
UID:frigcal-date150406-time091626-pid6604
CATEGORIES:gadgets
CREATED;VALUE=DATE-TIME:20150406T091626Z
DESCRIPTION:The current build makes a single-core netbook so slow that it'
 s unusable.\nThis is probably due to an early tech preview build\, but nee
 d to verify.\n
END:VEVENT
END:VCALENDAR



Release History


This section describes program changes and releases, most recent first. In this section:

     
Release Date Highlights
Version 1.7 August 27, 2016 Event foreground colors, Unicode clarifications, utilities
Version 1.6 October 6, 2015 PNGs without Pillow, Tk 8.6 colors, Linux upgrades
Version 1.5 March 18, 2015 More error handling, image folder, new release structure
Version 1.4 February 10, 2015 Edit cancel verify, new launcher, file format, image tweaks
Version 1.3 December 18, 2014 Event selection lists, cut/copy context, tear-offs dropped
Version 1.2 October 14, 2014 Icon, help, missing configs, read-only text, day shade
Version 1.1 October 3, 2014 Smarter saves: changed files only
Version 1.0 September 26, 2014 Initial release

 

Version 1.7, August 27, 2016

Highlights: Event foreground colors, Unicode clarifications, utilities

This version adds foreground (text) color configuration for events, and verifies and extends the support of non-ASCII Unicode characters throughout the system. New screenshots capturing both the event foreground configurations and Unicode support of this release can be found here, here, here, and here and this version's full set of screenshots can be browsed here. In short:

A handful of minor items were also addressed. Most prominently, a typo was fixed in an error popup, and screenshot folders now have thumbnail index pages courtesy of the thumbspage program.

Details

This section provides more information about this version's changes. Search on "[1.7]" in the code for implementation details underlying the following:

  1. Event foreground colors configuration: Event summary entry widgets, displayed on days, can now have both background and foreground colors configured on a category-name or calendar-file basis. Prior versions supported only widget background color, and always used black for foreground text. The text itself can now be colored as well, by providing a ('bgcolor', 'fgcolor') tuple for color settings, where both bgcolor and fgcolor may be a color name or #RRGGBB hex-value string. The former 'bgcolor' string setting still works to set background color only, making this a fully backward-compatible extension (existing configs files work unchanged). For example, an event summary color value setting of:
    In all cases, the default event display background and foreground are white and black, respectively. Also note that this change applies to event summary display widgets only; other widgets' color settings differ. See the event color settings near the end of the configs file for more documentation and examples.

  2. New color selector utility: Given the pervasiveness of color configurations in frigcal, this version also adds a simple GUI utility script for selecting custom colors. The new script, pickcolor.py, allows its user to choose a custom color, displays the color, and gives the color's #RRGGBB hex-value string in the GUI, ready for cut-and-paste into frigcal config-file settings (and GUIs, webpages, and any other system that uses these values). See the utility's screenshot for example usage.

  3. Unicode support validation: For this release, the system was verified to handle non-ASCII Unicode characters, with some minor limitations imposed by its GUI library. Specifically, both non-English language text and many Unicode symbols can be used in event summary and description test in the GUI, and may be saved in and loaded from calendar files. Basic Unicode support required no changes to code, as both the icalendar and Tk libraries used by frigcal support Unicode text:
  4. Unicode limitations in the GUI: As mentioned, the Tk GUI library—underlying the Python tkinter module used by frigcal—supports input and display of Unicode characters, and handles almost all modern languages and a large number of symbols. Like other software, though, Tk does not support every defined Unicode character, and frigcal's Unicode display support is naturally limited to that of Tk. This support is partially font-dependent, though Tk automatically searches all its fonts for a character's glyph to display if absent from the font used. See this for Tk's glyph lookup mechanism, and this for Tk fonts in general. Tk's Unicode support is also prone to evolve like Unicode itself, though Tk is currently years behind the Unicode curve.

    More fundamentally, Tk currently seems to be limited to Unicode character code points in the range U+0000...U+FFFF—a.k.a. the Unicode Basic Multilingual Plane, or BMP. This effectively precludes GUI use of supplementary characters and Unicode planes outside the BMP's range. The details and evidence behind this are fairly technical (and can be safely ignored by most frigcal users), but include tkinter error messages, developer forums, and source code.

    For example, inserting a Unicode checkmark character into either the tkinter Text or Entry widgets with the first line of the following code works as expected, displaying a checkmark in the GUI:
    text.insert(END, 'xyz\U00002713')     # works (checkmark symbol) 
    text.insert(END, 'xyz\U0001F383')     # fails (jack-o-lantern emoji)
    
    However, the second code line above fails to display a Unicode jack-o-lantern character, instead generating an exception and this tkinter error message on the console (tkinter uses Tk, which uses Tcl):
    _tkinter.TclError: character U+1f383 is above the range (U+0000-U+FFFF) allowed by Tcl
    
    Moreover, pasting a checkmark character from another source into either of these two widgets works fine, but pasting a jack-o-lantern leaves a generic square character on Windows instead. This is a well-known issue, which also impacts Python's IDLE, and is discussed in multiple Tcl/Tk and Python developer threads; for a representative sample, see this and this. This is also called-out in Tcl/Tk's source code; per its tcl.h header file in version 8.6:
    Tcl is currently UCS-2 and planning UTF-16 for the Unicode string rep that 
    Tcl_UniChar represents.  Changing the size of Tcl_UniChar is /not/ supported.
    
    Per all the evidence, Tk appears to currently support only the UCS-2 subset of Unicode, without the variable-length surrogate extensions of UTF-16. If you're too new to Unicode to know what that means, see this for UTF-16 and its more limited UCS-2 predecessor, and this for more on UCS-2's limitations. In sum, Tk's UCS-2 range can represent only the code points in the Unicode BMP. The net effect is that jack-o-lanterns, emojis in general, and some other silly bits appear to be right out for the time being in Tk, tkinter, and frigcal.

    The good news here is that Tk's Unicode support is very broad: modern text consists almost entirely of character code points in the UCS-2 range, and is fully supported by frigcal's GUI. For examples of the sorts of Unicode characters that Tk (and hence frigcal) does support, even for portable fonts such as Courier, Times, and Helvetica, open file unicode-cheat-sheet.txt in any Unicode-aware text editor or viewer. For more symbols to use in frigcal, try here and here, and choose characters whose code points are in Tk's current U+0000...U+FFFF BMP range.

    Also keep in mind that Tk's limits pertain to already-decoded Unicode characters used in the GUI, and are independent of Unicode file encodings. Python, icalendar, and frigcal can still happily load and save UTF-8 calendar-file text having characters outside the BMP, even if the Tk GUI library has issues inputting or displaying content outside this range.

  5. Unicode trace messages: As a minor accommodation to Unicode, frigcal's trace messages were specialized in this release to avoid exceptions on terminals that don't support ASCII text, by printing Unicode '\u....' escapes when required to produce ASCII. Trace messages are not significant to most users, and should not trigger exceptions in any event. PYTHONIOENCODING shell variable settings may address this is some cases, but seem less user-friendly. See sharednames.py for the new tracing code.

  6. Unicode strings in configs code: As another minor upgrade for Unicode, the user configs file grew an encoding declaration line, "# -*- coding: utf-8 -*-", along with a note and example documenting the use of non-English characters in the file's string literals. Python defaults to UTF-8 for source-file encoding (which handles ASCII), but the declaration line was added to ease changes to other encodings. This scheme allows non-English text to be used in the file's strings, for settings such as file paths and event category names. See screenshots of the latter in action here and here. Note that the configs file is now shipped in UTF-8 form to support and illustrate this change; see its documentation for usage details.

  7. Unicode conversion options: Although frigcal already supports Unicode characters in event text and files, its Unicode model always uses UTF-8 text encoding for both existing and newly-generated calendar files. For existing files encoded per other schemes, this constraint may be inconvenient. Consequently, this version provides a converter script for existing files, and documents how to change the default encoding in source files. The new options fall into three use cases, depending on your existing files, and in decreasingly-recommended order:

    1. No changes or conversions: when there are no existing calendar files, or all existing calendar files are encoded per either UTF-8 or ASCII, frigcal's default UTF-8 encoding suffices, and no file conversions or source-code changes are required. This case likely applies to the vast majority of users. Note that frigcal's UTF-8 default works for ASCII files and event text too, because ASCII is a subset of UTF-8.

    2. Converting existing files—new utility: for users with existing calendar files encoded with a Unicode scheme other than UTF-8 or ASCII (e.g., Latin-1), a new file-conversion script can be used to perform a one-time conversion to UTF-8. See the new encoding converter script, unicodemod.py and its usage screenshot for more details. In short, a simple command line:
      C:\...\frigcal> unicodemod.py icsfilename latin-1 utf-8
      
      suffices to convert a file from non-ASCII Latin-1 to the required UTF-8 format. Converting existing calendar files this way allows frigcal to use its default UTF-8 encodings unchanged for both input and output. Crucially, this also allows frigcal to generate newly-saved calendar files with Unicode characters that may be outside the scope of original narrower encodings of existing files. Because of this generality, this approach is recommended for users with existing non-UTF-8 files.

    3. Changing encoding in source: for users with existing non-UTF-8 calendar files, source-code changes offer another, though more complex, option. The underlying icalendar package's setting DEFAULT_ENCODING in module icalendar.parser_tools controls most or all of its Unicode behavior. Unfortunately, this setting can be changed only in its source code (file icalendar\parser_tools.py), because it is imported throughout the icalendar package with "from" statements before any modules outside the package have a chance to change it—thus precluding a frigcal configs file solution. If are willing to change source code, though, this setting might be used to avoid file conversions by changing icalendar's (and hence frigcal's) Unicode encoding scheme globally. Its preset value is UTF-8, but any encoding name string supported by Python is allowed, and will be used by frigcal's file processing (as of this release).

      Due to the icalendar library's implementation, however, this setting is used for both input and output file text—it will be used to both load existing calendar files and save generated calendar files. Hence, this setting cannot be used to load a non-UTF-8 existing file and save newly-generated files in general UTF-8. If changed to a narrower encoding (e.g., Latin-1), this setting makes Unicode characters outside the encoding's scope unusable in event text, because they cannot be saved in files. Users are thus encouraged to convert non-UTF-8 files to UTF-8 per option B above, unless they are both open to changing source code, and sure that their event text will always remain in a different encoding's scope.

  8. Unicode fonts on Linux: Per testing thus far, bold fonts can sometimes make some Asian characters (and possibly others) render unreadably on Linux. On the other hand, bold seems to work well for all characters and fonts on Windows (here and here) and most languages on Linux (here), and non-bold seems too dull in all other contexts. It's unknown whether the lack of readability for Linux bold fonts reflects testing parameters or a more general issue, but in either case dropping bold font presets for a subset of users on a single platform seems too extreme a response.

    To address this, bold font defaults were retained, but a new setting in the configs file, LINUX_ASIAN_FONTS, is precoded to use non-bold fonts for all event text displays. Set this to True if your non-English characters are difficult to read on Linux or elsewhere. You may also want to use a larger font size or a different font family in this same code on Linux, but this is subjective, and may also need to be specialized by platform (see MONOSPACE_FONTS in the configs file for an example of platform-specific fonts).

    For examples of non-English use on Linux, see the new screenshots here and here; view the first at full size to see its true clarity. These were taken after editing the configs file in gedit to enable its non-bold fonts code and increase its font sizes slightly, on a machine with default UTF-8 locale settings. As always, your mileage may vary.

    It may be possible to automate some font selections (e.g., with locale queries), but any such "clever" automatic choices in this domain would almost certainly be inappropriate for some individuals and use cases. frigcal instead encourages its users to configure the program for their own context and tastes.

  9. File end-line conversion utility: For users on Linux and other Unix-like platforms, this version includes a simple script, fixeoln.py which can be used to convert file end-lines from the shipped Windows format to the native Linux format, when no other tools are available. This script is essentially a Python-coded, Unicode-aware, and portable equivalent to the sometimes-absent Linux commands dos2unix and unix2dos, and works for files of any Unicode encoding type. See this script's docstring, run log and screenshot for usage details. For additional Linux end-line options, see the documentation of the configs file—this script's initial motivation.

 

Version 1.6, October 6, 2015—January 27, 2016

Highlights: PNGs without Pillow, Tk 8.6 colors, Linux upgrades

This version makes an install of the third-party Pillow (a.k.a. PIL) library optional for some combinations of image file type and Python version; addresses incompatible changes in color names in the Tk 8.6 library underlying tkinter in Python 3.4 and later on Windows; and on Linux improves the behavior of modal dialogs and window icons. As none of this release's changes impacted any existing GUI displays no prior screenshots were retaken, but new shots were taken for the new image load error handling, and usage examples on Windows 10 and Ubuntu Linux. This document was also updated and reformatted for usability.

Repackaged Jan-27-16
with minor changes reflecting the fact that Python version does not necessarily imply Tk version—it does on a standard python.org Windows install, but may not on Mac OS X and elsewhere, as described in detail here. Changed the configs file to pick colors based on Tk version (not Python version), and changed the documentation in this file as well as the text of READMEs and start-up and error messages to better clarify Tk version impact on month images. Also included the latest Pillow installers for Windows, for Pythons or image file types that don't satisfy the Tk 8.6 requirement described in #4 below.

Repackaged Nov-10-15
with doc changes (new font, header, and toolbar styling; minor content tweaks; and updated URLs for book site relocation); plus one minor nonfunctional code change (remove a pointless staticmethod declaration for one method).

Details

The following list provides more information on this release's changes. Search on "[1.6]" in the code for more implementation details underlying the following:

  1. Verified on Python 3.5, Windows 10, and Ubuntu Linux: frigcal was verified to run on Python 3.5, and on both Windows 10 and Ubuntu Linux. In the standard Windows installer, Python 3.5 (and 3.4) ships with and uses the Tk 8.6 library underlying Python's tkinter GUI toolkit, and changes in the Tk library were the motivation for many of this release's code changes. Note that all 1.6 changes are backward compatible with earlier Pythons, so frigcal still runs as before on all other Python 3.X releases. Also note that Python 3.5 appears to speed up initial calendar file loads substantially—reducing load time from 5 to 3 seconds in one use case, and from 13 to 8 seconds in another. Given this, and the PNG images support described ahead, Python 3.5 and later are suggested on Windows, and Tk 8.6 is suggested elsewhere.

  2. Color names and notes in configs file for Tk 8.6: Tk 8.6 changes the meaning of some color names to be compatible with a Web standard—instead of the X11/VGA standard used for the last 25 years. Namely, "green", "purple", "maroon", and "grey/gray" render more darkly than before, and are now too dark for use as label backgrounds in most contexts. To address this, the user configs file was expanded with a note, and pre-coded assignments that map the four old names to their Tk 8.6 equivalents when using Tk 8.6+ only. This is now version-specific, as some of the new names aren't available in prior Tk versions. Tk 8.6 is standard for Python 3.4+ in python.org's Windows installers, though not necessarily so on other platforms (e.g., Mac OS X may use Tk 8.5 with Python 3.5); Tk version can be queried with tkinter.TkVersion.

  3. Customizable current-day shading color in configs file: The shading color used for the currently-focused day frame is now user-customizable in the configs file. It still defaults to the former gray, but uses the pre-Tk 8.6 rendition of gray, and allows for customizations both for user preference and future Tk change. This new color setting, "currentdaycolor", is used both when days are clicked, and when moving to the current day via the "Today" button or Escape key presses.

  4. Third-party Pillow library is no longer required for some month images: Because Tk 8.6 adds support for PNG image display, the rules regarding Pillow installs have been relaxed. Rather than making Pillow an absolute requirement for month images, frigcal now attempts to load image files with Pillow if it's present, and without Pillow if it's absent, and simply catches the exception that occurs if the image load fails. The net result is that, depending on your image file types and Python version, a Pillow install may no longer be required to use the optional month images display. Specifically, month images now display with just Python itself and do not require a Pillow install for both:


    For all other combinations of image type and Python release (e.g. JPEGs, and PNGs under Windows Python 3.3 and earlier), Pillow is still currently required for the image display feature, and a new error popup is still issued if images are enabled and a month image cannot be displayed. The new popup is issued on each image load failure, and replaces the former error popup that was issued once at Images toggle-on if Pillow was not installed—the new protocol doesn't require Pillow, and allows some images to succeed if others fail. On image load failures, a default image is also displayed as a placeholder for the month.

    Users are generally encouraged to use the popular PNG format and Python 3.4+ on Windows (or a Tk 8.6+ elsewhere), as this both allows for high-quality images and removes the requirement to install Pillow altogether. The latter is especially desirable, given that Pillow releases for new Pythons may be delayed substantially. As shipped, the top-level MonthImages folder was changed to include PNG files instead of the former JPEGs, to both mirror suggested usage and support Windows Python 3.4 and later out of the box. Change and configure as desired.

  5. On Linux, keep modal dialogs on top: Custom modal dialogs on Linux are now kept above their month window, by adding a transient() call for the new dialog window. Without this call, frigcal's custom modal dialogs—event selection lists, right-click actions, and event create and edit—pop up on top initially, but can be covered and hence hidden by their disabled month window parent before being processed by the user. This is especially bad for the right-click dialog, given its small size; the unresponsive month window can easily cover the dialog if clicked, leaving the GUI in an odd state. With the added call, modal dialogs can never be covered by their parent on either Fedora or Ubuntu Linux; Fedora also erases the pop up's "X" close button, but the dialogs' Cancel closes as usual. This change applies to Linux only: transient() is not required and not used on Windows, as that platform's custom modal dialogs do the right thing without it, and dialog behavior on Macs is to be tested. See the "Dialog" superclass in frigcal.py for this change's code.

  6. On Linux, set some window icons: tkinter's iconphoto(), available in Tk 8.5 and later, is now used to set window icons on Linux. On both Fedora and Ubuntu, this change simply provides an image for the program's icon in the activities/applications bar, not on the program window itself (Fedora also displays a small version of the icon at the top of screen). There are additional ways to set icons on Linux (e.g., XBM bitmap files, via iconbitmap('@myicon.xbm'); see the web for pointers. This change applies to Linux only: the existing iconbitmap() scheme sets icons on Windows correctly and is retained for that platform, and iconphoto() is documented as not relevant for Macs. Search for "try_set_window_icon" in frigcal.py for this change's code.

  7. Also for Linux users in this release: There are new usage notes above, and the configs file automatically translates any Windows "\" to "/" in calendar and image folder paths—a minor change but useful when selecting precoded paths in the file.

  8. [Caveat] On Linux, images are not hidden with their months: Month image windows are not hidden (minimized) and restored with their month window parents on Linux as they are on Windows. To address this, this version intended to experiment with using tkinter's withdraw() to hide image windows on Linux, instead of the iconify() used successfully on Windows. The withdraw() call is required on Linux (only) if you wish to restore later with deiconify(). See the new supplemental example script __withdraw__.py for a simple demo of this Linux constraint.

    Unfortunately, the experiment never got that far—on Linux (again, only), the required "<Unmap>" and "<Map> events are never generated on window minimize and restore. Moreover, the "<Configure> event is also useless in this context: it doesn't fire on minimize or restore on Linux either (and only on restore on Windows), and in any event invokes a function on every size and location change. Hence, this issue has been abandoned. In the end, tkinter's portability may be less robust than it might be, and too incomplete to support this particular intended behavior. Run the __hide-unhide__.py supplemental example on Linux for a demo of the events issue.

    Note that the withdraw() call also works on Windows, so it is potentially more portable than iconify(). However, withdraw() also prevents the image window from appearing in the taskbar along with the month window (per __withdraw__.py). It's not clear which flavor is preferable, but the prior iconify() behavior and code was retained for Windows on the grounds of frigcal compatibility. The event triggers issue rendered Linux portability a wholly moot point in this instance. Search for "<Unmap>" in frigcal.py for the related code.

    The good news is that the vast majority of frigcal works and looks the same on Windows and Linux with no code changes; isolated window manager issues might be pardoned as unavoidably platform-specific.

 

Version 1.5, March 18—April 29, 2015

Highlights: more error handling, image folder, new release structure

Version 1.5 adds more explicit error checks to make the system more robust and user-friendly, and avoid silent shutdowns when used in non-console mode on Windows. It also tightens-up image folder handling to allow non-image files, and has a new release structure on the web.

Details

Search on "[1.5]" in the code for changes made in support of the following extensions introduced in this version.

March Release

  1. Catch calendar startup errors: When an error occurs while loading the calendar files(s) at startup, catch the exception and display a new GUI popup, not only Python exception text in the console window. This is especially important on Windows, where the console window may be absent (causing the program to close without any message), or may disappear immediately before the exception's text can be read (the notorious disappearing window feature). Startup failures are most likely due to bad calendar path settings (hence the message's hint), though bad data and parser failures are not impossible. For symmetry, also extended the existing backup/generate error popups to include basic exception text, lest the console be absent on Windows.

  2. Catch config and library startup errors: Also at start-up time, catch errors in the user configurations file and failed imports of 3rd-party library dependencies, and report these as GUI popups as well. The new GUI popups are captured here and here. Libraries are unlikely to fail (they are shipped with the system), but configs file errors may be common. Like calendar errors, these are both fatal, but the main GUI hasn't been built yet; show a special-case popup, instead of printing text to the console (which may not be present on Windows), and wait for an OK press, instead of keeping the console open by pausing for input (which may never come).

  3. Handle pruning delete failures: File deletions can fail for many reasons, including permission changes, device failures, and in-use locks; this is especially true on Windows, where even the covert operation of the search indexer or an antivirus scanner might interfere with deletions. When deleting an old calendar backup file in the pruning process, don't let a delete failure cancel the entire backup/save process. Instead, issue a warning popup but ignore the delete and cancel the pruning step only, and proceed on to backup/save. It's likely that the next backup/save will be able to prune the file whose delete failed.

  4. Warn about missing image files, bad image path: Issue a new warning popup in the GUI (once per window, at first image toggle-on) and a message in the console (on each display attempt) if the image folder does not contain exactly 12 image files. Otherwise, some months may appear to not load an image, because indexing a too-short sorted files list triggers an exception, shown only in the console. Having more than 12 images would work (ignoring items past the 12th by sort order), but most likely also reflects a user error. Also added a check and simple error popup for bad image file paths in the configs file; this and an existing error popup for Pillow-not-installed are both issued at each image toggle-on and disable images altogether, but don't stop the program. Update: see version 1.6's relaxation of Pillow install requirements, and the new image load error popup.

  5. Skip non-image files in images folder: When loading a month's image, skip and ignore any non-image files in the images folder, by using Python's mimetypes module to determine type from filename extension. This includes any ".txt" text files present (including the folder's README.txt, which works here now); an .htaccess web admin file (if ever added to customize indexes for 1.5's web image: see note #6); and any other non-image items added by the user. This test is in addition to the subfolder skipping added in version 1.4; with the new check, image file selection proceeds as follows—to display a month image, the system:

    1. Lists all items in the images folder
    2. Removes non-file items (e.g., subfolders)
    3. Removes non-image files (e.g., text files, web admin files)
    4. Sorts by item filename
    5. Indexes to load the item for the month's relative number

    The net effect is that users may place arbitrary subfolders and non-image files in their image folders; only 12 actual image files are expected and used. Unknown or corrupted image files in this folder may still fail to display, and some image displays may fail unless you have Pillow (PIL) installed (see Dependencies).

  6. Default calendar file on adds: In the GUI's create event dialog, select the "frigcal-default" calendar file as the initial default if it exists. This is somewhat arbitrary, but the first calendar by name sort order seems always inappropriate in the system's author's usage.

  7. New release structure, docs: Adjusted links in docs to use a new examples structure that's the same in the zip file and the new website that hosts this system for online viewing: for the web, use links relative to a simple unpacked copy of the zip file. This required adding an .htaccess file in the top-level folder (only) for indexes, reinstating a stub top-level README.txt file for index display removed in version 1.2, and renaming this doc file to "Readme-frigcal.html" (see .htaccess). Also made minor updates to this docs file, and added new 1.5 screenshots.

April Updates

After its March release, this version was repackaged—most recently on Apr-29-15—with:

  1. Assorted documentation changes in this file (formatting, typos, etc.), and 3 new/updated screenshots (Win7, Linux, Win8).

  2. A comment about reading and writing calendar files all at once in icsfiletools.py. See generate_ics_files().

  3. A new example-only file related to the prior item, __chunkio.py__.

  4. try/finally wrappers around a read and write in icsfiletools.py to ensure closes for non-CPython use (e.g., PyPy). Search for "finally".

  5. Minor code changes in icsfiletools.py for a very unlikely save of a calendar whose last event has been removed. See generate_ics_files().

  6. A one-line code addition in frigcal.py to equate the Escape key ("Esc") with a Today button press, for keyboard users. See "<Escape>" binding.

  7. A one-line code addition in frigcal.py to force focus to the month window instead of its image (if any) on window restores, for keyboard users. Otherwise, a mouse click is required before using bound keys (like Escape). This subsumes the prior 1.4 month window lift (raise) operation on restores, which isn't enough to set keyboard focus. See "<Map>" event in the code.
As none of these minor changes impacted normal functionality, a new release number was unwarranted (and as of this release, frigcal will no longer do three-level X.Y.Z point releases due to their maintenance overheads). Development on this version is expected to taper off after its final repackaging in late April, 2015.

 

Version 1.4, February 10, 2015

Highlights: edit cancel verify, new launcher, file format, image tweaks

This release adds a grab-bag of usability enhancements, and addresses calendar file formatting. The following lists document this version's minor enhancements. As none of this release's changes impacted any other GUI displays, only a handful of new screenshots were taken—the new edit cancel verify dialog, also showing one of the new month images; the startup message of the new Windows no console launcher; and the augmented backup/save error dialogs.

Details

Search on "[1.4]" in the code for more implementation details underlying the following changes applied in this release.

General Changes

  1. Verify Cancel if any data changed in event edit dialogs: Added a close confirmation dialog to the Cancel button in event edit/create dialogs, issued if any changeable field has been modified. Previously, Cancel simply closed the dialog, whether data was edited or not, potentially losing user changes. Closes now happen only if confirmed in a popup dialog, when any input field has been changed (but are still immediate if no inputs have been updated). This verification is also run for the edit dialog window's standard close ("X") button.

    Related note: as is, the Delete button in event edit dialogs does not verify its operation, because, unlike Cancel, this won't discard user inputs in the form, and Delete does not update calendar files immediately—files are updated only on quit button clicks which verify their action, allowing Deletes to be abandoned (albeit at the cost of other session changes). Delete could easily verify too, but this may be just enough popups to be dismissed by users blindly. Update does not verify changes for the same reasons (nor does right-click Cut; at some point, users must be trusted, not annoyed!). Cancel the save on quit to back out any inadvertent or unwanted deletes or updates.

  2. Order day's events by creation date year before month: Fixed a minor issue regarding the sort order of the events displayed within a month window's day tiles (and in their corresponding event selection lists added in version 1.3). Event labels in each day are now are ordered and thus grouped by calendar filename, and then by creation date (year#, month#, day#) within calendar group. Formerly, these events were instead inadvertently grouped by creation (month#, day#, year#) within calendar group, such that an event's month was more significant to ordering than its year (e.g., January, 2015 would appear above December, 2014 on a day's tile). This was due to the order in the tuple returned by the event class's __lt__ operator method, used by sorts; this method now correctly places year first.

    That said, this change is more minor enhancement than repair. Most users probably didn't rely on—or even notice?—the date ordering of events, for 3 reasons: a given day typically does not display enough events to make order significant (though the 1.3 selection list makes days with many events more manageable); the intended creation date ordering within a calendar group was only tersely documented (calendar filename grouping was emphasized); and it's not clear whether an event's creation or modification date should be used for this ordering (creation date is preferred when present, but this seems arbitrary). Events could also sort on creation time, but are not; time is generally ignored by frigcal. See also the related ordering TBD below.

  3. New no-console launcher for Windows: Added a new script, frigcal-noconsole.py, which launches the main frigcal.py script without opening a console messages window on Windows platforms. Drag this out to a desktop shortcut on Windows if desired. This launcher spawns the main script, and issues a startup message window that closes automatically after a brief timeout. The console is mostly meant for tracing and debugging, and not generally useful for end users. See the launcher script's docstring for more on Windows consoles; it's also possible to suppress the Windows console by renaming the top-level script with a ".pyw" extension, or marking a shortcut to the ".py" as Run Minimized, but the new launcher is more automatic and allows the ".py" to still be used when a console is desired.

    Three related notes: First, rather than closing its message window via a simple timeout, the launcher could be more precise (e.g., delete and poll for a file to be created, or use an IPC tool like sockets); that seems overkill in this context. Second, Linux and Mac users may use the new script too (after making frigcal.py executable), but there's not much reason to do so; scripts in these platforms are generally launched with shell command lines or platform-specific devices. Finally, some error messages or context may go only to the console (e.g., configs file syntax errors did, until 1.5); despite its seeming inconvenience, you may still want to run console mode in some contexts, especially if errors close the program or GUI silently.

    To put the preceding point more strongly: system errors happen. They can be triggered by in-use locks, read-only permissions, device failures, and more. Version 1.5 later added GUI popups for more types of startup and backup/save errors to make handling more user-friendly, and avoid silent shutdowns in non-console mode. Still, some error context text (e.g. tracebacks) may appear only on the console, and may be crucial in debugging. Although frigcal is generally very robust, and goes to great lengths to save multiple backup copies of your calendar data for safety, running without its console may limit options on some errors.

  4. Drop extra '\n' added by tkinter at end of description field: The tkinter Text widget always adds an extra '\n' newline at the end of the full text fetched from it. These are now forcibly stripped—dropping all '\n' (only) at the text's end, and putting one '\n' back in case some ICS parsers balk at empty (but legal) field values. Prior to this change, the system added one new '\n' to an event's description in the calendar file per event update or paste. This was harmless, and the extra space trivial (just 2 bytes per '\n'), but these could accumulate needlessly. Stripping doesn't effect change tests, because it is immediate on text widget fetches. Tradeoff: this could get Text to END+'-1c' instead of stripping, but strips drop any extras already present from prior releases (albeit dropping any intended but pointless blank lines at the end).

  5. Help on backup/save errors: In the very unlikely event that a calendar backup/save operation fails, the resulting error dialogs for backup and save are now augmented to include a pointer to the new help text above for more details on restoring from automatic backups and other possible resolutions.

  6. Retain literal backslashes in user-input text: Added a new configs file setting, retainLiteralBackslashes. When True (its default), data translations ensure that any literal '\' characters in text entered in the GUI's Summary or Description fields are retained verbatim when data is stored to and fetched from calendar files. When False, no data translations are performed, but some '\' characters in user inputs will be interpreted by the underlying ics library as escape sequence prefixes, and discarded when saved to a file and loaded in a future session. Although '\' characters are likely rare in user-input text, the new setting allows them to be retained or interpreted explicitly. Retaining—the default—makes sense in most use cases, as user-input is generally meant as entered.

    In more detail, to retain backslashes, the system escapes and unescapes them using common quoted-printable notation—translating any and all '\' to and from '=5C'. You won't see these replacement characters in the frigcal GUI, as the translation happens automatically on calendar file save and load. For example, a user input [a\nb\nc\;d\,e"f\"g] becomes [a=5Cnb=5Cnc=5C;d=5C,e"f=5C"g] in files only, and is restored to its original form when reloaded in a new session. Without this step, most literal '\' characters would be dropped, and a two-character literal '\n' translated to a one-character newline.

    This translation is switchable because: (1) you may wish your backslashes to denote escapes (e.g., allowing an entered '\n' to become a newline); and (2) the escape mechanism risks loss of file or data portability, if you use a calendar file containing such escapes in a GUI that doesn't recognize or undo them. The latter is a non-issue if there are no '\' or frigcal is the sole file user; its likely worst case just means you may see rare and isolated '=5C' instead of '\' in another GUI's event fields. See source file icsfiletools.py for more details; while functional, this may prove to be a temporary workaround, pending a better way of dealing with the underlying library's data mutations.

Month Image Changes

  1. Explicitly exclude subfolders in the month image files folder: When loading and displaying a month's image, the program now explicitly ignores non-files in the month images folder. This makes it less error prone to store alternative image sets in subfolders nested within this folder, to be copied up or selected via configs file settings. As it was, the system simply fetched, sorted, and indexed the image directory listing's names on month number without regard to entry type. This allowed some subfolders, but only accidentally—as long as subfolder names were all lexically greater than all month image filenames. This was true if image filenames all began with "NN_" digits following the pattern of images shipped, but this is unreliable (e.g., a "02_X" subfolder name that sorted among the true image names would have caused exceptions for its relative month). Update: as of version 1.5, non-image files are also ignored in image folders.

  2. Alternative month images sets and online zip files: Added additional and alternative sets of month images designed and contributed by third parties. One is capture in the new 1.4 screenshot. These image sets are shipped in the AlternateMonthImages subfolder of the program's MonthImages folder, and enabled by the GUI's Images toggle. To use these instead of the original set, either copy their files up to MonthImages, or set the image file path variable in the frigcal_configs.py file to point to a set's subfolder. See that file's "imgpath" setting for examples, and the MonthImages folder's README.txt for more details.

    Alternate image sets—possibly including others not shipped with the program—are also available online as zip files at this location: http://learning-python.com/AlternateMonthImages. Download a zip file from there, and unzip its image files into your images folder, either directly, or as a subfolder whose name you list in the configs file. The program's main zip file includes the original images set and a handful of alternates, but larger sets may not be included due to their added size. Watch for additional image set zip files to appear at the online collection in the future, as time, interest, or contributions allow, and see the MonthImages folder's README.txt file for pointers on creating sets of your own. Item #7 above allows for arbitrary image set subfolders, whether shipped or custom.

    Update: due to lack of time, as of version 1.6's release, the online alternative image sets have not yet been updated or maintained. Barring new activity on this front, use the image sets shipped with the frigcal package's zip file, or create some of your own. See the new support for PNGs without Pillow in version 1.6 which makes custom sets easier to deploy.

  3. Automatically hide/unhide image windows with their month windows: When a month window is minimized/restored, automatically minimize/restore its associated month image window, if its image is enabled. Image windows are strongly associated with their month windows logically. This change makes the pairing literal—treating an image as a dependent extension to its month window, automatically hidden/unhidden with the month in tandem. Image windows may still be hidden and restored independently, but month window hide/unhide operations are always propagated to the associated image. Without this linking, images loitered on the display disjointedly when months were minimized.

  4. Month image windows: non-resizable, position configuration: Make image windows non-user-resizable; resizing was pointless, as the program sizes the window to its image, but never vice-versa. Also, add a new configs file setting, "initimgposition", giving the initial position of all image windows (e.g., to always open a horizontal image at the '+0+0' upper left corner initially). By default, the underlying window system still chooses starting positions. This mirrors the existing position option for month windows. Using a fixed starting position means image windows may overlap each other when opened, but this is irrelevant to single month-window sessions, and all windows may be moved freely. Making a window non-resizable also makes it non-maximizable on Windows, but logically so; minimize/restore still work.

Non-Functional Changes/Notes

  1. Code refactoring—move two dialogs' code to classes: For readability, moved the code for the cut/copy and select-list dialogs from their former nested appearance in event handlers, to new top-level classes. This also allows modal wait-state code to be factored out to a common Dialog superclass, and shared by all 5 dialog classes (the 2 new classes, plus the 3 preexisting event edit dialog classes), instead of being repeated in-line; it's not much code, but redundancy is almost always a Bad Thing in software—multiple copies imply multiple future edits.

  2. Code cleanup—rename callbacks "actions": To avoid overloading the term "event" (used for user-created events on days), renamed callback handlers to use "actions" universally. For example, the arguably-confusing method "register_event_events" became "register_event_actions".

  3. TBD—event reordering isn't immediate on adds: There remains an open but minor event-in-day ordering issue, related to item #2 above. In short, a day's events are not reordered immediately when a new event is added (by Create), but only on the next refresh of the containing month's display. It would be simple to refill the entire month window, but that may be slow enough to "flash" the display on slower machines. A more custom coding to refill just one day (in each window displaying the subject month) would be better, but seems complex enough to warrant a pass for now, as the ordering of events on a day tile seems likely to be minor or irrelevant to most users. As is, navigate forward and back (or select Today for the current month) to refill and reorder if desired.

 

Version 1.3, December 18, 2014

Highlights: event selection lists, cut/copy context, tear-offs dropped

This release focuses on the new event selection list dialog, but also adds more usability extensions.

Details

The following list chronicles this version's changes; search on "[1.3]" in the code for more implementation details:

  1. Event selection list dialog: Specialized left-clicks on the day number area—the area with the numerical day number at the top of each day frame of month windows, above any events for the day—to open a new event selection dialog. The dialog displays all the events in that day in a scrollable list, and supports view, edit, and cut/copy actions on an unlimited number of events.

    This new dialog is designed to look and act nearly the same as a day frame in a month window to make it intuitive. For instance, clicks on an event's summary text in the list work largely the same as clicks on event summary fields on day frames, with the following triggers and actions:

    Single left-click (tap) on event list item Open a normal View/Edit dialog for the event clicked.
    Single right-click (tap+hold) on event list item Open the normal Cut/Copy dialog for the event clicked.


     

    In addition, selection lists and their items also mimic the appearance of day frames and their event fields, by applying the same background color and font configurations used for days and events, and colorizing list item events by calendar/category just as on day frames. The net effect is a dialog that is essentially a larger and more flexible version of a month's day frame, opened on left-click request.

    This enhancement addresses the physical size limitation of day frames, which by nature constrains the number of events that can be displayed—a former TBD and futurism. If there are too many events in a day to display all in the day's frame widget, you may now view and edit any of them by clicking on the day number area to open the new selection list. This can be useful in general, but may be especially pertinent on small-screen devices such a tablets.

    The new listbox dialog is modal (blocking) to avoid the need to update potentially many listboxes. It also includes a Create button for adding a new event on this day via the normal Create dialog—a fallback option which works just like just like a left-click on the day's background in general, and allows new events to be added even if no more day frame background is visible below events. The new selection list dialog is skipped if there are no events on a given day, making a day number click the same as a day frame click, just as before.

    Implementation Notes—this feature works as planned, but comes with a few tradeoffs:
  2. Cut/Copy context label: Added the event's summary text as a label on the Cut/Copy (and Open) right-click dialog, to give the user some context of the event being operated on. This is usually obvious after right-clicking an event on a day frame, but becomes more murky when right-clicking an event entry in the new selection listbox. The new right-click dialog and menu with new context are captured here and here.

  3. Drop right-click menu tear-off: Disabled the default tkinter dashed-line in the Cut/Copy/Open menu, as it's pointless in a modal dialog that is immediately erased (it's useful for quick access to actions in more persistent menus). The new right-click menu pull-down is captured here.

  4. Better event dialog titles: Changed the action names in event dialog titles for clarity. The former "View/Update/Delete Event" is now simply "View/Edit Event", and the former "Create/Paste Event" is now specialized by usage mode to be either just "Create Event" or "Paste Event," better differentiating its role. The dual "Create/Paste" seemed especially dubious, as it was not always clear that prefilled but editable data was present for a paste; the dialog seems otherwise difficult to distinguish from an edit or create.

  5. Code cleanup: Edited code to use more descriptive names where warranted, especially for callback handler names that now pair triggers with actions. Also updated some internal documentation for clarity. Considered splitting off event dialog and view-date classes to separate modules, but punted—this is a tradeoff at best, as it can make searches more complex (you must search N files and windows instead of 1), and adds module coupling and conceptualization complexity (especially given the strong component relationships).

  6. Docs, screenshots: Updated this file for this version's changes, and rewrote much of it substantially (an open-ended process). Took new screenshots for changed content in this release only—the new selection list, the new event dialog titles, and redesigned right-click dialog and menus (see the main screenshots list). Prior screenshots are from 1.2, but were not retaken, as their content has not otherwise changed, apart from the "1.2" in their titles and the redesigned doc page in some backgrounds.

 

Version 1.2, October 14—November 10, 2014

Highlights: icon, help, missing configs, read-only text, day shade

This release adds an assortment of usability features inspired by regular use. See the new screenshot for a quick look at some of this version's changes.

Details

The following lists itemizes this version's changes. Search on "[1.2]" in the code for more implementation details.

October Release

  1. Windows icon: Added a program icon for Windows, "frigcalset.ico", generated using the new Python-coded iconify.py example script. Attached to all top-level windows automatically; can also be attached to a frigcal.py desktop shortcut manually, via right-click + Properties + Change Icon.

  2. Help button: Added a "?" help button at upper right, that pops up this HTML file in a web browser portably, via the Python webbrowser module.

  3. Start position: Added an initwinposition setting in the configurations file for month windows' initial position, in pixels offset from the screen's upper left corner. This may help on tablets with smaller screens, but causes each Clone to pop up at the same spot.

  4. Allow missing configs: Wrapped the user configurations module in an attribute proxy class, such that missing settings issue warnings and use defaults, instead of causing the program to exit (syntax errors and most invalid settings in the file were already handled). Configuration settings may be missing in a configs file due to user error or upgrades to newer versions with new settings (e.g., prior item). Code: sharednames.py (changed), attributeproxy.py (new).

  5. Read-only footer text: Made the footer's overview text area non-editable, as any changes made there would be ignored. Requires the Text widget's state to be set to normal/disabled around deletes and inserts in onEnter_Event(). This widget has no readonly state, and disabled state prevents even programmatic changes. On Windows and Linux this works well: it does not alter the text's appearance, and still allows copying and pasting text from the footer (via Ctrl-C/Ctrl-V on Windows and highlight/middle-click on Linux), but nothing else. This is contrary to most advice seen on the web—a forum which also made the curious recommendation of an infinite loop instead of wait_visibility() for Linux dialogs. This change may or may not have unintended consequences on Macs; please report any issues.

  6. Move day shade on mouse single clicks: When click-mode is "mouse" in the configurations file, a single left-click on a day now moves the current-day shading to that day; a double left-click still opens the Create dialog as before. The "touch" mode has only single-clicks for days (which both moves the shading and opens the dialog), and other day and event clicks already moved the shading. The shading is only a visual cue, but useful nonetheless. See also the version 1.6 change that makes the current-day shading's color configurable.

  7. Minor "touch" mode fix: Fixed a number-arguments code error that disabled event single-left clicks when click-mode was set to the non-default "touch" in the configurations file—functionality that had not been recently used (user testing matters!).

  8. Other: Regenerated all screenshots for 1.2; thoroughly overhauled the docs in this file; and removed old README.txt file, which was just a stub that redirected to this HTML file. (This file was added back in version 1.5 for indexes when viewing the unzipped version on the web.)

Version 1.2 Point Releases

This version was repackaged with only minor changes that did not warrant a new major release number here or in the GUI:

 

Version 1.1, October 3, 2014

Highlights: smarter saves—changed files only

New in this version: On window close button presses (a.k.a. "X" and quit), don't backup and write the file of a calendar if it has not been changed since program startup or its most recent save. Only calendars newly changed (by updates, deletes, adds, cuts/pastes, or summary edits) are regenerated on close; this both is an optimization, and further minimizes data loss risk. If no calendars have been changed, the backup/save dialog is skipped altogether, and the exit verify appears. Search on "[1.1]" in the code for changes.

 

Version 1.0, September 26, 2014

Highlights: initial release

Fully functional. This version was current when frigcal was first posted on python-announce.



Project Goals and Review


As a wrap-up, this section provides a few words of perspective on this project's goals. Because this program was written as a personal-use tool, describing its motivation requires first-person narrative. Much of this section is fully optional reading if you're not interested in personal experience or opinion, but see its final part for future enhancement ideas.

In this section:

 

Why frigcal?

I wrote this program because:

  1. Sunbird trashed my data.

    The open source Sunbird calendar program erroneously lost 10 years of data in my main iCalendar file—it dropped all data in the file during one of its very frequent updates leaving it completely empty, and I had to resort to restoring a weeks-old manually backed-up archive file copy. This should never happen: something as important as calendar data should be saved only on request or exit, and should be backed up before being overwritten. As is, Sunbird updates an iCalendar file on every edit, with no backup of any kind.

    Sunbird also provides functionality I never used, and changes the number of weeks in its month display dynamically, which was visually confusing. Given its fixed interface choices and looming data loss risk, using Sunbird was no longer an option. In its defense, Sunbird is no longer maintained and has been subsumed by a much broader solution, and has other usage modes that may not use iCalendar files directly; neither of these solutions were relevant to my goals.

  2. Other free options seemed too simple, too complex, or too connected.

    I could find no other open source or free alternatives that suited my tastes and needs. Some seemed toyish—possibly because Outlook's dominance in the domain limits developer interest. Others seemed either radically over-engineered (e.g., Lightning) or too web-centric (e.g., Google Calendar): I need just a calendar, not a grandiose organizational paradigm, and not a system that requires a network connection, web browser, and account login just to look up a date or event.

    Email integration seems overkill for a calendar, and people really aren't always online, despite what you may have heard. Moreover, reworking any of the open source options would be much more work than starting from scratch, especially when coded in other, more tedious programming languages than Python. And we'll leave here aside the implications of having to log into a server account managed by a third party whose bread-and-butter is tracking people's lives for targeted advertising, apart from saying that privacy matters—"free" online calendar providers are also free to read your data.

  3. Outlook isn't an option for me.

    Microsoft Outlook may be pervasive, but has major downsides—it is no longer included in basic Office bundles and must be purchased separately; has quirks that drove me to Sunbird years ago; is far too feature rich for my use case; seems to support the open/portable iCalendar format only grudgingly; and may move to a subscription model in the future, and/or be used to force users to Microsoft's cloud service.

    The latter point is subjective, but alarming; Outlook's owners have lately taken to parroting a "mobile first, cloud first" mantra that seems a dark omen for developers and users alike. Cloud storage especially comes with some very negative consequences. More pragmatically, Outlook's utility just doesn't justify the cost and vendor dependence to me. In the same vein, Apple Calendar (a.k.a. iCal) shares many of Outlook's platform-specific downsides, as well as Google Calendar's cloud-focused negatives (and there are still a few people on the planet who don't use Apple hardware for such reasons).

Taken together with paper calendars' inherent limitations, a custom coded solution seemed best.

 

Design and Results

My use case requires only a simple "refrigerator"-style personal calendar, that uses a portable data file format, and allows notes to be displayed and edited on days freely. I don't use or require email integration, task planning, journaling, reminder alarms, meeting invitations, and the like—specific tools and usage modes which the iCalendar standard oddly seems designed to formalize and even mandate. I also don't have events that recur or span multiple days often enough to justify the complexity of their automation in this release. Some of these may be useful to others (and a few may show up here in future versions), but they also scale up program size and complexity in ways that can lead to error states and data loss, and make user interfaces unwieldy and unnatural.

Those goals and constraints make this program limited by comparison to some, but this is by design: it works, does what I require, and doesn't grow wildly complex and error-prone for rarely-used features. As open source, it can also be expanded as desired if you miss some utility here; in fact, its code was written with a primary focus on readability for this purpose. There are ideas for extensions in the next major section, if you care to experiment yourself.

Metrics

In its current form, this program is 4,002 lines of code among 11 source files at latest count. This includes comments, but not this documentation file, demo files, or any libraries or packages employed; see and run the included __sloc__.py to count live.

In terms of effort, this system required just 3 weeks of initial development time, during which 3 versions were coded—first using functions, then using classes for structure, and finally using refactored classes for edits. Later releases naturally consumed additional occasional time, raising the project's total span to 3.5 months, when that tally was last updated here. (Later stats: the 1.4 release took an additional week and a half; 1.5, 1.6, and 1.7 required roughly 1 week each; and the project has spanned 2 total years as of version 1.7.)

Those fairly small sizes and short schedules are due in part to the existence of the complete 3rd-party icalendar iCalendar parser/generator package in the Python open source world—a commendable library which accelerated development, but on which this program's correctness is also very much dependent. This is an inherent tradeoff in the "batteries included" paradigm promoted by tools such as Python. The good news in this case is that icalendar has worked without flaw for two years as of this writing.

Usability

Despite such tradeoffs, the results seem compelling: besides serving as a supplemental example for readers of my books, I use the frigcal calendar GUI on a regular—and often daily—basis, as part of a personal tools set that also includes the mergeall directory synchronization GUI, as well as examples from the book Programming Python, 4th Edition, including:

For more on this latter set of programs, see this page.

Although there are precoded alternatives to most of the above, custom-coded programs like these serve real-world purposes; are widely portable; have fully-known behavior; and may evolve entirely at their author's discretion. As usual, when equipped with a tool like Python and bit of free time and coding skill, one need not necessarily settle for software that is limited, proprietary, intrusive, or buggy.

 

Future Directions?

This program works as intended today, but is naturally open to evolution. Here are some ideas for future enhancements, and a few early implementation musings.

Day's Events List—Resolved in 1.3:
This issue was addressed in version 1.3, with the new event selection list dialog, triggered by left-clicks on the day number area, and described in the 1.3 release notes earlier in this document. The prior description that follows is retained, as it contains other and still valid workarounds; in the end, this extension proved useful, and perhaps even required for some use cases.

Prior description: As described earlier, there is by design a practical, physical limit on the number of events that can be displayed on a given day. This varies by font, window, and screen sizes, but can be just 3 to 5 in some contexts, and even 3 or 4 events per day is enough to limit the footer text's display on some smaller screens (though details can still be viewed in the event dialog with a click). A popup triggered by a button for days having more events than a configurable maximum is feasible, but would naturally complicate the GUI and implementation.

The workaround mentioned earlier—when needed, combining multiple events into a single event, listing them individually in the combined event's description text—seems reasonable for a personal-use tool. This GUI generally strives to show useful information without requiring extra interactions, in keeping with its "refrigerator" calendar metaphor; additional clicks and popups that encourage long event lists might violate this goal.

Android (and Other) Tablets:
This program has been used on both laptops and Windows 8.1 and 10 tablets. It might also work well on an Android tablet, though most phone screens may be too small for its display to be usable. This would require porting Python's tkinter GUI library to Android, but a port of its underlying Tcl/Tk support is already underway, and a tkinter port would enable a host of open source Python tools. For more details, see this post, and watch the web for developments on emerging tkinter platforms. At present, this idea seems sorely in need of traction.

Apple iPad tablets are another obvious candidate platform (but their proprietary architecture seems less likely to support this program), and Linux tablets likely run this program as is (though their market remains to be seen). Naturally, frigcal does run on all Windows tablets today, as a normal desktop program; see the screenshots section for examples of it in action.

Frozen Executable:
This programs seems a prime candidate for distribution as a frozen binary, which does not require a Python install step to run. See cx_freeze, Py2Exe, and PyInstaller for packaging options. At first glance, the MS Visual C++ DLL requirement on Windows may complicate this in some cases (if users must install a DLL, why not just install Python instead?), and Python 3.X support may still be sketchy as well.

Recurring Events:
A likely next step is implementing basic recurring events: weekly, monthly, and annual events. Currently, these can be simulated manually by cutting and pasting events on recurring days, just as one would do on a paper refrigerator calendar—this program's target usage model.

That said, automatic recurrence might be implemented with iCalendar RRULE file entries for events; new tables for these events, indexed by weekday, day number, and month+day; and additions in the GUI's event View/Edit dialog to specify repeats. In fill_events(), recurrence days in the displayed month matching the new tables' entries would display a virtual event, but clicks on these events would all open the original event object for which the RRULE recurrence was created (unless individual instances are allowed to differ, a usage mode that seems both subtle and complex). Recurrence dates that do not exist in a viewed month would also pose dilemmas (e.g., what to do for events that recur on the 30th of every month, when viewing a February?).

In any case, note that the iCalendar standard supports a very rich range of recurrence specifications; this program would probably be best served by implementing a small subset for simplicity, recognizing and generating RRULE file entries with known recurrence patterns only.

Spanning (Multiday) Events:
Events spanning days may seem a logical later addition, but may also violate the simplicity goals for the GUI and its code. This raises subtle issue with events that cross month boundaries, and others that seem to stretch the calendar metaphor to its breaking point (e.g., should events that span days also be allowed to recur?). Some of these may be too complex for a basic calendar program, and event cut/paste seems a far easier option for spans—in terms of both implementation and usage.

Images Option Ideas:
As mentioned previously, the Images option's current popup window model is tentative, and prone to change. This was initially intended to imitate the photos that fill the top half of a true refrigerator calendar, but may be prove too distracting and gimmicky in a GUI. A future version could show resized photos on the month display itself, or via a pulldown.

Update: the related enhancements added in 1.4 and 1.5—alternate image sets, subfolder and non-image file skipping, initial positioning, and pairing of images to their months for window hides/unhides—make this feature more useful and solid (e.g., it's easier to create new month image sets of your own, and images are now more strongly associated with months in the GUI); see 1.4 and 1.5 release notes. Version 1.6 further improves this story by making the third-party Pillow install optional for some image file types and Pythons (e.g., PNG and Python 3.4+). Given the changes, images now seem a likely permanent feature, though still subjective enough to be optional.

Event Search:
It may be useful to provide search functionality that locates events with matching keywords or content. A pop-up selection list would direct the GUI to the month and day of the located and selected event, or to the event itself. This might be particularly useful for calendars with many years of events (like those of this note's author), though use of particular keywords seems unlikely to be consistent in the dim calendar past.

Event Drag-and-Drop:
As mentioned earlier, event drag-and-drop move operations were omitted intentionally, due to the general inaccuracies of drag-and-drop on tablets and other touch devices, especially for desktop-metaphor interfaces. Indeed, drag-and-drop can be triggered much too easily, and seems to occur accidentally as often as deliberately in a Windows file explorer. In a calendar loaded with events, unintended drag-and-drops could be common, and are more likely to be annoying than useful. Instead, more deterministic right-click cut-and-paste operations are provided for moving events between days and months.

Still, ergonomics are always partly subjective, tkinter does support drag-and-drop operations, and this is a candidate for exploration. To pursue on your own as an extension, see Python's tkinter.dnd module in its Lib\tkinter folder for one interface. This may or may not require GUI redesign.

Tandem Minimize/Restore:
As described earlier, the Tandem button causes all month view widows (and their images) to navigate in synch between previous/next months and years. In principle, Tandem could also cause all month windows (and their images) to minimize whenever any is minimized, and restore whenever any is restored. That is, all windows would open and close in synch if Tandem were enabled. This isn't implemented because it's not clear that it's useful or desirable. Specifically:

Hence, this seems too rare a use case and too extreme a policy decision to mandate without user feedback. If you're a user and have any, please contact the system's author (see the top or bottom of this page). Or try coding this extension yourself—see the open/close pairing of images to month windows on "<Unmap>" and "<Map>" events in frigcal.py for hints, as well as portability issues on Linux (see version 1.6).

Refactor as Attachable Component:
As shipped, frigcal is oriented towards running as a standalone program, albeit with multiple copies (clones) of its main window. It may be a minor translation to recode it as a tkinter widget package, that may be attached to other GUIs as a nested component. Essentially, the 'root' passed in would have to be variable, and not hardcoded as a new Tk() or Toplevel(); the latter would be only precoded use cases for leveraging the code as standalone program.

This wasn't pursued, because the frigcal GUI seems too large to be useful as an attached component. This is especially so when there are events displayed on days. Moreover, frigcal is probably overkill if you want just a view-only calendar of eventless days (though its code may serve as inspiration for such a component). See the book Programming Python for more on coding GUIs that serve as both program and component; its PyEdit text editor and PyCalc calculator are prime examples of such dual-mode code. PyEdit, for example, serves both as standalone program, and attached component in PyMailGUI.

And so on:
This program has not yet been formally verified on Mac OS X; a user reported initial success, but testing time and budget has not yet allowed more rigorous validation. Consideration was also given to using a PyEdit component for event description edits in frigcal's event dialog, instead of the current simple scrolled text; this would add search and cut/copy/paste buttons, but was rejected because it would also add GUI clutter for rarely-used utility, and frigcal's simplicity seems one of its most valued features—which will suffice as a segue to this document's next and final section.

 

A Word on Extensions

One of this project's underlying goals has been to produce a useful and intuitively usable tool with no more functionality than is required for its intended roles. The temptation to add features like those of the prior section reflects a tension common to all software projects, and merits a note of perspective here.

In the end, the ease with which functionality can be added with a tool like Python is both asset and liability. Programs like this must find a balance between features and usability that stays focused on the original use case. Over-engineering seems an unfortunately common downfall in software—especially in the open source domain, where changes often come with little or no analysis of their impact. When unchecked, this can quickly lead to a state known to developers as feature creep: a cascade of enhancements that conspires to render a system unusable.

Less technically—but just as importantly—over-automation turns a program's users into detached operators, instead of active participants, and comes with often unexpected consequences. Personal anecdote: a company recently failed to make a payment owed to me, because they scheduled it to occur on a nonexistent day in February (comical but true: the check didn't get cut because it wasn't a leap year). Had the company's employees been required to manually post the payment date to a calendar, this probably would not have happened.

Software works best when it is a tool, not a replacement. This is as true for a basic calendar GUI as it is for systems of broader scope. Engaging users by avoiding unwarranted automation can not only make programs more relevant to real goals, it might just sidestep mistakes that people wouldn't otherwise make. Be it this program or others, the same rule applies: don't complicate things that don't need to be complicated.



[Python Logo] Email Books Training Main © Mark Lutz