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/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
- from watchdog.events import FileSystemEventHandler, FileSystemEvent
22
- from watchdog.observers import Observer
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
- s=system (Debuggable systems: 'taskwindow','signals','todoloading','user_settings', 'targets')'''
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
- levels = {-1: 'CLI output', 0:'Error', 1:'Warning', 2:'Info', 3:'Details'}
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
- if isinstance(data,str):
55
- print(data,"("+system+" "+levels[level]+")")
56
- else:
57
- o = tuple(filter(None,data)), "("+system+" "+levels[level]+")"
58
- pretty_print(o)
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 db_update():
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
- conf.file_watch_ignores[todo['id']] = True # get_todos can cause the same event that saving a file cases
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 button'''
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(l), group=button_group)
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
- l['header'].set_markup("<b>"+conf.user['todolists'][todolist]["label"]+"</b>")
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 requre space, and possibly a global screen height value?
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 list sub menus
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 sub menus (items) to parents (sub_menus)
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
- if not toplevel_todos and l['id'] == l['todolist']:
1178
- continue
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
- if 'priority' in l and l['priority']:
1181
- target_menu.prepend(list_menu_items[list_id])
1182
- elif (toplevel_todos and l['id'] == l['todolist']) or (not toplevel_todos and l['parent_id'] == l['todolist']):
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
- # Looks weird but...
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
- if num > 0: o = o - timedelta(days=num)
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
- if unit in ['month','months']:
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
- if unit in ['year','years']:
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 = "this year"
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