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