]> git.lyx.org Git - lyx.git/blob - src/Changes.cpp
prepare Qt 5.6 builds
[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 "Author.h"
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "Encoding.h"
21 #include "LaTeXFeatures.h"
22 #include "OutputParams.h"
23 #include "Paragraph.h"
24 #include "TocBackend.h"
25
26 #include "support/debug.h"
27 #include "support/gettext.h"
28 #include "support/lassert.h"
29 #include "support/lstrings.h"
30 #include "support/mutex.h"
31
32 #include "frontends/alert.h"
33
34 #include <ostream>
35
36 using namespace std;
37
38 namespace lyx {
39
40 /*
41  * Class Change has a changetime field that specifies the exact time at which
42  * a specific change was made. The change time is used as a guidance for the
43  * user while editing his document. Presently, it is not considered for LaTeX
44  * export.
45  * When merging two adjacent changes, the changetime is not considered,
46  * only the equality of the change type and author is checked (in method
47  * isSimilarTo(...)). If two changes are in fact merged (in method merge()),
48  * the later change time is preserved.
49  */
50
51 bool Change::isSimilarTo(Change const & change) const
52 {
53         if (type != change.type)
54                 return false;
55
56         if (type == Change::UNCHANGED)
57                 return true;
58
59         return author == change.author;
60 }
61
62
63 Color Change::color() const
64 {
65         Color color = Color_none;
66         switch (author % 5) {
67                 case 0:
68                         color = Color_changedtextauthor1;
69                         break;
70                 case 1:
71                         color = Color_changedtextauthor2;
72                         break;
73                 case 2:
74                         color = Color_changedtextauthor3;
75                         break;
76                 case 3:
77                         color = Color_changedtextauthor4;
78                         break;
79                 case 4:
80                         color = Color_changedtextauthor5;
81                         break;
82         }
83
84         if (deleted())
85                 color.mergeColor = Color_deletedtextmodifier;
86
87         return color;
88 }
89
90
91 bool operator==(Change const & l, Change const & r)
92 {
93         if (l.type != r.type)
94                 return false;
95
96         // two changes of type UNCHANGED are always equal
97         if (l.type == Change::UNCHANGED)
98                 return true;
99
100         return l.author == r.author && l.changetime == r.changetime;
101 }
102
103
104 bool operator!=(Change const & l, Change const & r)
105 {
106         return !(l == r);
107 }
108
109
110 bool operator==(Changes::Range const & r1, Changes::Range const & r2)
111 {
112         return r1.start == r2.start && r1.end == r2.end;
113 }
114
115
116 bool operator!=(Changes::Range const & r1, Changes::Range const & r2)
117 {
118         return !(r1 == r2);
119 }
120
121
122 bool Changes::Range::intersects(Range const & r) const
123 {
124         return r.start < end && r.end > start; // end itself is not in the range!
125 }
126
127
128 void Changes::set(Change const & change, pos_type const pos)
129 {
130         set(change, pos, pos + 1);
131 }
132
133
134 void Changes::set(Change const & change, pos_type const start, pos_type const end)
135 {
136         if (change.type != Change::UNCHANGED) {
137                 LYXERR(Debug::CHANGES, "setting change (type: " << change.type
138                         << ", author: " << change.author 
139                         << ", time: " << long(change.changetime)
140                         << ") in range (" << start << ", " << end << ")");
141         }
142
143         Range const newRange(start, end);
144
145         ChangeTable::iterator it = table_.begin();
146
147         for (; it != table_.end(); ) {
148                 // current change starts like or follows new change
149                 if (it->range.start >= start) {
150                         break;
151                 }
152
153                 // new change intersects with existing change
154                 if (it->range.end > start) {
155                         pos_type oldEnd = it->range.end;
156                         it->range.end = start;
157
158                         LYXERR(Debug::CHANGES, "  cutting tail of type " << it->change.type
159                                 << " resulting in range (" << it->range.start << ", "
160                                 << it->range.end << ")");
161
162                         ++it;
163                         if (oldEnd >= end) {
164                                 LYXERR(Debug::CHANGES, "  inserting tail in range ("
165                                         << end << ", " << oldEnd << ")");
166                                 it = table_.insert(it, ChangeRange((it-1)->change, Range(end, oldEnd)));
167                         }
168                         continue;
169                 }
170
171                 ++it;
172         }
173
174         if (change.type != Change::UNCHANGED) {
175                 LYXERR(Debug::CHANGES, "  inserting change");
176                 it = table_.insert(it, ChangeRange(change, Range(start, end)));
177                 ++it;
178         }
179
180         for (; it != table_.end(); ) {
181                 // new change 'contains' existing change
182                 if (newRange.contains(it->range)) {
183                         LYXERR(Debug::CHANGES, "  removing subrange ("
184                                 << it->range.start << ", " << it->range.end << ")");
185                         it = table_.erase(it);
186                         continue;
187                 }
188
189                 // new change precedes existing change
190                 if (it->range.start >= end)
191                         break;
192
193                 // new change intersects with existing change
194                 it->range.start = end;
195                 LYXERR(Debug::CHANGES, "  cutting head of type "
196                         << it->change.type << " resulting in range ("
197                         << end << ", " << it->range.end << ")");
198                 break; // no need for another iteration
199         }
200
201         merge();
202 }
203
204
205 void Changes::erase(pos_type const pos)
206 {
207         LYXERR(Debug::CHANGES, "Erasing change at position " << pos);
208
209         ChangeTable::iterator it = table_.begin();
210         ChangeTable::iterator end = table_.end();
211
212         for (; it != end; ++it) {
213                 // range (pos,pos+x) becomes (pos,pos+x-1)
214                 if (it->range.start > pos)
215                         --(it->range.start);
216                 // range (pos-x,pos) stays (pos-x,pos)
217                 if (it->range.end > pos)
218                         --(it->range.end);
219         }
220
221         merge();
222 }
223
224
225 void Changes::insert(Change const & change, lyx::pos_type pos)
226 {
227         if (change.type != Change::UNCHANGED) {
228                 LYXERR(Debug::CHANGES, "Inserting change of type " << change.type
229                         << " at position " << pos);
230         }
231
232         ChangeTable::iterator it = table_.begin();
233         ChangeTable::iterator end = table_.end();
234
235         for (; it != end; ++it) {
236                 // range (pos,pos+x) becomes (pos+1,pos+x+1)
237                 if (it->range.start >= pos)
238                         ++(it->range.start);
239
240                 // range (pos-x,pos) stays as it is
241                 if (it->range.end > pos)
242                         ++(it->range.end);
243         }
244
245         set(change, pos, pos + 1); // set will call merge
246 }
247
248
249 Change const & Changes::lookup(pos_type const pos) const
250 {
251         static Change const noChange = Change(Change::UNCHANGED);
252
253         ChangeTable::const_iterator it = table_.begin();
254         ChangeTable::const_iterator const end = table_.end();
255
256         for (; it != end; ++it) {
257                 if (it->range.contains(pos))
258                         return it->change;
259         }
260
261         return noChange;
262 }
263
264
265 bool Changes::isDeleted(pos_type start, pos_type end) const
266 {
267         ChangeTable::const_iterator it = table_.begin();
268         ChangeTable::const_iterator const itend = table_.end();
269
270         for (; it != itend; ++it) {
271                 if (it->range.contains(Range(start, end))) {
272                         LYXERR(Debug::CHANGES, "range ("
273                                 << start << ", " << end << ") fully contains ("
274                                 << it->range.start << ", " << it->range.end
275                                 << ") of type " << it->change.type);
276                         return it->change.type == Change::DELETED;
277                 }
278         }
279         return false;
280 }
281
282
283 bool Changes::isChanged(pos_type const start, pos_type const end) const
284 {
285         ChangeTable::const_iterator it = table_.begin();
286         ChangeTable::const_iterator const itend = table_.end();
287
288         for (; it != itend; ++it) {
289                 if (it->range.intersects(Range(start, end))) {
290                         LYXERR(Debug::CHANGES, "found intersection of range ("
291                                 << start << ", " << end << ") with ("
292                                 << it->range.start << ", " << it->range.end
293                                 << ") of type " << it->change.type);
294                         return true;
295                 }
296         }
297         return false;
298 }
299
300
301 void Changes::merge()
302 {
303         ChangeTable::iterator it = table_.begin();
304
305         while (it != table_.end()) {
306                 LYXERR(Debug::CHANGES, "found change of type " << it->change.type
307                         << " and range (" << it->range.start << ", " << it->range.end
308                         << ")");
309
310                 if (it->range.start == it->range.end) {
311                         LYXERR(Debug::CHANGES, "removing empty range for pos "
312                                 << it->range.start);
313
314                         table_.erase(it);
315                         // start again
316                         it = table_.begin();
317                         continue;
318                 }
319
320                 if (it + 1 == table_.end())
321                         break;
322
323                 if (it->change.isSimilarTo((it + 1)->change)
324                     && it->range.end == (it + 1)->range.start) {
325                         LYXERR(Debug::CHANGES, "merging ranges (" << it->range.start << ", "
326                                 << it->range.end << ") and (" << (it + 1)->range.start << ", "
327                                 << (it + 1)->range.end << ")");
328
329                         (it + 1)->range.start = it->range.start;
330                         (it + 1)->change.changetime = max(it->change.changetime,
331                                                           (it + 1)->change.changetime);
332                         table_.erase(it);
333                         // start again
334                         it = table_.begin();
335                         continue;
336                 }
337
338                 ++it;
339         }
340 }
341
342
343 namespace {
344
345 docstring getLaTeXMarkup(docstring const & macro, docstring const & author,
346                          docstring const & chgTime,
347                          OutputParams const & runparams)
348 {
349         if (macro.empty())
350                 return docstring();
351
352         docstring uncodable_author;
353         odocstringstream ods;
354
355         ods << macro;
356         // convert utf8 author name to something representable
357         // in the current encoding
358         pair<docstring, docstring> author_latexed =
359                 runparams.encoding->latexString(author, runparams.dryrun);
360         if (!author_latexed.second.empty()) {
361                 LYXERR0("Omitting uncodable characters '"
362                         << author_latexed.second
363                         << "' in change author name!");
364                 uncodable_author = author;
365         }
366         ods << author_latexed.first << "}{" << chgTime << "}{";
367
368         // warn user (once) if we found uncodable glyphs.
369         if (!uncodable_author.empty()) {
370                 static std::set<docstring> warned_authors;
371                 static Mutex warned_mutex;
372                 Mutex::Locker locker(&warned_mutex);
373                 if (warned_authors.find(uncodable_author) == warned_authors.end()) {
374                         frontend::Alert::warning(_("Uncodable character in author name"),
375                                 support::bformat(_("The author name '%1$s',\n"
376                                   "used for change tracking, contains the following glyphs that\n"
377                                   "cannot be represented in the current encoding: %2$s.\n"
378                                   "These glyphs will be omitted in the exported LaTeX file.\n\n"
379                                   "Choose an appropriate document encoding (such as utf8)\n"
380                                   "or change the spelling of the author name."),
381                                 uncodable_author, author_latexed.second));
382                         warned_authors.insert(uncodable_author);
383                 }
384         }
385
386         return ods.str();
387 }
388
389 } //namespace anon
390
391
392 int Changes::latexMarkChange(otexstream & os, BufferParams const & bparams,
393                              Change const & oldChange, Change const & change,
394                              OutputParams const & runparams)
395 {
396         if (!bparams.output_changes || oldChange == change)
397                 return 0;
398
399         int column = 0;
400
401         if (oldChange.type != Change::UNCHANGED) {
402                 // close \lyxadded or \lyxdeleted
403                 os << '}';
404                 column++;
405                 if (oldChange.type == Change::DELETED)
406                         --runparams.inulemcmd;
407         }
408
409         docstring chgTime;
410         chgTime += asctime(gmtime(&change.changetime));
411         // remove trailing '\n'
412         chgTime.erase(chgTime.end() - 1);
413
414         docstring macro_beg;
415         if (change.type == Change::DELETED) {
416                 macro_beg = from_ascii("\\lyxdeleted{");
417                 ++runparams.inulemcmd;
418         }
419         else if (change.type == Change::INSERTED)
420                 macro_beg = from_ascii("\\lyxadded{");
421         
422         docstring str = getLaTeXMarkup(macro_beg,
423                                        bparams.authors().get(change.author).name(),
424                                        chgTime, runparams);
425         
426         os << str;
427         column += str.size();
428
429         return column;
430 }
431
432
433 void Changes::lyxMarkChange(ostream & os, BufferParams const & bparams, int & column,
434                             Change const & old, Change const & change)
435 {
436         if (old == change)
437                 return;
438
439         column = 0;
440
441         int const buffer_id = bparams.authors().get(change.author).bufferId();
442
443         switch (change.type) {
444                 case Change::UNCHANGED:
445                         os << "\n\\change_unchanged\n";
446                         break;
447
448                 case Change::DELETED:
449                         os << "\n\\change_deleted " << buffer_id
450                                 << " " << change.changetime << "\n";
451                         break;
452
453                 case Change::INSERTED:
454                         os << "\n\\change_inserted " << buffer_id
455                                 << " " << change.changetime << "\n";
456                         break;
457         }
458 }
459
460
461 void Changes::checkAuthors(AuthorList const & authorList)
462 {
463         ChangeTable::const_iterator it = table_.begin();
464         ChangeTable::const_iterator endit = table_.end();
465         for ( ; it != endit ; ++it) 
466                 if (it->change.type != Change::UNCHANGED)
467                         authorList.get(it->change.author).setUsed(true);
468 }
469
470
471 void Changes::addToToc(DocIterator const & cdit, Buffer const & buffer,
472         bool output_active) const
473 {
474         if (table_.empty())
475                 return;
476
477         shared_ptr<Toc> change_list = buffer.tocBackend().toc("change");
478         AuthorList const & author_list = buffer.params().authors();
479         DocIterator dit = cdit;
480
481         ChangeTable::const_iterator it = table_.begin();
482         ChangeTable::const_iterator const itend = table_.end();
483         for (; it != itend; ++it) {
484                 docstring str;
485                 switch (it->change.type) {
486                 case Change::UNCHANGED:
487                         continue;
488                 case Change::DELETED:
489                         // ✂ U+2702 BLACK SCISSORS
490                         str.push_back(0x2702);
491                         break;
492                 case Change::INSERTED:
493                         // ✍ U+270D WRITING HAND
494                         str.push_back(0x270d);
495                         break;
496                 }
497                 dit.pos() = it->range.start;
498                 Paragraph const & par = dit.paragraph();
499                 str += " " + par.asString(it->range.start, min(par.size(), it->range.end));
500                 if (it->range.end > par.size())
501                         // ¶ U+00B6 PILCROW SIGN
502                         str.push_back(0xb6);
503                 docstring const & author = author_list.get(it->change.author).name();
504                 Toc::iterator it = change_list->item(0, author);
505                 if (it == change_list->end()) {
506                         change_list->push_back(TocItem(dit, 0, author, true));
507                         change_list->push_back(TocItem(dit, 1, str, output_active,
508                                 support::wrapParas(str, 4)));
509                         continue;
510                 }
511                 for (++it; it != change_list->end(); ++it) {
512                         if (it->depth() == 0 && it->str() != author)
513                                 break;
514                 }
515                 change_list->insert(it, TocItem(dit, 1, str, output_active,
516                         support::wrapParas(str, 4)));
517         }
518 }
519
520 } // namespace lyx