3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Jürgen Spitzmüller
9 * Full author contact details are available in file CREDITS.
13 #include "InsetInfo.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
18 #include "CutAndPaste.h"
20 #include "FuncRequest.h"
21 #include "FuncStatus.h"
22 #include "InsetGraphics.h"
23 #include "InsetSpecialChar.h"
25 #include "LaTeXFeatures.h"
27 #include "LayoutFile.h"
29 #include "LyXAction.h"
33 #include "Paragraph.h"
34 #include "ParIterator.h"
35 #include "ParagraphParameters.h"
38 #include "frontends/Application.h"
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"
55 #include <QtGui/QImage>
60 using namespace lyx::support;
66 typedef Translator<InsetInfo::info_type, string> NameTranslator;
68 NameTranslator const initTranslator()
70 NameTranslator translator(InsetInfo::UNKNOWN_INFO, "unknown");
72 translator.addPair(InsetInfo::SHORTCUTS_INFO, "shortcuts");
73 translator.addPair(InsetInfo::SHORTCUT_INFO, "shortcut");
74 translator.addPair(InsetInfo::LYXRC_INFO, "lyxrc");
75 translator.addPair(InsetInfo::PACKAGE_INFO, "package");
76 translator.addPair(InsetInfo::TEXTCLASS_INFO, "textclass");
77 translator.addPair(InsetInfo::MENU_INFO, "menu");
78 translator.addPair(InsetInfo::ICON_INFO, "icon");
79 translator.addPair(InsetInfo::BUFFER_INFO, "buffer");
80 translator.addPair(InsetInfo::LYX_INFO, "lyxinfo");
81 translator.addPair(InsetInfo::VCS_INFO, "vcs");
82 translator.addPair(InsetInfo::DATE_INFO, "date");
83 translator.addPair(InsetInfo::MODDATE_INFO, "moddate");
84 translator.addPair(InsetInfo::FIXDATE_INFO, "fixdate");
89 /// The translator between the information type enum and corresponding string.
90 NameTranslator const & nameTranslator()
92 static NameTranslator const translator = initTranslator();
97 typedef Translator<InsetInfo::info_type, string> DefaultValueTranslator;
99 DefaultValueTranslator const initDVTranslator()
101 DefaultValueTranslator translator(InsetInfo::UNKNOWN_INFO, "");
103 translator.addPair(InsetInfo::SHORTCUTS_INFO, "info-insert");
104 translator.addPair(InsetInfo::SHORTCUT_INFO, "info-insert");
105 translator.addPair(InsetInfo::LYXRC_INFO, "user_name");
106 translator.addPair(InsetInfo::PACKAGE_INFO, "graphics");
107 translator.addPair(InsetInfo::TEXTCLASS_INFO, "article");
108 translator.addPair(InsetInfo::MENU_INFO, "info-insert");
109 translator.addPair(InsetInfo::ICON_INFO, "info-insert");
110 translator.addPair(InsetInfo::BUFFER_INFO, "name");
111 translator.addPair(InsetInfo::LYX_INFO, "version");
112 translator.addPair(InsetInfo::VCS_INFO, "revision");
113 translator.addPair(InsetInfo::DATE_INFO, "loclong");
114 translator.addPair(InsetInfo::MODDATE_INFO, "loclong");
115 translator.addPair(InsetInfo::FIXDATE_INFO, "loclong");
120 /// The translator between the information type enum and some sensible default value.
121 DefaultValueTranslator const & defaultValueTranslator()
123 static DefaultValueTranslator const translator = initDVTranslator();
129 /////////////////////////////////////////////////////////////////////////
133 /////////////////////////////////////////////////////////////////////////
137 InsetInfo::InsetInfo(Buffer * buf, string const & name)
138 : InsetCollapsible(buf), initialized_(false),
139 type_(UNKNOWN_INFO), name_(), force_ltr_(false)
146 Inset * InsetInfo::editXY(Cursor & cur, int x, int y)
148 // do not allow the cursor to be set in this Inset
149 return Inset::editXY(cur, x, y);
153 string InsetInfo::infoType() const
155 return nameTranslator().find(type_);
159 docstring InsetInfo::layoutName() const
161 return from_ascii("Info:" + infoType());
165 docstring InsetInfo::toolTip(BufferView const &, int, int) const
168 switch (nameTranslator().find(infoType())) {
170 result = _("Invalid information inset");
173 result = bformat(_("The keybard shortcut for the function '%1$s'"),
177 result = bformat(_("The keybard shortcuts for the function '%1$s'"),
181 result = bformat(_("The menu location for the function '%1$s'"),
185 result = bformat(_("The toolbar icon for the function '%1$s'"),
189 result = bformat(_("The preference setting for the preference key '%1$s'"),
193 result = bformat(_("Availability of the LaTeX package '%1$s'"),
197 result = bformat(_("Availability of the LaTeX class '%1$s'"),
202 result = _("The name of this file");
203 else if (name_ == "path")
204 result = _("The path where this file is saved");
205 else if (name_ == "class")
206 result = _("The class this document uses");
209 if (name_ == "revision")
210 result = _("Version control revision");
211 else if (name_ == "tree-revision")
212 result = _("Version control tree revision");
213 else if (name_ == "author")
214 result = _("Version control author");
215 else if (name_ == "date")
216 result = _("Version control date");
217 else if (name_ == "time")
218 result = _("Version control time");
221 result = _("The current LyX version");
224 result = _("The current date");
227 result = _("The date of last save");
230 result = _("A static date");
238 void InsetInfo::read(Lexer & lex)
243 token = lex.getString();
244 if (token == "type") {
246 token = lex.getString();
247 type_ = nameTranslator().find(token);
248 } else if (token == "arg") {
250 name_ = lex.getString();
251 } else if (token == "\\end_inset")
254 if (token != "\\end_inset") {
255 lex.printError("Missing \\end_inset at this point");
256 throw ExceptionMessage(WarningException,
257 _("Missing \\end_inset at this point."),
263 void InsetInfo::write(ostream & os) const
265 os << "Info\ntype \"" << infoType()
266 << "\"\narg " << Lexer::quoteString(name_);
270 bool InsetInfo::validateModifyArgument(docstring const & arg) const
273 string name = trim(split(to_utf8(arg), type, ' '));
275 switch (nameTranslator().find(type)) {
282 FuncRequest func = lyxaction.lookupFunc(name);
283 return func.action() != LFUN_UNKNOWN_ACTION;
287 FuncCode const action = lyxaction.lookupFunc(name).action();
288 if (action == LFUN_UNKNOWN_ACTION) {
289 string dir = "images";
290 return !imageLibFileSearch(dir, name, "svgz,png").empty();
296 set<string> rcs = lyxrc.getRCs();
297 return rcs.find(name) != rcs.end();
305 return (name == "name" || name == "path" || name == "class");
308 if (name == "revision" || name == "tree-revision"
309 || name == "author" || name == "date" || name == "time")
310 return buffer().lyxvc().inUse();
314 return name == "version";
319 date = split(name, piece, '@');
320 if (!date.empty() && !QDate::fromString(toqstr(date), Qt::ISODate).isValid())
328 if (name == "long" || name == "short" || name == "ISO")
331 QDate date = QDate::currentDate();
332 return !date.toString(toqstr(name)).isEmpty();
342 set<string> getTexFileList(string const & filename)
345 FileName const file = libFileSearch(string(), filename);
350 vector<docstring> doclist =
351 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
353 // Normalise paths like /foo//bar ==> /foo/bar
354 for (auto doc : doclist) {
355 subst(doc, from_ascii("\r"), docstring());
356 while (contains(doc, from_ascii("//")))
357 subst(doc, from_ascii("//"), from_ascii("/"));
359 list.insert(removeExtension(onlyFileName(to_utf8(doc))));
368 docstring InsetInfo::getDate(string const name, QDate const date) const
372 loc = QLocale(toqstr(lang_->code()));
374 return qstring_to_ucs4(loc.toString(date, QLocale::LongFormat));
375 else if (name == "short")
376 return qstring_to_ucs4(loc.toString(date, QLocale::ShortFormat));
377 else if (name == "ISO")
378 return qstring_to_ucs4(date.toString(Qt::ISODate));
379 else if (name == "loclong")
380 return qstring_to_ucs4(loc.toString(date, toqstr(lang_->dateFormat(0))));
381 else if (name == "locmedium")
382 return qstring_to_ucs4(loc.toString(date, toqstr(lang_->dateFormat(1))));
383 else if (name == "locshort")
384 return qstring_to_ucs4(loc.toString(date, toqstr(lang_->dateFormat(2))));
386 return qstring_to_ucs4(loc.toString(date, toqstr(name)));
390 vector<pair<string,docstring>> InsetInfo::getArguments(string const & type) const
392 vector<pair<string,docstring>> result;
394 switch (nameTranslator().find(type)) {
396 result.push_back(make_pair("invalid", _("Please select a valid type!")));
403 result.push_back(make_pair("custom", _("Custom")));
404 LyXAction::const_iterator fit = lyxaction.func_begin();
405 LyXAction::const_iterator const fen = lyxaction.func_end();
406 for (; fit != fen; ++fit) {
407 string const lfun = fit->first;
409 result.push_back(make_pair(lfun, from_ascii(lfun)));
415 result.push_back(make_pair("custom", _("Custom")));
416 set<string> rcs = lyxrc.getRCs();
417 for (auto const & rc : rcs)
418 result.push_back(make_pair(rc, from_ascii(rc)));
423 case TEXTCLASS_INFO: {
424 result.push_back(make_pair("custom", _("Custom")));
425 string const filename = (type == "package") ? "styFiles.lst"
427 set<string> flist = getTexFileList(filename);
428 for (auto const & f : flist)
429 result.push_back(make_pair(f, from_utf8(f)));
434 result.push_back(make_pair("name", _("File name")));
435 result.push_back(make_pair("path", _("File path")));
436 result.push_back(make_pair("class", _("Used text class")));
440 if (!buffer().lyxvc().inUse()) {
441 result.push_back(make_pair("invalid", _("No version control!")));
444 result.push_back(make_pair("revision", _("Revision[[Version Control]]")));
445 result.push_back(make_pair("tree-revision", _("Tree revision")));
446 result.push_back(make_pair("author", _("Author")));
447 result.push_back(make_pair("date", _("Date")));
448 result.push_back(make_pair("time", _("Time")));
453 result.push_back(make_pair("version", _("LyX version")));
459 string const dt = split(name_, '@');
461 if (type == "moddate")
462 date = QDateTime::fromTime_t(buffer().fileName().lastModified()).date();
463 else if (type == "fixdate" && !dt.empty())
464 date = QDate::fromString(toqstr(dt), Qt::ISODate);
466 date = QDate::currentDate();
467 result.push_back(make_pair("long",getDate("long", date)));
468 result.push_back(make_pair("short", getDate("short", date)));
469 result.push_back(make_pair("loclong", getDate("loclong", date)));
470 result.push_back(make_pair("locmedium", getDate("locmedium", date)));
471 result.push_back(make_pair("locshort", getDate("locshort", date)));
472 result.push_back(make_pair("ISO", getDate("ISO", date)));
473 result.push_back(make_pair("yyyy", getDate("yyyy", date)));
474 result.push_back(make_pair("MMMM", getDate("MMMM", date)));
475 result.push_back(make_pair("MMM", getDate("MMM", date)));
476 result.push_back(make_pair("dddd", getDate("dddd", date)));
477 result.push_back(make_pair("ddd", getDate("ddd", date)));
478 result.push_back(make_pair("custom", _("Custom")));
486 bool InsetInfo::showInsetDialog(BufferView * bv) const
488 bv->showDialog("info");
493 bool InsetInfo::getStatus(Cursor & cur, FuncRequest const & cmd,
494 FuncStatus & flag) const
496 switch (cmd.action()) {
497 case LFUN_INSET_SETTINGS:
498 return InsetCollapsible::getStatus(cur, cmd, flag);
500 case LFUN_INSET_DIALOG_UPDATE:
501 case LFUN_INSET_COPY_AS:
502 flag.setEnabled(true);
505 case LFUN_INSET_MODIFY:
506 if (validateModifyArgument(cmd.argument())) {
507 flag.setEnabled(true);
509 string name = trim(split(to_utf8(cmd.argument()), typestr, ' '));
510 info_type type = nameTranslator().find(typestr);
511 string origname = name_;
512 if (type == FIXDATE_INFO)
513 split(name_, origname, '@');
514 flag.setOnOff(type == type_ && name == origname);
525 void InsetInfo::doDispatch(Cursor & cur, FuncRequest & cmd)
527 switch (cmd.action()) {
528 case LFUN_INSET_MODIFY:
530 setInfo(to_utf8(cmd.argument()));
531 cur.forceBufferUpdate();
532 initialized_ = false;
535 case LFUN_INSET_COPY_AS: {
536 cap::clearSelection();
538 copy.pushBackward(*this);
542 copy.pit() = copy.lastpit();
543 copy.pos() = copy.lastpos();
545 cap::copySelection(copy);
550 InsetCollapsible::doDispatch(cur, cmd);
556 void InsetInfo::setInfo(string const & name)
561 string saved_date_specifier;
562 // Store old date specifier for potential re-use
564 saved_date_specifier = split(name_, '@');
567 name_ = trim(split(name, type, ' '));
568 type_ = nameTranslator().find(type);
570 name_ = defaultValueTranslator().find(type_);
571 if (type_ == FIXDATE_INFO) {
572 string const date_specifier = split(name_, '@');
573 // If an explicit new fix date is specified, use that
574 // Otherwise, use the old one or, if there is none,
576 if (date_specifier.empty()) {
577 if (saved_date_specifier.empty())
578 name_ += "@" + fromqstr(QDate::currentDate().toString(Qt::ISODate));
580 name_ += "@" + saved_date_specifier;
586 void InsetInfo::error(docstring const & err, Language const * lang)
588 setText(bformat(translateIfPossible(err, lang->code()), from_utf8(name_)),
589 Font(inherit_font, lang), false);
593 void InsetInfo::info(docstring const & err, Language const * lang)
595 setText(translateIfPossible(err, lang->code()),
596 Font(inherit_font, lang), false);
600 void InsetInfo::setText(docstring const & str, Language const * lang)
602 setText(str, Font(inherit_font, lang), false);
606 bool InsetInfo::forceLTR() const
612 void InsetInfo::updateBuffer(ParIterator const & it, UpdateType utype) {
613 // If the Buffer is a clone, then we neither need nor want to do any
614 // of what follows. We want, rather, just to inherit how things were
615 // in the original Buffer. This is especially important for VCS.
616 // Otherwise, we could in principle have different settings here
617 // than in the Buffer we were exporting.
618 if (buffer().isClone())
621 BufferParams const & bp = buffer().params();
622 lang_ = it.paragraph().getFontSettings(bp, it.pos()).language();
623 Language const * tryguilang = languages.getFromCode(Messages::guiLanguage());
624 // Some info insets use the language of the GUI (if available)
625 Language const * guilang = tryguilang ? tryguilang : lang_;
627 force_ltr_ = !lang_->rightToLeft();
628 // This is just to get the string into the po files
632 gui = _("Unknown Info!");
633 info(from_ascii("Unknown Info!"), lang_);
634 initialized_ = false;
637 case SHORTCUTS_INFO: {
638 // shortcuts can change, so we need to re-do this each time
639 FuncRequest const func = lyxaction.lookupFunc(name_);
640 if (func.action() == LFUN_UNKNOWN_ACTION) {
641 gui = _("Unknown action %1$s");
642 error(from_ascii("Unknown action %1$s"), lang_);
645 KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(func);
646 if (bindings.empty()) {
647 gui = _("undefined");
648 info(from_ascii("undefined"), lang_);
651 if (type_ == SHORTCUT_INFO)
652 setText(bindings.begin()->print(KeySequence::ForGui), guilang);
654 setText(theTopLevelKeymap().printBindings(func, KeySequence::ForGui), guilang);
655 force_ltr_ = !guilang->rightToLeft() && !lang_->rightToLeft();
659 // this information could change, if the preferences are changed,
660 // so we will recalculate each time through.
663 gui = _("undefined");
664 info(from_ascii("undefined"), lang_);
667 // FIXME this uses the serialization mechanism to get the info
668 // we want, which i guess works but is a bit strange.
669 lyxrc.write(oss, true, name_);
670 string result = oss.str();
671 if (result.size() < 2) {
672 gui = _("undefined");
673 info(from_ascii("undefined"), lang_);
676 string::size_type loc = result.rfind("\n", result.size() - 2);
677 loc = loc == string::npos ? 0 : loc + 1;
678 if (result.size() < loc + name_.size() + 1
679 || result.substr(loc + 1, name_.size()) != name_) {
680 gui = _("undefined");
681 info(from_ascii("undefined"), lang_);
684 // remove leading comments and \\name and space
685 result = result.substr(loc + name_.size() + 2);
688 result = rtrim(result, "\n");
689 result = trim(result, "\"");
690 setText(from_utf8(result), lang_);
694 // only need to do this once.
697 // check in packages.lst
698 if (LaTeXFeatures::isAvailable(name_)) {
700 info(from_ascii("yes"), lang_);
703 info(from_ascii("no"), lang_);
708 case TEXTCLASS_INFO: {
709 // the TextClass can change
710 LayoutFileList const & list = LayoutFileList::get();
711 bool available = false;
712 // name_ is the class name
713 if (list.haveClass(name_))
714 available = list[name_].isTeXClassAvailable();
717 info(from_ascii("yes"), lang_);
720 info(from_ascii("no"), lang_);
725 // only need to do this once.
728 // and we will not keep trying if we fail
730 docstring_list names;
731 FuncRequest const func = lyxaction.lookupFunc(name_);
732 if (func.action() == LFUN_UNKNOWN_ACTION) {
733 gui = _("Unknown action %1$s");
734 error(from_ascii("Unknown action %1$s"), lang_);
737 // iterate through the menubackend to find it
739 gui = _("Can't determine menu entry for action %1$s in batch mode");
740 error(from_ascii("Can't determine menu entry for action %1$s in batch mode"), lang_);
743 if (!theApp()->searchMenu(func, names)) {
744 gui = _("No menu entry for action %1$s");
745 error(from_ascii("No menu entry for action %1$s"), lang_);
748 // if found, return its path.
750 Paragraph & par = paragraphs().front();
751 Font const f(inherit_font, guilang);
752 force_ltr_ = !guilang->rightToLeft();
754 //fu.fontInfo().setUnderbar(FONT_ON);
755 for (docstring const & name : names) {
756 // do not insert > for the top level menu item
757 if (&name != &names.front())
758 par.insertInset(par.size(), new InsetSpecialChar(InsetSpecialChar::MENU_SEPARATOR),
759 f, Change(Change::UNCHANGED));
760 //FIXME: add proper underlines here. This
761 // involves rewriting searchMenu used above to
762 // return a vector of menus. If we do not do
763 // that, we might as well use below
764 // Paragraph::insert on each string (JMarc)
765 for (char_type c : name)
766 par.insertChar(par.size(), c, f, Change(Change::UNCHANGED));
771 // only need to do this once.
774 // and we will not keep trying if we fail
776 FuncRequest func = lyxaction.lookupFunc(name_);
777 docstring icon_name = frontend::Application::iconName(func, true);
778 // FIXME: We should use the icon directly instead of
779 // going through FileName. The code below won't work
780 // if the icon is embedded in the executable through
781 // the Qt resource system.
782 // This is only a negligible performance problem:
783 // If the installed icon differs from the resource icon the
784 // installed one is preferred anyway, and all icons that are
785 // embedded in the resources are installed as well.
786 FileName file(to_utf8(icon_name));
787 if (file.onlyFileNameWithoutExt() == "unknown") {
788 string dir = "images";
789 FileName file2(imageLibFileSearch(dir, name_, "svgz,png"));
795 int percent_scale = 100;
797 // Compute the scale factor for the icon such that its
798 // width on screen is equal to 1em in pixels.
799 // The scale factor is rounded to the integer nearest
800 // to the float value of the ratio 100*iconsize/imgsize.
801 int imgsize = QImage(toqstr(file.absFileName())).width();
803 int iconsize = Length(1, Length::EM).inPixels(1);
804 percent_scale = (100 * iconsize + imgsize / 2)/imgsize;
807 InsetGraphics * inset = new InsetGraphics(buffer_);
808 InsetGraphicsParams igp;
810 igp.lyxscale = percent_scale;
811 igp.scale = string();
812 igp.width = Length(1, Length::EM);
813 inset->setParams(igp);
815 Font const f(inherit_font, lang_);
816 paragraphs().front().insertInset(0, inset, f,
817 Change(Change::UNCHANGED));
821 // this could all change, so we will recalculate each time
823 setText(from_utf8(buffer().fileName().onlyFileName()), lang_);
824 else if (name_ == "path")
825 setText(from_utf8(os::latex_path(buffer().filePath())), lang_);
826 else if (name_ == "class")
827 setText(from_utf8(bp.documentClass().name()), lang_);
831 // this information could change, in principle, so we will
832 // recalculate each time through
833 if (!buffer().lyxvc().inUse()) {
834 gui = _("No version control!");
835 info(from_ascii("No version control!"), lang_);
838 LyXVC::RevisionInfo itype = LyXVC::Unknown;
839 if (name_ == "revision")
841 else if (name_ == "tree-revision")
843 else if (name_ == "author")
844 itype = LyXVC::Author;
845 else if (name_ == "time")
847 else if (name_ == "date")
849 string binfo = buffer().lyxvc().revisionInfo(itype);
851 gui = _("%1$s[[vcs data]] unknown");
852 error(from_ascii("%1$s[[vcs data]] unknown"), lang_);
854 setText(from_utf8(binfo), lang_);
858 // only need to do this once.
861 if (name_ == "version")
862 setText(from_ascii(lyx_version), lang_);
868 string date_format = name_;
869 string const date_specifier = (type_ == FIXDATE_INFO && contains(name_, '@'))
870 ? split(name_, date_format, '@') : string();
872 if (type_ == MODDATE_INFO)
873 date = QDateTime::fromTime_t(buffer().fileName().lastModified()).date();
874 else if (type_ == FIXDATE_INFO && !date_specifier.empty())
875 date = QDate::fromString(toqstr(date_specifier), Qt::ISODate);
877 date = QDate::currentDate();
878 setText(getDate(date_format, date), lang_);
881 // Just to do something with that string
882 LYXERR(Debug::INFO, "info inset text: " << gui);
883 InsetCollapsible::updateBuffer(it, utype);
887 string InsetInfo::contextMenu(BufferView const &, int, int) const
889 //FIXME: We override the implementation of InsetCollapsible,
890 //because this inset is not a collapsible inset.
891 return contextMenuName();
895 string InsetInfo::contextMenuName() const
897 return "context-info";