 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Mon Jun 22, 2009 5:36 pm Post subject: Conspicuous todo list combined with Google Calendar |
|
|
I use a calendar to schedule various tasks and I always wanted a desktop popup for due tasks with the ability to add the tasks immediately to a todo list, so when I acknowledge the notification, the popup disappears and the tasks is added to my todo items. Furthermore, I like to see a permanent indicator on the screen if I have pending todo items.
That's what this script does. It's pretty much does what I want and I don't plan many more improvements, so I put it here as is, if someone's interested.
The script also provides an effortless way to add new items to the calendar using the Quick Add syntax of Google Calendar. The Quick Add format is so easy and effortless to use that I even use it to schedule trivial tasks at home for which I wouldn't use a calendar otherwise. E.g. "walk the dog 5pm"
The script is completely keyboard driven:
pause shows the todo list if there are items.
pause again shows the quick add dialog which can be used to add calendar items or regular todo items
in the quickadd dialog enter adds the typed item, esc cancels the dialog. It will be a calendar item if the last word contains a number (e.g. "do something 4pm" - see the quickadd syntax), otherwise it will be a regular todo item. In case of a calendar item if the item text contains the word "silent" then no msgbox is shown when the entry is due and it is added silently to the todo list.
delete deletes items from the list.
insert can be used to quickly reschedule an exitsing item, so it disappears from the list until its time comes again.
If the script is called with a parameter then it fetches new items from the calendar when started. It also fetches new items every time when a new calendar item is added.
| Code: | datadir = %A_AppData%\gcalahk
listfile = %datadir%\list
incomingfile = %datadir%\incoming
configfile = %datadir%\config
max_list_backups = 10
gui2_w = 40
gui2_h = 40
gui2_bgcolor = 99FF00
Gui 2: +LastFound -Caption +AlwaysOnTop +ToolWindow
Gui 2: Font, s20
Gui 2: Color, %gui2_bgcolor%
Gui 2: Add, Text, vCount, 99
SysGet, Monitor, Monitor
gui2_x := MonitorRight * 2 / 3
Gui 2: show, w%gui2_w% h%gui2_h% x%gui2_x% y0
Gui 2: hide
GuiControlGet Count, 2:Pos
countx := (gui2_w - countw) / 2
county := (gui2_h - counth) / 2
GuiControl 2: Move, Count, x%countx% y%county%
ifnotexist %datadir%
{
FileCreateDir %datadir%
if (errorlevel <> 0)
{
msgbox Cannot create data directory: %datadir%
exitapp
}
}
ifnotexist %configfile%
{
MsgBox Please create the config file: %configfile%
exitapp
}
Gui +LastFound -Caption +ToolWindow
Gui, Font, s20
Gui, Color, 87CEFA
Gui, Add, ListBox, w600 h400 vTodoList AltSubmit
gui3_w := 400
edit3_w := gui3_w - 30
Gui 3: Font, s15
Gui 3: Add, Edit, vQuickAdd w%edit3_w%
read_entries()
if (%0% > 0)
{
param = %1%
if (param == "delay")
sleep 30000
fetch_events()
}
refresh_listbox()
SetTimer check_for_notification, 60000
gosub check_for_notification
#ifwinactive Conspicuous todo list ahk_class AutoHotkeyGUI
pause::
show_quickadd(false, "")
return
#ifwinactive
pause::
if (listbox_count == 0)
show_quickadd(false, "")
else
Gui, Show, , Conspicuous todo list
Return
#ifwinactive Quick Add ahk_class AutoHotkeyGUI
enter::
gosub guiclose
if (quickadd_reschedule)
gosub delete_current_item
gosub quickadd
return
pause::
show_quickadd(false, "")
Gui 3: Hide
return
#ifwinactive
#ifwinactive Conspicuous todo list ahk_class AutoHotkeyGUI
insert::
guicontrolget pos,,TodoList
text := listbox_text_%pos%
time := listbox_time_%pos%
hour := substr(time, 9, 2)
minute := substr(time, 11, 2)
date := a_now
date += 1, day
year := substr(date, 1, 4)
month := substr(date, 5, 2)
day := substr(date, 7, 2)
date = %year%/%month%/%day% %hour%:%minute%
show_quickadd(true, text . " " . date)
return
delete::
gosub delete_current_item
if (listbox_count == 0)
gosub guiclose
return
#ifwinactive
delete_current_item:
guicontrolget pos,,TodoList
listboxpos := pos
delete_text := listbox_text_%pos%
delete_time := listbox_time_%pos%
loop %num_of_entries%
{
if (entry_text_%a_index% == delete_text and entry_time_%a_index% == delete_time)
{
entry_state_%a_index% := "deleted"
break
}
}
write_list()
refresh_listbox(listboxpos)
return
guiescape:
gosub guiclose
return
3guiescape:
gosub guiclose
return
guiclose:
gui, cancel
gui 3: cancel
return
check_for_notification:
StringLeft now, a_now, 12
newevent := false
loop %num_of_entries%
{
text := entry_text_%a_index%
time := entry_time_%a_index%
state := entry_state_%a_index%
if (state == "pending" and time <= now)
{
if (RegExMatch(text, " s$") == 0)
{
MsgBox 1, Todo, %text%
IfMsgBox OK
entry_state_%a_index% := "active"
else
entry_state_%a_index% := "deleted"
}
else
entry_state_%a_index% := "active"
newevent := true
}
}
if (newevent)
{
write_list()
refresh_listbox()
}
return
read_entries()
{
global
StringLeft today, a_now, 8
today .= 0000
num_of_entries = 0
Loop, read, %listfile%
{
;; why do we need to use the clipboard for unicode trasformation?
oldclip := clipboard
transform clipboard, unicode, %A_LoopReadLine%
entry := clipboard
clipboard := oldclip
stringsplit fields, entry, %A_Tab%
time := fields2
state := fields3
; purge old deleted items
if (state == "deleted" and time < today)
continue
num_of_entries += 1
entry_text_%num_of_entries% := fields1
entry_time_%num_of_entries% := time
entry_state_%num_of_entries% := state
}
}
refresh_listbox(deletedpos = 0)
{
global
entries =
listbox_count = 0
loop %num_of_entries%
{
text := entry_text_%a_index%
time := entry_time_%a_index%
state := entry_state_%a_index%
if (state == "active")
{
entries .= "|" . text
listbox_count += 1
listbox_text_%listbox_count% := text
listbox_time_%listbox_count% := time
}
}
if (listbox_count == 0)
entries = |
GuiControl,, TodoList, %entries%
if (listbox_count > 9)
GuiControl 2: , Count, %listbox_count%
else
GuiControl 2: , Count, %a_space%%listbox_count%
if (listbox_count == 0)
Gui 2:Hide
else
Gui 2:Show, NoActivate
if (deletedpos == 0)
GuiControl, Choose, TodoList, 1
else
{
if (deletedpos > listbox_count)
deletedpos := listbox_count
GuiControl, Choose, TodoList, %deletedpos%
}
}
quickadd:
GuiControlGet event, 3:, QuickAdd
calendar_event := false
FoundPos := RegExMatch(event, ".*\s(\S+)", match)
if (foundpos <> 0)
{
FoundPos := RegExMatch(match1, "[0-9]")
if (foundpos <> 0)
calendar_event := true
}
if (calendar_event)
fetch_events(event)
else
{
StringLeft now, a_now, 12
num_of_entries += 1
entry_text_%num_of_entries% := event
entry_time_%num_of_entries% := now
entry_state_%num_of_entries% := "active"
write_list()
refresh_listbox()
}
return
fetch_events(newevent = "")
{
global
RunWait python cal.py "%incomingfile%" "%newevent%",,hide useerrorlevel
if (errorlevel <> 0)
{
msgbox Error when contacting server. Check the error log file.
return
}
new_event_added := false
Loop, read, %incomingfile%
{
;; why do we need to use the clipboard for unicode trasformation?
oldclip := clipboard
transform clipboard, unicode, %A_LoopReadLine%
entry := clipboard
clipboard := oldclip
stringsplit fields, entry, %A_Tab%
text := fields1
time := fields2
entry_is_known := false
loop %num_of_entries%
{
if (entry_text_%a_index% == text and entry_time_%a_index% == time)
{
entry_is_known := true
break
}
}
if (entry_is_known)
continue
num_of_entries += 1
entry_text_%num_of_entries% := text
entry_time_%num_of_entries% := time
entry_state_%num_of_entries% := "pending"
new_event_added := true
}
if (new_event_added)
write_list()
}
write_list()
{
global
entrydata =
loop %num_of_entries%
{
oldclip := clipboard
clipboard := entry_text_%a_index% . a_tab . entry_time_%a_index% . a_tab . entry_state_%a_index% "`n"
transform text, unicode
entrydata .= text
clipboard := oldclip
}
filecopy %listfile%, %listfile%.%A_Now%.%A_TickCount%
filedelete %listfile%
fileappend %entrydata%, %listfile%
oldfiles =
Loop, %datadir%\list.*
{
if (oldfiles <> "")
oldfiles = %oldfiles%`n
oldfiles = %oldfiles%%A_LoopFileTimeModified%`t%A_LoopFileName%
}
Sort, oldfiles, R
count := 0
Loop, parse, oldfiles, `n
{
count += 1
if (count > max_list_backups)
{
StringSplit, FileItem, A_LoopField, %A_Tab%
FileDelete %datadir%\%FileItem2%
}
}
}
show_quickadd(reschedule, text)
{
global
GuiControl 3:, QuickAdd, %text%
quickadd_reschedule := reschedule
Gui, Hide
GuiControl 3: Focus ,QuickAdd
send ^{end}^{left}^{left}^+{end}
Gui 3: Show, w%gui3_w%, Quick Add
}
|
Accessing Google Calendar is done with the Python calendar library. Here are the instructions on how to install it, and here's the python script called by the AHK script. Put them in the same directory.
If you want to test calendar access by running the python script manually first, then the first parameter of the script is the path of the file into which the fetched calendar items are stored. The config file must be in the same directory as the file, so the script uses this path to determine the location of the config file.
| Code: | import gdata.calendar.service
import atom
import codecs
import datetime
import re
import sys
import os.path
import ConfigParser
import logging
import logging.handlers
LOG_LEVEL = logging.INFO
LOG_FORMAT = '%(asctime)s %(levelname)-8s %(message)s'
#----------------------------------------------------------------------
logging.basicConfig(level = LOG_LEVEL, format = LOG_FORMAT)
collog = logging.handlers.RotatingFileHandler("errorlog", None, 100000, 10)
collog.setLevel(LOG_LEVEL)
collog.setFormatter(logging.Formatter(LOG_FORMAT))
logging.getLogger().addHandler(collog)
#----------------------------------------------------------------------
try:
outfile = sys.argv[1]
quickadd = sys.argv[2] if len(sys.argv) > 2 else None
config = ConfigParser.ConfigParser()
config.read(os.path.join(os.path.dirname(outfile), "config"))
user = config.get('config', 'user')
password = config.get('config', 'pwd')
calendar = config.get('config', 'calendar')
calendar_service = gdata.calendar.service.CalendarService()
calendar_service.email = user
calendar_service.password = password
calendar_service.source = 'Google-Calendar_Python_Sample-1.0'
calendar_service.ProgrammaticLogin()
if quickadd:
event = gdata.calendar.CalendarEventEntry()
event.content = atom.Content(text = quickadd)
event.quick_add = gdata.calendar.QuickAdd(value='true')
new_event = calendar_service.InsertEvent(event, '/calendar/feeds/%s/private/full' % calendar)
query = gdata.calendar.service.CalendarEventQuery(calendar, 'private', 'full')
query.start_min = datetime.date.today().strftime("%Y-%m-%d")
query.start_max = (datetime.date.today() + datetime.timedelta(1)).strftime("%Y-%m-%d")
feed = calendar_service.CalendarQuery(query)
f = codecs.open(outfile, "w+", "utf-8")
for i, an_event in enumerate(feed.entry):
for a_when in an_event.when:
# skip timezone info, since the time
# seems local anyway
m = re.match("^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})(T(?P<hour>\d{2}):(?P<minute>\d{2}):)?",
a_when.start_time)
assert m
start_time = m.group("year") + m.group("month") + m.group("day")
if m.group("hour"):
start_time += m.group("hour") + m.group("minute")
else:
start_time += "0000"
print >> f, '%s\t%s' % (an_event.title.text.decode("utf-8"), start_time)
f.close()
except:
logging.exception("Unhandled exception")
sys.exit(1)
|
Fill the config file with the values required to access the calendar and put it in datadir (see the AHK script for its location). Calendar can be your primary calendar (default) or you can create a separate calendar for these todo items (I do that). In the latter case find the private calendar url in calendar settings and copy the email address-like part from it (....@group.calendar.google.com). That's the calendar id.
| Code: |
[config]
user=xxx@gmail.com
pwd=yourpassword
calendar=default
|
Last edited by keyboardfreak on Fri Aug 21, 2009 4:26 pm; edited 10 times in total |
|
| Back to top |
|
 |
bnbn2000
Joined: 10 Jul 2006 Posts: 6
|
Posted: Mon Jun 22, 2009 5:43 pm Post subject: |
|
|
| I also want on of this. Thanks! |
|
| Back to top |
|
 |
cedardoc Guest
|
Posted: Mon Jun 22, 2009 7:55 pm Post subject: |
|
|
Has anyone got this to work on windows 7? I tried it with python 2.6 and 3.0 and get the same error "Error when contacting server Errorlevel: ERROR"
or, has anyone come across another script to do quick adds to google calendar without opening the browser?
(I'd love to be able to use this!) |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Mon Jun 22, 2009 7:59 pm Post subject: |
|
|
| cedardoc wrote: | | Has anyone got this to work on windows 7? I tried it with python 2.6 and 3.0 and get the same error "Error when contacting server Errorlevel: ERROR" |
The python script creates an error log file when a problem occurs. Take a look at it. It may give you a more detailed error message. |
|
| Back to top |
|
 |
cedardoc Guest
|
Posted: Mon Jun 22, 2009 10:38 pm Post subject: |
|
|
| keyboardfreak wrote: | | cedardoc wrote: | | Has anyone got this to work on windows 7? I tried it with python 2.6 and 3.0 and get the same error "Error when contacting server Errorlevel: ERROR" |
The python script creates an error log file when a problem occurs. Take a look at it. It may give you a more detailed error message. |
I've searched and searched but can't find an error log. I looked for all ".txt" and everything with "log" or "error" but can't find it. I also looked in both places where the autohotkey script is and refers to.
Any hints? (p.s. I know zero about python- just trying to follow instructions correctly)
Thanks |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Tue Jun 23, 2009 5:58 am Post subject: |
|
|
| cedardoc wrote: | I've searched and searched but can't find an error log. I looked for all ".txt" and everything with "log" or "error" but can't find it. I also looked in both places where the autohotkey script is and refers to.
Any hints? (p.s. I know zero about python- just trying to follow instructions correctly) |
The file name is "errorlog" and it should be in the same directory as the python script I think. If it isn't then change its location in the script to a known path, e.g. C:/errorlog:
| Code: | | collog = logging.handlers.RotatingFileHandler("errorlog", None, 100000, 10) |
If it doesn't help then there is an other possibility. Errors are logged only after the script was started properly, it could import its necessary libraries at the top, etc. You can try running the python script from the command prompt to see if it actually gets to the "try:" statement from which errors are logged. If there is an error then it is printed to the console and it contains the line number where the error occurs.
If the script doesn't get to the "try:" line then you have a problem with your python install. Have you run the Google client library tests as suggested in the installation instructions? |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Wed Jul 01, 2009 11:46 am Post subject: |
|
|
I'm getting very used to the fact there is an instant google calendar quickadd dialog accessible from anywhere. It's very easy to juggle your tasks with it and send yourself quick reminders as Gina Trapani describes in this article:
http://lifehacker.com/127823/geek-to-live--tickle-yourself-with-yahoo-calendar
She uses Yahoo Calendar, but the concept is the same. |
|
| Back to top |
|
 |
shajul
Joined: 15 Sep 2006 Posts: 564
|
Posted: Wed Jul 01, 2009 3:16 pm Post subject: Error |
|
|
this is my errorlog file..
| Code: | 2009-07-01 20:43:47,217 ERROR Unhandled exception
Traceback (most recent call last):
File "D:\Programs\gdata\shajulsoft\gcal\pycal.py", line 28, in <module>
outfile = sys.argv[1]
IndexError: list index out of range |
couldn't get it to work.. the quick add dialog does not have any add button also.. either i am dumb or the tutorial is.. |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Wed Jul 01, 2009 3:28 pm Post subject: Re: Error |
|
|
| shajul wrote: | | couldn't get it to work.. the quick add dialog does not have any add button also.. either i am dumb or the tutorial is.. |
Nah, it's not dumb, only a bit sparse.
The error you get most probably occurs because you try to run the python script manually from the command line without any argument. It requires an argument as it is described in the first post:
| Quote: | | If you want to test calendar access by running the python script manually first, then the first parameter of the script is the path of the file into which the fetched calendar items are stored. The config file must be in the same directory as the file, so the script uses this path to determine the location of the config file. |
The quick add dialog doesn't have any buttons, because it is designed for efficency. Use of a mouse is discouraged, because it's horrible inefficent.
You can add your event by simply pressing Enter after typing it, or cancel the dialog with Esc. |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Wed Jul 08, 2009 2:13 pm Post subject: |
|
|
The great thing in a calendar-based todo list is that it's very easy to hide tasks which you cannot work on at the moment and get notified when the task is due again.
To streamline the process even more I added two small improvements to the reschedule/postpone feature:
1. The default date for reschedule is tomorrow at the same time the task was scheduled originally, so you can quickly postpone the task by a day. Of course, the date can be modified to anything you want, it's just the default.
2. The date/time part is highlighted automatically in the quickadd dialog when the task is rescheduled, so you can easily overwrite it by simply typing a new time (e.g. 4pm) if you only want to postpone the task by a few hours.
See the updated script in the first post. |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Tue Jul 14, 2009 2:09 pm Post subject: |
|
|
Until now the user had to choose when adding a new item if the item is a calendar item or a normal item. This was not convenient and as it turned out it was unnecessary too.
From now on the quickadd dialog is a single input field. If the user types an item in which the last word contains a number (e.g. walk the dog 4pm) then it will be handled as a calendar item and it is added to the calendar.
Otherwise, it will be handled as a regular item and added to the todo list immediately (e.g. empty trashcan).
So the only rule is the last word must contain a number, but it is always true for calendar items and not usual for normal todo items.
If you specify the day too then make sure you write it before the time and everything will be OK. E.g. instead of "walk the dog 4pm friday" write "walk the dog friday 4pm"
See the updated script in the first post. |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Tue Jul 21, 2009 4:26 pm Post subject: |
|
|
Sometimes when we can't deal with a task we reschedule it to a later time, so that it disappears from the current todo list. If we do that and the task is not time sensitive then popping up a msgbox is not always needed when the task is due again, since we don't need a new notification we only want the task on the todo list now that we can deal with it.
I added support for this by cheking if the task description contains the word "silent". If it does then the task is added to the todo list automatically when it's due again. No questions asked.
See the updated script in the first post. |
|
| Back to top |
|
 |
keyboardfreak
Joined: 09 Oct 2004 Posts: 216 Location: Budapest, Hungary
|
Posted: Fri Aug 21, 2009 4:27 pm Post subject: |
|
|
| The silent option mentioned in the previous post proved to be useful, but it's too verbose, so from now on if an entry ends with " s" (before the time specification) then it is considered silent. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|