-
-
Save introt/07a3378f2e7ae40fc3b8bf4fcf314686 to your computer and use it in GitHub Desktop.
Plugin for ZimWiki V0.66 or greater
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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