]> git.lyx.org Git - lyx.git/blob - src/Changes.cpp
cosmetics
[lyx.git] / src / Changes.cpp
1 /**
2  * \file Changes.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Michael Gerz
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  * Record changes in a paragraph.
12  */
13
14 #include <config.h>
15
16 #include "Changes.h"
17 #include "debug.h"
18 #include "Author.h"
19 #include "BufferParams.h"
20 #include "LaTeXFeatures.h"
21
22 #include <boost/assert.hpp>
23
24 #include <ostream>
25
26 namespace lyx {
27
28 /*
29  * Class Change has a changetime field that specifies the exact time at which
30  * a specific change was made. The change time is used as a guidance for the
31  * user while editing his document. Presently, it is not considered for LaTeX
32  * export.
33  * When merging two adjacent changes, the changetime is not considered,
34  * only the equality of the change type and author is checked (in method
35  * isSimilarTo(...)). If two changes are in fact merged (in method merge()),
36  * the later change time is preserved.
37  */
38
39 bool Change::isSimilarTo(Change const & change)
40 {
41         if (type != change.type)
42                 return false;
43
44         if (type == Change::UNCHANGED)
45                 return true;
46
47         return author == change.author;
48 }
49
50
51 bool operator==(Change const & l, Change const & r)
52 {
53         if (l.type != r.type)
54                 return false;
55
56         // two changes of type UNCHANGED are always equal
57         if (l.type == Change::UNCHANGED)
58                 return true;
59
60         return l.author == r.author && l.changetime == r.changetime;
61 }
62
63
64 bool operator!=(Change const & l, Change const & r)
65 {
66         return !(l == r);
67 }
68
69
70 bool operator==(Changes::Range const & r1, Changes::Range const & r2)
71 {
72         return r1.start == r2.start && r1.end == r2.end;
73 }
74
75
76 bool operator!=(Changes::Range const & r1, Changes::Range const & r2)
77 {
78         return !(r1 == r2);
79 }
80
81
82 bool Changes::Range::intersects(Range const & r) const
83 {
84         return r.start < end && r.end > start; // end itself is not in the range!
85 }
86
87
88 void Changes::set(Change const & change, pos_type const pos)
89 {
90         set(change, pos, pos + 1);
91 }
92
93
94 void Changes::set(Change const & change, pos_type const start, pos_type const end)
95 {
96         if (change.type != Change::UNCHANGED) {
97                 LYXERR(Debug::CHANGES, "setting change (type: " << change.type
98                         << ", author: " << change.author << ", time: " << change.changetime
99                         << ") in range (" << start << ", " << end << ")");
100         }
101
102         Range const newRange(start, end);
103
104         ChangeTable::iterator it = table_.begin();
105
106         for (; it != table_.end(); ) {
107                 // current change starts like or follows new change
108                 if (it->range.start >= start) {
109                         break;
110                 }
111
112                 // new change intersects with existing change
113                 if (it->range.end > start) {
114                         pos_type oldEnd = it->range.end;
115                         it->range.end = start;
116
117                         LYXERR(Debug::CHANGES, "  cutting tail of type " << it->change.type
118                                 << " resulting in range (" << it->range.start << ", "
119                                 << it->range.end << ")");
120
121                         ++it;
122                         if (oldEnd >= end) {
123                                 LYXERR(Debug::CHANGES, "  inserting tail in range ("
124                                         << end << ", " << oldEnd << ")");
125                                 it = table_.insert(it, ChangeRange((it-1)->change, Range(end, oldEnd)));
126                         }
127                         continue;
128                 }
129
130                 ++it;
131         }
132
133         if (change.type != Change::UNCHANGED) {
134                 LYXERR(Debug::CHANGES, "  inserting change");
135                 it = table_.insert(it, ChangeRange(change, Range(start, end)));
136                 ++it;
137         }
138
139         for (; it != table_.end(); ) {
140                 // new change 'contains' existing change
141                 if (newRange.contains(it->range)) {
142                         LYXERR(Debug::CHANGES, "  removing subrange ("
143                                 << it->range.start << ", " << it->range.end << ")");
144                         it = table_.erase(it);
145                         continue;
146                 }
147
148                 // new change precedes existing change
149                 if (it->range.start >= end)
150                         break;
151
152                 // new change intersects with existing change
153                 it->range.start = end;
154                 LYXERR(Debug::CHANGES, "  cutting head of type "
155                         << it->change.type << " resulting in range ("
156                         << end << ", " << it->range.end << ")");
157                 break; // no need for another iteration
158         }
159
160         merge();
161 }
162
163
164 void Changes::erase(pos_type const pos)
165 {
166         LYXERR(Debug::CHANGES, "Erasing change at position " << pos);
167
168         ChangeTable::iterator it = table_.begin();
169         ChangeTable::iterator end = table_.end();
170
171         for (; it != end; ++it) {
172                 // range (pos,pos+x) becomes (pos,pos+x-1)
173                 if (it->range.start > pos)
174                         --(it->range.start);
175                 // range (pos-x,pos) stays (pos-x,pos)
176                 if (it->range.end > pos)
177                         --(it->range.end);
178         }
179
180         merge();
181 }
182
183
184 void Changes::insert(Change const & change, lyx::pos_type pos)
185 {
186         if (change.type != Change::UNCHANGED) {
187                 LYXERR(Debug::CHANGES, "Inserting change of type " << change.type
188                         << " at position " << pos);
189         }
190
191         ChangeTable::iterator it = table_.begin();
192         ChangeTable::iterator end = table_.end();
193
194         for (; it != end; ++it) {
195                 // range (pos,pos+x) becomes (pos+1,pos+x+1)
196                 if (it->range.start >= pos)
197                         ++(it->range.start);
198
199                 // range (pos-x,pos) stays as it is
200                 if (it->range.end > pos)
201                         ++(it->range.end);
202         }
203
204         set(change, pos, pos + 1); // set will call merge
205 }
206
207
208 Change const & Changes::lookup(pos_type const pos) const
209 {
210         static Change const noChange = Change(Change::UNCHANGED);
211
212         ChangeTable::const_iterator it = table_.begin();
213         ChangeTable::const_iterator const end = table_.end();
214
215         for (; it != end; ++it) {
216                 if (it->range.contains(pos))
217                         return it->change;
218         }
219
220         return noChange;
221 }
222
223
224 bool Changes::isChanged(pos_type const start, pos_type const end) const
225 {
226         ChangeTable::const_iterator it = table_.begin();
227         ChangeTable::const_iterator const itend = table_.end();
228
229         for (; it != itend; ++it) {
230                 if (it->range.intersects(Range(start, end))) {
231                         LYXERR(Debug::CHANGES, "found intersection of range ("
232                                 << start << ", " << end << ") with ("
233                                 << it->range.start << ", " << it->range.end
234                                 << ") of type " << it->change.type);
235                         return true;
236                 }
237         }
238         return false;
239 }
240
241
242 void Changes::merge()
243 {
244         ChangeTable::iterator it = table_.begin();
245
246         while (it != table_.end()) {
247                 LYXERR(Debug::CHANGES, "found change of type " << it->change.type
248                         << " and range (" << it->range.start << ", " << it->range.end
249                         << ")");
250
251                 if (it->range.start == it->range.end) {
252                         LYXERR(Debug::CHANGES, "removing empty range for pos "
253                                 << it->range.start);
254
255                         table_.erase(it);
256                         // start again
257                         it = table_.begin();
258                         continue;
259                 }
260
261                 if (it + 1 == table_.end())
262                         break;
263
264                 if (it->change.isSimilarTo((it + 1)->change)
265                     && it->range.end == (it + 1)->range.start) {
266                         LYXERR(Debug::CHANGES, "merging ranges (" << it->range.start << ", "
267                                 << it->range.end << ") and (" << (it + 1)->range.start << ", "
268                                 << (it + 1)->range.end << ")");
269
270                         (it + 1)->range.start = it->range.start;
271                         (it + 1)->change.changetime = std::max(it->change.changetime,
272                                                           (it + 1)->change.changetime);
273                         table_.erase(it);
274                         // start again
275                         it = table_.begin();
276                         continue;
277                 }
278
279                 ++it;
280         }
281 }
282
283
284 int Changes::latexMarkChange(odocstream & os, BufferParams const & bparams,
285                              Change const & oldChange, Change const & change)
286 {
287         if (!bparams.outputChanges || oldChange == change)
288                 return 0;
289
290         int column = 0;
291
292         if (oldChange.type != Change::UNCHANGED) {
293                 os << '}'; // close \lyxadded or \lyxdeleted
294                 column++;
295         }
296
297         docstring chgTime;
298         chgTime += ctime(&change.changetime);
299         chgTime.erase(chgTime.end() - 1); // remove trailing '\n'
300
301         if (change.type == Change::DELETED) {
302                 docstring str = "\\lyxdeleted{" +
303                         bparams.authors().get(change.author).name() + "}{" +
304                         chgTime + "}{";
305                 os << str;
306                 column += str.size();
307         } else if (change.type == Change::INSERTED) {
308                 docstring str = "\\lyxadded{" +
309                         bparams.authors().get(change.author).name() + "}{" +
310                         chgTime + "}{";
311                 os << str;
312                 column += str.size();
313         }
314
315         return column;
316 }
317
318
319 void Changes::lyxMarkChange(std::ostream & os, int & column,
320                             Change const & old, Change const & change)
321 {
322         if (old == change)
323                 return;
324
325         column = 0;
326
327         switch (change.type) {
328                 case Change::UNCHANGED:
329                         os << "\n\\change_unchanged\n";
330                         break;
331
332                 case Change::DELETED: {
333                         os << "\n\\change_deleted " << change.author
334                                 << " " << change.changetime << "\n";
335                         break;
336                 }
337
338                 case Change::INSERTED: {
339                         os << "\n\\change_inserted " << change.author
340                                 << " " << change.changetime << "\n";
341                         break;
342                 }
343         }
344 }
345
346
347 void Changes::checkAuthors(AuthorList const & authorList)
348 {
349         ChangeTable::const_iterator it = table_.begin();
350         ChangeTable::const_iterator endit = table_.end();
351         for ( ; it != endit ; ++it) 
352                 if (it->change.type != Change::UNCHANGED)
353                         authorList.get(it->change.author).setUsed(true);
354 }
355
356 } // namespace lyx