Revised file format to conform to standard IFF layout
[ttodo.git] / tddoc.cc
blobb5d3895be25e94d15b292f8484f2537cb54e8c5a
1 // Copyright (c) 2006 by Mike Sharov <msharov@users.sourceforge.net>
2 //
3 // tddoc.cc
4 //
6 #include "tddoc.h"
7 #include <iff.h>
8 #include <unistd.h>
10 //----------------------------------------------------------------------
12 /// Default constructor.
13 CTodoDocument::CTodoDocument (void)
14 : CDocument (),
15 m_Todos (),
16 m_Deps ()
18 m_Todos.insert (CTodoItem());
21 //--{ Internal data accessors }-----------------------------------------
23 /// Looks up item \p id in m_Todos, returns NULL if not found.
24 CTodoDocument::icitem_t CTodoDocument::FindItem (itemid_t id) const
26 icitem_t i = m_Todos.find (id);
27 return (i == m_Todos.end() ? NULL : i);
30 /// Links \p m to the dependency list for \p id.
31 void CTodoDocument::ItemDeps (itemid_t id, tddepmap_t& m) const
33 pair<icdep_t, icdep_t> dr = equal_range (m_Deps, CTodoDep(id));
34 m.link (dr.first, dr.second);
37 /// Returns the next available item id.
38 inline CTodoDocument::itemid_t CTodoDocument::GetNextItemId (void) const
40 return (m_Todos.back().Id() + 1);
43 //--{ Serialization }---------------------------------------------------
45 static const iff::fmt_t fmt_TodoList = IFF_SFMT("TODO");
46 static const iff::fmt_t fmt_Options = IFF_SFMT("OPTS");
47 static const iff::fmt_t fmt_Item = IFF_SFMT("TITM");
48 static const iff::fmt_t fmt_Dep = IFF_SFMT("TDEP");
50 static const uint32_t c_CurrentVersion = 1;
52 //----------------------------------------------------------------------
54 class CTodoHeader {
55 public:
56 inline CTodoHeader (void) : m_Version (c_CurrentVersion), m_Flags (0), m_Reserved (0) { }
57 inline void read (istream& is) { is >> m_Version >> m_Flags >> m_Reserved; }
58 inline void write (ostream& os) const { os << m_Version << m_Flags << m_Reserved; }
59 inline size_t stream_size (void) const { return (stream_size_of (m_Version) + stream_size_of (m_Flags) + stream_size_of(m_Reserved)); }
60 public:
61 uint32_t m_Version; ///< File format's version number.
62 bitset<32> m_Flags; ///< Various flags. None for now.
63 uint32_t m_Reserved; ///< Future functionality.
66 STD_STREAMABLE (CTodoHeader)
68 /// Reads the object from stream \p is.
69 void CTodoDocument::read (istream& is)
71 iff::CGroupHeader fileHeader;
72 iff::ReadGroupHeader ("TODO file", is, fileHeader, fmt_TodoList, iff::cfmt_FORM);
73 CTodoHeader h;
74 iff::ReadChunk (is, h, fmt_Options);
75 iff::ReadVector (is, m_Todos, fmt_Item);
76 iff::ReadVector (is, m_Deps, fmt_Dep);
77 iff::VerifyChunkSize ("TODO file", 0, is.pos(), fileHeader.SizeWithHeader());
78 VerifyData();
81 /// Writes the object to stream \p os.
82 void CTodoDocument::write (ostream& os) const
84 const size_t dataSize = stream_size() - stream_size_of(iff::CGroupHeader());
85 iff::CGroupHeader fileHeader (dataSize, fmt_TodoList, iff::cfmt_FORM);
86 os << fileHeader;
87 iff::WriteChunk (os, CTodoHeader(), fmt_Options);
88 iff::WriteVector (os, m_Todos, fmt_Item);
89 iff::WriteVector (os, m_Deps, fmt_Dep);
92 /// Returns the size of the written object.
93 size_t CTodoDocument::stream_size (void) const
95 return (stream_size_of(iff::CGroupHeader()) +
96 iff::chunk_size_of (CTodoHeader()) +
97 iff::vector_size_of (m_Todos) +
98 iff::vector_size_of (m_Deps));
101 /// Opens \p filename and reads todo entries from it.
102 void CTodoDocument::Open (const string& filename)
104 CDocument::Open (filename);
105 if (access (filename, F_OK)) // Check if it's a new document.
106 return;
107 if (access (filename, W_OK)) // Check if it is read-only.
108 SetFlag (f_ReadOnly);
109 memblock buf;
110 buf.read_file (filename);
111 istream is (buf);
112 read (is);
113 SetFlag (f_Changed, false);
114 UpdateAllViews();
117 /// Saves the data to the currently open file.
118 void CTodoDocument::Save (void)
120 if (Flag (f_ReadOnly) && access (Filename(), W_OK)) {
121 MessageBox ("Can't save: this file is marked as read-only");
122 return;
124 memblock buf (stream_size_of (*this));
125 ostream os (buf);
126 write (os);
127 buf.write_file (Filename());
128 SetFlag (f_Changed, false);
129 UpdateAllViews();
132 /// Verifies and corrects any defects in the data.
133 void CTodoDocument::VerifyData (void)
135 foreach (tddepmap_t::iterator, i, m_Deps) {
136 if (!FindItem (i->m_ItemId) || !FindItem (i->m_DepId)) {
137 assert (!"Found a dependency pointing to a nonexistent item!");
138 --(i = m_Deps.erase (i));
143 /// Returns the true if \p i1 ought to appear before \p i2 in the visible list.
144 bool CTodoDocument::VisibleOrderLess (const CTodoDep& d1, const CTodoDep& d2) const
146 const icitem_t i1 (FindItem (d1.m_DepId)), i2 (FindItem (d2.m_DepId));
147 // Sort to put completed items at the end, then sort by priority, and then by creation date.
148 return (i1->Complete() < i2->Complete() ||
149 (!i1->Complete() && !i2->Complete() && (i1->Priority() < i2->Priority() ||
150 (i1->Priority() == i2->Priority() && *i1 < *i2))) ||
151 (i1->Complete() && i2->Complete() && (i1->Done() > i2->Done() ||
152 (i1->Done() == i2->Done() && *i1 < *i2))));
155 /// Reorders the items in the list by canonical sort order.
156 void CTodoDocument::ResortItemDeps (idep_t first, idep_t last) const
158 for (idep_t j, i = first; ++i < last;) {
159 for (j = i; j-- > first && !VisibleOrderLess (*j, *i););
160 rotate (++j, i, i + 1);
164 //--{ Item progress recursive updating }--------------------------------
166 /// Creates a new item and returns its id.
167 CTodoDocument::itemid_t CTodoDocument::CreateItem (void)
169 CTodoItem e (GetNextItemId());
170 assert (!FindItem (e.Id()));
171 // To the complete list
172 m_Todos.insert (e);
173 SetFlag (f_Changed);
174 return (e.Id());
177 /// Makes item \p id a dependency of \p parent item.
178 void CTodoDocument::LinkItem (itemid_t id, itemid_t parent)
180 iitem_t iItem = FindItem (id), iParent = FindItem (parent);
181 if (!iParent | !iItem)
182 return;
183 // m_Deps is sorted by parent, but not by < (because < requires accessing m_Todos)
184 idep_t ip = upper_bound (m_Deps, CTodoDep (parent));
185 m_Deps.insert (ip, CTodoDep (parent, id));
186 // Update the new item's status.
187 iItem->AddRef();
188 iParent->SetHasSublist (true);
189 UpdateCompleteStatus (id);
190 // Tell the world
191 SetFlag (f_Changed);
192 UpdateAllViews();
195 /// Removes one reference the given item.
196 void CTodoDocument::UnlinkItem (icdep_t id)
198 assert (id >= m_Deps.begin() && id < m_Deps.end() && "UnlinkItem takes an iterator from the vector set by ItemDeps");
199 iitem_t ii = FindItem (id->m_DepId); // Cache master list iterator while we still have the item to look for.
200 // Remove from dependency list.
201 m_Deps.erase (const_cast<idep_t>(id));
202 pair<idep_t, idep_t> r = equal_range (m_Deps, CTodoDep(ii->Id()));
203 if (r.first == r.second) // Can't be cleanly done in UpdateItemProgress.
204 FindItem (id->m_ItemId)->SetHasSublist (false);
205 // Update the item in the master list.
206 if (!ii->DelRef()) // If no more refs, then delete.
207 m_Todos.erase (ii);
208 UpdateItemProgress (r.first, r.second);
209 // Update visible list.
210 SetFlag (f_Changed);
211 UpdateAllViews();
214 /// Updates item \p v in the master list.
215 void CTodoDocument::UpdateItem (rcitem_t v)
217 iitem_t i = FindItem (v.Id());
218 assert (i && "Update item must only be called with already existent items. Call AppendItem to create new ones.");
219 *i = v;
220 UpdateCompleteStatus (v.Id());
221 // Tell the world
222 SetFlag (f_Changed);
223 UpdateAllViews();
226 /// Updates the progress of the item given by dependency range [first, last)
227 void CTodoDocument::UpdateItemProgress (idep_t first, idep_t last)
229 const uint32_t nItems = distance (first, last);
230 if (!nItems)
231 return;
232 ResortItemDeps (first, last);
233 const itemid_t rangeId (first->m_ItemId);
234 uint32_t progress = 0;
235 for (; first < last; ++first)
236 progress += FindItem(first->m_DepId)->Progress();
237 // +1 treats the parent item as part of the list.
238 progress = min (progress / (nItems + 1), 100U);
239 iitem_t ii = FindItem (rangeId);
240 if (!ii || ii->Progress() == progress)
241 return;
242 ii->MarkComplete (progress);
243 UpdateCompleteStatus (rangeId); // Recurse to propagate the change up.
246 /// Updates progress values of item dependent on \p dep
247 void CTodoDocument::UpdateCompleteStatus (itemid_t dep)
249 // Each item has a range of dependencies; ranges are sequential in m_Deps.
250 idep_t rangeStart = m_Deps.begin(); // The start of the current range.
251 const idep_t iEnd = m_Deps.end();
252 bool hasDep = false; // Does current range have dep?
253 for (idep_t i = rangeStart; i < iEnd; ++i) {
254 if (i->m_ItemId != rangeStart->m_ItemId) {
255 if (hasDep)
256 UpdateItemProgress (rangeStart, i);
257 rangeStart = i;
258 hasDep = false;
260 hasDep = hasDep || i->m_DepId == dep;
262 if (hasDep)
263 UpdateItemProgress (rangeStart, iEnd);