Hi Mario,

I implemented this feature you requested into the task list plugin.

You need to filter against your page name. Then you will see all open / closed tasks within that page.

Clicking on "tick all" should do the trick. I have not finalized this feature yet (need to add a warning dialog too).
You are the first to give it a try.

Feedback welcome!

Regards,
Murat



On 02.12.2015 16:39, mbe...@tiscali.it wrote:
Hello,

I have a basic [read: stupid] question about the "Task List" plugin:
In my main zim wiki I have a "AAAA To Do" page where I keep a list of all the things to do. Tasks are added to the list in no special order.

I then use the "Task List" plugin to see them in priority order (thanks to the ! tag). From the plugin, when a task is complete I have to go to the "AAAA To Do" page to just mark it done, and go back to the plugin to look at the next one. It would be nice to have a way (button, key, whatever) to mark tasks complete directly from within the plugin, avoiding to go back and forth.

Is this a reasonable request? I had a look at the manual, but couldn't find anything similar.

Maybe my way of managing of tasks is not the expected one and I should just change how I do things?

Comments appreciated.

Thanks in advance,
mario









































Connetti gratis il mondo con la nuova indoona: hai la chat, le chiamate, le video chiamate e persino le chiamate di gruppo.
E chiami gratis anche i numeri fissi e mobili nel mondo!
Scarica subito l’app Vai su https://www.indoona.com/



_______________________________________________
Mailing list: https://launchpad.net/~zim-wiki
Post to     : zim-wiki@lists.launchpad.net
Unsubscribe : https://launchpad.net/~zim-wiki
More help   : https://help.launchpad.net/ListHelp

# -*- coding: utf-8 -*-

# Copyright 2009-2017 Jaap Karssenberg <jaap.karssenb...@gmail.com>

# Additions by Murat Guven <mur...@online.de>
# V1.84 Fixed [no date] entry for children
# V1.83 Fixed wrong view in Task list history when parent task is open and 
child task is ticked.
# V1.82 Tag list rearranged in vbox tasklist dialog
# V1.81 using Zim widgets for autocompletion, duedate. Removed grey border from 
autocompletion entry (only visible in Windows)
# V1.8 added history of ticked tasks with ticked date (together with pageview)
# V1.74 little bug fix in comment dates format taken from dates.list
# V1.73 little bug fix in print function
# V1.72 added time for task comment
# V1.71 bug fixed with string truncating in comment
# V1.7 added possiblity to tick tasks
# V1.6 merged autocompletion for tags
# V1.5 merged with due date plugin
# V1.4 merged with task comment plugin
# V1.3 added comment column reading comment bullets under tasks + added tags 
column to dialog
# V1.21 added standard tag to look for on start
# V1.2 added new entry with autocompletion for tags

from __future__ import with_statement

import gtk
import pango
import logging
import re
import gtk.gdk
import sqlite3


#import zim.datetimetz as datetime
import zim.datetimetz as zim_datetime
from datetime import datetime as dtime
import datetime
import time

from zim.utils import natural_sorted
from zim.parsing import parse_date
from zim.plugins import PluginClass, extends, ObjectExtension, WindowExtension
from zim.actions import action
from zim.notebook import Path
from zim.gui.widgets import ui_environment, \
        Dialog, MessageDialog, \
        InputEntry, Button, IconButton, MenuButton, \
        BrowserTreeView, SingleClickTreeView, Window, ScrolledWindow, HPaned, 
VPaned, \
        encode_markup_text, decode_markup_text
from zim.gui.clipboard import Clipboard
from zim.signals import DelayedCallback, SIGNAL_AFTER
from zim.formats import get_format, \
        UNCHECKED_BOX, CHECKED_BOX, XCHECKED_BOX, BULLET, \
        PARAGRAPH, NUMBEREDLIST, BULLETLIST, LISTITEM, STRIKE, \
        Visitor, VisitorSkip
from zim.config import StringAllowEmpty, ConfigManager

from zim.plugins.calendar import daterange_from_path


logger = logging.getLogger('zim.plugins.tasklist')

KEYVALS_AT = map (gtk.gdk.keyval_from_name, ('at'))
KEYVALS_ESC = map (gtk.gdk.keyval_from_name, ('Escape'))
KEYSTATES = gtk.gdk.CONTROL_MASK | gtk.gdk.MOD2_MASK
ALTQ = '<alt>q'
ALTat = '<alt>at'


SQL_FORMAT_VERSION = (0, 6)
SQL_FORMAT_VERSION_STRING = "0.6"

# task column is needed for the pageview module to compare when a task is ticked
SQL_CREATE_TABLES = '''
create table if not exists tasklist (
        id INTEGER PRIMARY KEY,
        source INTEGER,
        parent INTEGER,
        haschildren BOOLEAN,
        open BOOLEAN,
        actionable BOOLEAN,
        prio INTEGER,
        due TEXT,
        tags TEXT,
        description TEXT,
        comment TEXT,
        tickmark BOOLEAN,
        tickdate TEXT,
        task TEXT
);
create table if not exists tasktickdate (
        id INTEGER PRIMARY KEY,
        tickmark BOOLEAN,
        tickdate TEXT,
        task TEXT
);
'''


_tag_re = re.compile(r'(?<!\S)@(\w+)\b', re.U)
_date_re = re.compile(r'\s*\[d:(.+)\]')
_tdate_re = re.compile(r'\s*\[x:(.+)\]')



_NO_DATE = '9999' # Constant for empty due date - value chosen for sorting 
properties
_NO_TAGS = '__no_tags__' # Constant that serves as the "no tags" tag - _must_ 
be lower case

# FUTURE: add an interface for this plugin in the WWW frontend

# TODO allow more complex queries for filter, in particular (NOT tag AND tag)
# See function filter_item for the AND implementation (all instead of any at 
'if visible and self.tag_filter:'
# TODO: think about what "actionable" means
#       - no open dependencies
#       - no defer date in the future
#       - no child item ?? -- hide in flat list ?
#       - no @waiting ?? -> use defer date for this use case


# TODO
# commandline option
# - open dialog
# - output to stdout with configurable format
# - force update, intialization


class TaskListPlugin(PluginClass):

        plugin_info = {
                'name': _('Task List'), # T: plugin name
                'description': _('''\
This plugin adds a dialog showing all open tasks in
this notebook. Open tasks can be either open checkboxes
or items marked with tags like "TODO" or "FIXME".

Additions by Murat Güven:

Auto completion for tags:
        When you press the @ key within a note, a list of available tags are 
shown and can be selected.
        Currently the activation is set to <ALT> + Q, as the Windows version 
does not work properly with <ALT-Gr> + Q.

Due date:
        To easily add or update a due date to a task in this format "[d:date]" 
via keyboard shortcut <ctrl> + period

        Options (see configuration):
         - add x number of days to current date
         - show due date in entry for quicker date selection (+ press c for 
calendar)
         - show calendar for selecting due date

        The due date format can be either maintained within the dates.list file
        (used by the date function <ctrl>D) or changed in Preferences.
        Standard is [d: %Y-%m-%d]

Task comment:
        To easily add a comment below a task via keyboard shortcut  <ctrl> + 
<shift> + >

        Either configure your format in preferences or re-use the date format 
within the dates.list file.
        Standard is [comment: %Y-%m-%d]
        
        The task comments are shown within the task list.

Ticking tasks:
        Tasks can be ticked / unticked within the tasklist window
        History of ticked tasks with ticked date are shown
        (with update of core pageview, ticked dates are handled outside of 
tasklist too)

# V1.8 added history of ticked tasks with ticked date (together with pageview)
# V1.7 added possiblity to tick tasks
# V1.6 merged with tag autocompletion plugin
# V1.5 merged with due date plugin
# V1.4 merged with task comment plugin
# V1.3 added column for task comments + added column for tags to dialog + Print
# V1.21 added standard tag to look for on start
# V1.2 added new entry with autocompletion for tags

This is a core plugin shipping with zim.
'''), # T: plugin description
                'author': 'Jaap Karssenberg',
                'help': 'Plugins:Task List'
        }

        plugin_preferences = (
                # key, type, label, default
                ('all_checkboxes', 'bool', _('Consider all checkboxes as 
tasks'), True),
                        # T: label for plugin preferences dialog
                ('tag_by_page', 'bool', _('Turn page name into tags for task 
items'), False),
                        # T: label for plugin preferences dialog
                ('deadline_by_page', 'bool', _('Implicit due date for task 
items in calendar pages'), False),
                        # T: label for plugin preferences dialog
                ('use_workweek', 'bool', _('Flag tasks due on Monday or Tuesday 
before the weekend'), True),
                ('show_history', 'bool', _('Show history of ticked tasks'), 
True),
                        # T: label for plugin preferences dialog
                ('labels', 'string', _('Labels marking tasks'), 'FIXME, TODO', 
StringAllowEmpty),
                        # T: label for plugin preferences dialog - labels are 
e.g. "FIXME", "TODO", "TASKS"
                ('next_label', 'string', _('Label for next task'), 'Next:', 
StringAllowEmpty),
                        # T: label for plugin preferences dialog - label is by 
default "Next"
                ('nonactionable_tags', 'string', _('Tags for non-actionable 
tasks'), '', StringAllowEmpty),
                        # T: label for plugin preferences dialog
                ('included_subtrees', 'string', _('Subtree(s) to index'), '', 
StringAllowEmpty),
                        # T: subtree to search for tasks - default is the whole 
tree (empty string means everything)
                ('excluded_subtrees', 'string', _('Subtree(s) to ignore'), '', 
StringAllowEmpty),
                        # T: subtrees of the included subtrees to *not* search 
for tasks - default is none
                ('standard_tag', 'string', _('Set tag to be searched for on 
start (without @)'), '', StringAllowEmpty),
                        # T: subtrees of the included subtrees to *not* search 
for tasks - default is none
                ('task_comment_string', 'string', _('Name the string for task 
comments: '), 'comment'),
                ('task_comment_date_format', 'string', _('Format for the task 
comment date string'), '%Y-%m-%d'),
                ('task_comment_dateslist', 'bool', _('Use due-date format in 
dates.list file for the date string'), False),
                ('task_comment_time', 'bool', _('Add time to task comment 
date'), False),
                ('task_comment_time_format', 'string', _('Format for the time 
string'), '%H:%M', StringAllowEmpty),
                ('task_comment_bold', 'bool', _('Task comment in bold'), False),
                ('task_comment_italic', 'bool', _('Task comment content in 
italic'), False),
                ('due_date_plus', 'int', _('Due date: Add days to today 
[d:today + days]'), 0, (0, 365)),
                ('due_date_entry', 'bool', _('Due date: Show entry popup'), 
False),
                ('due_date_cal', 'bool', _('Due date: Show calendar popup'), 
False),
                ('single_match', 'bool', _('Due date: Single match shall not be 
shown in a popup'), False),
                        # T: label for plugin preferences dialog
                ('completion_non_modal', 'bool', _('Due date: Disable lock of 
main window, if back focus does not work'), False),
        )
        _rebuild_on_preferences = ['all_checkboxes', 'labels', 'next_label', 
'deadline_by_page', 'nonactionable_tags',
                                   'included_subtrees', 'excluded_subtrees' ]
                # Rebuild database table if any of these preferences changed.
                # But leave it alone if others change.

        def extend(self, obj):
                name = obj.__class__.__name__
                if name == 'MainWindow':
                        index = obj.ui.notebook.index # XXX
                        i_ext = self.get_extension(IndexExtension, index=index)
                        mw_ext = MainWindowExtension(self, obj, i_ext)
                        self.extensions.add(mw_ext)
                else:
                        PluginClass.extend(self, obj)


@extends('Index')
class IndexExtension(ObjectExtension):

        # define signals we want to use - (closure type, return type and arg 
types)
        __signals__ = {
                'tasklist-changed': (None, None, ()),
        }

        def __init__(self, plugin, index):
                ObjectExtension.__init__(self, plugin, index)
                self.plugin = plugin
                self.index = index

                self.preferences = plugin.preferences

                self.task_labels = None
                self.task_label_re = None
                self.next_label = None
                self.next_label_re = None
                self.nonactionable_tags = []
                self.included_re = None
                self.excluded_re = None
                self.db_initialized = False
                self._current_preferences = None

                db_version = self.index.properties['plugin_tasklist_format']
                if db_version == '%i.%i' % SQL_FORMAT_VERSION:
                        self.db_initialized = True

                self._set_preferences()
                self.connectto(plugin.preferences, 'changed', 
self.on_preferences_changed)

                self.connectto_all(self.index, (
                        ('initialize-db', self.initialize_db, None, 
SIGNAL_AFTER),
                        ('page-indexed', self.index_page),
                        ('page-deleted', self.remove_page),
                ))
                # We don't care about pages that are moved

        def on_preferences_changed(self, preferences):
                if self._current_preferences is None \
                or not self.db_initialized:
                        return

                new_preferences = self._serialize_rebuild_on_preferences()
                if new_preferences != self._current_preferences:
                        self._drop_table()
                self._set_preferences()  # Sets _current_preferences

        def _set_preferences(self):
                self._current_preferences = 
self._serialize_rebuild_on_preferences()

                string = self.preferences['labels'].strip(' ,')
                if string:
                        self.task_labels = [s.strip() for s in 
self.preferences['labels'].split(',')]

                else:
                        self.task_labels = []

                if self.preferences['next_label']:
                        self.next_label = self.preferences['next_label']
                                # Adding this avoid the need for things like 
"TODO: Next: do this next"
                        self.next_label_re = re.compile(r'^' + 
re.escape(self.next_label) + r':?\s+' )
                        self.task_labels.append(self.next_label)
                else:
                        self.next_label = None
                        self.next_label_re = None

                if self.preferences['nonactionable_tags']:
                        self.nonactionable_tags = [
                                t.strip('@').lower()
                                        for t in 
self.preferences['nonactionable_tags'].replace(',', ' ').strip().split()]
                else:
                        self.nonactionable_tags = []

                if self.task_labels:
                        regex = r'^(' + '|'.join(map(re.escape, 
self.task_labels)) + r')(?!\w)'
                        self.task_label_re = re.compile(regex)
                else:
                        self.task_label_re = None

                if self.preferences['included_subtrees']:
                        included = [i.strip().strip(':') for i in 
self.preferences['included_subtrees'].split(',')]
                        included.sort(key=lambda s: len(s), reverse=True) # 
longest first
                        included_re = '^(' + '|'.join(map(re.escape, included)) 
+ ')(:.+)?$'
                        #~ print '>>>>>', "included_re", repr(included_re)
                        self.included_re = re.compile(included_re)
                else:
                        self.included_re = None

                if self.preferences['excluded_subtrees']:
                        excluded = [i.strip().strip(':') for i in 
self.preferences['excluded_subtrees'].split(',')]
                        excluded.sort(key=lambda s: len(s), reverse=True) # 
longest first
                        excluded_re = '^(' + '|'.join(map(re.escape, excluded)) 
+ ')(:.+)?$'
                        #~ print '>>>>>', "excluded_re", repr(excluded_re)
                        self.excluded_re = re.compile(excluded_re)
                else:
                        self.excluded_re = None

        def _serialize_rebuild_on_preferences(self):
                # string mapping settings that influence building the table
                string = ''
                for pref in self.plugin._rebuild_on_preferences:
                        string += str(self.preferences[pref])
                return string

        def initialize_db(self, index):
                with index.db_commit:
                        index.db.executescript(SQL_CREATE_TABLES)
                self.index.properties['plugin_tasklist_format'] = '%i.%i' % 
SQL_FORMAT_VERSION
                self.db_initialized = True

        def teardown(self):
                self._drop_table()

        def _drop_table(self):
                self.index.properties['plugin_tasklist_format'] = 0

                try:
                        self.index.db.execute('DROP TABLE "tasklist"')
                        self.index.db.execute('DROP TABLE "tasktickdate"')
                except:
                        if self.db_initialized:
                                logger.exception('Could not drop table:')

                self.db_initialized = False

        def _excluded(self, path):
                if self.included_re and self.excluded_re:
                        # judge which match is more specific
                        # this allows including subnamespace of excluded 
namespace
                        # and vice versa
                        inc_match = self.included_re.match(path.name)
                        exc_match = self.excluded_re.match(path.name)
                        if not exc_match:
                                return not bool(inc_match)
                        elif not inc_match:
                                return bool(exc_match)
                        else:
                                return len(inc_match.group(1)) < 
len(exc_match.group(1))
                elif self.included_re:
                        return not bool(self.included_re.match(path.name))
                elif self.excluded_re:
                        return bool(self.excluded_re.match(path.name))
                else:
                        return False

        def index_page(self, index, path, page):
                if not self.db_initialized: return
                #~ print '>>>>>', path, page, page.hascontent

                tasksfound = self.remove_page(index, path, _emit=False)
                if self._excluded(path):
                        if tasksfound:
                                self.emit('tasklist-changed')
                        return

                parsetree = page.get_parsetree()
                if not parsetree:
                        return

                #~ print '!! Checking for tasks in', path
                dates = daterange_from_path(path)
                if dates and self.preferences['deadline_by_page']:
                        deadline = dates[2]
                else:
                        deadline = None
                
                tasks = self._extract_tasks(parsetree, deadline)
                
                if tasks:
                        # Do insert with a single commit
                        with self.index.db_commit:
                                self._insert(path, 0, tasks)

                if tasks or tasksfound:
                        self.emit('tasklist-changed')

        def _insert(self, page, parentid, children):
                # Helper function to insert tasks in table
                c = self.index.db.cursor()
                for task, grandchildren in children:
                        task[4] = ','.join(sorted(task[4])) # set to text
                        task[6] = ''.join(sorted(task[6], reverse=True)) # sort 
comments against date and set to text
                        
                        c.execute(
                                'insert into tasklist(source, parent, 
haschildren, open, actionable, prio, due, tags, description, comment, tickmark, 
tickdate, task)'
                                'values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 
?)',
                                (page.id, parentid, bool(grandchildren)) + 
tuple(task)
                        )
                        if grandchildren:
                                self._insert(page, c.lastrowid, grandchildren) 
# recurs


        def _extract_tasks(self, parsetree, defaultdate=None):
                '''Extract all tasks from a parsetree.
                @param parsetree: a L{zim.formats.ParseTree} object
                @param defaultdate: default due date for the whole page (e.g. 
for calendar pages) as string
                @returns: nested list of tasks, each task is given as a 
2-tuple, 1st item is a tuple
                with following properties: C{(open, actionable, prio, due, 
description)}, 2nd item
                is a list of child tasks (if any).
                '''

                parser = TasksParser(
                        self.task_label_re,
                        self.next_label_re,
                        self.nonactionable_tags,
                        self.preferences['all_checkboxes'],
                        defaultdate,
                        self.preferences
                )
                parser.parse(parsetree)
                return parser.get_tasks()

        def remove_page(self, index, path, _emit=True):
                if not self.db_initialized: return

                tasksfound = False
                with index.db_commit:
                        cursor = index.db.cursor()
                        cursor.execute(
                                'delete from tasklist where source=?', 
(path.id,) )
                        tasksfound = cursor.rowcount > 0

                if tasksfound and _emit:
                        self.emit('tasklist-changed')

                return tasksfound

        def list_tasks(self, parent=None):
                '''List tasks
                @param parent: the parent task (as returned by this method) or 
C{None} to list
                all top level tasks
                @returns: a list of tasks at this level as sqlite Row objects
                '''
                if parent: parentid = parent['id']
                else: parentid = 0

                if self.db_initialized:
                        cursor = self.index.db.cursor()
                        cursor.execute('select * from tasklist where parent=? 
order by prio, due, description', (parentid,))
                                # Want order by prio & due - add desc to keep 
sorting more or less stable
                        for row in cursor:
                                yield row

        def get_task(self, taskid):
                cursor = self.index.db.cursor()
                cursor.execute('select * from tasklist where id=?', (taskid,))
                return cursor.fetchone()

        def get_path(self, task):
                '''Get the L{Path} for the source of a task
                @param task: the task (as returned by L{list_tasks()}
                @returns: an L{IndexPath} object
                '''
                return self.index.lookup_id(task['source'])

        def put_new_tickdate_to_db(self, task, tickmark=True):
                date = zim_datetime.now()
                cursor = self.index.db.cursor()
                cursor.execute('UPDATE tasktickdate set tickdate=? WHERE 
task=?', (date, unicode(task)))
                if not cursor.rowcount:
                        cursor.execute('INSERT into tasktickdate(tickmark, 
tickdate, task) values (?, ?, ?)', (tickmark, unicode(date), unicode(task)))

        def put_existing_tickdate_to_db(self, task, date, tickmark=True):
                print "executing"
                cursor = self.index.db.cursor()
                cursor.execute('UPDATE tasktickdate set tickdate=? WHERE 
task=?', (date, unicode(task)))
                if not cursor.rowcount:
                        cursor.execute('INSERT into tasktickdate(tickmark, 
tickdate, task) values (?, ?, ?)', (tickmark, unicode(date), unicode(task)))

        def get_tickdate_from_db(self, task):
                cursor = self.index.db.cursor()
                cursor.execute('SELECT * FROM tasktickdate WHERE task=?', 
(task,))
                return cursor.fetchone()

        def del_tickdate_from_db(self, task):
                cursor = self.index.db.cursor()
                cursor.execute('DELETE FROM tasktickdate WHERE task=?', 
(unicode(task),))



@extends('MainWindow')
class MainWindowExtension(WindowExtension):

        uimanager_xml = '''
                <ui>
                        <menubar name='menubar'>
                                <menu action='view_menu'>
                                        <placeholder name="plugin_items">
                                                <menuitem 
action="show_task_list" />
                                        </placeholder>
                                </menu>
                                <menu action='tools_menu'>
                                        <placeholder name='plugin_items'>
                                                <menuitem 
action='task_comment'/>
                                        </placeholder>
                                        <placeholder name='plugin_items'>
                                                <menuitem action='due_date'/>
                                        </placeholder>
                                        <placeholder name='plugin_items'>
                                                <menuitem 
action='auto_completion'/>
                                        </placeholder>
                                </menu>
                        </menubar>
                        <toolbar name='toolbar'>
                                <placeholder name='tools'>
                                        <toolitem action='show_task_list'/>
                                </placeholder>
                        </toolbar>
                </ui>
        '''

        def __init__(self, plugin, window, index_ext):
                WindowExtension.__init__(self, plugin, window)
                self.index_ext = index_ext
        @action(_('Task List'), stock='zim-task-list', readonly=True) # T: menu 
item
        def show_task_list(self):
                if not self.index_ext.db_initialized:
                        MessageDialog(self.window, (
                                _('Need to index the notebook'),
                                # T: Short message text on first time use of 
task list plugin
                                _('This is the first time the task list is 
opened.\n'
                                  'Therefore the index needs to be rebuild.\n'
                                  'Depending on the size of the notebook this 
can\n'
                                  'take up to several minutes. Next time you 
use the\n'
                                  'task list this will not be needed again.' )
                                # T: Long message text on first time use of 
task list plugin
                        ) ).run()
                        logger.info('Tasklist not initialized, need to rebuild 
index')
                        finished = self.window.ui.reload_index(flush=True) # XXX
                        # Flush + Reload will also initialize task list
                        if not finished:
                                self.index_ext.db_initialized = False
                                return
                        
                dialog = TaskListDialog.unique(self, self.window, 
self.index_ext, self.plugin.preferences)
                dialog.present()

        #~ accel_key = 'at'
        ac_accel_key = ALTQ

        @action(_('Task Comment'), accelerator='<ctrl>greater')  # T: menu item
        def task_comment(self):
                tc = TaskComment(self.window, self.plugin.preferences)
                tc.add()

        @action(_('_Due Date'), accelerator='<ctrl>period')  # T: menu item
        def due_date(self):
                dd = DueDate(self.window, self.plugin.preferences)
                dd.add()

        @action(_('_Auto Completion'), accelerator=ac_accel_key)  # T: menu item
        def auto_completion(self):
                ac = AutoCompletion(self.window, self.plugin.preferences)
                ac.show()


class AutoCompletion():

        def __init__(self, window, plugin_preferences):


                self.plugin_prefs = plugin_preferences
                self.window = window


        def show(self):
                #completion_window = gtk.Window()
                completion_window = Window()
                entry = InputEntry()
                entry.set_inner_border(None)
                hbox = gtk.VBox(spacing=0)
                completion_window.add(entry)

                # remove grey border from completion window around entry
                completion_window.set_geometry_hints(entry, max_height=1)       
        

                #to prevent that main window is used during autocompletion
                if not self.plugin_prefs['completion_non_modal']:
                        completion_window.set_modal(True)
                completion_window.set_keep_above(True)

                #get the cursor position when activated, move window
                main_window = self.window.pageview.view
                zim_buffer = self.window.pageview.view.get_buffer()
                cursor = zim_buffer.get_iter_at_mark(zim_buffer.get_insert())

                #insert @ key at cursor pos as it is not shown due to 
accelerator setting
                zim_buffer.insert(cursor, "@")
                x, y, height = self.ac_get_iter_pos(main_window, cursor)
                completion_window.move(x, y)
                entry_completion = gtk.EntryCompletion()

                # this brings the hit from the list into the entry box so
                # it can be selected hitting RETURN
                entry_completion.set_inline_completion(True)

                # add tags from zim index to the completion list
                zim_index = self.window.ui.notebook.index
                tag_liststore = gtk.ListStore(str)

                self.ac_set_tag_liststore(zim_index, tag_liststore, 
entry_completion)

                # if there is only one hit, don't show popup
                if self.plugin_prefs['single_match']:
                        entry_completion.set_popup_single_match(False)

                entry.set_completion(entry_completion)
                entry_completion.set_text_column(0)

                # just the plane entry box
                completion_window.set_decorated(False)

                # as lowercase entries are not matched after the 3rd character 
somehow with the standard completion
                # 
entry_completion.set_match_func(self.new_completion_match_func, 0)            # 
not working yet


                completion_window.show_all()


                # listen to the signals
                # if list element is selected via mouse klick or cursor + return
                entry_completion.connect('match-selected', 
self.ac_match_selected, completion_window, zim_buffer, entry)
                # when return key is pressed
                entry.connect('activate', self.ac_activate_return, 
completion_window, zim_buffer)
                #listen if ESC key is pressed for the entry completion
                entry.add_events(gtk.gdk.KEY_PRESS_MASK)
                entry.connect('key_press_event', self.ac_get_textbuffer, 
completion_window)

        # not working yet. Want to use this as the entry completion is not 
really working with lower case
        # def new_completion_match_func(completion, entry_str, iter, data):
        #       self.column = data
        #       self.model = completion.get_model()
        #       self.modelstr = self.model[iter][self.column]
        #       return self.modelstr.upper()(entry_str.upper())

        def ac_set_tag_liststore(self, zim_index, tag_liststore, 
entry_completion):
                self.zim_tags = zim_index.list_all_tags()
                tag_liststore.clear()
                for self.tag in self.zim_tags:
                        tag_liststore.append([self.tag.name])
                entry_completion.set_model(tag_liststore)

        def ac_get_textbuffer(self, widget, event, completion_window):
                if event.keyval:
                        # currently not used as I can't get the key_press_event 
signal. Therefore the
                        # workaround with the accelarator key --> @ for linux 
<alt>+q for windows
                        # if event.state & KEYSTATES:
                        #       if event.keyval == 
gtk.gdk.keyval_from_name('at'):
                        #               auto_completion()
                        if event.keyval == gtk.gdk.keyval_from_name('Escape'):
                                completion_window.destroy()

        def ac_match_selected(self, completion, model, iter, completion_window, 
zim_buffer, entry):
                self.tag = model[iter][0]
                #get the position of the cursor from the parent 
window/textbuffer
                self.cursor = 
zim_buffer.get_iter_at_mark(zim_buffer.get_insert())
                #insert the selected tag into the parent window at the current 
cursor pos
                #delete the completion window
                completion_window.destroy()
                zim_buffer.insert(self.cursor, self.tag)

        def ac_activate_return(self, entry, completion_window, zim_buffer):
                #get the selected tag string
                self.tag = entry.get_text()
                if self.tag:            # if return was hit in an empty box 
without text
                        #get the position of the cursor from the parent 
window/textbuffer
                        self.cursor = 
zim_buffer.get_iter_at_mark(zim_buffer.get_insert())
                        #add tag string into textbuffer
                        zim_buffer.insert(self.cursor, self.tag)
                        #close autocompletion window
                self.tag = ''
                completion_window.destroy()

        def ac_get_iter_pos(self, textview, cursor):
                self.top_x, self.top_y = textview.get_toplevel().get_position()
                self.iter_location = textview.get_iter_location(cursor)
                self.mark_x, self.mark_y = self.iter_location.x, 
self.iter_location.y + self.iter_location.height
                #calculate buffer-coordinates to coordinates within the window
                self.win_location = 
textview.buffer_to_window_coords(gtk.TEXT_WINDOW_WIDGET,
                                                                                
                                 int(self.mark_x), int(self.mark_y))
                #now find the right window --> Editor Window and the right pos 
on screen
                self.win = textview.get_window(gtk.TEXT_WINDOW_WIDGET);
                self.view_pos = self.win.get_position()
                self.xx = self.win_location[0] + self.view_pos[0]
                self.yy = self.win_location[1] + self.view_pos[1] + 
self.iter_location.height
                self.x = self.top_x + self.xx
                self.y = self.top_y + self.yy
                return (self.x, self.y, self.iter_location.height)


class DueDate():

        def __init__(self, window, plugin_preferences):
                self.plugin_prefs = plugin_preferences
                self.window = window


        def add(self):


                #self.cal_window = gtk.Window()
                self.cal_window = Window()
                self.cal_window.set_decorated(False)
                self.cal_window.set_modal(True)
                self.cal_window.set_keep_above(True)

                self.cal = gtk.Calendar()
                self.cal.display_options(gtk.CALENDAR_SHOW_WEEK_NUMBERS | 
gtk.CALENDAR_SHOW_HEADING | gtk.CALENDAR_SHOW_DAY_NAMES)
                self.cal_window.add(self.cal)

                #self.entry_window = gtk.Window()
                self.entry_window = Window()
                self.entry_window.set_decorated(False)
                self.entry_window.set_modal(True)
                self.entry_window.set_keep_above(True)

                self.vbox = gtk.VBox()
                self.label_day_name = gtk.Label()
                
                self.entry_hbox = gtk.HBox()
                #self.entry_year = gtk.Entry(4)
                self.entry_year = InputEntry()
                self.entry_year.set_max_length(4)
                self.entry_year.set_width_chars(4)
                #self.entry_month = gtk.Entry(2)
                self.entry_month = InputEntry()
                self.entry_month.set_max_length(2)
                self.entry_month.set_width_chars(2)
                #self.entry_day = gtk.Entry(2)
                self.entry_day = InputEntry()
                self.entry_day.set_max_length(2)
                self.entry_day.set_width_chars(2)
                #self.entry_week_no = gtk.Entry(2)
                self.entry_week_no = InputEntry()
                self.entry_week_no.set_max_length(2)
                self.entry_week_no.set_width_chars(2)
                self.entry_week_no.set_sensitive(False)

                self.label_due_date_plus = gtk.Label()
                self.label_due_date_plus.set_text('Today   +    ')
                #self.entry_due_date_plus = gtk.Entry(6)
                self.entry_due_date_plus = InputEntry()
                self.entry_due_date_plus.set_max_length(6)
                self.entry_due_date_plus.set_width_chars(6)
                self.due_date_plus_hbox = gtk.HBox()
                self.due_date_plus_hbox.pack_start(self.label_due_date_plus, 
expand = False, padding = 2)
                self.due_date_plus_hbox.pack_start(self.entry_due_date_plus, 
expand = True, padding = 1)

                self.due_date_plus = self.plugin_prefs['due_date_plus']

                self.vbox.pack_start(self.label_day_name, expand = False, 
padding=0)
                self.vbox.pack_start(self.entry_hbox, expand = False, padding=0)
                self.vbox.pack_start(self.due_date_plus_hbox, expand = False, 
padding = 2)

                '''
                [0: current_date_element, 1: prev_date_element]
                1: year, 2: month, 3: day, 4: due_date_plus, 5: week_no (not 
really used any more from history)
                '''
                # in fact I don't need 2 history levels any more, but I keep it 
for later use
                self.entry_history = {  1: [0, False],
                                                                2: [0, False],
                                                                3: [0, False],
                                                                4: [0, False],
                                                                5: [0, False]
                                                                }

                def up_down_arrow_key(arrow_key_dict):
                        # get the elements from the dict / list
                        function = arrow_key_dict[0]
                        entry = arrow_key_dict[1]
                        ddplus_calc_value = arrow_key_dict[2]

                        # call the right function from the list with its params
                        function(ddplus_calc_value, entry)
                        
                        # in case someone enters a new day and then directly 
uses Up arrow key
                        self.update_due_date_plus()                             
                        

                        # put back focus to the correct entry as it's set to 
entry_due_date_plus in above function      
                        entry.grab_focus()

                def left_right_arrow_key(arrow_key_dict):
                        function = arrow_key_dict[0]
                        entry = arrow_key_dict[1]
                        
                        entry.grab_focus()
                        #select data in entry
                        function(0 , -1)


                def cal_lrud_key(value, widget):
                        self.due_date_plus += value

                        new_date = self.get_date_with_ddplus(None, 
self.due_date_plus)

                        widget.select_month(new_date.month-1, new_date.year)
                        self.cal_select_day(new_date.day)
                        pass

                
                '''
                this dict provides me a set of functions which I call according 
to the id of the key press event
                this way I avoid repeating code...
                '''
                self.entry_key_inputs = {       65362 : (up_down_arrow_key, 
{'day'              : [self.ce_kpe_day_and_ddplus, self.entry_day, 1],  # up 
arrow key
                                                                                
                                                 'month'        : 
[self.ce_kpe_month, self.entry_month, 1],
                                                                                
                                                 'year'         : 
[self.ce_kpe_year, self.entry_year, 1],               
                                                                                
                                                'ddplus'        : 
[self.ce_kpe_day_and_ddplus, self.entry_due_date_plus, 1]}),
                                                                        65364 : 
(up_down_arrow_key, {'day'              : [self.ce_kpe_day_and_ddplus, 
self.entry_day, -1], # down arrow key
                                                                                
                                                 'month'        : 
[self.ce_kpe_month, self.entry_month, -1],
                                                                                
                                                 'year'         : 
[self.ce_kpe_year, self.entry_year, -1],              
                                                                                
                                                 'ddplus'       : 
[self.ce_kpe_day_and_ddplus, self.entry_due_date_plus, -1]}),
                                                                        65361 : 
(left_right_arrow_key, {'day' :         [self.entry_month.select_region, 
self.entry_month],                                                     # when 
event comes from entry_day, I need to go to month (left)
                                                                                
                                                        'month' :       
[self.entry_year.select_region, self.entry_year],                               
                        # when event comes from entry_month, I need to go to 
year (left)
                                                                                
                                                        'year' :        
[self.entry_due_date_plus.select_region, self.entry_due_date_plus],             
# when event comes from entry_year, I need to go to ddplus (left)
                                                                                
                                                        'ddplus' :      
[self.entry_day.select_region, self.entry_day]}),                               
                # when event comes from entry_due_date_plus, I need to go to 
day (left)
                                                                        65363 : 
(left_right_arrow_key, {'day' :         
[self.entry_due_date_plus.select_region, self.entry_due_date_plus],     # when 
event comes from entry_day, I need to go to entry_due_date_plus (right)
                                                                                
                                                        'ddplus' :      
[self.entry_year.select_region, self.entry_year],               # when event 
comes from entry_due_date_plus, I need to go to entry_year (right)
                                                                                
                                                        'year' :        
[self.entry_month.select_region, self.entry_month],             # when event 
comes from entry_year, I need to go to entry_month (right)
                                                                                
                                                        'month' :       
[self.entry_day.select_region, self.entry_day]})                        # when 
event comes from entry_month, I need to go to entry_day (right)
                                                                        }

                self.cal_key_inputs = {         65361 : (cal_lrud_key, -1),     
# left arrow key
                                                                        65363 : 
(cal_lrud_key, +1),     # right arrow key
                                                                        65362 : 
(cal_lrud_key,  -7),  # up arrow key
                                                                        65364 : 
(cal_lrud_key, +7)      # down arrow key
                                                                }

                
                # TODO: need to set focus chain manually to prevent focus on 
due_date_plus entry when arrow down is clicked
                # not quite satisfied with the current result though
                self.vbox.set_focus_chain([self.entry_due_date_plus, 
self.entry_day])           

                self.entry_hbox.pack_start(self.entry_year, expand=True, 
padding=1)
                self.entry_hbox.pack_start(self.entry_month, expand=True, 
padding=1)
                self.entry_hbox.pack_start(self.entry_day, expand=True, 
padding=1)
                self.entry_hbox.pack_start(self.entry_week_no, expand=True, 
padding=2)

                self.entry_window.add(self.vbox)

                self.buf = self.window.pageview.view.get_buffer()
                
                # needed to highlight due date
                self.text_tag = self.buf.create_tag(background = "grey")        
                
                self.page_view = self.window.pageview.view
                self.cursor = self.buf.get_iter_at_mark(self.buf.get_insert())

                self.due_date_begin_iter = ""
                self.due_date_end_iter = ""
                # preserve cursor position
                self.cursor_orig = 
self.buf.get_iter_at_mark(self.buf.get_insert())
                self.format = self.get_format()
                self.exist_due_date = None
                
                self.get_due_date_at_line(self.buf)
                

                if self.plugin_prefs['due_date_entry']:
                        self.show_entries()
                elif self.plugin_prefs['due_date_cal']:
                        self.show_cal()
                else:
                        self.insert_due_date(self.exist_due_date)

        def show_entries(self):
                # put data into entries
                self.fill_entries(self.exist_due_date)

                # move entries to cursor position
                x, y, height = self.get_iter_pos()
                self.entry_window.move(x, y)

                self.entry_day.connect("key_press_event", self.ce_keypress, 
"day")
                self.entry_month.connect("key_press_event", self.ce_keypress, 
"month")
                self.entry_year.connect("key_press_event", self.ce_keypress, 
"year")
                self.entry_due_date_plus.connect("key_press_event", 
self.ce_keypress, "ddplus")

                self.entry_year.connect("activate", 
self.ce_activate_insert_due_date)   
                self.entry_month.connect("activate", 
self.ce_activate_insert_due_date)  
                self.entry_day.connect("activate", 
self.ce_activate_insert_due_date)
                self.entry_due_date_plus.connect("activate", 
self.ce_activate_insert_due_date)

                self.entry_year.connect("event", self.ce_event, self.entry_year)
                self.entry_month.connect("event", self.ce_event, 
self.entry_month)
                self.entry_day.connect("event", self.ce_event, self.entry_day)
                self.entry_due_date_plus.connect("event", self.ce_event, 
self.entry_due_date_plus)

                self.entry_window.show_all()

        def show_day_name(self, year, month, day):
                date = self.get_specific_date(year, month, day)
                day_name = date.strftime("%A")
                self.label_day_name.set_text(str(day_name))

        def show_cal(self):
                self.entry_window.destroy()                                     
        
                # TODO: it's more logical to show the calendar in addition, but 
needs some re-design, 
                # that if calender entry is selected with cursor, it should 
also run through the entry date_elements
                # only if date is clicked in calender, add selection to buffer
                due_date = self.get_date_with_ddplus()                          
        
                
                # due_date.month-1 as calendar starts counting with 0 as 
January ....
                self.cal.select_month(due_date.month-1, due_date.year)          
                self.cal.select_day(due_date.day)

                x, y, height = self.get_iter_pos()
                # x-200 when above TODO is done
                self.cal_window.move(x, y)                                      
        
                self.cal_window.show_all()

                self.cal_mark_today()
                self.cal.connect("key_press_event", self.c_kpe_calendar, 
self.window)
                self.cal.connect("day-selected-double-click", 
self.ce_selected_cal_date)

        def show_date_element_data_in_entry(self, entry, date_element_data, 
history_index):
                '''
                data 0: current_date_element, 
                data 1: prev_date_element]
                index 1: year, 2: month, 3: day, 4: due_date_plus
                '''
                self.put_date_element_data_into_hist(date_element_data, 
history_index)
                entry.set_text(str(date_element_data))
                
        def show_all_date_element_data(self, year, month, day, ddplus, 
focus_entry=None):
                year, month, day, ddplus = 
self.validate_date_element_data(year, month, day, ddplus)
                # only validated data should be in history...
                self.put_all_date_element_data_into_hist(year, month, day, 
ddplus)

                week_no = self.get_week_no(year, month, day)
                self.show_day_name(year, month, day)

                # calculate the number of due date plus from current date to 
today
                self.calculate_due_date_plus_from_today(year, month, day)       
                                        

                self.show_date_element_data_in_entry(self.entry_due_date_plus, 
self.due_date_plus, 4)
                self.show_date_element_data_in_entry(self.entry_week_no, 
week_no, 5)

                self.show_date_element_data_in_entry(self.entry_year, year, 1)
                self.show_date_element_data_in_entry(self.entry_month, month, 2)
                self.show_date_element_data_in_entry(self.entry_day, day, 3)

                focus_entry.grab_focus()
                focus_entry.select_region(0 , -1)

        def get_week_no(self, year, month, day):
                date = self.get_specific_date(year, month, day)
                week_no = date.isocalendar()[1]
                return week_no

        def validate_date_element_data(self, year, month, day, ddplus):
        
                # make data int. By doing this in the beginning, I ensure that 
empty elements or chars are already handled. Only if 0 is entered, I need to 
check below 
                try:
                        year = int(year)
                except ValueError:
                        # If I ensure that only integer is put into history, I 
don't need to integer this again
                        year = self.entry_history[1][0]                         
                try:
                        month = int(month)
                except ValueError:
                        month = self.entry_history[2][0]
                try:
                        day = int(day)
                except ValueError:
                        day = self.entry_history[3][0]
                try:
                        ddplus = int(ddplus)
                except ValueError:
                        ddplus = self.entry_history[4][0]

                # identify, if data was passed. If None or empty, get history 
data
                if year == 0:
                        year = self.entry_history[1][0]
                if month == 0:
                        month = self.entry_history[2][0]
                if day == 0:
                        day = self.entry_history[3][0]

                # now ensure that year, month and day is a valid number. 
datetime takes only year > 1900, month obviously <= 12
                if year < 1900:
                        year = 1900

                if month > 12:
                        month = 12

                last_day_of_month = self.get_last_day_of_month(year, month)

                if day > last_day_of_month:
                        day = last_day_of_month

                return year, month, day, ddplus

        def put_all_date_element_data_into_hist(self, year, month, day, ddplus):
                '''
                The history is needed if user removes data from entry and then 
press enter or clicks to another entry
                data 0: current_date_element, 
                data 1: prev_date_element]
                index 1: year, 2: month, 3: day, 4: due_date_plus, 5: week_no 
not needed any more

                '''

                list_of_elements = [year, month, day, ddplus]

                for history_index in range(1-4):
                        # put data into history and cycle old elements through 
all levels of the history dict
                        # depth is the max number of history levels: -1 as 0 is 
the first level  -> depth = len -1 --> flexibility if I want to increase 
history depth per entry in the dict
                        depth = len(self.entry_history[history_index]) -1       
                        
                        # start with level 0, so level 0 is the first entry in 
the history
                        level = 0                                               
                                                                
                        # depth = 0 is filled after this loop with current 
date_element_data
                        while level < depth:                                    
                                                
                                # fill up the history from the bottom. So very 
last is put into one above
                                self.entry_history[history_index][depth-level] 
= self.entry_history[history_index][depth-level-1]
                                level += 1
                        self.entry_history[history_index][0] = 
list_of_elements[history_index-1]

        def put_date_element_data_into_hist(self, date_element_data, 
history_index):
                '''
                The history is needed if user removes data from entry and then 
press enter or clicks to another entry
                data 0: current_date_element, 
                data 1: prev_date_element]
                index 1: year, 2: month, 3: day, 4: due_date_plus, 5: week_no 
not needed any more
                '''
                # put data into history and cycle old elements through all 
levels of the history dict
                # depth is the max number of history levels: -1 as 0 is the 
first level  -> depth = len -1 --> flexibility if I want to increase history 
depth per entry in the dict
                depth = len(self.entry_history[history_index]) -1               
                
                # start with level 0, so level 0 is the first entry in the 
history
                level = 0                                                       
                                                        
                # depth = 0 is filled after this loop with current 
date_element_data
                while level < depth:                                            
                                        
                        # fill up the history from the bottom. So very last is 
put into one above
                        self.entry_history[history_index][depth-level] = 
self.entry_history[history_index][depth-level-1]
                        level += 1
                self.entry_history[history_index][0] = date_element_data

        def print_history(self):
                # to check if history is working correct
                for index in range(1, 5):
                        for level in range(len(self.entry_history[index])):
                                print 'Index: {0}, Level: {1}, data: , 
{2}'.format(index, level, self.entry_history[index][level])

        def print_data(self, method=None, data1=None, data2=None, data3=None):
                print 'method: {0}, data1: {1}, data2: {2}, data3: , 
{3}'.format(method, data1, data2, data3)

        def insert_due_date(self, due_date = None):
                # delete existing due date
                if self.due_date_begin_iter:
                        self.del_due_date_at_line(self.buf)
                if not due_date:
                        due_date = self.get_date_with_ddplus()
                # get the user's due date format
                # format = self.get_format() --> already in self.format
                # now put the due date into the format
                date_with_format = zim_datetime.strftime(self.format, due_date)
                self.buf.insert(self.cursor, date_with_format)  
                self.high_light_chars(2)
                return

        def calculate_due_date_plus_from_today(self, year, month, day):
                date = self.get_specific_date(year, month, day)
                date_today = zim_datetime.date.today()
                today = self.get_specific_date(date_today.year, 
date_today.month, date_today.day)
                date_diff = date - today
                self.due_date_plus = date_diff.days

        def ce_event(self, widget, event, entry):
                if event.type == gtk.gdk.BUTTON_RELEASE:
                        # in case someone enters new data into the other 
entries and then directly clicks into due_date_plus entry
                        self.update_due_date_plus()                             
                
                        entry.grab_focus()

        def update_due_date_plus(self):
                year, month, day, ddplus = self.get_all_date_element_data()
                self.calculate_due_date_plus_from_today(year, month, day)
                self.show_all_date_element_data(year, month, day, ddplus, 
focus_entry = self.entry_due_date_plus)

        def ce_activate_insert_due_date(self, widget):                          
                                # ce = connect entry...

                #add due date string into textbuffer at cursor
                year, month, day, ddplus = self.get_all_date_element_data()
                year_hist, month_hist, day_hist, ddplus_hist = 
self.get_all_date_element_data_from_hist()

                # if nothing has been changed manually, the date can be taken 
and inserted into buffer
                if year == year_hist and month == month_hist and day == 
day_hist and ddplus == ddplus_hist:
                        due_date = self.put_date_to_format(year, month, day)
                        if self.due_date_begin_iter:
                                self.del_due_date_at_line(self.buf)
                        self.buf.insert(self.cursor, due_date)
                        self.entry_window.destroy()

                # If due date plus = 0 entered --> reset to today
                if ddplus == 0:                                                 
                
                        self.due_date_plus = 0
                        self.fill_entries(zim_datetime.date.today())
                        return

                # something was entered into due_date_plus entry
                if ddplus <> ddplus_hist:
                        self.due_date_plus = ddplus
                        self.due_date = self.get_date_with_ddplus(None, ddplus) 
        
                        self.fill_entries(self.due_date)
                else:
                        self.calculate_due_date_plus_from_today(year, month, 
day)
                        self.show_all_date_element_data(year, month, day, 
ddplus, focus_entry = self.entry_due_date_plus)
                return                                                          
                                                        

        def ce_selected_cal_date(self, calendar):
                (year, month, day) = calendar.get_date()
                date = self.put_date_to_format(year, month+1, day)

                if self.due_date_begin_iter:
                        self.del_due_date_at_line(self.buf)
                
                cursor = self.buf.get_iter_at_mark(self.buf.get_insert())
                self.buf.insert(cursor, date + " ")
                self.cal_window.destroy()

        def put_date_to_format(self, year, month, day):
                date = self.get_specific_date(year,month,day)
                #format = self.get_format()
                return date.strftime(self.format)

        def fill_entries(self, date = None):
                if not date:
                        date = self.get_date_with_ddplus()
                self.show_all_date_element_data(date.year, date.month, 
date.day, self.due_date_plus, focus_entry = self.entry_day)


        def ce_keypress(self, widget, event, entry_type):
                ''' 
                entry_type = day, month, year, ddplus
                '''
                try:
                        # I get the function which I want to call and the list 
of arguments according to the keyval from self.inputs
                        function_for_arrow_key, args_for_arrow_key_function = 
self.entry_key_inputs[event.keyval]
                        # I call the necessary function with the list of 
arguments, using the entry type to identify the right element in the dict 
within the list of arguments
                        
function_for_arrow_key(args_for_arrow_key_function[entry_type])

                except (KeyError):
                        # all other keys:'c' or ESC or Pos1
                        self.ce_events_common_key(event.keyval)

        def ce_events_common_key(self, keyval):
                if keyval == gtk.gdk.keyval_from_name('Escape'):
                        try:
                                self.buf.remove_tag(self.text_tag, 
self.due_date_begin_iter[0], self.due_date_end_iter[1])
                        except (ValueError, AttributeError, TypeError):
                                dummy = 0

                        self.buf.place_cursor(self.cursor_orig)

                        self.entry_window.destroy()
                elif keyval == gtk.gdk.keyval_from_name('c'):
                        self.show_cal()
                elif keyval == 65360:                   #       Pos1 key
                        # reset entries to today
                        self.due_date_plus = 0
                        self.fill_entries(zim_datetime.date.today())


        def ce_kpe_day_and_ddplus(self, value, entry):
                self.due_date_plus += value

                new_date = self.get_date_with_ddplus(None, self.due_date_plus)
                self.show_all_date_element_data(new_date.year, new_date.month, 
new_date.day, self.due_date_plus, focus_entry = entry)

        def ce_kpe_month(self, value, entry):
                year, month, day, due_date_plus = 
self.get_all_date_element_data()

                self.due_date_plus += value * self.get_last_day_of_month(year, 
month)

                new_date = self.get_date_with_ddplus(None, self.due_date_plus)
                self.show_all_date_element_data(new_date.year, new_date.month, 
new_date.day, self.due_date_plus, focus_entry = entry)

        def ce_kpe_year(self, value, entry):
                year, month, day, due_date_plus = 
self.get_all_date_element_data()
                day_of_year = self.get_yday(year, 12, 31)

                self.due_date_plus += value * day_of_year

                new_date = self.get_date_with_ddplus(None, self.due_date_plus)
                self.show_all_date_element_data(new_date.year, new_date.month, 
new_date.day, self.due_date_plus, focus_entry = entry)


        def cal_mark_today(self):
                # just in case, make a clear start
                self.cal.clear_marks()                                          
                
                (year, month, day) = self.cal.get_date()
                today = zim_datetime.date.today()
                # if calendar is displaying current month, mark today
                if year == today.year and month == today.month-1:               
                                
                        self.cal.mark_day(today.day)

                # c - connect
        def c_kpe_calendar(self, widget, event, window):        
                # just in case, make a clear start
                widget.clear_marks()                                            
                
                (year, month, day) = self.cal.get_date()
                today = zim_datetime.date.today()


                # if calendar is displaying current month, mark today
                if year == today.year and month == today.month-1:               
                                
                        self.cal.mark_day(today.day)

                if event.keyval == gtk.gdk.keyval_from_name('Escape'):
                        self.cal_window.destroy()
                if event.keyval == gtk.gdk.keyval_from_name('Return'):
                        self.ce_selected_cal_date(widget)
                if event.keyval == 65360:                       #       Pos1 key
                        # reset calendar to today
                        self.due_date_plus = 0
                        due_date = self.get_date_with_ddplus()                  
                                                
                        # -1 as calendar starts counting with 0 as January ....
                        self.cal.select_month(due_date.month-1, due_date.year)  
        
                        self.cal_select_day(due_date.day)

                ## Picture up and down keys
                if event.keyval == 65365: # Picture up key
                        next_month = month + 1
                        if next_month == 12:
                                next_month = 0
                                year = year + 1
                        widget.select_month(next_month, year)
                        # or statement in order to take the last day of month 
for further months
                        if (day > self.get_last_day_of_month(year, 
next_month+1)) or (day == self.get_last_day_of_month(year, month+1)):           
     
                                day = self.get_last_day_of_month(year, 
next_month+1)
                        self.cal_select_day(day)


                if event.keyval == 65366: # Picture down key
                        prev_month = month - 1
                        if prev_month == -1:
                                prev_month = 11
                                year = year - 1
                        widget.select_month(prev_month, year)
                        # +1 as calendar counts starting with 0
                        if (day > self.get_last_day_of_month(year, 
prev_month+1)) or (day == self.get_last_day_of_month(year, month+1)):           
             
                                day = self.get_last_day_of_month(year, 
prev_month+1)
                        self.cal_select_day(day)


                try:
                        # I get the function which I want to call and the list 
of arguments according to the keyval from self.cal_key_inputs
                        function_for_arrow_key, args_for_arrow_key_function = 
self.cal_key_inputs[event.keyval]
                        # I call the necessary function with the list of 
arguments
                        function_for_arrow_key(args_for_arrow_key_function, 
widget)

                except (KeyError):
                        pass


        def cal_select_day(self, selected_day):
                # just in case, make a clear start
                self.cal.clear_marks()                                          
                
                (year, month, day) = self.cal.get_date()
                today = zim_datetime.date.today()
                # if calendar is displaying current month, mark today
                if year == today.year and month == today.month-1:               
                                
                        self.cal.mark_day(today.day)
                self.cal.select_day(selected_day)

        def high_light_chars(self, chars_n):
                # now move 1 char back and place the cursor
                self.cursor.backward_chars(chars_n-1)
                self.buf.place_cursor(self.cursor)

                # now get the iter at cursor pos
                bound = self.buf.get_iter_at_mark(self.buf.get_insert())

                # now2 move 2 char back and place the cursor
                self.cursor.backward_chars(chars_n)
                self.buf.place_cursor(self.cursor)

                # now get the iter at cursor pos
                ins = self.buf.get_iter_at_mark(self.buf.get_insert())
                
                # now select 2 chars
                self.buf.select_range(ins, bound)

        def get_specific_date(self, year, month, day):
                try:
                        date = zim_datetime.datetime(year, month, day)
                except ValueError:
                        date = zim_datetime.datetime(1900, month, day)
                return date

        def get_date_with_ddplus(self, current_date=None, due_date_plus=None):
                if not current_date:
                        current_date = zim_datetime.date.today()
                # get the current date + x days according to the days given in 
prefs or from entry due_date_plus
                if not due_date_plus:
                        try:
                                due_date = current_date + 
zim_datetime.timedelta(days=self.due_date_plus)
                                if due_date.year < 1900:
                                        raise ValueError
                        except ValueError:
                                due_date = self.get_specific_date(1900, 1, 1)
                                date_today = zim_datetime.date.today()
                                today = self.get_specific_date(date_today.year, 
date_today.month, date_today.day)

                                date_diff  = due_date - today
                                self.due_date_plus = date_diff.days
                else:
                        try: 
                                due_date = current_date + 
zim_datetime.timedelta(days=due_date_plus)
                                if due_date.year < 1900:
                                        raise ValueError
                        except ValueError:
                                due_date = self.get_specific_date(1900, 1, 1)
                                date_today = zim_datetime.date.today()
                                today = self.get_specific_date(date_today.year, 
date_today.month, date_today.day)

                                date_diff  = due_date - today
                                self.due_date_plus = date_diff.days
                return due_date

        def get_last_day_of_month(self, year, month):
                """ Work out the last day of the month """
                last_days = [31, 30, 29, 28, 27]
                for i in last_days:
                                try:
                                                end = dtime(year, month, i)     
        
                                except ValueError:
                                                continue
                                else:
                                                return i
                return None


        def get_yday(self, year, month, day):
                date = self.get_specific_date(year,month,day)
                date_tt = date.timetuple()
                year_of_day = date_tt.tm_yday
                return year_of_day
                
        def get_all_date_element_data(self):
                year = self.entry_year.get_text()
                month = self.entry_month.get_text()
                day = self.entry_day.get_text()
                ddplus = self.entry_due_date_plus.get_text()
                
                year, month, day, ddplus = 
self.validate_date_element_data(year, month, day, ddplus)
                return year, month, day, ddplus

        def get_all_date_element_data_from_hist(self):
                year_hist = self.entry_history[1][0]
                month_hist = self.entry_history[2][0]
                day_hist = self.entry_history[3][0]
                ddplus_hist = self.entry_history[4][0]

                return year_hist, month_hist, day_hist, ddplus_hist

        def get_format(self):
                #get the dates.list config file
                file = ConfigManager().get_config_file('<profile>/dates.list')
                format = "[d: %Y-%m-%d]" # This is the given standard format
                # look for a due date format in dates.list file
                for line in file.readlines():
                        line = line.strip()
                        if not line.startswith("[d:"):
                                continue
                        if format in line:
                                return format
                        else:
                                format = line
                return format

        def get_iter_pos(self):
                self.top_x, self.top_y = 
self.page_view.get_toplevel().get_position()
                self.iter_location = 
self.page_view.get_iter_location(self.cursor)
                self.mark_x, self.mark_y = self.iter_location.x, 
self.iter_location.y + self.iter_location.height
                #calculate buffer-coordinates to coordinates within the window
                self.win_location = 
self.page_view.buffer_to_window_coords(gtk.TEXT_WINDOW_WIDGET,
                                                                                
                                 int(self.mark_x), int(self.mark_y))
                #now find the right window --> Editor Window and the right pos 
on screen
                self.win = self.page_view.get_window(gtk.TEXT_WINDOW_WIDGET);
                self.view_pos = self.win.get_position()
                self.xx = self.win_location[0] + self.view_pos[0]
                self.yy = self.win_location[1] + self.view_pos[1] + 
self.iter_location.height
                self.x = self.top_x + self.xx
                self.y = self.top_y + self.yy
                return (self.x, self.y, self.iter_location.height)

        def del_due_date_at_line(self, buffer):
                try:
                        self.due_date_begin_iter, self.due_date_end_iter = 
self.get_due_date_at_line(buffer)
                        buffer.delete(self.due_date_begin_iter, 
self.due_date_end_iter)
                        buffer.place_cursor(self.due_date_begin_iter)
                        self.cursor = 
self.buf.get_iter_at_mark(self.buf.get_insert())
                except TypeError:
                        return

        def get_due_date_at_line(self, buffer):
                self.due_date_begin_iter = False
                self.due_date_end_iter = False

                # in order to get a due date entry no matter where the cursor 
was placed within the line,
                # move the cursor to the end of the line and look for [d:
                cursor = buffer.get_iter_at_mark(buffer.get_insert())

                # if there is nothing but a linefeed, just return, nothing to 
check
                if cursor.ends_line() and cursor.starts_line():
                        return
                
                # this is a workaround to get the start of the line...first 
move to end of previous line()
                cursor.backward_line()
                # then move to next line(), which positions at the start of the 
line ...
                cursor.forward_line()
                # now place the cursor at start of line
                buffer.place_cursor(cursor)
                line_start = buffer.get_iter_at_mark(buffer.get_insert())

                # move to end of line and then start search backwards.
                cursor.forward_to_line_end()
                buffer.place_cursor(cursor)
                        
                self.due_date_begin_iter = cursor.backward_search("[d:", 
gtk.TEXT_SEARCH_TEXT_ONLY, limit = line_start)
                if self.due_date_begin_iter:
                        # this might lead to a selection of more than wanted if 
there is a ] somewhere after the due date string.
                        # leaving it this way right now as this is not very 
likely to happen.
                        self.due_date_end_iter = cursor.backward_search("]", 
gtk.TEXT_SEARCH_TEXT_ONLY, limit = line_start)

                try:
                        buffer.apply_tag(self.text_tag, 
self.due_date_begin_iter[0], self.due_date_end_iter[1])
                        exist_due_date_txt = 
buffer.get_slice(self.due_date_begin_iter[0], self.due_date_end_iter[1])
                        self.exist_due_date =  
self.put_txt_into_date(exist_due_date_txt)


                except (UnboundLocalError, AttributeError, ValueError, 
TypeError):
                        buffer.place_cursor(self.cursor_orig)   
                        return self.due_date_begin_iter, self.due_date_end_iter
                
                # put the cursor to the end of the existing due date entry, so 
the entry or calendar can be shown there
                self.cursor = self.due_date_end_iter[1]
                return self.due_date_begin_iter[0], self.due_date_end_iter[1]

        def put_txt_into_date(self, date_txt):
                plain_format = self.format.strip("[d: ").strip("]")
                plain_due_date = date_txt.strip("[d: ").strip("]")
                exist_due_date = datetime.datetime.strptime(plain_due_date, 
plain_format).date()
                return exist_due_date


class TaskComment():

        def __init__(self, window, plugin_preferences):
                self.plugin_prefs = plugin_preferences
                self.window = window


        def add(self):
                
                current_date = zim_datetime.date.today()

                # get the user's task comment format
                format = self.get_format()
                self.task_comment_with_date = zim_datetime.strftime(format, 
current_date)


                # get the text buffer
                self.buffer = self.window.pageview.view.get_buffer()

                current_style = self.buffer.get_textstyle()

                line_number = self.buffer.get_insert_iter().get_line()
                indent_level = self.buffer.get_indent(line_number)
                new_line = line_number + 1

                # create new sub-bullet
                iter = self.buffer.get_iter_at_line(line_number)
                iter.forward_to_line_end()
                self.buffer.place_cursor(iter)                                  
                        # works also if cursor is placed inside a tag
                self.buffer.insert_at_cursor("\n")      
                #buffer.insert(iter, "\n")                                      
                        # doesn't work if cursor is placed inside a tag
                self.buffer.set_indent(new_line, indent_level + 1)
                self.buffer.set_bullet(new_line, '*') # set bullet type

                # place cursor in right spot
                iter = self.buffer.get_iter_at_line(new_line)
                iter.forward_to_line_end()
                self.buffer.place_cursor(iter)

                # get the cursor position
                cursor = self.buffer.get_insert_iter()
                ## add text at cursor position


                self.buffer.set_textstyle('sup')
                self.buffer.insert(cursor, self.task_comment_with_date)
                self.buffer.set_textstyle(current_style)
                # to make the current style work...
                self.buffer.insert(cursor, "> ")

                #now move the cursor within the italic field for easier typing
                if self.plugin_prefs['task_comment_italic']:
                        backward_chars_no = 2
                else:
                        backward_chars_no = 0
                cursor.backward_chars(backward_chars_no)
                self.buffer.place_cursor(cursor)

                # does this get the contacts page refreshed?
                page = self.window.pageview.get_page()


        def get_format(self):
                format = self.set_format(date_string = 
self.plugin_prefs['task_comment_date_format']) # this is the format set in 
preferences
                if self.plugin_prefs['task_comment_dateslist']:
                        # get the dates.list config file
                        file = 
ConfigManager().get_config_file('<profile>/dates.list')
                        for line in file.readlines():
                                line = line.strip()
                                if not line.startswith("[d:"):
                                        continue
                                # lazy approach to remove ] from the end of the 
date string
                                line = re.sub(']', '',line)
                                # and then strip [d: from format in dates.list 
and build format string set in preferences
                                format = self.set_format(date_string = 
line.lstrip("[d: ").lstrip("]")) 
                return format

        def set_format(self, date_string):
                if self.plugin_prefs['task_comment_time']:
                        if self.plugin_prefs['task_comment_time_format']:
                                time_format = 
self.plugin_prefs['task_comment_time_format']
                        else:
                                time_format = '%H:%M'
                        current_time = 
zim_datetime.datetime.now().strftime(time_format)
                        date_string = date_string + "][" + current_time
                if self.plugin_prefs['task_comment_bold']:
                        bracket_open = "**"
                        bracket_close = "]**"
                        format = bracket_open + 
self.plugin_prefs['task_comment_string'] + ": [" + date_string + bracket_close
                else:
                        bracket_open = ""
                        bracket_close = "]"
                        format = bracket_open + 
self.plugin_prefs['task_comment_string'] + ": [" + date_string + bracket_close

                if self.plugin_prefs['task_comment_italic']:
                        format = bracket_open + 
self.plugin_prefs['task_comment_string'] + ": [" + date_string + bracket_close 
+ "// //"        
                return format


class TasksParser(Visitor):
        '''Parse tasks from a parsetree'''

        def __init__(self, task_label_re, next_label_re, nonactionable_tags, 
all_checkboxes, defaultdate, preferences):
                self.task_label_re = task_label_re
                self.next_label_re = next_label_re
                self.nonactionable_tags = nonactionable_tags
                self.all_checkboxes = all_checkboxes

                defaults = (True, True, 0, defaultdate or _NO_DATE, set(), None)
                        # (open, actionable, prio, due, tags, description)

                self._tasks = []
                self._tasks_comments = []
                self.preferences = preferences

                self._stack = [(-1, defaults, self._tasks)]
                        # Stack for parsed tasks with tuples like (level, task, 
children)
                        # We need to include the list level in the stack 
because we can
                        # have mixed bullet lists with checkboxes, so task 
nesting is
                        # not the same as list nesting

                # Parsing state
                self._text = [] # buffer with pieces of text
                self._depth = 0 # nesting depth for list items
                self._last_node = (None, None) # (tag, attrib) of last item 
seen by start()
                self._intasklist = False # True if we are in a tasklist with a 
header
                self._tasklist_tags = None # global tags from the tasklist 
header


        def parse(self, parsetree):
                #~ filter = TreeFilter(
                        #~ TextCollectorFilter(self),
                        #~ tags=['p', 'ul', 'ol', 'li'],
                        #~ exclude=['strike']
                #~ )
                parsetree.visit(self)

        def get_tasks(self):
                '''Get the tasks that were collected by visiting the tree
                @returns: nested list of tasks, each task is given as a 2-tuple,
                1st item is a tuple with following properties:
                C{(open, actionable, prio, due, description)},
                2nd item is a list of child tasks (if any).
                '''
                return self._tasks


        def start(self, tag, attrib):
                if tag == STRIKE:
                        raise VisitorSkip # skip this node
                elif tag in (PARAGRAPH, NUMBEREDLIST, BULLETLIST, LISTITEM):
                        if tag == PARAGRAPH:
                                self._intasklist = False

                        # Parse previous chuck of text (para level text)
                        if self._text:
                                if tag in (NUMBEREDLIST, BULLETLIST) \
                                and self._last_node[0] == PARAGRAPH \
                                and self._check_para_start(self._text):
                                        pass
                                else:
                                        self._parse_para_text(self._text)

                                self._text = [] # flush

                        # Update parser state
                        if tag in (NUMBEREDLIST, BULLETLIST):
                                self._depth += 1
                        elif tag == LISTITEM:
                                self._pop_stack() # see comment in end()
                        self._last_node = (tag, attrib)
                else:
                        pass # do nothing for other tags (we still get the text)

        def text(self, text):
                self._text.append(text)

        def end(self, tag):
                if tag == PARAGRAPH:
                        if self._text:
                                self._parse_para_text(self._text)
                                self._text = [] # flush
                        self._depth = 0
                        self._pop_stack()
                elif tag in (NUMBEREDLIST, BULLETLIST):
                        self._depth -= 1
                        self._pop_stack()
                elif tag == LISTITEM:
                        if self._text:
                                attrib = self._last_node[1]
                                self._parse_list_item(attrib, self._text)
                                self._text = [] # flush
                        # Don't pop here, next item may be child
                        # Instead pop when next item opens
                else:
                        pass # do nothing for other tags

        def _pop_stack(self):
                # Drop stack to current level
                assert self._depth >= 0, 'BUG: stack count corrupted'
                level = self._depth
                if level > 0:
                        level -= 1 # first list level should be same as level 
of line items in para
                while self._stack[-1][0] >= level:
                        self._stack.pop()

        def _check_para_start(self, strings):
                # Check first line for task list header
                # SHould look like "TODO @foo @bar:"
                # FIXME shouldn't we depend on tag elements in the tree ??
                line = u''.join(strings).strip()

                if not '\n' in line \
                and self._matches_label(line):
                        words = line.strip(':').split()
                        words.pop(0) # label
                        if all(w.startswith('@') for w in words):
                                self._intasklist = True
                                self._tasklist_tags = set(w.strip('@') for w in 
words)
                        else:
                                self._intasklist = False
                else:
                        self._intasklist = False

                return self._intasklist

        def _parse_para_text(self, strings):
                # Paragraph text to be parsed - just look for lines with label
                for line in u''.join(strings).splitlines():
                        if self._matches_label(line):
                                self._parse_task(line)

        def _parse_list_item(self, attrib, text):
                # List item to parse - check bullet, then match label
                bullet = attrib.get('bullet')
                line = u''.join(text)
                
                # get comment
                if self.preferences['task_comment_string'] in line:
                        parent_level, parent, parent_children = self._stack[-1]
                        task_comment = self._parse_comment(line)
                        try:
                                parent[6].append(task_comment)
                        except IndexError:
                                pass
                if (
                        bullet in (UNCHECKED_BOX, CHECKED_BOX, XCHECKED_BOX)
                        and (self._intasklist or self.all_checkboxes)
                ):
                        open = (bullet == UNCHECKED_BOX)
                        # added bullet to params to identify if a task is 
ticked for the tick box
                        self._parse_task(line, bullet, open=open)
                elif self._matches_label(line):
                        self._parse_task(line)
                
        def _parse_comment(self, comment):
                c_string_len_formatting = 2
                c_string_len = len(self.preferences['task_comment_string']) + 
c_string_len_formatting
                comment_strip = comment[c_string_len:] + "\n"
                return comment_strip

        def _matches_label(self, line):
                return self.task_label_re and self.task_label_re.match(line)

        def _matches_next_label(self, line):
                return self.next_label_re and self.next_label_re.match(line)

        def _parse_t_date(self, text):
                return text.partition("[x:")[2].partition("]")[0]
                pass

        def _parse_task(self, text, bullet=None, open=True):
                level = self._depth
                if level > 0:
                        level -= 1 # first list level should be same as level 
of line items in para

                parent_level, parent, parent_children = self._stack[-1]

                
                # Get prio
                prio = text.count('!')
                if prio == 0:
                        prio = parent[2] # default to parent prio
                                
                # Get due date
                due = _NO_DATE
                datematch = _date_re.search(text) # first match
                if datematch:
                        date = parse_date(datematch.group(0))
                        if date:
                                due = '%04i-%02i-%02i' % date # (y, m, d)

                if due == _NO_DATE:
                        due = parent[3] # default to parent date (or default 
for root)

                # Find tags
                tags = set(_tag_re.findall(text))
                if self._intasklist and self._tasklist_tags:
                        tags |= self._tasklist_tags
                tags |= parent[4] # add parent tags

                # Check actionable
                if not parent[1]: # default parent not actionable
                        actionable = False
                elif any(t.lower() in self.nonactionable_tags for t in tags):
                        actionable = False
                elif self._matches_next_label(text) and parent_children:
                        previous = parent_children[-1][0]
                        actionable = not previous[0] # previous task not open
                else:
                        actionable = True

                # Parents are not closed if it has open child items
                if self._depth > 0 and open:
                        for l, t, c in self._stack[1:]:
                                t[0] = True
                
                # quick way to find out if task is already ticked (is needed to 
display already ticked parent tasks, while child is unticked)

                tickdate = ""
                if bullet in (CHECKED_BOX, XCHECKED_BOX):
                        tickmark = True 
                        # Get ticked date (if pageview adds tickdate to task 
within text)
                        t_datematch = _tdate_re.search(text) # first match
                        if t_datematch:
                                tickdate = 
self._parse_t_date(t_datematch.group(0))
                else:
                        tickmark = False


                # And finally add to stack
                comment=[]

                # adding text as this contains the whole task string
                task = [open, actionable, prio, due, tags, text, comment, 
tickmark, tickdate, text]                                             
                #task = [open, actionable, prio, due, tags, text, comment, 
tickmark,]                                           
                children = []
                task[6] = []    # flush old comments
                parent_children.append((task, children))
                if self._depth > 0: # (don't add paragraph level items to the 
stack)
                        self._stack.append((level, task, children))




class TaskListDialog(Dialog):

        def __init__(self, window, index_ext, preferences):

                screen_width = gtk.gdk.screen_width()
                screen_height = gtk.gdk.screen_height()
                TASKLISTDIALOG_WIDTH = screen_width / 4
                TASKLISTDIALOG_HEIGHT = screen_height / 4

                Dialog.__init__(self, window, _('Task List'), # T: dialog title
                        buttons=gtk.BUTTONS_CLOSE, help=':Plugins:Task List',
                        defaultwindowsize=(TASKLISTDIALOG_WIDTH, 
TASKLISTDIALOG_HEIGHT) )
                self.preferences = preferences
                self.index_ext = index_ext

                # Divider between Tasks and Tasks History
                # BUG? The position is not preserved
                self.vpane = VPaned()
                self.uistate.setdefault('vpane_pos', TASKLISTDIALOG_HEIGHT/2)
                self.vpane.set_position(self.uistate['vpane_pos'])
                
                # Divider between Tags and Tasks
                self.hpane = HPaned()
                self.uistate.setdefault('hpane_pos', TASKLISTDIALOG_WIDTH/2)
                self.hpane.set_position(self.uistate['hpane_pos'])


                self.hpane.add2(self.vpane)
                
                vbox_task_top = gtk.VBox(spacing = 5)
                vbox_task_bottom = gtk.VBox(spacing = 5)
                
                self.vpane.add1(vbox_task_top)
                self.vpane.add2(vbox_task_bottom)

                # Entries hbox
                hbox_entries = gtk.HBox(spacing=5)

                self.vbox.pack_start(hbox_entries, False)
                # now pack the hpane to vbox to have it below the entries
                self.vbox.pack_end(self.hpane, True)
                
                # Task list
                self.uistate.setdefault('only_show_act', False)
                self.uistate.setdefault('tick_all', False)
                opener = window.get_resource_opener()
                self.task_list = TaskListTreeView(
                        window, self.index_ext, opener,
                        #tick_all=self.uistate['tick_all'],
                        filter_actionable=self.uistate['only_show_act'],
                        tag_by_page=preferences['tag_by_page'],
                        use_workweek=preferences['use_workweek'],
                )
                self.task_list.set_headers_visible(True) # Fix for maemo
                
self.task_list.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)

                vbox_task_top.pack_end(ScrolledWindow(self.task_list), True)

                # Task list history
                if self.preferences['show_history']:
                        hbox_hist = gtk.HBox(spacing=10)
                        vbox_task_bottom.pack_start(gtk.Label(_('History of 
ticked tasks')), False) # T: Input label

                        self.hpane_hist = HPaned()
                        self.uistate.setdefault('hpane_pos', 75)
                        self.hpane_hist.set_position(self.uistate['hpane_pos'])

                # Ticked Task history list 
                if self.preferences['show_history']:
                        self.task_list_hist = TaskListHistoryTreeView(
                                window, self.index_ext, opener,
                                filter_actionable=self.uistate['only_show_act'],
                                tag_by_page=preferences['tag_by_page'],
                                use_workweek=preferences['use_workweek'],
                        )
                        self.task_list_hist.set_headers_visible(True) # Fix for 
maemo

                        
vbox_task_bottom.pack_end(ScrolledWindow(self.task_list_hist), True)
                        
self.task_list_hist.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)

                # Tag auto completion
                hbox_entries.pack_start(gtk.Label(_('Tags')+': '), False) # T: 
Input label
                tag_entry = InputEntry()
                hbox_entries.pack_start(tag_entry, False)
                tag_entry.set_icon_to_clear()

                # Tag list added 'self.tag_entry' to pass for auto completion 
entry
                if self.preferences['show_history']:
                        self.tag_list = TagListTreeView(self.index_ext, 
self.task_list, tag_entry, self.preferences, self.task_list_hist)
                else:
                        self.tag_list = TagListTreeView(self.index_ext, 
self.task_list, tag_entry, self.preferences)
                self.hpane.add1(ScrolledWindow(self.tag_list))

                # clear filter when icon is pressed
                def clear_entry(entry, iconpos, event):
                        entry.set_text('')
                        self.task_list.set_tag_filter(None, None)
                        self.task_list_hist.set_tag_filter(None, None)

                tag_entry.connect("icon-press", clear_entry)

                # Filter input
                hbox_entries.pack_start(gtk.Label(_('Filter')+': '), False) # 
T: Input label
                filter_entry = InputEntry()
                filter_entry.set_icon_to_clear()
                hbox_entries.pack_start(filter_entry, False)
                
                filter_cb = DelayedCallback(500,
                        lambda o: 
self.task_list.set_filter(filter_entry.get_text()))
                filter_entry.connect('changed', filter_cb)

                if self.preferences['show_history']:
                        filter_cb_hist = DelayedCallback(500,
                                lambda o: 
self.task_list_hist.set_filter(filter_entry.get_text()))
                        filter_entry.connect('changed', filter_cb_hist)
                

                # Dropdown with options - TODO
                #~ menu = gtk.Menu()
                #~ showtree = gtk.CheckMenuItem(_('Show _Tree')) # T: menu item 
in options menu
                #~ menu.append(showtree)
                #~ menu.append(gtk.SeparatorMenuItem())
                #~ showall = gtk.RadioMenuItem(None, _('Show _All Items')) # T: 
menu item in options menu
                #~ showopen = gtk.RadioMenuItem(showall, _('Show _Open Items')) 
# T: menu item in options menu
                #~ menu.append(showall)
                #~ menu.append(showopen)
                #~ menubutton = MenuButton(_('_Options'), menu) # T: Button 
label
                #~ hbox.pack_start(menubutton, False)


                # Tick all tasks
                self.tick_all_toggle = gtk.CheckButton(_('Tick all tasks'))
                # Don't want to mess around to get a consistent tick state 
depending on if list is empty or not
                self.tick_all_toggle.set_inconsistent(True)
                self.tick_all_toggle.connect('toggled', lambda o: 
self.task_list._toggle_all_tasks(o.get_active()))

                vbox_task_top.pack_start(self.tick_all_toggle, False)

                # Untick all tasks
                self.untick_all_toggle = gtk.CheckButton(_('Untick all tasks'))
                self.untick_all_toggle.set_inconsistent(True)
                untick_toggle_handler_id = 
self.untick_all_toggle.connect('toggled', lambda o: 
self.task_list_hist._toggle_all_tasks(o.get_active()))

                vbox_task_bottom.pack_start(self.untick_all_toggle, False)


                self.act_toggle = gtk.CheckButton(_('Only Show Actionable 
Tasks'))
                        # T: Checkbox in task list
                self.act_toggle.set_active(self.uistate['only_show_act'])
                self.act_toggle.connect('toggled', lambda o: 
self.task_list.set_filter_actionable(o.get_active()))
                hbox_entries.pack_start(self.act_toggle, False)

                # Statistics label
                self.statistics_label = gtk.Label()
                hbox_entries.pack_end(self.statistics_label, False)

                


                def set_statistics():
                        total, stats = self.task_list.get_statistics()
                        text = ngettext('%i open item', '%i open items', total) 
% total
                                # T: Label for statistics in Task List, %i is 
the number of tasks
                        text += ' (' + '/'.join(map(str, stats)) + ')'
                        self.statistics_label.set_text(text)

                set_statistics()

                def on_tasklist_changed(o):
                        self.task_list.refresh()
                        self.tag_list.refresh(self.task_list)
                        self.task_list_hist.refresh()
                        set_statistics()

                callback = DelayedCallback(10, on_tasklist_changed)
                        # Don't really care about the delay, but want to
                        # make it less blocking - should be async preferably
                        # now it is at least on idle
                self.connectto(index_ext, 'tasklist-changed', callback)

        def do_response(self, response):
                self.uistate['hpane_pos'] = self.hpane.get_position()
                self.uistate['only_show_act'] = self.act_toggle.get_active()
                #self.uistate['tick_all'] = self.tick_all_toggle.get_active()
                Dialog.do_response(self, response)


class TagListTreeView(SingleClickTreeView):
        '''TreeView with a single column 'Tags' which shows all tags available
        in a TaskListTreeView. Selecting a tag will filter the task list to
        only show tasks with that tag.
        '''

        _type_separator = 0
        _type_label = 1
        _type_tag = 2
        _type_untagged = 3

        def __init__(self, index_ext, task_list, tag_entry, preferences, 
task_list_hist=None):
                model = gtk.ListStore(str, int, int, int) # tag name, number of 
tasks, type, weight
                SingleClickTreeView.__init__(self, model)
                self.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
                self.index_ext = index_ext
                self.task_list = task_list

                # depending of preferences
                if not task_list_hist:
                        self.task_list_hist = self.task_list
                else:
                        self.task_list_hist = task_list_hist
                self.preferences = preferences
                self.tag_entry = tag_entry

                # add additional entry with auto completion for tags within 
tasks
                self.completion = gtk.EntryCompletion()
                # it's better to not have a popup for a single match here
                self.completion.set_inline_completion(True)
                self.completion.set_model(model)
                self.completion.set_popup_single_match(False)
                self.tag_entry.set_completion(self.completion)
                self.completion.set_text_column(0)

                # if user has selected a tag from the popup list
                self.completion.connect('match-selected', self.match_selected)

                #if only remaining match is shown and RETURN is hit
                self.tag_entry.connect('activate', self.activate_return)

                column = gtk.TreeViewColumn(_('Tags'))
                        # T: Column header for tag list in Task List dialog
                self.append_column(column)

                cr1 = gtk.CellRendererText()
                cr1.set_property('ellipsize', pango.ELLIPSIZE_END)
                column.pack_start(cr1, True)
                column.set_attributes(cr1, text=0, weight=3) # tag name, weight

                cr2 = self.get_cell_renderer_number_of_items()
                column.pack_start(cr2, False)
                column.set_attributes(cr2, text=1) # number of tasks

                self.set_row_separator_func(lambda m, i: m[i][2] == 
self._type_separator)

                self._block_selection_change = False
                self.get_selection().connect('changed', 
self.on_selection_changed)


                # get the standard_tag from preferences and set entry
                standard_tag = self.preferences['standard_tag']
                if standard_tag:
                        self.tag_entry.set_text(standard_tag)
                        self.activate_return(self.tag_entry)

                
                self.refresh(task_list)


        def match_selected(self, completion, model, iter):
                tags = []
                tags.append(model[iter][0])
                # there can be still a additional filter set
                labels = self.get_labels()
                self.task_list.set_tag_filter(tags, labels)
                self.task_list_hist.set_tag_filter(tags, labels)

        def activate_return(self, entry):
                # if return is hit without anything, don't show untagged tasks 
accidentally
                # TODO: This is another 'hidden' functionality for an easy way 
to show them
                # but it needs to be implemented in a proper way

                if entry.get_text() == "":
                        return
                tags = []
                tag_entered = entry.get_text()

                tags.append(tag_entered)
                # there can be still a additional filter set
                labels = self.get_labels()
                self.task_list.set_tag_filter(tags, labels)
                self.task_list_hist.set_tag_filter(tags, labels)
                tree_selection = self.get_selection()

                # get the path for the tag from the tag list to highlight in 
list
                model = self.get_model()
                path = 0
                for element in model:
                        if tag_entered == element[0]:
                                break
                        path += 1
                tree_selection.select_path(path)
                

        def get_tags(self):
                '''Returns current selected tags, or None for all tags'''
                tags = []
                for row in self._get_selected():
                        if row[2] == self._type_tag:
                                tags.append(row[0].decode('utf-8'))
                        elif row[2] == self._type_untagged:
                                tags.append(_NO_TAGS)
                return tags or None

        def get_labels(self):
                '''Returns current selected labels'''
                labels = []
                for row in self._get_selected():
                        if row[2] == self._type_label:
                                labels.append(row[0].decode('utf-8'))
                return labels or None

        def _get_selected(self):
                selection = self.get_selection()
                if selection:
                        model, paths = selection.get_selected_rows()
                        if not paths or (0,) in paths:
                                return []
                        else:
                                self.tag_entry.clear()
                                return [model[path] for path in paths]
                else:
                        return []

        def refresh(self, task_list):
                self._block_selection_change = True
                selected = [(row[0], row[2]) for row in self._get_selected()] # 
remember name and type

                # Rebuild model
                model = self.get_model()
                if model is None: return
                model.clear()

                n_all = self.task_list.get_n_tasks()
                model.append((_('All Tasks'), n_all, self._type_label, 
pango.WEIGHT_BOLD)) # T: "tag" for showing all tasks

                used_labels = self.task_list.get_labels()
                for label in self.index_ext.task_labels: # explicitly keep 
sorting from preferences
                        if label in used_labels \
                        and label != self.index_ext.next_label:
                                model.append((label, used_labels[label], 
self._type_label, pango.WEIGHT_BOLD))

                tags = self.task_list.get_tags()
                if _NO_TAGS in tags:
                        n_untagged = tags.pop(_NO_TAGS)
                        model.append((_('Untagged'), n_untagged, 
self._type_untagged, pango.WEIGHT_NORMAL))
                        # T: label in tasklist plugins for tasks without a tag

                model.append(('', 0, self._type_separator, 0)) # separator

                for tag in natural_sorted(tags):
                        model.append((tag, tags[tag], self._type_tag, 
pango.WEIGHT_NORMAL))

                # Restore selection
                def reselect(model, path, iter):
                        row = model[path]
                        name_type = (row[0], row[2])
                        if name_type in selected:
                                self.get_selection().select_iter(iter)

                if selected:
                        model.foreach(reselect)
                self._block_selection_change = False

        def on_selection_changed(self, selection):
                if not self._block_selection_change:
                        tags = self.get_tags()
                        labels = self.get_labels()
                        self.task_list.set_tag_filter(tags, labels)
                        self.task_list_hist.set_tag_filter(tags, labels)

HIGH_COLOR = '#EF5151' # red (derived from Tango style guide - #EF2929)
MEDIUM_COLOR = '#FCB956' # orange ("idem" - #FCAF3E)
ALERT_COLOR = '#FCEB65' # yellow ("idem" - #FCE94F)
# FIXME: should these be configurable ?


class TaskListTreeView(BrowserTreeView):

        VIS_COL = 0 # visible
        TICKED_COL = 1                  # additions
        PRIO_COL = 2
        TASK_COL = 3
        DATE_COL = 4
        PAGE_COL = 5
        TAGS0_COL = 6                   # additions
        TASK_COMMENT_COL = 7    # additions
        ACT_COL = 8 # actionable
        OPEN_COL = 9 # item not closed
        TASKID_COL = 10
        TAGS_COL = 11
        TASK0_COL = 12


        #VIS_COL = 0 # visible
        #PRIO_COL = 1
        #TASK_COL = 2
        #DATE_COL = 3
        #PAGE_COL = 4
        #ACT_COL = 5 # actionable
        #OPEN_COL = 6 # item not closed
        #TASKID_COL = 7
        #TAGS_COL = 8

        def __init__(self, window, index_ext, opener, filter_actionable=False, 
tag_by_page=False, use_workweek=False):
                self.real_model = gtk.TreeStore(bool, bool, int, str, str, str, 
str, str, bool, bool, int, object, str)
                        # VIS_COL, TICKED_COL, PRIO_COL, TASK_COL, DATE_COL, 
TAGS0_COL, TASK_COMMENT_COL, PAGE_COL, ACT_COL, OPEN_COL, TASKID_COL, TAGS_COL, 
TASK0_COL
                model = self.real_model.filter_new()
                model.set_visible_column(self.VIS_COL)
                model = gtk.TreeModelSort(model)
                model.set_sort_column_id(self.PRIO_COL, gtk.SORT_DESCENDING)
                BrowserTreeView.__init__(self, model)
                screen_width = gtk.gdk.screen_width()
                screen_height = gtk.gdk.screen_height()

                self.index_ext = index_ext
                self.opener = opener
                self.filter = None
                self.tag_filter = None
                self.label_filter = None
                self.filter_actionable = filter_actionable
                #self.tick_all = tick_all
                self.tag_by_page = tag_by_page
                self._tags = {}
                self._labels = {}
                self.win = window
                column_width = 150


                # Wrap text in column on resize
                def set_column_width(column, width, renderer, pan = True):
                        column_width = column.get_width()
                        renderer.props.wrap_width = column_width
                        if pan:
                                renderer.props.wrap_mode = pango.WRAP_WORD
                        else:
                                renderer.props.wrap_mode = gtk.WRAP_WORD

                # Add some rendering for the task tick box
                cell_renderer = gtk.CellRendererToggle()
                cell_renderer.set_property('activatable', True)
                column = gtk.TreeViewColumn('Tick', cell_renderer)
                column.set_sort_column_id(self.TICKED_COL)
                column.set_resizable(False)
                column.add_attribute(cell_renderer, "active", self.TICKED_COL)
                self.append_column(column)

                # Add some rendering for the Prio column
                def render_prio(col, cell, model, i):
                        prio = model.get_value(i, self.PRIO_COL)
                        cell.set_property('text', str(prio))
                        if prio >= 3: color = HIGH_COLOR
                        elif prio == 2: color = MEDIUM_COLOR
                        elif prio == 1: color = ALERT_COLOR
                        else: color = None
                        cell.set_property('cell-background', color)

                cell_renderer = gtk.CellRendererText()
                #~ column = gtk.TreeViewColumn(_('Prio'), cell_renderer)
                        # T: Column header Task List dialog
                column = gtk.TreeViewColumn(' ! ', cell_renderer)
                column.set_cell_data_func(cell_renderer, render_prio)
                column.set_sort_column_id(self.PRIO_COL)
                column.set_resizable(True)
                self.append_column(column)

                # Rendering for task description column
                cell_renderer = gtk.CellRendererText()
                cell_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
                column = gtk.TreeViewColumn(_('Task'), cell_renderer, 
markup=self.TASK_COL)
                                # T: Column header Task List dialog
                column.set_resizable(True)
                column.set_sort_column_id(self.TASK_COL)
                #column.set_expand(True)
                if ui_environment['platform'] == 'maemo':
                        column.set_min_width(int(screen_width*0.05)) # don't 
let this column get too small
                else:
                        column.set_min_width(int(screen_width*0.05)) # don't 
let this column get too small
                
                self.append_column(column)
                self.set_expander_column(column)

                if gtk.gtk_version >= (2, 12) \
                and gtk.pygtk_version >= (2, 12):
                        self.set_tooltip_column(self.TASK_COL)

                # Rendering of the Date column
                day_of_week = datetime.date.today().isoweekday()
                if use_workweek and day_of_week == 4:
                        # Today is Thursday - 2nd day ahead is after the weekend
                        delta1, delta2 = 1, 3
                elif use_workweek and day_of_week == 5:
                        # Today is Friday - next day ahead is after the weekend
                        delta1, delta2 = 3, 4
                else:
                        delta1, delta2 = 1, 2

                today    = str( datetime.date.today() )
                tomorrow = str( datetime.date.today() + 
datetime.timedelta(days=delta1))
                dayafter = str( datetime.date.today() + 
datetime.timedelta(days=delta2))
                def render_date(col, cell, model, i):
                        date = model.get_value(i, self.DATE_COL)
                        if date == _NO_DATE:
                                cell.set_property('text', '')
                        else:
                                cell.set_property('text', date)
                                # TODO allow strftime here

                        if date <= today: color = HIGH_COLOR
                        elif date <= tomorrow: color = MEDIUM_COLOR
                        elif date <= dayafter: color = ALERT_COLOR
                                # "<=" because tomorrow and/or dayafter can be 
after the weekend
                        else: color = None
                        cell.set_property('cell-background', color)

                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Date'), cell_renderer)
                        # T: Column header Task List dialog
                column.set_cell_data_func(cell_renderer, render_date)
                column.set_sort_column_id(self.DATE_COL)
                self.append_column(column)

                # Rendering for tag column
                cell_renderer = gtk.CellRendererText()

                cell_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
                column = gtk.TreeViewColumn(_('Tags'), cell_renderer, 
markup=self.TAGS0_COL)
                column.set_resizable(True)
                column.set_sort_column_id(self.TAGS0_COL)
                column.set_min_width(int(screen_width*0.05))

                column.connect_after("notify::width", set_column_width, 
cell_renderer)
                self.append_column(column)

                # Rendering for task comment column
                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Comment'), cell_renderer, 
text=self.TASK_COMMENT_COL)
                column.set_resizable(True)
                column.set_sort_column_id(self.TASK_COMMENT_COL)
                #column.set_min_width(column_width)
                cell_renderer.props.wrap_width = int(screen_width*0.05)
                cell_renderer.props.wrap_mode = pango.WRAP_WORD

                column.connect_after("notify::width", set_column_width, 
cell_renderer)
                self.append_column(column)

                # Rendering for page name column
                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Page'), cell_renderer, 
text=self.PAGE_COL)
                                # T: Column header Task List dialog
                column.set_sort_column_id(self.PAGE_COL)
                self.append_column(column)

                # Finalize
                self.refresh()

                # HACK because we can not register ourselves :S
                self.connect('row_activated', self.__class__.do_row_activated)

        def refresh(self):
                '''Refresh the model based on index data'''
                # Update data
                self._clear()
                self._append_tasks(None, None, {})

                # Make tags case insensitive
                tags = sorted((t.lower(), t) for t in self._tags)
                        # tuple sorting will sort ("foo", "Foo") before ("foo", 
"foo"),
                        # but ("bar", ..) before ("foo", ..)
                prev = ('', '')
                for tag in tags:
                        if tag[0] == prev[0]:
                                self._tags[prev[1]] += self._tags[tag[1]]
                                self._tags.pop(tag[1])
                        prev = tag

                # Set view
                self._eval_filter() # keep current selection
                self.expand_all()

        def _clear(self):
                self.real_model.clear() # flush
                self._tags = {}
                self._labels = {}

        def _append_tasks(self, task, iter, path_cache):
                for row in self.index_ext.list_tasks(task):
                        if not row['open']:
                                continue # Only include open items for now

                        if row['source'] not in path_cache:
                                path = self.index_ext.get_path(row)
                                if path is None:
                                        # Be robust for glitches - filter these 
out
                                        continue
                                else:
                                        path_cache[row['source']] = path

                        path = path_cache[row['source']]

                        # Update labels
                        for label in 
self.index_ext.task_label_re.findall(row['description']):
                                self._labels[label] = self._labels.get(label, 
0) + 1

                        # Update tag count
                        tags = row['tags'].split(',')
                        # Want to show tags one below the other instead of 
separated by comma, so creating tags0 instead
                        tags0 = ""      
                        for tag in tags:                                        
                
                                tags0 += "<span color=\"#ce5c00\">" + tag + 
"</span>" + "\n"

                        if self.tag_by_page:
                                tags = tags + path.parts

                        if tags:
                                for tag in tags:
                                        self._tags[tag] = self._tags.get(tag, 
0) + 1
                        else:
                                self._tags[_NO_TAGS] = self._tags.get(_NO_TAGS, 
0) + 1


                        # Format description
                        task = _date_re.sub('', row['description'], count=1)
                        task = _tdate_re.sub('', task, count=1)
                        task = re.sub('\s*!+\s*', ' ', task) # get rid of 
exclamation marks
                        task = self.index_ext.next_label_re.sub('', task) # get 
rid of "next" label in description
                        task = encode_markup_text(task)
                        if row['actionable']:
                                #task = _tag_re.sub(r'<span 
color="#ce5c00">@\1</span>', task) # highlight tags - same color as used in 
pageview
                                task = _tag_re.sub(r'', task) # get rid of tags 
in task description --> most probably not the best place to do that...  
                                task = 
self.index_ext.task_label_re.sub(r'<b>\1</b>', task) # highlight labels
                        else:
                                task = r'<span color="darkgrey">%s</span>' % 
task

                        # Insert all columns
                        modelrow = [False, row['tickmark'], row['prio'], task, 
row['due'], path.name, tags0, row['comment'], row['actionable'], row['open'], 
row['id'], tags, row['task']]
                        modelrow[0] = self._filter_item(modelrow)
                        myiter = self.real_model.append(iter, modelrow)

                        if row['haschildren']:
                                self._append_tasks(row, myiter, path_cache) # 
recurs

        def _toggle_all_tasks(self, tick_status):
                print "implement dialog: are you sure? This might take a 
while...!!"

                model = self.get_model()        
                model.foreach(self._do_toggle_all_tasks)

                
        def _do_toggle_all_tasks(self, model, path, iter):

                # skip tasks which are ticked but are show due to unticked 
children
                if not model[path][self.TICKED_COL]:
                        page = Path( model[path][self.PAGE_COL] )
                        text = self._get_raw_text(model[path])
                        pageview = self.opener.open_page(page)
                        pageview.find(text)

                        task = model[path][self.TASK0_COL]

                        # if pageview has not been updated to add tickdate to 
task into the text, then the tickdate is
                        # preserved at least within the index (for the time 
being)
                        self.index_ext.put_new_tickdate_to_db(task)
                        
                        self.win.pageview.toggle_checkbox()




        def set_filter_actionable(self, filter):
                '''Set filter state for non-actionable items
                @param filter: if C{False} all items are shown, if C{True} only 
actionable items
                '''
                self.filter_actionable = filter
                self._eval_filter()

        def set_filter(self, string):
                # TODO allow more complex queries here - same parse as for 
search
                if string:
                        inverse = False
                        if string.lower().startswith('not '):
                                # Quick HACK to support e.g. "not @waiting"
                                inverse = True
                                string = string[4:]
                        self.filter = (inverse, string.strip().lower())
                else:
                        self.filter = None
                self._eval_filter()

        def get_labels(self):
                '''Get all labels that are in use
                @returns: a dict with labels as keys and the number of tasks
                per label as value
                '''
                return self._labels

        def get_tags(self):
                '''Get all tags that are in use
                @returns: a dict with tags as keys and the number of tasks
                per tag as value
                '''
                return self._tags

        def get_n_tasks(self):
                '''Get the number of tasks in the list
                @returns: total number
                '''
                counter = [0]
                def count(model, path, iter):
                        if model[iter][self.OPEN_COL]:
                                # only count open items
                                counter[0] += 1
                self.real_model.foreach(count)
                return counter[0]

        def get_statistics(self):
                statsbyprio = {}

                def count(model, path, iter):
                        # only count open items
                        row = model[iter]
                        if row[self.OPEN_COL]:
                                prio = row[self.PRIO_COL]
                                statsbyprio.setdefault(prio, 0)
                                statsbyprio[prio] += 1

                self.real_model.foreach(count)

                if statsbyprio:
                        total = reduce(int.__add__, statsbyprio.values())
                        highest = max([0] + statsbyprio.keys())
                        stats = [statsbyprio.get(k, 0) for k in 
range(highest+1)]
                        stats.reverse() # highest first
                        return total, stats
                else:
                        return 0, []

        def set_tag_filter(self, tags=None, labels=None):
                if tags:
                        self.tag_filter = [tag.lower() for tag in tags]
                else:
                        self.tag_filter = None

                if labels:
                        self.label_filter = [label.lower() for label in labels]
                else:
                        self.label_filter = None

                self._eval_filter()

        def _eval_filter(self):
                logger.debug('Filtering task list with labels: %s tags: %s, 
filter: %s', self.label_filter, self.tag_filter, self.filter)

                def filter(model, path, iter):
                        visible = self._filter_item(model[iter])
                        model[iter][self.VIS_COL] = visible
                        if visible:
                                parent = model.iter_parent(iter)
                                while parent:
                                        model[parent][self.VIS_COL] = visible
                                        parent = model.iter_parent(parent)

                self.real_model.foreach(filter)
                self.expand_all()

        def _filter_item(self, modelrow):
                # This method filters case insensitive because both filters and
                # text are first converted to lower case text.
                visible = True

                if not modelrow[self.OPEN_COL] \
                or (not modelrow[self.ACT_COL] and self.filter_actionable):
                        visible = False

                description = modelrow[self.TASK_COL].decode('utf-8').lower()
                pagename = modelrow[self.PAGE_COL].decode('utf-8').lower()
                tags = [t.lower() for t in modelrow[self.TAGS_COL]]

                if visible and self.label_filter:
                        # Any labels need to be present
                        for label in self.label_filter:
                                if label in description:
                                        break
                        else:
                                visible = False # no label found

                if visible and self.tag_filter:
                        # Any tag should match --> changed to all to 'activate'
                        # 'hidden' functionality for multiple selection of tags 
with
                        # the use of the ctrl key. 
                        if (_NO_TAGS in self.tag_filter and not tags) \
                        or all(tag in tags for tag in self.tag_filter):
                                visible = True
                        else:
                                visible = False

                if visible and self.filter:
                        # And finally the filter string should match
                        # FIXME: we are matching against markup text here - may 
fail for some cases
                        inverse, string = self.filter
                        match = string in description or string in pagename
                        if (not inverse and not match) or (inverse and match):
                                visible = False

                return visible

        def do_row_activated(self, path, column):
                model = self.get_model()
                page = Path( model[path][self.PAGE_COL] )
                text = self._get_raw_text(model[path])
                pageview = self.opener.open_page(page)
                pageview.find(text)
                # I need to get the task text only to compare against the model 
below
                task_text = model[path][self.TASK_COL]
                                
                # now check if column Tick was clicked and tick box and action 
on page selected
                if column.get_title() == "Tick":
                        real_path = model.convert_path_to_child_path(path)
                        if (self.label_filter or self.tag_filter or 
self.filter):
                                real_path = self.get_real_path(task_text)

                        self.real_model[real_path][self.TICKED_COL] = not 
self.real_model[real_path][self.TICKED_COL]

                        task = self.real_model[real_path][self.TASK0_COL]

                        # if pageview has not been updated to add tickdate to 
task into the text, then the tickdate is
                        # preserved at least within the index (for the time 
being)
                        self.index_ext.put_new_tickdate_to_db(task)
                        
                        success = self.win.pageview.toggle_checkbox()
                        print success
                
        def get_real_path(self, text):
                '''
                this method looks for the correct path in the real_model 
according to the selected row in treeview.
                As treeview can be filtered against tag etc., the only reliable 
way to find the right path (even with children) is to
                compare the text selected against the text in self.real_model 
and then return the path
                '''
                # start from the beginning
                iter = self.real_model.get_iter_first()
                while iter:
                        path = self.real_model.get_path(iter)
                        if self.real_model[path][self.TASK_COL] == text:
                                #parent found
                                return path
                        if self.real_model.iter_has_child(iter):
                                # there are children
                                n_children = 
self.real_model.iter_n_children(iter)
                                # how many?
                                for child_no in range(0, n_children):
                                        child_iter = 
self.real_model.iter_nth_child(iter, child_no)
                                        child_path = 
self.real_model.get_path(child_iter)
                                        if 
self.real_model[child_path][self.TASK_COL] == text:
                                                # so there is a child found
                                                return child_path
                        iter = self.real_model.iter_next(iter)

        def _get_raw_text(self, task):
                id = task[self.TASKID_COL]
                row = self.index_ext.get_task(id)
                return row['description']

        def do_initialize_popup(self, menu):
                item = gtk.ImageMenuItem('gtk-copy')
                item.connect('activate', self.copy_to_clipboard)
                menu.append(item)
                self.populate_popup_expand_collapse(menu)

        def copy_to_clipboard(self, *a):
                '''Exports currently visible elements from the tasks list'''
                logger.debug('Exporting to clipboard current view of task 
list.')
                text = self.get_visible_data_as_csv()
                Clipboard.set_text(text)
                        # TODO set as object that knows how to format as text / 
html / ..
                        # unify with export hooks

        def get_visible_data_as_csv(self):
                text = ""
                for indent, prio, desc, date, tags0, comment, page in 
self.get_visible_data():
                        prio = str(prio)
                        desc = decode_markup_text(desc)
                        desc = '"' + desc.replace('"', '""') + '"'
                        text += ",".join((prio, desc, date, tags0, comment, 
page)) + "\n"
                return text


        # TODO: Show filter (e.g. list of selected tags) which lead to the 
print out
        def get_visible_data_as_html(self):
                html = '''\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd";>
<html>
        <head>
                <meta http-equiv="Content-Type" content="text/html; 
charset=UTF-8">
                <title>Task List - Zim</title>
                <meta name='Generator' content='Zim [%% zim.version %%]'>
                <style type='text/css'>
                        table.tasklist {
                                border-width: 1px;
                                border-spacing: 2px;
                                border-style: solid;
                                border-color: gray;
                                border-collapse: collapse;
                        }
                        table.tasklist th {
                                border-width: 1px;
                                padding: 8px;
                                border-style: solid;
                                border-color: gray;
                                text-align: left;
                                background-color: gray;
                                color: white;
                        }
                        table.tasklist td {
                                border-width: 1px;
                                padding: 8px;
                                border-style: solid;
                                border-color: gray;
                                text-align: left;
                        }
                        .high {background-color: %s}
                        .medium {background-color: %s}
                        .alert {background-color: %s}
                </style>
        </head>
        <body>


<h1>Task List - Zim</h1>
<table class="tasklist">
<tr><th>Status</th><th>Prio</th><th>Task</th><th>Date</th><th>Tags</th><th>Comments</th></tr>
''' % (HIGH_COLOR, MEDIUM_COLOR, ALERT_COLOR)

                today    = str( datetime.date.today() )
                tomorrow = str( datetime.date.today() + 
datetime.timedelta(days=1))
                dayafter = str( datetime.date.today() + 
datetime.timedelta(days=2))
                for indent, status, prio, desc, date, tags0, comment, page in 
self.get_visible_data():

                        if status == 1: status_str = '<td>Closed</td>'
                        if status == 0: status_str = '<td>Open</td>'

                        if prio >= 3: prio = '<td class="high">%s</td>' % prio
                        elif prio == 2: prio = '<td class="medium">%s</td>' % 
prio
                        elif prio == 1: prio = '<td class="alert">%s</td>' % 
prio
                        else: prio = '<td>%s</td>' % prio

                        if date and date <= today: date = '<td 
class="high">%s</td>' % date
                        elif date == tomorrow: date = '<td 
class="medium">%s</td>' % date
                        elif date == dayafter: date = '<td 
class="alert">%s</td>' % date
                        else: date = '<td>%s</td>' % date

                        desc = '<td>%s%s</td>' % ('&nbsp;' * (4 * indent), desc)
                        if "\n" in tags0:
                                tags0 = tags0.replace("\n", "<br />")
                        tags0 = '<td>%s</td>' % tags0
                        if "\n" in comment:
                                comment = comment.replace("\n", "<br />")
                        comment = '<td>%s</td>' % comment
                        page = '<td>%s</td>' % page

                        html += '<tr>' + status_str + prio + desc + date + 
tags0 + comment + '</tr>\n'

                html += '''\
</table>

        </body>

</html>
'''
                return html

        def get_visible_data(self):
                rows = []

                def collect(model, path, iter):
                        indent = len(path) - 1 # path is tuple with indexes

                        row = model[iter]
                        status = row[self.TICKED_COL]
                        prio = row[self.PRIO_COL]
                        desc = row[self.TASK_COL].decode('utf-8')
                        date = row[self.DATE_COL]
                        page = row[self.PAGE_COL].decode('utf-8')
                        tags0 = row[self.TAGS0_COL].decode('utf-8')
                        comment = row[self.TASK_COMMENT_COL].decode('utf-8')

                        if date == _NO_DATE:
                                date = ''

                        rows.append((indent, status, prio, desc, date, tags0, 
comment, page))

                model = self.get_model()
                model.foreach(collect)

                return rows


class TaskListHistoryTreeView(BrowserTreeView):

        
        VIS_COL = 0 # visible
        TICKED_COL = 1                  
        TICKED_DATE_COL = 2
        PRIO_COL = 3
        TASK_COL = 4
        DATE_COL = 5
        PAGE_COL = 6
        TAGS0_COL = 7 # TAGS separated by \n
        TASK_COMMENT_COL = 8    
        ACT_COL = 9 # actionable
        OPEN_COL = 10 # item not closed
        TASKID_COL = 11
        TAGS_COL = 12
        TASK0_COL = 13 # Tasks string complete (for search in index)    

        def __init__(self, window, index_ext, opener, filter_actionable=False, 
tag_by_page=False, use_workweek=False):
                self.real_model = gtk.TreeStore(bool, bool, str, int, str, str, 
str, str, str, bool, bool, int, object, str)
                        # VIS_COL, TICKED_COL, TICKED_DATE_COL, PRIO_COL, 
TASK_COL, DATE_COL, TAGS0_COL, TASK_COMMENT_COL, PAGE_COL, ACT_COL, OPEN_COL, 
TASKID_COL, TAGS_COL, TASK0_COL
                model = self.real_model.filter_new()
                model.set_visible_column(self.VIS_COL)
                model = gtk.TreeModelSort(model)
                model.set_sort_column_id(self.TICKED_DATE_COL, 
gtk.SORT_DESCENDING)
                BrowserTreeView.__init__(self, model)
                screen_width = gtk.gdk.screen_width()
                screen_height = gtk.gdk.screen_height()
                self.index_ext = index_ext
                self.opener = opener
                self.filter = None
                self.tag_filter = None
                self.label_filter = None
                self.filter_actionable = filter_actionable
                self.tag_by_page = tag_by_page
                self._tags = {}
                self._labels = {}
                self.win = window
                column_width = 300

                # Wrap text in column on resize
                def set_column_width(column, width, renderer, pan = True):
                        column_width = column.get_width()
                        renderer.props.wrap_width = column_width
                        if pan:
                                renderer.props.wrap_mode = pango.WRAP_WORD
                        else:
                                renderer.props.wrap_mode = gtk.WRAP_WORD

                # Add some rendering for the task tick box
                cell_renderer = gtk.CellRendererToggle()
                cell_renderer.set_property('activatable', True)
                column = gtk.TreeViewColumn('Tick', cell_renderer)
                column.set_sort_column_id(self.TICKED_COL)
                column.set_resizable(False)
                column.add_attribute(cell_renderer, "active", self.TICKED_COL)
                self.append_column(column)


                # Add some rendering for the task tick date
                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Ticked Date'), cell_renderer, 
text=self.TICKED_DATE_COL)
                column.set_sort_column_id(self.TICKED_DATE_COL)
                column.set_resizable(True)
                self.append_column(column)


                # Add some rendering for the Prio column
                def render_prio(col, cell, model, i):
                        prio = model.get_value(i, self.PRIO_COL)
                        cell.set_property('text', str(prio))
                        if prio >= 3: color = HIGH_COLOR
                        elif prio == 2: color = MEDIUM_COLOR
                        elif prio == 1: color = ALERT_COLOR
                        else: color = None
                        cell.set_property('cell-background', color)

                cell_renderer = gtk.CellRendererText()
                #~ column = gtk.TreeViewColumn(_('Prio'), cell_renderer)
                        # T: Column header Task List dialog
                column = gtk.TreeViewColumn(' ! ', cell_renderer)
                column.set_cell_data_func(cell_renderer, render_prio)
                column.set_sort_column_id(self.PRIO_COL)
                column.set_resizable(True)
                self.append_column(column)

                # Rendering for task description column
                cell_renderer = gtk.CellRendererText()
                cell_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
                column = gtk.TreeViewColumn(_('Task'), cell_renderer, 
markup=self.TASK_COL)
                                # T: Column header Task List dialog
                column.set_resizable(True)
                column.set_sort_column_id(self.TASK_COL)
                #column.set_expand(True)
                if ui_environment['platform'] == 'maemo':
                        column.set_min_width(int(screen_width*0.05)) # don't 
let this column get too small
                else:
                        column.set_min_width(int(screen_width*0.05)) # don't 
let this column get too small
                
                self.append_column(column)
                self.set_expander_column(column)

                if gtk.gtk_version >= (2, 12) \
                and gtk.pygtk_version >= (2, 12):
                        self.set_tooltip_column(self.TASK_COL)

                ## Rendering of the Date column
                #day_of_week = datetime.date.today().isoweekday()
                #if use_workweek and day_of_week == 4:
                #       # Today is Thursday - 2nd day ahead is after the weekend
                #       delta1, delta2 = 1, 3
                #elif use_workweek and day_of_week == 5:
                #       # Today is Friday - next day ahead is after the weekend
                #       delta1, delta2 = 3, 4
                #else:
                #       delta1, delta2 = 1, 2

                #today    = str( datetime.date.today() )
                #tomorrow = str( datetime.date.today() + 
datetime.timedelta(days=delta1))
                #dayafter = str( datetime.date.today() + 
datetime.timedelta(days=delta2))
                def render_date(col, cell, model, i):
                        date = model.get_value(i, self.DATE_COL)
                        if date == _NO_DATE:
                                cell.set_property('text', '')
                        #else:
                        #       cell.set_property('text', date)
                                # TODO allow strftime here

                #       if date <= today: color = HIGH_COLOR
                #       elif date <= tomorrow: color = MEDIUM_COLOR
                #       elif date <= dayafter: color = ALERT_COLOR
                #               # "<=" because tomorrow and/or dayafter can be 
after the weekend
                #       else: color = None
                #       cell.set_property('cell-background', color)

                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Date'), cell_renderer, 
text=self.DATE_COL)
                        # T: Column header Task List dialog
                column.set_cell_data_func(cell_renderer, render_date)
                column.set_sort_column_id(self.DATE_COL)
                self.append_column(column)

                # Rendering for tag column
                cell_renderer = gtk.CellRendererText()

                cell_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
                column = gtk.TreeViewColumn(_('Tags'), cell_renderer, 
markup=self.TAGS0_COL)
                column.set_resizable(True)
                column.set_sort_column_id(self.TAGS0_COL)
                column.set_min_width(int(screen_width*0.05))

                column.connect_after("notify::width", set_column_width, 
cell_renderer)
                self.append_column(column)

                # Rendering for task comment column
                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Comment'), cell_renderer, 
text=self.TASK_COMMENT_COL)
                column.set_resizable(True)
                column.set_sort_column_id(self.TASK_COMMENT_COL)
                #column.set_min_width(column_width)
                cell_renderer.props.wrap_width = int(screen_width*0.05)
                cell_renderer.props.wrap_mode = pango.WRAP_WORD

                column.connect_after("notify::width", set_column_width, 
cell_renderer)
                self.append_column(column)

                # Rendering for page name column
                cell_renderer = gtk.CellRendererText()
                column = gtk.TreeViewColumn(_('Page'), cell_renderer, 
text=self.PAGE_COL)
                                # T: Column header Task List dialog
                column.set_sort_column_id(self.PAGE_COL)
                self.append_column(column)

                # Finalize
                self.refresh()

                # HACK because we can not register ourselves :S
                self.connect('row_activated', self.__class__.do_row_activated)


        def refresh(self):
                '''Refresh the model based on index data'''
                # Update data
                self._clear()
                self._append_tasks(None, None, {})

                # Make tags case insensitive
                tags = sorted((t.lower(), t) for t in self._tags)
                        # tuple sorting will sort ("foo", "Foo") before ("foo", 
"foo"),
                        # but ("bar", ..) before ("foo", ..)
                prev = ('', '')
                for tag in tags:
                        if tag[0] == prev[0]:
                                self._tags[prev[1]] += self._tags[tag[1]]
                                self._tags.pop(tag[1])
                        prev = tag

                # Set view
                self._eval_filter() # keep current selection
                self.expand_all()

        def _clear(self):
                self.real_model.clear() # flush
                self._tags = {}
                self._labels = {}

        def _append_tasks(self, task, iter, path_cache):
                for row in self.index_ext.list_tasks(task):
                        # only include ticked tasks and open tasks with ticked 
children
                        if not row['tickmark'] and not row['haschildren']:
                                continue

                        if row['source'] not in path_cache:
                                path = self.index_ext.get_path(row)
                                if path is None:
                                        # Be robust for glitches - filter these 
out
                                        continue
                                else:
                                        path_cache[row['source']] = path

                        path = path_cache[row['source']]

                        # Update labels
                        for label in 
self.index_ext.task_label_re.findall(row['description']):
                                self._labels[label] = self._labels.get(label, 
0) + 1

                        # Update tag count
                        tags = row['tags'].split(',')
                        # Want to show tags one below the other instead of 
separated by comma, so creating tags0 instead
                        tags0 = ""      
                        for tag in tags:                                        
                
                                tags0 += "<span color=\"#ce5c00\">" + tag + 
"</span>" + "\n"

                        if self.tag_by_page:
                                tags = tags + path.parts

                        if tags:
                                for tag in tags:
                                        self._tags[tag] = self._tags.get(tag, 
0) + 1
                        else:
                                self._tags[_NO_TAGS] = self._tags.get(_NO_TAGS, 
0) + 1


                        # Format description
                        task = _date_re.sub('', row['description'], count=1)
                        task = _tdate_re.sub('', task, count=1)
                        task = re.sub('\s*!+\s*', ' ', task) # get rid of 
exclamation marks
                        task = self.index_ext.next_label_re.sub('', task) # get 
rid of "next" label in description
                        task = encode_markup_text(task)
                        if row['actionable']:
                        #if row['tickmark']:
                                #task = _tag_re.sub(r'<span 
color="#ce5c00">@\1</span>', task) # highlight tags - same color as used in 
pageview
                                task = _tag_re.sub(r'', task) # get rid of tags 
in task description --> most probably not the best place to do that...  
                                task = 
self.index_ext.task_label_re.sub(r'<b>\1</b>', task) # highlight labels
                        else:
                                task = r'<span color="darkgrey">%s</span>' % 
task
                        
                        tickdate = "[no date]"
                        if row['tickdate']:
                                # This means that pageview has added tickdate 
to task within text and has been parsed by Taskparser
                                tickdate = row['tickdate']
                        else:
                                # Either pageview is not updated to add 
tickdates to task, or it's disabled in pageview prefs.
                                # Therefore get the tickdat from the index. But 
if child is ticked,
                                # and parent is not but has entry in index, 
don't show date
                                if row['tickmark']:
                                        tickdate = 
self.get_tickdate_for_task(row['task'])
                                        if not tickdate:
                                                tickdate = "[no date]"

                        # Insert all columns
                        modelrow = [False, row['tickmark'], tickdate, 
row['prio'], task, row['due'], path.name, tags0, row['comment'], 
row['actionable'], row['open'], row['id'], tags, row['task']]
                        modelrow[0] = self._filter_item(modelrow)
                        myiter = self.real_model.append(iter, modelrow)

                        if row['haschildren']:
                                self._append_tasks(row, myiter, path_cache) # 
recurs


        def _toggle_all_tasks(self, tick_status):
                print "ticked", tick_status
                print "implement dialog: are you sure? This might take a 
while...!!"

                model = self.get_model()        
                model.foreach(self._do_toggle_all_tasks)

                
        def _do_toggle_all_tasks(self, model, path, iter):

                # skip tasks which are not ticked but are show due to ticked 
children
                if model[path][self.TICKED_COL]:
                        page = Path( model[path][self.PAGE_COL] )
                        text = self._get_raw_text(model[path])
                        pageview = self.opener.open_page(page)
                        pageview.find(text)

                        task = model[path][self.TASK0_COL]

                        # if pageview has not been updated to add tickdate to 
task into the text, then the tickdate is
                        # preserved at least within the index (for the time 
being)
                        self.index_ext.put_new_tickdate_to_db(task)
                        
                        self.win.pageview.toggle_checkbox()


        def get_tickdate_for_task(self, task):
                date = self.index_ext.get_tickdate_from_db(task)
                if date:
                        return date[2]
                return False

        def put_tickdate_to_db(self, task, date):
                print "putting tickdate to db"
                self.index_ext.put_existing_tickdate_to_db(task, date, 
tickmark=True)


        def set_filter_actionable(self, filter):
                '''Set filter state for non-actionable items
                @param filter: if C{False} all items are shown, if C{True} only 
actionable items
                '''
                self.filter_actionable = filter
                self._eval_filter()

        def set_filter(self, string):
                # TODO allow more complex queries here - same parse as for 
search
                if string:
                        inverse = False
                        if string.lower().startswith('not '):
                                # Quick HACK to support e.g. "not @waiting"
                                inverse = True
                                string = string[4:]
                        self.filter = (inverse, string.strip().lower())
                else:
                        self.filter = None
                self._eval_filter()

        def get_labels(self):
                '''Get all labels that are in use
                @returns: a dict with labels as keys and the number of tasks
                per label as value
                '''
                return self._labels

        def get_tags(self):
                '''Get all tags that are in use
                @returns: a dict with tags as keys and the number of tasks
                per tag as value
                '''
                return self._tags

        def get_n_tasks(self):
                '''Get the number of tasks in the list
                @returns: total number
                '''
                counter = [0]
                def count(model, path, iter):
                        if model[iter][self.OPEN_COL]:
                                # only count open items
                                counter[0] += 1
                self.real_model.foreach(count)
                return counter[0]

        def get_statistics(self):
                statsbyprio = {}

                def count(model, path, iter):
                        # only count open items
                        row = model[iter]
                        if row[self.OPEN_COL]:
                                prio = row[self.PRIO_COL]
                                statsbyprio.setdefault(prio, 0)
                                statsbyprio[prio] += 1

                self.real_model.foreach(count)

                if statsbyprio:
                        total = reduce(int.__add__, statsbyprio.values())
                        highest = max([0] + statsbyprio.keys())
                        stats = [statsbyprio.get(k, 0) for k in 
range(highest+1)]
                        stats.reverse() # highest first
                        return total, stats
                else:
                        return 0, []

        def set_tag_filter(self, tags=None, labels=None):
                if tags:
                        self.tag_filter = [tag.lower() for tag in tags]
                else:
                        self.tag_filter = None

                if labels:
                        self.label_filter = [label.lower() for label in labels]
                else:
                        self.label_filter = None

                self._eval_filter()

        def _eval_filter(self):
                logger.debug('Filtering task list history with labels: %s tags: 
%s, filter: %s', self.label_filter, self.tag_filter, self.filter)

                def filter(model, path, iter):
                        visible = self._filter_item(model[iter])
                        model[iter][self.VIS_COL] = visible
                        if visible:
                                parent = model.iter_parent(iter)
                                while parent:
                                        model[parent][self.VIS_COL] = visible
                                        parent = model.iter_parent(parent)

                self.real_model.foreach(filter)
                self.expand_all()

        def _filter_item(self, modelrow):
                # This method filters case insensitive because both filters and
                # text are first converted to lower case text.
                visible = True
                
                if not modelrow[self.TICKED_COL]:
                        visible = False

                #if not modelrow[self.OPEN_COL] \
                #or (not modelrow[self.ACT_COL] and self.filter_actionable):
                #       visible = False

                description = modelrow[self.TASK_COL].decode('utf-8').lower()
                pagename = modelrow[self.PAGE_COL].decode('utf-8').lower()
                tags = [t.lower() for t in modelrow[self.TAGS_COL]]

                if visible and self.label_filter:
                        # Any labels need to be present
                        for label in self.label_filter:
                                if label in description:
                                        break
                        else:
                                visible = False # no label found

                if visible and self.tag_filter:
                        # Any tag should match --> changed to all to 'activate'
                        # 'hidden' functionality for multiple selection of tags 
with
                        # the use of the ctrl key. 
                        if (_NO_TAGS in self.tag_filter and not tags) \
                        or all(tag in tags for tag in self.tag_filter):
                                visible = True
                        else:
                                visible = False

                if visible and self.filter:
                        # And finally the filter string should match
                        # FIXME: we are matching against markup text here - may 
fail for some cases
                        inverse, string = self.filter
                        match = string in description or string in pagename
                        if (not inverse and not match) or (inverse and match):
                                visible = False

                return visible

        def do_row_activated(self, path, column):
                model = self.get_model()
                page = Path( model[path][self.PAGE_COL] )
                text = self._get_raw_text(model[path])
                pageview = self.opener.open_page(page)
                pageview.find(text)
                # I need to get the task text only to compare against the model 
below
                task_text = model[path][self.TASK_COL]
                                
                # now check if column Tick was clicked and tick box and action 
on page selected
                if column.get_title() == "Tick":
                        real_path = model.convert_path_to_child_path(path)
                        # get the real path to self.real_model no matter what 
filter or tag is selected
                        if (self.label_filter or self.tag_filter or 
self.filter):
                                real_path = self.get_real_path(task_text)
                        self.real_model[real_path][self.TICKED_COL] = not 
self.real_model[real_path][self.TICKED_COL]
                        
                        task = self.real_model[real_path][self.TASK0_COL]
                        
                        # the tick date is removed from index no matter if 
pageview removes tickdate from task within text
                        self.index_ext.del_tickdate_from_db(task)

                        self.win.pageview.toggle_checkbox()

                
        def get_real_path(self, text):
                '''
                this method looks for the correct path in the real_model 
according to the selected row in treeview.
                As treeview can be filtered against tag etc., the only reliable 
way to find the right path (even with children) is to
                compare the text selected against the text in self.real_model 
and then return the path
                '''
                # start from the beginning
                iter = self.real_model.get_iter_first()
                while iter:
                        path = self.real_model.get_path(iter)
                        if self.real_model[path][self.TASK_COL] == text:
                                #parent found
                                return path
                        if self.real_model.iter_has_child(iter):
                                # there are children
                                n_children = 
self.real_model.iter_n_children(iter)
                                # how many?
                                for child_no in range(0, n_children):
                                        child_iter = 
self.real_model.iter_nth_child(iter, child_no)
                                        child_path = 
self.real_model.get_path(child_iter)
                                        if 
self.real_model[child_path][self.TASK_COL] == text:
                                                # so there is a child found
                                                return child_path
                        iter = self.real_model.iter_next(iter)

        def _get_raw_text(self, task):
                id = task[self.TASKID_COL]
                row = self.index_ext.get_task(id)
                return row['description']

        def do_initialize_popup(self, menu):
                item = gtk.ImageMenuItem('gtk-copy')
                item.connect('activate', self.copy_to_clipboard)
                menu.append(item)
                self.populate_popup_expand_collapse(menu)

        def copy_to_clipboard(self, *a):
                '''Exports currently visible elements from the tasks list'''
                logger.debug('Exporting to clipboard current view of task 
list.')
                text = self.get_visible_data_as_csv()
                Clipboard.set_text(text)
                        # TODO set as object that knows how to format as text / 
html / ..
                        # unify with export hooks

        def get_visible_data_as_csv(self):
                text = ""
                for indent, prio, desc, date, tags0, comment, page in 
self.get_visible_data():
                        prio = str(prio)
                        desc = decode_markup_text(desc)
                        desc = '"' + desc.replace('"', '""') + '"'
                        text += ",".join((prio, desc, date, tags0, comment, 
page)) + "\n"
                return text


        # TODO: Show filter (e.g. list of selected tags) which lead to the 
print out
        def get_visible_data_as_html(self):
                html = '''\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd";>
<html>
        <head>
                <meta http-equiv="Content-Type" content="text/html; 
charset=UTF-8">
                <title>Task List - Zim</title>
                <meta name='Generator' content='Zim [%% zim.version %%]'>
                <style type='text/css'>
                        table.tasklist {
                                border-width: 1px;
                                border-spacing: 2px;
                                border-style: solid;
                                border-color: gray;
                                border-collapse: collapse;
                        }
                        table.tasklist th {
                                border-width: 1px;
                                padding: 8px;
                                border-style: solid;
                                border-color: gray;
                                text-align: left;
                                background-color: gray;
                                color: white;
                        }
                        table.tasklist td {
                                border-width: 1px;
                                padding: 8px;
                                border-style: solid;
                                border-color: gray;
                                text-align: left;
                        }
                        .high {background-color: %s}
                        .medium {background-color: %s}
                        .alert {background-color: %s}
                </style>
        </head>
        <body>


<h1>Task List - Zim</h1>
<table class="tasklist">
<tr><th>Status</th><th>Prio</th><th>Task</th><th>Date</th><th>Tags</th><th>Comments</th></tr>
''' % (HIGH_COLOR, MEDIUM_COLOR, ALERT_COLOR)

                today    = str( datetime.date.today() )
                tomorrow = str( datetime.date.today() + 
datetime.timedelta(days=1))
                dayafter = str( datetime.date.today() + 
datetime.timedelta(days=2))
                for indent, status, prio, desc, date, tags0, comment, page in 
self.get_visible_data():

                        if status == 1: status_str = '<td>Closed</td>'
                        if status == 0: status_str = '<td>Open</td>'

                        if prio >= 3: prio = '<td class="high">%s</td>' % prio
                        elif prio == 2: prio = '<td class="medium">%s</td>' % 
prio
                        elif prio == 1: prio = '<td class="alert">%s</td>' % 
prio
                        else: prio = '<td>%s</td>' % prio

                        if date and date <= today: date = '<td 
class="high">%s</td>' % date
                        elif date == tomorrow: date = '<td 
class="medium">%s</td>' % date
                        elif date == dayafter: date = '<td 
class="alert">%s</td>' % date
                        else: date = '<td>%s</td>' % date

                        desc = '<td>%s%s</td>' % ('&nbsp;' * (4 * indent), desc)
                        if "\n" in tags0:
                                tags0 = tags0.replace("\n", "<br />")
                        tags0 = '<td>%s</td>' % tags0
                        if "\n" in comment:
                                comment = comment.replace("\n", "<br />")
                        comment = '<td>%s</td>' % comment
                        page = '<td>%s</td>' % page

                        html += '<tr>' + status_str + prio + desc + date + 
tags0 + comment + '</tr>\n'

                html += '''\
</table>

        </body>

</html>
'''
                return html

        def get_visible_data(self):
                rows = []

                def collect(model, path, iter):
                        indent = len(path) - 1 # path is tuple with indexes

                        row = model[iter]
                        status = row[self.TICKED_COL]
                        prio = row[self.PRIO_COL]
                        desc = row[self.TASK_COL].decode('utf-8')
                        date = row[self.DATE_COL]
                        page = row[self.PAGE_COL].decode('utf-8')
                        tags0 = row[self.TAGS0_COL].decode('utf-8')
                        comment = row[self.TASK_COMMENT_COL].decode('utf-8')

                        if date == _NO_DATE:
                                date = ''

                        rows.append((indent, status, prio, desc, date, tags0, 
comment, page))

                model = self.get_model()
                model.foreach(collect)

                return rows

# Need to register classes defining gobject signals
#~ gobject.type_register(TaskListTreeView)
# NOTE: enabling this line causes this treeview to have wrong theming under 
default ubuntu them !???
_______________________________________________
Mailing list: https://launchpad.net/~zim-wiki
Post to     : zim-wiki@lists.launchpad.net
Unsubscribe : https://launchpad.net/~zim-wiki
More help   : https://help.launchpad.net/ListHelp

Reply via email to