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