nowfocus 0.2.12__py3-none-any.whl → 0.4.0__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 +83 -80
- nowfocus/conf.py +4 -5
- nowfocus/connectors/todotxt.py +8 -3
- nowfocus/connectors/txt.py +43 -28
- nowfocus/new_task_dialog.py +3 -4
- nowfocus/settings.py +10 -51
- nowfocus/styles.css +9 -11
- nowfocus/task_window.py +117 -72
- nowfocus/user_idle_time.py +82 -0
- nowfocus/utils.py +169 -108
- {nowfocus-0.2.12.dist-info → nowfocus-0.4.0.dist-info}/METADATA +66 -48
- {nowfocus-0.2.12.dist-info → nowfocus-0.4.0.dist-info}/RECORD +16 -15
- {nowfocus-0.2.12.dist-info → nowfocus-0.4.0.dist-info}/WHEEL +0 -0
- {nowfocus-0.2.12.dist-info → nowfocus-0.4.0.dist-info}/entry_points.txt +0 -0
- {nowfocus-0.2.12.dist-info → nowfocus-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {nowfocus-0.2.12.dist-info → nowfocus-0.4.0.dist-info}/top_level.txt +0 -0
nowfocus/__main__.py
CHANGED
|
@@ -13,6 +13,7 @@ from playsound3 import playsound
|
|
|
13
13
|
import setproctitle
|
|
14
14
|
import psutil
|
|
15
15
|
import argparse
|
|
16
|
+
import traceback
|
|
16
17
|
|
|
17
18
|
import gi
|
|
18
19
|
gi.require_version('Gtk', '3.0')
|
|
@@ -28,7 +29,7 @@ except Exception as e:
|
|
|
28
29
|
gi.require_version('AppIndicator3', '0.1')
|
|
29
30
|
from gi.repository import AppIndicator3 as appindicator
|
|
30
31
|
|
|
31
|
-
from dbus_idle import IdleMonitor
|
|
32
|
+
# from dbus_idle import IdleMonitor
|
|
32
33
|
|
|
33
34
|
# Set working dir to file location
|
|
34
35
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
@@ -39,6 +40,8 @@ sys.path.append(os.path.dirname(__file__))
|
|
|
39
40
|
# from . import conf # this works in module context but not running as pile-of-files
|
|
40
41
|
import conf # this works running as pile-of-files but not in module context without sys.path.append
|
|
41
42
|
|
|
43
|
+
from user_idle_time import UserIdleTime
|
|
44
|
+
|
|
42
45
|
import utils
|
|
43
46
|
from utils import *
|
|
44
47
|
|
|
@@ -55,19 +58,21 @@ print(conf.app_name +" running from " + os.path.dirname(os.path.realpath(__file_
|
|
|
55
58
|
|
|
56
59
|
class Application(Gtk.Application):
|
|
57
60
|
def __init__(self, *args, **kwargs):
|
|
58
|
-
super().__init__(*args, application_id="org.
|
|
61
|
+
super().__init__(*args, application_id="org.nowfocus.nowfocus", **kwargs)
|
|
62
|
+
|
|
63
|
+
# try:
|
|
64
|
+
# # To put everything here...
|
|
65
|
+
# # this doesn't work because as soon as an exception occurs ii jumps to the handler and breaks all the following code.
|
|
59
66
|
|
|
67
|
+
# except Exception as e:
|
|
68
|
+
# print(conf.app_name +' had a pretty bad error. Please submit the following trace as an issue on the git repo or email it to the developers ')
|
|
69
|
+
# traceback.print_tb(e.__traceback__)
|
|
70
|
+
# return None
|
|
71
|
+
|
|
60
72
|
self.window = None
|
|
61
73
|
|
|
62
74
|
self.is_running = False
|
|
63
|
-
self.session =
|
|
64
|
-
"label":"Randomness",
|
|
65
|
-
"extended_label": 'Randomness',
|
|
66
|
-
"start_time":datetime.now(),
|
|
67
|
-
"duration":0,
|
|
68
|
-
'task':{},
|
|
69
|
-
'notes':'',
|
|
70
|
-
}
|
|
75
|
+
self.session = default_session()
|
|
71
76
|
|
|
72
77
|
self.menu_tasks = {}
|
|
73
78
|
self.list_menus = {}
|
|
@@ -80,7 +85,7 @@ class Application(Gtk.Application):
|
|
|
80
85
|
# menu.set_reserve_toggle_size(False) # skip menu left padding, doesn't work
|
|
81
86
|
|
|
82
87
|
utils.db_init()
|
|
83
|
-
utils.
|
|
88
|
+
utils.db_schema_update()
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
# self.update_menu()
|
|
@@ -90,7 +95,6 @@ class Application(Gtk.Application):
|
|
|
90
95
|
|
|
91
96
|
self.indicator.set_menu(self.menu)
|
|
92
97
|
|
|
93
|
-
# main_tick_timer = GLib.timeout_add_seconds(conf.user['tick_interval'], self.tick)
|
|
94
98
|
main_tick_timer = GLib.timeout_add_seconds(1, self.tick)
|
|
95
99
|
|
|
96
100
|
try:
|
|
@@ -105,7 +109,7 @@ class Application(Gtk.Application):
|
|
|
105
109
|
except Exception as e:
|
|
106
110
|
dbg("Error resuming session",e,l=1)
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
self.UserIdleTime = UserIdleTime()
|
|
109
113
|
|
|
110
114
|
self.pipethread = threading.Thread(target=self.check_pipe)
|
|
111
115
|
self.pipethread.daemon = True
|
|
@@ -157,39 +161,40 @@ class Application(Gtk.Application):
|
|
|
157
161
|
|
|
158
162
|
def quit(self, widget_or_signal_source=None, condition=None):
|
|
159
163
|
print("Adios ", conf.app_name)
|
|
160
|
-
# print("widget_or_signal_source ", widget_or_signal_source)
|
|
161
|
-
# print("condition ", condition)
|
|
162
164
|
|
|
163
165
|
if self.is_running:
|
|
164
|
-
|
|
166
|
+
dbg('Caching active session', self.session['label'])
|
|
165
167
|
db_set_session_cache(self.session)
|
|
168
|
+
|
|
166
169
|
try:
|
|
167
|
-
# print("before os.remove(conf.pipe)")
|
|
168
170
|
os.remove(conf.pipe)
|
|
169
|
-
print("Pipe removed")
|
|
170
171
|
except Exception as e:
|
|
171
|
-
|
|
172
|
+
dbd("Error removing conf.pipe in quit",e)
|
|
172
173
|
|
|
173
174
|
notify.uninit()
|
|
174
175
|
Gtk.main_quit()
|
|
175
176
|
exit()
|
|
176
177
|
|
|
177
178
|
|
|
178
|
-
def
|
|
179
|
+
def seconds_since_user_active(self):
|
|
179
180
|
# returns seconds of inactivity
|
|
180
|
-
# See https://stackoverflow.com/questions/67083083/how-to-get-idle-time-in-linux
|
|
181
|
+
# See https://stackoverflow.com/questions/67083083/how-to-get-idle-time-in-linux or
|
|
181
182
|
|
|
182
183
|
# Works on x11 but not wayland
|
|
183
184
|
# Requires xprintidle (sudo apt install xprintidle)
|
|
184
185
|
# idle_time = int(int(subprocess.getoutput('xprintidle')) / 1000)
|
|
185
186
|
|
|
186
|
-
#
|
|
187
|
+
# Version that works, using: https://github.com/bkbilly/dbus_idle
|
|
188
|
+
# but has many deps
|
|
187
189
|
# Requires:
|
|
188
190
|
# sudo apt install meson libdbus-glib-1-dev patchelf
|
|
189
191
|
# pip install dbus-idle
|
|
190
192
|
|
|
191
|
-
idle_time = int(int(IdleMonitor().get_dbus_idle()) / 1000)
|
|
192
|
-
return idle_time
|
|
193
|
+
# idle_time = int(int(IdleMonitor().get_dbus_idle()) / 1000)
|
|
194
|
+
# return idle_time
|
|
195
|
+
|
|
196
|
+
return self.UserIdleTime.get()
|
|
197
|
+
|
|
193
198
|
|
|
194
199
|
|
|
195
200
|
def toggle_do_not_disturb(self, widget):
|
|
@@ -210,7 +215,7 @@ class Application(Gtk.Application):
|
|
|
210
215
|
if 'do-not-disturb' in self.session:
|
|
211
216
|
return None
|
|
212
217
|
|
|
213
|
-
afk_time = self.
|
|
218
|
+
afk_time = self.seconds_since_user_active()
|
|
214
219
|
# print("Idle time: "+str(afk_time))
|
|
215
220
|
|
|
216
221
|
dbg('Last todo todo_sync_time', conf.todo_sync_time, 'Time diff',int(time_difference(conf.todo_sync_time)),'Auto refresh interval * 60', (conf.user['todolist_refresh_interval'] * 60), s="todoloading", l=3 )
|
|
@@ -224,7 +229,7 @@ class Application(Gtk.Application):
|
|
|
224
229
|
|
|
225
230
|
if(self.is_running == False):
|
|
226
231
|
|
|
227
|
-
if
|
|
232
|
+
if num_is_multiple_of(minutes,conf.user['randomness_interrupt_interval']):
|
|
228
233
|
if afk_time < 30:
|
|
229
234
|
|
|
230
235
|
notify.Notification.new("What Am I doing?","Your randomness timer is at "+str(minutes)+" minutes. ", None).show()
|
|
@@ -243,7 +248,6 @@ class Application(Gtk.Application):
|
|
|
243
248
|
# only show this once
|
|
244
249
|
if afk_time < 181:
|
|
245
250
|
self.open_task_window(None,{'afk_time':afk_time})
|
|
246
|
-
# session_options_dialog(None, 'test input_data')
|
|
247
251
|
else:
|
|
248
252
|
if self.session['label'] in conf.user['custom_pomodoro_intervals']:
|
|
249
253
|
check = conf.user['custom_pomodoro_intervals'][self.session['label']]
|
|
@@ -260,7 +264,7 @@ class Application(Gtk.Application):
|
|
|
260
264
|
t = self.session['target']
|
|
261
265
|
|
|
262
266
|
t['percent'] = round(( (t['starting_value'] + minutes) / t['value'] ) * 100,1)
|
|
263
|
-
print("At
|
|
267
|
+
print("At", t['percent'], "% of ", t['scope'], " target")
|
|
264
268
|
|
|
265
269
|
if t['type'] == 'max':
|
|
266
270
|
if t['percent'] >= 100:
|
|
@@ -275,64 +279,76 @@ class Application(Gtk.Application):
|
|
|
275
279
|
playsound('sound/xylophone-chord.mp3',False)
|
|
276
280
|
|
|
277
281
|
|
|
278
|
-
# maybe add a target % to the session and show with tick
|
|
279
282
|
|
|
280
283
|
icon_tick_number = 0
|
|
281
|
-
|
|
284
|
+
|
|
282
285
|
def tick(self):
|
|
283
|
-
menu = self.menu
|
|
284
|
-
indicator = self.indicator
|
|
285
286
|
|
|
286
287
|
# check for suspend indicated by gap in tick intervals
|
|
287
|
-
time_since_last_tick = round(time_difference(self.session['start_time'])
|
|
288
|
+
time_since_last_tick = round(time_difference(self.session['start_time']) - self.session['duration'])
|
|
288
289
|
if time_since_last_tick > 10:
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
dbg(time_since_last_tick, " seconds since last tick. Probably just woke from suspend. ")
|
|
291
291
|
if self.is_running:
|
|
292
292
|
self.open_task_window(None,{'afk_time':time_since_last_tick})
|
|
293
293
|
else:
|
|
294
|
-
print("resetting randomness timer")
|
|
295
|
-
self.session['duration'] = 0
|
|
296
294
|
self.session['start_time'] = now()
|
|
297
295
|
|
|
298
|
-
|
|
299
|
-
self.session['duration'] = int(time_difference(self.session['start_time']))
|
|
296
|
+
duration = self.session['duration'] = int(time_difference(self.session['start_time']))
|
|
300
297
|
|
|
301
|
-
if
|
|
298
|
+
if num_is_multiple_of(duration,60):
|
|
302
299
|
self.tock()
|
|
303
300
|
|
|
304
|
-
if
|
|
305
|
-
self.icon_tick_number
|
|
301
|
+
if self.is_running == True:
|
|
302
|
+
self.icon_tick_number += 1
|
|
306
303
|
|
|
307
304
|
if self.icon_tick_number > 8:
|
|
308
305
|
self.icon_tick_number = 1
|
|
309
306
|
|
|
310
|
-
label = self.session['label'] + ": " + sec_to_time(
|
|
307
|
+
label = self.session['label'] + ": " + sec_to_time(duration)
|
|
311
308
|
|
|
312
|
-
indicator.set_icon_full(os.path.abspath('icon/icon-'+str(self.icon_tick_number)+'.svg'),label)
|
|
309
|
+
self.indicator.set_icon_full(os.path.abspath('icon/icon-'+str(self.icon_tick_number)+'.svg'),label)
|
|
313
310
|
|
|
314
311
|
else:
|
|
315
312
|
|
|
316
|
-
# label = random.choice(conf.idle_messages) # Cool but makes menu bounce around #Could be paused when the menu opens
|
|
317
313
|
label = conf.user['default_text']
|
|
318
|
-
if
|
|
319
|
-
indicator.set_icon_full(os.path.abspath('icon/icon-red.svg'),label)
|
|
314
|
+
if duration > 60 and duration % 2:
|
|
315
|
+
self.indicator.set_icon_full(os.path.abspath('icon/icon-red.svg'),label)
|
|
320
316
|
else:
|
|
321
|
-
indicator.set_icon_full(os.path.abspath('icon/icon-1.svg'),label)
|
|
317
|
+
self.indicator.set_icon_full(os.path.abspath('icon/icon-1.svg'),label)
|
|
322
318
|
|
|
323
319
|
# https://lazka.github.io/pgi-docs/#AyatanaAppIndicator3-0.1/classes/Indicator.html#AyatanaAppIndicator3.Indicator.set_label
|
|
324
|
-
indicator.set_label(label,
|
|
325
|
-
|
|
326
|
-
for todo in conf.todo_sync_required:
|
|
327
|
-
# print('tick noticed a todo needing refreshment, time since refresh: ',time_difference(conf.todo_sync_times[todo]))
|
|
320
|
+
self.indicator.set_label(label,label)
|
|
328
321
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
conf.todo_sync_required = {}
|
|
322
|
+
if num_is_multiple_of(self.icon_tick_number,3):
|
|
323
|
+
self.refresh_all_changed_todo_files()
|
|
324
|
+
|
|
333
325
|
return True
|
|
334
326
|
|
|
335
327
|
|
|
328
|
+
def refresh_all_changed_todo_files(self):
|
|
329
|
+
for id, todo_config in conf.user['todolists'].items():
|
|
330
|
+
if todo_config['status'] and 'watch_file' in todo_config:
|
|
331
|
+
self.refresh_todo_if_file_changed(todo_config)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def refresh_todo_if_file_changed(self, todo_config):
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
todo_m_time = round(os.stat(todo_config['file']).st_mtime)
|
|
338
|
+
|
|
339
|
+
if todo_config['id'] not in conf.todo_file_change_times:
|
|
340
|
+
conf.todo_file_change_times[todo_config['id']] = todo_m_time
|
|
341
|
+
|
|
342
|
+
elif todo_m_time != conf.todo_file_change_times[todo_config['id']]:
|
|
343
|
+
dbg(todo_config['id']+" file was changed!",s='todoloading')
|
|
344
|
+
self.async_refresh(None,todo_config)
|
|
345
|
+
conf.todo_file_change_times[todo_config['id']] = todo_m_time
|
|
346
|
+
|
|
347
|
+
except Exception as e:
|
|
348
|
+
# TODO: consider how to quietly handle errors when refresh is not prompted by the user and temporarily disabling
|
|
349
|
+
handle_todo_read_error(todo_config,e)
|
|
350
|
+
|
|
351
|
+
|
|
336
352
|
def start_task(self, w = None, task_data_or_id = None, transfer_current_session_time = False):
|
|
337
353
|
|
|
338
354
|
if isinstance(task_data_or_id, dict):
|
|
@@ -356,7 +372,10 @@ class Application(Gtk.Application):
|
|
|
356
372
|
|
|
357
373
|
if(self.is_running == True):
|
|
358
374
|
self.stop_task()
|
|
359
|
-
|
|
375
|
+
elif(self.session['duration'] > 60):
|
|
376
|
+
# Log randomness session (to internal db only) if longer than one minute
|
|
377
|
+
utils.db_save_session(self.session)
|
|
378
|
+
|
|
360
379
|
|
|
361
380
|
self.is_running = True
|
|
362
381
|
|
|
@@ -442,34 +461,28 @@ class Application(Gtk.Application):
|
|
|
442
461
|
def mark_done(self, w=None, task = None):
|
|
443
462
|
''' second (task) argument is required and must be a task object '''
|
|
444
463
|
|
|
445
|
-
print("Mark Task done")
|
|
446
|
-
print(task)
|
|
464
|
+
print("Mark Task done",task['label'])
|
|
465
|
+
# print(task)
|
|
447
466
|
|
|
448
467
|
todolist_conf = conf.user['todolists'][task['todolist']]
|
|
449
468
|
|
|
450
469
|
try:
|
|
451
470
|
|
|
452
471
|
done_thread = threading.Thread(target=conf.todo_connectors[todolist_conf['type']].mark_task_done, args=(task,) )
|
|
472
|
+
|
|
453
473
|
conf.todo_sync_times[todolist_conf['id']] = now() # this is to avoid causing a refresh, perhaps not the best though
|
|
454
474
|
|
|
455
|
-
# Other Options:
|
|
456
|
-
# make a custom class extending Thread with callback method that runs del conf.file_watch_ignores[todolist_conf['id']]
|
|
457
|
-
# Complicated
|
|
458
|
-
# deal with file_watch_ignores in the connector
|
|
459
|
-
# poor seperation
|
|
460
|
-
#
|
|
461
475
|
done_thread.start()
|
|
462
476
|
|
|
463
477
|
db_query("UPDATE tasks set status = '0' WHERE id = ? ",(task['id'],) )
|
|
464
478
|
utils.reindex_one(task)
|
|
465
479
|
|
|
466
|
-
# print('remove menu item')
|
|
467
480
|
self.menu_tasks[task['id']].destroy()
|
|
468
|
-
|
|
481
|
+
|
|
469
482
|
playsound('sound/xylophone-chord.mp3',False)
|
|
470
483
|
|
|
471
484
|
except Exception as e:
|
|
472
|
-
error_notice('Error Marking Task Done'," Marking "+ task['label']+" as done in "+todolist_conf['label']+" had a serious failure",e )
|
|
485
|
+
error_notice('Error Marking Task Done'," Marking "+ task['label']+" as done in "+todolist_conf['label']+" had a serious failure", e=e )
|
|
473
486
|
|
|
474
487
|
|
|
475
488
|
|
|
@@ -510,8 +523,8 @@ class Application(Gtk.Application):
|
|
|
510
523
|
# print("add ",utils.extended_label(task)," to recent tasks")
|
|
511
524
|
i = Gtk.MenuItem.new_with_label(utils.extended_label(task))
|
|
512
525
|
i.connect('activate',self.start_task,task)
|
|
513
|
-
list_menus['recent'].prepend(i)
|
|
514
526
|
try:
|
|
527
|
+
list_menus['recent'].prepend(i)
|
|
515
528
|
list_menus['recent'].get_children()[11].destroy()
|
|
516
529
|
except Exception as e:
|
|
517
530
|
dbg("Exception trying to rotate recent tasks. probably are less than 11",l=2,s="recent")
|
|
@@ -533,19 +546,12 @@ class Application(Gtk.Application):
|
|
|
533
546
|
self.menu.show_all()
|
|
534
547
|
|
|
535
548
|
self.is_running = False
|
|
536
|
-
# print(utils.get_times(task))
|
|
537
549
|
if action != 'cancel':
|
|
538
550
|
notify.Notification.new("Focused on "+session['label']+" for "+sec_to_time(session['duration']),utils.pretty_dict(utils.get_times(task)), None).show()
|
|
539
551
|
# notify.Notification.new(action.capitalize()+" "+session['label']+" "+sec_to_time(session['duration']),utils.pretty_dict(utils.get_times(task)), None).show()
|
|
540
552
|
|
|
541
553
|
# Start randomness timer
|
|
542
|
-
self.session =
|
|
543
|
-
"label": 'Randomness',
|
|
544
|
-
"extended_label": 'Randomness',
|
|
545
|
-
"start_time": now(),
|
|
546
|
-
"duration":0,
|
|
547
|
-
"task":{}
|
|
548
|
-
}
|
|
554
|
+
self.session = default_session()
|
|
549
555
|
|
|
550
556
|
self.tick()
|
|
551
557
|
|
|
@@ -554,9 +560,6 @@ class Application(Gtk.Application):
|
|
|
554
560
|
self.menu.get_children()[0].destroy()
|
|
555
561
|
self.do_not_disturb_menu_item.set_label("Do Not Disturb")
|
|
556
562
|
|
|
557
|
-
else:
|
|
558
|
-
print('no task running!')
|
|
559
|
-
|
|
560
563
|
|
|
561
564
|
def task_running_menu_additions(self):
|
|
562
565
|
|
nowfocus/conf.py
CHANGED
|
@@ -52,20 +52,19 @@ connectors = {
|
|
|
52
52
|
todo_sync_time = datetime.now()
|
|
53
53
|
todo_sync_times = {}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
file_watch_ignores = {}
|
|
55
|
+
todo_file_change_times = {}
|
|
56
|
+
timers = {}
|
|
58
57
|
|
|
59
58
|
prototype_settings = {
|
|
60
59
|
"pomodoro_interval": 40,
|
|
61
60
|
"open_task_window_fullscreen": True,
|
|
61
|
+
"show_task_window_sidebars": False,
|
|
62
62
|
"randomness_interrupt_interval":5,
|
|
63
63
|
"default_text": "What am I doing?",
|
|
64
64
|
"todolist_refresh_interval":1,
|
|
65
|
-
"version":0.
|
|
65
|
+
"version":0.4,
|
|
66
66
|
"display_todolist_as_top_level_list":'auto',
|
|
67
67
|
'max_top_level_menu_items':10,
|
|
68
|
-
# 'tick_interval':1,
|
|
69
68
|
'hours_search_timeframe':'this year',
|
|
70
69
|
'invoice_hourly_rate':0,
|
|
71
70
|
|
nowfocus/connectors/todotxt.py
CHANGED
|
@@ -39,7 +39,6 @@ def add_new_task(user_conf,list,task_label):
|
|
|
39
39
|
|
|
40
40
|
todotxt.add(task)
|
|
41
41
|
todotxt.save()
|
|
42
|
-
# task.add_project
|
|
43
42
|
|
|
44
43
|
t = {
|
|
45
44
|
'id':task_label,
|
|
@@ -87,6 +86,7 @@ def get_todos(user_conf):
|
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
priority_letter_to_number_map = {'A':1,'B':2,'C':3,'D':4}
|
|
90
90
|
|
|
91
91
|
todotxt = pytodotxt.TodoTxt(user_conf['file'])
|
|
92
92
|
for t in todotxt.parse():
|
|
@@ -99,7 +99,6 @@ def get_todos(user_conf):
|
|
|
99
99
|
'parent_id':user_conf['id'],
|
|
100
100
|
'parent_label':user_conf['label'],
|
|
101
101
|
'status':1,
|
|
102
|
-
# 'priority':1 max, 5 min, 0 none,
|
|
103
102
|
'todolist':user_conf['id'],
|
|
104
103
|
'data':t.attributes #TODO: add other things like date etc
|
|
105
104
|
}
|
|
@@ -107,6 +106,12 @@ def get_todos(user_conf):
|
|
|
107
106
|
if t.is_completed:
|
|
108
107
|
tasks[id]['status'] = 0
|
|
109
108
|
|
|
109
|
+
if t.priority:
|
|
110
|
+
try:
|
|
111
|
+
tasks[id]['priority'] = priority_letter_to_number_map[t.priority]
|
|
112
|
+
except:
|
|
113
|
+
tasks[id]['priority'] = 5
|
|
114
|
+
|
|
110
115
|
|
|
111
116
|
if t.projects:
|
|
112
117
|
l = t.projects[0]
|
|
@@ -137,5 +142,5 @@ def get_todos(user_conf):
|
|
|
137
142
|
def launch(user_conf, item = None, category = None):
|
|
138
143
|
''' Open todolist '''
|
|
139
144
|
|
|
140
|
-
# It would b very nice to open
|
|
145
|
+
# It would b very nice to open the right line number but xdg-open doesn't support that...
|
|
141
146
|
utils.open_external(user_conf['file'])
|
nowfocus/connectors/txt.py
CHANGED
|
@@ -8,16 +8,17 @@ def add_new_task(user_conf,list,task_label):
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
with open(file_uri, 'r') as file:
|
|
11
|
-
# read a list of lines into data
|
|
12
11
|
data = file.readlines()
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
if list['id'] == user_conf['id']:
|
|
14
|
+
# Top level list, append to bottom
|
|
15
|
+
line_no = len(data) + 1
|
|
16
|
+
else:
|
|
17
|
+
line_no = get_line_no(list,data) + 1
|
|
15
18
|
|
|
16
|
-
line_no = get_line_no(list,data) + 1
|
|
17
19
|
new_line = list['data']['indent']+task_label+"\n"
|
|
18
20
|
|
|
19
21
|
# # Old version without recorded indent
|
|
20
|
-
|
|
21
22
|
# if list['id'] == user_conf['id']:
|
|
22
23
|
# # Top level insert
|
|
23
24
|
# new_line = task_label+"\n"
|
|
@@ -72,17 +73,15 @@ def get_line_no(i,lines):
|
|
|
72
73
|
else:
|
|
73
74
|
|
|
74
75
|
line_no = 0 #TODO: Improve, (better foreach with key)
|
|
75
|
-
in_list = False
|
|
76
76
|
|
|
77
77
|
for line in lines:
|
|
78
78
|
|
|
79
|
-
# TODO: Find the list
|
|
79
|
+
# TODO: Find the list
|
|
80
80
|
if line == i['data']['original_line']:
|
|
81
81
|
return line_no
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw("Line not found error Ha!")
|
|
83
|
+
|
|
84
|
+
utils.dbg("Line not found error",i,lines,s='txt',l=0)
|
|
86
85
|
|
|
87
86
|
def task_id(task):
|
|
88
87
|
''' The task parameter must have at least "label", "todolist" and "parent_id" '''
|
|
@@ -109,7 +108,7 @@ def mark_task_done(task):
|
|
|
109
108
|
if data[line_no].strip().startswith('[ ]'):
|
|
110
109
|
data[line_no] = data[line_no].replace('[ ]', '[x]')
|
|
111
110
|
else:
|
|
112
|
-
data[line_no] = data[line_no].replace(data[line_no].lstrip(), '[x]
|
|
111
|
+
data[line_no] = data[line_no].replace(data[line_no].lstrip(), '[x]'+ data[line_no].lstrip() )
|
|
113
112
|
|
|
114
113
|
# write everything back
|
|
115
114
|
with open(file_uri, 'w') as file:
|
|
@@ -128,18 +127,18 @@ def get_todos(user_conf):
|
|
|
128
127
|
tasks = {}
|
|
129
128
|
lists = {}
|
|
130
129
|
lists[user_conf['id']] = {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
130
|
+
'id':user_conf['id'],
|
|
131
|
+
'label':user_conf['label'],
|
|
132
|
+
'parent_id':'',
|
|
133
|
+
'parent_label':'',
|
|
134
|
+
'todolist':user_conf['id'],
|
|
135
|
+
'data':{
|
|
136
|
+
'line_no':0,
|
|
137
|
+
'original_line':'',
|
|
138
|
+
'accepts_tasks':True,
|
|
139
|
+
'indent':'',
|
|
140
|
+
}
|
|
141
|
+
}
|
|
143
142
|
|
|
144
143
|
current_list = []
|
|
145
144
|
current_list_label = []
|
|
@@ -177,7 +176,18 @@ def get_todos(user_conf):
|
|
|
177
176
|
status = 0
|
|
178
177
|
|
|
179
178
|
label = label.removeprefix('[ ]').removeprefix('[]').strip()
|
|
180
|
-
#TODO: use markdown title syntax as list name in addition to indentation
|
|
179
|
+
#TODO: use markdown title syntax as list name in addition to indentation?
|
|
180
|
+
|
|
181
|
+
# tags = ''
|
|
182
|
+
# # hashtag tagging
|
|
183
|
+
# if label.split("#").len() > 1:
|
|
184
|
+
# parts = label.split("#")
|
|
185
|
+
# print("label has hashtag",parts)
|
|
186
|
+
|
|
187
|
+
# label = parts[0].strip()
|
|
188
|
+
# del parts[0]
|
|
189
|
+
# tags = ",".join(parts)
|
|
190
|
+
|
|
181
191
|
|
|
182
192
|
indent = len(line) - len(line.lstrip())
|
|
183
193
|
indent_str = line[0:indent]
|
|
@@ -205,6 +215,13 @@ def get_todos(user_conf):
|
|
|
205
215
|
|
|
206
216
|
|
|
207
217
|
utils.dbg(line.rstrip(),s='txt',l=3)
|
|
218
|
+
|
|
219
|
+
# Get prioritized tasks (starting with a number)
|
|
220
|
+
if label.split()[0].isnumeric():
|
|
221
|
+
priority = float(label.split()[0])
|
|
222
|
+
label = label.removeprefix(label.split()[0]).strip()
|
|
223
|
+
else:
|
|
224
|
+
priority = '0'
|
|
208
225
|
|
|
209
226
|
task_id = file_uri+":"+"/".join(current_list_label)+":"+label
|
|
210
227
|
|
|
@@ -222,6 +239,8 @@ def get_todos(user_conf):
|
|
|
222
239
|
'parent_label':str(parent_label),
|
|
223
240
|
'todolist':user_conf['id'],
|
|
224
241
|
'status': status,
|
|
242
|
+
'priority': priority,
|
|
243
|
+
# 'tags':tags,
|
|
225
244
|
'data':{
|
|
226
245
|
'line_no':line_no,
|
|
227
246
|
'original_line':line,
|
|
@@ -229,11 +248,7 @@ def get_todos(user_conf):
|
|
|
229
248
|
}
|
|
230
249
|
}
|
|
231
250
|
|
|
232
|
-
|
|
233
|
-
# if label[0].isdigit()
|
|
234
|
-
if label.split()[0].isnumeric():
|
|
235
|
-
tasks[task_id]['priority'] = float(label.split()[0])
|
|
236
|
-
tasks[task_id]['label'] = label.removeprefix(label.split()[0]).strip()
|
|
251
|
+
|
|
237
252
|
|
|
238
253
|
prev_indent = indent
|
|
239
254
|
prev_label = label
|
nowfocus/new_task_dialog.py
CHANGED
|
@@ -87,9 +87,9 @@ class NewTaskWDialog(Gtk.Dialog):
|
|
|
87
87
|
todolist_conf = conf.user['todolists'][parent_list['todolist']]
|
|
88
88
|
try:
|
|
89
89
|
|
|
90
|
-
conf.file_watch_ignores[todolist_conf['id']] = True
|
|
91
90
|
task = conf.todo_connectors[todolist_conf['type']].add_new_task(todolist_conf,parent_list,task_label)
|
|
92
|
-
|
|
91
|
+
|
|
92
|
+
clear_todo_file_change_time(todolist_conf)
|
|
93
93
|
|
|
94
94
|
dbg('connector add task response',task)
|
|
95
95
|
|
|
@@ -121,9 +121,8 @@ class NewTaskWDialog(Gtk.Dialog):
|
|
|
121
121
|
if conf.debug_level > 1:
|
|
122
122
|
raise e
|
|
123
123
|
|
|
124
|
-
error_notice('Error adding tasks',"Adding "+ task_label+" to "+todolist_conf['label']+" had a serious failure",e
|
|
124
|
+
error_notice('Error adding tasks',"Adding "+ task_label+" to "+todolist_conf['label']+" had a serious failure",e) # NOTE: e, in the case of a key error, only prints the bad key, not the error type
|
|
125
125
|
|
|
126
|
-
|
|
127
126
|
|
|
128
127
|
elif response == Gtk.ResponseType.CANCEL:
|
|
129
128
|
print("Cancel button clicked")
|