]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/qt_helpers.cpp
Handle multiple files in file and localfile BibTeX field (#12896)
[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         vector<string> targets;
307
308         bool const is_external = prefixIs(target, "EXTERNAL ");
309         if (is_external) {
310                 if (!lyxrc.citation_search)
311                         return;
312                 string tmp, tar;
313                 tar = split(target, tmp, ' ');
314                 string const scriptcmd = subst(lyxrc.citation_search_view, "$${python}", os::python());
315                 string const command = scriptcmd + " " + tar;
316                 cmd_ret const ret = runCommand(commandPrep(command));
317                 if (!ret.valid) {
318                         // Script failed
319                         frontend::Alert::error(_("Could not open file"),
320                                 _("The lyxpaperview script failed."));
321                         return;
322                 }
323                 // lyxpaperview returns a \n-separated list of paths
324                 targets = getVectorFromString(rtrim(ret.result, "\n"), "\n");
325                 if (targets.empty()) {
326                         frontend::Alert::error(_("Could not open file"),
327                                 bformat(_("No file was found using the pattern `%1$s'."),
328                                         from_utf8(tar)));
329                         return;
330                 }
331         }
332         if (prefixIs(target, "file://")) {
333                 // file might have a \n-separated list of paths
334                 targets = getVectorFromString(target, "\n");
335         }
336         if (!targets.empty()) {
337                 if (targets.size() > 1) {
338                         QStringList files;
339                         for (auto const & t : targets)
340                                 files << toqstr(t);
341                         bool ok;
342                         QString file = QInputDialog::getItem(nullptr, qt_("Multiple files found!"),
343                                                              qt_("Select the file that should be opened:"),
344                                                              files, 0, false, &ok);
345                         if (!ok || file.isEmpty())
346                                 return;
347                         target = fromqstr(file);
348                 } else
349                         target = targets.front();
350         }
351         // security measure: ask user before opening if document is not marked trusted.
352         QSettings settings;
353         if (!settings.value("trusted documents/" + toqstr(docpath), false).toBool()) {
354                 QCheckBox * dontShowAgainCB = new QCheckBox();
355                 dontShowAgainCB->setText(qt_("&Trust this document and do not ask me again!"));
356                 dontShowAgainCB->setToolTip(qt_("If you check this, LyX will open all targets without asking for the given document in the future."));
357                 docstring const warn = bformat(_("LyX wants to open the following target in an external application:\n\n"
358                                                  "%1$s\n\n"
359                                                  "Be aware that this might entail security infringements!\n\n"
360                                                  "Only do this if you trust the origin of the document and the target of the link!\n\n"
361                                                  "How do you want to proceed?"), from_utf8(target));
362                 QMessageBox box(QMessageBox::Warning, qt_("Open external target?"), toqstr(warn),
363                                 QMessageBox::NoButton, qApp->focusWidget());
364                 QPushButton * openButton = box.addButton(qt_("&Open Target"), QMessageBox::ActionRole);
365                 box.addButton(QMessageBox::Abort);
366                 box.setCheckBox(dontShowAgainCB);
367                 box.setDefaultButton(QMessageBox::Abort);
368                 box.exec();
369                 if (box.clickedButton() != openButton)
370                         return;
371                 if (dontShowAgainCB->isChecked())
372                         settings.setValue("trusted documents/"
373                                 + toqstr(docpath), true);
374         }
375
376         bool success = false;
377         QUrl url = is_external
378                 ? QUrl::fromLocalFile(toqstr(target))
379                 : QUrl(toqstr(target), QUrl::TolerantMode);
380         if (url.isLocalFile()) {
381                 // For local files, we use our own viewers
382                 // (QDesktopServices employs xdg-open which
383                 // does not yet work everywhere)
384                 FileName fn(fromqstr(url.path()));
385                 string const format = theFormats().getFormatFromFile(fn);
386                 success = theFormats().view(buf, fn, format);
387         } else
388                 // For external files, we rely on QDesktopServices
389                 success =  QDesktopServices::openUrl(url);
390
391         if (!success)
392                 frontend::Alert::error(_("Could not open file"),
393                         bformat(_("The target `%1$s' could not be resolved."),
394                                 from_utf8(target)));
395 }
396 } // namespace frontend
397
398 QString const qt_(char const * str, const char *)
399 {
400         return toqstr(_(str));
401 }
402
403
404 QString const qt_(string const & str)
405 {
406         return toqstr(_(str));
407 }
408
409
410 QString const qt_(QString const & qstr)
411 {
412         return toqstr(_(fromqstr(qstr)));
413 }
414
415
416 void rescanTexStyles(string const & arg)
417 {
418         // Run rescan in user lyx directory
419         PathChanger p(package().user_support());
420         FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
421         Systemcall one;
422         string const command = os::python() + ' ' +
423             quoteName(prog.toFilesystemEncoding()) + ' ' +
424             arg;
425         int const status = one.startscript(Systemcall::Wait, command);
426         if (status == 0)
427                 return;
428         // FIXME UNICODE
429         frontend::Alert::error(_("Could not update TeX information"),
430                 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
431 }
432
433
434 QStringList texFileList(QString const & filename)
435 {
436         QStringList list;
437         FileName const file = libFileSearch(QString(), filename);
438         if (file.empty())
439                 return list;
440
441         // FIXME Unicode.
442         vector<docstring> doclist =
443                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
444
445         // Normalise paths like /foo//bar ==> /foo/bar
446         QSet<QString> set;
447         for (size_t i = 0; i != doclist.size(); ++i) {
448                 QString qfile = toqstr(doclist[i]);
449                 qfile.replace("\r", "");
450                 while (qfile.contains("//"))
451                         qfile.replace("//", "/");
452                 if (!qfile.isEmpty())
453                         set.insert(qfile);
454         }
455
456         // remove duplicates
457 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
458         return QList<QString>(set.begin(), set.end());
459 #else
460         return QList<QString>::fromSet(set);
461 #endif
462 }
463
464 QString const externalLineEnding(docstring const & str)
465 {
466 #ifdef Q_OS_MAC
467         // The MAC clipboard uses \r for lineendings, and we use \n
468         return toqstr(subst(str, '\n', '\r'));
469 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
470         // Windows clipboard uses \r\n for lineendings, and we use \n
471         return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
472 #else
473         return toqstr(str);
474 #endif
475 }
476
477
478 docstring const internalLineEnding(QString const & str)
479 {
480         docstring const s = subst(qstring_to_ucs4(str),
481                                   from_ascii("\r\n"), from_ascii("\n"));
482         return subst(s, '\r', '\n');
483 }
484
485
486 QString internalPath(const QString & str)
487 {
488         return toqstr(os::internal_path(fromqstr(str)));
489 }
490
491
492 QString onlyFileName(const QString & str)
493 {
494         return toqstr(support::onlyFileName(fromqstr(str)));
495 }
496
497
498 QString onlyPath(const QString & str)
499 {
500         return toqstr(support::onlyPath(fromqstr(str)));
501 }
502
503
504 QString changeExtension(QString const & oldname, QString const & ext)
505 {
506         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
507 }
508
509 /// Remove the extension from \p name
510 QString removeExtension(QString const & name)
511 {
512         return toqstr(support::removeExtension(fromqstr(name)));
513 }
514
515 /** Add the extension \p ext to \p name.
516  Use this instead of changeExtension if you know that \p name is without
517  extension, because changeExtension would wrongly interpret \p name if it
518  contains a dot.
519  */
520 QString addExtension(QString const & name, QString const & ext)
521 {
522         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
523 }
524
525 /// Return the extension of the file (not including the .)
526 QString getExtension(QString const & name)
527 {
528         return toqstr(support::getExtension(fromqstr(name)));
529 }
530
531
532 /** Convert relative path into absolute path based on a basepath.
533   If relpath is absolute, just use that.
534   If basepath doesn't exist use CWD.
535   */
536 QString makeAbsPath(QString const & relpath, QString const & base)
537 {
538         return toqstr(support::makeAbsPath(fromqstr(relpath),
539                 fromqstr(base)).absFileName());
540 }
541
542
543 /////////////////////////////////////////////////////////////////////////
544 //
545 // FileFilterList
546 //
547 /////////////////////////////////////////////////////////////////////////
548
549 /** Given a string such as
550  *      "<glob> <glob> ... *.{abc,def} <glob>",
551  *  convert the csh-style brace expressions:
552  *      "<glob> <glob> ... *.abc *.def <glob>".
553  *  Requires no system support, so should work equally on Unix, Mac, Win32.
554  */
555 static string const convert_brace_glob(string const & glob)
556 {
557         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
558         // "abc,def,ghi" as group 2, while allowing spaces in group 2.
559         static regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
560         // Matches "abc" and "abc,", storing "abc" as group 1,
561         // while ignoring surrounding spaces.
562         static regex const block_re(" *([^ ,}]+) *,? *");
563
564         string pattern;
565
566         string::const_iterator it = glob.begin();
567         string::const_iterator const end = glob.end();
568         while (true) {
569                 match_results<string::const_iterator> what;
570                 if (!regex_search(it, end, what, glob_re)) {
571                         // Ensure that no information is lost.
572                         pattern += string(it, end);
573                         break;
574                 }
575
576                 // Everything from the start of the input to
577                 // the start of the match.
578                 pattern += string(what[-1].first, what[-1].second);
579
580                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
581                 string const head = string(what[1].first, what[1].second);
582                 string const tail = string(what[2].first, what[2].second);
583
584                 // Split the ','-separated chunks of tail so that
585                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
586                 string const fmt = " " + head + "$1";
587                 pattern += regex_replace(tail, block_re, fmt);
588
589                 // Increment the iterator to the end of the match.
590                 it += distance(it, what[0].second);
591         }
592
593         return pattern;
594 }
595
596
597 struct Filter
598 {
599         /* \param description text describing the filters.
600          * \param one or more wildcard patterns, separated by
601          * whitespace.
602          */
603         Filter(docstring const & description, std::string const & globs);
604
605         docstring const & description() const { return desc_; }
606
607         QString toString() const;
608
609         docstring desc_;
610         std::vector<std::string> globs_;
611 };
612
613
614 Filter::Filter(docstring const & description, string const & globs)
615         : desc_(description)
616 {
617         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
618         //       "<glob> <glob> ... *.abc *.def <glob>"
619         string const expanded_globs = convert_brace_glob(globs);
620
621         // Split into individual globs.
622         globs_ = getVectorFromString(expanded_globs, " ");
623 }
624
625
626 QString Filter::toString() const
627 {
628         QString s;
629
630         bool const has_description = !desc_.empty();
631
632         if (has_description) {
633                 s += toqstr(desc_);
634                 s += " (";
635         }
636
637         s += toqstr(getStringFromVector(globs_, " "));
638
639         if (has_description)
640                 s += ')';
641         return s;
642 }
643
644
645 /** \c FileFilterList parses a Qt-style list of available file filters
646  *  to generate the corresponding vector.
647  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
648  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
649  *  will result in a vector of size 1 in which the description field is empty.
650  */
651 struct FileFilterList
652 {
653         // FIXME UNICODE: globs_ should be unicode...
654         /** \param qt_style_filter a list of available file filters.
655          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
656          *  The "All files (*)" filter is always added to the list.
657          */
658         explicit FileFilterList(docstring const & qt_style_filter =
659                                 docstring());
660
661         typedef std::vector<Filter>::size_type size_type;
662
663         bool empty() const { return filters_.empty(); }
664         size_type size() const { return filters_.size(); }
665         Filter & operator[](size_type i) { return filters_[i]; }
666         Filter const & operator[](size_type i) const { return filters_[i]; }
667
668         void parse_filter(std::string const & filter);
669         std::vector<Filter> filters_;
670 };
671
672 FileFilterList::FileFilterList(docstring const & qt_style_filter)
673 {
674         // FIXME UNICODE
675         string const filter = to_utf8(qt_style_filter)
676                 + (qt_style_filter.empty() ? string() : ";;")
677                 + to_utf8(_("All Files")) + " " + fromqstr(wildcardAllFiles());
678
679         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
680         // into individual filters.
681         static regex const separator_re(";;");
682
683         string::const_iterator it = filter.begin();
684         string::const_iterator const end = filter.end();
685         while (true) {
686                 match_results<string::const_iterator> what;
687
688                 if (!regex_search(it, end, what, separator_re)) {
689                         parse_filter(string(it, end));
690                         break;
691                 }
692
693                 // Everything from the start of the input to
694                 // the start of the match.
695                 parse_filter(string(it, what[0].first));
696
697                 // Increment the iterator to the end of the match.
698                 it += distance(it, what[0].second);
699         }
700 }
701
702
703 void FileFilterList::parse_filter(string const & filter)
704 {
705         // Matches "TeX documents (plain) (*.tex)",
706         // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
707         static regex const filter_re("(.*)\\(([^()]+)\\) *$");
708
709         match_results<string::const_iterator> what;
710         if (!regex_search(filter, what, filter_re)) {
711                 // Just a glob, no description.
712                 filters_.push_back(Filter(docstring(), trim(filter)));
713         } else {
714                 // FIXME UNICODE
715                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
716                 string const globs = string(what[2].first, what[2].second);
717                 filters_.push_back(Filter(trim(desc), trim(globs)));
718         }
719 }
720
721
722 QString wildcardAllFiles()
723 {
724 #if defined(_WIN32)
725                 return "(*.*)";
726 #else
727                 return "(*)";
728 #endif
729 }
730
731
732 /** \returns the equivalent of the string passed in
733  *  although any brace expressions are expanded.
734  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
735  */
736 QStringList fileFilters(QString const & desc)
737 {
738         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
739         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
740         FileFilterList filters(qstring_to_ucs4(desc));
741         //LYXERR0("DESC: " << desc);
742         QStringList list;
743         for (size_t i = 0; i != filters.filters_.size(); ++i) {
744                 QString f = filters.filters_[i].toString();
745                 //LYXERR0("FILTER: " << f);
746                 list.append(f);
747         }
748         return list;
749 }
750
751
752 QString formatToolTip(QString text, int width)
753 {
754         // 1. QTooltip activates word wrapping only if mightBeRichText()
755         //    is true. So we convert the text to rich text.
756         //
757         // 2. The default width is way too small. Setting the width is tricky; first
758         //    one has to compute the ideal width, and then force it with special
759         //    html markup.
760
761         // do nothing if empty or already formatted
762         if (text.isEmpty() || text.startsWith(QString("<html>")))
763                 return text;
764         // Convert to rich text if it is not already
765         if (!Qt::mightBeRichText(text))
766                 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
767         // Compute desired width in pixels
768         QFont const font = QToolTip::font();
769 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
770         int const px_width = width * QFontMetrics(font).horizontalAdvance("M");
771 #else
772         int const px_width = width * QFontMetrics(font).width("M");
773 #endif
774         // Determine the ideal width of the tooltip
775         QTextDocument td("");
776         td.setHtml(text);
777         td.setDefaultFont(QToolTip::font());
778         td.setDocumentMargin(0);
779         td.setTextWidth(px_width);
780         double best_width = td.idealWidth();
781         // Set the line wrapping with appropriate width
782         return QString("<html><body><table><tr>"
783                        "<td align=justify width=%1>%2</td>"
784                        "</tr></table></body></html>")
785                 .arg(QString::number(int(best_width) + 1), text);
786 }
787
788
789 QString qtHtmlToPlainText(QString const & text)
790 {
791         if (!Qt::mightBeRichText(text))
792                 return text;
793         QTextDocument td;
794         td.setHtml(text);
795         return td.toPlainText();
796 }
797
798
799 } // namespace lyx