]> git.lyx.org Git - lyx.git/blob - src/insets/InsetInfo.cpp
New info-inset type l7n
[lyx.git] / src / insets / InsetInfo.cpp
1 /**
2  * \file InsetInfo.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Bo Peng
7  * \author Jürgen Spitzmüller
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11 #include <config.h>
12
13 #include "InsetInfo.h"
14 #include "LyX.h"
15 #include "Buffer.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
18 #include "CutAndPaste.h"
19 #include "Font.h"
20 #include "FuncRequest.h"
21 #include "FuncStatus.h"
22 #include "InsetGraphics.h"
23 #include "InsetSpecialChar.h"
24 #include "KeyMap.h"
25 #include "LaTeXFeatures.h"
26 #include "Language.h"
27 #include "LayoutFile.h"
28 #include "Length.h"
29 #include "LyXAction.h"
30 #include "LyXRC.h"
31 #include "LyXVC.h"
32 #include "Lexer.h"
33 #include "Paragraph.h"
34 #include "ParIterator.h"
35 #include "ParagraphParameters.h"
36 #include "version.h"
37
38 #include "frontends/Application.h"
39
40 #include "support/convert.h"
41 #include "support/debug.h"
42 #include "support/docstream.h"
43 #include "support/docstring_list.h"
44 #include "support/ExceptionMessage.h"
45 #include "support/FileName.h"
46 #include "support/filetools.h"
47 #include "support/gettext.h"
48 #include "support/Messages.h"
49 #include "support/lstrings.h"
50 #include "support/qstring_helpers.h"
51 #include "support/Translator.h"
52
53 #include <sstream>
54
55 #include <QtGui/QImage>
56 #include <QDate>
57 #include <QLocale>
58
59 using namespace std;
60 using namespace lyx::support;
61
62 namespace lyx {
63
64 namespace {
65
66 typedef Translator<InsetInfoParams::info_type, string> NameTranslator;
67
68 NameTranslator const initTranslator()
69 {
70         NameTranslator translator(InsetInfoParams::UNKNOWN_INFO, "unknown");
71
72         translator.addPair(InsetInfoParams::SHORTCUTS_INFO, "shortcuts");
73         translator.addPair(InsetInfoParams::SHORTCUT_INFO, "shortcut");
74         translator.addPair(InsetInfoParams::LYXRC_INFO, "lyxrc");
75         translator.addPair(InsetInfoParams::PACKAGE_INFO, "package");
76         translator.addPair(InsetInfoParams::TEXTCLASS_INFO, "textclass");
77         translator.addPair(InsetInfoParams::MENU_INFO, "menu");
78         translator.addPair(InsetInfoParams::L7N_INFO, "l7n");
79         translator.addPair(InsetInfoParams::ICON_INFO, "icon");
80         translator.addPair(InsetInfoParams::BUFFER_INFO, "buffer");
81         translator.addPair(InsetInfoParams::LYX_INFO, "lyxinfo");
82         translator.addPair(InsetInfoParams::VCS_INFO, "vcs");
83         translator.addPair(InsetInfoParams::DATE_INFO, "date");
84         translator.addPair(InsetInfoParams::MODDATE_INFO, "moddate");
85         translator.addPair(InsetInfoParams::FIXDATE_INFO, "fixdate");
86         translator.addPair(InsetInfoParams::TIME_INFO, "time");
87         translator.addPair(InsetInfoParams::MODTIME_INFO, "modtime");
88         translator.addPair(InsetInfoParams::FIXTIME_INFO, "fixtime");
89
90         return translator;
91 }
92
93 /// The translator between the information type enum and corresponding string.
94 NameTranslator const & nameTranslator()
95 {
96         static NameTranslator const translator = initTranslator();
97         return translator;
98 }
99
100
101 typedef Translator<InsetInfoParams::info_type, string> DefaultValueTranslator;
102
103 DefaultValueTranslator const initDVTranslator()
104 {
105         DefaultValueTranslator translator(InsetInfoParams::UNKNOWN_INFO, "");
106
107         translator.addPair(InsetInfoParams::SHORTCUTS_INFO, "info-insert");
108         translator.addPair(InsetInfoParams::SHORTCUT_INFO, "info-insert");
109         translator.addPair(InsetInfoParams::LYXRC_INFO, "user_name");
110         translator.addPair(InsetInfoParams::PACKAGE_INFO, "graphics");
111         translator.addPair(InsetInfoParams::TEXTCLASS_INFO, "article");
112         translator.addPair(InsetInfoParams::MENU_INFO, "info-insert");
113         translator.addPair(InsetInfoParams::L7N_INFO, "");
114         translator.addPair(InsetInfoParams::ICON_INFO, "info-insert");
115         translator.addPair(InsetInfoParams::BUFFER_INFO, "name-noext");
116         translator.addPair(InsetInfoParams::LYX_INFO, "version");
117         translator.addPair(InsetInfoParams::VCS_INFO, "revision");
118         translator.addPair(InsetInfoParams::DATE_INFO, "loclong");
119         translator.addPair(InsetInfoParams::MODDATE_INFO, "loclong");
120         translator.addPair(InsetInfoParams::FIXDATE_INFO, "loclong");
121         translator.addPair(InsetInfoParams::TIME_INFO, "long");
122         translator.addPair(InsetInfoParams::MODTIME_INFO, "long");
123         translator.addPair(InsetInfoParams::FIXTIME_INFO, "long");
124
125         return translator;
126 }
127
128 /// The translator between the information type enum and some sensible default value.
129 DefaultValueTranslator const & defaultValueTranslator()
130 {
131         static DefaultValueTranslator const translator = initDVTranslator();
132         return translator;
133 }
134
135 } // namespace
136
137
138 /////////////////////////////////////////////////////////////////////
139 //
140 // InsetInfoParams
141 //
142 ///////////////////////////////////////////////////////////////////////
143
144 InsetInfoParams infoparams;
145
146 namespace{
147 set<string> getTexFileList(string const & filename)
148 {
149         set<string> list;
150         FileName const file = libFileSearch(string(), filename);
151         if (file.empty())
152                 return list;
153
154         // FIXME Unicode.
155         vector<docstring> doclist =
156                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
157
158         // Normalise paths like /foo//bar ==> /foo/bar
159         for (auto doc : doclist) {
160                 subst(doc, from_ascii("\r"), docstring());
161                 while (contains(doc, from_ascii("//")))
162                         subst(doc, from_ascii("//"), from_ascii("/"));
163                 if (!doc.empty())
164                         list.insert(removeExtension(onlyFileName(to_utf8(doc))));
165         }
166
167         // remove duplicates
168         return list;
169 }
170 } // namespace anon
171
172
173 docstring InsetInfoParams::getDate(string const iname, QDate const date) const
174 {
175         QLocale loc;
176         if (lang)
177                 loc = QLocale(toqstr(lang->code()));
178         if (iname == "long")
179                 return qstring_to_ucs4(loc.toString(date, QLocale::LongFormat));
180         else if (iname == "short")
181                 return qstring_to_ucs4(loc.toString(date, QLocale::ShortFormat));
182         else if (iname == "ISO")
183                 return qstring_to_ucs4(date.toString(Qt::ISODate));
184         else if (iname == "loclong")
185                 return qstring_to_ucs4(loc.toString(date, toqstr(lang->dateFormat(0))));
186         else if (iname == "locmedium")
187                 return qstring_to_ucs4(loc.toString(date, toqstr(lang->dateFormat(1))));
188         else if (iname == "locshort")
189                 return qstring_to_ucs4(loc.toString(date, toqstr(lang->dateFormat(2))));
190         else
191                 return qstring_to_ucs4(loc.toString(date, toqstr(iname)));
192 }
193
194
195 docstring InsetInfoParams::getTime(string const iname, QTime const time) const
196 {
197         QLocale loc;
198         if (lang)
199                 loc = QLocale(toqstr(lang->code()));
200         if (iname == "long")
201                 return qstring_to_ucs4(loc.toString(time, QLocale::LongFormat));
202         else if (iname == "short")
203                 return qstring_to_ucs4(loc.toString(time, QLocale::ShortFormat));
204         else if (iname == "ISO")
205                 return qstring_to_ucs4(time.toString(Qt::ISODate));
206         else
207                 return qstring_to_ucs4(loc.toString(time, toqstr(iname)));
208 }
209
210
211 vector<pair<string,docstring>> InsetInfoParams::getArguments(Buffer const * buf,
212                                                              string const & itype) const
213 {
214         vector<pair<string,docstring>> result;
215
216         switch (nameTranslator().find(itype)) {
217         case UNKNOWN_INFO:
218                 result.push_back(make_pair("invalid", _("Please select a valid type!")));
219                 break;
220
221         case SHORTCUT_INFO:
222         case SHORTCUTS_INFO:
223         case MENU_INFO:
224         case ICON_INFO: {
225                 result.push_back(make_pair("custom", _("Custom")));
226                 LyXAction::const_iterator fit = lyxaction.func_begin();
227                 LyXAction::const_iterator const fen = lyxaction.func_end();
228                 for (; fit != fen; ++fit) {
229                         string const lfun = fit->first;
230                         if (!lfun.empty())
231                                 result.push_back(make_pair(lfun, from_ascii(lfun)));
232                 }
233                 break;
234         }
235
236         case L7N_INFO:
237                 result.push_back(make_pair("custom", _("Custom")));
238                 break;
239
240         case LYXRC_INFO: {
241                 result.push_back(make_pair("custom", _("Custom")));
242                 set<string> rcs = lyxrc.getRCs();
243                 for (auto const & rc : rcs)
244                         result.push_back(make_pair(rc, from_ascii(rc)));
245                 break;
246         }
247
248         case PACKAGE_INFO:
249         case TEXTCLASS_INFO: {
250                 result.push_back(make_pair("custom", _("Custom")));
251                 string const filename = (itype == "package") ? "styFiles.lst"
252                                                             : "clsFiles.lst";
253                 set<string> flist = getTexFileList(filename);
254                 for (auto const & f : flist)
255                         result.push_back(make_pair(f, from_utf8(f)));
256                 break;
257         }
258
259         case BUFFER_INFO:
260                 result.push_back(make_pair("name", _("File name (with extension)")));
261                 result.push_back(make_pair("name-noext", _("File name (without extension)")));
262                 result.push_back(make_pair("path", _("File path")));
263                 result.push_back(make_pair("class", _("Used text class")));
264                 break;
265
266         case VCS_INFO: {
267                 if (!buf->lyxvc().inUse()) {
268                         result.push_back(make_pair("invalid", _("No version control!")));
269                         break;
270                 }
271                 result.push_back(make_pair("revision", _("Revision[[Version Control]]")));
272                 result.push_back(make_pair("tree-revision", _("Tree revision")));
273                 result.push_back(make_pair("author", _("Author")));
274                 result.push_back(make_pair("date", _("Date")));
275                 result.push_back(make_pair("time", _("Time[[of day]]")));
276                 break;
277         }
278
279         case LYX_INFO:
280                 result.push_back(make_pair("version", _("LyX version")));
281                 break;
282
283         case FIXDATE_INFO:
284         case DATE_INFO:
285         case MODDATE_INFO: {
286                 string const dt = split(name, '@');
287                 QDate date;
288                 if (itype == "moddate")
289                         date = QDateTime::fromTime_t(buf->fileName().lastModified()).date();
290                 else if (itype == "fixdate" && !dt.empty()) {
291                         QDate const gdate = QDate::fromString(toqstr(dt), Qt::ISODate);
292                         date = (gdate.isValid()) ? gdate : QDate::currentDate();
293                 } else
294                         date = QDate::currentDate();
295                 result.push_back(make_pair("long",getDate("long", date)));
296                 result.push_back(make_pair("short", getDate("short", date)));
297                 result.push_back(make_pair("loclong", getDate("loclong", date)));
298                 result.push_back(make_pair("locmedium", getDate("locmedium", date)));
299                 result.push_back(make_pair("locshort", getDate("locshort", date)));
300                 result.push_back(make_pair("ISO", getDate("ISO", date)));
301                 result.push_back(make_pair("yyyy", getDate("yyyy", date)));
302                 result.push_back(make_pair("MMMM", getDate("MMMM", date)));
303                 result.push_back(make_pair("MMM", getDate("MMM", date)));
304                 result.push_back(make_pair("dddd", getDate("dddd", date)));
305                 result.push_back(make_pair("ddd", getDate("ddd", date)));
306                 result.push_back(make_pair("custom", _("Custom")));
307                 break;
308         }
309         case FIXTIME_INFO:
310         case TIME_INFO:
311         case MODTIME_INFO: {
312                 string const tt = split(name, '@');
313                 QTime time;
314                 if (itype == "modtime")
315                         time = QDateTime::fromTime_t(buf->fileName().lastModified()).time();
316                 else if (itype == "fixtime" && !tt.empty()) {
317                         QTime const gtime = QTime::fromString(toqstr(tt), Qt::ISODate);
318                         time = (gtime.isValid()) ? gtime : QTime::currentTime();
319                 } else
320                         time = QTime::currentTime();
321                 result.push_back(make_pair("long",getTime("long", time)));
322                 result.push_back(make_pair("short", getTime("short", time)));
323                 result.push_back(make_pair("ISO", getTime("ISO", time)));
324                 result.push_back(make_pair("custom", _("Custom")));
325                 break;
326         }
327         }
328
329         return result;
330 }
331
332
333 string InsetInfoParams::infoType() const
334 {
335         return nameTranslator().find(type);
336 }
337
338
339
340 /////////////////////////////////////////////////////////////////////////
341 //
342 // InsetInfo
343 //
344 /////////////////////////////////////////////////////////////////////////
345
346
347
348 InsetInfo::InsetInfo(Buffer * buf, string const & name)
349         : InsetCollapsible(buf), initialized_(false)
350 {
351         params_.type = InsetInfoParams::UNKNOWN_INFO;
352         params_.force_ltr = false;
353         setInfo(name);
354         status_ = Collapsed;
355 }
356
357
358 Inset * InsetInfo::editXY(Cursor & cur, int x, int y)
359 {
360         // do not allow the cursor to be set in this Inset
361         return Inset::editXY(cur, x, y);
362 }
363
364
365 docstring InsetInfo::layoutName() const
366 {
367         return from_ascii("Info:" + params_.infoType());
368 }
369
370
371 docstring InsetInfo::toolTip(BufferView const &, int, int) const
372 {
373         docstring result;
374         switch (nameTranslator().find(params_.infoType())) {
375         case InsetInfoParams::UNKNOWN_INFO:
376                 result = _("Invalid information inset");
377                 break;
378         case InsetInfoParams::SHORTCUT_INFO:
379                 result = bformat(_("The keybard shortcut for the function '%1$s'"),
380                                 from_utf8(params_.name));
381                 break;
382         case InsetInfoParams::SHORTCUTS_INFO:
383                 result = bformat(_("The keybard shortcuts for the function '%1$s'"),
384                                 from_utf8(params_.name));
385                 break;
386         case InsetInfoParams::MENU_INFO: 
387                 result = bformat(_("The menu location for the function '%1$s'"),
388                                 from_utf8(params_.name));
389                 break;
390         case InsetInfoParams::L7N_INFO: 
391                 result = bformat(_("The localization for the string '%1$s'"),
392                                 from_utf8(params_.name));
393                 break;
394         case InsetInfoParams::ICON_INFO:
395                 result = bformat(_("The toolbar icon for the function '%1$s'"),
396                                 from_utf8(params_.name));
397                 break;
398         case InsetInfoParams::LYXRC_INFO:
399                 result = bformat(_("The preference setting for the preference key '%1$s'"),
400                                 from_utf8(params_.name));
401                 break;
402         case InsetInfoParams::PACKAGE_INFO:
403                 result = bformat(_("Availability of the LaTeX package '%1$s'"),
404                                 from_utf8(params_.name));
405                 break;
406         case InsetInfoParams::TEXTCLASS_INFO:
407                 result = bformat(_("Availability of the LaTeX class '%1$s'"),
408                                 from_utf8(params_.name));
409                 break;
410         case InsetInfoParams::BUFFER_INFO:
411                 if (params_.name == "name")
412                         result = _("The name of this file (incl. extension)");
413                 else if (params_.name == "name-noext")
414                         result = _("The name of this file (without extension)");
415                 else if (params_.name == "path")
416                         result = _("The path where this file is saved");
417                 else if (params_.name == "class")
418                         result = _("The class this document uses");
419                 break;
420         case InsetInfoParams::VCS_INFO:
421                 if (params_.name == "revision")
422                         result = _("Version control revision");
423                 else if (params_.name == "tree-revision")
424                         result = _("Version control tree revision");
425                 else if (params_.name == "author")
426                          result = _("Version control author");
427                 else if (params_.name == "date")
428                         result = _("Version control date");
429                 else if (params_.name == "time")
430                         result = _("Version control time");
431                 break;
432         case InsetInfoParams::LYX_INFO:
433                 result = _("The current LyX version");
434                 break;
435         case InsetInfoParams::DATE_INFO:
436                 result = _("The current date");
437                 break;
438         case InsetInfoParams::MODDATE_INFO:
439                 result = _("The date of last save");
440                 break;
441         case InsetInfoParams::FIXDATE_INFO:
442                 result = _("A static date");
443                 break;
444         case InsetInfoParams::TIME_INFO:
445                 result = _("The current time");
446                 break;
447         case InsetInfoParams::MODTIME_INFO:
448                 result = _("The time of last save");
449                 break;
450         case InsetInfoParams::FIXTIME_INFO:
451                 result = _("A static time");
452                 break;
453         }
454
455         return result;
456 }
457
458
459 void InsetInfo::read(Lexer & lex)
460 {
461         string token;
462         while (lex.isOK()) {
463                 lex.next();
464                 token = lex.getString();
465                 if (token == "type") {
466                         lex.next();
467                         token = lex.getString();
468                         params_.type = nameTranslator().find(token);
469                 } else if (token == "arg") {
470                         lex.next(true);
471                         params_.name = lex.getString();
472                 } else if (token == "\\end_inset")
473                         break;
474         }
475         if (token != "\\end_inset") {
476                 lex.printError("Missing \\end_inset at this point");
477                 throw ExceptionMessage(WarningException,
478                         _("Missing \\end_inset at this point."),
479                         from_utf8(token));
480         }
481 }
482
483
484 void InsetInfo::write(ostream & os) const
485 {
486         os << "Info\ntype  \"" << params_.infoType()
487            << "\"\narg   " << Lexer::quoteString(params_.name);
488 }
489
490
491 bool InsetInfo::validateModifyArgument(docstring const & arg) const
492 {
493         string type;
494         string name = trim(split(to_utf8(arg), type, ' '));
495
496         switch (nameTranslator().find(type)) {
497         case InsetInfoParams::UNKNOWN_INFO:
498                 return false;
499
500         case InsetInfoParams::SHORTCUT_INFO:
501         case InsetInfoParams::SHORTCUTS_INFO:
502         case InsetInfoParams::MENU_INFO: {
503                 FuncRequest func = lyxaction.lookupFunc(name);
504                 return func.action() != LFUN_UNKNOWN_ACTION;
505         }
506
507         case InsetInfoParams::L7N_INFO:
508                 return !name.empty();
509
510         case InsetInfoParams::ICON_INFO: {
511                 FuncCode const action = lyxaction.lookupFunc(name).action();
512                 if (action == LFUN_UNKNOWN_ACTION) {
513                         string dir = "images";
514                         return !imageLibFileSearch(dir, name, "svgz,png").empty();
515                 }
516                 return true;
517         }
518
519         case InsetInfoParams::LYXRC_INFO: {
520                 set<string> rcs = lyxrc.getRCs();
521                 return rcs.find(name) != rcs.end();
522         }
523
524         case InsetInfoParams::PACKAGE_INFO:
525         case InsetInfoParams::TEXTCLASS_INFO:
526                 return true;
527
528         case InsetInfoParams::BUFFER_INFO:
529                 return (name == "name" || name == "name-noext"
530                         || name == "path" || name == "class");
531
532         case InsetInfoParams::VCS_INFO:
533                 if (name == "revision" || name == "tree-revision"
534                     || name == "author" || name == "date" || name == "time")
535                         return buffer().lyxvc().inUse();
536                 return false;
537
538         case InsetInfoParams::LYX_INFO:
539                 return name == "version";
540
541         case InsetInfoParams::FIXDATE_INFO: {
542                 string date;
543                 string piece;
544                 date = split(name, piece, '@');
545                 if (!date.empty() && !QDate::fromString(toqstr(date), Qt::ISODate).isValid())
546                         return false;
547                 if (!piece.empty())
548                         name = piece;
549         }
550         // fall through
551         case InsetInfoParams::DATE_INFO:
552         case InsetInfoParams::MODDATE_INFO: {
553                 if (name == "long" || name == "short" || name == "ISO")
554                         return true;
555                 else {
556                         QDate date = QDate::currentDate();
557                         return !date.toString(toqstr(name)).isEmpty();
558                 }
559         }
560         case InsetInfoParams::FIXTIME_INFO: {
561                 string time;
562                 string piece;
563                 time = split(name, piece, '@');
564                 if (!time.empty() && !QTime::fromString(toqstr(time), Qt::ISODate).isValid())
565                         return false;
566                 if (!piece.empty())
567                         name = piece;
568         }
569         // fall through
570         case InsetInfoParams::TIME_INFO:
571         case InsetInfoParams::MODTIME_INFO: {
572                 if (name == "long" || name == "short" || name == "ISO")
573                         return true;
574                 else {
575                         QTime time = QTime::currentTime();
576                         return !time.toString(toqstr(name)).isEmpty();
577                 }
578         }
579         }
580
581         return false;
582 }
583
584
585 bool InsetInfo::showInsetDialog(BufferView * bv) const
586 {
587         bv->showDialog("info");
588         return true;
589 }
590
591
592 bool InsetInfo::getStatus(Cursor & cur, FuncRequest const & cmd,
593                 FuncStatus & flag) const
594 {
595         switch (cmd.action()) {
596         case LFUN_INSET_SETTINGS:
597                 return InsetCollapsible::getStatus(cur, cmd, flag);
598
599         case LFUN_INSET_DIALOG_UPDATE:
600         case LFUN_INSET_COPY_AS:
601         case LFUN_INSET_DISSOLVE:
602                 flag.setEnabled(true);
603                 return true;
604
605         case LFUN_INSET_MODIFY:
606                 if (validateModifyArgument(cmd.argument())) {
607                         flag.setEnabled(true);
608                         string typestr;
609                         string name = trim(split(to_utf8(cmd.argument()), typestr, ' '));
610                         InsetInfoParams::info_type type = nameTranslator().find(typestr);
611                         string origname = params_.name;
612                         if (type == InsetInfoParams::FIXDATE_INFO
613                             || type == InsetInfoParams::FIXTIME_INFO)
614                                 split(params_.name, origname, '@');
615                         flag.setOnOff(type == params_.type && name == origname);
616                         return true;
617                 }
618                 //fall through
619
620         default:
621                 return false;
622         }
623 }
624
625
626 void InsetInfo::doDispatch(Cursor & cur, FuncRequest & cmd)
627 {
628         switch (cmd.action()) {
629         case LFUN_INSET_MODIFY:
630                 cur.recordUndo();
631                 setInfo(to_utf8(cmd.argument()));
632                 cur.forceBufferUpdate();
633                 initialized_ = false;
634                 break;
635
636         case LFUN_INSET_COPY_AS: {
637                 cap::clearSelection();
638                 Cursor copy(cur);
639                 copy.pushBackward(*this);
640                 copy.pit() = 0;
641                 copy.pos() = 0;
642                 copy.resetAnchor();
643                 copy.pit() = copy.lastpit();
644                 copy.pos() = copy.lastpos();
645                 copy.setSelection();
646                 cap::copySelection(copy);
647                 break;
648         }
649
650         default:
651                 InsetCollapsible::doDispatch(cur, cmd);
652                 break;
653         }
654 }
655
656
657 void InsetInfo::setInfo(string const & name)
658 {
659         if (name.empty())
660                 return;
661
662         string saved_date_specifier;
663         // Store old date specifier for potential re-use
664         if (!params_.name.empty())
665                 saved_date_specifier = split(params_.name, '@');
666         // info_type name
667         string type;
668         params_.name = trim(split(name, type, ' '));
669         params_.type = nameTranslator().find(type);
670         if (params_.name.empty())
671                 params_.name = defaultValueTranslator().find(params_.type);
672         if (params_.type == InsetInfoParams::FIXDATE_INFO) {
673                 string const date_specifier = split(params_.name, '@');
674                 // If an explicit new fix date is specified, use that
675                 // Otherwise, use the old one or, if there is none,
676                 // the current date
677                 if (date_specifier.empty()) {
678                         if (saved_date_specifier.empty())
679                                 params_.name += "@" + fromqstr(QDate::currentDate().toString(Qt::ISODate));
680                         else
681                                 params_.name += "@" + saved_date_specifier;
682                 }
683         }
684         else if (params_.type == InsetInfoParams::FIXTIME_INFO) {
685                 string const time_specifier = split(params_.name, '@');
686                 // If an explicit new fix time is specified, use that
687                 // Otherwise, use the old one or, if there is none,
688                 // the current time
689                 if (time_specifier.empty()) {
690                         if (saved_date_specifier.empty())
691                                 params_.name += "@" + fromqstr(QTime::currentTime().toString(Qt::ISODate));
692                         else
693                                 params_.name += "@" + saved_date_specifier;
694                 }
695         }
696 }
697
698
699 void InsetInfo::error(docstring const & err, Language const * lang)
700 {
701         setText(bformat(translateIfPossible(err, lang->code()), from_utf8(params_.name)),
702                 Font(inherit_font, lang), false);
703 }
704
705
706 void InsetInfo::info(docstring const & err, Language const * lang)
707 {
708         setText(translateIfPossible(err, lang->code()),
709                         Font(inherit_font, lang), false);
710 }
711
712
713 void InsetInfo::setText(docstring const & str, Language const * lang)
714 {
715         setText(str, Font(inherit_font, lang), false);
716 }
717
718
719 bool InsetInfo::forceLTR() const
720 {
721         return params_.force_ltr;
722 }
723
724
725 void InsetInfo::updateBuffer(ParIterator const & it, UpdateType utype) {
726         // If the Buffer is a clone, then we neither need nor want to do any
727         // of what follows. We want, rather, just to inherit how things were
728         // in the original Buffer. This is especially important for VCS.
729         // Otherwise, we could in principle have different settings here
730         // than in the Buffer we were exporting.
731         if (buffer().isClone())
732                 return;
733
734         BufferParams const & bp = buffer().params();
735         params_.lang = it.paragraph().getFontSettings(bp, it.pos()).language();
736         Language const * tryguilang = languages.getFromCode(Messages::guiLanguage());
737         // Some info insets use the language of the GUI (if available)
738         Language const * guilang = tryguilang ? tryguilang : params_.lang;
739
740         params_.force_ltr = !params_.lang->rightToLeft();
741         // This is just to get the string into the po files
742         docstring gui;
743         switch (params_.type) {
744         case InsetInfoParams::UNKNOWN_INFO:
745                 gui = _("Unknown Info!");
746                 info(from_ascii("Unknown Info!"), params_.lang);
747                 initialized_ = false;
748                 break;
749         case InsetInfoParams::SHORTCUT_INFO:
750         case InsetInfoParams::SHORTCUTS_INFO: {
751                 // shortcuts can change, so we need to re-do this each time
752                 FuncRequest const func = lyxaction.lookupFunc(params_.name);
753                 if (func.action() == LFUN_UNKNOWN_ACTION) {
754                         gui = _("Unknown action %1$s");
755                         error(from_ascii("Unknown action %1$s"), params_.lang);
756                         break;
757                 }
758                 KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(func);
759                 if (bindings.empty()) {
760                         gui = _("undefined");
761                         info(from_ascii("undefined"), params_.lang);
762                         break;
763                 }
764                 docstring sequence;
765                 if (params_.type == InsetInfoParams::SHORTCUT_INFO)
766                         sequence = bindings.begin()->print(KeySequence::ForGui);
767                 else
768                         sequence = theTopLevelKeymap().printBindings(func, KeySequence::ForGui);
769                 // QKeySequence returns special characters for keys on the mac
770                 // Since these are not included in many fonts, we
771                 // re-translate them to textual names (see #10641)
772                 odocstringstream ods;
773                 string const lcode = params_.lang->code();
774                 for (size_t n = 0; n < sequence.size(); ++n) {
775                         char_type const c = sequence[n];
776                         switch(c) {
777                         case 0x21b5://Return
778                                 gui = _("Return[[Key]]");
779                                 ods << translateIfPossible(from_ascii("Return[[Key]]"), lcode);
780                                 break;
781                         case 0x21b9://Tab both directions (Win)
782                                 gui = _("Tab[[Key]]");
783                                 ods << translateIfPossible(from_ascii("Tab[[Key]]"), lcode);
784                                 break;
785                         case 0x21de://Qt::Key_PageUp
786                                 gui = _("PgUp");
787                                 ods << translateIfPossible(from_ascii("PgUp"), lcode);
788                                 break;
789                         case 0x21df://Qt::Key_PageDown
790                                 gui = _("PgDown");
791                                 ods << translateIfPossible(from_ascii("PgDown"), lcode);
792                                 break;
793                         case 0x21e4://Qt::Key_Backtab
794                                 gui = _("Backtab");
795                                 ods << translateIfPossible(from_ascii("Backtab"), lcode);
796                                 break;
797                         case 0x21e5://Qt::Key_Tab
798                                 gui = _("Tab");
799                                 ods << translateIfPossible(from_ascii("Tab"), lcode);
800                                 break;
801                         case 0x21e7://Shift
802                                 gui = _("Shift");
803                                 ods << translateIfPossible(from_ascii("Shift"), lcode);
804                                 break;
805                         case 0x21ea://Qt::Key_CapsLock
806                                 gui = _("CapsLock");
807                                 ods << translateIfPossible(from_ascii("CapsLock"), lcode);
808                                 break;
809                         case 0x2303://Control
810                                 gui = _("Control[[Key]]");
811                                 ods << translateIfPossible(from_ascii("Control[[Key]]"), lcode);
812                                 break;
813                         case 0x2318://CMD
814                                 gui = _("Command[[Key]]");
815                                 ods << translateIfPossible(from_ascii("Command[[Key]]"), lcode);
816                                 break;
817                         case 0x2324://Qt::Key_Enter
818                                 gui = _("Return[[Key]]");
819                                 ods << translateIfPossible(from_ascii("Return[[Key]]"), lcode);
820                                 break;
821                         case 0x2325://Option key
822                                 gui = _("Option[[Key]]");
823                                 ods << translateIfPossible(from_ascii("Option[[Key]]"), lcode);
824                                 break;
825                         case 0x2326://Qt::Key_Delete
826                                 gui = _("Delete[[Key]]");
827                                 ods << translateIfPossible(from_ascii("Delete[[Key]]"), lcode);
828                                 break;
829                         case 0x232b://Qt::Key_Backspace
830                                 gui = _("Fn+Del");
831                                 ods << translateIfPossible(from_ascii("Fn+Delete"), lcode);
832                                 break;
833                         case 0x238b://Qt::Key_Escape
834                                 gui = _("Esc");
835                                 ods << translateIfPossible(from_ascii("Esc"), lcode);
836                                 break;
837                         default:
838                                 ods.put(c);
839                         }
840                 }
841                 setText(ods.str(), guilang);
842                 params_.force_ltr = !guilang->rightToLeft() && !params_.lang->rightToLeft();
843                 break;
844         }
845         case InsetInfoParams::LYXRC_INFO: {
846                 // this information could change, if the preferences are changed,
847                 // so we will recalculate each time through.
848                 ostringstream oss;
849                 if (params_.name.empty()) {
850                         gui = _("undefined");
851                         info(from_ascii("undefined"), params_.lang);
852                         break;
853                 }
854                 // FIXME this uses the serialization mechanism to get the info
855                 // we want, which i guess works but is a bit strange.
856                 lyxrc.write(oss, true, params_.name);
857                 string result = oss.str();
858                 if (result.size() < 2) {
859                         gui = _("undefined");
860                         info(from_ascii("undefined"), params_.lang);
861                         break;
862                 }
863                 string::size_type loc = result.rfind("\n", result.size() - 2);
864                 loc = loc == string::npos ? 0 : loc + 1;
865                 if (result.size() < loc + params_.name.size() + 1
866                           || result.substr(loc + 1, params_.name.size()) != params_.name) {
867                         gui = _("undefined");
868                         info(from_ascii("undefined"), params_.lang);
869                         break;
870                 }
871                 // remove leading comments and \\name and space
872                 result = result.substr(loc + params_.name.size() + 2);
873
874                 // remove \n and ""
875                 result = rtrim(result, "\n");
876                 result = trim(result, "\"");
877                 gui = _("not set");
878                 if (result.empty())
879                         result = "not set";
880                 setText(from_utf8(result), params_.lang);
881                 break;
882         }
883         case InsetInfoParams::PACKAGE_INFO:
884                 // only need to do this once.
885                 if (initialized_)
886                         break;
887                 // check in packages.lst
888                 if (LaTeXFeatures::isAvailable(params_.name)) {
889                         gui = _("yes");
890                         info(from_ascii("yes"), params_.lang);
891                 } else {
892                         gui = _("no");
893                         info(from_ascii("no"), params_.lang);
894                 }
895                 initialized_ = true;
896                 break;
897
898         case InsetInfoParams::TEXTCLASS_INFO: {
899                 // the TextClass can change
900                 LayoutFileList const & list = LayoutFileList::get();
901                 bool available = false;
902                 // params_.name is the class name
903                 if (list.haveClass(params_.name))
904                         available = list[params_.name].isTeXClassAvailable();
905                 if (available) {
906                         gui = _("yes");
907                         info(from_ascii("yes"), params_.lang);
908                 } else {
909                         gui = _("no");
910                         info(from_ascii("no"), params_.lang);
911                 }
912                 break;
913         }
914         case InsetInfoParams::MENU_INFO: {
915                 // only need to do this once.
916                 if (initialized_)
917                         break;
918                 docstring_list names;
919                 FuncRequest func = lyxaction.lookupFunc(params_.name);
920                 if (func.action() == LFUN_UNKNOWN_ACTION) {
921                         gui = _("Unknown action %1$s");
922                         error(from_ascii("Unknown action %1$s"), params_.lang);
923                         break;
924                 }
925                 if (func.action() == LFUN_BUFFER_VIEW || func.action() == LFUN_BUFFER_UPDATE)
926                         // The default output format is in the menu without argument,
927                         // so strip it here.
928                         if (func.argument() == from_ascii(buffer().params().getDefaultOutputFormat()))
929                                 func = FuncRequest(func.action());
930                 // iterate through the menubackend to find it
931                 if (!theApp()) {
932                         gui = _("Can't determine menu entry for action %1$s in batch mode");
933                         error(from_ascii("Can't determine menu entry for action %1$s in batch mode"), params_.lang);
934                         initialized_ = true;
935                         break;
936                 }
937                 // and we will not keep trying if we fail
938                 initialized_ = theApp()->hasBufferView();
939                 if (!theApp()->searchMenu(func, names)) {
940                         gui = _("No menu entry for action %1$s");
941                         error(from_ascii("No menu entry for action %1$s"), params_.lang);
942                         break;
943                 }
944                 // if found, return its path.
945                 clear();
946                 Paragraph & par = paragraphs().front();
947                 Font const f(inherit_font, guilang);
948                 params_.force_ltr = !guilang->rightToLeft();
949                 //Font fu = f;
950                 //fu.fontInfo().setUnderbar(FONT_ON);
951                 for (docstring const & name : names) {
952                         // do not insert > for the top level menu item
953                         if (&name != &names.front())
954                                 par.insertInset(par.size(), new InsetSpecialChar(InsetSpecialChar::MENU_SEPARATOR),
955                                                 f, Change(Change::UNCHANGED));
956                         //FIXME: add proper underlines here. This
957                         // involves rewriting searchMenu used above to
958                         // return a vector of menus. If we do not do
959                         // that, we might as well use below
960                         // Paragraph::insert on each string (JMarc)
961                         for (char_type c : name)
962                                 par.insertChar(par.size(), c, f, Change(Change::UNCHANGED));
963                 }
964                 break;
965         }
966         case InsetInfoParams::L7N_INFO: {
967                 docstring locstring = _(params_.name);
968                 // Remove trailing colons
969                 locstring = rtrim(locstring, ":");
970                 // Remove menu accelerators
971                 if (contains(locstring, from_ascii("|"))) {
972                         docstring nlocstring;
973                         rsplit(locstring, nlocstring, '|');
974                         locstring = nlocstring;
975                 }
976                 // Remove Qt accelerators, but keep literal ampersands
977                 locstring = subst(locstring, from_ascii(" & "), from_ascii("</amp;>"));
978                 locstring = subst(locstring, from_ascii("&"), docstring());
979                 locstring = subst(locstring, from_ascii("</amp;>"), from_ascii(" & "));
980                 setText(locstring, guilang);
981                 params_.force_ltr = !guilang->rightToLeft() && !params_.lang->rightToLeft();
982                 break;
983         }
984         case InsetInfoParams::ICON_INFO: {
985                 // only need to do this once.
986                 if (initialized_)
987                         break;
988                 // and we will not keep trying if we fail
989                 initialized_ = true;
990                 FuncRequest func = lyxaction.lookupFunc(params_.name);
991                 docstring icon_name = frontend::Application::iconName(func, true);
992                 // FIXME: We should use the icon directly instead of
993                 // going through FileName. The code below won't work
994                 // if the icon is embedded in the executable through
995                 // the Qt resource system.
996                 // This is only a negligible performance problem:
997                 // If the installed icon differs from the resource icon the
998                 // installed one is preferred anyway, and all icons that are
999                 // embedded in the resources are installed as well.
1000                 FileName file(to_utf8(icon_name));
1001                 if (file.onlyFileNameWithoutExt() == "unknown") {
1002                         string dir = "images";
1003                         FileName file2(imageLibFileSearch(dir, params_.name, "svgz,png"));
1004                         if (!file2.empty())
1005                                 file = file2;
1006                 }
1007                 if (!file.exists())
1008                         break;
1009                 int percent_scale = 100;
1010                 if (use_gui) {
1011                         // Compute the scale factor for the icon such that its
1012                         // width on screen is equal to 1em in pixels.
1013                         // The scale factor is rounded to the integer nearest
1014                         // to the float value of the ratio 100*iconsize/imgsize.
1015                         int imgsize = QImage(toqstr(file.absFileName())).width();
1016                         if (imgsize > 0) {
1017                                 int iconsize = Length(1, Length::EM).inPixels(1);
1018                                 percent_scale = (100 * iconsize + imgsize / 2)/imgsize;
1019                         }
1020                 }
1021                 InsetGraphics * inset = new InsetGraphics(buffer_);
1022                 InsetGraphicsParams igp;
1023                 igp.filename = file;
1024                 igp.lyxscale = percent_scale;
1025                 igp.scale = string();
1026                 igp.width = Length(1, Length::EM);
1027                 inset->setParams(igp);
1028                 clear();
1029                 Font const f(inherit_font, params_.lang);
1030                 paragraphs().front().insertInset(0, inset, f,
1031                                                  Change(Change::UNCHANGED));
1032                 break;
1033         }
1034         case InsetInfoParams::BUFFER_INFO: {
1035                 // this could all change, so we will recalculate each time
1036                 if (params_.name == "name")
1037                         setText(from_utf8(buffer().fileName().onlyFileName()), params_.lang);
1038                 else if (params_.name == "name-noext")
1039                         setText(from_utf8(buffer().fileName().onlyFileNameWithoutExt()), params_.lang);
1040                 else if (params_.name == "path")
1041                         setText(from_utf8(os::latex_path(buffer().filePath())), params_.lang);
1042                 else if (params_.name == "class")
1043                         setText(from_utf8(bp.documentClass().name()), params_.lang);
1044                 break;
1045         }
1046         case InsetInfoParams::VCS_INFO: {
1047                 // this information could change, in principle, so we will 
1048                 // recalculate each time through
1049                 if (!buffer().lyxvc().inUse()) {
1050                         gui = _("No version control!");
1051                         info(from_ascii("No version control!"), params_.lang);
1052                         break;
1053                 }
1054                 LyXVC::RevisionInfo itype = LyXVC::Unknown;
1055                 if (params_.name == "revision")
1056                         itype = LyXVC::File;
1057                 else if (params_.name == "tree-revision")
1058                         itype = LyXVC::Tree;
1059                 else if (params_.name == "author")
1060                         itype = LyXVC::Author;
1061                 else if (params_.name == "time")
1062                         itype = LyXVC::Time;
1063                 else if (params_.name == "date")
1064                         itype = LyXVC::Date;
1065                 string binfo = buffer().lyxvc().revisionInfo(itype);
1066                 if (binfo.empty()) {
1067                         gui = _("%1$s[[vcs data]] unknown");
1068                         error(from_ascii("%1$s[[vcs data]] unknown"), params_.lang);
1069                 } else
1070                         setText(from_utf8(binfo), params_.lang);
1071                 break;
1072         }
1073         case InsetInfoParams::LYX_INFO:
1074                 // only need to do this once.
1075                 if (initialized_)
1076                         break;
1077                 if (params_.name == "version")
1078                         setText(from_ascii(lyx_version), params_.lang);
1079                 initialized_ = true;
1080                 break;
1081         case InsetInfoParams::DATE_INFO:
1082         case InsetInfoParams::MODDATE_INFO:
1083         case InsetInfoParams::FIXDATE_INFO: {
1084                 string date_format = params_.name;
1085                 string const date_specifier = (params_.type == InsetInfoParams::FIXDATE_INFO
1086                                                && contains(params_.name, '@'))
1087                                 ? split(params_.name, date_format, '@') : string();
1088                 QDate date;
1089                 if (params_.type == InsetInfoParams::MODDATE_INFO)
1090                         date = QDateTime::fromTime_t(buffer().fileName().lastModified()).date();
1091                 else if (params_.type == InsetInfoParams::FIXDATE_INFO && !date_specifier.empty())
1092                         date = QDate::fromString(toqstr(date_specifier), Qt::ISODate);
1093                 else
1094                         date = QDate::currentDate();
1095                 setText(params_.getDate(date_format, date), params_.lang);
1096                 break;
1097         }
1098         case InsetInfoParams::TIME_INFO:
1099         case InsetInfoParams::MODTIME_INFO:
1100         case InsetInfoParams::FIXTIME_INFO: {
1101                 string time_format = params_.name;
1102                 string const time_specifier = (params_.type == InsetInfoParams::FIXTIME_INFO
1103                                                && contains(params_.name, '@'))
1104                                 ? split(params_.name, time_format, '@') : string();
1105                 QTime time;
1106                 if (params_.type == InsetInfoParams::MODTIME_INFO)
1107                         time = QDateTime::fromTime_t(buffer().fileName().lastModified()).time();
1108                 else if (params_.type == InsetInfoParams::FIXTIME_INFO && !time_specifier.empty())
1109                         time = QTime::fromString(toqstr(time_specifier), Qt::ISODate);
1110                 else
1111                         time = QTime::currentTime();
1112                 setText(params_.getTime(time_format, time), params_.lang);
1113                 break;
1114         }
1115         }
1116
1117         // Just to do something with that string
1118         LYXERR(Debug::INFO, "info inset text: " << gui);
1119         InsetCollapsible::updateBuffer(it, utype);
1120 }
1121
1122
1123 string InsetInfo::contextMenu(BufferView const &, int, int) const
1124 {
1125         //FIXME: We override the implementation of InsetCollapsible,
1126         //because this inset is not a collapsible inset.
1127         return contextMenuName();
1128 }
1129
1130
1131 string InsetInfo::contextMenuName() const
1132 {
1133         return "context-info";
1134 }
1135
1136
1137 } // namespace lyx