]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/qt_helpers.cpp
Improve CITATION_OPEN
[lyx.git] / src / frontends / qt / qt_helpers.cpp
1 /**
2  * \file qt_helpers.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Dekel Tsur
7  * \author Jürgen Spitzmüller
8  * \author Richard Kimberly Heck
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "qt_helpers.h"
16
17 #include "Format.h"
18 #include "LengthCombo.h"
19 #include "LyXRC.h"
20
21 #include "frontends/alert.h"
22
23 #include "support/convert.h"
24 #include "support/debug.h"
25 #include "support/gettext.h"
26 #include "support/lstrings.h"
27 #include "support/Package.h"
28 #include "support/PathChanger.h"
29 #include "support/Systemcall.h"
30
31 #include <QApplication>
32 #include <QCheckBox>
33 #include <QComboBox>
34 #include <QDesktopServices>
35 #include <QDir>
36 #include <QInputDialog>
37 #include <QLineEdit>
38 #include <QMessageBox>
39 #include <QLocale>
40 #include <QPalette>
41 #include <QPushButton>
42 #include <QSet>
43 #include <QSettings>
44 #include <QTextLayout>
45 #include <QTextDocument>
46 #include <QToolTip>
47 #include <QUrl>
48
49 #include <algorithm>
50 #include <fstream>
51 #include <locale>
52
53 // for FileFilter.
54 // FIXME: Remove
55 #include <regex>
56
57 using namespace std;
58 using namespace lyx::support;
59
60 namespace lyx {
61
62 FileName libFileSearch(QString const & dir, QString const & name,
63                                 QString const & ext, search_mode mode)
64 {
65         return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
66 }
67
68
69 FileName imageLibFileSearch(QString & dir, QString const & name,
70                                 QString const & ext, search_mode mode)
71 {
72         string tmp = fromqstr(dir);
73         FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
74         dir = toqstr(tmp);
75         return fn;
76 }
77
78 namespace {
79
80 double locstringToDouble(QString const & str)
81 {
82         QLocale loc;
83         bool ok;
84         double res = loc.toDouble(str, &ok);
85         if (!ok) {
86                 // Fall back to C
87                 QLocale c(QLocale::C);
88                 res = c.toDouble(str);
89         }
90         return res;
91 }
92
93 } // namespace
94
95
96 namespace frontend {
97
98 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
99 {
100         QString const length = input->text();
101         if (length.isEmpty())
102                 return string();
103
104         // Don't return unit-from-choice if the input(field) contains a unit
105         if (isValidGlueLength(fromqstr(length)))
106                 return fromqstr(length);
107         // Also try with localized version
108         if (isValidGlueLength(fromqstr(unlocLengthString(length))))
109                 return fromqstr(unlocLengthString(length));
110
111         Length::UNIT const unit = combo->currentLengthItem();
112
113         return Length(locstringToDouble(length.trimmed()), unit).asString();
114 }
115
116
117 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
118 {
119         QString const length = input->text();
120         if (length.isEmpty())
121                 return Length();
122
123         // don't return unit-from-choice if the input(field) contains a unit
124         if (isValidGlueLength(fromqstr(length)))
125                 return Length(fromqstr(length));
126         // Also try with localized version
127         if (isValidGlueLength(fromqstr(unlocLengthString(length))))
128                 return Length(fromqstr(unlocLengthString(length)));
129
130         Length::UNIT unit = Length::UNIT_NONE;
131         QString const item = combo->currentText();
132         for (int i = 0; i < num_units; i++) {
133                 if (qt_(lyx::unit_name_gui[i]) == item) {
134                         unit = unitFromString(unit_name[i]);
135                         break;
136                 }
137         }
138
139         return Length(locstringToDouble(length.trimmed()), unit);
140 }
141
142
143 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
144         Length const & len, Length::UNIT /*defaultUnit*/)
145 {
146         if (len.empty()) {
147                 // no length (UNIT_NONE)
148                 combo->setCurrentItem(Length::defaultUnit());
149                 input->setText("");
150         } else {
151                 combo->setCurrentItem(len.unit());
152                 QLocale loc;
153                 loc.setNumberOptions(QLocale::OmitGroupSeparator);
154                 input->setText(formatLocFPNumber(Length(len).value()));
155         }
156 }
157
158
159 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
160         string const & len, Length::UNIT defaultUnit)
161 {
162         if (len.empty()) {
163                 // no length (UNIT_NONE)
164                 combo->setCurrentItem(defaultUnit);
165                 input->setText("");
166         } else if (!isValidLength(len) && !isStrDbl(len)) {
167                 // use input field only for gluelengths
168                 combo->setCurrentItem(defaultUnit);
169                 input->setText(locLengthString(toqstr(len)));
170         } else {
171                 lengthToWidgets(input, combo, Length(len), defaultUnit);
172         }
173 }
174
175
176 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
177         docstring const & len, Length::UNIT defaultUnit)
178 {
179         lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
180 }
181
182
183 double widgetToDouble(QLineEdit const * input)
184 {
185         QString const text = input->text();
186         if (text.isEmpty())
187                 return 0.0;
188
189         return locstringToDouble(text.trimmed());
190 }
191
192
193 string widgetToDoubleStr(QLineEdit const * input)
194 {
195         return convert<string>(widgetToDouble(input));
196 }
197
198
199 void doubleToWidget(QLineEdit * input, double value, char f, int prec)
200 {
201         QLocale loc;
202         loc.setNumberOptions(QLocale::OmitGroupSeparator);
203         input->setText(loc.toString(value, f, prec));
204 }
205
206
207 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
208 {
209         doubleToWidget(input, convert<double>(value), f, prec);
210 }
211
212
213 QString formatLocFPNumber(double d)
214 {
215         QString result = toqstr(formatFPNumber(d));
216         QLocale loc;
217         result.replace('.', loc.decimalPoint());
218         return result;
219 }
220
221
222 bool SortLocaleAware(QString const & lhs, QString const & rhs)
223 {
224         return QString::localeAwareCompare(lhs, rhs) < 0;
225 }
226
227
228 bool ColorSorter(ColorCode lhs, ColorCode rhs)
229 {
230         return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
231 }
232
233
234 void setValid(QWidget * widget, bool valid)
235 {
236         if (valid) {
237                 if (qobject_cast<QCheckBox*>(widget) != nullptr)
238                         // Check boxes need to be treated differenty, see
239                         // https://forum.qt.io/topic/93253/
240                         widget->setStyleSheet("");
241                 else
242                         widget->setPalette(QPalette());
243         } else {
244                 if (qobject_cast<QCheckBox*>(widget) != nullptr) {
245                         // Check boxes need to be treated differenty, see
246                         // https://forum.qt.io/topic/93253/
247                         if (qobject_cast<QCheckBox*>(widget)->isChecked())
248                                 widget->setStyleSheet("QCheckBox:unchecked{ color: red; }QCheckBox:checked{ color: red; }");
249                 } else {
250                         QPalette pal = widget->palette();
251                         pal.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0));
252                         pal.setColor(QPalette::Active, QPalette::Text, QColor(255, 0, 0));
253                         widget->setPalette(pal);
254                 }
255         }
256 }
257
258
259 void focusAndHighlight(QAbstractItemView * w)
260 {
261         w->setFocus();
262         w->setCurrentIndex(w->currentIndex());
263         w->scrollTo(w->currentIndex());
264 }
265
266
267 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
268 {
269         QPalette pal = QApplication::palette();
270         QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
271                         pal.color(QPalette::Active, QPalette::Highlight));
272         newpal.setColor(QPalette::WindowText,
273                         pal.color(QPalette::Active, QPalette::HighlightedText));
274         for (QWidget * w : highlighted)
275                 w->setPalette(newpal);
276         for (QWidget * w : plain)
277                 w->setPalette(pal);
278 }
279
280
281 void showDirectory(FileName const & directory)
282 {
283         if (!directory.exists())
284                 return;
285         QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
286         // Give hints in case of bugs
287         if (!qurl.isValid()) {
288                 frontend::Alert::error(_("Invalid URL"),
289                         bformat(_("The URL `%1$s' could not be resolved."),
290                                 qstring_to_ucs4(qurl.toString())));
291                 return;
292
293         }
294         if (!QDesktopServices::openUrl(qurl))
295                 frontend::Alert::error(_("URL could not be accessed"),
296                         bformat(_("The URL `%1$s' could not be opened although it exists!"),
297                                 qstring_to_ucs4(qurl.toString())));
298 }
299
300 void showTarget(string const & target_in, Buffer const & buf)
301 {
302         LYXERR(Debug::INSETS, "Showtarget:" << target_in << "\n");
303
304         string target = target_in;
305         string const & docpath = buf.absFileName();
306
307         bool const is_external = prefixIs(target, "EXTERNAL ");
308         if (is_external) {
309                 if (!lyxrc.citation_search)
310                         return;
311                 string tmp, tar;
312                 tar = split(target, tmp, ' ');
313                 string const scriptcmd = subst(lyxrc.citation_search_view, "$${python}", os::python());
314                 string const command = scriptcmd + " " + tar;
315                 cmd_ret const ret = runCommand(commandPrep(command));
316                 if (!ret.valid) {
317                         // Script failed
318                         frontend::Alert::error(_("Could not open file"),
319                                 _("The lyxpaperview script failed."));
320                         return;
321                 }
322                 // lyxpaperview returns a \n-separated list of paths
323                 vector<string> targets = getVectorFromString(rtrim(ret.result, "\n"), "\n");
324                 if (targets.empty()) {
325                         frontend::Alert::error(_("Could not open file"),
326                                 bformat(_("No file was found using the pattern `%1$s'."),
327                                         from_utf8(tar)));
328                         return;
329                 }
330                 if (targets.size() > 1) {
331                         QStringList files;
332                         for (auto const & t : targets)
333                                 files << toqstr(t);
334                         bool ok;
335                         QString file = QInputDialog::getItem(nullptr, qt_("Multiple files found!"),
336                                                              qt_("Select the file that should be opened:"),
337                                                              files, 0, false, &ok);
338                         if (!ok || file.isEmpty())
339                                 return;
340                         target = fromqstr(file);
341                 } else
342                         target = targets.front();
343         }
344         // security measure: ask user before opening if document is not marked trusted.
345         QSettings settings;
346         if (!settings.value("trusted documents/" + toqstr(docpath), false).toBool()) {
347                 QCheckBox * dontShowAgainCB = new QCheckBox();
348                 dontShowAgainCB->setText(qt_("&Trust this document and do not ask me again!"));
349                 dontShowAgainCB->setToolTip(qt_("If you check this, LyX will open all targets without asking for the given document in the future."));
350                 docstring const warn = bformat(_("LyX wants to open the following target in an external application:\n"
351                                                  "%1$s\n"
352                                                  "Be aware that this might entail security infringements!\n"
353                                                  "Only do this if you trust origin of the document and the target of the link!\n"
354                                                  "How do you want to proceed?"), from_utf8(target));
355                 QMessageBox box(QMessageBox::Warning, qt_("Open external target?"), toqstr(warn),
356                                 QMessageBox::NoButton, qApp->focusWidget());
357                 QPushButton * openButton = box.addButton(qt_("&Open Target"), QMessageBox::ActionRole);
358                 box.addButton(QMessageBox::Abort);
359                 box.setCheckBox(dontShowAgainCB);
360                 box.setDefaultButton(QMessageBox::Abort);
361                 box.exec();
362                 if (box.clickedButton() != openButton)
363                         return;
364                 if (dontShowAgainCB->isChecked())
365                         settings.setValue("trusted documents/"
366                                 + toqstr(docpath), true);
367         }
368
369         bool success = false;
370         QUrl url = is_external
371                 ? QUrl::fromLocalFile(toqstr(target))
372                 : QUrl(toqstr(target), QUrl::TolerantMode);
373         if (url.isLocalFile()) {
374                 // For local files, we use our own viewers
375                 // (QDesktopServices employs xdg-open which
376                 // does not yet work everywhere)
377                 FileName fn(fromqstr(url.path()));
378                 string const format = theFormats().getFormatFromFile(fn);
379                 success = theFormats().view(buf, fn, format);
380         } else
381                 // For external files, we rely on QDesktopServices
382                 success =  QDesktopServices::openUrl(url);
383
384         if (!success)
385                 frontend::Alert::error(_("Could not open file"),
386                         bformat(_("The target `%1$s' could not be resolved."),
387                                 from_utf8(target)));
388 }
389 } // namespace frontend
390
391 QString const qt_(char const * str, const char *)
392 {
393         return toqstr(_(str));
394 }
395
396
397 QString const qt_(string const & str)
398 {
399         return toqstr(_(str));
400 }
401
402
403 QString const qt_(QString const & qstr)
404 {
405         return toqstr(_(fromqstr(qstr)));
406 }
407
408
409 void rescanTexStyles(string const & arg)
410 {
411         // Run rescan in user lyx directory
412         PathChanger p(package().user_support());
413         FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
414         Systemcall one;
415         string const command = os::python() + ' ' +
416             quoteName(prog.toFilesystemEncoding()) + ' ' +
417             arg;
418         int const status = one.startscript(Systemcall::Wait, command);
419         if (status == 0)
420                 return;
421         // FIXME UNICODE
422         frontend::Alert::error(_("Could not update TeX information"),
423                 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
424 }
425
426
427 QStringList texFileList(QString const & filename)
428 {
429         QStringList list;
430         FileName const file = libFileSearch(QString(), filename);
431         if (file.empty())
432                 return list;
433
434         // FIXME Unicode.
435         vector<docstring> doclist =
436                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
437
438         // Normalise paths like /foo//bar ==> /foo/bar
439         QSet<QString> set;
440         for (size_t i = 0; i != doclist.size(); ++i) {
441                 QString qfile = toqstr(doclist[i]);
442                 qfile.replace("\r", "");
443                 while (qfile.contains("//"))
444                         qfile.replace("//", "/");
445                 if (!qfile.isEmpty())
446                         set.insert(qfile);
447         }
448
449         // remove duplicates
450 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
451         return QList<QString>(set.begin(), set.end());
452 #else
453         return QList<QString>::fromSet(set);
454 #endif
455 }
456
457 QString const externalLineEnding(docstring const & str)
458 {
459 #ifdef Q_OS_MAC
460         // The MAC clipboard uses \r for lineendings, and we use \n
461         return toqstr(subst(str, '\n', '\r'));
462 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
463         // Windows clipboard uses \r\n for lineendings, and we use \n
464         return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
465 #else
466         return toqstr(str);
467 #endif
468 }
469
470
471 docstring const internalLineEnding(QString const & str)
472 {
473         docstring const s = subst(qstring_to_ucs4(str),
474                                   from_ascii("\r\n"), from_ascii("\n"));
475         return subst(s, '\r', '\n');
476 }
477
478
479 QString internalPath(const QString & str)
480 {
481         return toqstr(os::internal_path(fromqstr(str)));
482 }
483
484
485 QString onlyFileName(const QString & str)
486 {
487         return toqstr(support::onlyFileName(fromqstr(str)));
488 }
489
490
491 QString onlyPath(const QString & str)
492 {
493         return toqstr(support::onlyPath(fromqstr(str)));
494 }
495
496
497 QString changeExtension(QString const & oldname, QString const & ext)
498 {
499         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
500 }
501
502 /// Remove the extension from \p name
503 QString removeExtension(QString const & name)
504 {
505         return toqstr(support::removeExtension(fromqstr(name)));
506 }
507
508 /** Add the extension \p ext to \p name.
509  Use this instead of changeExtension if you know that \p name is without
510  extension, because changeExtension would wrongly interpret \p name if it
511  contains a dot.
512  */
513 QString addExtension(QString const & name, QString const & ext)
514 {
515         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
516 }
517
518 /// Return the extension of the file (not including the .)
519 QString getExtension(QString const & name)
520 {
521         return toqstr(support::getExtension(fromqstr(name)));
522 }
523
524
525 /** Convert relative path into absolute path based on a basepath.
526   If relpath is absolute, just use that.
527   If basepath doesn't exist use CWD.
528   */
529 QString makeAbsPath(QString const & relpath, QString const & base)
530 {
531         return toqstr(support::makeAbsPath(fromqstr(relpath),
532                 fromqstr(base)).absFileName());
533 }
534
535
536 /////////////////////////////////////////////////////////////////////////
537 //
538 // FileFilterList
539 //
540 /////////////////////////////////////////////////////////////////////////
541
542 /** Given a string such as
543  *      "<glob> <glob> ... *.{abc,def} <glob>",
544  *  convert the csh-style brace expressions:
545  *      "<glob> <glob> ... *.abc *.def <glob>".
546  *  Requires no system support, so should work equally on Unix, Mac, Win32.
547  */
548 static string const convert_brace_glob(string const & glob)
549 {
550         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
551         // "abc,def,ghi" as group 2, while allowing spaces in group 2.
552         static regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
553         // Matches "abc" and "abc,", storing "abc" as group 1,
554         // while ignoring surrounding spaces.
555         static regex const block_re(" *([^ ,}]+) *,? *");
556
557         string pattern;
558
559         string::const_iterator it = glob.begin();
560         string::const_iterator const end = glob.end();
561         while (true) {
562                 match_results<string::const_iterator> what;
563                 if (!regex_search(it, end, what, glob_re)) {
564                         // Ensure that no information is lost.
565                         pattern += string(it, end);
566                         break;
567                 }
568
569                 // Everything from the start of the input to
570                 // the start of the match.
571                 pattern += string(what[-1].first, what[-1].second);
572
573                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
574                 string const head = string(what[1].first, what[1].second);
575                 string const tail = string(what[2].first, what[2].second);
576
577                 // Split the ','-separated chunks of tail so that
578                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
579                 string const fmt = " " + head + "$1";
580                 pattern += regex_replace(tail, block_re, fmt);
581
582                 // Increment the iterator to the end of the match.
583                 it += distance(it, what[0].second);
584         }
585
586         return pattern;
587 }
588
589
590 struct Filter
591 {
592         /* \param description text describing the filters.
593          * \param one or more wildcard patterns, separated by
594          * whitespace.
595          */
596         Filter(docstring const & description, std::string const & globs);
597
598         docstring const & description() const { return desc_; }
599
600         QString toString() const;
601
602         docstring desc_;
603         std::vector<std::string> globs_;
604 };
605
606
607 Filter::Filter(docstring const & description, string const & globs)
608         : desc_(description)
609 {
610         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
611         //       "<glob> <glob> ... *.abc *.def <glob>"
612         string const expanded_globs = convert_brace_glob(globs);
613
614         // Split into individual globs.
615         globs_ = getVectorFromString(expanded_globs, " ");
616 }
617
618
619 QString Filter::toString() const
620 {
621         QString s;
622
623         bool const has_description = !desc_.empty();
624
625         if (has_description) {
626                 s += toqstr(desc_);
627                 s += " (";
628         }
629
630         s += toqstr(getStringFromVector(globs_, " "));
631
632         if (has_description)
633                 s += ')';
634         return s;
635 }
636
637
638 /** \c FileFilterList parses a Qt-style list of available file filters
639  *  to generate the corresponding vector.
640  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
641  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
642  *  will result in a vector of size 1 in which the description field is empty.
643  */
644 struct FileFilterList
645 {
646         // FIXME UNICODE: globs_ should be unicode...
647         /** \param qt_style_filter a list of available file filters.
648          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
649          *  The "All files (*)" filter is always added to the list.
650          */
651         explicit FileFilterList(docstring const & qt_style_filter =
652                                 docstring());
653
654         typedef std::vector<Filter>::size_type size_type;
655
656         bool empty() const { return filters_.empty(); }
657         size_type size() const { return filters_.size(); }
658         Filter & operator[](size_type i) { return filters_[i]; }
659         Filter const & operator[](size_type i) const { return filters_[i]; }
660
661         void parse_filter(std::string const & filter);
662         std::vector<Filter> filters_;
663 };
664
665 FileFilterList::FileFilterList(docstring const & qt_style_filter)
666 {
667         // FIXME UNICODE
668         string const filter = to_utf8(qt_style_filter)
669                 + (qt_style_filter.empty() ? string() : ";;")
670                 + to_utf8(_("All Files")) + " " + fromqstr(wildcardAllFiles());
671
672         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
673         // into individual filters.
674         static regex const separator_re(";;");
675
676         string::const_iterator it = filter.begin();
677         string::const_iterator const end = filter.end();
678         while (true) {
679                 match_results<string::const_iterator> what;
680
681                 if (!regex_search(it, end, what, separator_re)) {
682                         parse_filter(string(it, end));
683                         break;
684                 }
685
686                 // Everything from the start of the input to
687                 // the start of the match.
688                 parse_filter(string(it, what[0].first));
689
690                 // Increment the iterator to the end of the match.
691                 it += distance(it, what[0].second);
692         }
693 }
694
695
696 void FileFilterList::parse_filter(string const & filter)
697 {
698         // Matches "TeX documents (plain) (*.tex)",
699         // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
700         static regex const filter_re("(.*)\\(([^()]+)\\) *$");
701
702         match_results<string::const_iterator> what;
703         if (!regex_search(filter, what, filter_re)) {
704                 // Just a glob, no description.
705                 filters_.push_back(Filter(docstring(), trim(filter)));
706         } else {
707                 // FIXME UNICODE
708                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
709                 string const globs = string(what[2].first, what[2].second);
710                 filters_.push_back(Filter(trim(desc), trim(globs)));
711         }
712 }
713
714
715 QString wildcardAllFiles()
716 {
717 #if defined(_WIN32)
718                 return "(*.*)";
719 #else
720                 return "(*)";
721 #endif
722 }
723
724
725 /** \returns the equivalent of the string passed in
726  *  although any brace expressions are expanded.
727  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
728  */
729 QStringList fileFilters(QString const & desc)
730 {
731         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
732         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
733         FileFilterList filters(qstring_to_ucs4(desc));
734         //LYXERR0("DESC: " << desc);
735         QStringList list;
736         for (size_t i = 0; i != filters.filters_.size(); ++i) {
737                 QString f = filters.filters_[i].toString();
738                 //LYXERR0("FILTER: " << f);
739                 list.append(f);
740         }
741         return list;
742 }
743
744
745 QString formatToolTip(QString text, int width)
746 {
747         // 1. QTooltip activates word wrapping only if mightBeRichText()
748         //    is true. So we convert the text to rich text.
749         //
750         // 2. The default width is way too small. Setting the width is tricky; first
751         //    one has to compute the ideal width, and then force it with special
752         //    html markup.
753
754         // do nothing if empty or already formatted
755         if (text.isEmpty() || text.startsWith(QString("<html>")))
756                 return text;
757         // Convert to rich text if it is not already
758         if (!Qt::mightBeRichText(text))
759                 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
760         // Compute desired width in pixels
761         QFont const font = QToolTip::font();
762 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
763         int const px_width = width * QFontMetrics(font).horizontalAdvance("M");
764 #else
765         int const px_width = width * QFontMetrics(font).width("M");
766 #endif
767         // Determine the ideal width of the tooltip
768         QTextDocument td("");
769         td.setHtml(text);
770         td.setDefaultFont(QToolTip::font());
771         td.setDocumentMargin(0);
772         td.setTextWidth(px_width);
773         double best_width = td.idealWidth();
774         // Set the line wrapping with appropriate width
775         return QString("<html><body><table><tr>"
776                        "<td align=justify width=%1>%2</td>"
777                        "</tr></table></body></html>")
778                 .arg(QString::number(int(best_width) + 1), text);
779 }
780
781
782 QString qtHtmlToPlainText(QString const & text)
783 {
784         if (!Qt::mightBeRichText(text))
785                 return text;
786         QTextDocument td;
787         td.setHtml(text);
788         return td.toPlainText();
789 }
790
791
792 } // namespace lyx