@@ -693,6 +693,128 @@ def __hash__(self) -> int:
693
693
# ------------------------------------------------------------------------------
694
694
# STORE
695
695
# ------------------------------------------------------------------------------
696
+ class FilteredTaskTreeManager :
697
+
698
+
699
+ def __init__ (self ,store :'TaskStore' ,task_filter :Gtk .Filter ) -> None :
700
+ self .root_model : Gio .ListStore = Gio .ListStore .new (Task )
701
+ self .task_filter : Gtk .Filter = task_filter
702
+ self .tid_to_subtask_model : Dict [UUID ,Gio .ListStore ] = dict ()
703
+ self .tid_to_containing_model : Dict [UUID ,Gio .ListStore ] = dict ()
704
+ self .tree_model = Gtk .TreeListModel .new (self .root_model , False , False , self ._model_expand )
705
+ self .store = store
706
+ self ._find_root_tasks ()
707
+
708
+
709
+ def get_tree_model (self ):
710
+ return self .tree_model
711
+
712
+
713
+ def set_filter (self ,new_filter :Gtk .Filter ):
714
+ self .task_filter = new_filter
715
+ self .task_filter .connect ('changed' ,self ._on_changed )
716
+ self ._refilter_all_tasks ()
717
+
718
+
719
+ def _refilter_all_tasks (self ) -> None :
720
+ for t in self .store .data :
721
+ self ._update_with_descendants (t )
722
+
723
+
724
+ def _on_changed (self ,* args ):
725
+ self ._refilter_all_tasks ()
726
+
727
+
728
+ def _find_root_tasks (self ) -> None :
729
+ self .root_model .remove_all ()
730
+ for t in self .store .lookup .values ():
731
+ if self ._should_be_root_item (t ):
732
+ self .root_model .append (t )
733
+ self .tid_to_containing_model [t .id ] = self .root_model
734
+
735
+
736
+ def _should_be_root_item (self ,t :Task ):
737
+ if not self .task_filter .do_match (t ):
738
+ return False
739
+ return t .parent is None or not self .task_filter .do_match (t .parent )
740
+
741
+
742
+ def update_position_of (self ,t :Task ):
743
+ if not self .task_filter .do_match (t ):
744
+ self .remove (t )
745
+ return
746
+ if not self ._in_the_right_model (t ):
747
+ self .remove (t )
748
+ self .add (t )
749
+
750
+ def _update_with_descendants (self ,t :Task ):
751
+ self .update_position_of (t )
752
+ for c in t .children :
753
+ self ._update_with_descendants (c )
754
+
755
+
756
+ def _in_the_right_model (self ,t :Task ):
757
+ """Return true if and only if the task matching the filter is in the correct ListStore or not yet present."""
758
+ current_model = self ._get_containing_model (t )
759
+ correct_model = self ._get_correct_containing_model (t )
760
+
761
+ if current_model is None and correct_model is None :
762
+ return True
763
+ if current_model == correct_model :
764
+ assert correct_model is not None
765
+ pos = correct_model .find (t )
766
+ return pos [0 ]
767
+ return False
768
+
769
+
770
+ def add (self ,task :Task ):
771
+ """Add the task to the correct ListStore."""
772
+ model = self ._get_correct_containing_model (task )
773
+ if model is None :
774
+ return
775
+ model .append (task )
776
+ self .tid_to_containing_model [task .id ] = model
777
+
778
+
779
+ def remove (self ,task :Task ):
780
+ """Remove the task from the containing ListStore."""
781
+ model = self ._get_containing_model (task )
782
+ if model is None :
783
+ return
784
+ pos = model .find (task )
785
+ if pos [0 ]:
786
+ model .remove (pos [1 ])
787
+ del self .tid_to_containing_model [task .id ]
788
+
789
+
790
+ def _get_correct_containing_model (self ,task :Task ) -> Optional [Gio .ListStore ]:
791
+ """Return the ListStore that should contain the given task matching the filter."""
792
+ if task .parent is None or not self .task_filter .do_match (task .parent ):
793
+ return self .root_model
794
+ return self .tid_to_subtask_model .get (task .parent .id )
795
+
796
+
797
+ def _get_containing_model (self ,task :Task ) -> Optional [Gio .ListStore ]:
798
+ """Return the ListStore that currently contains the given task."""
799
+ return self .tid_to_containing_model .get (task .id )
800
+
801
+
802
+ def _model_expand (self , item ):
803
+ """Return a ListStore with the matching children of the given task."""
804
+ model = Gio .ListStore .new (Task )
805
+
806
+ if type (item ) == Gtk .TreeListRow :
807
+ item = item .get_item ()
808
+
809
+ for child in item .children :
810
+ if self .task_filter is None or self .task_filter .do_match (child ):
811
+ model .append (child )
812
+ self .tid_to_containing_model [child .id ] = model
813
+
814
+ self .tid_to_subtask_model [item .id ] = model
815
+ return Gtk .TreeListModel .new (model , False , False , self ._model_expand )
816
+
817
+
696
818
697
819
class TaskStore (BaseStore [Task ]):
698
820
"""A tree of tasks."""
@@ -706,24 +828,26 @@ def __init__(self) -> None:
706
828
super ().__init__ ()
707
829
708
830
self .model = Gio .ListStore .new (Task )
709
- self .tree_model = Gtk .TreeListModel .new (self .model , False , False , self .model_expand )
710
- self .tid_to_subtask_model : Dict [UUID ,Gio .ListStore ] = dict ()
831
+ self .managers : list [FilteredTaskTreeManager ] = []
711
832
712
833
713
- def model_expand (self , item ):
714
- model = Gio .ListStore .new (Task )
834
+ def get_filtered_tree_model (self ,task_filter : Optional [Gtk .Filter ]):
835
+ manager = FilteredTaskTreeManager (self ,task_filter )
836
+ self .managers .append (manager )
837
+ return manager .get_tree_model (), manager
715
838
716
- if type (item ) == Gtk .TreeListRow :
717
- item = item .get_item ()
718
-
719
- # open the first one
720
- if item .children :
721
- for child in item .children :
722
- model .append (child )
723
839
724
- self .tid_to_subtask_model [item .id ] = model
725
- return Gtk .TreeListModel .new (model , False , False , self .model_expand )
840
+ def _update_task_in_managers (self ,t :Optional [Task ]):
841
+ if t is None :
842
+ return
843
+ for m in self .managers :
844
+ m .update_position_of (t )
726
845
846
+ def _remove_task_from_managers (self ,t :Optional [Task ]):
847
+ if t is None :
848
+ return
849
+ for m in self .managers :
850
+ m .remove (t )
727
851
728
852
def __str__ (self ) -> str :
729
853
"""String representation."""
@@ -773,6 +897,8 @@ def new(self, title: str = '', parent: Optional[UUID] = None) -> Task: # type: i
773
897
for tag in self .lookup [parent ].tags :
774
898
task .add_tag (tag )
775
899
900
+ self ._update_task_in_managers (task )
901
+ self ._update_task_in_managers (task .parent )
776
902
return task
777
903
778
904
@@ -921,47 +1047,13 @@ def to_xml(self) -> _Element:
921
1047
return root
922
1048
923
1049
924
- def _remove_from_parent_model (self ,task_id : UUID ) -> None :
925
- """
926
- Remove the task indicated by task_id from the model of its parent's subtasks.
927
- This is required to trigger a GUI update.
928
- """
929
- item = self .lookup [task_id ]
930
- if item .parent is None :
931
- return
932
- if item .parent .id not in self .tid_to_subtask_model :
933
- return
934
- model = self .tid_to_subtask_model [item .parent .id ]
935
- pos = model .find (item )
936
- if pos [0 ]:
937
- model .remove (pos [1 ])
938
-
939
-
940
- def _append_to_parent_model (self ,task_id : UUID ) -> None :
941
- """
942
- Appends the task indicated by task_id to the model of its parent's subtasks.
943
- This is required to trigger a GUI update.
944
- """
945
- item = self .lookup [task_id ]
946
- if item .parent is None :
947
- return
948
- if item .parent .id not in self .tid_to_subtask_model :
949
- return
950
- model = self .tid_to_subtask_model [item .parent .id ]
951
- pos = model .find (item )
952
- if not pos [0 ]:
953
- model .append (item )
954
-
955
-
956
1050
def add (self , item : Any , parent_id : Optional [UUID ] = None ) -> None :
957
1051
"""Add a task to the taskstore."""
958
1052
959
1053
super ().add (item , parent_id )
960
1054
961
- if not parent_id :
962
- self .model .append (item )
963
- else :
964
- self ._append_to_parent_model (item .id )
1055
+ self ._update_task_in_managers (item )
1056
+ self ._update_task_in_managers (item .parent )
965
1057
966
1058
item .duplicate_cb = self .duplicate_for_recurrent
967
1059
self .notify ('task_count_all' )
@@ -975,14 +1067,13 @@ def remove(self, item_id: UUID) -> None:
975
1067
976
1068
# Remove from UI
977
1069
item = self .lookup [item_id ]
978
- if item .parent is not None :
979
- self ._remove_from_parent_model (item .id )
980
- else :
981
- pos = self .model .find (item )
982
- self .model .remove (pos [1 ])
1070
+ parent = item .parent
1071
+ self ._remove_task_from_managers (item )
983
1072
984
1073
super ().remove (item_id )
985
1074
1075
+ self ._update_task_in_managers (parent )
1076
+
986
1077
self .notify ('task_count_all' )
987
1078
self .notify ('task_count_no_tags' )
988
1079
@@ -991,36 +1082,26 @@ def parent(self, item_id: UUID, parent_id: UUID) -> None:
991
1082
992
1083
item = self .lookup [item_id ]
993
1084
994
- # Remove from UI
995
- if item .parent is not None :
996
- self ._remove_from_parent_model (item_id )
997
- else :
998
- pos = self .model .find (item )
999
- self .model .remove (pos [1 ])
1000
-
1001
1085
super ().parent (item_id , parent_id )
1002
1086
1003
- # Add back to UI
1004
- self ._append_to_parent_model ( item_id )
1087
+ self . _update_task_in_managers ( item )
1088
+ self ._update_task_in_managers ( item . parent )
1005
1089
1006
1090
1007
1091
def unparent (self , item_id : UUID ) -> None :
1008
1092
1009
1093
item = self .lookup [item_id ]
1010
- parent = item .parent
1011
- if parent is None :
1094
+ old_parent = item .parent
1095
+ if old_parent is None :
1012
1096
return
1013
1097
1014
- # Remove from UI
1015
- self ._remove_from_parent_model (item_id )
1016
-
1017
1098
super ().unparent (item_id )
1018
1099
1019
1100
# remove inline references to the former subtask
1020
- parent .content = re .sub (r'\{\!\s*' + str (item_id )+ r'\s*\!\}' ,'' ,parent .content )
1101
+ old_parent .content = re .sub (r'\{\!\s*' + str (item_id )+ r'\s*\!\}' ,'' ,old_parent .content )
1021
1102
1022
- # Add back to UI
1023
- self .model . append ( item )
1103
+ self . _update_task_in_managers ( item )
1104
+ self ._update_task_in_managers ( old_parent )
1024
1105
1025
1106
1026
1107
def filter (self , filter_type : Filter , arg : Union [Tag ,List [Tag ],None ] = None ) -> List [Task ]:
0 commit comments