]> git.lyx.org Git - lyx.git/blob - src/insets/InsetInfo.cpp
InsetInfo: Move validateArgument() to params
[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 bool InsetInfoParams::validateArgument(Buffer const * buf, docstring const & arg,
334                                        bool const usedefaults) const
335 {
336         string type;
337         string name = trim(split(to_utf8(arg), type, ' '));
338         if (name.empty() && usedefaults)
339                 name = defaultValueTranslator().find(type);
340
341         switch (nameTranslator().find(type)) {
342         case UNKNOWN_INFO:
343                 return false;
344
345         case SHORTCUT_INFO:
346         case SHORTCUTS_INFO:
347         case MENU_INFO: {
348                 FuncRequest func = lyxaction.lookupFunc(name);
349                 return func.action() != LFUN_UNKNOWN_ACTION;
350         }
351
352         case L7N_INFO:
353                 return !name.empty();
354
355         case ICON_INFO: {
356                 FuncCode const action = lyxaction.lookupFunc(name).action();
357                 if (action == LFUN_UNKNOWN_ACTION) {
358                         string dir = "images";
359                         return !imageLibFileSearch(dir, name, "svgz,png").empty();
360                 }
361                 return true;
362         }
363
364         case LYXRC_INFO: {
365                 set<string> rcs = lyxrc.getRCs();
366                 return rcs.find(name) != rcs.end();
367         }
368
369         case PACKAGE_INFO:
370         case TEXTCLASS_INFO:
371                 return true;
372
373         case BUFFER_INFO:
374                 return (name == "name" || name == "name-noext"
375                         || name == "path" || name == "class");
376
377         case VCS_INFO:
378                 if (name == "revision" || name == "tree-revision"
379                     || name == "author" || name == "date" || name == "time")
380                         return buf->lyxvc().inUse();
381                 return false;
382
383         case LYX_INFO:
384                 return name == "version";
385
386         case FIXDATE_INFO: {
387                 string date;
388                 string piece;
389                 date = split(name, piece, '@');
390                 if (!date.empty() && !QDate::fromString(toqstr(date), Qt::ISODate).isValid())
391                         return false;
392                 if (!piece.empty())
393                         name = piece;
394         }
395         // fall through
396         case DATE_INFO:
397         case MODDATE_INFO: {
398                 if (name == "long" || name == "short" || name == "ISO")
399                         return true;
400                 else {
401                         QDate date = QDate::currentDate();
402                         return !date.toString(toqstr(name)).isEmpty();
403                 }
404         }
405         case FIXTIME_INFO: {
406                 string time;
407                 string piece;
408                 time = split(name, piece, '@');
409                 if (!time.empty() && !QTime::fromString(toqstr(time), Qt::ISODate).isValid())
410                         return false;
411                 if (!piece.empty())
412                         name = piece;
413         }
414         // fall through
415         case TIME_INFO:
416         case MODTIME_INFO: {
417                 if (name == "long" || name == "short" || name == "ISO")
418                         return true;
419                 else {
420                         QTime time = QTime::currentTime();
421                         return !time.toString(toqstr(name)).isEmpty();
422                 }
423         }
424         }
425
426         return false;
427 }
428
429
430
431
432 string InsetInfoParams::infoType() const
433 {
434         return nameTranslator().find(type);
435 }
436
437
438
439 /////////////////////////////////////////////////////////////////////////
440 //
441 // InsetInfo
442 //
443 /////////////////////////////////////////////////////////////////////////
444
445
446
447 InsetInfo::InsetInfo(Buffer * buf, string const & name)
448         : InsetCollapsible(buf), initialized_(false)
449 {
450         params_.type = InsetInfoParams::UNKNOWN_INFO;
451         params_.force_ltr = false;
452         setInfo(name);
453         status_ = Collapsed;
454 }
455
456
457 Inset * InsetInfo::editXY(Cursor & cur, int x, int y)
458 {
459         // do not allow the cursor to be set in this Inset
460         return Inset::editXY(cur, x, y);
461 }
462
463
464 docstring InsetInfo::layoutName() const
465 {
466         return from_ascii("Info:" + params_.infoType());
467 }
468
469
470 docstring InsetInfo::toolTip(BufferView const &, int, int) const
471 {
472         docstring result;
473         switch (nameTranslator().find(params_.infoType())) {
474         case InsetInfoParams::UNKNOWN_INFO:
475                 result = _("Invalid information inset");
476                 break;
477         case InsetInfoParams::SHORTCUT_INFO:
478                 result = bformat(_("The keybard shortcut for the function '%1$s'"),
479                                 from_utf8(params_.name));
480                 break;
481         case InsetInfoParams::SHORTCUTS_INFO:
482                 result = bformat(_("The keybard shortcuts for the function '%1$s'"),
483                                 from_utf8(params_.name));
484                 break;
485         case InsetInfoParams::MENU_INFO: 
486                 result = bformat(_("The menu location for the function '%1$s'"),
487                                 from_utf8(params_.name));
488                 break;
489         case InsetInfoParams::L7N_INFO: 
490                 result = bformat(_("The localization for the string '%1$s'"),
491                                 from_utf8(params_.name));
492                 break;
493         case InsetInfoParams::ICON_INFO:
494                 result = bformat(_("The toolbar icon for the function '%1$s'"),
495                                 from_utf8(params_.name));
496                 break;
497         case InsetInfoParams::LYXRC_INFO:
498                 result = bformat(_("The preference setting for the preference key '%1$s'"),
499                                 from_utf8(params_.name));
500                 break;
501         case InsetInfoParams::PACKAGE_INFO:
502                 result = bformat(_("Availability of the LaTeX package '%1$s'"),
503                                 from_utf8(params_.name));
504                 break;
505         case InsetInfoParams::TEXTCLASS_INFO:
506                 result = bformat(_("Availability of the LaTeX class '%1$s'"),
507                                 from_utf8(params_.name));
508                 break;
509         case InsetInfoParams::BUFFER_INFO:
510                 if (params_.name == "name")
511                         result = _("The name of this file (incl. extension)");
512                 else if (params_.name == "name-noext")
513                         result = _("The name of this file (without extension)");
514                 else if (params_.name == "path")
515                         result = _("The path where this file is saved");
516                 else if (params_.name == "class")
517                         result = _("The class this document uses");
518                 break;
519         case InsetInfoParams::VCS_INFO:
520                 if (params_.name == "revision")
521                         result = _("Version control revision");
522                 else if (params_.name == "tree-revision")
523                         result = _("Version control tree revision");
524                 else if (params_.name == "author")
525                          result = _("Version control author");
526                 else if (params_.name == "date")
527                         result = _("Version control date");
528                 else if (params_.name == "time")
529                         result = _("Version control time");
530                 break;
531         case InsetInfoParams::LYX_INFO:
532                 result = _("The current LyX version");
533                 break;
534         case InsetInfoParams::DATE_INFO:
535                 result = _("The current date");
536                 break;
537         case InsetInfoParams::MODDATE_INFO:
538                 result = _("The date of last save");
539                 break;
540         case InsetInfoParams::FIXDATE_INFO:
541                 result = _("A static date");
542                 break;
543         case InsetInfoParams::TIME_INFO:
544                 result = _("The current time");
545                 break;
546         case InsetInfoParams::MODTIME_INFO:
547                 result = _("The time of last save");
548                 break;
549         case InsetInfoParams::FIXTIME_INFO:
550                 result = _("A static time");
551                 break;
552         }
553
554         return result;
555 }
556
557
558 void InsetInfo::read(Lexer & lex)
559 {
560         string token;
561         while (lex.isOK()) {
562                 lex.next();
563                 token = lex.getString();
564                 if (token == "type") {
565                         lex.next();
566                         token = lex.getString();
567                         params_.type = nameTranslator().find(token);
568                 } else if (token == "arg") {
569                         lex.next(true);
570                         params_.name = lex.getString();
571                 } else if (token == "\\end_inset")
572                         break;
573         }
574         if (token != "\\end_inset") {
575                 lex.printError("Missing \\end_inset at this point");
576                 throw ExceptionMessage(WarningException,
577                         _("Missing \\end_inset at this point."),
578                         from_utf8(token));
579         }
580 }
581
582
583 void InsetInfo::write(ostream & os) const
584 {
585         os << "Info\ntype  \"" << params_.infoType()
586            << "\"\narg   " << Lexer::quoteString(params_.name);
587 }
588
589
590 bool InsetInfo::showInsetDialog(BufferView * bv) const
591 {
592         bv->showDialog("info");
593         return true;
594 }
595
596
597 bool InsetInfo::getStatus(Cursor & cur, FuncRequest const & cmd,
598                 FuncStatus & flag) const
599 {
600         switch (cmd.action()) {
601         case LFUN_INSET_SETTINGS:
602                 return InsetCollapsible::getStatus(cur, cmd, flag);
603
604         case LFUN_INSET_DIALOG_UPDATE:
605         case LFUN_INSET_COPY_AS:
606         case LFUN_INSET_DISSOLVE:
607                 flag.setEnabled(true);
608                 return true;
609
610         case LFUN_INSET_MODIFY:
611                 if (params_.validateArgument(&buffer(), cmd.argument())) {
612                         flag.setEnabled(true);
613                         string typestr;
614                         string name = trim(split(to_utf8(cmd.argument()), typestr, ' '));
615                         InsetInfoParams::info_type type = nameTranslator().find(typestr);
616                         string origname = params_.name;
617                         if (type == InsetInfoParams::FIXDATE_INFO
618                             || type == InsetInfoParams::FIXTIME_INFO)
619                                 split(params_.name, origname, '@');
620                         flag.setOnOff(type == params_.type && name == origname);
621                         return true;
622                 }
623                 //fall through
624
625         default:
626                 return false;
627         }
628 }
629
630
631 void InsetInfo::doDispatch(Cursor & cur, FuncRequest & cmd)
632 {
633         switch (cmd.action()) {
634         case LFUN_INSET_MODIFY:
635                 cur.recordUndo();
636                 setInfo(to_utf8(cmd.argument()));
637                 cur.forceBufferUpdate();
638                 initialized_ = false;
639                 break;
640
641         case LFUN_INSET_COPY_AS: {
642                 cap::clearSelection();
643                 Cursor copy(cur);
644                 copy.pushBackward(*this);
645                 copy.pit() = 0;
646                 copy.pos() = 0;
647                 copy.resetAnchor();
648                 copy.pit() = copy.lastpit();
649                 copy.pos() = copy.lastpos();
650                 copy.setSelection();
651                 cap::copySelection(copy);
652                 break;
653         }
654
655         default:
656                 InsetCollapsible::doDispatch(cur, cmd);
657                 break;
658         }
659 }
660
661
662 void InsetInfo::setInfo(string const & name)
663 {
664         if (name.empty())
665                 return;
666
667         string saved_date_specifier;
668         // Store old date specifier for potential re-use
669         if (!params_.name.empty())
670                 saved_date_specifier = split(params_.name, '@');
671         // info_type name
672         string type;
673         params_.name = trim(split(name, type, ' '));
674         params_.type = nameTranslator().find(type);
675         if (params_.name.empty())
676                 params_.name = defaultValueTranslator().find(params_.type);
677         if (params_.type == InsetInfoParams::FIXDATE_INFO) {
678                 string const date_specifier = split(params_.name, '@');
679                 // If an explicit new fix date is specified, use that
680                 // Otherwise, use the old one or, if there is none,
681                 // the current date
682                 if (date_specifier.empty()) {
683                         if (saved_date_specifier.empty())
684                                 params_.name += "@" + fromqstr(QDate::currentDate().toString(Qt::ISODate));
685                         else
686                                 params_.name += "@" + saved_date_specifier;
687                 }
688         }
689         else if (params_.type == InsetInfoParams::FIXTIME_INFO) {
690                 string const time_specifier = split(params_.name, '@');
691                 // If an explicit new fix time is specified, use that
692                 // Otherwise, use the old one or, if there is none,
693                 // the current time
694                 if (time_specifier.empty()) {
695                         if (saved_date_specifier.empty())
696                                 params_.name += "@" + fromqstr(QTime::currentTime().toString(Qt::ISODate));
697                         else
698                                 params_.name += "@" + saved_date_specifier;
699                 }
700         }
701 }
702
703
704 void InsetInfo::error(docstring const & err, Language const * lang)
705 {
706         setText(bformat(translateIfPossible(err, lang->code()), from_utf8(params_.name)),
707                 Font(inherit_font, lang), false);
708 }
709
710
711 void InsetInfo::info(docstring const & err, Language const * lang)
712 {
713         setText(translateIfPossible(err, lang->code()),
714                         Font(inherit_font, lang), false);
715 }
716
717
718 void InsetInfo::setText(docstring const & str, Language const * lang)
719 {
720         setText(str, Font(inherit_font, lang), false);
721 }
722
723
724 bool InsetInfo::forceLTR() const
725 {
726         return params_.force_ltr;
727 }
728
729
730 void InsetInfo::updateBuffer(ParIterator const & it, UpdateType utype) {
731         // If the Buffer is a clone, then we neither need nor want to do any
732         // of what follows. We want, rather, just to inherit how things were
733         // in the original Buffer. This is especially important for VCS.
734         // Otherwise, we could in principle have different settings here
735         // than in the Buffer we were exporting.
736         if (buffer().isClone())
737                 return;
738
739         BufferParams const & bp = buffer().params();
740         params_.lang = it.paragraph().getFontSettings(bp, it.pos()).language();
741         Language const * tryguilang = languages.getFromCode(Messages::guiLanguage());
742         // Some info insets use the language of the GUI (if available)
743         Language const * guilang = tryguilang ? tryguilang : params_.lang;
744
745         params_.force_ltr = !params_.lang->rightToLeft();
746         // This is just to get the string into the po files
747         docstring gui;
748         switch (params_.type) {
749         case InsetInfoParams::UNKNOWN_INFO:
750                 gui = _("Unknown Info!");
751                 info(from_ascii("Unknown Info!"), params_.lang);
752                 initialized_ = false;
753                 break;
754         case InsetInfoParams::SHORTCUT_INFO:
755         case InsetInfoParams::SHORTCUTS_INFO: {
756                 // shortcuts can change, so we need to re-do this each time
757                 FuncRequest const func = lyxaction.lookupFunc(params_.name);
758                 if (func.action() == LFUN_UNKNOWN_ACTION) {
759                         gui = _("Unknown action %1$s");
760                         error(from_ascii("Unknown action %1$s"), params_.lang);
761                         break;
762                 }
763                 KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(func);
764                 if (bindings.empty()) {
765                         gui = _("undefined");
766                         info(from_ascii("undefined"), params_.lang);
767                         break;
768                 }
769                 docstring sequence;
770                 if (params_.type == InsetInfoParams::SHORTCUT_INFO)
771                         sequence = bindings.begin()->print(KeySequence::ForGui);
772                 else
773                         sequence = theTopLevelKeymap().printBindings(func, KeySequence::ForGui);
774                 // QKeySequence returns special characters for keys on the mac
775                 // Since these are not included in many fonts, we
776                 // re-translate them to textual names (see #10641)
777                 odocstringstream ods;
778                 string const lcode = params_.lang->code();
779                 for (size_t n = 0; n < sequence.size(); ++n) {
780                         char_type const c = sequence[n];
781                         switch(c) {
782                         case 0x21b5://Return
783                                 gui = _("Return[[Key]]");
784                                 ods << translateIfPossible(from_ascii("Return[[Key]]"), lcode);
785                                 break;
786                         case 0x21b9://Tab both directions (Win)
787                                 gui = _("Tab[[Key]]");
788                                 ods << translateIfPossible(from_ascii("Tab[[Key]]"), lcode);
789                                 break;
790                         case 0x21de://Qt::Key_PageUp
791                                 gui = _("PgUp");
792                                 ods << translateIfPossible(from_ascii("PgUp"), lcode);
793                                 break;
794                         case 0x21df://Qt::Key_PageDown
795                                 gui = _("PgDown");
796                                 ods << translateIfPossible(from_ascii("PgDown"), lcode);
797                                 break;
798                         case 0x21e4://Qt::Key_Backtab
799                                 gui = _("Backtab");
800                                 ods << translateIfPossible(from_ascii("Backtab"), lcode);
801                                 break;
802                         case 0x21e5://Qt::Key_Tab
803                                 gui = _("Tab");
804                                 ods << translateIfPossible(from_ascii("Tab"), lcode);
805                                 break;
806                         case 0x21e7://Shift
807                                 gui = _("Shift");
808                                 ods << translateIfPossible(from_ascii("Shift"), lcode);
809                                 break;
810                         case 0x21ea://Qt::Key_CapsLock
811                                 gui = _("CapsLock");
812                                 ods << translateIfPossible(from_ascii("CapsLock"), lcode);
813                                 break;
814                         case 0x2303://Control
815                                 gui = _("Control[[Key]]");
816                                 ods << translateIfPossible(from_ascii("Control[[Key]]"), lcode);
817                                 break;
818                         case 0x2318://CMD
819                                 gui = _("Command[[Key]]");
820                                 ods << translateIfPossible(from_ascii("Command[[Key]]"), lcode);
821                                 break;
822                         case 0x2324://Qt::Key_Enter
823                                 gui = _("Return[[Key]]");
824                                 ods << translateIfPossible(from_ascii("Return[[Key]]"), lcode);
825                                 break;
826                         case 0x2325://Option key
827                                 gui = _("Option[[Key]]");
828                                 ods << translateIfPossible(from_ascii("Option[[Key]]"), lcode);
829                                 break;
830                         case 0x2326://Qt::Key_Delete
831                                 gui = _("Delete[[Key]]");
832                                 ods << translateIfPossible(from_ascii("Delete[[Key]]"), lcode);
833                                 break;
834                         case 0x232b://Qt::Key_Backspace
835                                 gui = _("Fn+Del");
836                                 ods << translateIfPossible(from_ascii("Fn+Delete"), lcode);
837                                 break;
838                         case 0x238b://Qt::Key_Escape
839                                 gui = _("Esc");
840                                 ods << translateIfPossible(from_ascii("Esc"), lcode);
841                                 break;
842                         default:
843                                 ods.put(c);
844                         }
845                 }
846                 setText(ods.str(), guilang);
847                 params_.force_ltr = !guilang->rightToLeft() && !params_.lang->rightToLeft();
848                 break;
849         }
850         case InsetInfoParams::LYXRC_INFO: {
851                 // this information could change, if the preferences are changed,
852                 // so we will recalculate each time through.
853                 ostringstream oss;
854                 if (params_.name.empty()) {
855                         gui = _("undefined");
856                         info(from_ascii("undefined"), params_.lang);
857                         break;
858                 }
859                 // FIXME this uses the serialization mechanism to get the info
860                 // we want, which i guess works but is a bit strange.
861                 lyxrc.write(oss, true, params_.name);
862                 string result = oss.str();
863                 if (result.size() < 2) {
864                         gui = _("undefined");
865                         info(from_ascii("undefined"), params_.lang);
866                         break;
867                 }
868                 string::size_type loc = result.rfind("\n", result.size() - 2);
869                 loc = loc == string::npos ? 0 : loc + 1;
870                 if (result.size() < loc + params_.name.size() + 1
871                           || result.substr(loc + 1, params_.name.size()) != params_.name) {
872                         gui = _("undefined");
873                         info(from_ascii("undefined"), params_.lang);
874                         break;
875                 }
876                 // remove leading comments and \\name and space
877                 result = result.substr(loc + params_.name.size() + 2);
878
879                 // remove \n and ""
880                 result = rtrim(result, "\n");
881                 result = trim(result, "\"");
882                 gui = _("not set");
883                 if (result.empty())
884                         result = "not set";
885                 setText(from_utf8(result), params_.lang);
886                 break;
887         }
888         case InsetInfoParams::PACKAGE_INFO:
889                 // only need to do this once.
890                 if (initialized_)
891                         break;
892                 // check in packages.lst
893                 if (LaTeXFeatures::isAvailable(params_.name)) {
894                         gui = _("yes");
895                         info(from_ascii("yes"), params_.lang);
896                 } else {
897                         gui = _("no");
898                         info(from_ascii("no"), params_.lang);
899                 }
900                 initialized_ = true;
901                 break;
902
903         case InsetInfoParams::TEXTCLASS_INFO: {
904                 // the TextClass can change
905                 LayoutFileList const & list = LayoutFileList::get();
906                 bool available = false;
907                 // params_.name is the class name
908                 if (list.haveClass(params_.name))
909                         available = list[params_.name].isTeXClassAvailable();
910                 if (available) {
911                         gui = _("yes");
912                         info(from_ascii("yes"), params_.lang);
913                 } else {
914                         gui = _("no");
915                         info(from_ascii("no"), params_.lang);
916                 }
917                 break;
918         }
919         case InsetInfoParams::MENU_INFO: {
920                 // only need to do this once.
921                 if (initialized_)
922                         break;
923                 docstring_list names;
924                 FuncRequest func = lyxaction.lookupFunc(params_.name);
925                 if (func.action() == LFUN_UNKNOWN_ACTION) {
926                         gui = _("Unknown action %1$s");
927                         error(from_ascii("Unknown action %1$s"), params_.lang);
928                         break;
929                 }
930                 if (func.action() == LFUN_BUFFER_VIEW || func.action() == LFUN_BUFFER_UPDATE)
931                         // The default output format is in the menu without argument,
932                         // so strip it here.
933                         if (func.argument() == from_ascii(buffer().params().getDefaultOutputFormat()))
934                                 func = FuncRequest(func.action());
935                 // iterate through the menubackend to find it
936                 if (!theApp()) {
937                         gui = _("Can't determine menu entry for action %1$s in batch mode");
938                         error(from_ascii("Can't determine menu entry for action %1$s in batch mode"), params_.lang);
939                         initialized_ = true;
940                         break;
941                 }
942                 // and we will not keep trying if we fail
943                 initialized_ = theApp()->hasBufferView();
944                 if (!theApp()->searchMenu(func, names)) {
945                         gui = _("No menu entry for action %1$s");
946                         error(from_ascii("No menu entry for action %1$s"), params_.lang);
947                         break;
948                 }
949                 // if found, return its path.
950                 clear();
951                 Paragraph & par = paragraphs().front();
952                 Font const f(inherit_font, guilang);
953                 params_.force_ltr = !guilang->rightToLeft();
954                 //Font fu = f;
955                 //fu.fontInfo().setUnderbar(FONT_ON);
956                 for (docstring const & name : names) {
957                         // do not insert > for the top level menu item
958                         if (&name != &names.front())
959                                 par.insertInset(par.size(), new InsetSpecialChar(InsetSpecialChar::MENU_SEPARATOR),
960                                                 f, Change(Change::UNCHANGED));
961                         //FIXME: add proper underlines here. This
962                         // involves rewriting searchMenu used above to
963                         // return a vector of menus. If we do not do
964                         // that, we might as well use below
965                         // Paragraph::insert on each string (JMarc)
966                         for (char_type c : name)
967                                 par.insertChar(par.size(), c, f, Change(Change::UNCHANGED));
968                 }
969                 break;
970         }
971         case InsetInfoParams::L7N_INFO: {
972                 docstring locstring = _(params_.name);
973                 // Remove trailing colons
974                 locstring = rtrim(locstring, ":");
975                 // Remove menu accelerators
976                 if (contains(locstring, from_ascii("|"))) {
977                         docstring nlocstring;
978                         rsplit(locstring, nlocstring, '|');
979                         locstring = nlocstring;
980                 }
981                 // Remove Qt accelerators, but keep literal ampersands
982                 locstring = subst(locstring, from_ascii(" & "), from_ascii("</amp;>"));
983                 locstring = subst(locstring, from_ascii("&"), docstring());
984                 locstring = subst(locstring, from_ascii("</amp;>"), from_ascii(" & "));
985                 setText(locstring, guilang);
986                 params_.force_ltr = !guilang->rightToLeft() && !params_.lang->rightToLeft();
987                 break;
988         }
989         case InsetInfoParams::ICON_INFO: {
990                 // only need to do this once.
991                 if (initialized_)
992                         break;
993                 // and we will not keep trying if we fail
994                 initialized_ = true;
995                 FuncRequest func = lyxaction.lookupFunc(params_.name);
996                 docstring icon_name = frontend::Application::iconName(func, true);
997                 // FIXME: We should use the icon directly instead of
998                 // going through FileName. The code below won't work
999                 // if the icon is embedded in the executable through
1000                 // the Qt resource system.
1001                 // This is only a negligible performance problem:
1002                 // If the installed icon differs from the resource icon the
1003                 // installed one is preferred anyway, and all icons that are
1004                 // embedded in the resources are installed as well.
1005                 FileName file(to_utf8(icon_name));
1006                 if (file.onlyFileNameWithoutExt() == "unknown") {
1007                         string dir = "images";
1008                         FileName file2(imageLibFileSearch(dir, params_.name, "svgz,png"));
1009                         if (!file2.empty())
1010                                 file = file2;
1011                 }
1012                 if (!file.exists())
1013                         break;
1014                 int percent_scale = 100;
1015                 if (use_gui) {
1016                         // Compute the scale factor for the icon such that its
1017                         // width on screen is equal to 1em in pixels.
1018                         // The scale factor is rounded to the integer nearest
1019                         // to the float value of the ratio 100*iconsize/imgsize.
1020                         int imgsize = QImage(toqstr(file.absFileName())).width();
1021                         if (imgsize > 0) {
1022                                 int iconsize = Length(1, Length::EM).inPixels(1);
1023                                 percent_scale = (100 * iconsize + imgsize / 2)/imgsize;
1024                         }
1025                 }
1026                 InsetGraphics * inset = new InsetGraphics(buffer_);
1027                 InsetGraphicsParams igp;
1028                 igp.filename = file;
1029                 igp.lyxscale = percent_scale;
1030                 igp.scale = string();
1031                 igp.width = Length(1, Length::EM);
1032                 inset->setParams(igp);
1033                 clear();
1034                 Font const f(inherit_font, params_.lang);
1035                 paragraphs().front().insertInset(0, inset, f,
1036                                                  Change(Change::UNCHANGED));
1037                 break;
1038         }
1039         case InsetInfoParams::BUFFER_INFO: {
1040                 // this could all change, so we will recalculate each time
1041                 if (params_.name == "name")
1042                         setText(from_utf8(buffer().fileName().onlyFileName()), params_.lang);
1043                 else if (params_.name == "name-noext")
1044                         setText(from_utf8(buffer().fileName().onlyFileNameWithoutExt()), params_.lang);
1045                 else if (params_.name == "path")
1046                         setText(from_utf8(os::latex_path(buffer().filePath())), params_.lang);
1047                 else if (params_.name == "class")
1048                         setText(from_utf8(bp.documentClass().name()), params_.lang);
1049                 break;
1050         }
1051         case InsetInfoParams::VCS_INFO: {
1052                 // this information could change, in principle, so we will 
1053                 // recalculate each time through
1054                 if (!buffer().lyxvc().inUse()) {
1055                         gui = _("No version control!");
1056                         info(from_ascii("No version control!"), params_.lang);
1057                         break;
1058                 }
1059                 LyXVC::RevisionInfo itype = LyXVC::Unknown;
1060                 if (params_.name == "revision")
1061                         itype = LyXVC::File;
1062                 else if (params_.name == "tree-revision")
1063                         itype = LyXVC::Tree;
1064                 else if (params_.name == "author")
1065                         itype = LyXVC::Author;
1066                 else if (params_.name == "time")
1067                         itype = LyXVC::Time;
1068                 else if (params_.name == "date")
1069                         itype = LyXVC::Date;
1070                 string binfo = buffer().lyxvc().revisionInfo(itype);
1071                 if (binfo.empty()) {
1072                         gui = _("%1$s[[vcs data]] unknown");
1073                         error(from_ascii("%1$s[[vcs data]] unknown"), params_.lang);
1074                 } else
1075                         setText(from_utf8(binfo), params_.lang);
1076                 break;
1077         }
1078         case InsetInfoParams::LYX_INFO:
1079                 // only need to do this once.
1080                 if (initialized_)
1081                         break;
1082                 if (params_.name == "version")
1083                         setText(from_ascii(lyx_version), params_.lang);
1084                 initialized_ = true;
1085                 break;
1086         case InsetInfoParams::DATE_INFO:
1087         case InsetInfoParams::MODDATE_INFO:
1088         case InsetInfoParams::FIXDATE_INFO: {
1089                 string date_format = params_.name;
1090                 string const date_specifier = (params_.type == InsetInfoParams::FIXDATE_INFO
1091                                                && contains(params_.name, '@'))
1092                                 ? split(params_.name, date_format, '@') : string();
1093                 QDate date;
1094                 if (params_.type == InsetInfoParams::MODDATE_INFO)
1095                         date = QDateTime::fromTime_t(buffer().fileName().lastModified()).date();
1096                 else if (params_.type == InsetInfoParams::FIXDATE_INFO && !date_specifier.empty())
1097                         date = QDate::fromString(toqstr(date_specifier), Qt::ISODate);
1098                 else
1099                         date = QDate::currentDate();
1100                 setText(params_.getDate(date_format, date), params_.lang);
1101                 break;
1102         }
1103         case InsetInfoParams::TIME_INFO:
1104         case InsetInfoParams::MODTIME_INFO:
1105         case InsetInfoParams::FIXTIME_INFO: {
1106                 string time_format = params_.name;
1107                 string const time_specifier = (params_.type == InsetInfoParams::FIXTIME_INFO
1108                                                && contains(params_.name, '@'))
1109                                 ? split(params_.name, time_format, '@') : string();
1110                 QTime time;
1111                 if (params_.type == InsetInfoParams::MODTIME_INFO)
1112                         time = QDateTime::fromTime_t(buffer().fileName().lastModified()).time();
1113                 else if (params_.type == InsetInfoParams::FIXTIME_INFO && !time_specifier.empty())
1114                         time = QTime::fromString(toqstr(time_specifier), Qt::ISODate);
1115                 else
1116                         time = QTime::currentTime();
1117                 setText(params_.getTime(time_format, time), params_.lang);
1118                 break;
1119         }
1120         }
1121
1122         // Just to do something with that string
1123         LYXERR(Debug::INFO, "info inset text: " << gui);
1124         InsetCollapsible::updateBuffer(it, utype);
1125 }
1126
1127
1128 string InsetInfo::contextMenu(BufferView const &, int, int) const
1129 {
1130         //FIXME: We override the implementation of InsetCollapsible,
1131         //because this inset is not a collapsible inset.
1132         return contextMenuName();
1133 }
1134
1135
1136 string InsetInfo::contextMenuName() const
1137 {
1138         return "context-info";
1139 }
1140
1141
1142 } // namespace lyx