nowfocus 0.2.13__py3-none-any.whl → 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nowfocus/__main__.py +89 -83
- nowfocus/conf.py +4 -5
- nowfocus/connectors/todotxt.py +8 -3
- nowfocus/connectors/txt.py +14 -2
- nowfocus/connectors/vikunja.py +16 -17
- nowfocus/new_task_dialog.py +3 -4
- nowfocus/settings.py +10 -51
- nowfocus/styles.css +9 -11
- nowfocus/task_window.py +125 -66
- nowfocus/user_idle_time.py +82 -0
- nowfocus/utils.py +231 -127
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/METADATA +26 -24
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/RECORD +17 -16
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/WHEEL +0 -0
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.2.13.dist-info → nowfocus-0.4.2.dist-info}/top_level.txt +0 -0
nowfocus/utils.py
CHANGED
|
@@ -18,8 +18,11 @@ from gi.repository import Gtk, GLib, Gdk
|
|
|
18
18
|
gi.require_version('Notify', '0.7')
|
|
19
19
|
from gi.repository import Notify as notify
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
# TODO: only import the following if dbg level is ... 2? or system is 'performance'? IDK
|
|
23
|
+
import inspect
|
|
24
|
+
import traceback
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
import conf
|
|
25
28
|
notify.init(conf.app_name)
|
|
@@ -31,10 +34,17 @@ db_file = conf.user_data_dir+"/data.db"
|
|
|
31
34
|
|
|
32
35
|
def dbg(*data, **kwargs):
|
|
33
36
|
''' Any number of positional args then kwargs
|
|
34
|
-
l=level {0:Error, 1:Warning, 2:Info, 3:Details}
|
|
35
|
-
|
|
37
|
+
l=level {-1: Default output, 0:Error, 1:Warning, 2:Info, 3:Details}
|
|
38
|
+
|
|
39
|
+
s=system (Debuggable systems: 'taskwindow','signals','todoloading','user_settings', 'targets')
|
|
40
|
+
|
|
41
|
+
If the optional 'notify' arg is true, a notification will be generated.
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
if the optional 'e' arg is present and an exception object its traceback will be printed
|
|
44
|
+
|
|
45
|
+
'''
|
|
46
|
+
|
|
47
|
+
levels = {-1:'', 0:'Error', 1:'Warning', 2:'Info', 3:'Details'}
|
|
38
48
|
|
|
39
49
|
# default system and level
|
|
40
50
|
system = ''
|
|
@@ -44,6 +54,10 @@ def dbg(*data, **kwargs):
|
|
|
44
54
|
|
|
45
55
|
if 'l' in kwargs: level = int(kwargs['l'])
|
|
46
56
|
|
|
57
|
+
if 'e' in kwargs and isinstance(kwargs['e'],Exception):
|
|
58
|
+
print(data)
|
|
59
|
+
traceback.print_tb(kwargs['e'].__traceback__)
|
|
60
|
+
|
|
47
61
|
if "all" in conf.debug_systems or system in conf.debug_systems or level <= conf.debug_level:
|
|
48
62
|
|
|
49
63
|
if 'notify' in kwargs:
|
|
@@ -51,11 +65,23 @@ def dbg(*data, **kwargs):
|
|
|
51
65
|
notify.Notification.new(conf.app_name+" "+levels[level], str(kwargs['notify']), None).show()
|
|
52
66
|
|
|
53
67
|
# o = "Debug "+system+" "+levels[level], tuple(filter(None,data))
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
filter(None,data)
|
|
69
|
+
if len(data) == 1:
|
|
70
|
+
data = data[0]
|
|
71
|
+
if system:
|
|
72
|
+
system = "- "+system
|
|
73
|
+
|
|
74
|
+
print(data, system+" "+levels[level])
|
|
75
|
+
|
|
76
|
+
# if isinstance(data,str):
|
|
77
|
+
# # print('str dbg data')
|
|
78
|
+
# print(data, system+" "+levels[level])
|
|
79
|
+
# else:
|
|
80
|
+
# print('dbg data',type(data),len(data))
|
|
81
|
+
# print(data)
|
|
82
|
+
# # o = tuple(filter(None,data)), "("+system+" "+levels[level]+")"
|
|
83
|
+
# o = data, system+" "+levels[level]
|
|
84
|
+
# pretty_print(o)
|
|
59
85
|
|
|
60
86
|
# else:
|
|
61
87
|
# # debug the debug
|
|
@@ -65,8 +91,13 @@ def dbg(*data, **kwargs):
|
|
|
65
91
|
|
|
66
92
|
def error_notice(title, details = None, e = None):
|
|
67
93
|
print('ERROR',title,details,e)
|
|
94
|
+
|
|
68
95
|
notify.Notification.new(str(title),str(details), None).show()
|
|
69
96
|
|
|
97
|
+
# try:
|
|
98
|
+
# raise FileExistsError("This is not except-able!")
|
|
99
|
+
# except Exception as e:
|
|
100
|
+
# dbg('test exception',e=e)
|
|
70
101
|
|
|
71
102
|
def pretty_dict(i, item_sep = ", ", use_indents=True, indent=''):
|
|
72
103
|
o = ''
|
|
@@ -98,6 +129,18 @@ def pretty_print(i):
|
|
|
98
129
|
print(pretty_dict(i))
|
|
99
130
|
|
|
100
131
|
|
|
132
|
+
def timeit(process=None):
|
|
133
|
+
if not process:
|
|
134
|
+
process = inspect.currentframe().f_back.f_code.co_name
|
|
135
|
+
|
|
136
|
+
if process in conf.timers.keys():
|
|
137
|
+
print('timeit', process, time.time() - conf.timers[process])
|
|
138
|
+
del conf.timers[process]
|
|
139
|
+
else:
|
|
140
|
+
print('timeit', process, 'started')
|
|
141
|
+
conf.timers[process] = time.time()
|
|
142
|
+
|
|
143
|
+
|
|
101
144
|
def time_to_sec(time=None):
|
|
102
145
|
if not time:
|
|
103
146
|
return 0
|
|
@@ -244,9 +287,9 @@ def extended_label(i):
|
|
|
244
287
|
o = ' > '.join(l)
|
|
245
288
|
return o
|
|
246
289
|
|
|
247
|
-
|
|
290
|
+
# TODO cleanup this
|
|
248
291
|
def lists_cache(new_lists = None):
|
|
249
|
-
''' This is useful but very gimpy. If new_lists is supplied it will exclusively use that (until it is replaced or the global lists var is emptied)'''
|
|
292
|
+
'''Set or get a dict of (all) lists. This is useful but very gimpy. If new_lists is supplied it will exclusively use that (until it is replaced or the global lists var is emptied)'''
|
|
250
293
|
global lists
|
|
251
294
|
|
|
252
295
|
if(new_lists):
|
|
@@ -279,15 +322,6 @@ def save_user_settings():
|
|
|
279
322
|
json.dump(conf.user, settings_file)
|
|
280
323
|
|
|
281
324
|
|
|
282
|
-
def get_todo_by_file(file):
|
|
283
|
-
for id, todo in conf.user['todolists'].items():
|
|
284
|
-
if 'file' in todo and todo['file'] == file:
|
|
285
|
-
return todo
|
|
286
|
-
|
|
287
|
-
print('get_todo_by_file failed for', file)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
325
|
def db_query(sql,parameters=None,key=None,error_handling=1):
|
|
292
326
|
'''error_handling: 2 = raise error, 1 = notify error, 0 = ignore errors '''
|
|
293
327
|
# print('db_query parameters type: ',type(parameters))
|
|
@@ -341,6 +375,8 @@ def db_init():
|
|
|
341
375
|
db_query("CREATE TABLE lists (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT DEFAULT '', data TEXT DEFAULT '{}');")
|
|
342
376
|
|
|
343
377
|
db_query("CREATE TABLE tasks (id TEXT, label TEXT DEFAULT '', parent_id TEXT DEFAULT '', parent_label TEXT DEFAULT '', todolist TEXT DEFAULT '', priority INTEGER DEFAULT 0, status INTEGER DEFAULT 1, extended_label TEXT, data TEXT DEFAULT '{}');")
|
|
378
|
+
|
|
379
|
+
# , tags TEXT
|
|
344
380
|
|
|
345
381
|
db_query("CREATE TABLE sessions (start_time TEXT, duration INTEGER, task_id TEXT, parent_id TEXT, todolist TEXT, extended_label TEXT, notes TEXT, timetracker TEXT);")
|
|
346
382
|
|
|
@@ -349,7 +385,7 @@ def db_init():
|
|
|
349
385
|
db_query("INSERT INTO system(field, value) VALUES('db_schema_version', '0.4')")
|
|
350
386
|
|
|
351
387
|
|
|
352
|
-
def
|
|
388
|
+
def db_schema_update():
|
|
353
389
|
|
|
354
390
|
try:
|
|
355
391
|
db_schema_version = float(db_query("SELECT field, value FROM system WHERE field = 'db_schema_version' ",error_handling=2)[0]['value'])
|
|
@@ -402,8 +438,6 @@ def db_update():
|
|
|
402
438
|
db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.4')")
|
|
403
439
|
print('adding timetracker column to session table')
|
|
404
440
|
|
|
405
|
-
# for s in db_query("SELECT start_time, task_id, todolist FROM sessions"):
|
|
406
|
-
|
|
407
441
|
for todolist_id, todo in conf.user['todolists'].items():
|
|
408
442
|
print("set timetracker to ",todo['timetracker'], " for sessions from ",todolist_id)
|
|
409
443
|
|
|
@@ -412,6 +446,9 @@ def db_update():
|
|
|
412
446
|
db_schema_version = 0.4
|
|
413
447
|
|
|
414
448
|
# if db_schema_version == 0.4:
|
|
449
|
+
# db_query("ALTER TABLE tasks ADD COLUMN tags TEXT DEFAULT '{}'")
|
|
450
|
+
# db_query("REPLACE INTO system(field, value) VALUES('db_schema_version', '0.5')")
|
|
451
|
+
# db_schema_version = 0.5
|
|
415
452
|
|
|
416
453
|
|
|
417
454
|
dbg('db_schema_version', db_schema_version,s='db')
|
|
@@ -427,7 +464,7 @@ def reindex(t=None):
|
|
|
427
464
|
|
|
428
465
|
def reindex_all():
|
|
429
466
|
|
|
430
|
-
db_query("DROP TABLE taskindex",None,None,0)
|
|
467
|
+
db_query("DROP TABLE IF EXISTS taskindex",None,None,0)
|
|
431
468
|
db_query("CREATE VIRTUAL TABLE taskindex USING fts5(id, extended_label, priority, status)")
|
|
432
469
|
db_query("INSERT INTO taskindex(id, extended_label, priority, status) SELECT id, extended_label, priority, status FROM tasks WHERE tasks.status IS NOT '-1' ")
|
|
433
470
|
|
|
@@ -498,7 +535,6 @@ def taskindex_search(i):
|
|
|
498
535
|
|
|
499
536
|
return tasks
|
|
500
537
|
|
|
501
|
-
# self.search_cache[i] = tasks
|
|
502
538
|
|
|
503
539
|
def db_to_py_type_map(db_type, value=None):
|
|
504
540
|
types = {
|
|
@@ -765,6 +801,69 @@ def db_save_session(session):
|
|
|
765
801
|
reindex(session['task'])
|
|
766
802
|
|
|
767
803
|
|
|
804
|
+
def default_session():
|
|
805
|
+
|
|
806
|
+
# TODO: if 'default_task' in user_conf:
|
|
807
|
+
# # use it!
|
|
808
|
+
|
|
809
|
+
o = {
|
|
810
|
+
"label":"Randomness",
|
|
811
|
+
"extended_label": 'Randomness',
|
|
812
|
+
"start_time":datetime.now(),
|
|
813
|
+
"duration":0,
|
|
814
|
+
'task':{
|
|
815
|
+
"label":"Randomness",
|
|
816
|
+
'id':'Randomness',
|
|
817
|
+
'parent_id':'None',
|
|
818
|
+
'todolist':'None',
|
|
819
|
+
},
|
|
820
|
+
'timetracker': 'None',
|
|
821
|
+
'notes':'',
|
|
822
|
+
}
|
|
823
|
+
return o
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def show_sessions(widget = None, self = None, sessions_box = None, label_text = "Most Recent Sessions", order_by = 'start_time', limit = 35, passed_sessions = None, truncate = None ):
|
|
828
|
+
|
|
829
|
+
sessions_box.foreach(lambda child: child.destroy())
|
|
830
|
+
|
|
831
|
+
if passed_sessions:
|
|
832
|
+
dbg("using passed_sessions",passed_sessions,s='settings')
|
|
833
|
+
sessions = passed_sessions
|
|
834
|
+
else:
|
|
835
|
+
dbg("show_sessions: order_by:",order_by, 'limit',limit, s='settings')
|
|
836
|
+
sessions = db_query(" SELECT * FROM sessions WHERE "+sessions_timeframe_sql()+" ORDER BY "+order_by+" DESC LIMIT ? ",(limit,))
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
if label_text:
|
|
840
|
+
label = Gtk.Label()
|
|
841
|
+
if passed_sessions:
|
|
842
|
+
label.set_markup('<b>'+label_text+'</b>')
|
|
843
|
+
else:
|
|
844
|
+
label.set_markup('<b>'+str(limit)+' '+label_text+' of '+conf.user['hours_search_timeframe']+'</b>')
|
|
845
|
+
sessions_box.add(label)
|
|
846
|
+
|
|
847
|
+
for ls in sessions:
|
|
848
|
+
dbg('Add session to session_box',ls['extended_label'],s='settings')
|
|
849
|
+
if truncate:
|
|
850
|
+
label = ls['extended_label'].split('>')[-1].strip()
|
|
851
|
+
if len(label) > truncate:
|
|
852
|
+
label = label[:truncate]+'…'
|
|
853
|
+
btn = Gtk.Button(label=label+' '+str(sec_to_time(ls['duration'])))
|
|
854
|
+
else:
|
|
855
|
+
btn = Gtk.Button(label=ls['extended_label']+' '+str(sec_to_time(ls['duration'])))
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
btn.set_halign(Gtk.Align.START)
|
|
859
|
+
btn.set_relief(Gtk.ReliefStyle.NONE)
|
|
860
|
+
|
|
861
|
+
btn.connect('clicked', lambda button_widget, ls: self.SessionEditDialog(None, self, ls),ls )
|
|
862
|
+
|
|
863
|
+
sessions_box.add(btn)
|
|
864
|
+
|
|
865
|
+
sessions_box.show_all()
|
|
866
|
+
|
|
768
867
|
def db_set_session_cache(s):
|
|
769
868
|
''' Add active session to system db table. Not to be confused with db_save_session '''
|
|
770
869
|
db_session = copy.deepcopy(s)
|
|
@@ -772,7 +871,6 @@ def db_set_session_cache(s):
|
|
|
772
871
|
db_query("REPLACE INTO system(field, value) VALUES(:field, :value)", {'field':'session','value':json.dumps(db_session)})
|
|
773
872
|
|
|
774
873
|
|
|
775
|
-
|
|
776
874
|
def get_total_time(id, category = 'tasks', start_time = None, end_time = None, get_minutes = None):
|
|
777
875
|
'''If start_time is int or float: it is treated as a number of days,
|
|
778
876
|
Default returns num seconds, (unless get_minutes is True)
|
|
@@ -805,6 +903,19 @@ def get_total_time(id, category = 'tasks', start_time = None, end_time = None, g
|
|
|
805
903
|
return result
|
|
806
904
|
|
|
807
905
|
|
|
906
|
+
def get_percent_time_focused():
|
|
907
|
+
|
|
908
|
+
rand_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE sessions.task_id = 'Randomness' AND "+ sessions_timeframe_sql())[0]['seconds']
|
|
909
|
+
|
|
910
|
+
focus_seconds = db_query("SELECT SUM(duration) as seconds FROM sessions WHERE sessions.task_id != 'Randomness' AND "+ sessions_timeframe_sql())[0]['seconds']
|
|
911
|
+
|
|
912
|
+
percent = round(divide(focus_seconds,(focus_seconds + rand_seconds)) * 100, 2)
|
|
913
|
+
|
|
914
|
+
dbg('get_percent_time_focused', str(percent)+"%", 'focus_seconds', focus_seconds, 'rand_seconds', rand_seconds)
|
|
915
|
+
|
|
916
|
+
return percent
|
|
917
|
+
|
|
918
|
+
|
|
808
919
|
def get_recent_tasks(count = 15):
|
|
809
920
|
data = db_query("SELECT DISTINCT tasks.* FROM tasks JOIN sessions ON sessions.task_id = tasks.id WHERE tasks.status = 1 GROUP BY tasks.id ORDER BY MAX(sessions.start_time) DESC LIMIT ?",(count,))
|
|
810
921
|
o = {}
|
|
@@ -866,6 +977,38 @@ def force_number(i):
|
|
|
866
977
|
return o
|
|
867
978
|
|
|
868
979
|
|
|
980
|
+
def divide(a, b):
|
|
981
|
+
a = force_number(a)
|
|
982
|
+
b = force_number(b)
|
|
983
|
+
|
|
984
|
+
if a == 0 or b == 0:
|
|
985
|
+
return 0
|
|
986
|
+
else:
|
|
987
|
+
return (a / b)
|
|
988
|
+
|
|
989
|
+
def clamp(value, min_limit, max_limit):
|
|
990
|
+
return max(min_limit, min(value, max_limit))
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def invert_number_scale(i, max=5):
|
|
994
|
+
''' Flips a positive number within a given range '''
|
|
995
|
+
o = abs(i - max )
|
|
996
|
+
return o
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def num_is_multiple_of(i,devisor = 2):
|
|
1000
|
+
try:
|
|
1001
|
+
i = int(i)
|
|
1002
|
+
if i > 0 and (i / devisor).is_integer():
|
|
1003
|
+
return True
|
|
1004
|
+
else:
|
|
1005
|
+
return False
|
|
1006
|
+
|
|
1007
|
+
except:
|
|
1008
|
+
return False
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
# TODO rename and cleanup
|
|
869
1012
|
def get_times(task):
|
|
870
1013
|
day = start_of_day()
|
|
871
1014
|
week = day - timedelta(days = 7)
|
|
@@ -889,76 +1032,6 @@ def get_times(task):
|
|
|
889
1032
|
return o
|
|
890
1033
|
|
|
891
1034
|
|
|
892
|
-
class TodoModifiedEventHandler(FileSystemEventHandler):
|
|
893
|
-
def __init__(self,todo = None):
|
|
894
|
-
# print('todo in TodoModifiedEventHandler',todo)
|
|
895
|
-
self.todo = todo
|
|
896
|
-
|
|
897
|
-
# Can't use on_modified because of swap-in save method used by editors
|
|
898
|
-
# def on_modified(self, event: FileModifiedEvent) -> None:
|
|
899
|
-
|
|
900
|
-
def on_any_event(self, event: FileSystemEvent) -> None:
|
|
901
|
-
|
|
902
|
-
# print(event)
|
|
903
|
-
# src_path=PATH/todo.txt', dest_path='', event_type='modified', is_directory=False, is_synthetic=False
|
|
904
|
-
# print(event.src_path,event.event_type)
|
|
905
|
-
|
|
906
|
-
if event.src_path == self.todo['file'] and event.event_type not in ('closed','opened') and not event.is_directory:
|
|
907
|
-
if self.todo['id'] not in conf.file_watch_ignores:
|
|
908
|
-
# print("Refresh", event.src_path, event.event_type)
|
|
909
|
-
conf.todo_sync_required[self.todo['id']] = self.todo['id']
|
|
910
|
-
# else:
|
|
911
|
-
# print('currently ignoring changes to '+self.todo['label'])
|
|
912
|
-
|
|
913
|
-
return True
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
def watch_todo_file(todo):
|
|
917
|
-
|
|
918
|
-
dbg("Setting up file modification observer for", todo['file'],s=todo['type'])
|
|
919
|
-
|
|
920
|
-
event_handler = TodoModifiedEventHandler(todo)
|
|
921
|
-
|
|
922
|
-
observer = Observer()
|
|
923
|
-
observer.daemon = True
|
|
924
|
-
path,filename=todo['file'].rsplit('/',1)
|
|
925
|
-
observer.schedule(event_handler, path+'/', recursive=False)
|
|
926
|
-
observer.start()
|
|
927
|
-
|
|
928
|
-
return observer
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
def start_todo_file_watchers(todo = None):
|
|
932
|
-
print("starting todo watchers. todo",todo)
|
|
933
|
-
if todo:
|
|
934
|
-
if todo['status'] and 'watch_file' in todo:
|
|
935
|
-
conf.file_watchers[todo['id']] = watch_todo_file(todo)
|
|
936
|
-
else:
|
|
937
|
-
print('todo passed to start_todo_file_watchers is ineligable', todo)
|
|
938
|
-
else:
|
|
939
|
-
for id, todo in conf.user['todolists'].items():
|
|
940
|
-
|
|
941
|
-
if todo['status'] and 'watch_file' in todo:
|
|
942
|
-
|
|
943
|
-
conf.file_watchers[id] = watch_todo_file(todo)
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
def stop_todo_file_watchers(todo = None):
|
|
947
|
-
|
|
948
|
-
if todo:
|
|
949
|
-
if todo['id'] in conf.file_watchers:
|
|
950
|
-
conf.file_watchers[todo['id']].stop()
|
|
951
|
-
conf.file_watchers[todo['id']].join()
|
|
952
|
-
del conf.file_watchers[todo['id']]
|
|
953
|
-
else:
|
|
954
|
-
for id, watcher in conf.file_watchers.items():
|
|
955
|
-
watcher.stop()
|
|
956
|
-
watcher.join()
|
|
957
|
-
del conf.file_watchers[id]
|
|
958
|
-
|
|
959
|
-
print('file_watchers after stop_todo_file_watchers')
|
|
960
|
-
print(conf.file_watchers.keys())
|
|
961
|
-
|
|
962
1035
|
|
|
963
1036
|
def db_deactivate_todo(id):
|
|
964
1037
|
dbg("deactivating todolist: "+id, s='todoloading')
|
|
@@ -975,9 +1048,8 @@ def refresh_todolist(todo, catch_errors = False):
|
|
|
975
1048
|
|
|
976
1049
|
try:
|
|
977
1050
|
dbg('Refreshing '+todo['label'],l=-1)
|
|
978
|
-
|
|
1051
|
+
|
|
979
1052
|
todos = conf.todo_connectors[todo['type']].get_todos(todo)
|
|
980
|
-
del conf.file_watch_ignores[todo['id']]
|
|
981
1053
|
|
|
982
1054
|
dbg('Done Refreshing '+todo['label'],l=-1)
|
|
983
1055
|
|
|
@@ -990,13 +1062,31 @@ def refresh_todolist(todo, catch_errors = False):
|
|
|
990
1062
|
|
|
991
1063
|
else:
|
|
992
1064
|
raise e
|
|
993
|
-
|
|
994
|
-
if todo['id'] in conf.todo_sync_required:
|
|
995
|
-
del conf.todo_sync_required[todo['id']]
|
|
996
1065
|
|
|
997
1066
|
conf.todo_sync_times[todo['id']] = now() # It may seem foolish to do this even on an error, but otherwise it will constantly try to update when offline
|
|
1067
|
+
|
|
1068
|
+
clear_todo_file_change_time(todo)
|
|
1069
|
+
|
|
998
1070
|
return todos
|
|
999
1071
|
|
|
1072
|
+
def clear_todo_file_change_time(todolist_conf):
|
|
1073
|
+
if todolist_conf['id'] in conf.todo_file_change_times:
|
|
1074
|
+
del conf.todo_file_change_times[todolist_conf['id']]
|
|
1075
|
+
|
|
1076
|
+
def handle_todo_read_error(todo_conf,e):
|
|
1077
|
+
|
|
1078
|
+
if isinstance(e, (FileExistsError, FileNotFoundError, PermissionError)):
|
|
1079
|
+
handle_todo_file_access_error(todo_conf,e)
|
|
1080
|
+
|
|
1081
|
+
dbg(todo_conf['label']+ " Access Error",e,l=0,notify=True)
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def handle_todo_file_read_error(todo_conf,e):
|
|
1085
|
+
conf.user['todolists'][todo_conf['id']]['status'] = False
|
|
1086
|
+
save_user_settings()
|
|
1087
|
+
dbg(todo_conf['label']+ " Deactivated",e,l=-1,notify=True)
|
|
1088
|
+
|
|
1089
|
+
|
|
1000
1090
|
|
|
1001
1091
|
def get_todolists(use_db_cache = False):
|
|
1002
1092
|
tasks = {}
|
|
@@ -1059,10 +1149,10 @@ def get_most_recent_list(session = None):
|
|
|
1059
1149
|
|
|
1060
1150
|
|
|
1061
1151
|
def choose_from_lists(callback, selected_list_id = None, session = None, accepts_tasks = True):
|
|
1062
|
-
''' Returns a Gtk.ScrolledWindow widget with radio
|
|
1152
|
+
''' Returns a Gtk.ScrolledWindow widget with radio buttons'''
|
|
1063
1153
|
|
|
1154
|
+
# TODO: consider using TreeView https://lazka.github.io/pgi-docs/Gtk-3.0/classes/TreeView.html#Gtk.TreeView
|
|
1064
1155
|
|
|
1065
|
-
# TODO: consider useing TreeView https://lazka.github.io/pgi-docs/Gtk-3.0/classes/TreeView.html#Gtk.TreeView
|
|
1066
1156
|
# get last used list
|
|
1067
1157
|
if not selected_list_id:
|
|
1068
1158
|
selected_list_id = get_most_recent_list(session = None)
|
|
@@ -1072,7 +1162,7 @@ def choose_from_lists(callback, selected_list_id = None, session = None, accepts
|
|
|
1072
1162
|
scrolled_window = Gtk.ScrolledWindow()
|
|
1073
1163
|
scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
1074
1164
|
scrolled_window.set_propagate_natural_height(True)
|
|
1075
|
-
scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
|
|
1165
|
+
# scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
|
|
1076
1166
|
scrolled_window.add(box)
|
|
1077
1167
|
|
|
1078
1168
|
todolist = None
|
|
@@ -1093,22 +1183,26 @@ def choose_from_lists(callback, selected_list_id = None, session = None, accepts
|
|
|
1093
1183
|
button_group = Gtk.RadioButton(label=l['label'])
|
|
1094
1184
|
item = button_group
|
|
1095
1185
|
else:
|
|
1096
|
-
item = Gtk.RadioButton(label=extended_label
|
|
1186
|
+
item = Gtk.RadioButton(label=l['extended_label'], group=button_group)
|
|
1097
1187
|
|
|
1098
1188
|
if todolist != l['todolist']:
|
|
1099
1189
|
# new buttongroup for new todolist
|
|
1100
1190
|
todolist = l['todolist']
|
|
1101
1191
|
l['header'] = Gtk.Label()
|
|
1102
|
-
|
|
1192
|
+
try:
|
|
1193
|
+
l['header'].set_markup("<b>"+conf.user['todolists'][todolist]["label"]+"</b>")
|
|
1194
|
+
except Exception as e:
|
|
1195
|
+
dbg('Exception creating button group header for list',l, e=e, l=0)
|
|
1196
|
+
|
|
1103
1197
|
box.add(l['header'])
|
|
1104
1198
|
|
|
1105
1199
|
if l['id'] == selected_list_id:
|
|
1106
1200
|
item.set_active(True)
|
|
1107
1201
|
|
|
1108
|
-
item.connect("toggled", callback, l)
|
|
1202
|
+
item.connect("toggled", callback, l) #
|
|
1109
1203
|
box.add(item)
|
|
1110
1204
|
|
|
1111
|
-
# TODO: allow a height argument to account for other things that
|
|
1205
|
+
# TODO: allow a height argument to account for other things that require space, and possibly a global screen height value?
|
|
1112
1206
|
# Optimize the height, 27px per item with max of 700 px and a min of 50
|
|
1113
1207
|
height = max(50,min(500, (len(lists) * 27)))
|
|
1114
1208
|
scrolled_window.set_size_request(-1, height)
|
|
@@ -1158,7 +1252,7 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1158
1252
|
# separator after priority tasks
|
|
1159
1253
|
target_menu.prepend(Gtk.SeparatorMenuItem.new())
|
|
1160
1254
|
|
|
1161
|
-
# Create
|
|
1255
|
+
# Create menu for each list
|
|
1162
1256
|
for list_id, l in lists.items():
|
|
1163
1257
|
if l['status'] != 1:
|
|
1164
1258
|
continue
|
|
@@ -1170,19 +1264,25 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1170
1264
|
list_menu_items[list_id] = Gtk.MenuItem.new_with_label(lists[list_id]['label']) # the "item" that gets added
|
|
1171
1265
|
list_menu_items[list_id].set_submenu(list_menus[list_id])
|
|
1172
1266
|
|
|
1173
|
-
# Add
|
|
1267
|
+
# Add each list to parents (sub_menus)
|
|
1174
1268
|
for list_id, l in lists.items():
|
|
1175
1269
|
if l['status'] == 1:
|
|
1176
1270
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1271
|
+
try:
|
|
1272
|
+
|
|
1273
|
+
if not toplevel_todos and l['id'] == l['todolist']:
|
|
1274
|
+
continue
|
|
1275
|
+
|
|
1276
|
+
if 'priority' in l and l['priority']:
|
|
1277
|
+
target_menu.prepend(list_menu_items[list_id])
|
|
1278
|
+
elif (toplevel_todos and l['id'] == l['todolist']) or (not toplevel_todos and l['parent_id'] == l['todolist']):
|
|
1279
|
+
target_menu.append(list_menu_items[list_id])
|
|
1280
|
+
else:
|
|
1281
|
+
list_menus[l['parent_id']].append(list_menu_items[list_id])
|
|
1179
1282
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
target_menu.append(list_menu_items[list_id])
|
|
1184
|
-
else:
|
|
1185
|
-
list_menus[l['parent_id']].append(list_menu_items[list_id])
|
|
1283
|
+
except Exception as e:
|
|
1284
|
+
dbg('Error adding list to menu',l,e=e,l=0)
|
|
1285
|
+
|
|
1186
1286
|
|
|
1187
1287
|
|
|
1188
1288
|
for id, t in tasks.items():
|
|
@@ -1230,27 +1330,29 @@ def add_todos_to_menu(target_menu = None, menu_tasks = None, list_menus = None,
|
|
|
1230
1330
|
|
|
1231
1331
|
target_menu.show_all()
|
|
1232
1332
|
|
|
1333
|
+
|
|
1233
1334
|
def datetime_minus_calendar_unit(unit = 'days', num = 1, ref_date = None):
|
|
1234
1335
|
''' returns a datetime for the start of the unit num units ago (from the optional ref_date)
|
|
1235
1336
|
unit can be days, weeks, months, or years (plural or singular)
|
|
1236
|
-
|
|
1237
1337
|
'''
|
|
1238
|
-
|
|
1338
|
+
|
|
1239
1339
|
num = num - 1
|
|
1340
|
+
|
|
1240
1341
|
if not ref_date:
|
|
1241
1342
|
ref_date = start_of_day()
|
|
1343
|
+
|
|
1242
1344
|
if unit in ['day','days']:
|
|
1243
1345
|
o = ref_date - timedelta(days=num)
|
|
1244
|
-
|
|
1245
|
-
if unit in ['week','weeks']:
|
|
1346
|
+
elif unit in ['week','weeks']:
|
|
1246
1347
|
o = ref_date - timedelta(days=ref_date.weekday())
|
|
1247
1348
|
if num > 0: o = o + relativedelta(dt1= o, weeks= - num)
|
|
1248
|
-
|
|
1349
|
+
elif unit in ['month','months']:
|
|
1249
1350
|
o = ref_date.replace(day=1)
|
|
1250
1351
|
if num > 0: o = o + relativedelta(dt1= o, months= - num)
|
|
1251
|
-
|
|
1352
|
+
elif unit in ['year','years']:
|
|
1252
1353
|
o = ref_date.replace(month=1,day=1)
|
|
1253
1354
|
if num > 0: o = o + relativedelta(dt1= o, years= - num)
|
|
1355
|
+
|
|
1254
1356
|
return o
|
|
1255
1357
|
|
|
1256
1358
|
def get_time_target_data(i, item_type = 'tasks'):
|
|
@@ -1301,11 +1403,11 @@ def hours_search_timeframes(frame_name = None):
|
|
|
1301
1403
|
''' returns a tuple with start and end time for given frame (if one is provided) or a dict of frame_options.
|
|
1302
1404
|
'''
|
|
1303
1405
|
|
|
1304
|
-
default = "
|
|
1406
|
+
default = "all time"
|
|
1305
1407
|
|
|
1306
1408
|
frames = {
|
|
1307
1409
|
'today':(datetime_minus_calendar_unit('day',1),now()),
|
|
1308
|
-
'yesterday':(datetime_minus_calendar_unit('day',1)),
|
|
1410
|
+
'yesterday':(datetime_minus_calendar_unit('day',2),datetime_minus_calendar_unit('day',1)),
|
|
1309
1411
|
'this week':(datetime_minus_calendar_unit('week',1),now()),
|
|
1310
1412
|
'last week':(datetime_minus_calendar_unit('week',2),datetime_minus_calendar_unit('week',1)),
|
|
1311
1413
|
'7 days':(datetime_minus_calendar_unit('days',7),now()),
|
|
@@ -1334,4 +1436,6 @@ def sessions_timeframe_sql():
|
|
|
1334
1436
|
timeframe = hours_search_timeframes(conf.user['hours_search_timeframe'])
|
|
1335
1437
|
|
|
1336
1438
|
timeframe_sql = " sessions.start_time > '"+timeframe[0].strftime("%Y-%m-%d %H:%M:%S")+"' AND sessions.start_time < '"+timeframe[1].strftime("%Y-%m-%d %H:%M:%S")+"' "
|
|
1439
|
+
|
|
1440
|
+
# dbg('sessions_timeframe_sql',timeframe_sql,s='search')
|
|
1337
1441
|
return timeframe_sql
|