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
8 * \author Richard Kimberly Heck
10 * Full author contact details are available in file CREDITS.
15 #include "qt_helpers.h"
17 #include "LengthCombo.h"
20 #include "frontends/alert.h"
22 #include "support/convert.h"
23 #include "support/debug.h"
24 #include "support/gettext.h"
25 #include "support/lstrings.h"
26 #include "support/Package.h"
27 #include "support/PathChanger.h"
28 #include "support/Systemcall.h"
30 #include <QApplication>
33 #include <QDesktopServices>
39 #include <QTextLayout>
40 #include <QTextDocument>
53 using namespace lyx::support;
57 FileName libFileSearch(QString const & dir, QString const & name,
58 QString const & ext, search_mode mode)
60 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
64 FileName imageLibFileSearch(QString & dir, QString const & name,
65 QString const & ext, search_mode mode)
67 string tmp = fromqstr(dir);
68 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
75 double locstringToDouble(QString const & str)
79 double res = loc.toDouble(str, &ok);
82 QLocale c(QLocale::C);
83 res = c.toDouble(str);
93 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
95 QString const length = input->text();
99 // Don't return unit-from-choice if the input(field) contains a unit
100 if (isValidGlueLength(fromqstr(length)))
101 return fromqstr(length);
103 Length::UNIT const unit = combo->currentLengthItem();
105 return Length(locstringToDouble(length.trimmed()), unit).asString();
109 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
111 QString const length = input->text();
112 if (length.isEmpty())
115 // don't return unit-from-choice if the input(field) contains a unit
116 if (isValidGlueLength(fromqstr(length)))
117 return Length(fromqstr(length));
119 Length::UNIT unit = Length::UNIT_NONE;
120 QString const item = combo->currentText();
121 for (int i = 0; i < num_units; i++) {
122 if (qt_(lyx::unit_name_gui[i]) == item) {
123 unit = unitFromString(unit_name[i]);
128 return Length(locstringToDouble(length.trimmed()), unit);
132 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
133 Length const & len, Length::UNIT /*defaultUnit*/)
136 // no length (UNIT_NONE)
137 combo->setCurrentItem(Length::defaultUnit());
140 combo->setCurrentItem(len.unit());
142 loc.setNumberOptions(QLocale::OmitGroupSeparator);
143 input->setText(formatLocFPNumber(Length(len).value()));
148 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
149 string const & len, Length::UNIT defaultUnit)
152 // no length (UNIT_NONE)
153 combo->setCurrentItem(defaultUnit);
155 } else if (!isValidLength(len) && !isStrDbl(len)) {
156 // use input field only for gluelengths
157 combo->setCurrentItem(defaultUnit);
158 input->setText(toqstr(len));
160 lengthToWidgets(input, combo, Length(len), defaultUnit);
165 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
166 docstring const & len, Length::UNIT defaultUnit)
168 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
172 double widgetToDouble(QLineEdit const * input)
174 QString const text = input->text();
178 return locstringToDouble(text.trimmed());
182 string widgetToDoubleStr(QLineEdit const * input)
184 return convert<string>(widgetToDouble(input));
188 void doubleToWidget(QLineEdit * input, double value, char f, int prec)
191 loc.setNumberOptions(QLocale::OmitGroupSeparator);
192 input->setText(loc.toString(value, f, prec));
196 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
198 doubleToWidget(input, convert<double>(value), f, prec);
202 QString formatLocFPNumber(double d)
204 QString result = toqstr(formatFPNumber(d));
206 result.replace('.', loc.decimalPoint());
211 bool SortLocaleAware(QString const & lhs, QString const & rhs)
213 return QString::localeAwareCompare(lhs, rhs) < 0;
217 bool ColorSorter(ColorCode lhs, ColorCode rhs)
219 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
223 void setValid(QWidget * widget, bool valid)
226 widget->setPalette(QPalette());
228 QPalette pal = widget->palette();
229 pal.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0));
230 widget->setPalette(pal);
235 void focusAndHighlight(QAbstractItemView * w)
238 w->setCurrentIndex(w->currentIndex());
239 w->scrollTo(w->currentIndex());
243 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
245 QPalette pal = QApplication::palette();
246 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
247 pal.color(QPalette::Active, QPalette::Highlight));
248 for (QWidget * w : highlighted)
249 w->setPalette(newpal);
250 for (QWidget * w : plain)
255 /// wrapper to hide the change of method name to setSectionResizeMode
256 void setSectionResizeMode(QHeaderView * view,
257 int logicalIndex, QHeaderView::ResizeMode mode) {
258 #if (QT_VERSION >= 0x050000)
259 view->setSectionResizeMode(logicalIndex, mode);
261 view->setResizeMode(logicalIndex, mode);
265 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
266 #if (QT_VERSION >= 0x050000)
267 view->setSectionResizeMode(mode);
269 view->setResizeMode(mode);
273 void showDirectory(FileName const & directory)
275 if (!directory.exists())
277 QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
278 // Give hints in case of bugs
279 if (!qurl.isValid()) {
280 frontend::Alert::error(_("Invalid URL"),
281 bformat(_("The URL `%1$s' could not be resolved."),
282 qstring_to_ucs4(qurl.toString())));
286 if (!QDesktopServices::openUrl(qurl))
287 frontend::Alert::error(_("URL could not be accessed"),
288 bformat(_("The URL `%1$s' could not be opened although it exists!"),
289 qstring_to_ucs4(qurl.toString())));
292 void showTarget(string const & target, string const & pdfv, string const & psv)
294 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
295 if (prefixIs(target, "EXTERNAL ")) {
296 if (!lyxrc.citation_search)
298 string tmp, tar, opts;
299 tar = split(target, tmp, ' ');
301 opts = " -v " + pdfv;
303 opts += " -w " + psv;
307 string const command = lyxrc.citation_search_view + " " + opts + tar;
308 int const result = one.startscript(Systemcall::Wait, command);
311 frontend::Alert::error(_("Could not open file"),
312 _("The lyxpaperview script failed."));
313 else if (result == 2)
314 frontend::Alert::error(_("Could not open file"),
315 bformat(_("No file was found using the pattern `%1$s'."),
319 if (!QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode)))
320 frontend::Alert::error(_("Could not open file"),
321 bformat(_("The target `%1$s' could not be resolved."),
324 } // namespace frontend
326 QString const qt_(char const * str, const char *)
328 return toqstr(_(str));
332 QString const qt_(string const & str)
334 return toqstr(_(str));
338 QString const qt_(QString const & qstr)
340 return toqstr(_(fromqstr(qstr)));
344 void rescanTexStyles(string const & arg)
346 // Run rescan in user lyx directory
347 PathChanger p(package().user_support());
348 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
350 string const command = os::python() + ' ' +
351 quoteName(prog.toFilesystemEncoding()) + ' ' +
353 int const status = one.startscript(Systemcall::Wait, command);
357 frontend::Alert::error(_("Could not update TeX information"),
358 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
362 QStringList texFileList(QString const & filename)
365 FileName const file = libFileSearch(QString(), filename);
370 vector<docstring> doclist =
371 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
373 // Normalise paths like /foo//bar ==> /foo/bar
375 for (size_t i = 0; i != doclist.size(); ++i) {
376 QString qfile = toqstr(doclist[i]);
377 qfile.replace("\r", "");
378 while (qfile.contains("//"))
379 qfile.replace("//", "/");
380 if (!qfile.isEmpty())
385 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
386 return QList<QString>(set.begin(), set.end());
388 return QList<QString>::fromSet(set);
392 QString const externalLineEnding(docstring const & str)
395 // The MAC clipboard uses \r for lineendings, and we use \n
396 return toqstr(subst(str, '\n', '\r'));
397 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
398 // Windows clipboard uses \r\n for lineendings, and we use \n
399 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
406 docstring const internalLineEnding(QString const & str)
408 docstring const s = subst(qstring_to_ucs4(str),
409 from_ascii("\r\n"), from_ascii("\n"));
410 return subst(s, '\r', '\n');
414 QString internalPath(const QString & str)
416 return toqstr(os::internal_path(fromqstr(str)));
420 QString onlyFileName(const QString & str)
422 return toqstr(support::onlyFileName(fromqstr(str)));
426 QString onlyPath(const QString & str)
428 return toqstr(support::onlyPath(fromqstr(str)));
432 QString changeExtension(QString const & oldname, QString const & ext)
434 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
437 /// Remove the extension from \p name
438 QString removeExtension(QString const & name)
440 return toqstr(support::removeExtension(fromqstr(name)));
443 /** Add the extension \p ext to \p name.
444 Use this instead of changeExtension if you know that \p name is without
445 extension, because changeExtension would wrongly interpret \p name if it
448 QString addExtension(QString const & name, QString const & ext)
450 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
453 /// Return the extension of the file (not including the .)
454 QString getExtension(QString const & name)
456 return toqstr(support::getExtension(fromqstr(name)));
460 /** Convert relative path into absolute path based on a basepath.
461 If relpath is absolute, just use that.
462 If basepath doesn't exist use CWD.
464 QString makeAbsPath(QString const & relpath, QString const & base)
466 return toqstr(support::makeAbsPath(fromqstr(relpath),
467 fromqstr(base)).absFileName());
471 /////////////////////////////////////////////////////////////////////////
475 /////////////////////////////////////////////////////////////////////////
477 /** Given a string such as
478 * "<glob> <glob> ... *.{abc,def} <glob>",
479 * convert the csh-style brace expressions:
480 * "<glob> <glob> ... *.abc *.def <glob>".
481 * Requires no system support, so should work equally on Unix, Mac, Win32.
483 static string const convert_brace_glob(string const & glob)
485 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
486 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
487 static regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
488 // Matches "abc" and "abc,", storing "abc" as group 1,
489 // while ignoring surrounding spaces.
490 static regex const block_re(" *([^ ,}]+) *,? *");
494 string::const_iterator it = glob.begin();
495 string::const_iterator const end = glob.end();
497 match_results<string::const_iterator> what;
498 if (!regex_search(it, end, what, glob_re)) {
499 // Ensure that no information is lost.
500 pattern += string(it, end);
504 // Everything from the start of the input to
505 // the start of the match.
506 pattern += string(what[-1].first, what[-1].second);
508 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
509 string const head = string(what[1].first, what[1].second);
510 string const tail = string(what[2].first, what[2].second);
512 // Split the ','-separated chunks of tail so that
513 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
514 string const fmt = " " + head + "$1";
515 pattern += regex_replace(tail, block_re, fmt);
517 // Increment the iterator to the end of the match.
518 it += distance(it, what[0].second);
527 /* \param description text describing the filters.
528 * \param one or more wildcard patterns, separated by
531 Filter(docstring const & description, std::string const & globs);
533 docstring const & description() const { return desc_; }
535 QString toString() const;
538 std::vector<std::string> globs_;
542 Filter::Filter(docstring const & description, string const & globs)
545 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
546 // "<glob> <glob> ... *.abc *.def <glob>"
547 string const expanded_globs = convert_brace_glob(globs);
549 // Split into individual globs.
550 globs_ = getVectorFromString(expanded_globs, " ");
554 QString Filter::toString() const
558 bool const has_description = !desc_.empty();
560 if (has_description) {
565 s += toqstr(getStringFromVector(globs_, " "));
573 /** \c FileFilterList parses a Qt-style list of available file filters
574 * to generate the corresponding vector.
575 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
576 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
577 * will result in a vector of size 1 in which the description field is empty.
579 struct FileFilterList
581 // FIXME UNICODE: globs_ should be unicode...
582 /** \param qt_style_filter a list of available file filters.
583 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
584 * The "All files (*)" filter is always added to the list.
586 explicit FileFilterList(docstring const & qt_style_filter =
589 typedef std::vector<Filter>::size_type size_type;
591 bool empty() const { return filters_.empty(); }
592 size_type size() const { return filters_.size(); }
593 Filter & operator[](size_type i) { return filters_[i]; }
594 Filter const & operator[](size_type i) const { return filters_[i]; }
596 void parse_filter(std::string const & filter);
597 std::vector<Filter> filters_;
601 FileFilterList::FileFilterList(docstring const & qt_style_filter)
604 string const filter = to_utf8(qt_style_filter)
605 + (qt_style_filter.empty() ? string() : ";;")
606 + to_utf8(_("All Files "))
613 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
614 // into individual filters.
615 static regex const separator_re(";;");
617 string::const_iterator it = filter.begin();
618 string::const_iterator const end = filter.end();
620 match_results<string::const_iterator> what;
622 if (!regex_search(it, end, what, separator_re)) {
623 parse_filter(string(it, end));
627 // Everything from the start of the input to
628 // the start of the match.
629 parse_filter(string(it, what[0].first));
631 // Increment the iterator to the end of the match.
632 it += distance(it, what[0].second);
637 void FileFilterList::parse_filter(string const & filter)
639 // Matches "TeX documents (plain) (*.tex)",
640 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
641 static regex const filter_re("(.*)\\(([^()]+)\\) *$");
643 match_results<string::const_iterator> what;
644 if (!regex_search(filter, what, filter_re)) {
645 // Just a glob, no description.
646 filters_.push_back(Filter(docstring(), trim(filter)));
649 docstring const desc = from_utf8(string(what[1].first, what[1].second));
650 string const globs = string(what[2].first, what[2].second);
651 filters_.push_back(Filter(trim(desc), trim(globs)));
656 /** \returns the equivalent of the string passed in
657 * although any brace expressions are expanded.
658 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
660 QStringList fileFilters(QString const & desc)
662 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
663 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
664 FileFilterList filters(qstring_to_ucs4(desc));
665 //LYXERR0("DESC: " << desc);
667 for (size_t i = 0; i != filters.filters_.size(); ++i) {
668 QString f = filters.filters_[i].toString();
669 //LYXERR0("FILTER: " << f);
676 QString formatToolTip(QString text, int width)
678 // 1. QTooltip activates word wrapping only if mightBeRichText()
679 // is true. So we convert the text to rich text.
681 // 2. The default width is way too small. Setting the width is tricky; first
682 // one has to compute the ideal width, and then force it with special
685 // do nothing if empty or already formatted
686 if (text.isEmpty() || text.startsWith(QString("<html>")))
688 // Convert to rich text if it is not already
689 if (!Qt::mightBeRichText(text))
690 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
691 // Compute desired width in pixels
692 QFont const font = QToolTip::font();
693 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
694 int const px_width = width * QFontMetrics(font).horizontalAdvance("M");
696 int const px_width = width * QFontMetrics(font).width("M");
698 // Determine the ideal width of the tooltip
699 QTextDocument td("");
701 td.setDefaultFont(QToolTip::font());
702 td.setDocumentMargin(0);
703 td.setTextWidth(px_width);
704 double best_width = td.idealWidth();
705 // Set the line wrapping with appropriate width
706 return QString("<html><body><table><tr>"
707 "<td align=justify width=%1>%2</td>"
708 "</tr></table></body></html>")
709 .arg(QString::number(int(best_width) + 1), text);
713 QString qtHtmlToPlainText(QString const & text)
715 if (!Qt::mightBeRichText(text))
719 return td.toPlainText();