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 "FileDialog.h"
18 #include "LengthCombo.h"
20 #include "frontends/alert.h"
22 #include "BufferParams.h"
23 #include "FloatList.h"
24 #include "FuncRequest.h"
28 #include "LyXAction.h"
29 #include "TextClass.h"
31 #include "support/convert.h"
32 #include "support/debug.h"
33 #include "support/gettext.h"
34 #include "support/Length.h"
35 #include "support/lstrings.h"
36 #include "support/os.h"
37 #include "support/Package.h"
38 #include "support/PathChanger.h"
39 #include "support/Systemcall.h"
41 #include <QApplication>
44 #include <QDesktopServices>
50 #include <QTextLayout>
51 #include <QTextDocument>
64 using namespace lyx::support;
68 FileName libFileSearch(QString const & dir, QString const & name,
69 QString const & ext, search_mode mode)
71 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
75 FileName imageLibFileSearch(QString & dir, QString const & name,
76 QString const & ext, search_mode mode)
78 string tmp = fromqstr(dir);
79 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
86 double locstringToDouble(QString const & str)
90 double res = loc.toDouble(str, &ok);
93 QLocale c(QLocale::C);
94 res = c.toDouble(str);
104 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
106 QString const length = input->text();
107 if (length.isEmpty())
110 // Don't return unit-from-choice if the input(field) contains a unit
111 if (isValidGlueLength(fromqstr(length)))
112 return fromqstr(length);
114 Length::UNIT const unit = combo->currentLengthItem();
116 return Length(locstringToDouble(length.trimmed()), unit).asString();
120 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
122 QString const length = input->text();
123 if (length.isEmpty())
126 // don't return unit-from-choice if the input(field) contains a unit
127 if (isValidGlueLength(fromqstr(length)))
128 return Length(fromqstr(length));
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]);
139 return Length(locstringToDouble(length.trimmed()), unit);
143 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
144 Length const & len, Length::UNIT /*defaultUnit*/)
147 // no length (UNIT_NONE)
148 combo->setCurrentItem(Length::defaultUnit());
151 combo->setCurrentItem(len.unit());
153 loc.setNumberOptions(QLocale::OmitGroupSeparator);
154 input->setText(formatLocFPNumber(Length(len).value()));
159 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
160 string const & len, Length::UNIT defaultUnit)
163 // no length (UNIT_NONE)
164 combo->setCurrentItem(defaultUnit);
166 } else if (!isValidLength(len) && !isStrDbl(len)) {
167 // use input field only for gluelengths
168 combo->setCurrentItem(defaultUnit);
169 input->setText(toqstr(len));
171 lengthToWidgets(input, combo, Length(len), defaultUnit);
176 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
177 docstring const & len, Length::UNIT defaultUnit)
179 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
183 double widgetToDouble(QLineEdit const * input)
185 QString const text = input->text();
189 return locstringToDouble(text.trimmed());
193 string widgetToDoubleStr(QLineEdit const * input)
195 return convert<string>(widgetToDouble(input));
199 void doubleToWidget(QLineEdit * input, double value, char f, int prec)
202 loc.setNumberOptions(QLocale::OmitGroupSeparator);
203 input->setText(loc.toString(value, f, prec));
207 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
209 doubleToWidget(input, convert<double>(value), f, prec);
213 QString formatLocFPNumber(double d)
215 QString result = toqstr(formatFPNumber(d));
217 result.replace('.', loc.decimalPoint());
222 bool SortLocaleAware(QString const & lhs, QString const & rhs)
224 return QString::localeAwareCompare(lhs, rhs) < 0;
228 bool ColorSorter(ColorCode lhs, ColorCode rhs)
230 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
234 void setValid(QWidget * widget, bool valid)
237 widget->setPalette(QPalette());
239 QPalette pal = widget->palette();
240 pal.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0));
241 widget->setPalette(pal);
246 void focusAndHighlight(QAbstractItemView * w)
249 w->setCurrentIndex(w->currentIndex());
250 w->scrollTo(w->currentIndex());
254 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
256 QPalette pal = QApplication::palette();
257 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
258 pal.color(QPalette::Active, QPalette::Highlight));
259 for (QWidget * w : highlighted)
260 w->setPalette(newpal);
261 for (QWidget * w : plain)
266 /// wrapper to hide the change of method name to setSectionResizeMode
267 void setSectionResizeMode(QHeaderView * view,
268 int logicalIndex, QHeaderView::ResizeMode mode) {
269 #if (QT_VERSION >= 0x050000)
270 view->setSectionResizeMode(logicalIndex, mode);
272 view->setResizeMode(logicalIndex, mode);
276 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
277 #if (QT_VERSION >= 0x050000)
278 view->setSectionResizeMode(mode);
280 view->setResizeMode(mode);
284 void showDirectory(FileName const & directory)
286 if (!directory.exists())
288 QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
289 // Give hints in case of bugs
290 if (!qurl.isValid()) {
291 frontend::Alert::error(_("Invalid URL"),
292 bformat(_("The URL `%1$s' could not be resolved."),
293 qstring_to_ucs4(qurl.toString())));
297 if (!QDesktopServices::openUrl(qurl))
298 frontend::Alert::error(_("URL could not be accessed"),
299 bformat(_("The URL `%1$s' could not be opened although it exists!"),
300 qstring_to_ucs4(qurl.toString())));
303 void showTarget(string const & target, string const & pdfv, string const & psv)
305 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
306 if (prefixIs(target, "EXTERNAL ")) {
307 if (!lyxrc.citation_search)
309 string tmp, tar, opts;
310 tar = split(target, tmp, ' ');
312 opts = " -v " + pdfv;
314 opts += " -w " + psv;
318 string const command = lyxrc.citation_search_view + " " + opts + tar;
319 int const result = one.startscript(Systemcall::Wait, command);
322 frontend::Alert::error(_("Could not open file"),
323 _("The lyxpaperview script failed."));
324 else if (result == 2)
325 frontend::Alert::error(_("Could not open file"),
326 bformat(_("No file was found using the pattern `%1$s'."),
330 if (!QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode)))
331 frontend::Alert::error(_("Could not open file"),
332 bformat(_("The target `%1$s' could not be resolved."),
335 } // namespace frontend
337 QString const qt_(char const * str, const char *)
339 return toqstr(_(str));
343 QString const qt_(string const & str)
345 return toqstr(_(str));
349 QString const qt_(QString const & qstr)
351 return toqstr(_(fromqstr(qstr)));
355 void rescanTexStyles(string const & arg)
357 // Run rescan in user lyx directory
358 PathChanger p(package().user_support());
359 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
361 string const command = os::python() + ' ' +
362 quoteName(prog.toFilesystemEncoding()) + ' ' +
364 int const status = one.startscript(Systemcall::Wait, command);
368 frontend::Alert::error(_("Could not update TeX information"),
369 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
373 QStringList texFileList(QString const & filename)
376 FileName const file = libFileSearch(QString(), filename);
381 vector<docstring> doclist =
382 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
384 // Normalise paths like /foo//bar ==> /foo/bar
386 for (size_t i = 0; i != doclist.size(); ++i) {
387 QString qfile = toqstr(doclist[i]);
388 qfile.replace("\r", "");
389 while (qfile.contains("//"))
390 qfile.replace("//", "/");
391 if (!qfile.isEmpty())
396 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
397 return QList<QString>(set.begin(), set.end());
399 return QList<QString>::fromSet(set);
403 QString const externalLineEnding(docstring const & str)
406 // The MAC clipboard uses \r for lineendings, and we use \n
407 return toqstr(subst(str, '\n', '\r'));
408 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
409 // Windows clipboard uses \r\n for lineendings, and we use \n
410 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
417 docstring const internalLineEnding(QString const & str)
419 docstring const s = subst(qstring_to_ucs4(str),
420 from_ascii("\r\n"), from_ascii("\n"));
421 return subst(s, '\r', '\n');
425 QString internalPath(const QString & str)
427 return toqstr(os::internal_path(fromqstr(str)));
431 QString onlyFileName(const QString & str)
433 return toqstr(support::onlyFileName(fromqstr(str)));
437 QString onlyPath(const QString & str)
439 return toqstr(support::onlyPath(fromqstr(str)));
443 QString changeExtension(QString const & oldname, QString const & ext)
445 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
448 /// Remove the extension from \p name
449 QString removeExtension(QString const & name)
451 return toqstr(support::removeExtension(fromqstr(name)));
454 /** Add the extension \p ext to \p name.
455 Use this instead of changeExtension if you know that \p name is without
456 extension, because changeExtension would wrongly interpret \p name if it
459 QString addExtension(QString const & name, QString const & ext)
461 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
464 /// Return the extension of the file (not including the .)
465 QString getExtension(QString const & name)
467 return toqstr(support::getExtension(fromqstr(name)));
471 /** Convert relative path into absolute path based on a basepath.
472 If relpath is absolute, just use that.
473 If basepath doesn't exist use CWD.
475 QString makeAbsPath(QString const & relpath, QString const & base)
477 return toqstr(support::makeAbsPath(fromqstr(relpath),
478 fromqstr(base)).absFileName());
482 /////////////////////////////////////////////////////////////////////////
486 /////////////////////////////////////////////////////////////////////////
488 /** Given a string such as
489 * "<glob> <glob> ... *.{abc,def} <glob>",
490 * convert the csh-style brace expressions:
491 * "<glob> <glob> ... *.abc *.def <glob>".
492 * Requires no system support, so should work equally on Unix, Mac, Win32.
494 static string const convert_brace_glob(string const & glob)
496 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
497 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
498 static regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
499 // Matches "abc" and "abc,", storing "abc" as group 1,
500 // while ignoring surrounding spaces.
501 static regex const block_re(" *([^ ,}]+) *,? *");
505 string::const_iterator it = glob.begin();
506 string::const_iterator const end = glob.end();
508 match_results<string::const_iterator> what;
509 if (!regex_search(it, end, what, glob_re)) {
510 // Ensure that no information is lost.
511 pattern += string(it, end);
515 // Everything from the start of the input to
516 // the start of the match.
517 pattern += string(what[-1].first, what[-1].second);
519 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
520 string const head = string(what[1].first, what[1].second);
521 string const tail = string(what[2].first, what[2].second);
523 // Split the ','-separated chunks of tail so that
524 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
525 string const fmt = " " + head + "$1";
526 pattern += regex_replace(tail, block_re, fmt);
528 // Increment the iterator to the end of the match.
529 it += distance(it, what[0].second);
538 /* \param description text describing the filters.
539 * \param one or more wildcard patterns, separated by
542 Filter(docstring const & description, std::string const & globs);
544 docstring const & description() const { return desc_; }
546 QString toString() const;
549 std::vector<std::string> globs_;
553 Filter::Filter(docstring const & description, string const & globs)
556 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
557 // "<glob> <glob> ... *.abc *.def <glob>"
558 string const expanded_globs = convert_brace_glob(globs);
560 // Split into individual globs.
561 globs_ = getVectorFromString(expanded_globs, " ");
565 QString Filter::toString() const
569 bool const has_description = !desc_.empty();
571 if (has_description) {
576 s += toqstr(getStringFromVector(globs_, " "));
584 /** \c FileFilterList parses a Qt-style list of available file filters
585 * to generate the corresponding vector.
586 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
587 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
588 * will result in a vector of size 1 in which the description field is empty.
590 struct FileFilterList
592 // FIXME UNICODE: globs_ should be unicode...
593 /** \param qt_style_filter a list of available file filters.
594 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
595 * The "All files (*)" filter is always added to the list.
597 explicit FileFilterList(docstring const & qt_style_filter =
600 typedef std::vector<Filter>::size_type size_type;
602 bool empty() const { return filters_.empty(); }
603 size_type size() const { return filters_.size(); }
604 Filter & operator[](size_type i) { return filters_[i]; }
605 Filter const & operator[](size_type i) const { return filters_[i]; }
607 void parse_filter(std::string const & filter);
608 std::vector<Filter> filters_;
612 FileFilterList::FileFilterList(docstring const & qt_style_filter)
615 string const filter = to_utf8(qt_style_filter)
616 + (qt_style_filter.empty() ? string() : ";;")
617 + to_utf8(_("All Files "))
624 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
625 // into individual filters.
626 static regex const separator_re(";;");
628 string::const_iterator it = filter.begin();
629 string::const_iterator const end = filter.end();
631 match_results<string::const_iterator> what;
633 if (!regex_search(it, end, what, separator_re)) {
634 parse_filter(string(it, end));
638 // Everything from the start of the input to
639 // the start of the match.
640 parse_filter(string(it, what[0].first));
642 // Increment the iterator to the end of the match.
643 it += distance(it, what[0].second);
648 void FileFilterList::parse_filter(string const & filter)
650 // Matches "TeX documents (plain) (*.tex)",
651 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
652 static regex const filter_re("(.*)\\(([^()]+)\\) *$");
654 match_results<string::const_iterator> what;
655 if (!regex_search(filter, what, filter_re)) {
656 // Just a glob, no description.
657 filters_.push_back(Filter(docstring(), trim(filter)));
660 docstring const desc = from_utf8(string(what[1].first, what[1].second));
661 string const globs = string(what[2].first, what[2].second);
662 filters_.push_back(Filter(trim(desc), trim(globs)));
667 /** \returns the equivalent of the string passed in
668 * although any brace expressions are expanded.
669 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
671 QStringList fileFilters(QString const & desc)
673 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
674 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
675 FileFilterList filters(qstring_to_ucs4(desc));
676 //LYXERR0("DESC: " << desc);
678 for (size_t i = 0; i != filters.filters_.size(); ++i) {
679 QString f = filters.filters_[i].toString();
680 //LYXERR0("FILTER: " << f);
687 QString formatToolTip(QString text, int width)
689 // 1. QTooltip activates word wrapping only if mightBeRichText()
690 // is true. So we convert the text to rich text.
692 // 2. The default width is way too small. Setting the width is tricky; first
693 // one has to compute the ideal width, and then force it with special
696 // do nothing if empty or already formatted
697 if (text.isEmpty() || text.startsWith(QString("<html>")))
699 // Convert to rich text if it is not already
700 if (!Qt::mightBeRichText(text))
701 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
702 // Compute desired width in pixels
703 QFont const font = QToolTip::font();
704 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
705 int const px_width = width * QFontMetrics(font).horizontalAdvance("M");
707 int const px_width = width * QFontMetrics(font).width("M");
709 // Determine the ideal width of the tooltip
710 QTextDocument td("");
712 td.setDefaultFont(QToolTip::font());
713 td.setDocumentMargin(0);
714 td.setTextWidth(px_width);
715 double best_width = td.idealWidth();
716 // Set the line wrapping with appropriate width
717 return QString("<html><body><table><tr>"
718 "<td align=justify width=%1>%2</td>"
719 "</tr></table></body></html>")
720 .arg(QString::number(int(best_width) + 1), text);
724 QString qtHtmlToPlainText(QString const & text)
726 if (!Qt::mightBeRichText(text))
730 return td.toPlainText();