# -*- coding: latin-1 -*- # above line added to avoid deprecation warnings regarding unicode (see below) from os.path import join # for what is that needed here? Is it really needed WIKIDPAD_PLUGIN = (("hooks", 1),) def startup(wikidPad): """ Called when application starts """ pass def newWiki(wikidPad, wikiName, wikiDir): """ Called when a new wiki is about to be created. wikiName -- name of the wiki (already checked to be a proper CamelCase word) wikiDir -- directory to create the wiki in (more precisely the .wiki config file). This directory may already exist """ pass def createdWiki(wikidPad, wikiName, wikiDir): """ Called when creation of a new wiki was done successfully. The home wiki word (equals name of the wiki) is not yet loaded. wikiName -- name of the wiki wikiDir -- directory the wiki was created in """ pass def openWiki(wikidPad, wikiConfig): """ Called when an existing wiki is about to be opened. wikiConfig -- path to the .wiki config file """ pass def openedWiki(wikidPad, wikiName, wikiConfig): """ Called when an existing wiki was opened successfully wikiName -- name of the wiki wikiConfig -- path to the .wiki config file """ pass def openWikiWord(wikidPad, wikiWord): """ Called when a new or existing wiki word is about to be opened. The previous active page is already saved, new one is not yet loaded. wikiWord -- name of the wiki word to open """ pass def newWikiWord(wikidPad, wikiWord): """ Called when a new wiki word is about to be created. The wikidPad.currentWikiPage of the new word is already available wikiWord -- name of the wiki word to create """ pass def openedWikiWord(wikidPad, wikiWord): """ Called when a new or existing wiki word was opened successfully. wikiWord -- name of the wiki word to create ============================================================================================ Written by Christian Ziemski 2006-10-30 first version 2006-11-02 bug fixes 2006-11-09 Fix to work with pages named like [no wiki word] as well (beta) 2006-11-10 Fix to work with pages named like [no WikiWord] as well (beta) 2006-11-11 Use internal function to check WikiWord 2006-11-16 several small mods (config, filter, color) 2007-01-31 fixes to run in WikidPad up to 1.8 and in the new 1.9 as well 2007-06-16 fixes tags with sub-text and date; text without trailing LF at ToDo page 2007-06-18 fix for problem with multiple-tabs (todos on wrong page inserted) 2007-07-27 fix for displayMessage in 1.9 (up to 1.9beta7) 2007-07-27 above fix removed because no longer necessary due to 1.9beta8 History: -------- I was looking for a good To-Do/Notes/PIM application for a long time. WikidPad seems to be my choice for this. To make it even better I found the following extensions: http://wikidpad.python-hosting.com/wiki/GettingThingsDone The starting point. NextActions by Eric Neumann Enhanced by icosahedral to organize by context instead of by project name. http://wikidpad.python-hosting.com/wiki/SortedTodos JaysenNaidoo version to sort the page according to tags (todo.:) Mike Crowe added dynamic tags Because these extensions still don't exactly fit my needs I wrote my own, loosely based on the above. I mainly added date calculations, date sorting and a configuration section. (The formulae for date calculation from week number I found somewhere in 'the internet'.) Description: ------------ If a wikiWord beginning with "ToDo" is created or selected (viewed) it automatically collects all 'todo' entries from all other pages and inserts them here (after a placemark). Of course you can write own text before that placemark without being changed. If the wikiWord has additional characters after the "ToDo" like "ToDoPrivate" those characters "Private" are used as filter (case sensitive or insensitive). If there are spaces or underlines between "ToDo" and the following character (e.g "ToDo_Private") these are ignored and so still the filter is "Private". Only Todos containing that filter string are collected. (Behavior depends on configuration option below.) Exception: Within the WikidPad help wiki this hook does nothing. The todos are sorted by 'tag' the following way: 1.) Completely untagged e.g. 'todo: shopping' 2.) Tagged with special tags. e.g. 'todo.High: pay taxes' Sort order and display see config section below 3.) Tagged with a date (at End-Of-Tag!) Sorted by date (earliest first in list) Marked as 'GONE', 'TODAY', 'n hours left', 'n days left' dependent on dayrange below Displayed in different formats, dependent the same way. The following date formats are recognized: "yyyy-mm-dd" # standard date e.g. 'todo.2006-10-31: Halloween 2006' "mm/dd/yyyy" # GB/US date "dd.mm.yyyy" # European date "yyyy-Wnn-d" # Week; the DayOfWeek "d" is Mon(1)-Sun(7) e.g. todo.2007-W03-2: Tuesday of 3rd week in 2007 "yyyy-Wnn" # Week e.g. todo.2008-W12: Week 12 in 2008 (Monday) "*-mm-dd" # a date every year e.g. todo.*-10-31: Halloween, in every year "*-*-dd" # a day every month (and every year) e.g. todo.*-*-01: Every month, the 1st day "*-*-*" # every day "KWnn" # just for testing (German: Calendar Week) The dates with "*" are optimistic ones: if such a date is in the past it is assumed as "for next year" and not as "GONE this year". 4.) otherwise tagged e.g. 'todo.family: holidays' If there isn't already a custom [icon: ...] set, a [icon: spanner] will be placed. Technical questions: -------------------- 1) There has been this error when using German Umlauts in todos: ## C:\Program Files\WikidPad\library.zip\pwiki\PluginManager.py:188: ## ## DeprecationWarning: Non-ASCII character '\xfc' ## in file C:\Program Files\WikidPad\user_extensions\WikidPadHooks.py on line ..., ## but no encoding declared; see http://www.python.org/peps/pep-0263.html for details ## The new line number 1 at top seems to solve that: # -*- coding: latin-1 -*- But is that o.k.this way? 2) Why does this happen: #s = str(todo[1]) # gives an unicode-error when using German Umlauts or so s = todo[1] 3) The whole Unicode thing isn't exactly clear to me... 4) For what is the original "from os.path import join" needed? 5) Since this is my first Python code: Is it o.k. this way? 6) Where to put the def's for date calculation best? At top level of file or inside the hook? Still to do: ------------ Implement a timer Warning time for dates ============================================================================================ """ if (wikiWord[:4] == "ToDo"): # Attention: This determination of the version is not foolproof! from WikidPadStarter import VERSION_STRING # e.g. "wikidPad 1.8beta6" version = VERSION_STRING[9:12] # e.g. "1.8" if VERSION_STRING[13:17] == "beta": betaversion = int(VERSION_STRING[17:]) else: betaversion = 0 if version < "1.9": if wikidPad.wikiName == "WikidPadHelp": return # to avoid modification of help-wiki else: if wikidPad.getMainControl().wikiName == "WikidPadHelp": return # to avoid modification of help-wiki import pwiki.srePersistent as pre from re import compile, match from time import strftime, strptime, mktime, localtime, time from string import lstrip, replace #************************************************************************************ # begin of configuration section #************************************************************************************ # tags contain the (Tag, TagHeader, '') triples # TagHeaders are the descriptive headings that will be shown for each category. # UNTAGGED is a special one to collect the untagged "todo:" tags = [ ('UNTAGGED', '++++ Not yet tagged', ''), ('High', '+++ HIGH!', ''), ('Next', '++++ Next Actions', ''), ('ThisWeek', '++++ This Week', ''), ('SomeDay', '++++ SomeDay / Maybe', ''), ('TimeToTime', '++++ From time to time', ''), ('Low', '++++ Tagged as LOW', ''), ('VeryLow', '++++ Tagged as Very LOW', '')] placemark = "++++ ________auto-collected todos________" underline = "_______" spacerline = ('-'*4) bullet = " * " iconString = '[icon: spanner]' colorStringNormal = '[color: black]' # Colors for the ToDo entry in the tree colorStringNext = '[color: orange]' # colorStringToday = '[color: red]' # colorStringMissed = '[color: magenta]' # # notagHeading = "++++ Tagged " # for unknown tagged entries notagHeading = "++++ " # for unknown tagged entries dateHeading = "++++ " # for date entries far away (distance see below) dateHeadingX = "+++ " # for date entries in near future or missed in past missedString = " ___ (+GONE+)" nowString = " ___ !!! TODAY !!!" checkSearchString = 1 # 0 = no search string: ignore characters after 'ToDo' # 1 = case insensitive search string after 'ToDo' # 2 = case sensitive search string after 'ToDo' dayrange = 7 # to mark date entries in that range as "more important" # than the ones in "far" future showInfoPopup = 0 # about todos for today / missed ones onlyRealTodos = 1 # 1 = ignore done, wait, question etc. # 0 = all from wikidPads todo-like category #************************************************************************************ # end of configuration section #************************************************************************************ #--- begin of used functions -------------------------------------------------------- def LeapYr(y): if (y % 4): return False; if (y % 100): return True return not(y % 400) def Jan1DoWG(y): y = y - 1 return (int(1.25 * y) - int(y / 100) + int(y / 400) + 1) % 7 def DoYLtoMonth(J,L): if (J < 3): return 1 return 1 + int((303 + 5 * (J - 59 - L)) / 153) def DoYMLtoDate(J,M,L): if (M < 3): return J - (31 * (M - 1)) return J - (int((153 * M - 2) / 5) - 32 + L) def RevWN(Y,W,D): Yr = Y Leap = LeapYr(Yr) X = (Jan1DoWG(Yr) + 2) % 7 J = 7 * W + D - X - 4 Mth = int(DoYLtoMonth(J, Leap)) Dte = int(DoYMLtoDate(J, Mth, Leap)) if (Mth == 13): Yr = Yr + 1 Mth = 1 if (Dte < 1): Yr = Yr - 1 Mth = 12 Dte = Dte + 31 return [Yr, Mth, Dte] #--- end of used functions ---------------------------------------------------------- missedTodos = 0 todosForToday = 0 todosForNextDays = 0 colorStringFlag = 1 # set color string per default, checked later below now = strftime("%Y-%m-%d %H:%M", localtime(time())) today = strftime("%Y-%m-%d", localtime(time())) thisweek = strftime("%W", localtime(time())) warndate = strftime("%Y-%m-%d", localtime(time() + dayrange*24*60*60)) (thisyear,thismonth,thisday,thishour,thisminute,thisecond,thiswday,thisdoy,thisdst) = localtime(time()) if version < "1.9": editor = wikidPad.getActiveEditor() todosFull = wikidPad.wikiData.getTodos() else: if version == "1.9" and betaversion < 6: editor = wikidPad.getSubControl("textedit") else: editor = wikidPad.getActiveEditor() todosFull = wikidPad.getMainControl().wikiData.getTodos() srchstr = wikiWord[4:] # remove leading spaces and underscore from srchstr srchstr = lstrip(srchstr) while srchstr.find("_") == 0: srchstr = replace(srchstr, "_","",1) if checkSearchString == 0: todos = [todo for todo in todosFull] # without checking srchstr elif checkSearchString == 1: todos = [todo for todo in todosFull if (srchstr.upper() in todo[1].upper()) ] # case insensitive elif checkSearchString == 2: todos = [todo for todo in todosFull if (srchstr in todo[1]) ] # case sensitive if onlyRealTodos: todos = [todo for todo in todos if todo[1][0:4] == "todo" ] # prepare text page to insert the collected todos after placemark st = editor.FindText(0, editor.GetLength(), placemark, 0) if st != -1: # if placemark found editor.SetSelection(st+ len(placemark), editor.GetLength()) editor.ReplaceSelection(" (@ " + now + ") " + underline) if srchstr != "" and checkSearchString > 0: editor.AddText(" (filter: '" + srchstr + "')") editor.AddText("\n\n\n") else: # if no placemark found editor.GotoPos(editor.GetLength()) editor.AddText("\n\n" + placemark + " (@ " + now + ") " + underline) if srchstr != "" and checkSearchString > 0: editor.AddText(" (filter: '" + srchstr + "')") editor.AddText("\n\n\n") #st = editor.FindText(0, editor.GetLength(), iconString, 0) #if st != -1: # editor.SetSelection(st, st+len(iconString)) # editor.ReplaceSelection("") # ##st = editor.FindText(0, editor.GetLength(), colorString, 0) ##if st != -1: ## editor.SetSelection(st, st+len(colorString)) ## editor.ReplaceSelection("") st = editor.FindText(0, editor.GetLength(), "[icon:", 0) if st != -1: # if there already is an icon:string: iconString = "" # don't add our own st = editor.FindText(0, editor.GetLength(), "[color:", 0) if st != -1: # if there already is an color:string: colorStringFlag = 0 # don't add our own # determine tags checkTags = [tag[0] for tag in tags] # first part newtags = [] for todo in todos: ## [(u'pagename', u'todo.tag1.tag2: text')... allTodo = todo[1].split(":") ## [u'todo.tag1.tag2', u' text'] ... words = allTodo[0].split(".",1) ## [u'todo', u'tag1.tag2'] ... ## (keep additionally dotted parts together)! o.k.? for word in words[1:]: ## u'tag1.tag2' newtags.append( (word) ) #--------------------------------------------------- dateChecks = [ ( compile("[0-9]{4}-[0-1][0-9]-[0-3][0-9]$"), "yyyy-mm-dd" ), ( compile("[0-1][0-9]/[0-3][0-9]/[0-9]{4}$"), "mm/dd/yyyy" ), ( compile("[0-3][0-9].[0-1][0-9].[0-9]{4}$"), "dd.mm.yyyy" ), ( compile("[0-9]{4}-W[0-9]{2}-[0-7]$"), "yyyy-Wnn-d" ), # DayOfWeek "d" is Mon(1)-Sun(7) ( compile("[0-9]{4}-W[0-9]{2}$"), "yyyy-Wnn" ), ( compile("\*-[0-1][0-9]-[0-3][0-9]$"), "*-mm-dd" ), ( compile("\*-\*-[0-3][0-9]$"), "*-*-dd" ), ( compile("\*-\*-\*$"), "*-*-*" ), ( compile("KW[0-9]{2}$"), "KWnn" ) ] newtagsdata=[] # check all tags for being a date of special format for newtag in newtags: for dateCheck in dateChecks: # any match? result = dateCheck[0].search(newtag) if result: break if not result: # not a date like the above, perhaps no date at all newtagsdata.append( (newtag, newtag, 0) ) else: if dateCheck[1] == "yyyy-mm-dd": y = result.group()[0:4] m = result.group()[5:7] d = result.group()[8:10] elif dateCheck[1] == "mm/dd/yyyy": m = result.group()[0:2] d = result.group()[3:5] y = result.group()[6:10] elif dateCheck[1] == "yyyy-Wnn-d": y = int(result.group()[0:4]) w = int(result.group()[6:8]) dw = int(result.group()[9:10]) y,m,d = RevWN(y, w, dw) # eval year, month, day from week [and day] elif dateCheck[1] == "yyyy-Wnn": y = int(result.group()[0:4]) w = int(result.group()[6:8]) dw = 1 y,m,d = RevWN(y, w, dw) elif dateCheck[1] == "dd.mm.yyyy": d = result.group()[0:2] m = result.group()[3:5] y = result.group()[6:10] elif dateCheck[1] == "KWnn": w = int(result.group()[2:4]) dw = 1 y,m,d = RevWN(thisyear, w, dw) if w < thisweek or (w == thisweek and d < thisday): y = y + 1 y,m,d = RevWN(thisyear, w, dw) elif dateCheck[1] == "*-mm-dd": y = thisyear m = int(result.group()[2:4]) d = int(result.group()[5:7]) if m < thismonth or (m == thismonth and d < thisday): y = y + 1 elif dateCheck[1] == "*-*-dd": y = thisyear m = thismonth d = int(result.group()[4:6]) if d < thisday: m = m + 1 if m > 12: m = 1 y = y + 1 elif dateCheck[1] == "*-*-*": y = thisyear m = thismonth d = thisday # with flag = 1 to mark as date entry newtagsdata.append( (str(y) + "-" + str(m).zfill(2) + "-" + str(d).zfill(2), newtag, 1) ) # ==> newtagsdata = [ ( "yyyy-mm-dd" , "some date string" , 1), # for date tags # ( tagstring , tagstring , 0), # for normal tags #--------------------------------------------------- # search for tags not in defined list and automatically add them in correct order for sortednewtag in sorted(newtagsdata): if not sortednewtag[1] in checkTags: # only if not already in tag list if sortednewtag[2] == 0: # if not a recognized date tags.append( (sortednewtag[1], notagHeading, sortednewtag[1]) ) else: # if a date if sortednewtag[0] <= warndate: if sortednewtag[0] < today: missedTodos += 1 tags.append( (sortednewtag[1], dateHeadingX, sortednewtag[0] + missedString ) ) elif sortednewtag[0] == today: todosForToday += 1 tags.append( (sortednewtag[1], dateHeadingX, sortednewtag[0] + nowString ) ) else: todosForNextDays += 1 n = (mktime(strptime(sortednewtag[0],"%Y-%m-%d")) - time()) / (60*60*24) plural = "" if n <= 1: if int(n*24) > 1: plural = "s" tags.append( (sortednewtag[1], dateHeadingX, sortednewtag[0] + " ___ " + str(int(n*24)) + " hour" + plural + " left") ) else: if int(n) > 1: plural = "s" tags.append( (sortednewtag[1], dateHeadingX, sortednewtag[0] + " ___ " + str(int(n)) + " day" + plural + " left") ) else: tags.append( (sortednewtag[1], dateHeading, sortednewtag[0]) ) checkTags.append(sortednewtag[1]) # ==> tags = [ ( "some date string", "heading" , "YYYY-MM-DD ...."), # for new dates #--------------------------------------------------- # iterate thru all tags now and insert them on page lasttag = u"" newtext = u"" tagAdded = False for tag in tags: if lasttag != "" and lasttag == tag[2][0:10]: tagContinued = True else: tagContinued = False lasttag = tag[2][0:10] # write SPACER to the tag list: if tagAdded and not tagContinued: newtext += '\n' + spacerline tagAdded = False wroteHeader = False # write header only once # handle untagged todos if tag[0] == 'UNTAGGED': for todo in todos: foundTag = False for t in tags: # avoid doubles if t[0] in todo[1]: # perhaps still not accurate enough??? (see below on tagged todos) foundTag = True break if not foundTag: if not wroteHeader: newtext += '\n' + tag[1] + '\n' wroteHeader = True #s = str(todo[1]) # gives an unicode-error when using German Umlauts or so s = todo[1] if wikidPad.getFormatting().isCcWikiWord(todo[0]): newtext += bullet + todo[0] + ': ' + s[s.find(':')+1:] + '\n' else: newtext += bullet + '['+ todo[0] + ']: ' + s[s.find(':')+1:] + '\n' tagAdded = True # handle tagged todos else: for todo in todos: if (tag[0] == todo[1][todo[1].find(".")+1:todo[1].find(":")]): if not wroteHeader and not tagContinued: if tag[2] == "": # normal tag of known type newtext += '\n' + tag[1] elif tag[2] != tag[0]: # tag with date and add. info newtext += '\n' + tag[1] + tag[2] #newtext += " (" + tag[0] + ")" else: # tag of unknown type and std. dates YYYY-MM-DD without info newtext += '\n' + tag[1] + tag[0] newtext += '\n' wroteHeader = True #s = str(todo[1]) # gives an unicode-error when using German Umlauts or so s = todo[1] #editor.AddText("\n###" + todo[0] + "###\n") if wikidPad.getFormatting().isCcWikiWord(todo[0]): newtext += bullet + todo[0] + ': '+ s[s.find(':')+1:] + '\n' else: newtext += bullet + '['+ todo[0] + ']: '+ s[s.find(':')+1:] + '\n' tagAdded = True # and finally the footer newtext += '\n' + spacerline if iconString != "" or colorStringFlag: newtext += '\n' + '\n' if iconString != "": newtext += iconString + "\n" if colorStringFlag: if missedTodos: newtext += colorStringMissed elif todosForToday: newtext += colorStringToday elif todosForNextDays: newtext += colorStringNext else: newtext += colorStringNormal # and now put all the new text into the editor page editor.AddText(newtext) if showInfoPopup: msg = "" if missedTodos: msg = str(missedTodos) + " x missed todo.\n" if todosForToday: msg = msg + "\n" + str(todosForToday) + " x todo for today! \n" if todosForNextDays: msg = msg + "\n" + str(todosForNextDays) + " x todo for the next " + str(dayrange) + " days!" if msg != "": wikidPad.displayMessage("Info", msg) #if version < "1.9": # wikidPad.displayMessage("Info", msg) #else: # wikidPad.getMainControl().displayMessage("Info", msg) def savingWikiWord(wikidPad, wikiWord): """ Called when a new or existing wiki word is about to be saved wikiWord -- name of the wiki word to create """ pass def savedWikiWord(wikidPad, wikiWord): """ Called when a wiki word was saved successfully wikiWord -- name of the wiki word to create """ pass def renamedWikiWord(wikidPad, fromWord, toWord): """ Called when a wiki word was renamed successfully. The changed data is already saved in the fileset, the GUI is not updated yet, the renamed page is not yet loaded. fromWord -- name of the wiki word before renaming toWord -- name of the wiki word after renaming """ pass def deletedWikiWord(wikidPad, wikiWord): """ Called when a wiki word was deleted successfully. The changed data is already saved in the fileset, the GUI is not updated yet, another page (normally the last in history before the deleted one) is not yet loaded. wikiWord -- name of the deleted wiki word """ pass def exit(wikidPad): """ Called when the application is about to exit. The global and the wiki configuration (if any) are saved already, the current wiki page (if any) is saved already. """ pass