1 // Copyright (c) 2006 by Mike Sharov <msharov@users.sourceforge.net>
10 //----------------------------------------------------------------------
12 /// Default constructor.
13 CTodoDocument::CTodoDocument (void)
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 //----------------------------------------------------------------------
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
)); }
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
);
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());
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
);
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.
107 if (access (filename
, W_OK
)) // Check if it is read-only.
108 SetFlag (f_ReadOnly
);
110 buf
.read_file (filename
);
113 SetFlag (f_Changed
, false);
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");
124 memblock
buf (stream_size_of (*this));
127 buf
.write_file (Filename());
128 SetFlag (f_Changed
, false);
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
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
)
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.
188 iParent
->SetHasSublist (true);
189 UpdateCompleteStatus (id
);
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.
208 UpdateItemProgress (r
.first
, r
.second
);
209 // Update visible list.
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.");
220 UpdateCompleteStatus (v
.Id());
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
);
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
)
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
) {
256 UpdateItemProgress (rangeStart
, i
);
260 hasDep
= hasDep
|| i
->m_DepId
== dep
;
263 UpdateItemProgress (rangeStart
, iEnd
);