#@+leo-ver=5-thin
#@+node:ekr.20101220161557.6014: * @file inactiveTests.txt
#@@language python
#@+all
#@+node:ekr.20040712101754.2: ** Load tests for .leo files
@language python
@tabwidth -4

# These work well with the qttabs gui.
#@+node:ekr.20040712101754.3: *3* @test test.leo
path = g.os_path_join(g.app.loadDir,"..","test","test.leo")
c.testManager.runLeoTest(path,verbose=False,full=True)
#@+node:ekr.20040803090901: *3* @test leoDist.leo
path = g.os_path_join(g.app.loadDir,"..","dist","leoDist.leo")
c.testManager.runLeoTest(path,verbose=False,full=True)
#@+node:ekr.20081121164135.1: *3* @test leoGuiPluginsRef.leo
path = g.os_path_join(g.app.loadDir,"..","plugins","leoGuiPluginsRef.leo")
c.testManager.runLeoTest(path,verbose=False,full=True)
#@+node:ekr.20040712101754.4: *3* @test LeoPyRef.leo
path = g.os_path_join(g.app.loadDir,"..","core","LeoPyRef.leo")
c.testManager.runLeoTest(path)
#@+node:ekr.20040712101754.5: *3* @test leoPluginsRef.leo
path = g.os_path_join(g.app.loadDir,"..","plugins","leoPluginsRef.leo")
c.testManager.runLeoTest(path)
#@+node:ekr.20040712101754.6: *3* @test LeoDocs.leo
path = g.os_path_join(g.app.loadDir,"..","doc","LeoDocs.leo")
c.testManager.runLeoTest(path)
#@+node:ekr.20040730181601: *3* @test minimalLeoFile.leo
path = g.os_path_join(g.app.loadDir,"..","test","unittest","minimalLeoFile.leo")
c.testManager.runLeoTest(path,verbose=False,full=True)
#@+node:ekr.20040730181610: *3* @test minimalLeoFile2.leo
path = g.os_path_join(g.app.loadDir,"..","test","unittest","minimalLeoFile2.leo")
c.testManager.runLeoTest(path,verbose=False,full=True)
#@+node:ekr.20040831104758: *3* @test minimalLeoFile3.leo
path = g.os_path_join(g.app.loadDir,"..","test","unittest","minimalLeoFile3.leo")
c.testManager.runLeoTest(path,verbose=False,full=True)
#@+node:ekr.20080405085247.1: ** slow tests
#@+node:ekr.20060208195054: *3* @@test k.inverseCommandsDict is inverse of c.commandsDict
# c.commandsDict: keys are emacs command names, values are functions f.
# k.inverseCommandsDict: keys are f.__name__, values are emacs command names.

@others

d1 = c.commandsDict ; d2 = c.k.inverseCommandsDict

if 0:
    vals = d2.values() ; vals.sort()
    vals = [z for z in vals if z.startswith('contract')]
    g.pr('inverseCommandsDict.values()',vals)

keys1 = d1.keys() ; keys1.sort()
vals1 = d1.values()
vals1 = [f.__name__ for f in vals1]
vals1.sort()

keys2 = d2.keys() ; keys2.sort()
vals2 = d2.values(); vals2.sort()

if 0:
    g.pr(keys1,'\n\n')
    g.pr(vals2,'\n\n')
    g.pr(keys2,'\n\n')
    g.pr(vals1)

# g.trace(g.dictToString(c.k.abbreviationsDict))

abbrevDict = c.config.getAbbrevDict()

# Find @button and @command nodes in this file.
@others
buttonKeys = []
for p in c.allNodes_iter():
    h = p.h.strip().lower()
    for kind in ('@button','@command'):
        if h.startswith(kind):
            key1 = mungeKey(h,kind,substitute=False)
            if key1 not in buttonKeys:
                buttonKeys.append(key1)
            key = mungeKey(h,kind,substitute=True)
            if key not in buttonKeys:
                buttonKeys.append(key)

for z in g.app.config.atCommonButtonsList:
    h,junk = z
    key = mungeKey(h,'@button')
    # g.trace(key)
    if key not in buttonKeys:
        buttonKeys.append(key)

# g.pr('buttonKeys',buttonKeys)

for key in keys1:
    if key not in vals2 and key.lower() not in vals2:
        if (
            key.startswith('enter-') and key.endswith('-mode') or
            key.startswith('press-') and key.endswith('-button') or
            key.startswith('delete-') and key.endswith('-button') or
            key.startswith('nav-') and key.endswith('-menu')
        ):
            vals2.append(key)
        elif key in buttonKeys or key.lower() in buttonKeys:
            # List of buttons defined in this file, or in @settings tree.
            vals2.append(key)
        elif key.startswith('open-with-'):
            vals2.append(key)
        elif key in abbrevDict.keys():
            pass # g.trace('abbrev',key)
        else:
            assert False, '%s not in inverseCommandsDict.values()' % key

vals2.sort()
for val in vals2:
    if val not in keys1:
        assert False, '%s not in commandsDict.keys()' % (val)
#@+node:ekr.20070926095117: *4* mungeKey
def mungeKey (h,kind,substitute=True):

    key = h[len(kind):].strip()
    i = key.find('@key')
    if i > -1: key = key[:i].strip()
    if substitute:
        key = key.replace(' ','-')
    # g.trace(key)
    return key


#@+node:ekr.20110728154927.3324: *3* @@test position > operator
# Disabled because very slow
aList = [z.copy() for z in c.all_positions()]

total = 0

for i in range(len(aList)):
    for j in range(0,i):
        assert aList[j] < aList[i],(i,j)
        total += 1

g.es('"%s": total tests: %s' % (p.h,total))
#@+node:ekr.20080703104536.1: ** @ignore mini tests
#@+node:ekr.20100203163606.5365: *3* 2to3 script
import os

def run(files):
    args = [r'python c:\python26\Tools\Scripts\2to3.py']
    for z in files:
        args.append(z)
        # args.append('-xprint')
    args.append('>out2')
    args = ','.join(args)
    os.system(args)

tkPass = (
    'EditAttributes','Library',
    'URLloader','UniversalScrolling','UASearch',
    'autotrees','chapter_hoist','cleo','dump_globals',
    'expfolder','geotag','graphed','groupOperations',
    'hoist','import_cisco_config',
    'keybindings','leoupdate',
    'maximizeNewWindows', 'mnplugins','mod_labels',
    'mod_read_dir_outline','mod_tempfname','multifile',
    'newButtons','nodeActions','nodenavigator',
    'open_with','pie_menus','pluginsTest',
    'read_only_nodes','rClick',
    'scheduler','searchbar','searchbox','shortcut_button',
    'script_io_to_body','searchbox',
    'templates','textnode','tkGui','toolbar',
    'xcc_nodes',
)

passList = (
    '__init__','FileActions','UNL',
    'active_path','add_directives','attrib_edit',
    'backlink','base64Packager','baseNativeTree','bibtex','bookmarks',
    'codewisecompleter','colorize_headlines','contextmenu',
    'ctagscompleter','cursesGui','datenodes','debugger_pudb',
    'detect_urls','dtest','empty_leo_file','enable_gc','initinclass',
    'leo_to_html','leo_interface','leo_pdf','leo_to_rtf',
    'leoOPML','leoremote','lineNumbers',
    'macros','mime','mod_autosave','mod_framesize','mod_leo2ascd',
    'mod_scripting','mod_speedups','mod_timestamp',
    'nav_buttons','nav_qt','niceNosent','nodeActions','nodebar',
    'open_shell','outline_export','quit_leo',
    'paste_as_headlines','plugins_menu','pretty_print','projectwizard',
    'qtGui','qt_main','qt_quicksearch','qtframecommands',
    'quickMove',
        # Warning: changed this line by guessing!
        # func = types.MethodType(func, quickMove)
    'quicksearch','redirect_to_log','rClickBasePluginClasses',
    'run_nodes', # Changed thread.allocate_lock to threading.lock().acquire()
    'rst3',
    'scrolledmessage','setHomeDirectory','slideshow','spydershell','startfile',
    'testRegisterCommand','todo','trace_gc_plugin','trace_keys','trace_tags',
    'vim','xemacs',
)
core_files = (
    'leoApp','leoAtFile','leoCache','leoChapters','leoCommands',
    'leoEditCommands','leoFileCommands','leoFind','leoFrame',
    'leoGlobals','leoGui','leoImport','leoMenu','leoNodes',
    'leoPlugins','leoShadow','leoTangle','leoUndo',
)
external_files = (
    'ipy_leo','lproto',
)
table = (
    ('plugins',passList),
    ('plugins',tkPass),
    ('core',core_files),
    ('external',external_files),
)
files = []
for theDir,aList in table:
    for z in aList:
        if not z.endswith('.py'): z = z + '.py'
        # print(z)
        fn = os.path.abspath(os.path.join('leo',theDir,z))
        if os.path.exists(fn): files.append(fn)
        else: print('*** file not found:',fn)

run(files)
print('done: results are in out2')
#@+node:ekr.20100127162342.5123: *3* Import all plugins script
import glob,os

tkPass = (
    'EditAttributes','Library',
    'URLloader','UniversalScrolling','UASearch',
    'autotrees','chapter_hoist','cleo','dump_globals',
    'expfolder','geotag','graphed','groupOperations',
    'hoist','import_cisco_config',
    'keybindings','leoupdate',
    'maximizeNewWindows', 'mnplugins','mod_labels',
    'mod_read_dir_outline','mod_tempfname','multifile',
    'newButtons','nodeActions','nodenavigator',
    'open_with','pie_menus','pluginsTest',
    'read_only_nodes','rClick',
    'scheduler','searchbar','searchbox','shortcut_button',
    'script_io_to_body','searchbox',
    'templates','textnode','tkGui','toolbar',
    'xcc_nodes',
)
tkPassWithProblems = (
    'at_view', # at_view plugin not loaded: win32Clipboard not present.
    'image', # can not import ImageTk.
    'table', # failed to import 'tktable'
    'xsltWithNodes', # Can not import Ft from plugin leo.plugins.xsltWithNodes.
)
tkFail = (
    'ConceptualSort','at_produce','autocompleter','rowcol',
)
passList = (
    '__init__','FileActions','UNL',
    'active_path','add_directives','attrib_edit',
    'backlink','base64Packager','baseNativeTree','bibtex','bookmarks',
    'codewisecompleter','colorize_headlines','contextmenu',
    'ctagscompleter','cursesGui','datenodes','debugger_pudb',
    'detect_urls','dtest','empty_leo_file','enable_gc','initinclass',
    'leo_to_html','leo_interface','leo_pdf','leo_to_rtf',
    'leoOPML','leoremote','lineNumbers',
    'macros','mime','mod_autosave','mod_framesize','mod_leo2ascd',
    'mod_scripting','mod_speedups','mod_timestamp',
    'nav_buttons','nav_qt','niceNosent','nodeActions','nodebar',
    'open_shell','outline_export','quit_leo',
    'paste_as_headlines','plugins_menu','pretty_print','projectwizard',
    'qtGui','qt_main','qt_quicksearch','qtframecommands',
    'quickMove',
        # Warning: changed this line by guessing!
        # func = types.MethodType(func, quickMove)
    'quicksearch','redirect_to_log','rClickBasePluginClasses',
    'run_nodes', # Changed thread.allocate_lock to threading.lock().acquire()
    'rst3',
    'scrolledmessage','setHomeDirectory','slideshow','spydershell','startfile',
    'testRegisterCommand','todo','trace_gc_plugin','trace_keys','trace_tags',
    'vim','xemacs',
)
passWithImportProblems = ( # Other than tk input problems.
    'ipython','word_export',
)
dead = (
    'at_folder','exampleTemacsExtension','ironPythonGui','LeoN',
    'rst2','swing_gui','temacs','usetemacs','wxGui',)
error = ( # Real errors with tracebacks.
)
fail = (
    'stickynotes_plus', # requires markdown.
    'zenity_file_dialogs', # requires zenity, and probably ubuntu.
)
noAttribute = (
    # AttributeError: 'module' object has no attribute <module name>
    # This was a sign of a missing init top-level function.
)
changed = (
    'LeoN',
)
plugins = g.os_path_abspath(g.os_path_join(
    g.app.loadDir,'..','plugins','*.py'))
files = glob.glob(plugins)
files.sort()
os.system('cls') # Clear the screen on windows.
for fn in files:
    m = g.shortFileName(fn)[:-3]
    # Change the next line to choose different plugins.
    if m in passList:
        try:
            __import__('leo.plugins.%s' % m)
            if 1: print('pass %s' % m)
        except ImportError:
            if 1: print('FAIL %s' % m)
        except Exception:
            if 1: g.es_exception()
            if 1: print('error %s' % m)
#@+node:ekr.20051104081502.99: *3* Manual tests...
#@+node:ekr.20051104081502.101: *4* Other Reformat Paragraph tests
@language plain
@pagewidth 40

A one-line paragraph one two three four five six seven eight nine ten...

An @rawfile tree is a tree whose root headline starts with
@rawfile <filename>. Similarly, an @silentfile tree is a
tree whose root headline starts with an @silentfile
<filename> directive.

    Leo creates derived files from @rawfile and @silentfile trees by writing the body text of all nodes of the tree in outline order.  Leo writes the body text _as is_, without recognizing section definitions, without expanding section references, and without treating directives specially in any way.  In particular, Leo copies all directives, including @space or @c directives, to the derived file as text. Exception: Leo recognizes the @ignore directive in @rawfile or @silentfile nodes, so you may use the @ignore directive as usual to prevent Leo from writing @rawfile and @silentfile trees.

There are several difference between @rawfile and @silentfile trees:

  This
  is
  a
  test.

  1. This is the first line and it is really really really long. And it has
     a hanging indentation.
     and another line.

  2. This is a lllllllllllllllllllllllllllllllllllloooooooooooooooooong
     next item.
     And it too has a hanging indentation.

  3. This is an exxxxxxxxxxxxxxxxxxxxxxxxtrrrrrrrrrrrrrrreeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeemlylong
     word.
And it too has a hanging indentation.
#@+node:ekr.20051104081502.102: *4* Test of @tabwidth
@tabwidth -4
@language plain


    a   b   c
a   b   c   d
aa  b   c   d
aaa b   c   d
end
#@+node:ekr.20051104081502.103: *4* Test of pasting into big node
@killcolor
@language plain

Note: Previously, one could crash Leo by pasting a large text into a headline.  Leo now truncates that text, and furthermore Leo no longer makes all headline text into one gigantic line.  Therefore, we don't have to test Tk's ability to handle super-long lines.

The test:  Copy the following and paste it into a headline.  Leo should give 2 truncation messages:

- Truncating headline to one line.
- Truncating headline to 250 characters.

About a year ago I found the website at www.literateprogamming.com and was immediately convinced that the basic idea of Literate Programming is an important breakthrough. At the time I was working on a contract trying to decipher a true masterpiece of over-engineering, and if at any time during the construction of this masterpiece the perpetrators had been required to explain themselves in English, my client would have saved millions of dollars.

I never did try CWEB or NOWEB though, because on the literate programming site I read about a tool named “Leo” that combined outlines with Literate Programming techniques. Since I’ve always found outlining tools very useful I downloaded and tried this. I found that using this tool completely changed my programming practice and brought out all of the power inherent in the original Literate Programming idea as I understood it.
#@+node:ekr.20051104081502.104: *4* Test of Remove sentinels
import os

g.pr(os.getcwd())
name = g.os_path_join("test","removeSentTest.txt")
c.importCommands.removeSentinelsCommand(name)
#@+node:ekr.20051104081502.105: *4* Test new docutils stull
import glob

g.pr('-' * 40)

tm = c.testManager

if 0:
    g.pr("modules in test.leo...")
    paths = tm.findAllAtFileNodes(c)
    modules = tm.importAllModulesInPathList(paths)
    for module in modules:
        g.pr(module)

if 1:
    g.pr("modules in leo/src...")
    path = g.os_path_join(g.app.loadDir,"..","src")
    modules = tm.importAllModulesInPath(path)
    for module in modules:
        g.pr(module)

if 0:
    directory = g.os_path_join(g.app.loadDir,"..","src")
    glob_path = g.os_path_join(directory,"leo*.py")
    files = glob.glob(glob_path)
    modules = tm.importAllModulesInPathList(files)
    for module in modules:
        g.pr(module)
#@+node:ekr.20040712101754.221: *4* Manual test of TM.replaceOutline
outline1 = p.firstChild()
outline2 = outline1.next()
assert(outline1.h=="outline1")
assert(outline2.h=="outline2")

c.testManager.replaceOutline(outline1,outline2)
c.redraw()
c.checkOutline()
#@+node:ekr.20040712101754.222: *5* outline1
#@+node:ekr.20040712101754.223: *6* a
#@+node:ekr.20040712101754.224: *5* outline2
#@+node:ekr.20040712101754.225: *6* b
#@+node:ekr.20051104081502.220: *3* Mini test arguments to hooks
"""Mini test that documentation of hooks in leoDocs.leo is correct.

hookData should match that documentation for this test to be effective.

This is not a complete unit test:  it does not force executions of all hooks.
"""

<< imports >>
<< define hookData >>
<< define typeData >>
checked = [] # List of all hooks that have been checked.

@others

tags = [] 
for name,args in hookData:
    tags.append(name)
    << define checkHook >>
    leoPlugins.registerHandler(name,checkHook)

if 0: # print all hooks.
    handlers = leoPlugins.getHandlersForTag(tags)
    if handlers:
        g.pr("-" * 20)
        for h in handlers:
            g.pr(h)
#@+node:ekr.20051104081502.221: *4* << imports >>
import leoColor
import leoCommands
import leoNodes
import leoPlugins
import leoTkinterTree

import types
import Tkinter as Tk
#@+node:ekr.20051104081502.222: *4* << define hookData >>
hookData = (
    ("bodyclick1",   ("c","p","v","event")),
    ("bodyclick2",   ("c","p","v","event")),
    ("bodydclick1",  ("c","p","v","event")),
    ("bodydclick2",  ("c","p","v","event")),
    ("bodykey1",     ("c","p","v","ch","oldSel","undoType")),
    ("bodykey2",     ("c","p","v","ch","oldSel","undoType")),
    ("bodyrclick1",  ("c","p","v","event")),
    ("bodyrclick2",  ("c","p","v","event")),
    ("boxclick1",    ("c","p","v","event")),
    ("boxclick2",    ("c","p","v","event")),
    ("command1",     ("c","p","v","label")),
    ("command2",     ("c","p","v","label")),
    ("drag1",        ("c","p","v","event")),
    ("drag2",        ("c","p","v","event")),
    ("dragging1",    ("c","p","v","event")),
    ("dragging2",    ("c","p","v","event")),
    ("end1",         None),
    ("enddrag1",     ("c","p","v","event")),
    ("enddrag2",     ("c","p","v","event")),
    ("headclick1",   ("c","p","v","event")),
    ("headclick2",   ("c","p","v","event")),
    ("headrclick1",  ("c","p","v","event")),
    ("headrclick2",  ("c","p","v","event")),
    ("headkey1",     ("c","p","v","ch")),
    ("headkey2",     ("c","p","v","ch")),
    ("hypercclick1", ("c","p","v","event")),
    ("hypercclick2", ("c","p","v","event")),
    ("hyperenter1",  ("c","p","v","event")),
    ("hyperenter2",  ("c","p","v","event")),
    ("hyperleave1",  ("c","p","v","event")),
    ("hyperleave2",  ("c","p","v","event")),
    ("iconclick1",   ("c","p","v","event")),
    ("iconclick2",   ("c","p","v","event")),
    ("iconrclick1",  ("c","p","v","event")),
    ("iconrclick2",  ("c","p","v","event")),
    ("icondclick1",  ("c","p","v","event")),
    ("icondclick2",  ("c","p","v","event")),
    ("idle",         ("c",)),
    ("menu1",        ("c","p","v")),
    ("menu2",        ("c","p","v")),
    ("open1",        ("old_c","new_c","fileName")),
    ("open2",        ("old_c","new_c","fileName")),
    ("openwith1",    ("c","p","v","openType","arg","ext")),
    ("openwith2",    ("c","p","v","openType","arg,ext" )),
    ("recentfiles1", ("c","p","v","fileName","closeFlag")),
    ("recentfiles2", ("c","p","v","fileName","closeFlag")),
    ("save1",        ("c","p","v","fileName" )),
    ("save2",        ("c","p","v","fileName" )),
    ("select1",      ("c","new_p","old_p","new_v","old_v")),
    ("select2",      ("c","new_p","old_p","new_v","old_v")),
    ("select3",      ("c","new_p","old_p","new_v","old_v")),
    ("set-mark",     ("c","p","v")),
    ("start1",       None),
    ("start2",       ("c","p","v","fileName" )),
    ("unselect1",    ("c","new_p","old_p","new_v","old_v")),
    ("unselect2",    ("c","new_p","old_p","new_v","old_v")),
    ("@url1",        ("c","p","v")),
    ("@url2",        ("c","p","v")),
    # Stub hooks.
    ("after-redraw-outline",         ("c",)),
    ("clear-mark",                   ("c","p","v")),
    ("close-frame",                  ("c",)),
    ("color-optional-markup",        ("colorer","p","v","s","i","j","colortag")),
    ("create-optional-menus",        ("c",)),
    ("destroy-all-global-windows",   None),
    ("draw-outline-box",             ("tree","p","v","x","y")), #
    ("draw-outline-icon",            ("tree","p","v","x","y")), #
    ("draw-outline-node",            ("tree","p","v","x","y")), #
    ("draw-outline-text-box",        ("tree","p","v","x","y")), #
    ("create-popup-menu-items",      ("c","p","v","event")),
    ("enable-popup-menu-items",      ("c","p","v","event")),
    ("init-color-markup",            ("colorer","p","v")),
    ("new",                          ("old_c","new_c")),
    ("redraw-entire-outline",        ("c",)),
    ("scan-directives",              ("c","p","v","s","old_dict","dict","pluginsList")),
    ("set-mark",                     ("c","p","v" )),
    ("show-popup-menu",              ("c","p","v","event")),
)
#@+node:ekr.20051104081502.223: *4* << define typeData >>
typeData = {
    "arg":      types.StringType,
    "c":        leoCommands.Commands,
    "ch":       types.StringType,
    "closeFlag":types.StringType,
    "colorer":  leoColor.colorizer,
    "colortag": types.StringType,
    "dict":     types.DictType,
    "event":    Tk.Event,
    "ext":      types.StringType,
    "fileName": types.StringType,
    "i":        types.IntType,
    "j":        types.IntType,
    "label":    types.StringType,
    "new_c":    leoCommands.Commands,
    "new_p":    leoNodes.position,
    "newSel":   types.TupleType,
    "new_v":    leoNodes.position,
    "old_c":    leoCommands.Commands,
    "old_dict": types.DictType,
    "old_p":    leoNodes.position,
    "oldSel":   types.TupleType,
    "old_v":    leoNodes.position,
    "openType": types.StringType,
    "p":        leoNodes.position,
    "pluginsList": types.ListType,
    "s":        types.UnicodeType,
    "tree":     leoTkinterTree.leoTkinterTree,
    "v":        leoNodes.position,
    "undoType": types.StringType,
    "x":        types.IntType,
    "y":        types.IntType,
}
#@+node:ekr.20051104081502.224: *4* << defineCheckHook >>
def checkHook (tag,keywords,args=args):

    """Check to see that the keywords passed to the hook are as described in args.
    Each arg is a list of strings whose type is defined in typeData."""

    global checked, verbose
    if tag in checked: return
    ok = True
    checked.append(tag)
    if args is None: args = []
    args = list(args)
    args.sort()
    keys = list(keywords.keys())
    keys.sort()

    if len(args) != len(keys):
        g.pr("%25s expected:" % (tag),args)
        g.pr("%25s      got:" % (tag),keys)
        ok = False
    else:
        for arg,key in zip(args,keys):
            arg_type = typeData.get(arg)
            val = keywords.get(key)
            if not checkOneHook(arg_type,val):
                g.pr("%25s      arg:" % (tag), arg)
                g.pr("%25s expected:" % (tag), arg_type)
                g.pr("%25s      got:" % (tag), type(val))
                ok = False
    if ok:
        g.pr(tag)
#@+node:ekr.20051104081502.225: *4* checkOneHook
def checkOneHook (arg_type, val):

    if 0:
        if arg_type != type(val):
            g.trace(arg,key,arg_type,type(val))

    return (
        (arg_type is type(val)) or
        (arg_type == types.StringType and type(val) is types.UnicodeType) or
        (type(arg_type) == types.ClassType and isinstance(val,arg_type)))
#@+node:ekr.20111121113227.4035: *3* Mini test of add-editor
c.k.simulateCommand('add-editor')
c.k.simulateCommand('delete-editor')
#@+node:ekr.20051104081502.312: *3* Mini test of g.es_exception
try:
    assert False, 'Assert False'
except AssertionError:
    g.es_exception()
#@+node:ekr.20051104081502.311: *3* Mini test of g.pdb
# Running this as a unit test would hang the unit tests!
g.pdb()
#@+node:ekr.20111108170253.3968: *3* Mini test of unicode stuff
@first # -*- coding: utf-8 -*-

table = (
    'test',
    'Ä 궯 奠',
    'Ä 궯 奠 after', # fails with cp6501: after is duplicated.
)

print('*'*20)
print('isPython3: %s' % g.isPython3)

for s in table:
    if g.isPython3:
        s = s.encode('ascii','replace') # create bytes.
    g.es(repr(s))
    g.es(s)
    g.pr ('g.pr(s)       : %s' % s)
    g.pr ('g.pr(repr(s)) : %s' % repr(s))
    print('print(s)      : %s' % s)
    print('print(repr(s)): %s' % s)
#@+node:ekr.20111121081052.3909: *3* Mini test that g.es leaves focus unchanged
if g.app.isExternalUnitTest:
    pass # Prints to console, which is annoying.
else:
    old_focus = c.get_focus()
    
    for flag in (False,True):
        g.es('Hi')
        
    if flag:
        c.outerUpdate() # Restores focus, especially when run from a script.
    new_focus = c.get_focus()
    
    try:
        assert old_focus == new_focus,'old focus: %s new focus: %s' % (
            old_focus,new_focus)
    except AssertionError:
        c.bodyWantsFocusNow()
#@+node:ekr.20111121090700.3914: *3* mini test that print-bindings puts results in Bindings tab
if g.app.isExternalUnitTest:
    pass
else:
    c.k.simulateCommand('print-bindings')
    log = c.frame.log
    
    # This works when run via execute-script.
    assert log.tabName == 'Bindings',log.tabName
    
    # w = c.frame.log.contentsDict.get('Bindings')
    # assert w,'no Bindings widget'
    # wrapper = w.leo_log_wrapper
    # s = wrapper.getAllText()
    # assert s,wrapper
#@+node:ekr.20051104081502.106: *3* Mini tests of script buttons
#@+node:ekr.20051104081502.107: *4* Redundant: @suite run all doctests in @file nodes
import doctest
import unittest

tm = c.testManager

createUnitTest = True

if createUnitTest:
    suite = unittest.makeSuite(unittest.TestCase)
else:
    suite = None

paths   = tm.findAllAtFileNodes(c)
modules = tm.importAllModulesInPathList(paths)

if createUnitTest:
    suite = tm.createUnitTestsFromDoctests(modules)
else:
    for module in modules:
        doctest.testmod(module,verbose=True,report=False)

if suite:
    g.app.scriptDict['suite'] = suite
#@+node:ekr.20051104081502.108: *4* runProfile button mini-test
for i in range(10000):
    if i and (i % 1000) == 0:
        g.pr(i)
#@+node:ekr.20051104081502.109: *4* runTimeit mini-test
i = 0
for i in range(100000):
    i += 1
    i -= 1
#@+node:ekr.20051104081502.110: *4* profile redraws
# c.redraw just schedules the actual drawing.
# We want to profile the actual idle-time drawing.

c.frame.tree.idle_redraw()
#@+node:ekr.20051104081502: *3* Other tests
@language python
@tabwidth -4
#@+node:ekr.20051104081502.542: *4* @@nosent test-niceNosent
#@+node:ekr.20051104081502.543: *5* part 1
part 1, line 1
part 2, line 2, no newline
#@+node:ekr.20051104081502.544: *5* part 2
part 2, line 1, no newline
#@+node:ekr.20051104081502.545: *5* part 3
part 3, line 1
part 3, line 2, newline
#@+node:ekr.20051104081502.320: *4* @nowrap tests
@nowrap
aaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccc ddddddddddddddd eeeeeeeeeeeeeee ffffffffffffffff 
#@+node:ekr.20051104081502.22: *4* Experiments
#@+node:ekr.20051104081502.23: *5* Test of moving positions
class position:
    def __init__(self):
        self.v = "a"
    def move(self):
        self.v = "b"

p = position()
v = p.v
g.pr("before", v, p.v, v is p.v)
p.move()
g.pr("after ", v, p.v, v is p.v)
#@+node:ekr.20051104081502.24: *5* Test of using an iterator inside a list comprehension
class test_iter_class:
    def __init__ (self):
        self.vals = ("a","b","c")
        self.n = 0
    def __iter__(self):
        return self
    def next(self):
        if self.n < len(self.vals):
            val = self.vals[self.n]
            self.n += 1
            return val
        else:
            raise StopIteration

def test_iter(): return test_iter_class()

vals = [val for val in test_iter()]

g.pr(vals)
#@+node:ekr.20051104081502.25: *5* Test of using c.allNodes_iter to create a list of all positions
g.pr('-'*20)

# These are equivalent.
positions1 = [p for p in c.allNodes_iter(copy=True)]
positions2 = [p.copy() for p in c.allNodes_iter()]

assert(len(positions1) == len(positions2))
for i in range(len(positions1)):
    assert(positions1[i] == positions2[i])

if 0:
    for p in positions1:
        g.pr(p)
g.pr("done")
#@+node:ekr.20051104081502.26: *5* Creating a list of distinct vnodes
g.pr('-'*20)

positions = [p.copy() for p in c.allNodes_iter()]

tnodes = {} ; vnodes = []
for p in c.allNodes_iter():
    t = p.v.t
    if tnodes.get(t) is None:
        tnodes[t]=t
        vnodes.append(p.v)

g.pr(len(positions),len(vnodes))

for v in vnodes:
    g.pr(v)
#@+node:ekr.20051104081502.28: *5* test of list comparisons
stack1 = ["a","b","c"]
stack2 = ["a","b","c"]
stack3 = ["a","b","d"]
stack4 = ["a","b"]
g.pr(stack1 == stack2)
g.pr(stack1 == stack3)
g.pr(stack1 == stack4)
#@+node:ekr.20051104081502.29: *5* test that childIndex doesn't mess with p
g.pr(p.h)
g.pr(p.childIndex())
g.pr(p.h)
#@+node:ekr.20051104081502.30: *5* Test of __cmp__ vrs equal
import timeit

s1 = '''\
class test(object):
    def __cmp__(self,p2):   return 0
    def equal(self,p2):     return 0
p1 = test() ; p2 = test()'''

s2 = '''\
class test:
    def __cmp__(self,p2):   return 0
    def equal(self,p2):     return 0
p1 = test() ; p2 = test()'''

s3 = '''\
import leoNodes
p1 = leoNodes.position(None,[])
p2 = leoNodes.position(None,[])'''

for s in (s1,s2,s3):
    t1 = timeit.Timer(stmt='p1==p2',setup=s).timeit()
    t2 = timeit.Timer(stmt='p1.equal(p2)',setup=s).timeit()
    g.pr("%2.2f,%2.2f,%0.2f" % (t1,t2,t1/t2))
#@+node:ekr.20051104081502.31: *5* Test print
# "LPT1:", "PRN:" and "PRN" all freeze

s = 'stuff\n'
port = 'USB002'

try:
    f = file(port,'w')
    f.write(s)
    f.flush()
    f.close()
    g.pr("done")
except IOError:
    g.pr("Can not open",port)
#@+node:ekr.20051104081502.32: *5* String-based imports...
@ By far the simplest way is just to write the string to a temp file, then import the temp files.

All other approaches quickly get deeply involved with Leo's internals...
#@+node:ekr.20051104081502.33: *6* import from string
@language plain

The first idea was to use Python's imp module to simulate an import from a file.  This does not work well because imp expects a file, not a StringIO object.

The second idea was to use Python's parser module.  But this returns an instance type, not a module.

A third idea would be to subclass the file type to fool the imp module.

A fourth idea would be to use the ihooks module.  Apparently this module was designed to do something like what I am trying to do!  However, there doesn't seem to be docs for it, so I have imported the code...

@color
#@+node:ekr.20051104081502.34: *7* @@test import from string
import imp
import StringIO

@
load_module( name, file, filename, description) 

Load a module that was previously found by find_module() (or by an otherwise conducted search yielding compatible results). This function does more than importing the module: if the module was already imported, it is equivalent to a reload()! The name argument indicates the full module name (including the package name, if this is a submodule of a package). The file argument is an open file, and filename is the corresponding file name; these can be None and '', respectively, when the module is not being loaded from a file. The description argument is a tuple, as would be returned by get_suffixes(), describing what kind of module must be loaded. 
If the load is successful, the return value is the module object; otherwise, an exception (usually ImportError) is raised. 

Important: the caller is responsible for closing the file argument, if it was not None, even when an exception is raised. This is best done using a try ... finally statement.
@c

s = """

def foobar(): pass

"""

@ get_suffixes( ) 

Return a list of triples, each describing a particular type of module. Each triple has the form (suffix, mode, type), where suffix is a string to be appended to the module name to form the filename to search for, mode is the mode string to pass to the built-in open() function to open the file (this can be 'r' for text files or 'rb' for binary files), and type is the file type, which has one of the values PY_SOURCE, PY_COMPILED, or C_EXTENSION, described below.
@c

g.pr('-' * 20)
description = (".py","r",imp.PY_SOURCE)
theFile = StringIO.StringIO(s) # Create a file-like object
g.pr(repr(theFile))
try:
    imp.load_module("myModule",theFile,"myFileName",description)
except:
    g.es_exception()



#@+node:ekr.20051104081502.35: *6* Subclass the file type for use with imp module
if 0:
    class myFile(file):
        pass

    g.pr(myFile)
    g.pr(issubclass(myFile,file))
    g.pr(isinstance(myFile,file))
    g.pr(super(myFile))
    g.pr(__import__)

if 0:
    old_import = __import__

    def myImport(*args,**keys):
        g.pr("myImport")
        global old_import
        old_import(*args,**keys)

    __import__ = myImport

mod = __import__("leoApp")
g.pr(mod)
#@+node:ekr.20051104081502.36: *6* Use parser module to simulate import from string
import compiler

for child in p.children_iter():
    h = child.h
    body = child.b

    try:
        val = compiler.parse(body)
        g.pr(type(val))
        g.pr(val)
    except SyntaxError:
        g.es("Syntax error: %s" % h,color="blue")
#@+node:ekr.20051104081502.37: *7* test1
import doctest
g.pr(doctest)
#@+node:ekr.20051104081502.209: *4* Make sure openWith changes are benign
arg = "arg" ; filename = "fileName"
path = "path" ; shortPath = "shortPath"
vtuple = "vtuple"

def test(a,b):
    assert(a==b)

test(
    "os.system("+arg+shortPath+")",
    "os.system(%s)" % (arg+shortPath))
test(
    "os.startfile("+arg+shortPath+")",
    "os.startfile(%s)" % (arg+shortPath))
test(
    "exec("+arg+shortPath+")",
    "exec(%s)" % (arg+shortPath))
test(
    "os.spawnl("+arg+","+filename+','+ shortPath+")",
    "os.spawnl(%s,%s,%s)" % (arg,filename,shortPath))
test(
    "os.spawnv("+arg[0]+","+repr(vtuple)+")",
    "os.spawnv(%s,%s)" % (arg[0],repr(vtuple)))
#@+node:ekr.20051104081502.111: *4* Perfect import stuff...
@language python
@tabwidth -4
#@+node:ekr.20051104081502.112: *5* Mulder Update script
# EKR: I don't remember the status of this.

@language python

import shutil

testing = True
sourcedir=r"c:/prog/test/perfectImport"
targetdir=r"c:/prog/test/perfectImport/leo"
s1 = g.os_path_join(sourcedir,"leoAtFile.py")
t1 = g.os_path_join(targetdir,"leoAtFile.py")
files = [(s1,t1)]

@others

g.pr('\n' + '-' * 20)
sync(files) # push or pull, depending on date.
#@+node:ekr.20051104081502.113: *6* sync
def sync(files):

    """Do a pull or a push, depending on the date of the files."""

    none, push, pull = 'None', 'push', 'pull'
    mu = g.mulderUpdateAlgorithm()

    for sourcefilename, targetfilename in files:
        << compute sourcetime and targettime >>
        << compute operation >>
        if operation == push:
            if testing: g.pr(push, sourcefilename, targetfilename)
            strippedLines = mu.removeSentinelsFromFile(sourcefilename)
            mu.write_if_changed(strippedLines,sourcefilename,targetfilename)
            mu.copy_time(sourcefilename,targetfilename)
        elif operation == pull:
            if testing: g.pr(pull, sourcefilename, targetfilename)
            if sourcetime:
                mu.propagateDiffsToSentinelsFile(sourcefilename,targetfilename)
                mu.copy_time(targetfilename,sourcefilename)
            else:
                shutil.copy2(targetfilename,sourcefilename)
#@+node:ekr.20051104081502.114: *7* << compute sourcetime and targettime >>
sourcetime = targettime = None

if g.os_path_exists(sourcefilename):
    sourcetime = g.os_path_getmtime(sourcefilename)

if g.os_path_exists(targetfilename):
    targettime = g.os_path_getmtime(targetfilename)
#@+node:ekr.20051104081502.115: *7* << compute operation >>
operation = None
if sourcetime:
    if targettime:
        if sourcetime > targettime:
            operation = push
        elif sourcetime < targettime:
            operation = pull
    else:
        operation = push
elif targettime:
    operation = pull
#@+node:ekr.20051104081502.116: *5* Perfect Import Script
# Run this script to import a file.
# This is undoable because the Import @file command is undoable.

path = r"c:\prog\test\perfectImport"

# Two files from Python23/Lib
name1 = g.os_path_join(path,"formatter.py")
name2 = g.os_path_join(path,"SimpleHTTPServer.py")
names = [name1]

c.importCommands.importFilesCommand (names,"@file",
    perfectImport=True,testing=False,verbose=True)
#@+node:ekr.20051104081502.117: *4* Printing tests...
#@+node:ekr.20051104081502.118: *5* Print findAllPotentiallyDirtyNodes
g.pr('-'*20)

for p in c.allNodes_iter():
    if p.isDirty():
        vnodes = p.findAllPotentiallyDirtyNodes()
        g.pr('-'*5, p)
        for v in vnodes:
            g.pr(v)

g.pr("done")
#@+node:ekr.20051104081502.119: *5* Print iterations: do not delete
import leoNodes

position = leoNodes.position

@others

current = pos = c.p
child1 = current.firstChild()
child2 = child1.firstChild()

if 0:
    g.pr('-'*10, "parents")
    for p in child2.parents_iter(): g.pr(p)
if 0:
    g.pr('-'*10, "subtree")
    for p in pos.subtree_iter(): g.pr(p)
if 0:
    g.pr('-'*10, "children")
    for p in child1.children_iter(): g.pr(p)
if 0:
    g.pr('-'*10, "siblings")
    for p in pos.siblings_iter(): g.pr(p)
if 1:
    g.pr('-'*10, "all nodes")
    for p in c.allNodes_iter():
        g.pr(p.isCloned(),p)
#@+node:ekr.20051104081502.120: *6* b
#@+node:ekr.20051104081502.121: *7* c
#@+node:ekr.20051104081502.122: *8* c2
#@+node:ekr.20051104081502.123: *9* c3
#@+node:ekr.20051104081502.124: *9* c4
#@+node:ekr.20051104081502.125: *6* Clone test data
#@+node:ekr.20051104081502.126: *7* aa
#@+node:ekr.20051104081502.127: *7* a
#@+node:ekr.20051104081502.120: *8* b
#@+node:ekr.20051104081502.121: *9* c
#@+node:ekr.20051104081502.122: *10* c2
#@+node:ekr.20051104081502.123: *11* c3
#@+node:ekr.20051104081502.124: *11* c4
#@+node:ekr.20051104081502.128: *7* d
#@+node:ekr.20051104081502.127: *8* a
#@+node:ekr.20051104081502.120: *9* b
#@+node:ekr.20051104081502.121: *10* c
#@+node:ekr.20051104081502.122: *11* c2
#@+node:ekr.20051104081502.123: *12* c3
#@+node:ekr.20051104081502.124: *12* c4
#@+node:ekr.20051104081502.129: *7* e
#@+node:ekr.20051104081502.130: *7* z
#@+node:ekr.20051104081502.131: *6* last node
#@+node:ekr.20051104081502.145: *5* Print isAnyAtFileNode
g.pr('-'*20)

for p in c.allNodes_iter():
    if p.isAnyAtFileNode():
        g.pr(p)

g.pr("done")
#@+node:ekr.20051104081502.146: *5* Print fundChildrenOf and
tm = c.testManager

g.pr("children", '-' * 20)
children = tm.findChildrenOf(p)
for child in children: g.pr(child.h)

g.pr("subtree", '-' * 20)
descendants = tm.findSubnodesOf(p)
for descendant in descendants: g.pr(descendant.h)
#@+node:ekr.20051104081502.147: *5* Tests of pickle & hexlify
import binascii
import pickle

d = { "a":True }

g.pr('-' * 40)

s = pickle.dumps(d,bin=True)
s2 = binascii.hexlify(s)
g.pr(`s`,s2)

s3 = binascii.unhexlify(s2)
d2 = cPickle.loads(s3)

g.pr(`d2`)
g.pr(d == d2, d is d2)
#@+node:ekr.20051104081502.148: *5* Test of undo registration
def redoBletch(self):
    g.trace()

def undoBletch(self):
    g.trace()

u = c.undoer

if 0:
    # bad functions
    u.registerUndoHandlers("Bletch","abc","xyz")
else:
    u.registerUndoHandlers("Bletch",undoBletch,redoBletch)

# "Execute" the Bletch command :-)  The Edit command should contain "Undo Bletch"
u.setUndoParams("Bletch",p)

# Selecting "Undo Bletch" will enable "Redo Bletch", etc.
#@+node:ekr.20051104081502.149: *5* Test of unknownAttributes
d = { "a":True }

if 1:
    # Warning: executing this in the a2 code base will cause any save operation to fail.
    p.v.unknownAttributes = { "myPlugin" : d }

g.pr(repr(p.v.unknownAttributes))
#@+node:ekr.20051104081502.150: *5* Test of "end1" hook
import leoPlugins

def onEnd (tag,keys):
    g.pr("onEnd",tag,keys)

count = 0

def onIdle (tag,keys):
    global count ; count += 1
    if count % 10 == 0:
        g.pr("onIdle",count,keys.get("c"))

leoPlugins.registerHandler("end1", onEnd)
g.pr("onEnd registered as end1 hook")

leoPlugins.registerHandler("idle", onIdle)
g.pr("onIdle registered as idle hook")
#@+node:ekr.20051104081502.151: *5* Print timestamps of all nodes
for p in c.all_positions_iter():
    g.pr(p.v.t.fileIndex)
#@+node:ekr.20051104081502.152: *5* test of focus
g.pr(c.frame.bodyCtrl.focus())
#@+node:ekr.20051104081502.153: *5* Using a generator instead of readLinesClass
# This kind of code is used in the prototypes of new commands.

from __future__ import generators

@others

lines = "a\nb\nc\nd"

if 1: # Both work
    readline = g.readLinesGenerator(lines).next
else:
    readline = g.readLinesClass(lines).next

g.pr('-' * 20)

if 1: # Both work
    for s in g.readLinesGenerator(lines):
        g.pr(s,)
else:
    while 1:
        s = readline()
        if s: g.pr(s,)
        else: break

g.pr('\n' + '-' * 20)
#@+node:ekr.20051104081502.154: *5* Test of better error messages in Execute Script command
# Test
@others
# Last
#@+node:ekr.20051104081502.155: *6* Contains error
a = 1
g.pr("hello")
c = b
#@+node:ekr.20051104081502.157: *5* print all docstrings from a module
import leoTest
import types

specialDictNames = ('__builtins__','__doc__','__name__','__file__','__module__')

def printDoc(x,s):
    if hasattr(x,"__doc__") and x.__doc__:
        g.pr("%4d %s" % (len(x.__doc__),s))
    else:
        g.pr("%4s %s" % (' ',s))

g.pr('-' * 60)
g.pr("%4d %s" % (len(leoTest.__doc__),"leoTest"))

if 1:
    for s in leoTest.__dict__:
        if s not in specialDictNames:
            x = getattr(leoTest,s)
            if type(x) != types.ModuleType:
                printDoc(x,s)
                if type(x) == types.ClassType:
                    for s2 in x.__dict__:
                        x2 = getattr(x,s2)
                        if s2 not in specialDictNames:
                            g.pr(' '*4,)
                            printDoc(x2,s2)
else:
    << print names sorted by type >>
#@+node:ekr.20051104081502.158: *6* << print names sorted by type >>
for theType,typeName in (
    (types.ModuleType,"modules"),
    (types.ClassType,"classes"),
    (types.FunctionType,"functions"),
):

    g.pr("\n%s..." % typeName)
    for s in leoTest.__dict__:

        if s not in specialDictNames:
            x = getattr(leoTest,s)
            if type(x) == theType:
                printDoc(x,s)
                if theType == types.ClassType:
                    g.pr("\tmethods...")
                    for s2 in x.__dict__:
                        x2 = getattr(x,s2)
                        if s2 not in specialDictNames:
                            g.pr("\t",newline=False)
                            printDoc(x2,s2)
#@+node:ekr.20051104081502.216: *4* Registering & unregistering the "new" drawing hooks
#@+node:ekr.20051104081502.217: *5* Register all new hooks
import leoPlugins as plugins

def traceHook(tag,event):
    g.trace(tag)

tags = (
    "boxclick1","boxclick2",
    "drag1","drag2",
    "dragging1","dragging2",
    "enddrag1","enddrag2",
    "iconclick1","iconclick2"  , 
    "iconrclick1","iconrclick2",
    "icondclick1","icondclick2",
)

plugins.registerHandler(tags,traceHook)

handlers = plugins.getHandlersForTag(tags)
if handlers:
    g.pr("-" * 20)
    for h in handlers:
        g.pr(h)
#@+node:ekr.20051104081502.218: *5* Unregister all new hooks
import leoPlugins as plugins

tags = (
    "boxclick1","boxclick2",
    "drag1","drag2",
    "dragging1","dragging2",
    "enddrag1","enddrag2",
    "iconclick1","iconclick2"  , 
    "iconrclick1","iconrclick2",
    "icondclick1","icondclick2",
)

for tag in tags:
    handlers = plugins.getHandlersForTag(tag)
    if handlers:
        g.pr(handlers)
        for f in handlers:
            plugins.unregisterHandler(tag,f)

handlers = plugins.getHandlersForTag(tags)
if handlers:
    g.pr("-" * 20)
    for h in handlers:
        g.pr(h)
#@+node:ekr.20051104081502.219: *5* Print all new hooks
import leoPlugins as plugins

tags = (
    "boxclick1","boxclick2",
    "drag1","drag2",
    "dragging1","dragging2",
    "enddrag1","enddrag2",
    "iconclick1","iconclick2"  , 
    "iconrclick1","iconrclick2",
    "icondclick1","icondclick2",
)

handlers = plugins.getHandlersForTag(tags)
if handlers:
    g.pr("-" * 20)
    for h in handlers:
        g.pr(h)
#@+node:ekr.20051104081502.538: *4* ReportLab sample scripts
import sys
sys.path.append(r'c:\reportlab_1_20')

debug = True

@others

from reportlab.pdfgen import canvas
c = canvas.Canvas('hello.pdf')
for i in (10,50):
    text(c,'x'*10,i,i)
# pencil(c,text='Note')

key = 'key1'

c.bookmarkPage(key)
c.addOutlineEntry('OutlineEntry',key)


c.showPage()
c.save()
#@+node:ekr.20051104081502.539: *5* text
def text(c,text,i=100,j=100):
    c.drawString(i,j,text)
#@+node:ekr.20051104081502.540: *5* pencil
def pencil(canvas, text="No.2"):
    from reportlab.lib.colors import yellow, red, black,white
    from reportlab.lib.units import inch
    u = inch/10.0
    canvas.setStrokeColor(black)
    canvas.setLineWidth(4)
    # draw erasor
    canvas.setFillColor(red)
    canvas.circle(30*u, 5*u, 5*u, stroke=1, fill=1)
    # draw all else but the tip (mainly rectangles with different fills)
    canvas.setFillColor(yellow)
    canvas.rect(10*u,0,20*u,10*u, stroke=1, fill=1)
    canvas.setFillColor(black)
    canvas.rect(23*u,0,8*u,10*u,fill=1)
    canvas.roundRect(14*u, 3.5*u, 8*u, 3*u, 1.5*u, stroke=1, fill=1)
    canvas.setFillColor(white)
    canvas.rect(25*u,u,1.2*u,8*u, fill=1,stroke=0)
    canvas.rect(27.5*u,u,1.2*u,8*u, fill=1, stroke=0)
    canvas.setFont("Times-Roman", 3*u)
    canvas.drawCentredString(18*u, 4*u, text)
    # now draw the tip
    penciltip(canvas,debug=0)
    # draw broken lines across the body.
    canvas.setDash([10,5,16,10],0)
    canvas.line(11*u,2.5*u,22*u,2.5*u)
    canvas.line(22*u,7.5*u,12*u,7.5*u)
#@+node:ekr.20051104081502.541: *5* penciltip
def penciltip(canvas, debug=1):
    from reportlab.lib.colors import tan, black, green
    from reportlab.lib.units import inch
    u = inch/10.0
    canvas.setLineWidth(4)
    if debug:
        canvas.scale(2.8,2.8) # make it big
        canvas.setLineWidth(1) # small lines
    canvas.setStrokeColor(black)
    canvas.setFillColor(tan)
    p = canvas.beginPath()
    p.moveTo(10*u,0)
    p.lineTo(0,5*u)
    p.lineTo(10*u,10*u)
    p.curveTo(11.5*u,10*u, 11.5*u,7.5*u, 10*u,7.5*u)
    p.curveTo(12*u,7.5*u, 11*u,2.5*u, 9.7*u,2.5*u)
    p.curveTo(10.5*u,2.5*u, 11*u,0, 10*u,0)
    canvas.drawPath(p, stroke=1, fill=1)
    canvas.setFillColor(black)
    p = canvas.beginPath()
    p.moveTo(0,5*u)
    p.lineTo(4*u,3*u)
    p.lineTo(5*u,4.5*u)
    p.lineTo(3*u,6.5*u)
    canvas.drawPath(p, stroke=1, fill=1)
    if debug:
        canvas.setStrokeColor(green) # put in a frame of reference
        canvas.grid([0,5*u,10*u,15*u], [0,5*u,10*u])
#@+node:ekr.20051104081502.272: *4* Standalone imports
#@+node:ekr.20051104081502.273: *5* test that of standalone imports of leo files
import glob,sys,traceback

def printModules():
    mods = sys.modules.keys()
    mods.sort()
    for mod in mods: g.pr(mod)

def leoModules():
    files = glob.glob(r'%s\*.py' % g.app.loadDir)
    modules = []
    for file in files:
        path,file = g.os_path_split(file)
        module,ext = g.os_path_splitext(file)
        if g.match(module,0,'leo'):
            modules.append(module)
    return modules

def delLeoModules():
    for module in leoModules():
        if module in sys.modules:
            del sys.modules[module]

def test():
    for module in leoModules():
        g.pr(module)
        exec 'import %s' % module in {},{}
        del sys.modules[module]

delLeoModules()        
test()
# printModules()
#@+node:ekr.20051104081502.274: *5* Script to run in Idle
def test():
    '''Tests whether all files can be imported.'''
    import glob, os, sys, traceback
    dir = r'c:\prog\leoCVS\leo\src'
    files = glob.glob(r'%s\*.py' % dir)
    modules = []
    for file in files:
        path,file = os.path.split(file)
        module,ext = os.path.splitext(file)
        if module[:3] == 'leo':
            modules.append(module)
    for module in modules:
        g.pr(module)
        try:
            exec 'import %s' % module in {},{}
            del sys.modules[module]
        except:
            traceback.print_exc()

def printModules():
    import sys
    mods = sys.modules.keys()
    mods.sort()
    for mod in mods: g.pr(mod)

#@+node:ekr.20051104081502.307: *4* test local settings (c.redirect_execute_script_output_to_log_pane)
g.es(c.redirect_execute_script_output_to_log_pane)
g.es(c.config.redirect_execute_script_output_to_log_pane)
g.pr('hello')

#assert c.redirect_execute_script_output_to_log_pane is True
#assert c.config.redirect_execute_script_output_to_log_pane is True
#@+node:ekr.20051104081502.164: *4* test of 4.3 str_ attributes
#@+node:ekr.20051104081502.165: *5* set
# Set the attribute.
d = {'str_ekr_attribute': 'abc'}
p.v.t.unknownAttributes = d
#@+node:ekr.20051104081502.166: *5* get
for p in c.allNodes_iter():
    h = p.h
    if hasattr(p.v.t,'unknownAttributes'):
        d = p.v.t.unknownAttributes
        val = d.get('str_ekr_attribute')
        if val:
           g.es('str_ekr_attribute is: %s' % val)
#@+node:ekr.20051104081502.316: *4* test of an exception in another module
c.testManager.throwAssertionError()
#@+node:ekr.20051104081502.210: *4* Test of autocompleter
@language python

# Type a period to autocomplete
leoTest

# Type an open paren to bring up calltip.
c.testManager.findAllAtFileNodes
#@+node:ekr.20051104081502.322: *4* Test of g.getScript with forcePythonSentinels = False
@language html
#@+node:ekr.20051104081502.323: *5* g.getScript
g.pr('-'*20)
g.pr(g.getScript(c,p,forcePythonSentinels=False))
#@+node:ekr.20051104081502.324: *5* html stuff
<body>
@others
</body>
#@+node:ekr.20051104081502.325: *6* body
This is a body
#@+node:ekr.20051104081502.315: *4* test of NameError traceback
# Comment

g.pr(z)
#@+node:ekr.20051104081502.318: *4* Test of os.spawnv calls to c.openWith
table = ('spawnv',None,(
    'os.spawnv',[
    r'c:\vim\vim63\gvim.exe',
    ' --servername LEO ',
    ' --remote-silent ',
    ],
    ".py")),

c.frame.menu.createOpenWithMenuFromTable(table)
#@+node:ekr.20051104081502.321: *4* Test of redirected scipt with error
# To run this test, set @bool redirect_execute_script_output_to_log_pane = True in the @settings tree.

g.pr('hi')
g.pr(c.config.redirect_execute_script_output_to_log_pane)
g.pr(c.xyzzy)
#@+node:ekr.20051104081502.183: *4* Test of reportBadChars
s = u"ß"

g.reportBadChars(s,"latin_1")

g.pr(g.toEncodedString(s,"latin_1"))
#@+node:ekr.20051104081502.234: *4* test of tab_width & tab_width ivars
g.pr(c)
g.pr('use_plugins',c.use_plugins)
g.pr('tab_width',c.tab_width)
g.pr('page_width',c.page_width)
#@+node:ekr.20051104081502.211: *4* Test of template plugin
#@+node:ekr.20051104081502.213: *5* A node that uses the template
#@+node:ekr.20051104081502.214: *4* test of using changes to Go To Line number to handle scripts
# Go To Line number now assumes that selected node is
# the root of a script if there is no ancestor @file node.

@others

# last line
#@+node:ekr.20051104081502.215: *5* node that throws exception
# We should also be able to use the goto line number command to get to the erroneous line.

a = 1/0 # ZeroDivisionError

b = 2
#@+node:ekr.20051104081502.547: *4* Test of warnings of conflicting shortcuts
# This problem has been around forever.
g.pr('-' * 40)
# Yes. We *do* want to warn in c.config.exists.
g.pr('exists',g.app.config.exists(c,'showMinibuffer','bool'))
val = c.config.getBool('showMinibuffer')
g.pr('bool:showMinibuffer',val)
val = c.config.getShortcut('showMinibuffer')
g.pr('shortcut:showMinibuffer',val)
#@+node:ekr.20051104081502.551: *4* test k.registerCommand
k = c.keyHandler

def f (event):
    g.es_print('Hello',color='purple')

def f2 (event):
    g.es_print('Hello2',color='purple')

k.registerCommand('print-hello','Alt-Ctrl-Shift-p',f)
k.registerCommand('print-hello2',None,f2)
#@+node:ekr.20051104081502.226: *4* Tests of leoGlobals
#@+node:ekr.20051104081502.227: *5* @test g.rawPrint
g.rawPrint("Test of g.rawPrint")
g.redirectStdout()
g.rawPrint("Test of g.rawPrint")
g.restoreStdout()
#@+node:ekr.20051104081502.232: *5* test of g.pdb
g.pdb()
#@+node:ekr.20051104081502.233: *5* Test of g.app.debugSwitch
g.pr(g.app.debugSwitch)

g.app.debugSwitch = 0 # 2: drop into pdb

zerodivide = 1 / 0
#@+node:ekr.20051104081502.326: *4* Tests of rst3 plugin
@nocolor
@pagewidth 100
@language python
#@+node:ekr.20051104081502.327: *5* @rst ../doc/ListManagerDocs.html
@language python

@ @rst-options
code_mode = False
show_leo_directives = True
number_code_lines = False
@c

#########################
ListManager Documentation
#########################

:Author: Steven Zatz, Modified by EKR.
:Contact: slzatz@hotmail.com
:Date: $Date: 2008/02/14 14:59:04 $
:Status: This is a "work in progress"
:Revision: $Revision: 1.247 $
:Copyright: Application and documentation use the Python license which is compatible with the GPL. 

This is experimental documentation of a program called ListManager, written in
Python and wxPython using Leo to create both the application code and the
associated reST documentation.

ListManager is an application that allows a group of people working on a joint
project to maintain a common list of todos and related items that have owners,
due dates and associated notes. The application uses mysql as its database for
group use and also uses sqlite for locally resident databases for personal
lists. It works in conjunction with Outlook to allow email messages to be sent
to ListManager for inclusion in lists and uses Outlook to mail messages to
users.

.. contents:: Table of Contents
#@+node:ekr.20051104081502.328: *6* wxListManager.py
@language python
@color
@others

@ @rst-options
code_mode = True
@c
#@+node:ekr.20051104081502.329: *7* Initial stuff
@ @rst-markup

Nothing unusual in what follows:  we start with the module imports, setting some global constants including Menu Ids and read the ListManager.ini file.
#@+node:ekr.20051104081502.330: *8* Module Imports
from wxPython.wx import *
from wxPython.lib.mixins.listctrl import wxListCtrlAutoWidthMixin

import os
import time
import pickle
import socket
import select
import random
import ConfigParser
import threading
import re
import sys

from pywintypes import CreateGuid
from win32com.client import Dispatch
#import win32pdh
import win32api
#from win32com.client import constants #--> just needed two constants...

import MySQLdb
import sqlite
import mx.DateTime

from LMDialogs import CalendarDialog, ModifierDialog, TicklerDialog, MailDialog,LoggerDialog, FinishedDialog, FindDialog, EvalDialog, TreeDialog, StartupDialog
#from wxTreeCtrl import TreeDialog

from printout import PrintTable
#@+node:ekr.20051104081502.331: *9* @rst-no-head About imports
@nocolor

os
    uses ``os.getcwd``, ``os.path.split``, ``os.chdir``, ``os.path.join``, ``os.path.getmtime``, ``os.startfile``, ``os.environ``

time
    uses ``time.sleep``, ``time.asctime``

pickle
    used to serialize data that is moved from Outlook to ListManager via sockets.  

socket
    as noted above, a socket is opened between Outlook and ListManager to move messages back and forth

select
    ListManager selects on the socket to see if there is a message that has been queued by Outlook

random
    used by the reminder popup to select messages

ConfigParser
    not surprisingly, using ConfiParser to parse the ListManager.ini file.  

threading
    more for fun than absolute necessity, a thread is opened on starting the program that constructs the list of owners for items.  In theory, if the datasize and number of Lists were large enough it could delay the appearance of the GUI and its initial responsiveness if we didn't construct the ownerlist in a thread.  On the other hand, it really let me play with threads and with creating a custom event that signalled the construction of the owner list to the main thread by posting a custom event.

re
    mainly using ``re.sub('[\\/:*"<>|\?]','-',f)`` to make sure that files are constructed only with legal characters.  Also searching the body text of nodes using re because it allows case insensitive searches through ``re.compile(pat, re.I)``.

pywintypes.CreateGuid
    probably should use pure python GUID that is in ASPN cookbook but it was easiest to just use the Windows GUID function.  Thank you Mark Hammond for win32all.

win32com.client.Dispatch
    used when launching Outlook to send email messages

win32api
    using win32api.GetUserName() in case there is no user name in the ini file or no ini file

MySQLdb
    using Andy Dustman's python extension module to connect to mysql back-end.

sqlite
    using  D. Richard Hipp's python extension to connect to local sqlite databases

import mx.DateTime
    using Marc-André Lemburg's mx.DateTime for dealing with datetime stuff in the databases

CalendarDialog, ModifierDialog, TicklerDialog, MailDialog,LoggerDialog, FinishedDialog, FindDialog, EvalDialog, TreeDialog, StartupDialog
   should just import LMDialogs and then access each dialog class by LM.WhateverDialog

printout.PrintTable
    There was an existing wxPython print module for printing from tables that I have modified to print Lists.

*#from win32com.client import constants*
    probably not wise but since the app only needs two constants from this module, just set the directly.  If MSFT decides to change the api, this is not good.
#@+node:ekr.20051104081502.332: *8* Constants
cwd = os.getcwd()
DIRECTORY = os.path.split(cwd)[0]
os.chdir(DIRECTORY)
del cwd

#Outlook Constants
olMailItem = 0x0
olFlagMarked = 0x2

OFFLINE_ONLY = False #False-> Online only  ; True-> Online and Offline possible; REMOTE_HOST = None -> Offline only

VERSION = '1.02'

@ @rst-markup

The following two global constants are needed to create emails through Outlook via COM::

    olMailItem = 0x0
    olFlagMarked = 0x2

For some reason, it seemed easier to just include them explicitly rather than worrying about generating all the Outlook constants in order to use early binding.  I supppose if MSFT changes the api, that would be a problem.
#@+node:ekr.20051104081502.333: *8* Menu IDs
@ @rst-markup
Menu Ids -- not much more to say although there should be something to say.
@c

#File Menu-----------------#
idNEWLIST = 1000
idOPENLIST = 1010
idCLOSELIST = 1015
idCLOSEALL = 1017
idSAVEAS = 1020
idDELETELIST = 1025
idPAGESETUP = 1030
idPRINT = 1035
idPRINTPREV = 1040
idMAILLIST = 1045
idOFFLINE = 1048
idEXIT = 1050

#Edit Menu-----------------
idCUT = 1055
idCOPY = 1060
idPASTE = 1065
idDELETEITEMS = 1070
idCOMBINEITEMS = 1075
idFIND = 1080

#Item Menu-------------------
idNEWITEM = 1085
idTOGGLEFINISHED = 1090
idEDITOWNER = 1095
idDUEDATE = 1100
idEDITNOTE = 1105
idMAILITEM =1110

#Diplay Menu---------------------
idSHOWFINISHED = 1115
idSHOWALL = 1120
idREFRESH = 1125
idDISPLAYDATE = 1130

#Tool Menu------------------------
idTICKLERACTIVE = 1135
idSHOWNEXT = 1140
idSYNC = 1145
idARCHIVE = 1150
idEVALUATE = 1155
idTOOLPRINT = 1165
idSENDTO = 1170

#Help Menu-------------------------
idABOUT = 1175
idHELP = 1180


#@+node:ekr.20051104081502.334: *8* Read Config File
config_file = os.path.join(DIRECTORY, "List Manager.ini")
defaults = dict(pw='python', db='listmanager', ext='txt', local='wxLMDB:sqlite', x='700', y='400')
cp = ConfigParser.ConfigParser(defaults=defaults)
cp.read(config_file) #ConfigParser closes the file

USER = cp.has_option('User','user') and cp.get('User','user') or win32api.GetUserName()

# the following all have default values provided in the constructor
PW = cp.get('User','pw')
DB = cp.get('Database','db')
NOTE_EXT = cp.get('Note','ext')
LOCAL_HOST = cp.get('Hosts','local')
X = cp.getint('Configuration','x')
Y = cp.getint('Configuration','y')

# the folloowing default to None
MAIL_LIST_PATH = cp.has_option('Mail','path') and cp.get('Mail','path') or None
QUICK_LIST = cp.has_option('User','quicklist') and cp.get('User','quicklist') or None

# the following default to False
STARTUP_DIALOG = cp.has_option('User','startup_dialog') and cp.getboolean('User','startup_dialog')
DELETE_LIST = cp.has_option('User','delete_list') and cp.getboolean('User','delete_list')
OUTLOOK = cp.has_option('Mail','outlook') and cp.getboolean('Mail','outlook')

if cp.has_option('Hosts','remote'):
    REMOTE_HOST = cp.get('Hosts','remote')
else:
    REMOTE_HOST = None
    OFFLINE_ONLY = True

# reading it again because of the way defaults are handled
cp = ConfigParser.ConfigParser()
cp.read(config_file) #ConfigParser closes the file

if cp.has_section('Synchronization'):
    SYNC_TABLES = [t[1] for t in cp.items('Synchronization')]
else:
    SYNC_TABLES = ['follow_ups']

#@+node:ekr.20051104081502.335: *9* @rst-no-head About configuration files
@nocolor

.. sidebar:: A typical *List Manager.ini* file:

    ::

        [Files]
        path0 = wxLMDB:sqlite:mine
        path1 = nycpsszatzsql:mysql:follow_ups

        [Database]
        db = listmanager

        [Note]
        ext = txt

        [Synchronization]
        sync2 = follow_ups
	sync1 = test

        [Hosts]
        remote = nycpsszatzsql:mysql
        local = wxLMDB:sqlite

        [User]
        startup_dialog = true
        user = szatz
        pw = python

        [Mail]
	outlook = true
	path = wxLMDB:sqlite:mail_transfer

        [Configuration]
        y = 642
        x = 975

Application uses the ``ConfigParser`` module ito parse the ini file.  Unfortunately, ``ConfigParser`` doesn't work exactly like I think it should although it has been improved in 2.3.  My main issue is in the handling of default options.  The default options specified through the constructor show up in every section.  For example, if you use the items(*section*) method
then in addition to returning a list of tuples with whatever option/value pairs exist in the section, the list will include all the default option/value pairs, which does not make a whole lot of sense to me.  At the least, there should be a 'nodefaults' argument whose default was *False* but which could be set to *True*.  The following methods should have this option:

- items
- options
- has_option

In any event, because a nodefaults option does not exist, I create the ConfigParser object twice -- once with default options and once without them.  

The application will work fine if there is no ini file. In an effort to save some typing but not be too obscure, many of the options are read such that they default to the correct value either through explicit defaults in the constructor or statements that evaluate to *None* or *False*.

    ``QUICK_LIST = cp.has_option('User','quicklist') and cp.get('User','quicklist') or None``

    ``OUTLOOK = cp.has_option('Mail','outlook') and cp.getboolean('Mail,'outlook')``
#@+node:ekr.20051104081502.336: *7* class ListManager
class ListManager(wxFrame):
    @others

@ @rst-markup

ListManager is the main class in the application and is a sublass of ``wxFrame``, which is typical for a wxPython application.  From a GUI standpoint, the main child window of the ListManager object is a ``wxNoteBook`` object that holds one ``wxListCtrl`` per notebook page and one ``wxListBox``.  The ``wxListCtrl``\s display item information (e.g., name of the item, owners of the item, etc.) for a particular List and the ``wxListBox``\es displays a list of owners that is used to filter the items displayed by the ``wxListCtrl`` object.

Each ``wxListCtrl`` object has its own set of events that it is hooked to (see CreateNewNotebookPage`<< ListControl Events >>`_.
#@+node:ekr.20051104081502.337: *8* Instantiation
#@+node:ekr.20051104081502.338: *9* def __init__
def __init__(self, parent, id, title, size):
    wxFrame.__init__(self, parent, id, title, size = size)

    self.SetIcon(wxIcon('bitmaps//wxpdemo.ico', wxBITMAP_TYPE_ICO))
    self.CreateStatusBar()

    << ListManager Attributes >>
    << Menu Setup >>
    << Toolbar Setup >>
    << Menu/Toolbar Events >>
    << Create Controls>>
    << Layout Stuff >>
    << Other Events >>
    << GUI Instance Objects >>
    << Create Socket >>
    << Load Recent Files >>
    << Idle Timer >>

    ownerthread = threading.Thread(target=self.createownerlist)
    ownerthread.start()
    self.ModifierDialog = None

#@+node:ekr.20051104081502.339: *10* @rst-no-head About the ctor
@nocolor

The ListManager ``__init__`` method is pretty straightforward.  The ``__init__`` arguments are the ones that need to be passed to ``wxFrame __init__`` method. The wxFrame class has the following form:

    ``wxFrame(parent, id, title, pos=wxDefaultPosition, size=wxDefaultSize, style=wxDEFAULT_FRAME_STYLE, name="frame")``

The default style (``wxDEFAULT_FRAME_STYLE``) includes ``wxMINIMIZE_BOX``, ``wxMAXIMIZE_BOX``, ``wxRESIZE_BORDER``, ``wxSYSTEM_MENU``, ``wxCAPTION`` (the latter is the text that appears in the title bar).

``SetIcon`` is a method of ``wxFrame`` that sets the icon in the upper left of the title bar of the frame.  The wxIcon class has the following form:

    ``wxIcon(filename, type, desiredWidth=-1, desiredHeight=-1)``

``CreateStatusBar`` is a method of ``wxFrame``. The wxPython form is:

        ``CreateStatusBar(number=1, style=0, id=-1)``

*number* -->
    number of fields to create. Specify a value greater than 1 to create a multi-field status bar.

``CreateStatusBar`` needs to be called before << Load Recent Files >>.

The various sections of ``__init__`` are explained in their corresponding section::

    << ListManager Attributes >>
    << Menu Setup >>
    << Toolbar Setup >>
    << Menu/Toolbar Events >>
    << Create Controls>>
    << Layout Stuff >>
    << Other Events >>
    << GUI Instance Objects >>
    << Create Socket >>
    << Load Recent Files >>
#@+node:ekr.20051104081502.340: *10* << List Manager Attributes >>
self.PropertyDicts = []
self.ItemLists = []
self.ListCtrls = []
self.OwnerLBoxes = []

self.L = -1
self.curIdx = -1

self.printdata = wxPrintData()
self.printdata.SetPaperId(wxPAPER_LETTER)
self.printdata.SetOrientation(wxPORTRAIT)

#self._options = {} #would be used in loadconfig

self.copyitems = []    
self.modified = {}
self.tickler_active = False

#there is a wxPanel in the AddListControl method so each wxListCtrl has a different panel as parent
#there is a nb_sizer = wxNotebookSizer(nb) class but doesn't seem to make any difference

self.editor = []

self.Cursors = {}
self.sqlite_connections = []
self.popupvisible = False
self.in_place_editor = None
self.showrecentcompleted = 0

self.LC_font = wxFont(9, wxSWISS, wxNORMAL, wxNORMAL)

self.date_titles = {'createdate':"Create Date",'duedate':"Due Date",'timestamp':"Last Modified",'finisheddate':"Completion Date"}
self.attr2col_num = {'priority':0, 'name':1,'owners':2, 'date':3}

self.FindDialog = FindDialog(self, "Find...", "")
self.EvalDialog = EvalDialog(self, "Evaluate...", "")
#@+node:ekr.20051104081502.341: *11* @rst
@nocolor

self.PropertyDicts
    list of dictionaries that describe properties of each ListManager List (note that when referring to a collection of ListManager items a capital *L* List and table are used interchangeably).

self.ItemLists
    list of lists that consist of instance objects of class ``Item``.  Each of the lists contained in self.ItemLists correspond to the items that are being displayed in the ListCtrl.  So ``self.Itemlist[2]`` corresponds to the 2nd tab of the notebook and to the items in self.ListCtrls[2].

The class ``Item`` is just an empty class being used as a convenience to hold item attributes::

    class Item:
        pass

The purpose of the class is just to create an object that can have various attributes as follows:

+-----------------+----------------------------------------------------+
|item.id          |GUID                                                |
+-----------------+----------------------------------------------------+
|item.name        |string that describes the item                      |
+-----------------+----------------------------------------------------+
|item.priority    |integer ranging from 1 (high) to 3 (low)            |
+-----------------+----------------------------------------------------+
|item.owners      |list of the form ["Zatz, Steve", "Hoffman, Steve"]  |
+-----------------+----------------------------------------------------+
|item.note        |string that provides additional info on item        |
+-----------------+----------------------------------------------------+
|item.timestamp   |timestamp indicating when an item was last modified |
+-----------------+----------------------------------------------------+
|item.duedate     |default is None; mx.DateTime date                   |
+-----------------+----------------------------------------------------+
|item.createdate  |mx.DateTime.now() mx.DateTime timestamp             |
+-----------------+----------------------------------------------------+
|item.finisheddate|efaut is None; mx.DateTime date                     |
+-----------------+----------------------------------------------------+

self.ListCtrls
    list of of instance objects of class ListCtrls, which are a subclass of wxPython class wxListCtrl.

self.OwnerLBoxes
    list of of instance objects of wxPython class wxListBox, which is a simple one column List Control.

The wxPython constructor for a wxListBox is:

    ``wxListBox(parent, id, pos=wxDefaultPosition, size=wxDefaultSize, choices=[], style=0)``

self.L
    index of the currently active notebook tab.  If there are any tabs in the notebook then one of them is always selected.  If there are no tabs then this is indicated by setting ``self.L = -1``.

self.curIdx
    currently selected row in the active ``ListCtrl``.  There are times like after a row is deleted in which there may be rows visible but no row is selected.

The following lines set the default printer data::

    self.printdata = wxPrintData()
    self.printdata.SetPaperId(wxPAPER_LETTER)
    self.printdata.SetOrientation(wxPORTRAIT)


The wxPython class ``wxPrintData`` holds a variety of information related to printers and printer device contexts. This class is used to create a wxPrinterDC and a wxPostScriptDC. It is also used as a data member of wxPrintDialogData and wxPageSetupDialogData, as part of the mechanism for transferring data between the print dialogs and the application.

self.copyitems
    list that contains item instance objects that have been copied from one list to be moved to another list.

self.modified
    dictionary that contains the information concerning whether any of several elements have been changed.  Chose a dictionary more to test the idea that I could create a simple method that would update the dictionary and here is an example:

    ``EVT_TEXT(self, self.name.GetId(), lambda e: self.modified.update({'name':1}))``

So this lambda function means that if an ``EVT_TEXT`` event occurs then update the dictionary by adding the key to the dictionary (the value is not used and arbitrarily set to 1).  The wxPython form for the macro ``EVT_TEXT`` is:

    ``EVT_TEXT(window, id, func)``

A ``wxEVT_COMMAND_TEXT_UPDATED`` event is generated when the text in a ``wxTextCtrl`` changes and that is what ``EVT_TEXT`` catches. Note that this event will always be sent when the text control’s content changes - whether this is due to user input or comes programmatically (for example, if ``SetValue()`` is called)

self.Cursors
    dictionary that holds the database cursor objects.  For example, it will look like:  ``{'sqlite':<sqlite cursor object>,'nycpsltszatz':<mysql cursor object>}``

self.tickler_active
    booean determines whether the tickler capabililty is active; can be shut off by unchecking Tickler menu item

self.editor
    list that holds the dictionaries that describe the notes that are edited by the external text editor::

        [
        {
        'table': 'mine',
        'host': 'wxLMDB:sqlite',
        'path': 'C:\\DOCUME~1\\STEVEN~1\\LOCALS~1\\Temp\\Journal Scan schedule.txt',
        'id': '1AB34FB9-9EE6-4AFC-8AF0-FFCA50103BF3',
        'time': 1070850894
        }, 
        {
        'table': 'factoids',
        'host': 'wxLMDB:sqlite',
        'path': 'C:\\DOCUME~1\\STEVEN~1\\LOCALS~1\\Temp\\How many cme programs are sponsored- - 91%.txt', 
        'id': '9CAC4D18-DE1C-4535-B9A5-4CDB1AD3F304', 
        'time': 1070850908
        }
        ]

The method that uses self.editor is `<< Check if Edited File has Changed >>`_.

There is a ``wxPanel`` in the ``AddListControl`` method so each ``wxListCtrl`` has a different panel as parent.

There is a nb_sizer = wxNotebookSizer(nb) class but doesn't seem to make any difference.

self.sqlite_connections
    Here because the sqlite connection has a weakreference that deletes it when you want it around

self.popupvisible
    boolean that is used to ensure that two reminder popups aren't visible at the same time.

self.in_place_editor 
    boolean that indicates whether the inplace item name text editor is active or not.

self.showrecentcompleted
    integer that determines the number of days in the past to retain completed items in the display.

self.LC_font
    default font for all of the ``ListCtrls``:  ``self.LC_font = wxFont(9, wxSWISS, wxNORMAL, wxNORMAL)``

The wxPython ``wxFont`` constructor is:

    ``wxFont(pointSize, family, style, weight, underline=False, faceName="", wencoding=wxFONTENCODING_DEFAULT)``

self.date_titles
    dictionary that holds the various dates that are associated with each item and which can be displayed in the date column.  The dictionary is not modified.  We use one column of each ``ListCtrl`` to display any one of the four dates that that the application tracks. This dictionary associates the item attribute with the text that will be displayed in both the column header for the date and in the dropdown that allows you to change the date:  ``self.date_titles = {'createdate':"Create Date",'duedate':"Due Date",'timestamp':"Last Modified",'finisheddate':"Completion Date"}``

self.attr2col_num
    dictionary that associates the item attribute with the column that attribute is displayed in in the ``ListCtrl``:  ``self.attr2col_num = {'priority':0, 'name':1,'owners':2, 'date':3}``

The following lines construct the Find Dialog and the Dialog that catches errors and shows expressions for debugging::

    self.FindDialog = FindDialog(self, "Find...", "")
    self.EvalDialog = EvalDialog(self, "Evaluate...", "")
#@+node:ekr.20051104081502.342: *10* << Menu Setup >>
filemenu = wxMenu()
filemenu.Append(idNEWLIST, "New List...", "Create a new List")
filemenu.Append(idOPENLIST, "Open List...", "Open a List")
filemenu.Append(idCLOSELIST, "Close", "Close the current List")
filemenu.Append(idCLOSEALL, "Close All", "Close all open Lists")
filemenu.Append(idSAVEAS, "Save As Text File...", "Save the current List")
filemenu.AppendSeparator()
filemenu.Append(idDELETELIST, "Delete List...", "Select a list to delete")
filemenu.AppendSeparator()
filemenu.Append(idPAGESETUP, "Page Setup...")
filemenu.Append(idPRINT, "Print...", "Print the current view")
filemenu.Append(idPRINTPREV, "Print Preview")
filemenu.AppendSeparator()
filemenu.Append(idMAILLIST, "Mail...", "Mail the current view")
filemenu.AppendSeparator()
filemenu.AppendCheckItem(idOFFLINE, "Work Offline")
filemenu.AppendSeparator()
filemenu.Append(idEXIT, "Exit", "Exit the program")

editmenu = wxMenu()
editmenu.Append(idCUT, "Cut\tCtrl+X")
editmenu.Append(idCOPY, "Copy\tCtrl+C")
editmenu.Append(idPASTE, "Paste\tCtrl+V")
editmenu.AppendSeparator()
editmenu.Append(idDELETEITEMS, "Delete")
editmenu.AppendSeparator()
editmenu.Append(idCOMBINEITEMS, "Combine Items...")
editmenu.AppendSeparator()
editmenu.Append(idFIND, "Find...")

itemmenu = wxMenu()
itemmenu.Append(idNEWITEM, "New Item")
itemmenu.AppendSeparator()
itemmenu.Append(idTOGGLEFINISHED, "Toggle Finished")
itemmenu.Append(idEDITOWNER, "Owner...")
itemmenu.Append(idDUEDATE, "Due Date...")
itemmenu.Append(idEDITNOTE, "Note...")
itemmenu.AppendSeparator()
itemmenu.Append(idMAILITEM, "Mail...")

displaymenu = wxMenu()
displaymenu.Append(idSHOWFINISHED, "Show/Hide Finished...")
displaymenu.AppendSeparator()
displaymenu.Append(idSHOWALL, "Show All", "Show all items in the current list")
displaymenu.AppendSeparator()
displaymenu.Append(idREFRESH, "Refresh Display", "Refresh the Display")
displaymenu.Append(idDISPLAYDATE, "Select Date to Display")

toolmenu = wxMenu()
toolmenu.AppendCheckItem(idTICKLERACTIVE, "Tickler Active")
toolmenu.Check(idTICKLERACTIVE,False)
toolmenu.Append(idSHOWNEXT, "Show Next Reminder")
toolmenu.Append(idSYNC, "Synchronize local and remote DBs")
toolmenu.Append(idARCHIVE, "Archive completed items in list...")
toolmenu.Append(idEVALUATE, "Evaluate an expression...")

helpmenu = wxMenu()
helpmenu.Append(idABOUT, "About ListManager")
helpmenu.Append(idHELP, "Help")

menubar = wxMenuBar()
menubar.Append(filemenu, '&File')
menubar.Append(editmenu, 'Edit')
menubar.Append(itemmenu, 'Item')
menubar.Append(displaymenu, 'Display')
menubar.Append(toolmenu, 'Tools')
menubar.Append(helpmenu, 'Help')
self.SetMenuBar(menubar)
toolmenu.Enable(idSHOWNEXT,self.tickler_active)
filemenu.Enable(idDELETELIST,DELETE_LIST)
filemenu.Check(idOFFLINE,OFFLINE_ONLY)

#file history
self.filehistory = wxFileHistory()
self.filehistory.UseMenu(filemenu)

#@+node:ekr.20051104081502.343: *11* @rst
@nocolor

+------------------------+------------------------------------------------+
|**File Menu**           |                                                |
+------------------------+------------------------------------------------+
| "New List... "         ||nl| ``self.OnNewList``                         |
+------------------------+------------------------------------------------+
| "Open List..."         ||ol| ``self.OnOpenList``                        |
+------------------------+------------------------------------------------+
| "Close"                |``self.OnCloseList``                            |
+------------------------+------------------------------------------------+
| "Close All"            |``self.OnCloseAll``                             |
+------------------------+------------------------------------------------+
| "Save As Text File..." |``self.OnSaveAsText``                           |
+------------------------+------------------------------------------------+
| "Delete List..."       ||de| ``self.OnDeleteList``                      |
+------------------------+------------------------------------------------+
| "Page Setup..."        ||ps| ``self.OnPageSetup``                       |
+------------------------+------------------------------------------------+
| "Print..."             ||pt| ``self.OnPrint``                           |
+------------------------+------------------------------------------------+
| "Print Preview"        ||pp| ``lambda e: self.OnPrint(e, prev=True)``   |
+------------------------+------------------------------------------------+
| "Mail..."              |``self.OnMailView``                             |
+------------------------+------------------------------------------------+
| "Work Offline"         |``self.OnWorkOffline``                          |
+------------------------+------------------------------------------------+
| "Exit"                 |``self.OnExit``                                 |
+------------------------+------------------------------------------------+
| **Edit Menu**          |                                                |
+------------------------+------------------------------------------------+
| "Cut" [Ctrl+X ]        ||ec| ``lambda e: self.OnCopyItems(e, cut=True)``|
+------------------------+------------------------------------------------+
| "Copy" [Ctrl+C]        ||ey| ``self.OnCopyItems``                       |
+------------------------+------------------------------------------------+
| "Paste" [Ctrl+V]       ||ep| ``self.OnPasteItems``                      |
+------------------------+------------------------------------------------+
| "Delete"               ||de| ``self.OnDeleteItems``                     |
+------------------------+------------------------------------------------+
| "Combine Items..."     |``self.OnCombineItems``                         |
+------------------------+------------------------------------------------+
| "Find..."              ||fi| ``self.OnFind``                            |
+------------------------+------------------------------------------------+
| **Item Menu**          |                                                |
+------------------------+------------------------------------------------+
| "New Item"             ||ni| ``self.OnNewItem``                         |
+------------------------+------------------------------------------------+
| "Toggle Finished"      ||co| ``self.OnToggleFinished``                  |
+------------------------+------------------------------------------------+
| "Owner..."             ||ow| ``self.OnEditOwner``                       |
+------------------------+------------------------------------------------+
| "Due Date..."          ||dd| ``self.OnDueDate``                         |
+------------------------+------------------------------------------------+
| "Note..."              ||en| ``self.OnEditNote``                        |
+------------------------+------------------------------------------------+
| "Mail..."              ||mi| ``self.OnMailItem``                        |
+------------------------+------------------------------------------------+
| **Display Menu**       |                                                |
+------------------------+------------------------------------------------+
| "Show/Hide Finished..."|``self.OnShowFinished``                         |
+------------------------+------------------------------------------------+
| "Show All"             |``self.OnShowAll``                              |
+------------------------+------------------------------------------------+
| "Refresh Display"      ||re| ``self.OnRefresh``                         |
+------------------------+------------------------------------------------+
|"Select Date to Display"|``self.OnDisplayDateCategory``                  |
+------------------------+------------------------------------------------+
| **Tool Menu**          |                                                |
+------------------------+------------------------------------------------+
| "Tickler Active"       |``self.OnActivateTickler``                      |
+------------------------+------------------------------------------------+
| "Show Next Reminder"   |``self.OnShowTickler``                          |
+------------------------+------------------------------------------------+
| "Synchronize ..."      |``self.OnSync``                                 |
+------------------------+------------------------------------------------+
| "Archive completed..." |``self.OnArchive``                              |
+------------------------+------------------------------------------------+
| "Evaluate expression"  |``self.OnShowEvaluate``                         |
+------------------------+------------------------------------------------+
| **Help Menu**          |                                                |
+------------------------+------------------------------------------------+
| "About ListManager"    |``self.OnShowAbout``                            |
+------------------------+------------------------------------------------+
| "Help"                 |``self.OnShowHelp``                             |
+------------------------+------------------------------------------------+




#@+node:ekr.20051104081502.344: *10* << Toolbar Setup >>
tb = self.CreateToolBar(wxTB_HORIZONTAL|wxTB_FLAT)

tb.AddLabelTool(idNEWLIST, "New (local) List", wxBitmap('bitmaps\\new.bmp'), shortHelp="Create New List")
tb.AddLabelTool(idOPENLIST, "Open", wxBitmap('bitmaps\\open.bmp'), shortHelp="Open List")
tb.AddSeparator()
tb.AddLabelTool(idTOOLPRINT, "Print", wxBitmap('bitmaps\\print.bmp'), shortHelp="Print List")
tb.AddLabelTool(idPRINTPREV, "Preview", wxBitmap('bitmaps\\preview.bmp'), shortHelp="Print Preview")
tb.AddLabelTool(idPAGESETUP, "Setup", wxBitmap('bitmaps\\setup.bmp'), shortHelp="Page Setup")
tb.AddSeparator()
tb.AddLabelTool(idNEWITEM, "New Item", wxBitmap('bitmaps\\new_item.bmp'), shortHelp="Create New Item")
tb.AddSeparator()
tb.AddLabelTool(idREFRESH, "Refresh", wxBitmap('bitmaps\\refresh.bmp'), shortHelp="Refresh Display")     
tb.AddSeparator()
tb.AddLabelTool(idEDITNOTE, "Edit Note", wxBitmap('bitmaps\\edit_doc.bmp'), shortHelp="Edit Note")
tb.AddSeparator()
tb.AddLabelTool(idFIND, "Find", wxBitmap('bitmaps\\find.bmp'), shortHelp = "Find Item")        
tb.AddSeparator()
tb.AddLabelTool(idCUT, "Cut", wxBitmap('bitmaps\\editcut.bmp'), shortHelp ="Cut Item")        
tb.AddLabelTool(idCOPY, "Copy", wxBitmap('bitmaps\\copy.bmp'), shortHelp ="Copy Item")
tb.AddLabelTool(idPASTE, "Paste", wxBitmap('bitmaps\\paste.bmp'), shortHelp="Paste Item")
tb.AddSeparator()
tb.AddLabelTool(idTOGGLEFINISHED, "Toggle Date", wxBitmap('bitmaps\\filledbox.bmp'), shortHelp="Toggle Finished Date")
tb.AddLabelTool(idDELETEITEMS, "Delete", wxBitmap('bitmaps\\delete.bmp'), shortHelp="Delete Item")
tb.AddLabelTool(idDUEDATE, "Due Date", wxBitmap('bitmaps\\calendar.bmp'), shortHelp="Enter Due Date")
tb.AddLabelTool(idEDITOWNER,"Owner", wxBitmap('bitmaps\\owners.bmp'), shortHelp="Select Owner(s)")
tb.AddSeparator()
tb.AddLabelTool(idMAILITEM, "Mail", wxBitmap('bitmaps\\mail.bmp'), shortHelp="Mail Item")

if QUICK_LIST:
    tb.AddSeparator()
    tb.AddLabelTool(idSENDTO, "Send to", wxBitmap('bitmaps\\sendto.bmp'), shortHelp="Send to %s"%QUICK_LIST)

tb.Realize()
#@+node:ekr.20051104081502.346: *10* << Menu/Toolbar Events >>
#File Menu ------------------------------------
EVT_MENU(self, idNEWLIST, self.OnNewList)
EVT_MENU(self, idOPENLIST, self.OnOpenList)
EVT_MENU(self, idCLOSELIST, self.OnCloseList)
EVT_MENU(self, idCLOSEALL, self.OnCloseAll)
EVT_MENU(self, idSAVEAS, self.OnSaveAsText)
EVT_MENU(self, idDELETELIST, self.OnDeleteList)
EVT_MENU(self, idPAGESETUP, self.OnPageSetup)
EVT_MENU(self, idPRINT, self.OnPrint)
EVT_MENU(self, idPRINTPREV, lambda e: self.OnPrint(e, prev=True))
EVT_MENU(self, idOFFLINE, self.OnWorkOffline)
EVT_MENU(self, idMAILLIST, self.OnMailView)      
EVT_MENU_RANGE(self, wxID_FILE1, wxID_FILE9, self.OnFileList)
EVT_MENU(self, idEXIT, self.OnExit)
#Edit Menu ------------------------------------
EVT_MENU(self, idCUT, lambda e: self.OnCopyItems(e, cut=True))        
EVT_MENU(self, idCOPY, self.OnCopyItems)
EVT_MENU(self, idPASTE, self.OnPasteItems)
EVT_MENU(self, idDELETEITEMS, self.OnDeleteItems)
EVT_MENU(self, idCOMBINEITEMS, self.OnCombineItems)
EVT_MENU(self, idFIND, self.OnFind)
#item Menu ------------------------------------
EVT_MENU(self, idNEWITEM, self.OnNewItem)
EVT_MENU(self, idTOGGLEFINISHED, self.OnToggleFinished)             
EVT_MENU(self, idDUEDATE, self.OnDueDate)
EVT_MENU(self, idEDITOWNER, self.OnEditOwner)
EVT_MENU(self, idEDITNOTE, self.OnEditNote)
EVT_MENU(self, idMAILITEM, self.OnMailItem)
#Dips Menu ------------------------------------
EVT_MENU(self, idSHOWFINISHED, self.OnShowFinished)
EVT_MENU(self, idSHOWALL, self.OnShowAll)
EVT_MENU(self, idREFRESH, self.OnRefresh)
EVT_MENU(self, idDISPLAYDATE, self.OnDisplayDateCategory)
#Tool Menu ---------------------------------------
EVT_MENU(self, idTICKLERACTIVE, self.OnActivateTickler)
EVT_MENU(self, idSHOWNEXT, self.OnShowTickler)
EVT_MENU(self, idSYNC, self.OnSync)
EVT_MENU(self, idARCHIVE, self.OnArchive)
EVT_MENU(self, idEVALUATE, self.OnShowEvaluate)
#Help Menu -----------------------------------------
EVT_MENU(self, idABOUT, self.OnShowAbout)
EVT_MENU(self, idHELP, self.OnShowHelp)

EVT_TOOL(self, idTOOLPRINT, lambda e: self.OnPrint(e,showprtdlg=False))

if QUICK_LIST:
    EVT_TOOL(self, idSENDTO, lambda e: self.OnMoveToSpecificList(e,QUICK_LIST))
#@+node:ekr.20051104081502.348: *10* << Create Controls>>
upper_panel = wxPanel(self, -1)   #size = (900,400)
bottom_panel = wxPanel(self, -1, size = (900,150)) #900 note that 000 seems to work???

nb = wxNotebook(upper_panel, -1, size=(900,500), style=wxNB_BOTTOM)

f = wxFont(10, wxSWISS, wxNORMAL, wxNORMAL)
self.name = wxTextCtrl(bottom_panel, -1, size = (285,42), style = wxTE_MULTILINE|wxTE_RICH2)#34 #wxTE_PROCESS_ENTER
self.name.SetDefaultStyle(wxTextAttr("BLACK", font = f))

self.owners = wxTextCtrl(bottom_panel, -1, size = (250,42),style = wxTE_MULTILINE|wxTE_RICH2)
self.owners.SetDefaultStyle(wxTextAttr("BLACK", font = f))

self.note = wxTextCtrl(bottom_panel, -1, size = (400,50), style=wxTE_MULTILINE)

#@+node:ekr.20051104081502.350: *10* << Other Events >>
EVT_TEXT(self, self.name.GetId(), lambda e: self.modified.update({'name':1}))
EVT_TEXT(self, self.note.GetId(), lambda e: self.modified.update({'note':1}))
EVT_TEXT(self, self.owners.GetId(), lambda e: self.modified.update({'owners':1}))

EVT_CLOSE(self, self.OnWindowExit)

EVT_IDLE(self, self.OnIdle)

#@+node:ekr.20051104081502.352: *10* << Layout Stuff >>
#Appears necessary to really get the listcontrol to size with the overall window  
#upper_panel sizer
sizer = wxBoxSizer(wxHORIZONTAL)
sizer.Add(nb,1,wxALIGN_LEFT|wxEXPAND)
upper_panel.SetSizer(sizer)        

#sizer for the row of data items
box = wxBoxSizer(wxHORIZONTAL)
box.Add(self.name,1,wxEXPAND)
box.Add(self.owners,0)

#bottom_panel sizer  
sizer = wxBoxSizer(wxVERTICAL)        
sizer.AddSizer(box, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5)
sizer.Add(self.note,1,wxALIGN_LEFT|wxEXPAND)
bottom_panel.SetSizer(sizer)

sizer = wxBoxSizer(wxVERTICAL)
sizer.Add(upper_panel,1,wxALIGN_TOP|wxEXPAND)
sizer.Add(bottom_panel,0,wxALIGN_TOP|wxEXPAND)

self.SetAutoLayout(1)
self.SetSizer(sizer)
#sizer.Fit(self) #actively does bad things to the dimensions on startup
#@+node:ekr.20051104081502.354: *10* << GUI Instance Objects >>
self.toolmenu = toolmenu
self.filemenu = filemenu
self.nb = nb
self.tb = tb
#@+node:ekr.20051104081502.356: *10* << Create Socket >>
if OUTLOOK:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a TCP socket
    s.bind(('localhost',8888)) # Bind to port 8888
    s.listen(5) # Listen, but allow no more than
    self.sock = s
#@+node:ekr.20051104081502.358: *10* << Load Recent Files >>
try:
    pathlist = [f[1] for f in cp.items('Files')]
except:
    pathlist = []

if pathlist:
    pathlist.sort()
    pathlist.reverse()
    for path in pathlist[1:]:
        self.OnFileList(path=path)

    #don't want to trigger the page change event until n-1 of n files are loaded
    EVT_NOTEBOOK_PAGE_CHANGED(self,nb.GetId(),self.OnPageChange)

    self.OnFileList(path=pathlist[0])
else:
    EVT_NOTEBOOK_PAGE_CHANGED(self,nb.GetId(),self.OnPageChange)



#@+node:ekr.20051104081502.360: *10* << Idle Timer >>
ID_TIMER = wxNewId()
self.timer = wxTimer(self, ID_TIMER) 
EVT_TIMER(self,  ID_TIMER, self.OnIdle)
self.timer.Start(3000)
#@+node:ekr.20051104081502.362: *8* Ownerlist creation methods (used by thread)
#@+node:ekr.20051104081502.364: *9* def createownerlist
def createownerlist(self):

    if REMOTE_HOST and OFFLINE_ONLY is False:
        cursor = self.GetCursor(REMOTE_HOST)
        sql = "SHOW TABLES" #sorted
    else:
        cursor = self.GetCursor(LOCAL_HOST)
        sql = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"

    cursor.execute(sql)
    results = cursor.fetchall()

    #excluding 'system' tables and archive tables
    excluded_tables = ['user_sync','sync','owners']
    tables = [t for (t,) in results if t.find('_archive')== -1 and t not in excluded_tables]

    sql_list = []
    for table in tables:
        sql_list.append("""SELECT owner1 FROM %s UNION SELECT owner2 FROM %s UNION SELECT owner3 FROM %s"""%((table,)*3))

    sql = " UNION ".join(sql_list)
    cursor.execute(sql)
    results = cursor.fetchall()

    _list = [x[0] for x in results]
    if '' in _list:
        _list.remove('')
    if None in _list:
        _list.remove(None)

    self._list = _list

    #posting custom event to signal that this thread is done
    evt = wxPyEvent()
    evt_id = wxNewEventType()
    evt.SetEventType(evt_id)
    self.Connect(-1, -1, evt_id, self.createownerdialog)
    wxPostEvent(self, evt)

#@+node:ekr.20051104081502.366: *9* def createownerdialog
def createownerdialog(self, evt=None):
    self.ModifierDialog = ModifierDialog(parent=self, title="Select owner(s)", size=(180,300), style=wxCAPTION, modifierlist = self._list)
    del self._list

#@+node:ekr.20051104081502.368: *8* Notebook methods
#@+node:ekr.20051104081502.370: *9* def CreateNewNotebookPage
def CreateNewNotebookPage(self, host, table):

    Properties = {'owner':'*ALL',
                'LCdate':'duedate',
                'sort':{'attribute':'priority','direction':0}, #these could be set in Config
                'showfinished':0} #-1 show them all; 0 show none; integer show for that many days

    Properties['table'] = table
    Properties['host'] = host

    self.PropertyDicts.append(Properties)

    self.L = len(self.ItemLists)#could use self.ListCtrls, self.OwnerLBoxes, etc. with a -1

    results = self.ReadFromDB()
    if results is None:
        self.PropertyDicts = self.PropertyDicts[:-1]
        self.L = self.L - 1
        return

    panel = wxPanel(self.nb, -1, size = (900,400))
    LCtrl = ListCtrl(panel, -1, style=wxLC_REPORT|wxSUNKEN_BORDER|wxLC_VRULES|wxLC_HRULES)
    LCtrl.SetFont(self.LC_font)
    self.ListCtrls.append(LCtrl)

    OLBox = wxListBox(panel, -1, size = (126,550), choices = [""], style=wxLB_SORT|wxSUNKEN_BORDER)
    self.OwnerLBoxes.append(OLBox)

    sizer = wxBoxSizer(wxHORIZONTAL)
    sizer.Add(OLBox,0,wxALIGN_LEFT|wxEXPAND)
    sizer.Add(LCtrl,1,wxALIGN_LEFT|wxEXPAND)
    panel.SetSizer(sizer)

    self.ItemLists.append(self.CreateAndDisplayList(results)) 

    << Fill OwnerListBox >>
    << ListControl Events >>

    #img_num = LCtrl.arrows[Properties['sort']['direction']]
    #LCtrl.SetColumnImage(self.attr2col_num[Properties['sort']['attribute']], img_num)

    rdbms = host.split(':')[1]
    if rdbms == 'mysql':
        tab_title = '%s (remote)'%table
    else:
        tab_title = table

    if table in SYNC_TABLES:
        tab_title = '*'+tab_title

    self.nb.AddPage(panel,tab_title)
    self.nb.SetSelection(self.L)

    self.filehistory.AddFileToHistory('%s:%s'%(host,table))

    self.SetStatusText("Successfully loaded %s"%tab_title)

#@+node:ekr.20051104081502.372: *10* << Fill OwnerListBox >>
cursor = self.GetCursor(host)
if cursor is None:
    g.pr("Couldn't get cursor to fill OwnerListBox")
    return

cursor.execute("SELECT owner1 FROM %s UNION SELECT owner2 FROM %s UNION SELECT owner3 FROM %s"%((table,)*3))

owners = [x for (x,) in cursor.fetchall()]

if None in owners:
    owners.remove(None)
if '' in owners:
    owners.remove('')

OLBox.Clear()
for name in owners: 
    OLBox.Append(name)
OLBox.Append('*ALL')
OLBox.SetSelection(0)

#@+node:ekr.20051104081502.374: *10* << ListControl Events >>
LCId = LCtrl.GetId()
EVT_LIST_ITEM_SELECTED(self, LCId, self.OnItemSelected)
EVT_LIST_ITEM_ACTIVATED(self, LCId, self.OnDisplayInPlaceEditor)
EVT_LEFT_DOWN(LCtrl, self.OnLeftDown) 
EVT_LEFT_DCLICK(LCtrl, self.OnLeftDown)
EVT_RIGHT_DOWN(LCtrl, self.OnRightDown)
EVT_LIST_COL_CLICK(self, LCId, self.OnColumnClick)
EVT_LIST_COL_RIGHT_CLICK(self, LCId, self.OnColumnRightClick)

# the following is a ListBox event
EVT_LISTBOX(self, OLBox.GetId(), self.OnFilterOwners)

#@+node:ekr.20051104081502.376: *9* def OnPageChange
def OnPageChange(self, evt=None):
    if self.modified:
        self.OnUpdate()

    self.L = L = self.nb.GetSelection()

    << Find Highlighted Row >>
    << Update Title >>

    evt.Skip() #051403

#@+node:ekr.20051104081502.378: *10* << Find Highlighted Row >>
idx = self.ListCtrls[L].GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)
if idx != -1:
    self.curIdx = idx
    #LCtrl.EnsureVisible(idx)
    self.OnItemSelected()
elif self.ItemLists[L]:
    self.curIdx = 0
    self.ListCtrls[L].SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
    #the line above triggers an OnItemSelected EVT so don't need self.OnItemSelected() 092803
else:
    self.curIdx = -1

#@+node:ekr.20051104081502.380: *10* << Update Title >>
location,rdbms = self.PropertyDicts[L]['host'].split(':')
table = self.PropertyDicts[L]['table']
self.SetTitle("List Manager:  %s:  %s:  %s"%(location,rdbms,table))

#@+node:ekr.20051104081502.382: *8* Tickler methods
#@+node:ekr.20051104081502.383: *9* def OnShowTickler
def OnShowTickler(self, evt=None):
    if self.popupvisible:
        return

    self.popupvisible = True

    host = 'wxLMDB:sqlite'
    cursor = self.Cursors[host]
    table = 'follow_ups'

    sql = "SELECT COUNT() FROM "+table+" WHERE finisheddate IS NULL AND priority > 1"
    cursor.execute(sql)
    results = cursor.fetchone()

    num_items = int(results[0])

    if not num_items:
        return

    if self.modified: #Should decide if this should be put back or not
        self.OnUpdate()

    n = random.randint(0,num_items-1)

    sql = "SELECT priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,id,timestamp,note FROM "+table+" WHERE finisheddate IS NULL AND priority > 1 LIMIT 1 OFFSET %d"%n

    try:
        cursor.execute(sql)
    except:
        g.pr("In OnShowTickler and attempt to Select an item failed")
        return

    row = cursor.fetchone()

    class Item: pass
    item = Item()

    item.priority = int(row[0]) #int(row[0]) needs int because it seems to come back as a long from MySQL
    item.name = row[1]
    item.createdate = row[2]
    item.finisheddate = row[3]
    item.duedate = row[4]
    item.owners = [z for z in row[5:7] if z is not None] #if you carry around ['tom',None,None] you have an issue when you go write it
    item.id = row[8]
    item.timestamp = row[9]
    item.note = row[10]

    dlg = TicklerDialog(self, "", "Do something about this!!!", size=(550,350))
    TC = dlg.TC

    f = wxFont(14, wxSWISS, wxITALIC, wxBOLD, False)
    TC.SetDefaultStyle(wxTextAttr("BLUE",wxNullColour, f))
    TC.AppendText("%s..."%item.name)

    if item.priority == 3:
        TC.SetDefaultStyle(wxTextAttr("RED","YELLOW",f))
    TC.AppendText("%d\n\n"%item.priority)

    f = wxFont(8, wxSWISS, wxNORMAL, wxNORMAL)
    TC.SetDefaultStyle(wxTextAttr("BLACK","WHITE", f))
    TC.AppendText("owners: %s\n"%", ".join(item.owners))
    TC.AppendText("created on: %s\n"%item.createdate.Format('%m/%d/%y'))
    if item.duedate:
        ddate = item.duedate.Format('%m/%d/%y')
    else:
        ddate = "<no due date>"
    TC.AppendText("due on: %s\n\n"%ddate)

    note = item.note
    if not note:
        note = "<no note>"
    TC.AppendText("%s\n\n"%note)
    f = wxFont(10, wxSWISS, wxITALIC, wxBOLD)
    TC.SetDefaultStyle(wxTextAttr("BLACK",wxNullColour, f))
    TC.AppendText('follow_ups')
    TC.ShowPosition(0)   #did not do anything
    TC.SetInsertionPoint(0)
    result = dlg.ShowModal()
    dlg.Destroy()
    self.popupvisible = False     

    if result in (wxID_OK, wxID_APPLY):

        for L,Properties in enumerate(self.PropertyDicts):
            if Properties['table'] == table:
                break
        else:
            g.pr("Can't find %s"%table)
            return

        self.nb.SetSelection(L) #if the page changes it sends a EVT_NOTEBOOK_PAGE_CHANGED, which calls OnPageChange
        self.L = L
        self.FindNode(item)
        if result==wxID_APPLY:
            self.OnMailItem(item)

    elif result==wxID_FORWARD:
        self.OnShowTickler()

#@+node:ekr.20051104081502.384: *9* def OnActivateTickler
def OnActivateTickler(self, evt):
    self.tickler_active = not self.tickler_active
    self.toolmenu.Enable(idSHOWNEXT,self.tickler_active)


#@+node:ekr.20051104081502.385: *8* Email methods
#@+node:ekr.20051104081502.386: *9* OnMailItem
def OnMailItem(self, evt=None, item=None):
    if item is None:
        if self.curIdx == -1:
            return
        else:
            item = self.ItemLists[self.L][self.curIdx]

    dlg = MailDialog(self,"Mail a reminder", size=(450,500),
               recipients=item.owners,    
               subject=item.name,
               body=self.GetNote())          
    result = dlg.ShowModal()
    if result==wxID_OK:
        outlook= Dispatch("Outlook.Application")
        newMsg = outlook.CreateItem(olMailItem) #outlook.CreateItem(constants.olMailItem)
        newMsg.To = to = dlg.RTC.GetValue()
        newMsg.Subject = subject = dlg.STC.GetValue()
        newMsg.Body = body = dlg.BTC.GetValue()

        #newMsg.FlagStatus = constants.olFlagMarked

        newMsg.Display()

        dlg.Destroy()            
        #del outlook

        self.note.SetSelection(0,0)
        self.note.WriteText("**************************************************\n")
        self.note.WriteText("Email sent on %s\n"%mx.DateTime.today().Format("%m/%d/%y"))
        self.note.WriteText("To: %s\n"%to)
        self.note.WriteText("Subject: %s\n"%subject)
        self.note.WriteText("%s\n"%body)
        self.note.WriteText("**************************************************\n")

#@+node:ekr.20051104081502.387: *9* OnMailView
def OnMailView(self, evt=None):
    recipients = [self.PropertyDicts[self.L]['owner']]

    body = ""
    for i,item in enumerate(self.ItemLists[self.L]):
        body = body+"%d. %s (%d)\n"%(i+1, item.name, item.priority)

    subject = "Follow-ups " + mx.DateTime.today().Format("%m/%d/%y")

    dlg = MailDialog(self,"Follow-up List", size=(450,500),
               recipients=recipients,
               subject=subject,
               body=body)

    val = dlg.ShowModal()
    dlg.Destroy()
    if val==wxID_OK:
        outlook= Dispatch("Outlook.Application")
        newMsg = outlook.CreateItem(olMailItem) #outlook.CreateItem(constants.olMailItem)
        newMsg.To = dlg.RTC.GetValue()
        newMsg.Subject = dlg.STC.GetValue()
        newMsg.Body = dlg.BTC.GetValue()

        newMsg.FlagStatus = olFlagMarked #constants.olFlagMarked
        newMsg.Categories = "Follow-up"

        newMsg.Display()

        #del outlook

#@+node:ekr.20051104081502.388: *8* Cut/Copy/Paste methods
#@+node:ekr.20051104081502.389: *9* OnCopyItems
def OnCopyItems(self, event=None, cut=False):
    if self.curIdx == -1:
        return

    L = self.L
    IList = self.ItemLists[L]
    LCtrl = self.ListCtrls[L]

    << Find Highlighted Items >>

    self.SetStatusText("%d items copied"%len(copyitems))
    if cut:
        self.OnDeleteItems()

#@+node:ekr.20051104081502.390: *10* << Find Highlighted Items >>
copyitems = []
i = -1
while 1:
    i = LCtrl.GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)
    if i==-1:
        break
    item = IList[i]
    item.notes = self.GetNote(L,item) #handles the database situation
    copyitems.append(item)

self.copyitems = copyitems
#@+node:ekr.20051104081502.391: *9* OnPasteItems
def OnPasteItems(self, evt=None, L=None): #noselect 051603
    #used by OnMoveToList, OnMoveToSpecificList and called directly
    if not self.copyitems:
        g.pr("Nothing was selected to be copied")
        return

    if L is None: #this is not needed by OnMoveTo or OnDragToTab but is for a straight call
        L = self.L

    Properties = self.PropertyDicts[L]
    LCtrl = self.ListCtrls[L]
    IList = self.ItemLists[L]

    items = self.copyitems
    numitems = len(items)

    for item in items:

        z = item.owners+[None,None,None]

        id = self.GetUID() #we do give it a new id
        host = Properties['host']
        cursor = self.Cursors[host]
        table = Properties['table']

        createdate = mx.DateTime.now() #need this or else it won't be seen as a new item when synching; would be seen as updated
        command = "INSERT INTO "+table+" (priority,name,createdate,finisheddate,duedate,note,owner1,owner2,owner3,id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
        cursor.execute(command,(item.priority,item.name,createdate,item.finisheddate,item.duedate,item.notes,z[0],z[1],z[2],id))

        timestamp = self.TimeStamper(host, cursor, table, id)

        #creating a new item breaks the connection between item.x and new_item.x
        class Item: pass
        new_item = Item()
        new_item.id = id
        new_item.priority = item.priority
        new_item.owners = item.owners
        new_item.name = item.name
        new_item.timestamp = timestamp
        new_item.duedate =item.duedate
        new_item.finisheddate = item.finisheddate
        new_item.createdate = createdate
        IList.insert(0,new_item)

    self.DisplayList(IList,L)

    #If we didn't come from OnMoveToList or OnMoveToSpecificList where L != self.L
    if L==self.L:
        for i in range(numitems):
            LCtrl.SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
        self.curIdx = numitems-1



#@+node:ekr.20051104081502.392: *9* OnDeleteItems
def OnDeleteItems(self, event=None):
    """Called directly and by OnCopyItems (cut = true)
    """
    if self.curIdx == -1: #not absolutely necessary but gets you out quickly
        return

    L = self.L
    LCtrl = self.ListCtrls[L]
    IList = self.ItemLists[L]
    Properties = self.PropertyDicts[L]

    i = -1
    while 1:
        i = LCtrl.GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)
        if i==-1:
            break
        item = IList.pop(i)
        LCtrl.DeleteItem(i)

        host = Properties['host']
        cursor = self.Cursors[host]
        table = Properties['table']

        cursor.execute("DELETE from "+table+" WHERE id = %s", (item.id,))

        #Track Deletes for Syncing ############################################
        if table in SYNC_TABLES:
            if host.split(':')[1] == 'sqlite':
                timestamp = mx.DateTime.now()
                cursor.execute("INSERT INTO sync (id,action,table_name,name,timestamp) VALUES (%s,%s,%s,%s,%s)",(item.id,'d',table,item.name,timestamp))
            else:
                cursor.execute("INSERT INTO sync (id,action,table_name,user,name) VALUES (%s,%s,%s,%s,%s)",(item.id,'d',table,USER,item.name))
        #########################################################################
        i-=1

    self.name.Clear()
    self.owners.Clear()
    self.note.Clear()
    #note that Clearing does cause self.modified -->{'name':1}
    self.modified = {}
    self.curIdx = -1

#@+node:ekr.20051104081502.393: *8* MouseDown methods
#@+node:ekr.20051104081502.394: *9* OnLeftDown (Action depends on x coordinate)
def OnLeftDown(self, evt):
    g.pr("Here")
    if self.modified:
        #if inplace editor is open and you click anywhere (same or different row from current row) but in the editor itself then just to close editor
        flag = self.modified.has_key('inplace')
        self.OnUpdate()
        if flag:
            evt.Skip() #without Skip, EVT_LIST_ITEM_SELECTED is not generated if you click in a new row
            return

    x,y = evt.GetPosition()
    LCtrl = self.ListCtrls[self.L]

    #Using HitTest to obtain row clicked on because there was a noticable delay in the generation of an
    #EVT_LIST_ITEM_SELECTED event when you click on the already selected row
    idx,flags = LCtrl.HitTest((x,y))

    #if you are below rows of items then idx = -1 which could match self.curIdx = -1
    if idx == -1:
        return

    # only if you click on the currently selected row do the following events occur
    if idx == self.curIdx:
        if x < 18:
            self.OnToggleFinished()
        elif x < 33:
            self.OnPriority()
        elif x < 33 + LCtrl.GetColumnWidth(1):
            self.OnDisplayInPlaceEditor()
        elif x < 33 + LCtrl.GetColumnWidth(1) + LCtrl.GetColumnWidth(2): 
            self.OnEditOwner()
        else:
            self.OnDueDate
    else:
        evt.Skip() #without Skip, EVT_LIST_ITEM_SELECTED is not generated if you click in a new row



#@+node:ekr.20051104081502.395: *9* OnRightDown (Display popup sendto menu)
def OnRightDown(self, evt):
    x,y = evt.GetPosition()

    sendtomenu = wxMenu()

    open_tables = []
    for page,Properties in enumerate(self.PropertyDicts):
        host,table = Properties['host'],Properties['table']
        open_tables.append((host,table))
        sendtomenu.Append(1+page,"%s (%s)"%(table,host))
        EVT_MENU(self, 1+page, lambda e,p=page: self.OnMoveToList(e,p))

    sendtomenu.Delete(self.L+1) # don't send it to the page you're already on
    sendtomenu.AppendSeparator()

    self.closed_tables = []
    for host,cursor in self.Cursors.items():

        location, rdbms = host.split(':')

        if rdbms == 'sqlite':
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
        elif rdbms == 'mysql':
            cursor.execute("SHOW tables")

        results = cursor.fetchall()

        page+=1
        for (table,) in results:
            if not ((host,table) in open_tables or table in ['user_sync','owners','sync']):
                self.closed_tables.append((host,table))
                sendtomenu.Append(1+page,"%s (%s)"%('*'+table,host))
                EVT_MENU(self, 1+page, lambda e,p=page: self.OnMoveToList(e,p))
                page+=1

    self.PopupMenu(sendtomenu,(x+125,y+40))
    sendtomenu.Destroy()

#@+node:ekr.20051104081502.396: *8* Move/Combine items methods
#@+node:ekr.20051104081502.397: *9* OnCombineItems
def OnCombineItems(self, evt):
    L = self.L
    idx = self.curIdx
    IList = self.ItemLists[L]
    LCtrl = self.ListCtrls[L]

    combine_list = []
    i = -1
    while 1:
        i = LCtrl.GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)
        if i==-1:
            break
        combine_list.append((IList[i].createdate,IList[i]))


    if len(combine_list) < 2:
        g.pr("Fewer than two items highlighted")
        return

    combine_list.sort()
    combine_list.reverse()

    dlg = wxMessageDialog(self,
                        "Combine the %d selected items?"%len(combine_list),
                        "Combine Items?",
                        wxICON_QUESTION|wxYES_NO)

    if dlg.ShowModal() == wxID_YES:
        Properties = self.PropertyDicts[L]
        host = Properties['host']
        cursor = self.Cursors[host]
        table = Properties['table']

        t_item = combine_list[0][1]
        merge_list = combine_list[1:]
        new_note = ""

        for date,item in merge_list:
            note = self.GetNote(item=item)
            date = date.Format("%m/%d/%y")
            new_note = "%s\n%s %s\n\n%s"%(new_note, date, item.name, note)

            cursor.execute("DELETE from "+table+" WHERE id = %s", (item.id,))
            #Track Deletes for Syncing ############################################
            if table in SYNC_TABLES:
                if host.split(':')[1] == 'sqlite':
                    timestamp = mx.DateTime.now()
                    cursor.execute("INSERT INTO sync (id,action,table_name,name,timestamp) VALUES (%s,%s,%s,%s,%s)",(item.id,'d',table,item.name,timestamp))
                else:
                    cursor.execute("INSERT INTO sync (id,action,table_name,user,name) VALUES (%s,%s,%s,%s,%s)",(item.id,'d',table,USER,item.name))
            #########################################################################

        t_note = self.GetNote(item=t_item)
        t_note = "%s\n%s"%(t_note,new_note)

        #What about combining owners?######################################

        cursor.execute("UPDATE "+table+" SET name = %s, note = %s WHERE id = %s", (t_item.name+"*",t_note,t_item.id))
        t_item.timestamp = self.TimeStamper(host, cursor, table, t_item.id)

        self.OnRefresh()
        LCtrl.SetItemState(0, 0, wxLIST_STATE_SELECTED)
        IList = self.ItemLists[L]
        id = t_item.id
        idx = -1
        for item in IList:
            idx+=1
            if id == item.id:
                break
        else:
            idx = -1 

        #should never be -1
        if idx != -1:	
            LCtrl.SetItemState(idx, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
            LCtrl.EnsureVisible(idx)
        self.curIdx = idx

    dlg.Destroy()

#@+node:ekr.20051104081502.398: *9* OnMoveToList
def OnMoveToList(self, evt=None, page=0):
    self.OnCopyItems(cut=True)
    pc = self.nb.GetPageCount()
    if page < pc:		
        self.OnPasteItems(L=page)
    else:
        host,table = self.closed_tables[page-pc]
        cursor = self.Cursors[host]# in ini self.Cursors[host]

        for item in self.copyitems:
            z = item.owners+[None,None,None]
            id = self.GetUID() #give it a new id

            #need this or else it won't be seen as a new item when syncing; would be seen as updated
            createdate = mx.DateTime.now() 
            command = "INSERT INTO "+table+" (priority,name,createdate,finisheddate,duedate,note,owner1,owner2,owner3,id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
            cursor.execute(command,(item.priority,item.name,createdate,item.finisheddate,item.duedate,item.notes,z[0],z[1],z[2],id))
            timestamp = self.TimeStamper(host, cursor, table, id)

    self.copyitems = []

#@+node:ekr.20051104081502.399: *9* OnMoveToSpecificList
def OnMoveToSpecificList(self, evt=None, table='follow_ups'):
    matches = {}
    for page,Properties in enumerate(self.PropertyDicts):
        host,tble = Properties['host'],Properties['table']
        if tble == table:
            rdbms = host.split(':')[1]
            matches[rdbms] = page

    self.OnCopyItems(cut=True)

    if matches:
        if matches.get('mysql'):	
            self.OnPasteItems(L=matches['mysql'])
        else:
            self.OnPasteItems(L=matches['sqlite'])
    else:
        cursor = self.Cursors[LOCAL_HOST]

        for item in self.copyitems:
            z = item.owners+[None,None,None]
            id = self.GetUID() #give it a new id

            #need this or else it won't be seen as a new item when syncing; would be seen as updated
            createdate = mx.DateTime.now() 
            command = "INSERT INTO "+table+" (priority,name,createdate,finisheddate,duedate,note,owner1,owner2,owner3,id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
            cursor.execute(command,(item.priority,item.name,createdate,item.finisheddate,item.duedate,item.notes,z[0],z[1],z[2],id))
            timestamp = self.TimeStamper(host, cursor, table, id)

    self.copyitems = []



#@+node:ekr.20051104081502.400: *8* Change/update items methods
#@+node:ekr.20051104081502.401: *9* OnToggleFinished
def OnToggleFinished(self, evt=None):
    L = self.L
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]
    idx = self.curIdx

    item = self.ItemLists[L][idx]
    LC_Item = LCtrl.GetItem(idx)

    if not item.finisheddate:
        item.finisheddate = mx.DateTime.today()
        LC_Item.SetImage(LCtrl.idx0)
    else:
        item.finisheddate = None
        LC_Item.SetImage(LCtrl.idx1)

    << draw item >>

    self.tb.EnableTool(30, True)

    host = Properties['host']	
    cursor = self.Cursors[host]
    table = Properties['table']

    cursor.execute("UPDATE "+table+" SET finisheddate = %s WHERE id = %s", (item.finisheddate, item.id))
    item.timestamp = self.TimeStamper(host, cursor, table, item.id)

    if Properties['LCdate'] == 'timestamp':
        LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.timestamp.Format("%m/%d %H:%M:%S"))
    elif Properties['LCdate'] == 'finisheddate':
        LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.finisheddate.Format('%m/%d/%y'))



#@+node:ekr.20051104081502.402: *10* << draw item >>
if item.finisheddate:
    #It appears that SetTextColour resets font weight to Normal but this makes no sense
    #This means that all finished items have Normal weight whether they are priority 3,2 or 1
    #May actually be that GetItem() and then SetItem() sets the weight to Normal no matter what it was originally
    LC_Item.SetTextColour(wxLIGHT_GREY)

elif item.priority==1:
    #see note above about SetTextColour apparently resetting weight
    LC_Item.SetTextColour(wxBLACK)

elif item.priority==2:
    #LC_Item.SetTextColour(wxBLACK) -- this line should be necessary but it does not appear to be
    # ? font is black so ? if have to reset it
    f = self.LC_font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) # resetting font weight

else:
    LC_Item.SetTextColour(wxRED) #appears to be the only way to set color - can't through font
    f = self.LC_font #LCtrl.font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) # resetting font weight

LCtrl.SetItem(LC_Item)
#@+node:ekr.20051104081502.403: *9* OnPriority
def OnPriority(self, event=None, input=None):
    L = self.L
    idx = self.curIdx
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]
    item = self.ItemLists[L][idx]

    if input:
        item.priority=input

    else:
        if item.priority < 3:
            item.priority+= 1
        else:
            item.priority=1

    LC_Item = LCtrl.GetItem(idx)

    << draw item >>

    text = str(item.priority)        
    LCtrl.SetStringItem(idx, 0, text)

    host = Properties['host']
    cursor = self.Cursors[host]
    table = Properties['table']

    cursor.execute("UPDATE "+table+" SET priority = %s WHERE id = %s", (item.priority,item.id))
    item.timestamp = self.TimeStamper(host, cursor, table, item.id)

    if Properties['LCdate'] == 'timestamp':
        LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.timestamp.Format('%m/%d %H:%M:%S'))

    wxCallAfter(LCtrl.SetFocus)

#@+node:ekr.20051104081502.404: *10* << draw item >>
if item.finisheddate:
    #It appears that SetTextColour resets font weight to Normal but this makes no sense
    #This means that all finished items have Normal weight whether they are priority 3,2 or 1
    #May actually be that GetItem() and then SetItem() sets the weight to Normal no matter what it was originally
    LC_Item.SetTextColour(wxLIGHT_GREY)

elif item.priority==1:
    #see note above about SetTextColour apparently resetting weight
    LC_Item.SetTextColour(wxBLACK)

elif item.priority==2:
    #LC_Item.SetTextColour(wxBLACK) -- this line should be necessary but it does not appear to be
    # ? font is black so ? if have to reset it
    f = self.LC_font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) # resetting font weight

else:
    LC_Item.SetTextColour(wxRED) #appears to be the only way to set color - can't through font
    f = self.LC_font #LCtrl.font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) # resetting font weight

LCtrl.SetItem(LC_Item)
#@+node:ekr.20051104081502.405: *9* Inplace Edit Methods
#@+node:ekr.20051104081502.406: *10* OnDisplayInPlaceEditor
def OnDisplayInPlaceEditor(self,evt=None):
    L = self.L
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]
    idx = self.curIdx
    item = self.ItemLists[L][idx]

    host = Properties['host']
    cursor = self.Cursors[host]
    table = Properties['table']

    #if self.Conflict(host, cursor, table, item): return #works -- may be overkill so i've commented it out

    TCid = wxNewId()
    y = LCtrl.GetItemPosition(idx)[1] 
    length = LCtrl.GetColumnWidth(1)

    editor = wxTextCtrl(self, TCid, pos = (167,y+28), size = (length,23), style=wxTE_PROCESS_ENTER)
    editor.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxNORMAL))
    editor.SetBackgroundColour(wxColour(red=255,green=255,blue=175)) #Yellow
    editor.AppendText(item.name)
    editor.Show(True)
    editor.Raise()
    editor.SetSelection(-1,-1)
    editor.SetFocus()	

    EVT_TEXT_ENTER(self, TCid, self.OnCloseInPlaceEditor)		

    self.in_place_editor = editor
    self.modified['inplace'] = 1	





#@+node:ekr.20051104081502.407: *10* OnCloseInPlaceEditor
def OnCloseInPlaceEditor(self,evt=None):
    L = self.L
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]
    idx = self.curIdx
    item = self.ItemLists[L][idx]

    host = Properties['host']
    cursor = self.Cursors[host]
    table = Properties['table']
    LCdate = Properties['LCdate']

    #if self.Conflict(host, cursor, table, item)...

    text = self.in_place_editor.GetValue().strip()[:150]
    item.name = text
    LCtrl.SetStringItem(idx, self.attr2col_num['name'], text)
    self.in_place_editor.Destroy()

    cursor.execute("UPDATE "+table+" SET name = %s WHERE id = %s", (text, item.id))
    item.timestamp = self.TimeStamper(host, cursor, table, item.id)

    if Properties['LCdate'] == 'timestamp':
        LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.timestamp.Format('%m/%d %H:%M:%S'))

    self.name.Clear()
    self.name.AppendText(text) #this will cause self.modified['name'] = 1, which is dealt with below

    #using default in case for some reason self.modified does not have the keys
    self.modified.pop('inplace', None)
    self.modified.pop('name', None)

    wxCallAfter(LCtrl.SetFocus) #sets focus on LCtrl and current selection to be highlighted



#@+node:ekr.20051104081502.408: *9* OnDueDate
def OnDueDate(self, evt=None):
    idx = self.curIdx
    if idx == -1:
        return
    L = self.L
    Properties = self.PropertyDicts[L]
    item = self.ItemLists[L][idx]
    LCtrl = self.ListCtrls[L]

    if item.duedate:
        date = wxDateTime()
        date.SetTimeT(item.duedate) #I am surprised it takes a mx.DateTime object; supposed to need ticks
    else:
        date = 0
    dlg = CalendarDialog(parent=self,
                 title="Select a date",
                 size=(400,400),
                 style=wxCAPTION,
                 date = date)
    if dlg.ShowModal()==wxID_OK:
        date = dlg.cal.GetDate() # this is some date object
        #date = date.GetTicks()
        item.duedate = mx.DateTime.DateFromTicks(date.GetTicks())

        host = Properties['host']
        cursor = self.Cursors[host]
        table = Properties['table']

        cursor.execute("UPDATE "+table+" SET duedate = %s WHERE id = %s", (item.duedate,item.id))
        item.timestamp = self.TimeStamper(host, cursor, table, item.id)
        if Properties['LCdate'] == 'timestamp':
            LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.timestamp.Format("%m/%d %H:%M:%S"))
        elif Properties['LCdate'] == 'duedate':
            LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.duedate.Format('%m/%d/%y'))
    dlg.cal.Destroy()
    dlg.Destroy()

#@+node:ekr.20051104081502.409: *9* OnEditOwner
def OnEditOwner(self, evt=None): #, new=False) removed Aug. 31 for simplicity
    idx = self.curIdx
    if idx == -1:
        return
    L = self.L
    Properties = self.PropertyDicts[L]
    LCtrl = self.ListCtrls[L]
    item = self.ItemLists[L][idx]
    if not self.ModifierDialog:
        g.pr("self.ModifierDialog is still being constructed")
        return
    #need to clear the current selections or you'll just be making more and more selections
    self.ModifierDialog.SelectCurrent(item.owners)
    self.ModifierDialog.tc.Clear()
    self.ModifierDialog.CenterOnParent()

    val = self.ModifierDialog.ShowModal()

    if val == wxID_OK:
        item.owners, new_names = self.ModifierDialog.GetUserInput()

        << Common Owner Code >>

        for owner in item.owners:
            if self.OwnerLBoxes[L].FindString(owner) == -1:
                self.OwnerLBoxes[L].Append(owner)

        for owner in new_names:
            self.ModifierDialog.lb.Append(owner)

        host = Properties['host']
        cursor = self.Cursors[host]
        table = Properties['table']

        cursor.execute("UPDATE "+table+" SET owner1 = %s, owner2 = %s, owner3 = %s WHERE id = %s", (z[0],z[1],z[2],item.id))
        item.timestamp = self.TimeStamper(host, cursor, table, item.id)
        if Properties['LCdate'] == 'timestamp':
            LCtrl.SetStringItem(idx, self.attr2col_num['date'], item.timestamp.Format("%m/%d %H:%M:%S"))

        if 'owners' in self.modified:
            del self.modified['owners']

    wxCallAfter(LCtrl.SetFocus)

#@+node:ekr.20051104081502.410: *10* << Common Owner Code >>
owner_str = '; '.join(item.owners)
LCtrl.SetStringItem(idx, self.attr2col_num['owners'], owner_str)
self.owners.Clear()
self.owners.AppendText(owner_str)

z = item.owners+[None,None,None] #note that + creates a new list
#@+node:ekr.20051104081502.411: *9* OnUpdate
def OnUpdate(self, evt=None):
    if 'inplace' in self.modified:
        self.OnCloseInPlaceEditor()
        if not self.modified:
            return

    L = self.L
    LCtrl = self.ListCtrls[L]
    IList = self.ItemLists[L]
    Properties = self.PropertyDicts[L]
    OLBox = self.OwnerLBoxes[L]
    idx = self.curIdx

    # there is some chance that it is never true that idx == -1 and then this could be eliminated
    if idx != -1:
        item = IList[idx]
    else:
        msg = wxMessageDialog(self, "There is no selected item to update", "", wxICON_ERROR|wxOK)
        msg.ShowModal()
        msg.Destroy()
        self.modified = {}
        return

    host = Properties['host']
    cursor = self.Cursors[host]
    table = Properties['table']

    if 'name' in self.modified:
        item.name = self.name.GetValue().strip()[:150]
        LCtrl.SetStringItem(idx, self.attr2col_num['name'], item.name)
        cursor.execute("UPDATE "+table+" SET name =%s WHERE id = %s",(item.name,item.id))

    if 'note' in self.modified:
        note = self.note.GetValue() #a blank note starts out as None but after this it becomes '' -- ??
        cursor.execute("UPDATE "+table+" SET note =%s WHERE id = %s",(note,item.id))

    if 'owners' in self.modified:
        owner_str = self.owners.GetValue().strip()
        item.owners = []
        if owner_str:
            owner_list = [x.strip() for x in owner_str.split(';')]
            for owner in owner_list:
                owner = ", ".join([x.strip().title() for x in owner.split(',')])
                item.owners.append(owner)

        << Common Owner Code >>

        cursor.execute("UPDATE "+table+" SET owner1 = %s, owner2 = %s, owner3 = %s WHERE id = %s", (z[0],z[1],z[2],item.id))

        for owner in item.owners:
            if self.ModifierDialog.lb.FindString(owner) == -1:
                self.ModifierDialog.lb.Append(owner)
                OLBox.Append(owner)
            elif OLBox.FindString(owner) == -1:
                OLBox.Append(owner)		

    item.timestamp = self.TimeStamper(host, cursor, table, item.id)
    if Properties['LCdate'] == 'timestamp':
        LCtrl.SetStringItem(idx, 3, item.timestamp.Format("%m/%d %H:%M:%S"))

    self.modified = {}


#@+node:ekr.20051104081502.412: *10* << Common Owner Code >>
owner_str = '; '.join(item.owners)
LCtrl.SetStringItem(idx, self.attr2col_num['owners'], owner_str)
self.owners.Clear()
self.owners.AppendText(owner_str)

z = item.owners+[None,None,None] #note that + creates a new list
#@+node:ekr.20051104081502.413: *9* OnNewItem
def OnNewItem(self, evt=None):
    L=self.L
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]

    if self.curIdx != -1:
        LCtrl.SetItemState(self.curIdx, 0, wxLIST_STATE_SELECTED)

    << Clear data fields >>

    class Item: pass
    item = Item()
    item.name = '<New Item>'
    item.priority = 1
    item.owners = []
    item.createdate = mx.DateTime.now() #need this to be a timestamp and not just date for syncing
    item.duedate = item.finisheddate = None

    self.ItemLists[L].insert(0,item)

    host = Properties['host']
    cursor = self.Cursors[host]
    table = Properties['table']
    item.id = self.GetUID()

    cursor.execute("INSERT INTO "+table+" (priority,name,createdate,finisheddate,duedate,id) VALUES (%s,%s,%s,%s,%s,%s)",
                (item.priority,item.name,item.createdate,None,None,item.id))

    item.timestamp = self.TimeStamper(host, cursor, table, item.id)

    #tracking new item for syncing will happen in Edit Name

    LCtrl.InsertImageStringItem(0,"1", LCtrl.idx1)
    LCtrl.SetStringItem(0,1,item.name)

    if Properties['LCdate'] == 'timestamp':
        LCtrl.SetStringItem(0, self.attr2col_num['date'], item.timestamp.Format("%m/%d %H:%M:%S"))
    elif Properties['LCdate'] == 'createdate':
        LCtrl.SetStringItem(0, self.attr2col_num['date'], item.createdate.Format('%m/%d/%y'))

    self.curIdx = 0

    #if Display is being filtered we assume that is the owner of the new node
    owner = Properties['owner']	
    if owner and owner!='*ALL':
        self.ListCtrls[L].SetStringItem(0, self.attr2col_num['owners'], owner)
        item.owners = [owner]

        self.owners.Clear()
        self.owners.AppendText(owner)

        cursor.execute("UPDATE "+table+" SET owner1 = %s WHERE id = %s", (owner,item.id))
        item.timestamp = self.TimeStamper(host, cursor, table, item.id)  #not really necessary since just got a timestamp

    # decided that it was actually better not to ask for the owner on a new node	
    #else:
        #self.OnEditOwner()

    LCtrl.SetFocus() #needed for the in place editor to look right
    LCtrl.SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)

    self.OnDisplayInPlaceEditor() #(new=True)
#@+node:ekr.20051104081502.414: *10* << Clear data fields >>
self.name.Clear()
self.owners.Clear()
self.note.Clear()
#@+node:ekr.20051104081502.415: *9* Conflict (not in use)
@ Need to decide if we are going to have timestamp checking to be sure something hasn't changed
Note that there would not need to be timestamp checking on a new node
Also  there is no need to timestamp check on a local DB
The following code seems to work fine, however, I have just commented out the calls to it in NameEditor methods
@c
def Conflict(self, host, cursor, table, item):
    if host is 'sqlite':
        return False
    cursor.execute("Select timestamp from "+table+" WHERE id = %s", (item.id,))
    db_timestamp = cursor.fetchone()[0]
    if db_timestamp != item.timestamp:
        g.pr("There is a conflict and you should refresh display")
        return True
    else:
        return False
#@+node:ekr.20051104081502.416: *9* OnEditNote
def OnEditNote(self, evt=None):
    if self.modified:
        self.OnUpdate()

    idx = self.curIdx

    if idx == -1:
        return

    L = self.L

    #if self.editor:
        #machine = None
        #win32pdh.EnumObjects(None, machine, 0, 1) # resets Enum otherwise it seems to hold onto old data
        #object = "Process"
        #items, instances = win32pdh.EnumObjectItems(None,None,"Process", -1)
        #if 'TextPad' in instances:
            #g.pr("TextPad is running")
        #else:
            #self.editor = {}

    item = self.ItemLists[L][idx]
    file_name = re.sub('[\\/:*"<>|\?]','-',item.name) #make sure all chars are legal file name characters

    path = os.path.join(os.environ['TMP'],file_name[:50])+'.%s'%NOTE_EXT

    f = file(path,'w')
    f.write(self.GetNote())
    f.close()

    os.startfile(path)

    id = item.id
    for d in self.editor:
        if d['id'] == id:
            return

    ed = {}
    ed['time'] = os.path.getmtime(path)
    ed['host'] = self.PropertyDicts[L]['host']
    ed['table'] = self.PropertyDicts[L]['table']
    ed['path'] = path
    ed['id'] = item.id

    self.editor.append(ed)

    time.sleep(.1)
#@+node:ekr.20051104081502.417: *8* File menu methods
#@+node:ekr.20051104081502.418: *9* OnNewList
def OnNewList(self, event=None):
    if self.modified:
        self.OnUpdate()

    if OFFLINE_ONLY is True or REMOTE_HOST is None:
        hosts = [LOCAL_HOST]
    else:
        hosts = [LOCAL_HOST, REMOTE_HOST]

    dlg = wxSingleChoiceDialog(self, 'Databases', 'Choose a database:', hosts, wxCHOICEDLG_STYLE)
    val = dlg.ShowModal()
    dlg.Destroy()
    if val == wxID_OK:
        host = dlg.GetStringSelection()
    else:
        return

    cursor = self.GetCursor(host)
    if cursor is None:
        return

    dlg = wxTextEntryDialog(self, 'What is the name of the new table?', 'Create Table')
    val = dlg.ShowModal()
    dlg.Destroy()
    if val == wxID_OK:
        table = dlg.GetValue()
    else:
        return

    if not table:
        return

    location, rdbms = host.split(':')

    if rdbms == 'sqlite':
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
    else:
        cursor.execute("SHOW tables")

    if (table,) in cursor.fetchall():
        msg = wxMessageDialog(self,
                              "Table '%s' already exists"%table,
                              "Duplicate Table",
                              wxICON_ERROR|wxOK)
        msg.ShowModal()
        msg.Destroy()
        return

    dlg = wxMessageDialog(self,
          "Are you sure you want to create Table '%s'?"%table,
          "Create Table?",
          wxICON_QUESTION|wxYES_NO)

    if dlg.ShowModal() == wxID_YES:
        self.CreateTable(host,table)
        self.CreateNewNotebookPage(host,table)

        #self.AddListControl(tab_title) #add listcontrol displays the list

        #self.OnNewItem()

    dlg.Destroy()


#@+node:ekr.20051104081502.419: *9* OnFileList
def OnFileList(self, evt=None, path=None):
    if self.modified:
        self.OnUpdate()

    #if there is no event, we got here through the start up loading of lists
    if evt:
        fileNum = evt.GetId() - wxID_FILE1			
        path = self.filehistory.GetHistoryFile(fileNum)
        location, rdbms, table = path.split(':')
        host = '%s:%s'%(location, rdbms)
        # only need to check if table is open if this is not at startup
        if table in [p['table'] for p in self.PropertyDicts if p['host'] == host]:
            dlg = wxMessageDialog(self,"%s (%s) is already open!"%(table,host),"List Open",wxICON_ERROR|wxOK)
            dlg.ShowModal()
            dlg.Destroy()
            return

    else:
        location, rdbms, table = path.split(':')
        host = '%s:%s'%(location, rdbms)

    cursor = self.GetCursor(host)
    if cursor is None:
        return

    if rdbms == 'sqlite':
        sql = "SELECT name FROM sqlite_master WHERE name = '%s'"%table
    else:
        sql = "SHOW TABLES LIKE '%s'"%table

    cursor.execute(sql)
    if not cursor.fetchall():
        dlg = wxMessageDialog(self,
                    "Table '%s' at host '%s' does not appear to exist!"%(table,host),
                    "Table does not exist",
                    wxICON_ERROR|wxOK)
        dlg.ShowModal()
        dlg.Destroy()
        return

    self.CreateNewNotebookPage(host,table)

#@+node:ekr.20051104081502.420: *9* OnOpenList
def OnOpenList(self, evt=None):
    if self.modified:
        self.OnUpdate()

    tree = {}

    if OFFLINE_ONLY is True or REMOTE_HOST is None:
        hosts = [LOCAL_HOST]
    else:
        hosts = [LOCAL_HOST, REMOTE_HOST]

    for host in hosts:
        cursor = self.GetCursor(host)
        if cursor:
            if host.split(':')[1] == 'sqlite':
                sql = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
            else:
                sql = "SHOW TABLES" #sorted

            cursor.execute(sql)
            results = cursor.fetchall()

            #excluding already open tables + 'system' tables
            excluded_tables = [p['table'] for p in self.PropertyDicts if p['host'] == host]
            excluded_tables.extend(['user_sync','sync','owners'])

            tables = [t for (t,) in results if t not in excluded_tables]

            tree[host] = tables

    dlg = TreeDialog(self, "Open List", tree=tree)
    val = dlg.ShowModal()
    dlg.Destroy()
    if val == wxID_OK:
        sel = dlg.TreeCtrl.GetSelection()
        table = dlg.TreeCtrl.GetItemText(sel)
        sel = dlg.TreeCtrl.GetItemParent(sel)
        host = dlg.TreeCtrl.GetItemText(sel)

        if host in hosts: #takes care of highlighting root or hosts
            self.CreateNewNotebookPage(host,table)
#@+node:ekr.20051104081502.421: *9* OnDeleteList
def OnDeleteList(self, evt=None):
    #ini controls whether the menu item is enabled
    Properties = self.PropertyDicts[self.L]
    host = Properties['host']
    table = Properties['table']

    #if table is in SYNC_TABLES, should we make a point of that?
    dlg = wxMessageDialog(self,
                        "Are you sure that you want to delete table %s (%s)?\n(Please note that you cannot recover it once it is deleted!)"%(table,host),
                        "Delete Table...",
                        wxICON_EXCLAMATION|wxYES_NO|wxNO_DEFAULT)

    val = dlg.ShowModal()
    dlg.Destroy()
    if val == wxID_NO:
        return

    rdbms = host.split(':')[1]

    if rdbms == 'mysql':
        dlg = wxMessageDialog(self,
                        "Are you sure really really sure you want to delete table %s (%s)?\n(You really really cannot recover it once it is deleted)"%(table,host),
                        "Delete Table...",
                        wxICON_EXCLAMATION|wxYES_NO|wxNO_DEFAULT)

        val = dlg.ShowModal()
        dlg.Destroy()
        if val == wxID_NO:
            return

    cursor = self.Cursors[host]
    cursor.execute("DROP TABLE %s"%table)

    self.OnCloseList()

#@+node:ekr.20051104081502.422: *9* OnCloseList
def OnCloseList(self, evt=None):
    if self.modified:
        self.OnUpdate()

    L = self.L

    del self.ItemLists[L]
    del self.PropertyDicts[L]
    del self.ListCtrls[L]
    del self.OwnerLBoxes[L]

    self.nb.DeletePage(L)        

    ln = len(self.PropertyDicts)
    if ln:
        self.nb.SetSelection(0)
        self.L = 0
    else:
        self.L = -1




#@+node:ekr.20051104081502.423: *9* OnCloseAll
def OnCloseAll(self, evt=None):
    if self.modified:
        self.OnUpdate()

    while self.L != -1:
        self.OnCloseList()

    self.name.Clear()
    self.owners.Clear()
    self.note.Clear()
    #note that Clearing does set self.modified (eg {'name':1})
    self.modified = {}

#@+node:ekr.20051104081502.424: *9* OnSaveAsText
def OnSaveAsText(self, evt=None):
    if self.modified:
        self.OnUpdate()

    Properties = self.PropertyDicts[self.L]
    wildcard = "txt files (*.txt)|*.txt|All files (*.*)|*.*"
    #dlg = wxFileDialog(self, "Save file", "", Properties['table'], wildcard, wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR)

    body = ""
    for i,item in enumerate(self.ItemLists[self.L]):
        body = body+"%d. %s (%d)\n"%(i+1, item.name, item.priority)

    table = Properties['table']
    location, rdbms = Properties['host'].split(':')
    filename = re.sub('[\\/:*"<>|\?]','-','%s-%s-%s'%(location,rdbms,table)) 
    filename = filename[:50]+'.txt'

    path = os.path.join(DIRECTORY,filename)

    f = file(path,'w')
    f.write(body)
    f.close()

    os.startfile(path)

    self.SetStatusText("Saved file %s"%path)

#@+node:ekr.20051104081502.425: *9* OnArchive
def OnArchive(self, evt=None):
    if self.modified:
        self.OnUpdate()

    Properties = self.PropertyDicts[self.L]
    host = Properties['host']
    cursor = self.Cursors[host]
    table = Properties['table']
    rdbms = host.split(':')[1]

    table_archive = table+'_archive'

    #need to test for existence of table_archive
    if rdbms == 'sqlite':
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
    else:
        cursor.execute("SHOW tables")

    results = cursor.fetchall()

    if (table_archive,) not in results:
        dlg = wxMessageDialog(self,
                    "Do you want to create an archive for table %s (%s)"%(table,rdbms),
                    "Create an archive...",
                    wxICON_QUESTION|wxYES_NO)
        val = dlg.ShowModal()
        dlg.Destroy()
        if val==wxID_YES:
            self.CreateTable(host,table_archive)
        else:
            return

    label1 = "In table %s (%s) \narchive all finished items older than:"%(table,rdbms)
    label2 = "Archive all finished items"
    dlg = FinishedDialog(self, "Archive completed items", days=7, spin_label=label1, check_label=label2)

    val = dlg.ShowModal()
    dlg.Destroy() #dialogs and frames not destroyed right away to allow processing events, methods
    if val==wxID_CANCEL:
        return

    if dlg.check.GetValue():
        cursor.execute("SELECT id,priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,note FROM "+table+" WHERE finisheddate IS NOT NULL")
    else:
        days = dlg.text.GetValue()
        date = mx.DateTime.today() - int(days)
        cursor.execute("SELECT id,priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,note FROM "+table+" WHERE finisheddate < %s",(date,))

    results = cursor.fetchall()
    dlg = wxMessageDialog(self,
                        "Archiving will remove %d records from %s.\nDo you want to proceed?"%(len(results),table),
                        "Proceed to archive...",
                        wxICON_QUESTION|wxYES_NO)

    val = dlg.ShowModal()
    dlg.Destroy()
    if val == wxID_NO:
        return

    if table in SYNC_TABLES:
        if rdbms == 'sqlite':
            def track_deletes():
                timestamp = mx.DateTime.now()
                cursor.execute("INSERT INTO sync (id,action,table_name,name,timestamp) VALUES (%s,%s,%s,%s,%s)",(id,'d',table,name,timestamp))
        else:
            def track_deletes():
                cursor.execute("INSERT INTO sync (id,action,table_name,user,name) VALUES (%s,%s,%s,%s,%s)",(id,'d',table,USER,name))
    else:
        def track_deletes():
            pass	

    for row in results:
        # the next line is necessary because pysqlite returns a tuple-like object that is not a tuple
        r = tuple(row)
        id = r[0]
        name = r[2]
        cursor.execute("INSERT INTO "+table_archive+"  (id,priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,note) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",r)
        timestamp = self.TimeStamper(host, cursor, table_archive, id)
        cursor.execute("DELETE from "+table+" WHERE id = %s", (id,))
        track_deletes()

    self.OnRefresh()
    dlg = wxMessageDialog(self,
                        "Table %s had items older than %s days successfully archived"%(table,days),
                        "Archiving successful...",
                        wxICON_INFORMATION|wxOK)
    dlg.ShowModal()
#@+node:ekr.20051104081502.426: *9* OnWorkOffline
def OnWorkOffline(self, evt=None):
    global OFFLINE_ONLY
    OFFLINE_ONLY = not OFFLINE_ONLY
    if OFFLINE_ONLY:
        del self.Cursors[REMOTE_HOST]
    else:
        server = REMOTE_HOST.split(':')[0]
        try:
            socket.gethostbyname(server)
        except:
            dlg = wxMessageDialog(None, "Cannot connect to remote server! Will set to work offline.", "ListManager", style=wxOK|wxICON_EXCLAMATION|wxSTAY_ON_TOP)
            dlg.ShowModal()
            dlg.Destroy()
            OFFLINE_ONLY = True

    self.filemenu.Check(idOFFLINE,OFFLINE_ONLY)

#@+node:ekr.20051104081502.428: *8* Display methods
#@+node:ekr.20051104081502.429: *9* OnItemSelected
def OnItemSelected(self, evt=None):
    if self.modified:
        self.OnUpdate()

    if evt:
        idx = evt.GetIndex()
    elif self.curIdx != -1:
        idx = self.curIdx
    else: # really to catch self.curIdx = -1 (see OnDelete and OnRefresh)
        self.name.Clear() # could be moved out of if
        self.owners.Clear() # could be moved out of if
        self.note.Clear()
        #note that Clearing does set self.modified (eg {'name':1})
        self.modified = {}
        return

    L = self.L
    item = self.ItemLists[L][idx]

    self.name.Clear()
    self.name.AppendText(item.name) #SetValue(item.name) - if you use setvalue you don't get the font

    self.owners.Clear()
    self.owners.AppendText('; '.join(item.owners))

    note = self.GetNote(L,item)
    if note.find("<leo_file>") != -1:
        self.note.SetValue("Leo Outline")
        self.note.SetEditable(False)
    else:
        self.note.SetValue(note)
        self.note.SetEditable(True)

    self.ListCtrls[L].EnsureVisible(idx)
    self.curIdx = idx

    #writing to text widgets caused wxEVT_COMMAND_TEXT_UPDATED which is caught by EVT_TEXT, which updates self.modified
    self.modified={}

#@+node:ekr.20051104081502.430: *9* OnItemActivated
def OnItemActivated(self,evt):
    g.pr("On Activated")

#@+node:ekr.20051104081502.431: *9* OnShowAll
def OnShowAll(self, evt=None):
    L = self.L
    OLBox = self.OwnerLBoxes[L]

    Properties = self.PropertyDicts[L]
    Properties['showfinished'] = -1
    Properties['owner'] = '*ALL'

    OLBox.SetStringSelection('*ALL')

    self.OnRefresh()
#@+node:ekr.20051104081502.432: *9* OnRefresh
def OnRefresh(self, evt=None):
    #OnItemSelected should be able to handle no items so this could be very short
    L = self.L

    results = self.ReadFromDB()
    self.ItemLists[L] = self.CreateAndDisplayList(results)

    if self.ItemLists[L]:
        self.ListCtrls[L].SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
        self.curIdx = 0
    else:
        self.curIdx = -1		

    self.OnItemSelected()
#@+node:ekr.20051104081502.433: *9* OnFilterOwners
def OnFilterOwners(self, evt=None):
    if self.modified:
        self.OnUpdate()
    sel = self.OwnerLBoxes[self.L].GetStringSelection()

    if sel:
        self.PropertyDicts[self.L]['owner'] = sel
        self.OnRefresh()
#@+node:ekr.20051104081502.434: *9* OnColumnClick (to sort columns)
def OnColumnClick(self, evt):
    col_num = evt.GetColumn()
    L = self.L
    LCtrl = self.ListCtrls[L]
    Sort = self.PropertyDicts[L]['sort']
    attr2col = self.attr2col_num

    prev_sort_attr = Sort.get('attribute') #if this is the first sort Properties['sort'] is {}

    #following is a little bit ugly but gets the key from the value, which is col_num
    Sort['attribute'] = attr2col.keys()[attr2col.values().index(col_num)]

    if prev_sort_attr == Sort['attribute']:
        Sort['direction'] = not Sort['direction']
    else:
        Sort['direction'] = 0

    self.OnRefresh()

    LCtrl.ClearColumnImage(attr2col['priority'])
    LCtrl.ClearColumnImage(attr2col['date'])
    img_num = LCtrl.arrows[Sort['direction']]
    LCtrl.SetColumnImage(col_num, img_num)

#@+node:ekr.20051104081502.435: *9* OnShowFinished
def OnShowFinished(self,evt):
    Properties = self.PropertyDicts[self.L]
    label1 = "Enter the number of days to retain\ncompleted tasks in the display:"
    label2 = "Show all finished items"
    dlg = FinishedDialog(self, "Display of completed items", days=Properties['showfinished'], spin_label=label1, check_label=label2)
    if dlg.ShowModal()==wxID_OK:
        if dlg.check.GetValue():
            Properties['showfinished'] = -1
        else:
            days = dlg.text.GetValue()
            Properties['showfinished'] = int(days)			
        self.OnRefresh()
    dlg.Destroy()

#@+node:ekr.20051104081502.436: *9* OnColumnRightClick (popup to change date displayed)
def OnColumnRightClick(self, evt=None):
    col = evt.GetColumn()
    if col != self.attr2col_num['date']:
        return

    L = self.L
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]

    #x,y = evt.GetPosition()
    datemenu = wxMenu()

    for i,date in enumerate(['Create Date','Last Modified','Due Date','Completion Date']):
        datemenu.Append(200+i, date)
        EVT_MENU(self, 200+i, lambda e, i=i: self.ChangeDateDisplayed(e,i))

    x = LCtrl.GetColumnWidth(1)+ LCtrl.GetColumnWidth(2) + LCtrl.GetColumnWidth(3)
    self.PopupMenu(datemenu,(x,40))
    datemenu.Destroy()


#@+node:ekr.20051104081502.437: *9* OnDisplayDateCategory
def OnDisplayDateCategory(self, evt=None):
    dlg = wxSingleChoiceDialog(self, 'Date Display', 'Choose a date to display:',
                    ['Create Date','Last Modified','Due Date','Completion Date']
                    , wxOK|wxCANCEL)
    val = dlg.ShowModal()
    dlg.Destroy()

    if val == wxID_OK:
        idx = dlg.GetSelection()
        self.ChangeDateDisplayed(i=idx)

#@+node:ekr.20051104081502.438: *9* ChangeDateDisplayed
def ChangeDateDisplayed(self, evt=None, i=0):
    L = self.L
    LCtrl = self.ListCtrls[L]
    self.PropertyDicts[L]['LCdate'] = displaydate = ('createdate','timestamp','duedate','finisheddate')[i]	
    col_num = self.attr2col_num['date']
    col_info = LCtrl.GetColumn(col_num)
    col_info.SetText(self.date_titles[displaydate])
    LCtrl.SetColumn(col_num,col_info)
    self.DisplayList(self.ItemLists[L])
    #self.OnRefresh() #have gone back and forth but think that it should be self.DisplayList
#@+node:ekr.20051104081502.439: *9* DisplayList
def DisplayList(self, List, L=None):
    #OnPasteItems needs to be able to have an L that is not self.L
    if L is None:
        L = self.L
    LCtrl = self.ListCtrls[L]
    LCdate = self.PropertyDicts[L]['LCdate']
    if LCdate == 'timestamp':
        format = '%m/%d %H:%M:%S'
    else:
        format = '%m/%d/%y'
    LCtrl.DeleteAllItems()

    for x,item in enumerate(List):
        << draw item >>


#@+node:ekr.20051104081502.440: *10* << draw item >>
LCtrl.InsertImageStringItem(x, str(item.priority), LCtrl.idx1)
LCtrl.SetStringItem(x,1,item.name)
LCtrl.SetStringItem(x,2,'; '.join(item.owners))
date = item.__dict__[LCdate]
LCtrl.SetStringItem(x,3,date and date.Format(format) or "")

if item.finisheddate:
    LC_Item = LCtrl.GetItem(x)
    LC_Item.SetImage(LCtrl.idx0) #might just want generic number or greyed one two three
    LC_Item.SetTextColour(wxLIGHT_GREY)
    LCtrl.SetItem(LC_Item)

elif item.priority==2:
    LC_Item = LCtrl.GetItem(x)
    f = self.LC_font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) #resetting weight
    LCtrl.SetItem(LC_Item)

elif item.priority==3:
    LC_Item = LCtrl.GetItem(x)
    f = self.LC_font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) #return to normal
    LC_Item.SetTextColour(wxRED)
    LCtrl.SetItem(LC_Item)
#@+node:ekr.20051104081502.441: *8* Printing methods
#@+node:ekr.20051104081502.442: *9* OnPageSetup
def OnPageSetup(self, evt):
    #need to pass printdata to tableprint

    psdata = wxPageSetupDialogData()

    # if want to vary margins will need to save them as ivars and then set
    #psdata.SetMarginTopLeft((self.Left,self.Top))
    psdata.EnableMargins(False)
    psdata.SetPrintData(self.printdata) #gets Paper Orientation and PaperId info from printdata

    dlg = wxPageSetupDialog(self, psdata)
    if dlg.ShowModal() == wxID_OK:
        self.printdata = dlg.GetPageSetupData().GetPrintData()
        dlg.Destroy()
#@+node:ekr.20051104081502.443: *9* OnPrint
def OnPrint(self, evt=None, prev=False, showprtdlg=True): 		#???self.psdata = psdata
    IList = self.ItemLists[self.L]
    Properties = self.PropertyDicts[self.L]

    prt = PrintTable(self.printdata) #self.printdata is the wxPrintData object with Orientation Info

    font_name = prt.default_font_name
    prt.text_font = {'Name':font_name, 'Size':11, 'Colour':[0, 0, 0], 'Attr':[0, 0, 0]}
    prt.label_font = {'Name':font_name, 'Size':12, 'Colour':[0, 0, 0], 'Attr':[1, 0, 0]}
    prt.header_font = {'Name':font_name, 'Size':14, 'Colour':[0, 0, 0], 'Attr':[1, 0, 0]}

    prt.row_def_line_colour = wxLIGHT_GREY
    prt.column_def_line_colour = wxLIGHT_GREY

    prt.left_margin = 0.5

    data = []
    for row,item in enumerate(IList):	
        data.append([str(item.priority),
                    item.name,
                    item.duedate and item.duedate.Format('%m/%d/%y') or '',
                    '; '.join([x.split(',')[0] for x in item.owners])]) #just last names

        if item.finisheddate:
            prt.SetCellText(row, 0, wxLIGHT_GREY)
            prt.SetCellText(row, 1, wxLIGHT_GREY)
            prt.SetCellText(row, 2, wxLIGHT_GREY)
            prt.SetCellText(row, 3, wxLIGHT_GREY)

    prt.data = data
    prt.label = ['P','Item','Due','Owner']

    if self.printdata.GetOrientation() == wxPORTRAIT:
        prt.set_column = [.2, 5, .65, 1]
    else:
        prt.set_column = [.2, 7, .65, 1.5]

    title = "Table: %s   Owner: %s    "%(Properties['table'],Properties['owner'])
    prt.SetHeader(title, type='Date & Time', align=wxALIGN_LEFT, indent = 1.5)
    prt.SetFooter("Page No ", type ="Num")

    if prev:
        prt.Preview()
    else:
        prt.Print(prompt=showprtdlg)
#@+node:ekr.20051104081502.444: *8* Exiting methods
#@+node:ekr.20051104081502.445: *9* OnWindowExit
def OnWindowExit(self, evt):
    #this is called if you close the ListManager Window with the X
    if evt.CanVeto():
        self.OnExit()
    else:
        evt.Skip()
#@+node:ekr.20051104081502.446: *9* OnExit
def OnExit(self, event=None):   
    << save configuration file >>
    sys.stderr.dlg.Destroy() #destroys the error dialog; need to do this to shut down correctly
    if self.ModifierDialog: #only reason to check is if closed before ModifierDialog is constructed
        self.ModifierDialog.Destroy()
    self.Close(1)
#@+node:ekr.20051104081502.447: *10* <<save configuration file>>
cp.remove_section('Files')
cp.add_section("Files")

x,y = self.GetSizeTuple()

cp.set('Configuration','x', str(x))
cp.set('Configuration','y', str(y))

numfiles = self.filehistory.GetNoHistoryFiles()

for n in range(numfiles):
    cp.set("Files", "path%d"%n, self.filehistory.GetHistoryFile(n))

try:
    #you have to give ConfigParser a writable object
    cfile = file(config_file, 'w')
    cp.write(cfile)
    cfile.close()
except IOError:
    g.pr("The configuration file can't be written!")
    time.sleep(10) #so you can see that there was a problem
#@+node:ekr.20051104081502.448: *8* Find methods
#@+node:ekr.20051104081502.449: *9* OnFind
def OnFind(self, evt=None):
    self.FindDialog.Show(True)
    self.FindDialog.FindText.SetSelection(-1,-1)
    self.FindDialog.FindText.SetFocus()


#@+node:ekr.20051104081502.450: *9* FindString
def FindString(self, evt=None):
    L = self.L
    Properties = self.PropertyDicts[L]
    cursor = self.Cursors[Properties['host']]
    table = Properties['table']

    pat = self.FindDialog.FindText.GetValue()
    likepat = r"'%"+pat+r"%'"
    finished = self.FindDialog.SearchFinished.GetValue()
    notes = self.FindDialog.SearchNotes.GetValue()

    if finished:
        WHERE = "WHERE "
    else:
        WHERE = "WHERE finisheddate IS NULL AND "

    if notes:
        SELECT = "SELECT priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,id,timestamp,note FROM %s "%table
        WHERE = WHERE + "(name LIKE %s OR note LIKE %s) ORDER BY timestamp DESC"%(likepat,likepat)
    else:
        SELECT = "SELECT priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,id,timestamp FROM %s "%table
        WHERE = WHERE + "name LIKE %s ORDER BY timestamp DESC"%likepat

    sql = SELECT + WHERE			
    try:
        cursor.execute(sql)
    except:
        g.pr("Cannot read %s: %s"%(Properties['host'],table))
        return
    else:
        results = cursor.fetchall()

    case = self.FindDialog.MatchCase.GetValue()
    whole = self.FindDialog.MatchWhole.GetValue()

    if whole:
        pat = '\\b%s\\b'%pat

    if case:
        z = re.compile(pat)
    else:
        z =re.compile(pat, re.I)

    if notes:
        results = [x for x in results if re.search(z,x[1]) or re.search(z,x[10])]
    else:
        results = [x for x in results if re.search(z,x[1])]

    Properties['LCdate'] = 'timestamp'
    self.ItemLists[L]= IList = self.CreateAndDisplayList(results)

    LCtrl = self.ListCtrls[L]
    col_num = self.attr2col_num['date']
    col_info = LCtrl.GetColumn(col_num)
    col_info.SetText(self.date_titles['timestamp'])
    LCtrl.SetColumn(col_num,col_info)

    if IList:
        self.curIdx = 0
        LCtrl.SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
    else:		
        self.curIdx = -1

    self.OnItemSelected()

    Properties['sort'] = {'direction':0,'attribute':'date'}
    Properties['owner'] = '*ALL'

    owner_idx = self.OwnerLBoxes[L].GetSelection()
    if owner_idx != -1:
        self.OwnerLBoxes[L].SetSelection(owner_idx, 0) #get exception if index = -1

    self.SetStatusText("Found %d items"%len(IList))
#@+node:ekr.20051104081502.451: *9* FindNode
def FindNode(self, item, showfinished=True):
    L = self.L
    LCtrl = self.ListCtrls[L]
    Properties = self.PropertyDicts[L]

    Properties['owner'] = '*ALL'
    Properties['showfinished'] = showfinished

    self.ItemLists[L] = IList = self.CreateAndDisplayList(self.ReadFromDB())

    id = item.id
    idx = -1
    for item in IList:
        idx+=1
        if id == item.id:
            break
    else:
        idx = -1

    if idx != -1:	
        LCtrl.SetItemState(idx, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
        LCtrl.EnsureVisible(idx)
    self.curIdx = idx

#@+node:ekr.20051104081502.452: *8* Database-related methods
#@+node:ekr.20051104081502.453: *9* GetCursor
def GetCursor(self, host):
    cursor = self.Cursors.get(host)
    if cursor:
        return cursor

    location, rdbms = host.split(':')

    if rdbms == 'sqlite':
        db = os.path.join(DIRECTORY,location,DB)
        try:
            Con = sqlite.connect(db=db, autocommit=1)
            cursor = Con.cursor()
            self.sqlite_connections.append(Con)  #getting a weak reference error from PySQLite and this makes it go away
        except:
            dlg = wxMessageDialog(self,
                    "Could not connect to SQLite database at %s"%location,
                    "Connection problem!",
                    wxICON_ERROR|wxOK)
            dlg.ShowModal()
            dlg.Destroy()
            cursor = None

    elif not OFFLINE_ONLY:
        try:
            Con = MySQLdb.connect(host=location, user=USER, passwd=PW, db=DB)
            cursor = Con.cursor()
        except:
            dlg = wxMessageDialog(self,
                    "host = %s | user = %s | password = %s**** | db = %s - could not connect!"%(host,USER,PW[:3],DB),
                    "Connection problem",
                    wxICON_ERROR|wxOK)
            dlg.ShowModal()
            dlg.Destroy()
            cursor = None

    if cursor:
        self.Cursors[host] = cursor

    return cursor


#@+node:ekr.20051104081502.454: *9* GetNote
def GetNote(self, L=None, item=None):
    if L is None:
        L = self.L

    if item is None:
        if self.curIdx != -1:
            item = self.ItemLists[L][self.curIdx]
        else:
            return ''

    Properties = self.PropertyDicts[L]

    cursor = self.Cursors[Properties['host']]
    table = Properties['table']
    cursor.execute("SELECT note from "+table+" WHERE id = %s", (item.id,))

    ###### Debug -- this does happen where note brings back None 053003
    z = cursor.fetchone()
    if z is None:
        g.pr("In GetNote -> SELECT should not bring back None")
        g.pr("           -> item.id=",item.id)
        z = (None,)
    note = z[0]
    if note is None:
        note = ''
    return note

#@+node:ekr.20051104081502.455: *9* CreateTable
def CreateTable(self, host, table):
    cursor = self.Cursors[host]
    rdbms = host.split(':')[1]
    if rdbms == 'sqlite':
        sql = """CREATE TABLE '%s' ('id' varchar(36) PRIMARY KEY,
'priority' int(1),
'name' varchar(150),
'createdate' datetime,
'finisheddate' date,
'duedate' date,
'owner1' varchar(25),
'owner2' varchar(25),
'owner3' varchar(25),
'note' text,
'timestamp' timestamp(14))"""%table
    else:
        sql = """CREATE TABLE `%s` (`id` varchar(36) NOT NULL default '',
`priority` int(1) NOT NULL default '1',
`name` varchar(150) NOT NULL default '',
`createdate` datetime NOT NULL default '0000-00-00 00:00:00',
`finisheddate` date default '0000-00-00',
`duedate` date default '0000-00-00',
`owner1` varchar(25) default '',
`owner2` varchar(25) default '',
`owner3` varchar(25) default '',
`note` text,
`timestamp` timestamp(14) NOT NULL,PRIMARY KEY  (`id`)) TYPE=MyISAM"""%table

    cursor.execute(sql)
#@+node:ekr.20051104081502.456: *9* ReadFromDB (returns db results)
def ReadFromDB(self):
    L = self.L
    Properties = self.PropertyDicts[L]

    host = Properties['host']
    cursor = self.GetCursor(host)
    if cursor is None:
        return None

    table = Properties['table']

    owner = Properties['owner']
    if owner == '*ALL':
        WHERE = ""
    else:
        WHERE = 'WHERE (owner1 = "%s" OR owner2 = "%s" OR owner3 = "%s")'%(owner,owner,owner)

    #-1 show them all; 0 show none; integer show for that many days
    days = Properties['showfinished']	
    if days != -1:
        if days:
            date = mx.DateTime.now() - days
            t = "(finisheddate IS NULL OR finisheddate > '%s')"%date
        else:
            t = "finisheddate IS NULL"

        if WHERE:
            WHERE = "%s AND %s"%(WHERE,t)
        else:
            WHERE = " WHERE %s"%t

    Sort = Properties['sort']
    if Sort:
        sort_attr = Sort['attribute']
        if sort_attr == 'date':
            sort_attr = Properties['LCdate']
        elif sort_attr == 'owners':
            sort_attr = 'owner1'

        WHERE = WHERE + " ORDER BY " + sort_attr
        #if not direction: WHERE = WHERE + " DESC"   works because ASC is the default
        if not Sort['direction']:
            WHERE = WHERE + " DESC" 

    sql = "SELECT priority,name,createdate,finisheddate,duedate,owner1,owner2,owner3,id,timestamp FROM %s %s"%(table,WHERE)

    try:
        cursor.execute(sql)
    except:
        g.pr("Cannot read %s: %s"%(Properties['host'],table))
        return None #[]
    else:
        return cursor.fetchall()



#@+node:ekr.20051104081502.457: *9* CreateAndDisplayList (returns Item List)
def CreateAndDisplayList(self, results):
    LCtrl = self.ListCtrls[self.L]
    LCdate = self.PropertyDicts[self.L]['LCdate']
    if LCdate == 'timestamp':
        format = '%m/%d %H:%M:%S'
    else:
        format = '%m/%d/%y'
    itemlist = []

    LCtrl.DeleteAllItems()
    class Item: pass

    for x,row in enumerate(results):

        item = Item()
        << assign item attributes >>
        itemlist.append(item)

        << draw item >>

    return itemlist


#@+node:ekr.20051104081502.458: *10* << assign item attributes >>
item.priority = int(row[0]) #int(row[0]) needs int because it seems to come back as a long from MySQL
item.name = row[1]
item.createdate = row[2]
item.finisheddate = row[3]
item.duedate = row[4]
item.owners = [y for y in row[5:8] if y] #if you carry around ['tom',None,None] Note this is 5:8 not 5:7
item.id = row[8]
item.timestamp = row[9]

#@+node:ekr.20051104081502.459: *10* << draw item >>
LCtrl.InsertImageStringItem(x, str(item.priority), LCtrl.idx1)
LCtrl.SetStringItem(x,1,item.name)
LCtrl.SetStringItem(x,2,'; '.join(item.owners))
date = item.__dict__[LCdate]
LCtrl.SetStringItem(x,3,date and date.Format(format) or "")

if item.finisheddate:
    LC_Item = LCtrl.GetItem(x)
    LC_Item.SetImage(LCtrl.idx0) #might just want generic number or greyed one two three
    LC_Item.SetTextColour(wxLIGHT_GREY)
    LCtrl.SetItem(LC_Item)

elif item.priority==2:
    LC_Item = LCtrl.GetItem(x)
    f = self.LC_font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) #resetting weight
    LCtrl.SetItem(LC_Item)

elif item.priority==3:
    LC_Item = LCtrl.GetItem(x)
    f = self.LC_font
    f.SetWeight(wxBOLD)
    LC_Item.SetFont(f)
    f.SetWeight(wxNORMAL) #return to normal
    LC_Item.SetTextColour(wxRED)
    LCtrl.SetItem(LC_Item)
#@+node:ekr.20051104081502.460: *9* OnSync
def OnSync(self, evt=None):
    if self.modified:
        self.OnUpdate()
    #Note that the results of an sqlite query are an instance that you need to turn into a tuple or MySQL gets unhappy

    if OFFLINE_ONLY:
        dlg = wxMessageDialog(self, "You need to be online to synchronize!", style = wxOK|wxICON_ERROR)
        dlg.ShowModal()
        dlg.Destroy()
        return

    dlg = wxMessageDialog(self,"Synchronize Table(s): "+" and ".join(SYNC_TABLES),"Synchronize...",wxICON_QUESTION|wxYES_NO)
    val = dlg.ShowModal()
    dlg.Destroy()
    if val == wxID_NO:
        return

    if REMOTE_HOST is None:
        g.pr("There doesn't appear to be a Remote Server")
        return

    if LOCAL_HOST is None:
        g.pr("There doesn't appear to be a Local Server")
        return

    g.pr("LOCAL_HOST=",LOCAL_HOST)
    g.pr("REMOTE_HOST=",REMOTE_HOST)

    r_cursor = self.GetCursor(REMOTE_HOST)
    if r_cursor is None:
        g.pr("Couldn't get a cursor for %s"%REMOTE_HOST)
        return

    l_cursor = self.GetCursor(LOCAL_HOST)
    if l_cursor is None:
        g.pr("Couldn't get a cursor for %s"%LOCAL_HOST)
        return

    # moving the sync time back a second to make sure that we don't lose track of any nodes
    #that are being updated or inserted at the same time as we are syncing
    r_cursor.execute("SELECT NOW()")
    l_now = mx.DateTime.now()-mx.DateTime.oneSecond
    r_now = r_cursor.fetchone()[0]-mx.DateTime.oneSecond
    #because of some inconsistent rounding appears necessary to make sure the sqlite timestamp is less than l_now
    #having seen same issue for mysql but for consistency (and because sqlite could also be "server" rdbms
    l_ts = l_now - mx.DateTime.DateTimeDelta(0,0,0,0.02)
    r_ts = r_now - mx.DateTime.DateTimeDelta(0,0,0,0.02)
    g.pr("l_now=",l_now, "l_ts =",l_ts)
    g.pr("r_now=",r_now, "r_ts=",r_ts)

    r_cursor.execute("SELECT MAX(last_sync) FROM user_sync WHERE user = %s", (USER,))
    r_last_sync = r_cursor.fetchone()[0]
    g.pr("last sync (remote time) =",r_last_sync)

    l_cursor.execute("SELECT MAX(last_sync) FROM user_sync")
    l_last_sync = l_cursor.fetchone()[0] #note MAX returns a string with sqlite so we turn it make into DateTime
    l_last_sync = mx.DateTime.DateTimeFrom(l_last_sync)
    g.pr("last sync (local time) =",l_last_sync)

    for table in SYNC_TABLES:
        # Need to pick up changes for both so syncing one doesn't add new things and screw up the second sync
        g.pr("Checking "+table+" on the Remote Server; changes (excluding deletes) are:")
        r_cursor.execute("SELECT id,createdate from "+table+" WHERE timestamp > %s AND timestamp <= %s",(r_last_sync,r_now)) 
        r_results = r_cursor.fetchall()
        g.pr("Server changes (excluding deletes)")
        g.pr(r_results)

        g.pr("Checking "+table+" on Local; changes (excluding deletes) are:")
        l_cursor.execute("SELECT id,createdate from "+table+" WHERE timestamp > %s AND timestamp <= %s",(l_last_sync,l_now))
        l_results = l_cursor.fetchall()
        g.pr("Local changes (excluding deletes)")
        g.pr(l_results)

        for id, createdate in r_results:
            r_cursor.execute("SELECT priority,name,owner1,owner2,owner3,createdate,finisheddate,duedate,note,id FROM "+table+" WHERE ID = %s",(id,))
            row = r_cursor.fetchone()
            if row:
                if createdate > r_last_sync:
                    l_cursor.execute("INSERT INTO "+table+" (priority,name,owner1,owner2,owner3,createdate,finisheddate,duedate,note,id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", row) #*row also works
                    g.pr("Created %s in %s on Local"%(id,table))
                else:
                    l_cursor.execute("UPDATE "+table+" SET priority = %s, name =%s, owner1 = %s, owner2 = %s, owner3 = %s, createdate = %s, finisheddate = %s, duedate = %s, note = %s WHERE id = %s", row)
                    g.pr("Updated %s in %s on Local"%(id,table))
                # for reasons I don't understand l_now here is a 1/100 ahead of l_now when inserted into user_sync
                l_cursor.execute("UPDATE "+table+" SET timestamp = %s WHERE id = %s", (l_ts,id))

        for id, createdate in l_results:
            l_cursor.execute("SELECT priority,name,owner1,owner2,owner3,createdate,finisheddate,duedate,note,id FROM "+table+" WHERE ID = %s",(id,))
            row = l_cursor.fetchone()
            if row:
                row = tuple(row)
                #above needed because sqlite returns an enhanced tuple-like object that is not a tuple
                if createdate > l_last_sync:
                    r_cursor.execute("INSERT INTO "+table+" (priority,name,owner1,owner2,owner3,createdate,finisheddate,duedate,note,id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", row)
                    g.pr("Created %s in %s on Server"%(id,table))
                else:
                    r_cursor.execute("UPDATE "+table+" SET priority = %s, name =%s, owner1 = %s, owner2 = %s, owner3 = %s, createdate = %s, finisheddate = %s, duedate = %s, note = %s WHERE id = %s", row)
                    g.pr("Updated %s in %s on Server"%(id,table))
                r_cursor.execute("UPDATE "+table+" SET timestamp = %s WHERE id = %s", (r_ts,id))

    #Handle the deletes; Note if at some point only 'd's are being written won't have to check for 'd'
    r_cursor.execute("SELECT id,table_name FROM sync WHERE timestamp > %s AND timestamp <= %s AND action = 'd'",(r_last_sync,r_now))
    r_results = r_cursor.fetchall()

    l_cursor.execute("SELECT id,table_name FROM sync WHERE timestamp > %s AND timestamp <= %s AND action = 'd'",(l_last_sync,l_now))
    l_results = l_cursor.fetchall()

    for id,table in l_results:
        r_cursor.execute("DELETE from "+table+" WHERE id = %s", (id,))
        g.pr("Deleted %s from %s on Server (if it existed there)"%(id,table))

    for id,table in r_results:
        l_cursor.execute("DELETE from "+table+" WHERE id = %s", (id,))
        g.pr("Deleted %s from %s on Local (if it existed there)"%(id,table)	)
    #End of deletes code

    #update the user_sync database with the latest sync times
    l_cursor.execute("INSERT INTO user_sync (user,last_sync) VALUES (%s,%s)", (USER,l_now)) #don't really need USER for local
    r_cursor.execute("INSERT INTO user_sync (user,last_sync) VALUES (%s,%s)", (USER,r_now)) 

    g.pr("Synchronization completed")

#@+node:ekr.20051104081502.461: *9* TimeStamper
def TimeStamper(self, host, cursor, table, id):
    #note that you can insert a timestamp value into an mysql timestamp field
    if host.split(':')[1] == 'sqlite': #host -> location:rdbms
        timestamp = mx.DateTime.now()
        cursor.execute("UPDATE "+table+" SET timestamp = %s WHERE id = %s", (timestamp,id))
    else:
        cursor.execute("Select timestamp from "+table+" WHERE id = %s", (id,))
        timestamp = cursor.fetchone()[0]

    return timestamp
#@+node:ekr.20051104081502.462: *8* Evaluate methods
#@+node:ekr.20051104081502.463: *9* OnShowEvaluate
def OnShowEvaluate(self, evt=None):

    self.EvalDialog.Show(True)
    self.EvalDialog.EvalText.SetSelection(-1,-1)
    self.EvalDialog.EvalText.SetFocus()

#@+node:ekr.20051104081502.464: *9* OnEvaluate
def OnEvaluate(self, evt=None):
    expr = self.EvalDialog.EvalText.GetValue()
    g.pr("%s => "%expr,newline=False)
    g.pr(eval(expr))

#@+node:ekr.20051104081502.465: *8* Help menu methods
#@+node:ekr.20051104081502.466: *9* OnShowAbout
def OnShowAbout(self, evt=None):
    from about import AboutBox
    dlg = AboutBox(self, app_version = VERSION)
    dlg.ShowModal()
    dlg.Destroy()

#@+node:ekr.20051104081502.467: *9* OnShowHelp
def OnShowHelp(self, evt=None):
    os.startfile('ListManager.chm')

#@+node:ekr.20051104081502.468: *8* GetUID
def GetUID(self):
    pyiid = CreateGuid()
    # the str(pyiid) looks like {....} and doing [1:-1] strips that off
    return str(pyiid)[1:-1]

#@+node:ekr.20051104081502.469: *8* OnIdle
def OnIdle(self, evt):	
    << Check for Transfers From Outlook >>
    << Check if Edited File has Changed >>

#@+node:ekr.20051104081502.471: *9* << Check for Transfers From Outlook >>
if OUTLOOK:
    input,output,exc = select.select([self.sock],[],[],0)
    if input:
        client,addr = self.sock.accept() # Get a connection
        rec = client.recv(8192)
        d = pickle.loads(rec)

        class Item: pass

        item = Item()
        item.id = self.GetUID()
        item.priority = 1
        item.createdate = mx.DateTime.now()
        item.duedate = item.finisheddate = None

        #outlook strings are unicode; ascii encode makes sure no chars above 127
        name = d['Subject'].encode('ascii','replace') 
        item.name = name[:150]

        owner = d['SenderName'].encode('ascii','replace') #encode takes unicode to standard strings
        owner = owner[:25]
        item.owners = [owner]

        note = d['CreationTime'] + '\n' + d['Body'].encode('ascii','replace')
        #foldername = d['Parent.Name']

        #location, rdbms, table = MAIL_LIST_PATH.split(':')
        #host = '%s:%s'%(location,rdbms)
        host, table = re.split('(.*?:.*?):', MAIL_LIST_PATH)[1:3] #really just for fun

        cursor = self.Cursors[host]

        cursor.execute("INSERT INTO "+table+" (priority,name,createdate,finisheddate,duedate,owner1,note,id) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
            (item.priority, name, item.createdate, item.finisheddate, item.duedate, owner, note, item.id))

        item.timestamp = self.TimeStamper(host, cursor, table, item.id)

        #check to see if table is open
        for L,Properties in enumerate(self.PropertyDicts):
            if Properties['table'] == table and Properties['host'] == host:
                break
        else:
            g.pr("Table not open but wrote to database anyway") #Needs to be a dialog box
            return

        # could have started to edit something and never finished it
        if self.modified:
            self.OnUpdate()

        if self.L != L:
            self.nb.SetSelection(L) # Note that this does not call OnPageChange if the page doesn't change

        LCtrl = self.ListCtrls[L]

        if self.curIdx != -1:
            LCtrl.SetItemState(self.curIdx, 0, wxLIST_STATE_SELECTED)

        self.ItemLists[L].insert(0,item)    
        LCtrl.InsertImageStringItem(0,"1", LCtrl.idx1)
        LCtrl.SetStringItem(0,self.attr2col_num['name'],name)
        LCtrl.SetStringItem(0, self.attr2col_num['owners'], owner)

        if Properties['LCdate'] == 'timestamp':
            LCtrl.SetStringItem(0, self.attr2col_num['date'], item.timestamp.Format("%m/%d %H:%M:%S"))
        elif Properties['LCdate'] == 'createdate':
            LCtrl.SetStringItem(0, self.attr2col_num['date'], item.createdate.Format('%m/%d/%y'))

        LCtrl.SetItemState(0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED)
        self.curIdx = 0 

#@+node:ekr.20051104081502.473: *9* << Check if Edited File has Changed >>
for ed in self.editor:
    path = ed['path']
    t = os.path.getmtime(path)
    if t != ed['time']:
        f = file(path,'r')
        note = f.read()
        f.close()
        ed['time'] = t

        host = ed['host']
        cursor = self.Cursors[host]
        table = ed['table']
        id = ed['id']
        cursor.execute("UPDATE "+table+" SET note = %s WHERE id = %s", (note,id)) 
        # see @rst documentation note
        ts = self.TimeStamper(host, cursor, table, id)

        idx = self.curIdx
        L = self.L
        if idx != -1:
            item = self.ItemLists[L][idx]
            if item.id == id:
                self.note.SetValue(note)
                item.timestamp = ts

                if self.PropertyDicts[L]['LCdate'] == 'timestamp':
                    self.ListCtrls[L].SetStringItem(idx, self.attr2col_num['date'], item.timestamp.Format("%m/%d %H:%M:%S"))

                if 'note' in self.modified: #if necessary only if somehow note text didn't change
                    del self.modified['note']

#@+node:ekr.20051104081502.475: *7* class ListCtrl
class ListCtrl(wxListCtrl, wxListCtrlAutoWidthMixin):
    @others
#@+node:ekr.20051104081502.476: *8* __init__
def __init__(self, parent, ID, pos=wxDefaultPosition, size=wxDefaultSize, style=0):
    wxListCtrl.__init__(self, parent, ID, pos, size, style)
    wxListCtrlAutoWidthMixin.__init__(self)

    self.il = wxImageList(16,16)

    sm_up = self.il.Add(wxBitmap('bitmaps\\up_arrow.bmp')) #(images.getSmallUpArrowBitmap())
    sm_dn = self.il.Add(wxBitmap('bitmaps\\down_arrow.bmp'))
    self.arrows = (sm_up,sm_dn)

    self.idx1 = self.il.Add(wxBitmap('bitmaps\\box.bmp'))
    self.idx0 = self.il.Add(wxBitmap('bitmaps\\filledwhitebox.bmp'))    

    self.SetImageList(self.il, wxIMAGE_LIST_SMALL)

    EVT_LIST_COL_BEGIN_DRAG(self, self.GetId(), self.OnColBeginDrag)    

    self.SetUpColumns()

#@+node:ekr.20051104081502.477: *8* SetUpColumns
def SetUpColumns(self):
    #Need to to construct column heads for columns with sorting by hand to get sorting images on columns
    info = wxListItem()
    info.m_mask = wxLIST_MASK_TEXT | wxLIST_MASK_IMAGE | wxLIST_MASK_FORMAT
    info.m_image = -1

    #Oth column is priority which is sortable
    info.m_format = wxLIST_FORMAT_LEFT
    info.m_text = "P"
    self.InsertColumnInfo(0, info)
    self.SetColumnWidth(0, 35)

    self.InsertColumn(1, "Name")
    self.SetColumnWidth(1, 590)

    self.InsertColumn(2, "Owner")
    self.SetColumnWidth(2, 100)

    #3th column is create ate and same as with priority - needs to constructed by hand
    info.m_format = wxLIST_FORMAT_LEFT
    info.m_text = "Due Date"
    self.InsertColumnInfo(3, info)
    self.SetColumnWidth(3, 75)                

#@+node:ekr.20051104081502.478: *8* OnColBeginDrag
def OnColBeginDrag(self, evt):
    #if inplace editor then change its dimensions
    if evt.GetColumn() == 0:
        evt.Veto()
#@+node:ekr.20051104081502.479: *7* class MyApp
class MyApp(wxApp):
    @others
#@+node:ekr.20051104081502.480: *8* OnInit
def OnInit(self):
    global OFFLINE_ONLY, CANCEL
    wxInitAllImageHandlers()

    if STARTUP_DIALOG:
        startup = StartupDialog(None, 'List Manager')
        val = startup.ShowModal()
        startup.Destroy()
        if val == wxID_YES:
            OFFLINE_ONLY = True
        elif val == wxID_NO:
            OFFLINE_ONLY = False
        elif val == wxID_CANCEL:
            CANCEL = True
            return True

    if OFFLINE_ONLY is False:
        server = REMOTE_HOST.split(':')[0]
        try:
            socket.gethostbyname(server)
        except:
            dlg = wxMessageDialog(None, "Cannot connect to remote server! Only offline access is possible.", "ListManager", style=wxOK|wxICON_EXCLAMATION|wxSTAY_ON_TOP)
            dlg.ShowModal()
            dlg.Destroy()
            OFFLINE_ONLY = True

    frame = ListManager(None, -1, "List Manager", size = (X,Y))
    frame.Show(True)
    self.SetTopWindow(frame)
    CANCEL = False
    return True


#@+node:ekr.20051104081502.481: *7* class Logger
class Logger:
    def __init__(self):
        self.dlg = LoggerDialog(None, "", "Alerts and Exceptions", dir=DIRECTORY)
    def write(self, error_msg):
        if not self.dlg.IsShown():
            self.dlg.text.AppendText("\n%s\n"%time.asctime())
            self.dlg.Show(True)

        self.dlg.text.AppendText(error_msg)

#@+node:ekr.20051104081502.482: *7* run
def run():
    app = MyApp(0)
    if not CANCEL:
        sys.stderr = sys.stdout = Logger()
        app.MainLoop()

if __name__ == '__main__':
    run()
#@+node:ekr.20051104081502.483: *6* LMDialogs.py
@ @rst-options
code_mode = True
@c


@language python
from wxPython.wx import *
# the following two are needed for the calendar
from wxPython.calendar import *
from wxPython.utils import *
import os
@others
#@+node:ekr.20051104081502.484: *7* class PopDialog
class TicklerDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.485: *8* __init__
def __init__(self, parent, msg, caption, pos = wxDefaultPosition, size = wxDefaultSize):
    wxDialog.__init__(self, parent, -1, caption, pos, size, style=wxSTAY_ON_TOP | wxTHICK_FRAME | wxCAPTION)

    TC = wxTextCtrl(self, -1, msg, wxDefaultPosition,
                    (450,250),
                    wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2)

    sizer = wxBoxSizer(wxVERTICAL)
    box = wxBoxSizer(wxHORIZONTAL)        

    sizer.Add(TC, 1, wxALIGN_CENTRE|wxALL, 5)
    line = wxStaticLine(self, -1, size = (20,-1), style = wxLI_HORIZONTAL)

    sizer.Add(line, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP, 5)
    btn = wxButton(self, wxID_OK, "GO TO ITEM")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
    btn.SetDefault()

    btn = wxButton(self, wxID_FORWARD, "SHOW NEXT")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    btn = wxButton(self, wxID_APPLY, "MAIL")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)        

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    sizer.AddSizer(box, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5)
    self.SetSizer(sizer)
    self.SetAutoLayout(True)
    sizer.Fit(self)

    EVT_LEFT_DOWN(TC, self.OnLeftDown)
    EVT_BUTTON(self, wxID_FORWARD, self.OnForward)
    EVT_BUTTON(self, wxID_APPLY, self.OnMail)

    TC.SetCursor(wxStockCursor(wxCURSOR_ARROW))        

    self.TC = TC
#@+node:ekr.20051104081502.486: *8* OnLeftDown
def OnLeftDown(self, evt):
    self.EndModal(wxID_OK)
#@+node:ekr.20051104081502.487: *8* OnForward
def OnForward(self, evt):
    self.EndModal(wxID_FORWARD)
#@+node:ekr.20051104081502.488: *8* OnMail
def OnMail(self, evt):
    self.EndModal(wxID_APPLY)        
#@+node:ekr.20051104081502.489: *7* class StartupDialog
class StartupDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.490: *8* __init__
def __init__(self, parent, caption, pos=wxDefaultPosition, size=(300,115)):
    wxDialog.__init__(self, parent, -1, caption, pos, size, style=wxSTAY_ON_TOP|wxCAPTION)

    msg = "You can connect to the server using the network,\nor work offline, or cancel this logon."

    image = wxStaticBitmap(self, -1, wxBitmap('bitmaps\\wxpdemo.bmp'), (-1,-1), size=(32,32)) #sizer determines position
    text = wxStaticText(self, -1, msg, (-1,-1), size=(250,32)) #sizer determines position

    rect = wxBoxSizer(wxHORIZONTAL)
    rect.Add(image, 0, wxALIGN_LEFT|wxALL, 4)
    rect.Add(text, 1, wxALIGN_CENTER|wxTOP, 7)
    sizer = wxBoxSizer(wxVERTICAL)


    box = wxBoxSizer(wxHORIZONTAL)
    btn = wxButton(self, wxID_NO, 'Connect')
    box.Add(btn, 0, wxALL, 10)
    btn.SetDefault()

    btn = wxButton(self, wxID_YES, 'Work Offline')
    box.Add(btn, 0, wxALL, 10)

    btn = wxButton(self, wxID_CANCEL, 'Cancel')
    box.Add(btn, 0, wxALL, 10)

    sizer.AddSizer(rect)
    sizer.AddSizer(box)

    self.SetSizer(sizer)

    EVT_BUTTON(self, wxID_NO, self.OnSelection)
    EVT_BUTTON(self, wxID_YES, self.OnSelection)
    EVT_BUTTON(self, wxID_CANCEL, self.OnSelection)
#@+node:ekr.20051104081502.491: *8* OnSelection
def OnSelection(self,evt):
    val = evt.GetId()
    self.EndModal(val)
#@+node:ekr.20051104081502.492: *7* class ModifierDialog
class ModifierDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.493: *8* __init__
def __init__(self, parent, title,
             pos=wxDefaultPosition,
             size=wxDefaultSize,
             style=wxCAPTION,
             modifierlist=None,
             curselections = ''):
    wxDialog.__init__(self, parent, -1, title, pos, size, style)

    sizer1 = wxBoxSizer(wxVERTICAL)
    sizer2 = wxBoxSizer(wxHORIZONTAL)

    tc = wxTextCtrl(self, -1, "", size = (150,-1))
    sizer1.Add(tc, 0, wxALIGN_CENTRE|wxALL, 5)
    self.tc = tc

    if not modifierlist:
        modifierlist = []
    lb = wxListBox(self, -1,  wxDefaultPosition, (150,300), #wxPoint(90, 80)
                    modifierlist, wxLB_MULTIPLE|wxLB_SORT)

    sizer1.Add(lb, 1, wxALIGN_CENTRE|wxALL, 5)

    line = wxStaticLine(self, -1, size = (20,-1), style = wxLI_HORIZONTAL)
    sizer1.Add(line, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP, 5)


    btn = wxButton(self, wxID_OK, "OK")
    sizer2.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
    btn.SetDefault()

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    sizer2.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    sizer1.AddSizer(sizer2, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5)
    self.SetSizer(sizer1)
    self.SetAutoLayout(True)
    sizer1.Fit(self)

    for sel in curselections:
        index = lb.FindString(sel)
        if index !=-1:
            lb.SetSelection(index)

    self.lb = lb

    EVT_BUTTON(self, wxID_CANCEL, self.ClearSelections)


#@+node:ekr.20051104081502.494: *8* GetUserInput
def GetUserInput(self):
    idx_list = self.lb.GetSelections()
    mod_list =[]
    for i in idx_list:
        mod_list.append(self.lb.GetString(i))
        self.lb.Deselect(i) #071203

    new_list = []
    manual_string = self.tc.GetValue() #text entry box

    if manual_string:
        manual_list = [x.strip() for x in manual_string.split(';')]
        for name in manual_list:
            clean_name = ", ".join([x.strip().title() for x in name.split(',')])
            if clean_name not in mod_list:
                mod_list.append(clean_name)
                new_list.append(clean_name)


    return (mod_list, new_list)
#@+node:ekr.20051104081502.495: *8* SelectCurrent
def SelectCurrent(self, cur_sel):
    for sel in cur_sel:
        index = self.lb.FindString(sel)
        if index !=-1:
            self.lb.SetSelection(index)


#@+node:ekr.20051104081502.496: *8* ClearSelections
def ClearSelections(self, evt=None):
    idx_list = self.lb.GetSelections() #note you can't just use the indexes of the SelectCurrent since they may have clicked before cancelling
    for i in idx_list:
        self.lb.Deselect(i)

    evt.Skip()
#@+node:ekr.20051104081502.497: *7* class MailDialog
class MailDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.498: *8* __init__
def __init__(self, parent, title,
             pos=wxDefaultPosition,
             size=wxDefaultSize,
             style=wxSTAY_ON_TOP| wxTHICK_FRAME|wxCAPTION|wxSYSTEM_MENU,
             recipients='',
             subject = '',
             body = ''):

    wxDialog.__init__(self, parent, -1, title, pos, size, style)

    sizer = wxBoxSizer(wxVERTICAL)
    box = wxBoxSizer(wxHORIZONTAL)

    recipients = "; ".join(recipients)
    label = wxStaticText(self, -1, "To:",wxDefaultPosition, size=(40,-1), style=wxALIGN_LEFT)
    RTC = wxTextCtrl(self, -1, recipients, size = (480,-1))
    box.Add(label)
    box.Add(RTC)

    #sizer.Add(10,10,0)      

    sizer.AddSizer(box)        

    box = wxBoxSizer(wxHORIZONTAL)       
    label = wxStaticText(self, -1, "Subject:",wxDefaultPosition, size=(40,-1),style=wxALIGN_LEFT)
    STC = wxTextCtrl(self, -1, subject, size = (480,-1)) 
    box.Add(label)
    box.Add(STC)

    sizer.AddSizer(box)
    sizer.Add(1, 5, 0)

    BTC = wxTextCtrl(self, -1, body, wxDefaultPosition, size = (500,400), style=wxTE_MULTILINE|wxTE_RICH2)

    sizer.Add(BTC)

    box = wxBoxSizer(wxHORIZONTAL)
    btn = wxButton(self, wxID_OK, "SEND MAIL")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
    btn.SetDefault()

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    sizer.AddSizer(box)
    self.SetSizer(sizer)
    self.SetAutoLayout(True)
    sizer.Fit(self)

    self.RTC = RTC
    self.STC = STC
    self.BTC = BTC

#@+node:ekr.20051104081502.499: *7* class CalendarDialog
class CalendarDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.500: *8* __init__
def __init__(self, parent, title,
             pos=wxDefaultPosition,
             size=wxDefaultSize,
             style=wxCAPTION,
             date=0):

    wxDialog.__init__(self, parent, -1, title, pos, size, style)

    if not date:
        date = wxDateTime_Now()

    cal = wxCalendarCtrl(self, -1, date, #pos = (25,50),
                         style = wxCAL_SHOW_HOLIDAYS | wxCAL_SUNDAY_FIRST)

    EVT_CALENDAR(self, cal.GetId(), self.OnCalSelected)

    #EVT_CLOSE(self, self.OnCloseWindow)          

    self.cal = cal

    # Set up control to display a set of holidays:
    EVT_CALENDAR_MONTH(self, cal.GetId(), self.OnChangeMonth)

    self.holidays = [(1,1), (10,31), (12,25) ]    # (these don't move around)

    self.OnChangeMonth()        

#-------------------------------------------------------------------------        
    sizer1 = wxBoxSizer(wxVERTICAL)
    sizer2 = wxBoxSizer(wxHORIZONTAL)

    sizer1.Add(cal, 0, wxALIGN_CENTRE|wxALL, 5)

    line = wxStaticLine(self, -1, size = (20,-1), style = wxLI_HORIZONTAL)
    sizer1.Add(line, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxRIGHT|wxTOP, 5)


    btn = wxButton(self, wxID_OK, "OK")
    btn.SetDefault()
    sizer2.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    #btn.SetDefault()
    sizer2.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    sizer1.AddSizer(sizer2, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5)
    self.SetSizer(sizer1)
    self.SetAutoLayout(True)
    sizer1.Fit(self)
#@+node:ekr.20051104081502.501: *8* OnCalSelected
def OnCalSelected(self, evt):
    self.result = evt.GetDate()
    self.EndModal(wxID_OK)
#@+node:ekr.20051104081502.502: *8* OnChangeMonth
def OnChangeMonth(self, evt=None):
    cur_month = self.cal.GetDate().GetMonth() + 1   # convert wxDateTime 0-11 => 1-12
    for month, day in self.holidays:
        if month == cur_month:
            self.cal.SetHoliday(day)        
#@+node:ekr.20051104081502.503: *8* OnCloseWindow
def OnCloseWindow(self, event):
    #self.cal.Destroy
    #self.Destroy()
    g.pr("I got to close window")
#@+node:ekr.20051104081502.504: *8* GetDate
def GetDate(self):
    return self.result
#@+node:ekr.20051104081502.505: *7* class FindDialog
class FindDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.506: *8* __init__
def __init__(self, parent, caption, msg, pos=wxDefaultPosition, size=(300,120)):
    wxDialog.__init__(self, parent, -1, caption, pos, size, style=wxSTAY_ON_TOP|wxCAPTION)    

    self.FindText = wxTextCtrl(self, -1, msg, wxDefaultPosition,(200,24))

    box_a = wxBoxSizer(wxHORIZONTAL)
    box_a.Add(self.FindText, 1, wxALIGN_CENTER|wxALL, 5)

    box_b = wxBoxSizer(wxVERTICAL)        
    btn = wxButton(self, wxID_OK, "OK")
    box_b.Add(btn, 0, wxALIGN_CENTER|wxALL,5)
    btn.SetDefault()               

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    box_b.Add(btn, 0, wxALIGN_CENTER)

    box_a.AddSizer(box_b)

    self.MatchCase = wxCheckBox(self, -1, "Match Case")
    self.MatchWhole = wxCheckBox(self, -1, "Match Whole Word")
    box_c = wxBoxSizer(wxVERTICAL)
    box_c.Add(self.MatchCase, 0, wxLEFT|wxBOTTOM, 5)
    box_c.Add(self.MatchWhole, 0, wxLEFT, 5)

    self.SearchNotes = wxCheckBox(self, -1, "Search Notes")
    self.SearchFinished = wxCheckBox(self, -1, "Search Finished")
    box_d = wxBoxSizer(wxVERTICAL)
    box_d.Add(self.SearchNotes, 0, wxLEFT|wxBOTTOM, 5)
    box_d.Add(self.SearchFinished, 0, wxLEFT, 5)

    box_e = wxBoxSizer(wxHORIZONTAL)
    box_e.AddSizer(box_c)
    box_e.AddSizer(box_d)

    sizer = wxBoxSizer(wxVERTICAL)
    sizer.AddSizer(box_a)
    sizer.AddSizer(box_e)

    self.SetSizer(sizer)

    EVT_BUTTON(self, wxID_OK, parent.FindString)


#@+node:ekr.20051104081502.507: *7* class EvalDialog
class EvalDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.508: *8* __init__
def __init__(self, parent, caption, msg, pos=wxDefaultPosition, size=(300,80)):
    wxDialog.__init__(self, parent, -1, caption, pos, size, style=wxSTAY_ON_TOP|wxCAPTION)    

    EvalText = wxTextCtrl(self, -1, msg, wxDefaultPosition,(200,24))


    box_a = wxBoxSizer(wxHORIZONTAL)
    box_a.Add(EvalText, 1, wxALIGN_CENTER|wxALL, 5)

    box_b = wxBoxSizer(wxVERTICAL)        
    btn = wxButton(self, wxID_OK, "OK")
    box_b.Add(btn, 0, wxALIGN_CENTER|wxALL,5)
    btn.SetDefault()               

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    box_b.Add(btn, 0, wxALIGN_CENTER)

    box_a.AddSizer(box_b)

    self.SetSizer(box_a)

    self.EvalText = EvalText
    self.parent = parent

    #EVT_BUTTON(self, wxID_OK, self.PostOKEvent)
    EVT_BUTTON(self, wxID_OK, parent.OnEvaluate)



#@+node:ekr.20051104081502.509: *8* PostOKEvent
def PostOKEvent(self, evt=None):
    wxPostEvent(self.parent, evt)
#@+node:ekr.20051104081502.510: *7* class LoggerDialog
class LoggerDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.511: *8* __init__
def __init__(self, parent, msg, caption, pos=(-1,-1), size=(500,300), dir=None):
    wxDialog.__init__(self, parent, -1, caption, pos, size)
    #if pos == (-1,-1):
        #self.CenterOnScreen(wxBOTH)

    if dir:
        self.dir = dir
    else:
        self.dir = os.getcwd()

    text = wxTextCtrl(self, -1, msg, (-1,-1), (450,250), wxTE_MULTILINE | wxTE_READONLY)

    sizer = wxBoxSizer(wxVERTICAL)
    box = wxBoxSizer(wxHORIZONTAL)        

    sizer.Add(text, 1, wxALIGN_CENTRE|wxALL, 5)

    btn = wxButton(self, wxID_OK, "Close")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
    btn.SetDefault()

    ID_SAVE = wxNewId()

    btn = wxButton(self, ID_SAVE, "Save to File")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)        

    sizer.AddSizer(box, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5)
    self.SetSizer(sizer)
    self.SetAutoLayout(True)
    sizer.Fit(self)

    self.text = text

    EVT_BUTTON(self, ID_SAVE, self.OnSave)
#@+node:ekr.20051104081502.512: *8* OnSave
def OnSave(self, evt):

    path = os.path.join(self.dir, 'logfile.txt')

    f = file(path,'a')
    f.write(self.text.GetValue())
    f.close()

    dlg = wxMessageDialog(self,"Appended text to logfile.text", "Notice", wxICON_INFORMATION|wxOK)
    dlg.ShowModal()
    dlg.Destroy()

    self.text.Clear()
#@+node:ekr.20051104081502.513: *7* class FinishedDialog
class FinishedDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.514: *8* __init__
def __init__(self, parent, title,
            pos=wxDefaultPosition,
            size=wxDefaultSize,
            style=wxCAPTION,
            days=0,
            spin_label="",
            check_label=""):

    wxDialog.__init__(self, parent, -1, title, pos, size)
    self.Centre()

    self.check = wxCheckBox(self, -1, check_label)

    if days == -1:
        self.check.SetValue(True)
        days = 0

    panel = wxPanel(self, -1, (-1,-1),(225,75))
    wxStaticText(panel, -1, spin_label,(15, 15))
    self.text = wxTextCtrl(panel, -1, str(days), (30, 50), (30, -1))
    h = self.text.GetSize().height
    self.spin = wxSpinButton(panel, -1, (56, 50), (h, h), wxSP_VERTICAL)
    wxStaticText(panel, -1, 'days',(76, 53))
    self.spin.SetRange(0, 14)
    self.spin.SetValue(days)

    H_sizer = wxBoxSizer(wxHORIZONTAL)

    line = wxStaticLine(self, -1, size = (20,-1), style = wxLI_HORIZONTAL)

    btn = wxButton(self, wxID_OK, "OK")
    H_sizer.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
    btn.SetDefault()

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    H_sizer.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    V_sizer = wxBoxSizer(wxVERTICAL)
    V_sizer.Add(panel,1,wxALIGN_CENTER|wxEXPAND)
    V_sizer.Add(-1,5)
    V_sizer.Add(self.check,0,wxALIGN_LEFT|wxALL,5)
    V_sizer.Add(line,0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP, 5)
    V_sizer.AddSizer(H_sizer, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5)

    self.SetSizer(V_sizer)
    self.SetAutoLayout(True)
    V_sizer.Fit(self)

    EVT_SPIN(self, self.spin.GetId(), self.OnSpin)
    EVT_CHECKBOX(self, self.check.GetId(), self.OnCheck)

    if self.check.GetValue():
        self.spin.Enable(False)
        self.text.Enable(False)

    self.Layout() #doesn't appear necessary


#@+node:ekr.20051104081502.515: *8* OnSpin
def OnSpin(self, evt):
    self.text.SetValue(str(evt.GetPosition()))
#@+node:ekr.20051104081502.516: *8* OnCheck
def OnCheck(self, evt=None):
    if self.check.GetValue():
        self.spin.Enable(False)
        self.text.Enable(False)
    else:
        self.spin.Enable(True)
        self.text.Enable(True)
#@+node:ekr.20051104081502.517: *7* class TreeDialog
class TreeDialog(wxDialog):
    @others
#@+node:ekr.20051104081502.518: *8* __init__
def __init__(self, parent, caption, pos=wxDefaultPosition, size=(300,400), tree={}):
    wxDialog.__init__(self, parent, -1, caption, pos, size, style=wxSTAY_ON_TOP|wxCAPTION)

    TreeCtrl = wxTreeCtrl(self, -1, wxDefaultPosition, (300,400), wxTR_HAS_BUTTONS)    #|wxTR_HIDE_ROOT)#wxDefaultSize,

    sizer = wxBoxSizer(wxVERTICAL)
    sizer.Add(TreeCtrl, 1, wxALIGN_CENTER|wxALL, 5)

    box = wxBoxSizer(wxHORIZONTAL)
    btn = wxButton(self, wxID_OK, "OK")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
    btn.SetDefault()

    btn = wxButton(self, wxID_CANCEL, "CANCEL")
    box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)

    sizer.AddSizer(box)
    self.SetAutoLayout(1)
    self.SetSizer(sizer)

    il = wxImageList(16,16)

    fldridx = il.Add(wxBitmap('bitmaps\\folder.bmp'))
    fldropenidx = il.Add(wxBitmap('bitmaps\\folder_open.bmp'))
    listidx =  il.Add(wxBitmap('bitmaps\\list.bmp'))

    TreeCtrl.SetImageList(il)

    root = TreeCtrl.AddRoot("List Manager")
    TreeCtrl.SetItemImage(root, fldridx, wxTreeItemIcon_Normal)
    TreeCtrl.SetItemImage(root, fldropenidx, wxTreeItemIcon_Expanded)

    for host in tree:
        child = TreeCtrl.AppendItem(root, host)
        TreeCtrl.SetItemImage(child, fldridx, wxTreeItemIcon_Normal)
        TreeCtrl.SetItemImage(child, fldropenidx, wxTreeItemIcon_Expanded)
        for listname in tree[host]:
            last = TreeCtrl.AppendItem(child, listname)
            TreeCtrl.SetItemImage(last, listidx, wxTreeItemIcon_Normal)
            TreeCtrl.SetItemImage(last, listidx, wxTreeItemIcon_Selected)

    TreeCtrl.Expand(root)

    self.TreeCtrl= TreeCtrl
    self.il = il #? prevents GC

    EVT_LEFT_DCLICK(TreeCtrl, self.OnLeftDClick)
#@+node:ekr.20051104081502.519: *8* OnLeftDClick:
def OnLeftDClick(self, event=None):
    self.EndModal(wxID_OK)
#@+node:ekr.20051104081502.520: *6* outlookAddin.py
@ @rst-options
code_mode = True
@c

@language python
<< outlookAddin declarations >>
@others

if __name__ == '__main__':
    import win32com.server.register
    win32com.server.register.UseCommandLine(OutlookAddin)
    if "--unregister" in sys.argv:
        UnregisterAddin(OutlookAddin)
    else:
        RegisterAddin(OutlookAddin)
#@+node:ekr.20051104081502.521: *7* << outlookAddin declarations >>
# This is mainly stolen from Mark Hammond's demo plugin for win32com.client
# A demo plugin for Microsoft Outlook (NOT Outlook Express)

from win32com import universal
from win32com.server.exception import COMException
from win32com.client import gencache, DispatchWithEvents
from win32com.client import Dispatch
import winerror
import pythoncom
from win32com.client import constants
import win32ui ##
import sys
from socket import *
import pickle

# Support for COM objects we use.
#sz comment gencache.EnsureModule makes sure you are using makepy if the makepy-derived
#file doesn't already exist
#but as long as you did run makepy then you should just be alble to do a normal dispatch

mod = gencache.EnsureModule('{00062FFF-0000-0000-C000-000000000046}', 0, 9, 0, bForDemand=True) # Outlook 9
gencache.EnsureModule('{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}', 0, 2, 1, bForDemand=True) # Office 9

# The TLB defining the interfaces we implement
universal.RegisterInterfaces('{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}', 0, 1, 0, ["_IDTExtensibility2"])

Target = 'mail_transfer'


#@+node:ekr.20051104081502.522: *7* class ButtonEvent
class ButtonEvent:
    @others
#@+node:ekr.20051104081502.523: *8* OnClick
def OnClick(self, button, cancel):
    #activeExplorer and MailTransferFolder are globals defined in OnConnection
    sel = activeExplorer.Selection

    for i in range(1,sel.Count+1):
        item = sel.Item(i)
        item.Move(MailTransferFolder)

    return cancel

#@+node:ekr.20051104081502.524: *7* class FolderEvent
class FolderEvent:
    @others
#@+node:ekr.20051104081502.525: *8* OnItemAdd
def OnItemAdd(self, item):
    try:
        s = socket(AF_INET,SOCK_STREAM)
        s.connect(('localhost', 8888))
        d = {}
        d['Parent.Name'] = item.Parent.Name
        d['SenderName'] = item.SenderName
        d['Subject'] = item.Subject
        d['Body'] = item.Body[:5000]
        d['CreationTime'] = item.CreationTime.Format()
        str = pickle.dumps(d)
        s.send(str) # ?Receive no more than 1024 bytes
        s.close()
        win32ui.MessageBox("Sent %s to ListManager"%item.Subject)
    except:
        pass
#@+node:ekr.20051104081502.526: *7* class OutlookAddin
class OutlookAddin:
    << class OutlookAddin declarations >>
    @others
#@+node:ekr.20051104081502.527: *8* << class OutlookAddin declarations >>
_com_interfaces_ = ['_IDTExtensibility2']
_public_methods_ = []
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
_reg_clsid_ = "{0F47D9F3-598B-4d24-B7E3-92AC15ED27E2}"
_reg_progid_ = "Python.Test.OutlookAddin"
_reg_policy_spec_ = "win32com.server.policy.EventHandlerPolicy"
#@+node:ekr.20051104081502.528: *8* OnConnection
def OnConnection(self, application, connectMode, addin, custom):
    global MailTransferFolder
    global activeExplorer
    # ActiveExplorer may be none when started without a UI (eg, WinCE synchronisation)
    activeExplorer = application.ActiveExplorer()
    if activeExplorer:
        bars = activeExplorer.CommandBars
        toolbar = bars.Item("Standard")
        item = toolbar.Controls.Add(Type=constants.msoControlButton, Temporary=True)
        item = self.toolbarButton = DispatchWithEvents(item, ButtonEvent) #? just need this to be an ivar
        item.Caption="List Manager"
        item.TooltipText = "Click to move"
        item.Enabled = True
        #self.toolbarButton = DispatchWithEvents(item, ButtonEvent) #need something that won't get GC'd. Note Dispatch returns item

    ns = application.GetNamespace("MAPI")
    Folders = ns.Folders

    for i in range(1,len(Folders)+1):
        if Folders[i].Name.find("Mailbox") != -1:
            folders = Folders[i].Folders
            break
    else:
        win32ui.MessageBox("Can't find Mailbox!")
        return	

    for i in range(1,len(folders)+1):
        if folders[i].Name == Target:
            MailTransferFolder = folders[i]
            self.targetMailbox = DispatchWithEvents(folders[i].Items, FolderEvent) #? just need this to be an ivar
            win32ui.MessageBox("Enabled: %s\nOutlookAddin3"%Target)
            break
    else:
        win32ui.MessageBox("Could not find mail folder: %s\nOutlookAddin3"%Target)
#@+node:ekr.20051104081502.529: *8* OnDisconnection
def OnDisconnection(self, mode, custom):
    g.pr("OnDisconnection")
#@+node:ekr.20051104081502.530: *8* OnAddInsUpdate
def OnAddInsUpdate(self, custom):
    g.pr("OnAddInsUpdate", custom)
#@+node:ekr.20051104081502.531: *8* OnStartupComplete
def OnStartupComplete(self, custom):
    g.pr("OnStartupComplete", custom)
#@+node:ekr.20051104081502.532: *8* OnBeginShutdown
def OnBeginShutdown(self, custom):
    g.pr("OnBeginShutdown", custom)
#@+node:ekr.20051104081502.533: *7* RegisterAddin
def RegisterAddin(klass):
    import _winreg
    key = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\Outlook\\Addins")
    subkey = _winreg.CreateKey(key, klass._reg_progid_)
    _winreg.SetValueEx(subkey, "CommandLineSafe", 0, _winreg.REG_DWORD, 0)
    _winreg.SetValueEx(subkey, "LoadBehavior", 0, _winreg.REG_DWORD, 3)
    _winreg.SetValueEx(subkey, "Description", 0, _winreg.REG_SZ, klass._reg_progid_)
    _winreg.SetValueEx(subkey, "FriendlyName", 0, _winreg.REG_SZ, klass._reg_progid_)
#@+node:ekr.20051104081502.534: *7* UnregisterAddin
def UnregisterAddin(klass):
    import _winreg
    try:
        _winreg.DeleteKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\Outlook\\Addins\\" + klass._reg_progid_)
    except WindowsError:
        pass
#@+node:ekr.20051104081502.535: *5* @rst ../doc/pdfTest.pdf
########
Headline
########

@ @rst-options
.. These options have NO EFFECT for rst2 plugin!
code_mode=False
generate_rst=True
http_server_support = False
show_organizer_nodes=True
show_headlines=True
show_leo_directives=True
stylesheet_path=c:\prog\leoCVS\leo\doc
write_intermediate_file = True
verbose=True
@c

This is a test of pdf stuff

.. contents::
#@+node:ekr.20051104081502.536: *6* child node
#@+node:ekr.20051104081502.537: *7* @rst
child node text
#@+node:ekr.20051104081502.546: *4* Tests of settings when opened from another .leo file
# g.app.config.updateSettings(c)
g.es('test_setting',c.config.getBool('test_setting'))
#@+node:ekr.20051104081502.159: *4* Tk bindtags test
import Tkinter as Tk

root = Tk.Tk()
c = Tk.Canvas(root,background='white')
g.pr(c.bindtags())

if 0:
    c.pack(expand=1,fill='both')
    f = Tk.Frame(c)
    c.create_window(0,0,window=f,anchor='nw')
    f.pack_configure(fill='both',expand=1)
    body = olCreateControl(self,frame,f)
    c.on = False 
    sel = lambda event, c = c, body = body:select(event,c,body)
    ai = lambda event, c = c, body = body, colorizer = frame.body:add_item(event,c,body,colorizer.getColorizer())
    c.bind("<Key>",watcher,'+')
    c.bind("<Key>",sel,'+')
    c.bind("<Key>",ai,'+')
    ctags = c.bindtags()
    btags = body.bindtags()
    btags =(ctags[0],btags[0],btags[1],btags[2],btags[3])
    body.bindtags(btags)
#@+node:ekr.20051104081502.305: *4* Unicode stuff
stuff = g.toEncodedString(u'∑','utf-8')
g.pr(type(stuff))
g.pr('*' * 10)
for ch in stuff:
    g.pr(ch, ord(ch),newline=False)
g.pr()
#@+node:ekr.20051104081502.549: *4* Write to log tab
c.frame.log.selectTab('Log')
g.es('Test',color='blue')
#@+node:ekr.20051104081502.548: *4* Write to test tab
c.frame.log.selectTab('Test')
g.es('Test',color='red',tabName='Test')
#@+node:ekr.20100125180231.5120: *3* Print a unicode character
@first # -*- coding: utf-8 -*-

s = g.ue('炰','utf-8')
g.es(s)
g.pr(s)
s = '炰'
g.pr(s)
#@+node:ekr.20051104081502.108: *3* runProfile button mini-test
for i in range(10000):
    if i and (i % 1000) == 0:
        g.pr(i)
#@+node:ekr.20051104081502.109: *3* runTimeit mini-test
i = 0
for i in range(100000):
    i += 1
    i -= 1
#@+node:ekr.20080821123427.2: *3* Standard print test
@first # -*- coding: utf-8 -*-

import sys

print('=' * 40)

e = sys.getdefaultencoding()
print('encoding',e)

table = (
    'La Peña',
    g.u('La Peña'),
    # u'La Peña',
    g.u('La Pe\xf1a')
)

for s in table:
    print(type(s))
    g.es_print('g.es_print',s)
    if type(s) != type(u'a'):
        s = unicode(s,e)
    print('print     ',s)
    print('repr(s)   ',repr(s))
#@+node:ekr.20051104081502.160: *3* User Icon tests
#@+node:ekr.20051104081502.161: *4* Delete user icons
for p in c.allNodes_iter():

    if hasattr(p.v.t,"unknownAttributes"):
        a = p.v.t.unknownAttributes
        iconsList = a.get("icons")
        if dict:
            a["icons"] = []
            a["lineYOffset"] = 0

c.redraw()
#@+node:ekr.20051104081502.162: *4* Test of user icons
p.v.t.unknownAttributes = {}
a = p.v.t.unknownAttributes

<< define event callbacks >>

path = g.os_path_join(g.app.loadDir,"..","Icons")
icon1 = g.os_path_join(path,"lt_arrow_enabled.gif")
icon2 = g.os_path_join(path,"rt_arrow_enabled.gif")

d1 = {
    "type" : "file", "file" : icon1,
    "where" : "beforeIcon",
    "yoffset" : -3,
    # "yoffset" : 5, "ypad" : -5,
    # "height" : 40, # automatically adjust headline y position.
    "xpad": 2
}

# Classes and functions can only be pickled if they are at the top level of a module.
    #"onClick" : onClick,
    #"onRightClick" : onRightClick,
    #"onDoubleClick" : onDoubleClick }

d2 = {
    "type" : "file", "file" : icon2,
    "where" : "beforeHeadline",
    "yoffset" : -3,
    "xoffset" : 2, "xpad" : -2 }

a["icons"] = [d1,d2] # [d1,d2]
a["lineYOffset"] = 3

c.redraw()
#@+node:ekr.20051104081502.163: *5* << define event callbacks >>
def onClick(p=p):

    g.trace(p)

def onRightClick(p=p):

    g.trace(p)

def onDoubleClick(p=p):

    g.trace(p)
#@+node:ekr.20111107113442.3849: ** @ignore unused tests
#@+node:ekr.20101021205258.6013: *3* general file stuff
#@+node:ekr.20100204153116.5369: *4* @@test raw file copy
import tempfile
import os

s = 'Select the following string: वादक. Typing and undo now work.'
fd,fn = tempfile.mkstemp(text=False)
s = g.toEncodedString(s)
os.write(fd,s)
os.close(fd)
f = open(fn,'rb')
s2 = f.read()
f.close()
assert s==s2
os.remove(fn)
print('deleted',fn)
#@+node:ekr.20080806211453.5: *4* @@test round-trip-uAs for @shadow
root = p.firstChild()
uA = 'unknownAttributes'
tag = 'round-trip-u.uA'
ttag = 'round-trip-t.uA'
trace = False

if 0: # Set the uA's.
    for p2 in root.self_and_subtree_iter():
        p2.v.unknownAttributes = {tag: p2.h}
else: # Test the uA's.
    # The root is a special case.
    v = root.v
    assert hasattr(v,uA),'no v.uA for %s' % v
    assert getattr(v,uA),'empty v.uA for %s' % v
    for p2 in root.self_and_subtree_iter():
        v = p2.v
        assert hasattr(v,uA),'no v.uA for %s' % v
        a = getattr(v,uA)
        d = {tag: v.h}
        if trace: print(d)
        assert a == d, 'expected v.uA: "%s", got "%s"' % (d,a)
#@+node:ekr.20080806211453.1: *5* @@shadow uA_test_shadow_file.py
@language python
@tabwidth -4
@others
#@+node:ekr.20080822160527.1: *6* uA_test_shadow_file declarations
pass
pass
pass
#@+node:ekr.20080806095923.2: *4* @@test round-trip-uAs for @thin
root = p.firstChild()
uA = 'unknownAttributes'
tag = 'round-trip-u.uA'
ttag = 'round-trip-t.uA'
trace = False

if 0: # Set the uA's.
    for p2 in root.self_and_subtree_iter():
        p2.v.unknownAttributes = {tag: p2.h}
else: # Test the uA's.
    # The root is a special case.
    v = root.v
    assert hasattr(v,uA),'no v.uA for %s' % v
    assert getattr(v,uA),'empty v.uA for %s' % v
    for p2 in root.self_and_subtree_iter():
        v = p2.v
        assert hasattr(v,uA),'no v.uA for %s' % v
        a = getattr(v,uA)
        d = {tag: v.h}
        if trace: print(d)
        assert a == d, 'expected v.uA: "%s", got "%s"' % (d,a)
#@+node:ekr.20080806084924.2: *5* @@thin uA_test_file.py
@others
#@+node:ekr.20080806084924.3: *6* child1
pass
#@+node:ekr.20080806084924.4: *7* grandChild1
pass
#@+node:ekr.20080806084924.5: *8* greatGrandChild1
pass
#@+node:ekr.20100223094723.5375: *3* New import tests
#@+node:ekr.20100223094723.5376: *4* @test skipToTheNextClassOrFunction (a class next)
import leo.core.leoImport as leoImport
ic = c.importCommands

s = '''\

def one():
    pass

import a
from . import a

@language python

d = {} # An interior comment.

# This is a comment.
# and another comment.
@aDecorator
class cl: # An interior comment
    def method(self):
        pass

def two():
    pass

'''

# tree = c.importCommands.pythonUnitTest(p,s=s,showTree=True)

expected = s.find('# This is a comment')
scanner = leoImport.pythonScanner(importCommands=ic,atAuto=False)
i = s.find('import a')
assert i > -1
i = scanner.skipToTheNextClassOrFunction(s,i,lastIndent=0)
assert i==expected,'expected %s, got %s %s' % (
    expected,i,repr(s[i:]))
#@+node:ekr.20100223094723.5377: *4* @test skipToTheNextClassOrFunction (a function next)
import leo.core.leoImport as leoImport
ic = c.importCommands

s = '''\

def one():
    pass

import a
from . import a

d = {}

# This is a comment.
@tabwith -4 # This looks like a comment.
# and another comment.
@aDecorator
def two():
    pass

'''

# tree = c.importCommands.pythonUnitTest(p,s=s,showTree=True)

expected = s.find('# This is a comment')
scanner = leoImport.pythonScanner(importCommands=ic,atAuto=False)
i = s.find('import a')
assert i > -1
i = scanner.skipToTheNextClassOrFunction(s,i,lastIndent=0)
assert i==expected,'expected %s, got %s %s' % (
    expected,i,repr(s[i:]))
#@+node:ekr.20100223094723.5378: *4* @test skipToTheNextClassOrFunction (nothing next)
import leo.core.leoImport as leoImport
ic = c.importCommands

s = '''\

def one():
    pass

import a
from . import a

d = {}

# This is a comment.
# and another comment.

@tabwidth -4

aList = ('a','b','def')

if __name__ == '__main__':
    pass

'''

lastLine = 'pass\n'
expected = s.find(lastLine) + len(lastLine) + 1
scanner = leoImport.pythonScanner(importCommands=ic,atAuto=False)
i = s.find('import a')
assert i > -1
i = scanner.skipToTheNextClassOrFunction(s,i,lastIndent=0)
assert i==expected,'expected %s, got %s %s' % (
    expected,i,repr(s[i:]))
#@+node:ekr.20100223094723.5379: *4* @test skipToTheNextClassOrFunction (indented def next)
import leo.core.leoImport as leoImport
ic = c.importCommands

s = '''\

def one():
    pass

import a
from . import a

if 0:
    def two():
        pass

if __name__ == '__main__':
    pass

'''

scanner = leoImport.pythonScanner(importCommands=ic,atAuto=False)
expected = i = s.find('import a')
assert i > -1
i = scanner.skipToTheNextClassOrFunction(s,i,lastIndent=0)
assert i==expected,'expected %s, got %s %s' % (
    expected,i,repr(s[i:]))
#@+node:ekr.20100225094004.5405: *3* @@test loading .leo file with @file nodes
# test/at-file-test.leo contains tnodeList.
# test/at-file-test.py contains file-like sentinels.
g.app.unitTestDict={}
fn = g.os_path_finalize_join(g.app.loadDir,'..','test','unittest','at-file-test.leo')

try:
    c2 = g.openWithFileName(fn,c)
    assert c2
    assert c2.changed,'not changed'
    p2 = g.findNodeAnywhere(c2,'@file at-file-test.py')
    assert p2,'no p2'
    assert p2.isDirty(),'not dirty'
    assert g.app.unitTestDict.get('read-convert'),'not converted'
    ok = True
finally:
    if True: # and ok:
        # Close the frame without prompt.
        g.app.destroyWindow(c2.frame)
        c.setLog()
        c.bodyWantsFocus()
#@+node:ekr.20061008162912: *3* @@test write .leo file with @ignore node
assert p.firstChild(), 'no child node'
assert p.firstChild().b.startswith('@ignore'), 'No @ignore in child'
ok = c.fileCommands.write_Leo_file(
    'file-name',outlineOnlyFlag=True,toString=True,toOPML=False)
assert ok, 'error writing file'
count = 0
s = g.app.write_Leo_file_string
for line in g.splitLines(s):
    if line.find('@ignore') != -1:
        count += 1
assert count >=1, "not enough @ignore's in written file: count: %s, lines:\n%s" % (count,s)
#@+node:ekr.20061008162912.1: *4* child
@ignore # Test that this node gets written.
#@+node:ekr.20111112193817.3908: *3* @@test delete-body-editor 3 times
if 0:
    # This works, but the label sticks, presumably because of a race condition.
    # Therefore, this is best left as a mini-test.

    w = c.frame.body
    for i in range(4):
        print(i)
        w.addEditor()
        w.deleteEditor()
#@+node:ekr.20051107115231.25: *3* @@test typing in headline recomputes width
# getWidth no longer exists.
k = c.keyHandler
h = 'Test headline abc'
p = c.testManager.findNodeAnywhere(h)
assert p,'node not found: %s' % h
c.redraw(p) # To make node visible
c.frame.tree.editLabel(p)
w = c.edit_widget(p)
try:
    assert w
    g.app.gui.set_focus(c,w)
    w2 = g.app.gui.get_focus(c)
    # assert w == w2 or hasattr(w,'widget') and w.widget == w2,'w: %s\nw2: %s' % (w,w2)
    w.setSelectionRange('end','end')
    n = w.getWidth()
    g.app.gui.event_generate(c,'X','Shift+X',w)
    g.app.gui.event_generate(c,'Y','Shift+Y',w)
    g.app.gui.event_generate(c,'Z','Shift+Z',w)
    g.app.gui.event_generate(c,'\n','Return',w)
    w.update()
    assert w, 'fail 2'
   
finally:
    if 1:
        c.setHeadString(p,h) # Essential
        c.redraw(p)
#@+node:ekr.20061104172236.23: *3* @@test tkBody.onClick
w = c.frame.body.bodyCtrl
y = 10
for x in range(0,100,10):
    event = g.app.gui.create_key_event(c,None,None,w,x=x,y=y)
    c.frame.body.onClick(event)
#@-all
#@-leo
