Skip to content

Instantly share code, notes, and snippets.

@introt
Forked from muguu/taskdate.py
Created September 28, 2021 18:39
Show Gist options
  • Save introt/07a3378f2e7ae40fc3b8bf4fcf314686 to your computer and use it in GitHub Desktop.
Save introt/07a3378f2e7ae40fc3b8bf4fcf314686 to your computer and use it in GitHub Desktop.
Plugin for ZimWiki V0.66 or greater
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2017 Murat Guven <muratg@online.de>
# This taskdate plugin provides a date for tasks in start / due date
# format for ZimWiki from Jaap Karssenberg
# V0.80 for Zim > 0.65
# Change Log
# V0.80: Added Calendar within expander. Mouse selection of start / due date within calendar
# and SHIFT key
# V0.70: Update for Zim V0.65 with new due date format.
# Therefore renamed to Taskdate. Added activation on > or < key when within a task
# V0.60: Code cleaning in arrow key handling and simplification of date calculation and bug fixing
# V0.57: Removed trailing space. Didn't find due date without trailing space
# (e.g. due date at end of line)
# V0.56: Bug fix: plugin crashes when number is added into due date due to old text iter being used.
# V0.55: If due date already exists at current line, automatically select it and use as date to show
# entry or calendar at the position of the existing due date
# V0.50: Added entries and calendar as additional options
# V0.21: move cursor back and highlight day to make overwriting easier
# V0.2: First version
# pylint: disable=import-error, anomalous-backslash-in-string
import sys
import re
import gtk
import gtk.gdk
import time
from zim.plugins import PluginClass, WindowExtension, extends
from zim.actions import action
from zim.gui.widgets import InputEntry, Window
import zim.datetimetz as zim_datetime
from zim.plugins.tasklist.dates import parse_date
from zim.parsing import parse_date as old_parse_date
from zim.plugins.tasklist.indexer import _date_re
from zim.gui.pageview import bullet_types
DATE_RE_1 = re.compile(
'(?:'
'\d{4}-\d{2}-\d{2}'
')(?![\S])'
)
DATE_RE_2 = re.compile(
'(?:'
'\d{4}-\d{2}'
')(?![\S])'
)
DATE_RE_3_1 = re.compile(
'(?:'
'(?:\d{4})[Ww][Kk]?\d{2}'
')(?![\S])'
)
DATE_RE_3_2 = re.compile(
'(?:'
'(?:\d{2})[Ww][Kk]?\d{2}'
')(?![\S])'
)
DATE_RE_3_3 = re.compile(
'(?:'
'(?:\d{2})-?[Ww][Kk]?\d{2}'
')(?![\S])'
)
DATE_RE_3_4 = re.compile(
'(?:'
'(?:\d{2})-?[Ww][Kk]?\d{2}(?:-\d)?'
')(?![\S])'
)
DATE_RE_4_1 = re.compile(
'(?:'
'[Ww][Kk]?(?:\d{2}|\d{4})\d{2}'
')(?![\S])'
)
DATE_RE_4_2 = re.compile(
'(?:'
'[Ww][Kk]?(?:\d{2}|\d{4})\d{2}(?:[\.-]\d)?'
')(?![\S])'
)
DATE_RE_OLD = re.compile(
'|\[d:.+\]'
)
FORMAT_1 = '%Y-%m-%d'
FORMAT_2 = '%Y-%m'
FORMAT_3_1 = '%YW%W'
FORMAT_3_2 = '%yW%W'
FORMAT_3_3 = '%y-W%W'
FORMAT_3_4 = '%y-W%W-%w'
FORMAT_4_1 = 'wk%y%W'
FORMAT_4_2 = 'wk%y%W.%w'
FORMAT_OLD = '[d:%Y-%m-%d]'
# FIXME: Format 3_4 and 4_2 do not work correctly with strftime (week 0-6 instead of 1-7) issue.
# removing for now2
ALL_FORMATS = (FORMAT_1, FORMAT_2, FORMAT_3_1, FORMAT_3_2, FORMAT_3_3,
FORMAT_3_4, FORMAT_4_1, FORMAT_4_2)
ALL_FORMATS_TODAY = []
ALL_FORMATS_REAL_FORMAT = {}
for fmt in ALL_FORMATS:
# create a list for the preferencdes with the current date
# in the available formats
ALL_FORMATS_TODAY.append(zim_datetime.date.today().strftime(fmt))
# create a dict to get the real format from the selected prefs
ALL_FORMATS_REAL_FORMAT[zim_datetime.date.today().strftime(fmt)] = fmt
class TaskDatePlugin(PluginClass):
'''
Plugin Class for Zim
'''
# pylint: disable=undefined-variable, line-too-long
plugin_info = {
'name': _('Task date'), # T: plugin name
'description': _('''\
This plugin provides a dialog to quickly add a start and/or due date for a task.
It activates on > or < within a task or via keyboard shortcut <CTRL> + (period).
All new formats are accepted. The old due date format is changed to the new format.
Keys used:
<TAB>: You can switch between the start and the due date with the TAB key
(same as mouse click on the tab)
<ENTER>: The date is inserted into the page.
If both dates are altered, both are inserted.
<Mouse double click on calendar>: Same as <ENTER>
<SHIFT + ENTER> : Both dates are inserted into the page,
no matter if they were altered or not.
<SHIFT + Mouse double click on calendar>: Same as <SHIFT + ENTER>
<ALT + c>: Expand / hide calendar
<Select day with mouse in calendar> : Take the selected day into the entries
<Select day with mouse> THEN <SHIFT + Select day with mouse> :
Take earlier day as start, later day as due date and mark the days in the calendars
<SHIFT + Select day with mouse double click > : Take earlier day as start,
later day as due date and mark the days in the calendars AND insert both dates
<CURSOR UP / DOWN>: Increase / Decrease Day/Month/Year/Today +, depending on the cursor focus
<CURSOR LEFT / RIGHT> Switch between Day/Month/Year/Today +
<POS 1> : Show today
<Picture Up / Down > Increase / Decrease Month
Some logic behind:
If a date already exists, the date and its format is respected. Otherwise the selected
date format from the preferences is used for new dates (Inherit date)
If the start date and the due date do not match, they will be highlighted in red.
Within the dialog, if the start date is later than the due date or vice versa, the
date title will turn red.
The time between the start and the due date is marked within the calendar.
If a date is changed, then switched to the other date and is also changed
or just ENTER is pressed, both dates are inserted into the page after ENTER key
If < or > is used to activate, then TAB is pressed and then ENTER, both dates
are inserted into the page
If a date is changed by entering the number into a field, the ENTER key needs
to be pressed to accept that number. To insert that date into the page, the ENTER
key needs to be pressed again.
Options (see preferences):
- Select the preferred date format
- Choose to inherit the date format if a date exists
- Add x number of days to a start or due date
- Show calendar expanded
- Re-order the dates to start date then due date and group them (see options).
(V0.80)
'''), # T: plugin description
'author': "Murat Güven",
'help': 'Plugins:Task date',
}
plugin_preferences = (
# T: label for plugin preferences dialog
('task_date_format', 'choice', _('Preferred date format'), ALL_FORMATS_TODAY[0], ALL_FORMATS_TODAY),
('task_date_format_inherit', 'bool', _('Inherit date format if date already exists'), True),
('start_date_plus', 'int', _('Start date is x days relative to today\'s date (>date + x)'), 0, (0, 365)),
('due_date_plus', 'int', _('Due date is y days relative to start date (<date + x + y)'), 0, (0, 365)),
('task_date_greater', 'bool', _('Activate on typing > or < within a task for NEW date'), True),
('task_date_cal', 'bool', _('Show calendar expanded'), True),
('task_dates_reorder', 'bool', _('Re-order and group dates (start date then due date)'), True),
('task_dates_pos', 'choice', _('Grouping behaviour. Move '), 'Due date to start date', \
['Due date to start date', 'Start date to due date', 'Dates to line start', 'Dates to line end']),
# T: plugin preference
)
@extends('MainWindow')
class MainWindowExtension(WindowExtension):
'''
Main window extension for Zim Window
'''
# pylint: disable=undefined-variable, unused-argument
uimanager_xml = '''
<ui>
<menubar name='menubar'>
<menu action='tools_menu'>
<placeholder name='plugin_items'>
<menuitem action='task_date'/>
</placeholder>
</menu>
</menubar>
</ui>
'''
def __init__(self, plugin, window):
WindowExtension.__init__(self, plugin, window)
self.plugin = plugin
self.window = window
self.connectto(window.pageview.view, 'key-press-event')
self.date_type_key = ""
@action(_('_Task Date'), accelerator='<ctrl>period') # T: menu item #
def task_date(self):
'''
create the task date instance
'''
buf = self.window.pageview.view.get_buffer()
cursor_orig = buf.get_iter_at_mark(buf.get_insert())
buf.create_mark("cursor_orig", cursor_orig)
task_date = TaskDate(self.window, self.plugin.preferences, \
date_type_key=None, date_char_needed=True)
task_date.show_task_date(self.date_type_key)
def new_task_date(self, date_type_key):
'''
# added this method as I want to pass date_type_key to __init__ ...lazy approach.
# Remove, when code cleaning
'''
task_date = TaskDate(self.window, self.plugin.preferences, \
date_type_key, date_char_needed=False)
task_date.show_task_date(self.date_type_key)
def on_key_press_event(self, widget, event):
'''
if enabled in preferences
get the < or > key
'''
if not self.plugin.preferences['task_date_greater']:
return
if gtk.gdk.keyval_name(event.keyval) == "greater" \
or gtk.gdk.keyval_name(event.keyval) == "less":
self.date_type_key = gtk.gdk.keyval_name(event.keyval)
buf = self.window.pageview.view.get_buffer()
cursor_orig = buf.get_iter_at_mark(buf.get_insert())
buf.create_mark('cursor_orig', cursor_orig)
_iter = buf.get_insert_iter()
line_no = _iter.get_line()
bullet = buf.get_bullet(line_no)
if bullet in bullet_types:
self.new_task_date(self.date_type_key)
return
GREY = 65535
_DATE_COL = 0
_DATE_STRING_COL = 1
_DATE_FORMAT_COL = 2
_DATE_NEW_COL = 3
DATE_COL = 0
DATE_FORMAT_COL = 1
DATE_NEW_COL = 2
DATE_MARK_START_COL = 3
DATE_MARK_END_COL = 4
DATE_CHAR_TEXT_COL = 0
DATE_CHAR_SIGN_COL = 1
TASK_CHAR = ['<', '>']
WIN_WIDTH = 155
WIN_HEIGHT = 115
SMALL_WIN_WIDTH = 155
SMALL_WIN_HEIGHT = 115
EXP_WIN_WIDTH = 195
EXP_WIN_HEIGHT = 290
class TaskDate(object):
'''
Handle Window and keys
'''
# pylint: disable=unused-variable, no-member, unused-argument
def __init__(
self, window, plugin_preferences,
date_type_key=None, date_char_needed=None):
self.plugin_prefs = plugin_preferences
self.window = window
self.page_view = self.window.pageview.view
self.buf = self.window.pageview.view.get_buffer()
self.cursor_orig_mark = self.buf.get_mark("cursor_orig")
self.cursor_orig = self.buf.get_iter_at_mark(self.cursor_orig_mark)
# needed to highlight due date
self.grey_tag = self.buf.create_tag(background="grey")
self.red_tag = self.buf.create_tag(background="red")
self.task_dates = {}
self.date_type_key = date_type_key
self.date_char_needed = date_char_needed
self.date_char = ""
preferred_date_format_today = self.plugin_prefs['task_date_format']
self.preferred_date_format = ALL_FORMATS_REAL_FORMAT[preferred_date_format_today]
self.x_pos = 0
self.y_pos = 0
self.line_start = None
self.line_end = None
self.page_vb_size = None
# get self.due_dates filled.
# If dates are found, will be filled. If no dates found, new dates = today
self.task_dates = self.get_dates_at_line(self.buf)
self.marked_days = set()
# if < or > is entered and a date already exists of that type, don't do anything
if self.date_type_key in self.task_dates \
and not self.task_dates[self.date_type_key][DATE_NEW_COL]:
return
self.taskdate_window = Window()
self.taskdate_window.set_decorated(False)
self.taskdate_window.set_modal(True)
self.taskdate_window.set_keep_above(True)
self.taskdate_window.resize(WIN_WIDTH, WIN_HEIGHT)
self.notebook = TaskDateNotebook(self.window.pageview.view, self.taskdate_window)
self.page_vb_start_date = self.notebook.page_vb_start_date
self.page_vb_date_due = self.notebook.page_vb_due_date
self.page_num_start_date = self.notebook.page_num(self.page_vb_start_date)
self.page_num_due_date = self.notebook.page_num(self.page_vb_date_due)
# > or < not needed any more here, but leaving it for now
self.page_num_date_type = {
self.page_num_start_date: ['greater', '>'],
self.page_num_due_date : ['less', '<']}
self.n_pages = self.notebook.get_n_pages()
start_date_new = self.task_dates['greater'][DATE_NEW_COL]
due_date_new = self.task_dates['less'][DATE_NEW_COL]
self.today = zim_datetime.date.today()
self.start_tab = TaskDateTab(self.window, self.plugin_prefs,
list(self.task_dates['greater']),
self.page_vb_start_date, self.taskdate_window,
self.notebook, self, self.date_char_needed,
date_char='>', mark_type="date_start")
self.start_tab.show_task_date_tab()
self.tab_list = [self.start_tab]
self.due_tab = TaskDateTab(self.window, self.plugin_prefs,
list(self.task_dates['less']),
self.page_vb_date_due, self.taskdate_window,
self.notebook, self, self.date_char_needed,
date_char='<', mark_type="date_due")
self.due_tab.show_task_date_tab()
self.tab_list.append(self.due_tab)
self.taskdate_window.show_all()
self.page_vb_size = self.page_vb_date_due.size_request()
# if due date already exists (not new) and not > or < key was pressed,\
# switch to due date page
if start_date_new and not self.date_type_key:
self.notebook.set_current_page(self.page_num_due_date)
# if start date already exists (not new) and not > or < key was pressed,\
# switch to start date page
if due_date_new and not self.date_type_key:
self.notebook.set_current_page(self.page_num_start_date)
# if due date already exists (not new) and > key was pressed, \
# switch to due start page as new start date
# shall be added
if start_date_new and self.date_type_key == "greater":
if not due_date_new:
self.untag_dates_at_line()
self.notebook.set_current_page(self.page_num_start_date)
# if start date already exists (not new) and < key was pressed,\
# switch to due date page as new due date
# shall be added
if due_date_new and self.date_type_key == "less":
if not start_date_new:
self.untag_dates_at_line()
self.notebook.set_current_page(self.page_num_due_date)
self.do_tag_date_at_line()
self.do_modify_label_color()
def show_task_date(self, date_type_key_pressed):
'''
show the dialog
'''
_iter = self.buf.get_insert_iter()
line_start, line_end = self.buf.get_line_bounds(_iter.get_line())
self.buf.create_mark('line_start', line_start)
self.buf.create_mark('line_end', line_end)
# if both exist < and >
try:
self.notebook.show_notebook()
self.taskdate_window.connect(
"key-press-event",
self.do_key_press,
self.notebook.taskdate_window)
self.notebook.connect('switch-page', self.do_page_select)
except AttributeError:
pass
def do_page_select(self, notebook, page, page_num):
# as the page_num is showing the switched page, but notebook_get_current_page
# shows the old page, I handle the tab_switch in a separate method although
# repeating code for do_tab_switch and do_mouse_tab_switch. Keeping it for this release
self.do_mouse_tab_switch(page_num)
return True
def do_key_press(self, widget, event, window):
'''
Handle ESC and TAB keys
'''
#print gtk.gdk.keyval_name(event.keyval)
if gtk.gdk.keyval_name(event.keyval) == 'Escape':
for date_type in self.task_dates:
self.untag_dates_at_line()
self.remove_all_marks()
window.destroy()
return
if gtk.gdk.keyval_name(event.keyval) == 'Tab' or \
gtk.gdk.keyval_name(event.keyval) == 'ISO_Left_Tab':
self.do_tab_switch()
# IMPORTANT: Return true to keep focus on entry day when switching
return True
def do_tab_switch(self, page_num=None):
current_page = self.notebook.get_current_page()
if current_page == self.page_num_start_date:
# show due date page
self.notebook.set_current_page(self.page_num_due_date)
# if the start date exceeds the due date, due date is
# changed relative to new start date according to prefs
# but only if due date does not exist yet
if self.start_tab.task_date[DATE_COL] > self.due_tab.task_date[DATE_COL]:
new_due_date = self.start_tab.task_date[DATE_COL] + \
zim_datetime.timedelta(days=self.plugin_prefs['due_date_plus'])
self.due_tab.task_date[DATE_COL] = new_due_date
# TODO: get more performance by updating the entries only if necessary
# With this, it is updated at any tab switch
self.due_tab.fill_entries(new_due_date)
self.due_tab.calendar.select_month(new_due_date.month - 1, new_due_date.year)
self.due_tab.calendar.select_day(new_due_date.day)
#green = gtk.gdk.color_parse("green")
#self.notebook.due_label.modify_bg(gtk.STATE_NORMAL, green)
else:
# show start date page
self.notebook.set_current_page(self.page_num_start_date)
# if the due date is earlier than the start date,
# start date is changed relative to new due date according to prefs
# but only if start date does not exist yet
if self.due_tab.task_date[DATE_COL] < self.start_tab.task_date[DATE_COL]:
new_start_date = self.due_tab.task_date[DATE_COL] - zim_datetime.timedelta(\
days=self.plugin_prefs['start_date_plus'])
self.start_tab.task_date[DATE_COL] = new_start_date
self.start_tab.fill_entries(new_start_date)
self.start_tab.calendar.select_month(new_start_date.month - 1, \
new_start_date.year)
self.start_tab.calendar.select_day(new_start_date.day)
#green = gtk.gdk.color_parse("green")
#self.notebook.start_label.modify_fg(gtk.STATE_NORMAL, green)
self.do_tag_date_at_line()
_current_page_num = self.notebook.get_current_page()
self.date_char = self.page_num_date_type[_current_page_num][DATE_CHAR_SIGN_COL]
self.calendars_mark_start_due()
def do_mouse_tab_switch(self, new_page):
if new_page != self.page_num_start_date:
# if the start date exceeds the due date, due date is
# changed relative to new start date according to prefs
# but only if due date does not exist yet
if self.start_tab.task_date[DATE_COL] > self.due_tab.task_date[DATE_COL]:
new_due_date = self.start_tab.task_date[DATE_COL] + \
zim_datetime.timedelta(days=self.plugin_prefs['due_date_plus'])
self.due_tab.task_date[DATE_COL] = new_due_date
self.due_tab.fill_entries(new_due_date)
self.due_tab.calendar.select_month(new_due_date.month - 1, new_due_date.year)
self.due_tab.calendar.select_day(new_due_date.day)
#green = gtk.gdk.color_parse("green")
#self.notebook.due_label.modify_bg(gtk.STATE_NORMAL, green)
else:
# if the due date is earlier than the start date,
# start date is changed relative to new due date according to prefs
# but only if start date does not exist yet
if self.due_tab.task_date[DATE_COL] < self.start_tab.task_date[DATE_COL]:
new_start_date = self.due_tab.task_date[DATE_COL] - zim_datetime.timedelta(\
days=self.plugin_prefs['start_date_plus'])
self.start_tab.task_date[DATE_COL] = new_start_date
self.start_tab.fill_entries(new_start_date)
self.start_tab.calendar.select_month(new_start_date.month - 1, \
new_start_date.year)
self.start_tab.calendar.select_day(new_start_date.day)
#green = gtk.gdk.color_parse("green")
#self.notebook.start_label.modify_fg(gtk.STATE_NORMAL, green)
self.do_tag_date_at_line(None, None, new_page)
_current_page_num = new_page
self.date_char = self.page_num_date_type[_current_page_num][DATE_CHAR_SIGN_COL]
self.calendars_mark_start_due()
def calendars_mark_start_due(self):
'''
Mark the days in calendar from start to due on both pages
'''
self.start_tab.calendar_mark_days()
self.due_tab.calendar_mark_days()
def untag_dates_at_line(self):
'''
Untag tagged dates
'''
for _date_type in self.task_dates:
start_iter = self.buf.get_iter_at_mark(self.task_dates[_date_type][DATE_MARK_START_COL])
end_iter = self.buf.get_iter_at_mark(self.task_dates[_date_type][DATE_MARK_END_COL])
self.buf.remove_tag(self.red_tag, start_iter, end_iter)
self.buf.remove_tag(self.grey_tag, start_iter, end_iter)
def do_tag_date_at_line(self, _new_date=None, task_date_tab=None, page_num=None):
'''
PARAMS: the buffer, the text_tag and the list of dates
This method tags the date in order to highlight it in grey or red,
depending on the date validation.
Also the label on the tab is turned red, if the start / due date combination
is invalid (e.g. start > due)
'''
if page_num is None:
current_page_num = self.notebook.get_current_page()
else:
current_page_num = page_num
mark_types_cross_over = {self.start_tab: 'less', self.due_tab: 'greater'}
mark_types = {self.start_tab: 'greater', self.due_tab: 'less'}
if task_date_tab:
_date = self.task_dates[mark_types_cross_over[task_date_tab]][DATE_COL]
if task_date_tab:
_mark_type = mark_types[task_date_tab]
# first unmark all dates if marked.
self.untag_dates_at_line()
date_type = self.page_num_date_type[current_page_num][DATE_CHAR_TEXT_COL]
start_iter = self.buf.get_iter_at_mark(self.task_dates[date_type][DATE_MARK_START_COL])
end_iter = self.buf.get_iter_at_mark(self.task_dates[date_type][DATE_MARK_END_COL])
if self.task_dates['greater'][DATE_COL] > self.task_dates['less'][DATE_COL]:
self.buf.apply_tag(self.red_tag, start_iter, end_iter)
else:
self.buf.apply_tag(self.grey_tag, start_iter, end_iter)
win_width, win_height = self.taskdate_window.get_size()
self.x_pos, self.y_pos, height = self.notebook.get_iter_pos(
self.page_view, win_width, win_height, self.taskdate_window, \
self.task_dates[date_type][DATE_MARK_END_COL])
self.taskdate_window.move(self.x_pos, self.y_pos)
# it is more reasonable to place the cursor to the date which is marked
# as the user would expect the cursor there if ESC is pressed
mark = self.task_dates[date_type][DATE_MARK_END_COL]
if mark:
_iter = self.buf.get_iter_at_mark(mark)
self.buf.place_cursor(_iter)
def do_modify_label_color(self, task_date_tab=None):
'''
if the date displayed in the dialog is greater or less than the cross over date
say: if the displayed start date is greater than the existing due date
'''
start_date = self.start_tab.task_date[DATE_COL]
due_date = self.due_tab.task_date[DATE_COL]
red = gtk.gdk.color_parse("red")
black = gtk.gdk.color_parse("black")
white = gtk.gdk.color_parse("white")
if start_date > due_date and task_date_tab == self.start_tab:
self.notebook.start_label.modify_fg(gtk.STATE_NORMAL, red)
self.due_tab.entry_day.modify_base(gtk.STATE_NORMAL, red)
else:
self.notebook.start_label.modify_fg(gtk.STATE_NORMAL, black)
self.due_tab.entry_day.modify_base(gtk.STATE_NORMAL, white)
if due_date < start_date and task_date_tab == self.due_tab:
self.notebook.due_label.modify_fg(gtk.STATE_NORMAL, red)
self.start_tab.entry_day.modify_base(gtk.STATE_NORMAL, red)
else:
self.notebook.due_label.modify_fg(gtk.STATE_NORMAL, black)
self.start_tab.entry_day.modify_base(gtk.STATE_NORMAL, white)
def insert_dates(self, task_date_tab, both_dates=None, mark_type=None):
'''
insert date of the current tab <task_date_tab> or both dates. mark type is greater or less
'''
_task_date_tab = 0
_mark_type = 1
_mark_types = {'greater': 'date_start', 'less': 'date_due'}
_date_tabs = {'date_start': self.start_tab, 'date_due': self.due_tab}
_date_tabs_cross_over = {'date_start': self.due_tab, 'date_due': self.start_tab}
if both_dates and not self.date_type_key:
# TODO: FIXME: There is a glitch, if a due date exists, start date is
# not changed but SHIFT RETURN is pressed
# at Du date, the start date is deleted. --> Check marks
self._do_insert_date(self.start_tab)
self._do_insert_date(self.due_tab)
if self.plugin_prefs['task_dates_reorder']:
self.reorder_dates()
self.remove_all_marks()
self.taskdate_window.destroy()
return
# check if < or > was used to activate
if self.date_type_key:
entered_mark_type = _mark_types[self.date_type_key]
date_tab_to_use = _date_tabs[entered_mark_type]
self._do_insert_date(date_tab_to_use)
if entered_mark_type != mark_type:
task_date_tab.date_char_needed = True
self._do_insert_date(task_date_tab)
if both_dates:
# just add the other date as well with a date_type char
entered_mark_type = _mark_types[self.date_type_key]
date_tab_to_use = _date_tabs_cross_over[entered_mark_type]
date_tab_to_use.date_char_needed = True
self._do_insert_date(date_tab_to_use)
# if not < or > used then just insert right away
else:
self._do_insert_date(task_date_tab)
# if I'm called from start_tab and due tab was also changed
if task_date_tab == self.start_tab and self.due_tab.date_changed:
self._do_insert_date(self.due_tab)
# if I'm called from due_tab and start_tab was also changed
if task_date_tab == self.due_tab and self.start_tab.date_changed:
self._do_insert_date(self.start_tab)
if self.plugin_prefs['task_dates_reorder']:
self.reorder_dates()
self.remove_all_marks()
self.taskdate_window.destroy()
def _do_insert_date(self, task_date_tab):
'''
insert the date of the current tab
'''
# TODO: Review, which method to be used to get new_date
#new_date = self.check_date_entered(task_date_tab)
_date = task_date_tab.task_date[DATE_COL]
new_date = task_date_tab.put_date_to_format(_date.year, _date.month, _date.day)
### when plugin is started via accel, I need to add the < or > char
if task_date_tab.date_char_needed:
new_date = task_date_tab.date_char + new_date
# if a date already exists, delete it before pasting new date
if not task_date_tab.task_date[DATE_NEW_COL]:
task_date_tab._del_date_at_line(self.buf, task_date_tab.task_date)
pass
self.insert_date_at_cursor(new_date)
return True
def check_date_entered(self, task_date_tab):
'''
This checks if a date was manually entered in one of the entries instead
of using the cursor up / down keys.
With pressing ENTER, the new date is accepted and updated on the dialog first.
2nd ENTER then accepts to paste into buffer
Returns a date string in the right format or a none string
'''
year, month, day, tdplus = task_date_tab.get_all_date_element_data()
new_date = task_date_tab.get_date_with_tdplus(None, tdplus)
year_hist, month_hist, day_hist, tdplus_hist = \
task_date_tab._get_date_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 tdplus == tdplus_hist:
new_date = task_date_tab.put_date_to_format(year, month, day)
return new_date
# something was entered into task_date_plus entry
if tdplus != tdplus_hist:
task_date_tab.task_date_plus = tdplus
new_date = task_date_tab.get_date_with_tdplus(None, tdplus)
task_date_tab.task_date[DATE_COL] = new_date
task_date_tab.date_changed = True
task_date_tab.fill_entries(new_date)
#self.do_select_start_due_on_both_cals()
self.calendars_mark_start_due()
# something was entered in the other entries
else:
task_date_tab.task_date_plus = task_date_tab.calc_tdplus_from_today(year, month, day)
new_date = task_date_tab.get_date_with_tdplus(None, task_date_tab.task_date_plus)
task_date_tab.task_date[DATE_COL] = new_date
task_date_tab.date_changed = True
task_date_tab.fill_entries(new_date)
self.calendars_mark_start_due()
return False
def remove_all_marks(self):
'''
clean up the created marks
'''
if self.buf.get_mark("cursor_orig"):
self.buf.delete_mark_by_name("cursor_orig")
if self.buf.get_mark("date_start_begin"):
self.buf.delete_mark_by_name("date_start_begin")
if self.buf.get_mark("date_start_end"):
self.buf.delete_mark_by_name("date_start_end")
if self.buf.get_mark("date_due_begin"):
self.buf.delete_mark_by_name("date_due_begin")
if self.buf.get_mark("date_due_end"):
self.buf.delete_mark_by_name("date_due_end")
def get_char_left_or_right(self, buf, _iter, n_char, direction):
'''
get the char left or right, omitting n_char, so actually, returning n_char'th char
'''
line_start = False
line_end = False
char = None
def backward_chars(_iter, chars):
'''
move the iter backward n chars
'''
_iter.backward_chars(chars)
def forward_chars(_iter, chars):
'''
move the iter forward n chars
'''
_iter.forward_chars(chars)
function = {"left": backward_chars,
"right": forward_chars}
move_char = function[direction]
if _iter.starts_line():
line_start = True
if _iter.ends_line():
line_end = True
with buf.tmp_cursor(_iter):
buf.place_cursor(_iter)
move_char(_iter, n_char)
buf.place_cursor(_iter)
iter_start = self.buf.get_insert_iter()
move_char(iter_start, 1)
buf.place_cursor(iter_start)
iter_end = buf.get_insert_iter()
char = self.buf.get_slice(_iter, iter_end)
return char, line_start, line_end, iter_start, iter_end
def reorder_dates(self):
'''
if enabled in Preferences, the dates are grouped and reorderd
according to preferences setting
'''
# todo: make this method re-usable (pass prefs as params)
# for other plugins (e.g. 'reorder dates at page')
date_start_begin = None
date_start_begin_iter = None
date_start_end_iter = None
date_due_begin = None
date_due_begin_iter = None
date_due_end_iter = None
self.task_dates = self.get_dates_at_line(self.buf)
dates_pos = self.plugin_prefs['task_dates_pos']
start_date_new = self.task_dates['greater'][DATE_NEW_COL]
due_date_new = self.task_dates['less'][DATE_NEW_COL]
# no reordering needed as there is only one existing date
if start_date_new or due_date_new:
return
# get the iters of all marks
date_start_begin = self.buf.get_mark("date_start_begin")
if date_start_begin:
date_start_begin_iter = self.buf.get_iter_at_mark(date_start_begin)
date_start_end = self.buf.get_mark("date_start_end")
if date_start_end:
date_start_end_iter = self.buf.get_iter_at_mark(date_start_end)
date_due_begin = self.buf.get_mark("date_due_begin")
if date_due_begin:
date_due_begin_iter = self.buf.get_iter_at_mark(date_due_begin)
date_due_end = self.buf.get_mark("date_due_end")
if date_due_end:
date_due_end_iter = self.buf.get_iter_at_mark(date_due_end)
# reorder according to preferences
# TODO: Refactoring of following code
if dates_pos == 'Due date to start date':
# move the due dat to the end mark of the start date
self.group_dates(date_due_begin_iter, date_due_end_iter, "date_start_end")
return
if dates_pos == 'Start date to due date':
# move the start date to the begin mark of the due date
self.group_dates(date_start_begin_iter, date_start_end_iter, "date_due_begin")
return
if dates_pos == 'Dates to line start':
self.group_dates_to_line_border(date_start_begin_iter, date_start_end_iter,
"line_start")
return
if dates_pos == 'Dates to line end':
self.group_dates_to_line_border(date_start_begin_iter, date_start_end_iter,
"line_end")
return
def group_dates(self, date_begin_iter, date_end_iter, date_mark):
'''
reorder due date to start date or vice versa according to
date_mark = "date_start_end" or "date_due_begin"
'''
if date_begin_iter and date_end_iter:
_date = self.buf.get_slice(date_begin_iter, date_end_iter)
if _date:
self.delete_date(self.buf, date_begin_iter, date_end_iter)
# just in case, iter is invalided, get date_start_end again
_date_mark = self.buf.get_mark(date_mark)
if _date_mark:
date_iter = self.buf.get_iter_at_mark(_date_mark)
# date_start_end_iter exists, otherwise no due_date
self.buf.place_cursor(date_iter)
self.insert_date_at_cursor(_date)
if date_mark == "date_due_begin":
# position the cursor at the end of the due date after inserting
_date_mark = self.buf.get_mark(date_mark)
if _date_mark:
date_iter = self.buf.get_iter_at_mark(_date_mark)
# date_start_end_iter exists, otherwise no due_date
self.buf.place_cursor(date_iter)
def group_dates_to_line_border(self, date_start_begin_iter, date_start_end_iter, line_mark):
'''
reorder dates and move to line start or end, depending on
line_mark = "line_start" or "line_end"
'''
if date_start_begin_iter and date_start_end_iter:
start_date = self.buf.get_slice(date_start_begin_iter, date_start_end_iter)
if start_date:
self.delete_date(self.buf, date_start_begin_iter, date_start_end_iter)
else:
start_date = ""
# get iter again after buf.delete
date_due_begin = self.buf.get_mark("date_due_begin")
if date_due_begin:
date_due_begin_iter = self.buf.get_iter_at_mark(date_due_begin)
date_due_end = self.buf.get_mark("date_due_end")
if date_due_end:
date_due_end_iter = self.buf.get_iter_at_mark(date_due_end)
if date_due_begin_iter and date_due_end_iter:
due_date = self.buf.get_slice(date_due_begin_iter, date_due_end_iter)
if due_date:
self.delete_date(self.buf, date_due_begin_iter, date_due_end_iter)
else:
due_date = ""
line_border_mark = self.buf.get_mark(line_mark)
if line_border_mark:
line_border_iter = self.buf.get_iter_at_mark(line_border_mark)
self.buf.place_cursor(line_border_iter)
# need to go one char backward in order not to move linefeed
if line_mark == "line_end":
line_border_iter.backward_char()
self.buf.place_cursor(line_border_iter)
self.insert_date_at_cursor(start_date)
self.insert_date_at_cursor(due_date)
def delete_date(self, buf, start_iter, end_iter):
'''
The start and end iter do not include any spaces.
Usually there is a space before and afte a date.
This has to be deleted as well, if a date is moved
'''
start = start_iter
end = end_iter
# nothing to delete
if start == end:
return
left_char, line_start, line_end, lchar_iter_start, lchar_iter_end = \
self.get_char_left_or_right(buf, start_iter, 0, "left")
if left_char == " ":
start = lchar_iter_start
right_char, line_start, line_end, rchar_iter_start, rchar_iter_end = \
self.get_char_left_or_right(buf, end_iter, 0, "right")
# just want to remove one space.
if right_char == " " and left_char != " ":
end = rchar_iter_end
buf.delete(start, end)
return
def insert_date_at_cursor(self, date):
'''
insert the given date at the position of the cursor
'''
buf = self.buf
new_date = date
_iter = buf.get_iter_at_mark(buf.get_insert())
line_start, line_end = buf.get_line_bounds(_iter.get_line())
left_char, line_start, line_end, lchar_iter_start, lchar_iter_end = \
self.get_char_left_or_right(buf, _iter, 0, "left")
# found space or line start / line end
if left_char == " " or line_start or left_char in TASK_CHAR:
pass
else:
new_date = " " + new_date
right_char, line_start, line_end, rchar_iter_start, rchar_iter_end = \
self.get_char_left_or_right(buf, _iter, 0, "right")
if right_char == " " or line_end:
pass
else:
new_date = new_date + " "
buf.insert_at_cursor(new_date)
return
def get_dates_at_line(self, buf):
'''
identify the dates at a line and put them
into the task_dates dict
'''
_dates = {}
start_date = False
start_date_format = False
due_date = False
due_date_format = 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_orig_mark = buf.get_mark("cursor_orig")
cursor = buf.get_iter_at_mark(buf.get_insert())
new_date_cursor = buf.get_insert_iter()
new_date_mark = self.buf.create_mark("new_date", new_date_cursor)
date_today = zim_datetime.date.today()
start_date_plus = date_today + \
zim_datetime.timedelta(days=self.plugin_prefs['start_date_plus'])
due_date_plus = date_today + \
zim_datetime.timedelta(days=self.plugin_prefs['start_date_plus']) + \
zim_datetime.timedelta(days=self.plugin_prefs['due_date_plus'])
# if there is nothing but a linefeed, just return, nothing to check
if cursor.ends_line() and cursor.starts_line():
_dates['greater'] = (start_date_plus,
self.preferred_date_format,
True, cursor_orig_mark, cursor_orig_mark)
_dates['less'] = (due_date_plus, self.preferred_date_format,
True, cursor_orig_mark, cursor_orig_mark)
return _dates
#not working as expected...
#line_start, line_end = buffer.get_line_bounds(cursor.get_line())
# 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
buf.place_cursor(cursor)
line_start = buf.get_iter_at_mark(buf.get_insert())
self.line_start = line_start
# move to end of line and then start search backwards.
cursor.forward_to_line_end()
buf.place_cursor(cursor)
line_end = buf.get_iter_at_mark(buf.get_insert())
self.line_end = line_end
text = buf.get_text(line_start, line_end)
# now put the cursor back to the original place,
# if there was one (not if line start)
cursor_orig_mark = self.buf.get_mark("cursor_orig")
if cursor_orig_mark:
cursor_orig = self.buf.get_iter_at_mark(cursor_orig_mark)
self.buf.place_cursor(cursor_orig)
date_tuple = self._date_from_text(text)
start_date = date_tuple['greater'][_DATE_COL]
start_date_str = date_tuple['greater'][_DATE_STRING_COL]
start_date_format = date_tuple['greater'][_DATE_FORMAT_COL]
start_date_new = date_tuple['greater'][_DATE_NEW_COL]
due_date = date_tuple['less'][_DATE_COL]
due_date_str = date_tuple['less'][_DATE_STRING_COL]
due_date_format = date_tuple['less'][_DATE_FORMAT_COL]
due_date_new = date_tuple['less'][_DATE_NEW_COL]
if not self.plugin_prefs['task_date_format_inherit']:
start_date_format = self.preferred_date_format
due_date_format = self.preferred_date_format
if start_date_new:
_dates['greater'] = (
start_date_plus,
start_date_format,
start_date_new,
new_date_mark,
new_date_mark)
else:
start_start_mark_begin, start_date_mark_end = \
self.get_date_marks_at_line(start_date_str, cursor, buf, line_start, "date_start")
date_strf = self.put_date_str_into_date_strf(start_date, start_date_format)
_start_date = self.put_date_strf_into_datetime(start_date)
_dates['greater'] = (
_start_date,
start_date_format,
start_date_new,
start_start_mark_begin,
start_date_mark_end)
if due_date_new:
_dates['less'] = (
due_date_plus,
due_date_format,
due_date_new,
new_date_mark,
new_date_mark)
else:
due_date_mark_begin, due_date_mark_end = \
self.get_date_marks_at_line(due_date_str, cursor, buf, line_start, "date_due")
date_strf = self.put_date_str_into_date_strf(due_date, due_date_format)
_due_date = self.put_date_strf_into_datetime(due_date)
_dates['less'] = (
_due_date,
due_date_format,
due_date_new,
due_date_mark_begin,
due_date_mark_end)
return _dates
def get_date_marks_at_line(self, date_str, cursor, buf, line_start, date_type):
'''
get the created marks of a date
'''
if not date_str:
return None, None
# this returns to iters as a tuple, match_start, match_end
date_str_begin_iter = cursor.backward_search(date_str, \
gtk.TEXT_SEARCH_TEXT_ONLY, limit=line_start)
if date_str_begin_iter:
date_str_end_iter = cursor.backward_search(date_str, \
gtk.TEXT_SEARCH_TEXT_ONLY, limit=line_start)
# put the cursor to the end of the existing due date entry,
# so the entry or calendar can be shown there
mark_begin = buf.create_mark(date_type + "_begin", date_str_begin_iter[0])
mark_end = buf.create_mark(date_type + "_end", date_str_end_iter[1])
return mark_begin, mark_end
return None, None
def put_date_strf_into_datetime(self, date_str):
'''
put string into datetime.date object
'''
if not date_str:
return False
plain_format = '%Y-%m-%d'
plain_date = date_str.strip("[d: ").strip("]")
_date = zim_datetime.datetime.strptime(plain_date, plain_format).date()
return _date
def put_date_str_into_date_strf(self, date_str, fmt):
'''
put the date string into string format
'''
date = zim_datetime.datetime.strptime(date_str, '%Y-%m-%d')
return date.strftime(fmt)
def _date_from_text(self, text):
'''
return the date record for single line of text
'''
start = ""
start_str = ""
due_str = ""
due = ""
# I want to show a date in start or due even if it is not added yet
# the new start / due dates according to preferences
start_new = False
due_new = False
for string in _date_re.findall(text):
if string.startswith('[d:'): # backward compat
date = old_parse_date(string[3:-1].strip())
if date:
(year, month, day) = date
due = zim_datetime.date(year, month, day).isoformat()
#due = '%04i-%02i-%02i' % date # (y, m, d)
due_str = string
elif string.startswith('>'):
start_date = parse_date(string[1:]).first_day
start = parse_date(string[1:]).first_day.isoformat()
start_str = string
elif string.startswith('<'):
due = parse_date(string[1:]).last_day.isoformat()
due_str = string
else:
pass # Huh !?
# todo: re-think the following code-block when doing code cleaning
if start_str:
format_start = self._get_existing_date_format(start_str)
else:
format_start = self.preferred_date_format
if due_str:
format_due = self._get_existing_date_format(due_str)
else:
format_due = self.preferred_date_format
date_today = zim_datetime.date.today()
start_date_new = date_today + \
zim_datetime.timedelta(days=self.plugin_prefs['start_date_plus'])
if start:
# if a start date already exists, take this and add x days from prefs to due date
due_date_new = start_date + \
zim_datetime.timedelta(days=self.plugin_prefs['due_date_plus'])
else:
# if no start date, take today and add both x days from prefs to due date
due_date_new = date_today + \
zim_datetime.timedelta(days=self.plugin_prefs['start_date_plus']) + \
zim_datetime.timedelta(days=self.plugin_prefs['due_date_plus'])
if not due:
due = due_date_new.isoformat()
due_str = ""
# inheritence is checked from above
format_due = format_start
due_new = True
if not start:
start = start_date_new.isoformat()
start_str = ""
# inheritence is checked from above
format_start = format_due
start_new = True
return {'greater': (start, start_str, format_start, start_new), \
'less': (due, due_str, format_due, due_new)}
def _get_existing_date_format(self, text):
'''
get the given date format for the date provided as text
'''
if not text:
return None
# get the format from preferences, which is the current date in the selected format
selected_format_today = self.plugin_prefs['task_date_format']
# now get the real format from the dict
fmt = ALL_FORMATS_REAL_FORMAT[selected_format_today]
# respect the order as otherwise format is always the one with the greatest re match!
for string in DATE_RE_1.findall(text):
fmt = FORMAT_1
for string in DATE_RE_2.findall(text):
fmt = FORMAT_2
for string in DATE_RE_3_4.findall(text):
fmt = FORMAT_3_4
for string in DATE_RE_3_3.findall(text):
fmt = FORMAT_3_3
for string in DATE_RE_3_2.findall(text):
fmt = FORMAT_3_2
for string in DATE_RE_3_1.findall(text):
fmt = FORMAT_3_1
for string in DATE_RE_4_2.findall(text):
fmt = FORMAT_4_2
for string in DATE_RE_4_1.findall(text):
fmt = FORMAT_4_1
return fmt
def print_data(self, *args):
'''
to print a parameter,
pass a list with [the name of the param as a string and the param itself]
'''
method_name = str(sys._getframe().f_back.f_code.co_name)
print '________________________________________'
print 'method: ', method_name
print '________________________________________'
for arg in args:
if arg == self:
continue
print 'Argument: ', arg[0]
print '________________________________________'
print arg[1]
print '________________________________________'
SHIFT = ('Shift_L', 'Shift_R')
DATE_COL = 0
DATE_FORMAT_COL = 1
DATE_NEW_COL = 2
DATE_MARK_START_COL = 3
DATE_MARK_END_COL = 4
ARROW_FUNC = 0
ARROW_ENTRY = 1
ARROW_TDPLUS = 2
YEAR_IDX = 1
MONTH_IDX = 2
DAY_IDX = 3
TD_PLUS_IDX = 4
WEEK_NO_IDX = 5
class TaskDateTab(object):
'''
Create and handle all entries and the calendar within the notebook page
'''
# pylint: disable=unused-argument, unused-variable, invalid-name, no-member
def __init__(self, window, plugin_preferences, \
_date, \
page_vb, task_date_window, notebook, task_date_class, \
date_char_needed, date_char, mark_type):
#self.date_type_key = date_type_key
self.window = window
self.page_view = self.window.pageview.view
self.buf = self.page_view.get_buffer()
self.plugin_prefs = plugin_preferences
self.exist_due_date = _date[DATE_COL]
self.task_date = _date
self.new_date = ""
self.cursor_orig_mark = self.buf.get_mark("cursor_orig")
self.cursor_orig = self.buf.get_iter_at_mark(self.cursor_orig_mark)
# Flag to check, if date was changed
self.date_changed = False
self.cursor = self.buf.get_iter_at_mark(self.buf.get_insert())
self.page_vb = page_vb
self.format = _date[DATE_FORMAT_COL]
self.task_date_window = task_date_window
self.notebook = notebook
self.date_char_needed = date_char_needed
self.date_char = date_char
self.mark_type = mark_type
self.entry_day = InputEntry()
self.widget_focus_chain = []
self.grey_tag = self.buf.create_tag(background="grey")
self.red_tag = self.buf.create_tag(background="red")
self.task_date_tab = self
self.task_date_class = task_date_class
self.expanded = False
'''
[0: current_date_element, 1: prev_date_element]
1: year, 2: month, 3: day, 4: task_date_plus,
5: week_no (not really used any more from history)
'''
self.entry_history = {
1: [0],
2: [0],
3: [0],
4: [0],
5: [0]
}
self.cal_expander = gtk.expander_new_with_mnemonic(label="_Calendar")
self.cal_expander.set_use_underline(True)
self.calendar = gtk.Calendar()
self.calendar.display_options(gtk.CALENDAR_SHOW_WEEK_NUMBERS | \
gtk.CALENDAR_SHOW_HEADING | gtk.CALENDAR_SHOW_DAY_NAMES)
self.cal_expander.add(self.calendar)
self.label_day_name = gtk.Label()
self.entry_hbox = gtk.HBox()
self.entry_year = InputEntry()
self.entry_year.set_max_length(4)
self.entry_year.set_width_chars(4)
self.entry_month = InputEntry()
self.entry_month.set_max_length(2)
self.entry_month.set_width_chars(2)
self.entry_day.set_max_length(2)
self.entry_day.set_width_chars(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_task_date_plus = gtk.Label()
self.label_task_date_plus.set_text('Today + ')
self.entry_task_date_plus = InputEntry()
self.entry_task_date_plus.set_max_length(6)
self.entry_task_date_plus.set_width_chars(6)
self.task_date_plus_hbox = gtk.HBox()
self.task_date_plus = 0
def show_task_date_tab(self):
'''
Show the task date dialog
'''
self.task_date_plus_hbox.pack_start(self.label_task_date_plus, expand=False, padding=2)
self.task_date_plus_hbox.pack_start(self.entry_task_date_plus, expand=True, padding=2)
self.page_vb.pack_start(self.label_day_name, expand=False, fill=False, padding=0)
self.page_vb.pack_start(self.entry_hbox, expand=False, fill=False, padding=0)
self.page_vb.pack_start(self.task_date_plus_hbox, expand=False, fill=False, padding=2)
self.page_vb.pack_start(self.cal_expander, expand=False, fill=False, padding=2)
self.widget_focus_chain.append(self.entry_day)
self.page_vb.set_focus_chain(self.widget_focus_chain)
def _do_up_down_arrow_key(arrow_key_dict):
'''
increase / decrease day, month, year, taskdateplus
'''
# get the elements from the dict / list
function = arrow_key_dict[ARROW_FUNC]
entry = arrow_key_dict[ARROW_ENTRY]
tdplus_calc_value = arrow_key_dict[ARROW_TDPLUS]
# call the right function from the list with its params
function(tdplus_calc_value, entry)
# in case someone enters a new day and then directly uses Up arrow key
self._update_task_date_plus()
# put back focus to the correct entry as it's set to entry_task_date_plus
# in above function
entry.grab_focus()
# Important to return True to prevent unwanted behaviour on focus chain
return True
def _do_left_right_arrow_key(arrow_key_dict):
'''
Position the cursor from one entry to the other (day - month - year - taskdateplus)
'''
function = arrow_key_dict[ARROW_FUNC]
entry = arrow_key_dict[ARROW_ENTRY]
entry.grab_focus()
#select data in entry
function(0, -1)
def _do_cal_arrow_keys(value, widget):
'''
handle the calendar arrow keys
'''
# left / right / up / down
self.task_date_plus += value
new_date = self.get_date_with_tdplus(None, self.task_date_plus)
self.calendar.select_month(new_date.month-1, new_date.year)
self.calendar.select_day(new_date.day)
self.entry_key_inputs = {
# 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...
65362 : (_do_up_down_arrow_key, {
'day' : [self.on_day_and_tdplus, self.entry_day, 1], # up arrow key
'month' : [self.on_month, self.entry_month, 1],
'year' : [self.on_year, self.entry_year, 1],
'ddplus': [self.on_day_and_tdplus, self.entry_task_date_plus, 1]}),
65364 : (_do_up_down_arrow_key, {
'day' : [self.on_day_and_tdplus, self.entry_day, -1], # down arrow key
'month' : [self.on_month, self.entry_month, -1],
'year' : [self.on_year, self.entry_year, -1],
'ddplus': [self.on_day_and_tdplus, self.entry_task_date_plus, -1]}),
65361 : (_do_left_right_arrow_key, {
# when event comes from entry_day, I need to go to month (left)
'day' : [self.entry_month.select_region, self.entry_month],
# when event comes from entry_month, I need to go to year (left)
'month' : [self.entry_year.select_region, self.entry_year],
# when event comes from entry_year, I need to go to ddplus (left)
'year' : [self.entry_task_date_plus.select_region, self.entry_task_date_plus],
# when event comes from entry_task_date_plus, I need to go to day (left)
'ddplus': [self.entry_day.select_region, self.entry_day]}),
65363 : (_do_left_right_arrow_key, {
# when event comes from entry_day, I need to go to entry_task_date_plus (right)
'day' : [self.entry_task_date_plus.select_region, self.entry_task_date_plus],
# when event comes from entry_task_date_plus, I need to go to entry_year (right)
'ddplus': [self.entry_year.select_region, self.entry_year],
# when event comes from entry_year, I need to go to entry_month (right)
'year' : [self.entry_month.select_region, self.entry_month],
# when event comes from entry_month, I need to go to entry_day (right)
'month' : [self.entry_day.select_region, self.entry_day]})
}
self.cal_key_inputs = {
65361 : (_do_cal_arrow_keys, -1), # left arrow key
65363 : (_do_cal_arrow_keys, +1), # right arrow key
65362 : (_do_cal_arrow_keys, -7), # up arrow key
65364 : (_do_cal_arrow_keys, +7) # down arrow key
}
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.show_entries()
self.show_cal()
if self.plugin_prefs['task_date_cal']:
self.cal_expander.set_expanded(True)
def show_entries(self):
'''
put the date data into the entries
'''
self.fill_entries(self.exist_due_date)
self.entry_day.connect("key_press_event", self.on_keypress, "day")
self.entry_month.connect("key_press_event", self.on_keypress, "month")
self.entry_year.connect("key_press_event", self.on_keypress, "year")
self.entry_task_date_plus.connect(
"key_press_event", self.on_keypress, "ddplus")
self.entry_year.connect("event", self.on_event, self.entry_year)
self.entry_month.connect("event", self.on_event, self.entry_month)
self.entry_day.connect("event", self.on_event, self.entry_day)
self.entry_task_date_plus.connect(
"event", self.on_event, self.entry_task_date_plus)
def _show_day_name(self, year, month, day):
'''
Display the name of the 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):
'''
Show the calendar
'''
due_date = self.get_date_with_tdplus()
# due_date.month-1 as calendar starts counting with 0 as January ....
self.calendar.select_month(due_date.month - 1, due_date.year)
self.calendar.select_day(due_date.day)
#self._calendar_mark_today()
self._cal_mark_existing_start_due()
self.calendar.show_all()
# need to connect after to ensure that day is selected.
# Connecting to day-selected leads to
# failure in left / right key handling...
self.calendar.connect_after(
"button-press-event", self._do_select_cal_day)
#self.calendar.connect("day-selected-double-click", self.do_insert_selected_cal_date)
# emulating SHIFT Return for double click to re-use self.on_keypress
self.calendar.connect_after(
'key_press_event', self.on_keypress, "day")
self.calendar.connect("prev-month", self._do_select_cal_date)
self.calendar.connect("next-month", self._do_select_cal_date)
self.calendar.connect("prev-year", self._do_select_cal_date)
self.calendar.connect("next-year", self._do_select_cal_date)
self.cal_expander.connect("notify::expanded", self.on_expander, self.task_date_tab)
def _cal_mark_existing_start_due(self):
'''
Mark the days of already existing dates within the calendar
(First store the dates into the cal_store)
'''
start_date = self.task_date_class.task_dates['greater'][DATE_COL]
due_date = self.task_date_class.task_dates['less'][DATE_COL]
self.calendar_mark_days(start_date, due_date)
def calendar_mark_days(self, start_date=None, due_date=None):
'''
Mark the selected start and due dates
which are visible in the calendar
Buffer to store the selected start / due dates
'''
# to get the month currently shown.
(year, month, day) = self.calendar.get_date()
if not start_date:
start_date = self.task_date_class.start_tab.task_date[DATE_COL]
if not due_date:
due_date = self.task_date_class.due_tab.task_date[DATE_COL]
diff = due_date - start_date
self.task_date_class.marked_days.clear()
for d in range(diff.days + 1):
_date = (start_date + zim_datetime.timedelta(d))
self.task_date_class.marked_days.add(_date)
self.calendar.clear_marks()
# calendar month correction
month += 1
for _date in self.task_date_class.marked_days:
if _date.month == month and _date.year == year:
# only mark visible days (of the month / year displayed)
self.calendar.mark_day(_date.day)
def on_expander(self, expander, param_spec, tab_orig):
'''
handle the expander for the calendar
'''
if expander.get_expanded():
# if one cal is expanded, expand the other as well. Check AttributeError if
# the other tab is not yet available
try:
for tab in self.task_date_class.tab_list:
self.do_expander(tab.cal_expander, tab, tab_orig, do_expand=True)
except AttributeError:
self.do_expander(self.cal_expander, self.task_date_tab, tab_orig, do_expand=True)
else:
# so the other tab is folded, do this with the other as well
try:
for tab in self.task_date_class.tab_list:
self.do_expander(tab.cal_expander, tab, tab_orig, do_expand=False)
except AttributeError:
self.do_expander(self.cal_expander, self.task_date_tab, tab_orig, do_expand=False)
def do_expander(self, expander, tab, tab_orig, do_expand):
'''
handle the expander
'''
# FIXME: There is a glitch that the start calendar disappears
# when tab is used forth and back
#xxx
if do_expand:
if not expander.child:
expander.add(tab.calendar)
# do the other tab
if tab != tab_orig:
expander.set_expanded(True)
# need to resposition as the cal might show outside the sreen otherwise
x_pos, y_pos, height = \
self.notebook.get_iter_pos(self.page_view, EXP_WIN_WIDTH, EXP_WIN_HEIGHT,
self.task_date_window,
tab_orig.task_date[DATE_MARK_END_COL])
self.task_date_window.move(x_pos, y_pos)
#self.cal.show_all()
# fold
else:
if expander.child:
expander.remove(expander.child)
if tab != tab_orig:
expander.set_expanded(False)
self.task_date_window.resize(SMALL_WIN_WIDTH, SMALL_WIN_HEIGHT)
# need to reposition, as the window will be smaller
x_pos, y_pos, height = \
self.notebook.get_iter_pos(self.page_view, SMALL_WIN_WIDTH, SMALL_WIN_HEIGHT,
self.task_date_window,
tab_orig.task_date[DATE_MARK_END_COL])
self.task_date_window.move(x_pos, y_pos)
#self.cal.show_all()
#def _show_date_element_in_entry(self, entry, date_element, history_date_index):
# '''
# data 0: current_date_element,
# data 1: prev_date_element]
# index 1: year, 2: month, 3: day, 4: task_date_plus
# '''
# self._put_date_element_into_hist(date_element, history_date_index)
# entry.set_text(str(date_element))
def show_all_date_data(self, year, month, day, tdplus, focus_entry=None):
'''
show all validated data in entries and put data into history
'''
year, month, day, tdplus = self._validate_date_element_data(year, month, day, tdplus)
# only validated data should be in history...
self._put_date_into_hist(year, month, day, tdplus)
week_no = self._get_iso_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.task_date_plus = self.calc_tdplus_from_today(year, month, day)
self.entry_year.set_text(str(year))
self.entry_month.set_text(str(month))
self.entry_day.set_text(str(day))
self.entry_task_date_plus.set_text(str(self.task_date_plus))
self.entry_week_no.set_text(str(week_no))
self._put_date_into_hist(year, month, day, self.task_date_plus)
if focus_entry:
focus_entry.grab_focus()
focus_entry.select_region(0, -1)
def _get_iso_week_no(self, year, month, day):
'''
return the iso wekk number
'''
date = self.get_specific_date(year, month, day)
iso_year, iso_week, iso_weekday = date.isocalendar()
return iso_week
def _validate_date_element_data(self, year, month, day, tdplus):
'''
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
'''
YEAR = 1
MONTH = 2
DAY = 3
TD_PLUS = 4
WEEK_NO = 5
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[YEAR]
try:
month = int(month)
except ValueError:
month = self.entry_history[MONTH]
try:
day = int(day)
except ValueError:
day = self.entry_history[DAY]
try:
tdplus = int(tdplus)
except ValueError:
tdplus = self.entry_history[TD_PLUS]
# identify, if data was passed. If None or empty, get history data
if year == 0:
year = self.entry_history[YEAR]
if month == 0:
month = self.entry_history[MONTH]
if day == 0:
day = self.entry_history[DAY]
# 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, tdplus
def _put_date_into_hist(self, year, month, day, tdplus):
'''
The history is needed if user removes data from entry and then
press enter or clicks to another entry
index 1: year, 2: month, 3: day, 4: task_date_plus, 5: week_no not needed any more
'''
list_of_elements = [year, month, day, tdplus]
for index in range(0, len(list_of_elements)):
self.entry_history[index+1] = list_of_elements[index]
def print_history(self, tab_name=None):
'''
to check if history is working correct
'''
index_name = {1: "Year ",
2: "Month ",
3: "Day ",
4: "tdplus",
5: "weekno"}
method_name = str(sys._getframe().f_back.f_code.co_name)
#for index in range(1, 5):
# for level in range(len(self.entry_history[index])):
# print '{0}, Level: {1}, data: , {2}'.format(
# index_name[index], level, self.entry_history[index][level])
print "History"
print '________________________________________'
print 'method: ', method_name
print 'Tab: ', tab_name
print '________________________________________'
for index in range(1, 6):
print '{0}: {1}'.format(
index_name[index], self.entry_history[index])
def print_data(self, *args):
'''
to print a parameter,
pass a list with [the name of the param as a string and the param itself]
'''
method_name = str(sys._getframe().f_back.f_code.co_name)
print '________________________________________'
print time.strftime("%a, %d %b %Y %H:%M:%S")
print '________________________________________'
print 'method: ', method_name
print '________________________________________'
for arg in args:
if arg == self:
continue
print ' Argument: ', arg[0]
print '________________________________________'
print " ", arg[1]
print '________________________________________'
def calc_tdplus_from_today(self, year, month, day):
'''
calculate taskdate_plus (the number of days from today)
assign the return value to self.task_date_plus!
'''
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.task_date_plus = date_diff.days
return date_diff.days
def on_event(self, widget, event, entry):
'''
in case someone enters new data into the other entries
and then directly clicks into task_date_plus entry
'''
if event.type == gtk.gdk.BUTTON_RELEASE:
self._update_task_date_plus()
entry.grab_focus()
def _update_task_date_plus(self):
'''
update the task date plus entry according to the changed dates
'''
year, month, day, tdplus = self.get_all_date_element_data()
self.task_date_plus = self.calc_tdplus_from_today(year, month, day)
new_date = self.get_date_with_tdplus(None, self.task_date_plus)
self.fill_entries(new_date)
#self.show_all_date_data(year, month, day, tdplus, focus_entry=self.entry_task_date_plus)
def _do_insert_selected_cal_date(self, calendar):
'''
not used
'''
# TODO: rather use this than emulate a signal?
(year, month, day) = calendar.get_date()
date = self.get_specific_date(year, month + 1, day)
self.fill_entries(date)
self.task_date_class.insert_dates(self.task_date_tab, both_dates=True)
def _do_select_cal_date(self, calendar):
'''
Handle click event on calendar arrows.
'''
# As the calendar is changed on click, I can't re-use picture up / down method
(year, month, day) = calendar.get_date()
date = self.get_specific_date(year, month + 1, day)
if date < self.get_specific_date(1900, 1, 1):
date = self.get_specific_date(1900, 1, 1)
calendar.select_month(1, 1900)
calendar.select_day(1)
self.calendar_mark_days()
#self.fill_entries(date, self.entry_day)
#self.entry_day.grab_focus()
def _do_select_cal_day(self, widget, event):
'''
This method is initially added because listening to day-selected signal
led to failure in left / right key handling.
I addition it handles the selection of of a start / due date with the
mouse click together with the SHIFT key
'''
# TODO: make SPACE and SHIFT SPACE same as mouse click and SHIFT mouse click
date_tab_identifier = {self.task_date_class.start_tab: "start", \
self.task_date_class.due_tab: "due"}
shift_selected = False
(year, month, day) = widget.get_date()
_date = self.get_specific_date(year, month + 1, day)
# SHIFT and mouse one click
if event.get_state() & gtk.gdk.SHIFT_MASK:
shift_selected = True
# NO SHIFT, then just fill the entries
else:
self.fill_entries(_date)
# with SHIFT means, that a new end date (start or due date) has to be set, depending
# on which tab the user has selected the date from calendar
first_selected_date = self.task_date[DATE_COL]
self._do_cal_with_shift(first_selected_date, _date)
self.task_date_class.calendars_mark_start_due()
# grab the focus again to ensure that user can use ENTER key to set the dates
self.entry_day.grab_focus()
## SHIFT and mouse double click --> insert both dates
if event.get_state() & gtk.gdk.SHIFT_MASK and event.type == gtk.gdk._2BUTTON_PRESS:
# emulate SHIFT Return to re-use method
key_event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
key_event.keyval = gtk.keysyms.Return
key_event.state = gtk.gdk.SHIFT_MASK
key_event.time = 0 # assign current time
# widget = calendar
widget.emit('key_press_event', key_event)
return
# # No SHIFT and mouse double click - insert date
if not event.get_state() & gtk.gdk.SHIFT_MASK and event.type == gtk.gdk._2BUTTON_PRESS:
key_event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
key_event.keyval = gtk.keysyms.Return
key_event.state = 0
key_event.time = 0 # assign current time
# widget = calendar
widget.emit('key_press_event', key_event)
return
return
def _do_cal_with_shift(self, first_selected_date, shift_selected_date):
'''
mark the start and the due date in both calendars,
no matter in which order they were clicked
with the mouse and SHIFT mouse
'''
if first_selected_date == shift_selected_date:
self.task_date_class.start_tab.date_changed = False
self.task_date_class.due_tab.date_changed = False
return
# user selects first the start date then due date
if first_selected_date < shift_selected_date:
start_date = first_selected_date
due_date = shift_selected_date
# user selects first the due date then the start date
else:
start_date = shift_selected_date
due_date = first_selected_date
# mark days at both calendars
self.task_date_class.start_tab.calendar_mark_days(
start_date, due_date)
self.task_date_class.due_tab.calendar_mark_days(
start_date, due_date)
self.task_date_class.start_tab.fill_entries(start_date)
self.task_date_class.start_tab.date_changed = True
self.task_date_class.due_tab.fill_entries(due_date)
self.task_date_class.due_tab.date_changed = True
def put_date_to_format(self, year, month, day):
'''
put the date into the correct format
'''
date = self.get_specific_date(year, month, day)
(iso_year, iso_week, iso_day) = date.isocalendar()
# as strftime does not return the correct values on week no and week day,
# I need to put the format together myself
if self.format == FORMAT_3_1:
date_fmt_iso = str(iso_year) + "W" + str(iso_week)
elif self.format == FORMAT_3_2:
date_fmt_iso = str(iso_year)[2:] + "W" + str(iso_week)
elif self.format == FORMAT_3_3:
date_fmt_iso = str(iso_year)[2:] + "-W" + str(iso_week)
elif self.format == FORMAT_3_4:
date_fmt_iso = str(iso_year)[2:] + "-W" + str(iso_week).zfill(2) + "-" + str(iso_day)
elif self.format == FORMAT_4_1:
date_fmt_iso = "wk" + str(iso_year)[2:] + str(iso_week)
elif self.format == FORMAT_4_2:
date_fmt_iso = "wk" + str(iso_year)[2:] + str(iso_week).zfill(2) + "." + str(iso_day)
else:
date_fmt_iso = date.strftime(self.format)
return date_fmt_iso
def fill_entries(self, date=None, focus_entry=None):
'''
put the date into the entries and show all date data,
including adding data into history
focus on given entry
(self.entry_year, self.entry_month, self.entry_day, self.entry_tdplus)
'''
if not date:
date = self.get_date_with_tdplus()
self.show_all_date_data(
date.year, date.month, date.day,
self.task_date_plus,
focus_entry=self.entry_day)
self.task_date[DATE_COL] = date
self._select_cal_day()
def _select_cal_day(self):
date = self.task_date[DATE_COL]
self.calendar.select_month(date.month - 1, date.year)
self.calendar.select_day(date.day)
pass
def on_keypress(self, widget, event, entry_type):
'''
entry_type = day, month, year, tdplus
'''
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])
self.do_calendar(event)
#self.cal_mark_existing_start_due()
#self.task_date_class.do_select_start_due_on_both_cals()
self.task_date_class.calendars_mark_start_due()
# Return True in order to suppress focus change
return True
except KeyError:
# all other keys:'c' or ESC or Pos1
self.do_events_common_key(event.keyval, event)
return
def do_events_common_key(self, keyval, event):
'''
handle all other keys but the cursor keys
'''
shift_mod = event.state & gtk.gdk.SHIFT_MASK
#print gtk.gdk.keyval_name(keyval)
self.buf = self.window.pageview.view.get_buffer()
if keyval == 65360: # Pos1 key
# reset entries to today
self.task_date_plus = 0
# TODO: Find out, why data is not selected in day entry
self.fill_entries()
self.do_calendar(event)
if event.keyval == 65365: # Picture up key
self.do_picture_up()
if event.keyval == 65366: # Picture down key
self.do_picture_down()
elif keyval == gtk.gdk.keyval_from_name('Tab'):
pass
elif shift_mod or gtk.gdk.keyval_name(event.keyval) in SHIFT:
# to prevent that SHIFT code is added to buffer.
# Don't know if there is another way to handle this
if gtk.gdk.keyval_name(event.keyval) in SHIFT:
return
# TODO: Also review, which method to be used
#self.new_date = self.task_date_class.check_date_entered(self.task_date_tab)
_new_date = self.task_date[DATE_COL]
self.new_date = self.put_date_to_format(_new_date.year, _new_date.month, _new_date.day)
if not self.new_date:
return
elif keyval == gtk.gdk.keyval_from_name('Return'):
self.task_date_class.insert_dates(
self.task_date_tab, 'both', mark_type=self.mark_type)
return
elif keyval == gtk.gdk.keyval_from_name('Return'):
self.new_date = self.task_date_class.check_date_entered(self.task_date_tab)
if not self.new_date:
new_date = self.get_date_with_tdplus()
self.calendar.select_month(new_date.month - 1, new_date.year)
self.calendar.select_day(new_date.day)
return
self.task_date_class.insert_dates(
self.task_date_tab, None, mark_type=self.mark_type)
return
def do_picture_up(self):
(year, month, day) = self.calendar.get_date()
next_month = month + 1
if next_month == 12:
next_month = 0
year = year + 1
self.calendar.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.calendar.select_day(day)
(year, month, day) = self.calendar.get_date()
date = self.get_specific_date(year, month + 1, day)
self.fill_entries(date, focus_entry=self.entry_month)
self.date_changed = True
self.calendar_mark_days()
self.task_date_class.do_tag_date_at_line(date, self.task_date_tab)
self.task_date_class.do_modify_label_color(self.task_date_tab)
def do_picture_down(self):
'''
Increase / Decrease month
'''
(year, month, day) = self.calendar.get_date()
prev_month = month - 1
if prev_month == -1:
prev_month = 11
year = year - 1
self.calendar.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.calendar.select_day(day)
(year, month, day) = self.calendar.get_date()
date = self.get_specific_date(year, month + 1, day)
self.fill_entries(date, focus_entry=self.entry_month)
self.date_changed = True
self.calendar_mark_days()
self.task_date_class.do_tag_date_at_line(date, self.task_date_tab)
self.task_date_class.do_modify_label_color(self.task_date_tab)
def on_day_and_tdplus(self, value, entry):
'''
handle key press on the day and taskdate plus entry
'''
self.task_date_plus += value
new_date = self.get_date_with_tdplus(None, self.task_date_plus)
self.fill_entries(new_date)
self.date_changed = True
self.task_date[DATE_COL] = new_date
self.task_date_class.do_tag_date_at_line(new_date, self.task_date_tab)
self.task_date_class.do_modify_label_color(self.task_date_tab)
#self.task_date_class.do_select_start_due_on_both_cals()
self.task_date_class.calendars_mark_start_due()
def on_month(self, value, entry):
'''
handle key press on the month entry
'''
#year, month, day, task_date_plus = self.get_all_date_element_data()
#self.task_date_plus += value * self.get_last_day_of_month(year, month)
#new_date = self.get_date_with_tdplus(None, self.task_date_plus)
#self.fill_entries(new_date)
#self.date_changed = True
#self.task_date[DATE_COL] = new_date
if value > 0:
self.do_picture_up()
else:
self.do_picture_down()
def on_year(self, value, entry):
'''
handle key press on the year entry
'''
year, month, day, task_date_plus = self.get_all_date_element_data()
days_per_year = self.get_yday(year, 12, 31)
self.task_date_plus += value * days_per_year
new_date = self.get_date_with_tdplus(None, self.task_date_plus)
self.fill_entries(new_date)
self.calendar_mark_days()
#self.show_all_date_data(new_date.year, new_date.month,\
# new_date.day, self.task_date_plus, focus_entry=entry)
self.date_changed = True
self.task_date[DATE_COL] = new_date
self.task_date_class.do_tag_date_at_line(new_date, self.task_date_tab)
self.task_date_class.do_modify_label_color(self.task_date_tab)
def _calendar_mark_today(self):
'''
mark today's date in calendar
(not used any more as it is confusing as
start to due is marked)
'''
# just in case, make a clear start
self.calendar.clear_marks()
(year, month, day) = self.calendar.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.calendar.mark_day(today.day)
def do_calendar(self, event):
'''
handle the calendar on key press event
'''
due_date = self.get_date_with_tdplus()
# due_date.month-1 as calendar starts counting with 0 as January ....
self.calendar.select_month(due_date.month - 1, due_date.year)
self.calendar.select_day(due_date.day)
self._cal_mark_existing_start_due()
#self._calendar_mark_today()
def get_specific_date(self, year, month, day):
'''
return a datetime.date object for given date in year, month, day
'''
if year < 1900:
year = 1900
date = zim_datetime.date(year, month, day)
#try:
# date = zim_datetime.date(year, month, day)
#except ValueError:
# pass
return date
def get_date_with_tdplus(self, current_date=None, task_date_plus=None):
'''
This method returns a datetime.date object either from the
date and task date plus params or from today
'''
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 task_date_plus
if not task_date_plus:
try:
due_date = current_date + \
zim_datetime.timedelta(days=self.task_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.task_date_plus = date_diff.days
else:
try:
due_date = current_date + \
zim_datetime.timedelta(days=task_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.task_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:
self.get_specific_date(year, month, i)
except ValueError:
continue
else:
return i
return None
def get_yday(self, year, month, day):
'''
get the number of days per year
'''
date = self.get_specific_date(year, month, day)
date_tt = date.timetuple()
days_per_year = date_tt.tm_yday
return days_per_year
def get_all_date_element_data(self):
'''
get the data from all entries
'''
year = self.entry_year.get_text()
month = self.entry_month.get_text()
day = self.entry_day.get_text()
tdplus = self.entry_task_date_plus.get_text()
year, month, day, tdplus = self._validate_date_element_data(year, month, day, tdplus)
return year, month, day, tdplus
def _get_date_data_from_hist(self):
'''
get teh date data from history in case the user
has entered anything into the entries or without RETURN
or which do not make sense
'''
year_hist = self.entry_history[YEAR_IDX]
month_hist = self.entry_history[MONTH_IDX]
day_hist = self.entry_history[DAY_IDX]
tdplus_hist = self.entry_history[TD_PLUS_IDX]
return year_hist, month_hist, day_hist, tdplus_hist
def _del_date_at_line(self, buf, _date):
'''
delete a given date
'''
try:
start_iter = buf.get_iter_at_mark(_date[DATE_MARK_START_COL])
end_iter = buf.get_iter_at_mark(_date[DATE_MARK_END_COL])
buf.delete(start_iter, end_iter)
buf.place_cursor(start_iter)
self.buf.delete_mark(_date[DATE_MARK_START_COL])
self.buf.delete_mark(_date[DATE_MARK_END_COL])
except TypeError:
return
class TaskDateNotebook(gtk.Notebook):
'''
Create a gtk.notebook for the start and the due date.
Move the window according to the iter position of the dates
'''
# pylint: disable=no-member, invalid-name
def __init__(self, page_view, taskdate_window):
self.page_view = page_view
self.taskdate_window = taskdate_window
gtk.Notebook.__init__(self)
self.set_tab_pos(gtk.POS_TOP)
self.page_vb_start_date = gtk.VBox()
self.start_label = gtk.Label("> Start date ")
self.append_page(self.page_vb_start_date, self.start_label)
self.page_vb_due_date = gtk.VBox()
self.due_label = gtk.Label("< Due date ")
self.append_page(self.page_vb_due_date, self.due_label)
self.taskdate_window.add(self)
def show_notebook(self):
'''
show the taskdate window
'''
self.taskdate_window.show_all()
def get_iter_pos(self, textview, win_width, win_height, taskdate_window, mark=None):
'''
get the pixel position of the given iter
'''
ACTKEY_CORRECTION = 0
COL_INVISIBLE_CORR = 0
buf = textview.get_buffer()
if not mark:
mark = buf.get_mark('cursor_orig')
cursor = buf.get_iter_at_mark(mark)
else:
cursor = buf.get_iter_at_mark(mark)
top_x, top_y = textview.get_toplevel().get_position()
iter_location = textview.get_iter_location(cursor)
mark_x, mark_y = iter_location.x, iter_location.y + iter_location.height
#calculate buffer-coordinates to coordinates within the window
win_location = textview.buffer_to_window_coords(gtk.TEXT_WINDOW_WIDGET,
int(mark_x), int(mark_y))
#now find the right window --> Editor Window and the right pos on screen
win = textview.get_window(gtk.TEXT_WINDOW_WIDGET)
view_pos = win.get_position()
xx_pos = win_location[0] + view_pos[0]
yy_pos = win_location[1] + view_pos[1] + iter_location.height
x_pos = top_x + xx_pos + ACTKEY_CORRECTION
y_pos = top_y + yy_pos - COL_INVISIBLE_CORR
x_pos, y_pos = self.calculate_with_monitors(
x_pos, y_pos, iter_location,
taskdate_window, win_width, win_height)
return (x_pos, y_pos + iter_location.height, iter_location.height)
def calculate_with_monitors(self, x_pos, y_pos, iter_location, window, win_width, win_height):
'''
Calculate correct x,y position if multiple monitors are used
'''
status_bar_correction = 30
screen = window.get_screen()
cursor_screen = screen.get_monitor_at_point(x_pos, y_pos)
cursor_monitor_geom = screen.get_monitor_geometry(cursor_screen)
if x_pos + win_width >= (cursor_monitor_geom.width + cursor_monitor_geom.x):
diff = x_pos - (cursor_monitor_geom.width + cursor_monitor_geom.x) + win_width
x_pos = x_pos - diff
if y_pos + iter_location.height + win_height >= \
(cursor_monitor_geom.height + cursor_monitor_geom.y - status_bar_correction):
diff = win_height + 2 * iter_location.height
y_pos = y_pos - diff
return x_pos, y_pos
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment