nowfocus 0.2.8__tar.gz → 0.2.12__tar.gz

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.
Files changed (63) hide show
  1. {nowfocus-0.2.8/src/nowfocus.egg-info → nowfocus-0.2.12}/PKG-INFO +9 -6
  2. {nowfocus-0.2.8 → nowfocus-0.2.12}/README.md +7 -5
  3. {nowfocus-0.2.8 → nowfocus-0.2.12}/pyproject.toml +3 -5
  4. nowfocus-0.2.12/src/nowfocus/__init__.py +1 -0
  5. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/__main__.py +42 -24
  6. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/conf.py +12 -5
  7. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/todotxt.py +2 -19
  8. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/txt.py +31 -20
  9. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/vikunja.py +6 -4
  10. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/new_task_dialog.py +4 -1
  11. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/settings.py +55 -25
  12. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/task_window.py +4 -4
  13. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/utils.py +143 -21
  14. {nowfocus-0.2.8 → nowfocus-0.2.12/src/nowfocus.egg-info}/PKG-INFO +9 -6
  15. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/requires.txt +1 -0
  16. nowfocus-0.2.8/src/nowfocus/__init__.py +0 -68
  17. {nowfocus-0.2.8 → nowfocus-0.2.12}/LICENSE +0 -0
  18. {nowfocus-0.2.8 → nowfocus-0.2.12}/setup.cfg +0 -0
  19. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/activitywatch.py +0 -0
  20. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/caldav.py +0 -0
  21. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/csv.py +0 -0
  22. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/psc_timetracker.py +0 -0
  23. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/taskwarrior.py +0 -0
  24. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/timewarrior.py +0 -0
  25. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/todo_template.py +0 -0
  26. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/connectors/trello.py +0 -0
  27. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/desktop-extras/nowfocus.desktop +0 -0
  28. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/desktop-extras/nowfocus.png +0 -0
  29. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/desktop-extras/nowfocus.svg +0 -0
  30. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/example-todo.txt +0 -0
  31. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/cancel.png +0 -0
  32. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/cancel.svg +0 -0
  33. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/edit.png +0 -0
  34. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/edit.svg +0 -0
  35. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-0.svg +0 -0
  36. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-1.svg +0 -0
  37. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-2.svg +0 -0
  38. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-3.svg +0 -0
  39. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-4.svg +0 -0
  40. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-5.svg +0 -0
  41. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-6.svg +0 -0
  42. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-7.svg +0 -0
  43. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-8.svg +0 -0
  44. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-9.svg +0 -0
  45. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon-red.svg +0 -0
  46. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/icon.svg +0 -0
  47. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/mark-done.png +0 -0
  48. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/mark-done.svg +0 -0
  49. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/pause.png +0 -0
  50. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/pause.svg +0 -0
  51. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/settings.png +0 -0
  52. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/icon/settings.svg +0 -0
  53. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/session_edit_dialog.py +0 -0
  54. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/session_options.py +0 -0
  55. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sessions.csv +0 -0
  56. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sound/bell-xylophone-g.mp3 +0 -0
  57. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sound/dinner-bell.mp3 +0 -0
  58. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/sound/xylophone-chord.mp3 +0 -0
  59. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus/styles.css +0 -0
  60. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/SOURCES.txt +0 -0
  61. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/dependency_links.txt +0 -0
  62. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/entry_points.txt +0 -0
  63. {nowfocus-0.2.8 → nowfocus-0.2.12}/src/nowfocus.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nowfocus
3
- Version: 0.2.8
3
+ Version: 0.2.12
4
4
  Summary: nowfocus: the open source task-tracking self-control panel.
5
5
  Author: GitFr33
6
6
  Project-URL: Homepage, https://gitlab.com/GitFr33/nowfocus
@@ -42,6 +42,7 @@ Requires-Dist: tzlocal
42
42
  Requires-Dist: urllib3
43
43
  Requires-Dist: vobject
44
44
  Requires-Dist: x-wr-timezone
45
+ Requires-Dist: watchdog
45
46
  Dynamic: license-file
46
47
 
47
48
  <div align="center"><img src="https://gitlab.com/GitFr33/nowfocus/-/raw/main/nowfocus.svg" width="60" align="center">
@@ -52,15 +53,15 @@ Dynamic: license-file
52
53
 
53
54
  </div>
54
55
 
55
- nowfocus is a clean, keyboard-driven project time tracker build with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
56
+ nowfocus is a clean, keyboard-driven project time tracker built with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
56
57
 
57
58
  ## Features
58
59
  - Unlimited flexible combinations of to-do lists and time tracking systems
59
60
  - Infinitely nestable lists
60
61
  - Inactivity detection that automatically pauses time tracking
61
62
  - Pomodoro timer
62
- - Task prioritization
63
- - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
63
+ - Task prioritization
64
+ - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
64
65
  - Randomness interrupt bell (optional) to keep you on track with tracking your time
65
66
  - Keyboard-driven interface
66
67
  - Offline to-do list cache
@@ -76,7 +77,7 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
76
77
 
77
78
  ### Currently Supported To-do List Backends
78
79
 
79
- - Simple text or markdown file with indentation based sub-lists
80
+ - Simple text file with indentation based sub-lists
80
81
  - Any to-do list that supports [CalDav todos](https://en.wikipedia.org/wiki/CalDAV)
81
82
  - [todotxt format](http://todotxt.org/)
82
83
  - [TaskWarrior](https://taskwarrior.org/)
@@ -99,7 +100,9 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
99
100
  # Install dependencies
100
101
  sudo apt install pipx gir1.2-appindicator3-0.1 meson libdbus-glib-1-dev patchelf libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev
101
102
 
102
- # Set up pipx
103
+ # note gir1.2-ayatanaappindicator3-0.1 can be substituted for gir1.2-appindicator3-0.1
104
+
105
+ # Set up pipx
103
106
  pipx ensurepath
104
107
 
105
108
  # At this point you may need to restart your terminal window
@@ -6,15 +6,15 @@
6
6
 
7
7
  </div>
8
8
 
9
- nowfocus is a clean, keyboard-driven project time tracker build with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
9
+ nowfocus is a clean, keyboard-driven project time tracker built with python + GTK that flexibly connects multiple to-do lists with multiple time trackers and displays your current task and time spent in the status bar.
10
10
 
11
11
  ## Features
12
12
  - Unlimited flexible combinations of to-do lists and time tracking systems
13
13
  - Infinitely nestable lists
14
14
  - Inactivity detection that automatically pauses time tracking
15
15
  - Pomodoro timer
16
- - Task prioritization
17
- - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
16
+ - Task prioritization
17
+ - Time targets: set a minimum or maximum time for any task or list of tasks and get reminded to follow though
18
18
  - Randomness interrupt bell (optional) to keep you on track with tracking your time
19
19
  - Keyboard-driven interface
20
20
  - Offline to-do list cache
@@ -30,7 +30,7 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
30
30
 
31
31
  ### Currently Supported To-do List Backends
32
32
 
33
- - Simple text or markdown file with indentation based sub-lists
33
+ - Simple text file with indentation based sub-lists
34
34
  - Any to-do list that supports [CalDav todos](https://en.wikipedia.org/wiki/CalDAV)
35
35
  - [todotxt format](http://todotxt.org/)
36
36
  - [TaskWarrior](https://taskwarrior.org/)
@@ -53,7 +53,9 @@ nowfocus is a clean, keyboard-driven project time tracker build with python + GT
53
53
  # Install dependencies
54
54
  sudo apt install pipx gir1.2-appindicator3-0.1 meson libdbus-glib-1-dev patchelf libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev
55
55
 
56
- # Set up pipx
56
+ # note gir1.2-ayatanaappindicator3-0.1 can be substituted for gir1.2-appindicator3-0.1
57
+
58
+ # Set up pipx
57
59
  pipx ensurepath
58
60
 
59
61
  # At this point you may need to restart your terminal window
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nowfocus"
3
- version = "0.2.8"
3
+ version = "0.2.12"
4
4
  authors = [
5
5
  { name="GitFr33" },
6
6
  ]
@@ -15,12 +15,9 @@ classifiers = [
15
15
  "Intended Audience :: End Users/Desktop",
16
16
  ]
17
17
 
18
- # license-files = ["LICEN[CS]E*"]
18
+ license-files = ["LICENSE"]
19
19
 
20
20
  dependencies = [
21
- # "pycairo",
22
- # "PyGObject",
23
-
24
21
  "psutil",
25
22
  "pygobject == 3.50.0",
26
23
  "pycairo == 1.27",
@@ -52,6 +49,7 @@ dependencies = [
52
49
  "urllib3",
53
50
  "vobject",
54
51
  "x-wr-timezone",
52
+ "watchdog",
55
53
  ]
56
54
 
57
55
  [project.urls]
@@ -0,0 +1 @@
1
+
@@ -50,7 +50,6 @@ from session_options import SessionOptionsDialog
50
50
  dbg(conf.user,l=3,s='user_settings')
51
51
 
52
52
  setproctitle.setproctitle(conf.app_name)
53
- pipe_file = os.path.dirname(os.path.realpath(__file__))+"/"+conf.app_id+"_pipe"
54
53
 
55
54
  print(conf.app_name +" running from " + os.path.dirname(os.path.realpath(__file__)))
56
55
 
@@ -106,6 +105,8 @@ class Application(Gtk.Application):
106
105
  except Exception as e:
107
106
  dbg("Error resuming session",e,l=1)
108
107
 
108
+ utils.start_todo_file_watchers()
109
+
109
110
  self.pipethread = threading.Thread(target=self.check_pipe)
110
111
  self.pipethread.daemon = True
111
112
  self.pipethread.start()
@@ -163,11 +164,11 @@ class Application(Gtk.Application):
163
164
  print('Caching active session', self.session['label'])
164
165
  db_set_session_cache(self.session)
165
166
  try:
166
- # print("before os.remove(pipe_file)")
167
- os.remove(pipe_file)
167
+ # print("before os.remove(conf.pipe)")
168
+ os.remove(conf.pipe)
168
169
  print("Pipe removed")
169
170
  except Exception as e:
170
- print("Error removing pipe_file in quit",e)
171
+ print("Error removing conf.pipe in quit",e)
171
172
 
172
173
  notify.uninit()
173
174
  Gtk.main_quit()
@@ -217,6 +218,8 @@ class Application(Gtk.Application):
217
218
  if (int(time_difference(conf.todo_sync_time)) / 60) > conf.user['todolist_refresh_interval'] * 60 :
218
219
  self.async_refresh()
219
220
 
221
+ # TODO: use individual todo_refresh_times
222
+
220
223
  minutes = (int(self.session['duration']) / 60)
221
224
 
222
225
  if(self.is_running == False):
@@ -320,6 +323,13 @@ class Application(Gtk.Application):
320
323
  # https://lazka.github.io/pgi-docs/#AyatanaAppIndicator3-0.1/classes/Indicator.html#AyatanaAppIndicator3.Indicator.set_label
321
324
  indicator.set_label(label, "Wide")
322
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]))
328
+
329
+ if time_difference(conf.todo_sync_times[todo]) > 4:
330
+ # print('tick noticed a todo needing refreshment')
331
+ self.async_refresh(None,conf.user['todolists'][todo])
332
+ conf.todo_sync_required = {}
323
333
  return True
324
334
 
325
335
 
@@ -431,13 +441,23 @@ class Application(Gtk.Application):
431
441
 
432
442
  def mark_done(self, w=None, task = None):
433
443
  ''' second (task) argument is required and must be a task object '''
444
+
434
445
  print("Mark Task done")
435
446
  print(task)
436
447
 
437
448
  todolist_conf = conf.user['todolists'][task['todolist']]
438
449
 
439
450
  try:
451
+
440
452
  done_thread = threading.Thread(target=conf.todo_connectors[todolist_conf['type']].mark_task_done, args=(task,) )
453
+ conf.todo_sync_times[todolist_conf['id']] = now() # this is to avoid causing a refresh, perhaps not the best though
454
+
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
+ #
441
461
  done_thread.start()
442
462
 
443
463
  db_query("UPDATE tasks set status = '0' WHERE id = ? ",(task['id'],) )
@@ -454,7 +474,7 @@ class Application(Gtk.Application):
454
474
 
455
475
 
456
476
  def stop_task(self, w = '', action = 'save', custom_end_time=None):
457
- ''' supported actions are save,cancel,mark_done '''
477
+ ''' supported actions are save, cancel, mark_done '''
458
478
 
459
479
  list_menus = self.list_menus
460
480
  menu_tasks = self.menu_tasks
@@ -543,7 +563,6 @@ class Application(Gtk.Application):
543
563
  i = Gtk.ImageMenuItem.new_with_label("Edit Session")
544
564
  i.set_image(Gtk.Image.new_from_file(os.path.abspath('icon/edit.svg')))
545
565
 
546
- # i.set_image(Gtk.Image.new_from_file('/usr/share/icons/Yaru/scalable/status/software-installed-symbolic.svg'))
547
566
  i.set_always_show_image(True)
548
567
  i.connect("activate", self.open_session_options_dialog,'from_menu')
549
568
  self.menu.insert(i,0)
@@ -551,33 +570,34 @@ class Application(Gtk.Application):
551
570
  i = Gtk.ImageMenuItem.new_with_label("Pause" )
552
571
  i.set_image(Gtk.Image.new_from_file(os.path.abspath('icon/pause.svg')))
553
572
 
554
- # i.set_image(Gtk.Image.new_from_file('/usr/share/icons/Yaru/scalable/multimedia/pause-symbolic.svg'))
555
573
  i.set_always_show_image(True)
556
574
  i.connect("activate", self.stop_task)
557
575
  self.menu.insert(i,0)
558
576
 
559
577
 
560
- def async_refresh(self, w=None):
578
+ def async_refresh(self, w=None, single_todo = None):
561
579
 
562
580
  self.indicator.set_label("Refreshing Todolists", "Wide")
563
581
  menu_item = Gtk.MenuItem.new_with_label("Refreshing Todolists")
564
582
  self.menu.append(menu_item)
565
583
  self.menu.show_all()
566
584
 
567
- connectors_thread = threading.Thread(target=self.async_refresh_inner)
585
+ connectors_thread = threading.Thread(target=self.async_refresh_inner,args=(single_todo,))
568
586
  connectors_thread.start()
569
587
 
570
588
 
571
- def async_refresh_inner(self):
589
+ def async_refresh_inner(self, single_todo = None):
572
590
  # dbg("async refresh started",s="todoloading",l=3)
573
- utils.get_todolists()
591
+ if single_todo:
592
+ utils.refresh_todolist(single_todo)
593
+ utils.reindex()
594
+
595
+ else:
596
+ utils.get_todolists()
597
+
574
598
  dbg("async refresh complete",s="todoloading",l=3)
575
599
  GLib.idle_add(self.update_menu)
576
600
 
577
- try:
578
- GLib.idle_add(self.taskwindow.refresh_search_cache)
579
- except Exception:
580
- pass
581
601
 
582
602
 
583
603
  def update_menu(self, w = ''):
@@ -727,10 +747,10 @@ class Application(Gtk.Application):
727
747
  dbg("no handler for received signal",s='signals',l=3)
728
748
 
729
749
  def check_pipe(self):
730
- # print("Listening to pipe at ",pipe_file)
750
+ # print("Listening to pipe at ",conf.pipe)
731
751
 
732
752
  try:
733
- with open(pipe_file, "r") as pipe:
753
+ with open(conf.pipe, "r") as pipe:
734
754
  data = pipe.read().strip()
735
755
 
736
756
  print("check_pipe ")
@@ -739,10 +759,8 @@ class Application(Gtk.Application):
739
759
  pipe.close()
740
760
 
741
761
  # TODO: add registry of special commands
762
+ # How to handle function arguments? (for example refresh a todolist with it's id)
742
763
  # if data in ['quit','open_task_window']:
743
- # TODO: how to all dynamic function name here?
744
- # GLib.idle_add(self.open_task_window)
745
-
746
764
 
747
765
  if data == 'quit':
748
766
  GLib.idle_add(self.quit)
@@ -795,13 +813,13 @@ def startup():
795
813
  if args.force:
796
814
  print("Lanched with --force flag, forcibly deleting old pipe")
797
815
  try:
798
- os.remove(pipe_file)
816
+ os.remove(conf.pipe)
799
817
  except Exception as e:
800
818
  print(e)
801
819
 
802
820
 
803
821
  try:
804
- os.mkfifo(pipe_file)
822
+ os.mkfifo(conf.pipe)
805
823
  dbg("Named pipe created successfully!", s="cli")
806
824
 
807
825
  signal.signal(signal.SIGUSR1, Application.signal_handler)
@@ -809,7 +827,7 @@ def startup():
809
827
 
810
828
  if args.task:
811
829
  print("Writing args.task to pipe", args.task)
812
- with open(pipe_file, "w") as pipeout:
830
+ with open(conf.pipe, "w") as pipeout:
813
831
  pipeout.write(args.task)
814
832
  pipeout.close()
815
833
 
@@ -827,7 +845,7 @@ def startup():
827
845
 
828
846
  dbg("Writing arg to pipe ",pipe_line, s="cli")
829
847
 
830
- with open(pipe_file, "w") as pipeout:
848
+ with open(conf.pipe, "w") as pipeout:
831
849
  pipeout.write(pipe_line)
832
850
  pipeout.close()
833
851
 
@@ -22,20 +22,24 @@ Path(user_settings_dir).mkdir(parents=True, exist_ok=True)
22
22
 
23
23
  debug_level = 1 # dev value
24
24
  debug_systems = []
25
+ pipe = "/tmp/"+app_id+"-pipe" # Will that work?
25
26
 
26
27
  # key and type must be the same, (Seems redundant but it's is quite helpful)
27
28
  connectors = {
28
29
  "todolists":{
29
- 'txt':{'id':'txt','type':'txt','label':'','file':'',"status":True,'timetracker':''},
30
+ 'txt':{'id':'txt','type':'txt','label':'','file':'', "watch_file":True, "status":True,'timetracker':''},
30
31
 
31
32
  'trello':{'id':'trello','type':'trello','label':'Trello','APIKey':'aecf1d7791b4f1a4bb7d6ca5827ba0d3', 'token':'',"status":True,'timetracker':''},
32
33
 
33
34
  'vikunja':{'id':'vikunja','type':'vikunja','label':'Vikunja','url':'http://localhost:3456/', 'token':'',"username":"","status":True,'timetracker':''},
34
35
 
35
36
  'caldav':{'id':'caldav','type':'caldav','label':'CalDav Todo','url':'http://localhost:3456/','password': "",'username': '',"status":True,'timetracker':''},
37
+
36
38
  'psc_timetracker':{'id':'psc_timetracker','type':'psc_timetracker','label':'Timetracker','url':'https://photosynth.ca/timetracker/','key':'',"status":True,'timetracker':'Timetracker'},
39
+
37
40
  'taskwarrior':{'id':'taskwarrior','type':'taskwarrior','label':'TaskWarrior',"status":True,'timetracker':''},
38
- 'todotxt':{'id':'todotxt','type':'todotxt','label':'','file':"","status":True,'timetracker':''},
41
+
42
+ 'todotxt':{'id':'todotxt','type':'todotxt','label':'','file':"", "watch_file":True, "status":True,'timetracker':''},
39
43
  },
40
44
  "timetrackers":{
41
45
  'csv':{'id':'csv','type':'csv','label':'CSV file','file':'sessions.csv',"status":True},
@@ -46,18 +50,21 @@ connectors = {
46
50
  }
47
51
 
48
52
  todo_sync_time = datetime.now()
53
+ todo_sync_times = {}
49
54
 
50
55
  todo_sync_required = {}
56
+ file_watchers = {}
57
+ file_watch_ignores = {}
51
58
 
52
59
  prototype_settings = {
53
- "pomodoro_interval": 26,
60
+ "pomodoro_interval": 40,
54
61
  "open_task_window_fullscreen": True,
55
62
  "randomness_interrupt_interval":5,
56
63
  "default_text": "What am I doing?",
57
- "todolist_refresh_interval":6,
64
+ "todolist_refresh_interval":1,
58
65
  "version":0.2,
59
66
  "display_todolist_as_top_level_list":'auto',
60
- 'max_top_level_menu_items':18,
67
+ 'max_top_level_menu_items':10,
61
68
  # 'tick_interval':1,
62
69
  'hours_search_timeframe':'this year',
63
70
  'invoice_hourly_rate':0,
@@ -20,24 +20,6 @@ import utils
20
20
  # }
21
21
  # }
22
22
 
23
- # def watch(user_conf):
24
- # import pyinotify
25
-
26
- # """Watch for modifications of the todo file with pyinotify."""
27
- # wm = pyinotify.WatchManager()
28
- # notifier = pyinotify.ThreadedNotifier(wm, mark_changed, user_conf)
29
- # notifier.start()
30
-
31
- # # wm.add_watch(os.path.dirname(user_conf['file']), pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO) # Watch whole folder. Perhaps more reliable
32
- # wm.add_watch(user_conf['file'], pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO) # just the file. Perhaps better
33
-
34
-
35
- # def mark_changed(event, user_conf):
36
- # ''' This is in a thread '''
37
- # if event.pathname == user_conf['file']:
38
- # conf.todo_sync_required[user_conf['id']] = True
39
-
40
-
41
23
 
42
24
  def task_id(user_conf, t):
43
25
  ''' The task parameter must have at least "label", "todolist" and "parent_id" '''
@@ -71,7 +53,8 @@ def add_new_task(user_conf,list,task_label):
71
53
  }
72
54
  }
73
55
  return t
74
-
56
+
57
+
75
58
  def mark_task_done(task):
76
59
  '''Return True on success False on error'''
77
60
 
@@ -11,24 +11,32 @@ def add_new_task(user_conf,list,task_label):
11
11
  # read a list of lines into data
12
12
  data = file.readlines()
13
13
 
14
- if list['id'] == user_conf['id']:
15
- # Top level insert
16
- new_line = task_label+"\n"
17
- line_no = len(data) + 1
18
- else:
19
- # Sub list insert
20
- # TODO: Add it at the end of the list rather than the start ...
21
- # Yup. rjust(indent + len)
22
-
23
- indent = len(list['data']['original_line']) - len(list['data']['original_line'].lstrip()) + 4 #BUG: check indent of next line instead of arbitrarily using 4 spaces
14
+ # New Version
15
+
16
+ line_no = get_line_no(list,data) + 1
17
+ new_line = list['data']['indent']+task_label+"\n"
18
+
19
+ # # Old version without recorded indent
20
+
21
+ # if list['id'] == user_conf['id']:
22
+ # # Top level insert
23
+ # new_line = task_label+"\n"
24
+ # line_no = len(data) + 1
25
+ # else:
26
+ # Sub list insert
27
+ # TODO: Add it at the end of the list rather than the start ...
28
+ # Yup. rjust(indent + len)
29
+
30
+
31
+ # indent = len(list['data']['original_line']) - len(list['data']['original_line'].lstrip()) + 4 #BUG: check indent of next line instead of arbitrarily using 4 spaces
24
32
 
25
- new_line = task_label+"\n"
26
- # I'm sure there's a better way to add a bunch of spaces to to a string
27
- while indent > 0:
28
- new_line = " " + new_line
29
- indent = indent - 1
33
+ # new_line = task_label+"\n"
34
+ # # I'm sure there's a better way to add a bunch of spaces to to a string
35
+ # while indent > 0:
36
+ # new_line = " " + new_line
37
+ # indent = indent - 1
30
38
 
31
- line_no = get_line_no(list,data) + 1
39
+ # line_no = get_line_no(list,data) + 1
32
40
 
33
41
  data.insert(line_no,new_line)
34
42
 
@@ -128,7 +136,8 @@ def get_todos(user_conf):
128
136
  'data':{
129
137
  'line_no':0,
130
138
  'original_line':'',
131
- 'accepts_tasks':True
139
+ 'accepts_tasks':True,
140
+ 'indent':'',
132
141
  }
133
142
  }
134
143
 
@@ -149,7 +158,7 @@ def get_todos(user_conf):
149
158
  # file_uri = "todo.txt"
150
159
  line_no = -1
151
160
 
152
- with open(file_uri) as file:
161
+ with open(file_uri,"r") as file:
153
162
  for line in file:
154
163
 
155
164
  line_no = line_no + 1
@@ -171,11 +180,13 @@ def get_todos(user_conf):
171
180
  #TODO: use markdown title syntax as list name in addition to indentation
172
181
 
173
182
  indent = len(line) - len(line.lstrip())
183
+ indent_str = line[0:indent]
174
184
 
175
185
  if indent > prev_indent:
176
- # prev item is a sub_list header, move it to lists
186
+ # prev item is a sub_list header, copy it to lists
177
187
  lists[prev_id] = tasks[prev_id]
178
- del tasks[prev_id]
188
+ lists[prev_id]['data']['indent'] = indent_str
189
+ # del tasks[prev_id]
179
190
  current_list.append(prev_id)
180
191
  current_list_label.append(prev_label)
181
192
  current_indent.append(indent)
@@ -294,14 +294,16 @@ def get_todos(user_conf):
294
294
  return todos
295
295
 
296
296
 
297
- def launch(user_conf, item = None, category = None):
297
+ def launch(user_conf, item = None, category = 'task'):
298
298
  ''' Open Vikunja '''
299
+ utils.dbg("vikunja.py launch","user_conf", user_conf,"item",item,'category',category,l=3, s='vikunja')
300
+
299
301
  url = user_conf['url']
300
302
 
301
- if item and category:
302
- if category == 'list':
303
+ if item:
304
+ if category in ('list','lists'):
303
305
  url += 'projects/'+str(item['data']['id'])
304
- elif category == 'task':
306
+ else:
305
307
  url += 'tasks/'+str(item['data']['id'])
306
308
 
307
309
  utils.open_external(url)
@@ -77,7 +77,7 @@ class NewTaskWDialog(Gtk.Dialog):
77
77
 
78
78
  if response == 1 or response == 2:
79
79
 
80
- task_label = self.task_label_entry.get_text()
80
+ task_label = self.task_label_entry.get_text().strip()
81
81
 
82
82
  if not task_label:
83
83
  error_notice("Please Enter a name before saving new task")
@@ -86,7 +86,10 @@ class NewTaskWDialog(Gtk.Dialog):
86
86
  parent_list = self.selected_list
87
87
  todolist_conf = conf.user['todolists'][parent_list['todolist']]
88
88
  try:
89
+
90
+ conf.file_watch_ignores[todolist_conf['id']] = True
89
91
  task = conf.todo_connectors[todolist_conf['type']].add_new_task(todolist_conf,parent_list,task_label)
92
+ del conf.file_watch_ignores[todolist_conf['id']]
90
93
 
91
94
  dbg('connector add task response',task)
92
95