1 // This file is part of a terminal todo application.
3 // Copyright (C) 2006 by Mike Sharov <msharov@users.sourceforge.net>
4 // This file is free software, distributed under the MIT License.
13 //----------------------------------------------------------------------
15 /// Default constructor.
16 CCurlistDocument::CCurlistDocument (void)
23 m_Stack
.push_back (c_RootItemId
);
24 m_Selection
.push_back (0);
27 //--{ Internal data accessors }-----------------------------------------
29 /// Set selection to index \p v.
30 void CCurlistDocument::SetSelection (uoff_t v
)
32 m_Selection
.back() = v
;
36 /// Sets selection to item \p id, if found.
37 void CCurlistDocument::SelectItem (itemid_t id
)
39 foreach (icitem_t
, i
, List())
41 SetSelection (distance (List().begin(), i
));
44 /// Returns the currently selected item.
45 CCurlistDocument::rcitem_t
CCurlistDocument::CurrentItem (void) const
47 static const CTodoItem s_NullItem
;
48 return (Selection() < m_List
.size() ? m_List
[Selection()] : s_NullItem
);
51 /// Returns the full path to the directory in containing the .todo file.
52 CCurlistDocument::rcfname_t
CCurlistDocument::GetTodoFileDir (void) const
57 // Build the full path name of the supposed directory.
58 if (Filename()[0] != '/') {
60 if (!getcwd (VectorBlock (cwd
)))
61 throw libc_exception ("getcwd");
65 dir
+= Document()->Filename();
66 // Remove the trailing /.todo
67 dir
.erase (dir
.iat (dir
.rfind ('/')), dir
.end());
71 /// Returns true if the current item is a directory under the current item.
72 bool CCurlistDocument::ItemNameIsADirectory (rcfname_t name
) const
74 string
dir (GetTodoFileDir());
75 // Add the current item stack (skip the root item which is empty)
76 for (itstack_t::const_iterator i
= m_Stack
.begin()+1; i
< m_Stack
.end(); ++i
) {
77 icitem_t ii
= Document()->FindItem (*i
);
78 if (ii
->Priority() != CTodoItem::priority_Directory
)
79 return (false); // The full path must be a directory
85 return (access (dir
, F_OK
) == 0);
88 /// Verifies that the item is or is not a directory as it says.
89 void CCurlistDocument::UpdateItemDirectoryFlag (iitem_t icuri
) const
91 if (ItemNameIsADirectory (icuri
->Text()))
92 icuri
->SetPriority (CTodoItem::priority_Directory
);
93 else if (icuri
->Priority() == CTodoItem::priority_Directory
)
94 icuri
->SetPriority (CTodoItem::priority_Medium
);
97 /// Uses the current working directory to find its sublist in the tree.
98 void CCurlistDocument::DescendByCwd (void)
100 if ((ItemId() != c_RootItemId
) | Flag(f_Descended
)) // Only autodescend from root
102 // Get current working directory
103 char cwdbuf
[PATH_MAX
];
104 if (!getcwd (VectorBlock (cwdbuf
)))
105 throw libc_exception ("getcwd");
107 cwd
.link (cwdbuf
, strlen (cwdbuf
));
108 // Determine the path to descend down
109 uoff_t pathBase
= cwd
.size();
110 const string
& fn (Document()->Filename());
111 SetFlag (f_Descended
, !fn
.empty());
112 string dir
; // Parse fn for ../../../.todo
113 for (uoff_t i
= 0; i
< fn
.size(); i
+= dir
.size() + 1) {
114 dir
.relink (fn
.iat(i
), min (fn
.find ('/', i
), fn
.size()) - i
);
116 pathBase
= cwd
.rfind ('/', pathBase
-1);
117 else if (dir
!= ".todo") // If the user specified some .todo explicitly,
118 return; // ... then don't descend.
120 // Now for each dir in the path see if there is a matching item
121 for (uoff_t i
= pathBase
+ 1; i
< cwd
.size(); i
+= dir
.size() + 1) {
122 dir
.relink (cwd
.iat(i
), min (cwd
.find ('/', i
), cwd
.size()) - i
);
123 foreach (icitem_t
, ii
, List()) {
124 if (ii
->Complete()) // Directory items are never complete
125 return; // ... so stop looking at that point.
126 if (ii
->Text() == dir
) { // Found the proper item, so descend
127 SetSelection (distance (List().begin(), ii
));
135 //--{ List manipulation }-----------------------------------------------
137 /// Called the first time the document is connected.
138 void CCurlistDocument::OnInitialUpdate (void)
141 m_Stack
.push_back (c_RootItemId
);
143 m_Selection
.push_back (0);
144 CDocument::OnInitialUpdate();
147 /// Copies visible data from the document.
148 void CCurlistDocument::OnUpdate (void)
150 CDocument::OnUpdate();
152 SetFlag (f_Changed
, Document()->Flag (f_Changed
));
153 SetFlag (f_ReadOnly
, Document()->Flag (f_ReadOnly
));
157 /// Reloads the currently visible list from the overall data.
158 void CCurlistDocument::ReloadList (void)
161 // Find the list of dependencies for item lid
162 Document()->ItemDeps (ItemId(), m_Deps
);
163 m_List
.reserve (Deps().size());
164 const time_t c_OneDay
= 24 * 60 * 60;
165 const time_t tooOld (time(NULL
) - c_OneDay
);
166 // Insert all the items dependent on m_ItemId into m_List.
167 foreach (icdep_t
, d
, Deps()) {
168 iitem_t ii
= Document()->FindItem (d
->m_DepId
);
169 if (!Flag (f_CompleteVisible
) && ii
->Complete() && ii
->Done() < tooOld
)
171 m_List
.push_back (*ii
);
173 SetSelection (min (Selection(), uoff_t (m_List
.size() - 1)));
176 //--{ Item editing }---------------------------------------------------
178 /// Sets the current item to \p e
179 void CCurlistDocument::UpdateCurrentItem (rcitem_t ni
)
181 assert (Selection() < List().size());
182 iitem_t
i (m_List
.iat (Selection()));
184 UpdateItemDirectoryFlag (i
);
186 Document()->UpdateItem (*i
);
189 /// Creates a new item and selects it.
190 void CCurlistDocument::AppendItem (void)
192 itemid_t id
= Document()->CreateItem();
193 Document()->LinkItem (id
, ItemId()); // Calls update/reload
197 /// Makes \p id a dependency of the current item.
198 void CCurlistDocument::PasteLinkToItem (itemid_t id
)
200 // Create a dependency link to it from the current item.
201 foreach (icdep_t
, d
, Deps())
202 if (d
->m_DepId
== id
)
203 return; // Already in this list
204 Document()->LinkItem (id
, ItemId()); // Calls UpdateAllViews->ReloadList
205 // List is reloaded at this point, so select the new item.
209 /// Removes the current item.
210 void CCurlistDocument::RemoveCurrentItem (void)
212 Document()->UnlinkItem (m_Deps
.iat (Selection()));
215 /// Marks the currently selected item as 100% complete.
216 void CCurlistDocument::MarkItemComplete (void)
218 CTodoItem
& i (m_List
[Selection()]);
219 i
.MarkComplete (100 * !i
.Complete());
220 UpdateCurrentItem (i
);
223 /// Sets the priority of the currently selected item to \p p.
224 void CCurlistDocument::SetCurrentItemPriority (CTodoItem::EPriority p
)
226 m_List
[Selection()].SetPriority (p
);
227 UpdateCurrentItem (m_List
[Selection()]);
230 /// Sets the currently active item as the current list.
231 void CCurlistDocument::EnterCurrentItem (void)
233 rcitem_t
i (CurrentItem());
236 m_Stack
.push_back (i
.Id());
237 m_Selection
.push_back (0);
241 /// Goes up to the parent list of the current list.
242 void CCurlistDocument::LeaveCurrentItem (void)
244 if (m_Stack
.size() <= 1)
247 m_Selection
.pop_back();
251 //--{ Command switching }-----------------------------------------------
253 /// Executes command \p c.
254 void CCurlistDocument::OnCommand (cmd_t c
)
256 CDocument::OnCommand (c
);
258 case cmd_Item_Complete
: MarkItemComplete(); break;
259 case cmd_List_Leave
: LeaveCurrentItem(); break;
260 case cmd_List_Enter
: EnterCurrentItem(); break;
261 case cmd_List_OldItems
: ToggleCompleteVisible(); break;
262 case cmd_Item_Delete
: RemoveCurrentItem(); break;
263 case cmd_File_Save
: Document()->Save(); break;
264 case cmd_Item_Priority_Highest
:
265 case cmd_Item_Priority_High
:
266 case cmd_Item_Priority_Medium
:
267 case cmd_Item_Priority_Low
:
268 case cmd_Item_Priority_Lowest
:
269 SetCurrentItemPriority (CTodoItem::EPriority (c
- cmd_Item_Priority_Highest
+ 1));
272 assert (Document()->Flag (f_Changed
) == Flag (f_Changed
));
273 if (Flag (f_Changed
)) {
274 int rv
= MessageBox ("There are unsaved changes. Save now?", MB_YesNoCancel
);
275 if (rv
== mbrv_Cancel
)
277 else if (rv
!= mbrv_No
)
280 CRootWindow::Instance().Close();
285 /// Updates visible flags of command \p rc.
286 void CCurlistDocument::OnUpdateCommandUI (rcmd_t rc
) const
288 CDocument::OnUpdateCommandUI (rc
);
289 const bool bHaveSelection (Selection() < List().size());
290 const bool bReadOnly (Flag (f_ReadOnly
));
293 case cmd_File_Save
: bActive
= !bReadOnly
&& Flag(f_Changed
);break;
294 case cmd_List_Leave
: bActive
= NestingDepth(); break;
295 case cmd_List_OldItems
:
296 rc
.SetFlag (SCmd::cf_Checked
, Flag (f_CompleteVisible
)); break;
297 case cmd_List_Enter
: bActive
= bHaveSelection
; break;
298 case cmd_Item_Delete
: bActive
= bHaveSelection
&& !bReadOnly
; break;
299 case cmd_Item_Complete
: bActive
= bHaveSelection
&& !bReadOnly
; break;
300 case cmd_Item_Priority
:
301 bActive
= bHaveSelection
&& CurrentItem().Priority() != CTodoItem::priority_Directory
;
303 case cmd_Item_Priority_Highest
:
304 case cmd_Item_Priority_High
:
305 case cmd_Item_Priority_Medium
:
306 case cmd_Item_Priority_Low
:
307 case cmd_Item_Priority_Lowest
:
308 bActive
= bHaveSelection
&& !bReadOnly
;
309 rc
.SetFlag (SCmd::cf_Checked
, rc
.cmd
== CurrentItem().Priority() + cmd_Item_Priority_Highest
- 1);
312 bActive
= !(rc
.flags
& SCmd::cf_Grayed
);
315 rc
.SetFlag (SCmd::cf_Grayed
, !bActive
);