(Current version: 1.0 - released 6 Jun, 2008)
This article is an attempt to describe how I created a Calendar plug-in for .dan.g.'s ToDoList. It may be useful to you for two reasons:
- You might simply want to download the DLL and get a useful Calendar view of your task list.
- You may want to go that one step further and write your own plug-in.
If you're here for reason #1 and scared of reason #2, please don't worry. Read on - it's easy!
When I started using .dan.g.'s mighty ToDoList, it made me rethink the way I approached my tasks. Previously I had to try to actually remember (ugh) every minor thing that needed to be done before I could regard a task as fully completed. Now to be honest I have a memory worse than a goldfish, so finding the ToDoList was a godsend. I now break down all my tasks recursively into squillions of subtasks and my memory is no longer required (leaving it free to concentrate on other equally important things, such as my favourite Bill Hicks jokes, or the theme tune to the Mysterious Cities Of Gold. "Aaaaaah ahaa ahaaaaa someday we will find the cities of gold..." - quality!).
Having a large number of tasks and subtasks poses a problem because it is difficult to figure out how far ahead/behind of schedule you are (after all, these are what your boss cares about. Get 'em wrong and it could mean a visit to the Jobcentre. Again.). So, I started spending a lot of time setting the Start Date and Due Date for all my tasks, but this just wasn't enough. I couldn't visualize my progress in my head, no matter how accurate/up-to-date the Start/Due Dates were. What I wanted was a quick overview of my progress against the schedule, a visualization of my project.
What I needed was a calendar-type view within the ToDoList itself. Of course, I could have tried to export my data somehow and import it into another program, but where's the fun in that? This was already on .dan.g.'s own ToDoList, so I decided to help him out. It's the least I could do. And he gave me extensive advice on how to go about writing my own ToDoList Calendar plug-in.
The file CalendarExt.dll needs to be placed in the ToDoList directory.
The Calendar can be activated from ToDoList's [Tools->Calendar] menu item. This displays the modeless Calendar window for the active task list (switching task lists will hide/show the Calendar window appropriately). The Calendar window is split into two panes:
- Left hand pane =
- Right hand pane =
MiniCalendar pane initially contains the current month and the next month. If the window is resized large enough, it will also contain the following month.
- Clicking the left/right scroll arrows on the first month will move to the previous/next month accordingly.
- Clicking on the title of a month will display a list of months, from which you can select a month to jump to.
- Clicking on a date in the
MiniCalendar will jump to that date in the
- Clicking the "Today" menuitem will jump to that date in both calendars.
BigCalendar pane initially contains each date in the 6-week period surrounding today's date.
- A date in the
BigCalendar contains all tasks in the active task list with a Start/Due Date equal to this date.
- Start Date Tasks are shown with a green marker.
- Due Date Tasks are shown with a red marker.
- Any dates containing one or more tasks will be highlighted in yellow, in both calendars.
MiniCalendar pane can be hidden/shown using the [View->Mini Calendar] menu item.
BigCalendar can be manoeuvred using the scrollbars, mouse wheel, arrow keys and PageUp/PageDown.
- Clicking on a task in the
BigCalendar (or selecting it using the arrow keys and pressing Return) will jump to that task in the active task list.
- Renaming a task in the active task list will automatically update the task's name in the
- Changing a task's Start/Due Date in the active task list will automatically move the task in the
The Technical Bit
MiniCalendar are third party classes which I most graciously "stole" (is that the right word?) from other articles at The Code Project. If I use someone else's class, I like to spend some time working out what it is doing and why it is doing it. I like to rewrite the code in my own style so I can (a) understand it better and (b) debug it easier. Any part of the class I don't need will be ripped out. This is what I've done with the following three classes that I used in the Calendar:
- The right hand pane (
BigCalendar) is a beautiful custom-drawn calendar by Frederic Rezeau. The main change I made to this class was to use
CListBoxes in each date instead of drawing the tasks as text directly.
- The left hand pane (
MiniCalendar) is a cool Microsoft Outlook-style mini-calendar by Matt Gullett.
CListBoxes in the
CTransparentListBoxes by Ali Rafiee. I also added tooltip support to this class.
ToDoList's Plug-in Architecture
ToDoList uses plug-ins to provide a number of ways of achieving different customizations to the main application. There are several types of plug-ins already in use, including Encryption, Custom Comments, Task Importers/Exporters and UI Extensions. The benefits of using a plug-in architecture include:
- Keeping the implementations of different features distinct, making maintenance considerably easier for all parties.
- Avoiding increasing the size and complexity of the core application unnecessarily.
- Making it easier and more accessible for third parties to create their own plug-ins, rather than editing (and perhaps breaking) the code of the core application.
- Allowing an entire chunk of unneeded functionality to be removed just by deleting a DLL.
All of ToDoList's plug-ins are architected the same way: the plug-in resides in a DLL and exports one (or more) C functions (their names are unmangled/undecorated). One of these exported functions is typically a
CreateXXX() function that returns a pointer to a pure virtual interface (class) that ToDoList then uses to communicate with the plug-in. In the case of UI Extension plug-ins, this function is called
Whilst a full discussion of pure virtual interfaces is beyond the scope of this article, essentially they define a contract which the plug-in commits to supporting without requiring any explicit binding between the two components. You might say that they are the C++ equivalent of calling
GetProcAddress on a loaded DLL. Pure virtual interfaces are at the heart of COM and are a proven method for decoupling the interface and implementation of a component.
- Note 1: ToDoList avoids using COM simply because of the registration requirements which would prevent plug-ins working off a USB stick.
- Note 2: ToDoList typically loads plug-ins at start-up and then holds onto the interface pointer for the duration of the session.
For UI Extension plug-ins, ToDoList provides two interfaces, both located in Shared\IUIExtension.h:
IUIExtension interface is a high-level interface that allows ToDoList to create plug-in windows and also obtain menu-text and icon information from the plug-in so that a menu item can be added to the [Tools] menu for each plug-in.
IUIExtensionWindow interface represents the window that the user will actually interact with. This is where the guts of the plug-in reside, responding to editing notifications from ToDoList and optionally sending selection events back to ToDoList when the user clicks on something.
Creating the Calendar Plug-in
Here are the 10 Easy Steps™ I used to create a basic working Calendar plug-in:
Stage 1: Create Project, Create Classes, Create Interface Stubs
- Create a new C++ DLL project, called
CalendarExt. Change the linker settings so that CalendarExt.dll is built to the same folder as ToDoList.exe.
- Create two new classes,
CCalendarFrameWnd, derived from
IUIExtensionWindow respectively, containing stubs for all the pure virtual functions.
- Create an exported function called
CreateUIExtensionInterface (also defined in Shared\IUIExtension.h) which simply returns a pointer to a newly created (or
static) instance of
- Implement the
CCalendarExtApp::GetMenuText function to return "Calendar" and implement the
CCalendarExtApp::GetIcon() function to return an appropriate icon.
- Compile the project and restart ToDoList.exe. Check that ToDoList is finding the plug-in by examining the [Tools] menu for a menu item called [Calendar], with the correct icon. Note: at this point, selecting the menu item will have no effect because this has not been wired up yet.
Stage 2: Implement Interface Functions, Create Window
CCalendarFrameWnd::Create() to create a suitable window for the user to interact with. I chose to derive
CFrameWnd, but you could use a modeless
CView or whatever.
CCalendarExtApp::CreateExtensionWindow() to return a pointer to a newly created instance of
CCalendarFrameWnd and call this window's
- Recompile the project and restart ToDoList.exe. Check that selecting the [Tools->Calendar] menu item displays an instance of the window. This is also a useful time to check that switching between multiple task lists correctly hides and displays the correct window, since each open task list gets its own instance of the plug-in window. Switching task lists should close the plug-in window of the current task list and open the plug-in window of the new task list (if it has one).
Note: ToDoList looks after all the display and hiding of plug-in windows; the plug-in's responsibility is simply to implement the pure virtual interface sensibly.
Stage 3: The Not-quite-so-easy Bit - Implement What will be Displayed in Your Window, Respond to Task List Update Events
- Modify the
CCalendarFrameWnd class to display the
CMiniCalendarCtrl windows in its client area, and to respond to resizing events.
- Implement two-way communication (if necessary) between your plug-in and the task list. This involves:
- Adding code to respond to editing events from the active task list. When a task's attributes are changed,
CCalendarFrameWnd::Update() gets called. From here, I update my
CCalendarData object (a class to cache all tasks for the active task list) with the updated information. I then call a
TasklistUpdated function in both the
CMiniCalendarCtrl objects, which tells them to update their views using the latest data.
- Add code to notify the active task list from the plug-in. In my case, when the user clicks on a task in the calendar, I send the
WM_TDCM_TASKLINK message to the main ToDoList application window, along with the ID of the clicked task. ToDoList interprets this message and selects the specified task in the active task list.
That's it! I won't bore you with further details about how the Big/MiniCalendars work. You haven't got time for that! What are you waiting for? Go write a plug-in now!!
This version of the ToDoList Calendar plug-in is ready to go with version 5.4 or later. Please keep an eye on this page for updates. All comments, bugs, grumbles or suggestions are welcome.
Big thanks go to the writers of the three classes that I, erm... borrowed, namely:
- Frederic Rezeau
- Matt Gullett
- Ali Rafiee
A massive thanks also obviously to .dan.g., who in my opinion is an inspiration to open source coders. I don't think I've ever come across such a nice guy with such quick bug/request turnaround and dedication to his user-base. Dan has been very patient with me and helped me lots with his plug-in architecture and suggestions for the Calendar, helping me to fix some dodgy bugs, as well as giving me loads of information for this article!
- Task colouring to match the colours in the active task list
- Drag and drop a task from one date into another
- Pop-up menu on a task
- Extra keyboard handling, such as the Delete key to remove a task
- Print window contents (to printer and to file)
- Add intermediate dates between Start and End (and Completed?)
- Add Due Time feature from TDL 5.5
- 1.0 alpha (12 Jan 2008)
- 1.0 alpha.2 (16 Jan 2008)
- New: Esc closes the Calendar window
- New: Tooltips for tasks
- New: Maximize button on title-bar
- Fixed: Non-ASCII character support in day cells
- 1.0 alpha.3 (12 Feb 2008)
- New: Show/Hide Weekends - use the [View->Weekends] menu
- New: Remembers [View->Weekends] and [View->Mini Calendar] settings (in the ToDoList.ini file)
- Fixed: Immediately after opening the Calendar window the second time, key presses do nothing
- Fixed: First day of the week is now set according to the current regional settings, in both MiniCalendar and BigCalendar
- 1.0 alpha.4 (9 Mar 2008)
- New: Save window position, including maximized state
- New: Select the number of weeks to display - use the [View->Number Of Weeks] menu
- New: Select the format of completed tasks - use the [View->Completed Tasks...] menu
- New: Add the name of the associated tasklist to the Calendar window titlebar (requires TDL version 5.4.9 or higher)
- Fixed: Calendar flicker when editing the comments pane of a task in the main tasklist
- 1.0 alpha.5 (30 Apr 2008)
- New: Added status-bar to show additional information for the selected task - use the [View->Status Bar] menu
- New: Tooltips now display full path of hovered item
- New: Changed task double-click behaviour to single-click - added hand-cursor to make it more obvious
- New: Days from previous/next months in MiniCalendar are now shown in grey
- New: Replaced the Today button with a Go To Today menuitem
- New: Mousewheeling over the MiniCalendar now scrolls the BigCalendar
- Fixed: Tooltips that disappeared after timeout did not reappear
- Fixed: Unified fonts across BigCalendar and MiniCalendar
- Fixed: Now displays correct days of week in non-English languages, in BigCalendar
- Fixed: Updating child tasks when marking parent task as complete
- Fixed: Could right-click to select multiple tasks across multiple cells, in BigCalendar
- 1.0 (6 Jun, 2008)
- New: Added auto-update functionality - use the [Check for Updates...] menu
- Fixed: GPF when displaying a very long tooltip
- Fixed: Focus lost after switching back to Calendar window from another app
- Fixed: Focus lost after using the MiniCalendar Month Picker
- Fixed: Month names in BigCalendar were not being displayed if the 1st day of the month is hidden because it's on a weekend
- Fixed: Minor UI improvements