Switched to git
[ttodo.git] / cldoc.cc
blobbd8eb486c5e77e2fa51c6055851e0999b4747990
1 // This file is part of a terminal todo application.
2 //
3 // Copyright (C) 2006 by Mike Sharov <msharov@users.sourceforge.net>
4 // This file is free software, distributed under the MIT License.
5 //
6 // cldoc.cc
7 //
9 #include "cldoc.h"
10 #include <unistd.h>
11 #include <time.h>
13 //----------------------------------------------------------------------
15 /// Default constructor.
16 CCurlistDocument::CCurlistDocument (void)
17 : CDocument (),
18 m_List (),
19 m_Deps (),
20 m_Stack (),
21 m_Selection ()
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;
33 UpdateAllViews();
36 /// Sets selection to item \p id, if found.
37 void CCurlistDocument::SelectItem (itemid_t id)
39 foreach (icitem_t, i, List())
40 if (i->Id() == id)
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
54 static string dir;
55 if (!dir.empty())
56 return (dir);
57 // Build the full path name of the supposed directory.
58 if (Filename()[0] != '/') {
59 char cwd [PATH_MAX];
60 if (!getcwd (VectorBlock (cwd)))
61 throw libc_exception ("getcwd");
62 dir = cwd;
64 dir += '/';
65 dir += Document()->Filename();
66 // Remove the trailing /.todo
67 dir.erase (dir.iat (dir.rfind ('/')), dir.end());
68 return (dir);
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
80 dir += '/';
81 dir += ii->Text();
83 dir += '/';
84 dir += name;
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
101 return;
102 // Get current working directory
103 char cwdbuf [PATH_MAX];
104 if (!getcwd (VectorBlock (cwdbuf)))
105 throw libc_exception ("getcwd");
106 string cwd;
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);
115 if (dir == "..")
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));
128 EnterCurrentItem();
129 break;
135 //--{ List manipulation }-----------------------------------------------
137 /// Called the first time the document is connected.
138 void CCurlistDocument::OnInitialUpdate (void)
140 m_Stack.clear();
141 m_Stack.push_back (c_RootItemId);
142 m_Selection.clear();
143 m_Selection.push_back (0);
144 CDocument::OnInitialUpdate();
147 /// Copies visible data from the document.
148 void CCurlistDocument::OnUpdate (void)
150 CDocument::OnUpdate();
151 ReloadList();
152 SetFlag (f_Changed, Document()->Flag (f_Changed));
153 SetFlag (f_ReadOnly, Document()->Flag (f_ReadOnly));
154 DescendByCwd();
157 /// Reloads the currently visible list from the overall data.
158 void CCurlistDocument::ReloadList (void)
160 m_List.clear();
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)
170 break;
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()));
183 *i = ni;
184 UpdateItemDirectoryFlag (i);
185 SetFlag (f_Changed);
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
194 SelectItem (id);
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.
206 SelectItem (id);
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());
234 if (!i.Id())
235 return;
236 m_Stack.push_back (i.Id());
237 m_Selection.push_back (0);
238 ReloadList();
241 /// Goes up to the parent list of the current list.
242 void CCurlistDocument::LeaveCurrentItem (void)
244 if (m_Stack.size() <= 1)
245 return;
246 m_Stack.pop_back();
247 m_Selection.pop_back();
248 ReloadList();
251 //--{ Command switching }-----------------------------------------------
253 /// Executes command \p c.
254 void CCurlistDocument::OnCommand (cmd_t c)
256 CDocument::OnCommand (c);
257 switch (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));
270 break;
271 case cmd_File_Quit:
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)
276 return;
277 else if (rv != mbrv_No)
278 Document()->Save();
280 CRootWindow::Instance().Close();
281 break;
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));
291 bool bActive = true;
292 switch (rc.cmd) {
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;
302 break;
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);
310 break;
311 default:
312 bActive = !(rc.flags & SCmd::cf_Grayed);
313 break;
315 rc.SetFlag (SCmd::cf_Grayed, !bActive);