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
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/lyxalgo.h"
37 #include "support/os.h"
38 #include "support/Package.h"
39 #include "support/PathChanger.h"
40 #include "support/Systemcall.h"
42 #include <QApplication>
45 #include <QDesktopServices>
51 #include <QTextLayout>
52 #include <QTextDocument>
62 #include "support/regex.h"
66 using namespace lyx::support;
70 FileName libFileSearch(QString const & dir, QString const & name,
71 QString const & ext, search_mode mode)
73 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
77 FileName imageLibFileSearch(QString & dir, QString const & name,
78 QString const & ext, search_mode mode)
80 string tmp = fromqstr(dir);
81 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
88 double locstringToDouble(QString const & str)
92 double res = loc.toDouble(str, &ok);
95 QLocale c(QLocale::C);
96 res = c.toDouble(str);
106 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
108 QString const length = input->text();
109 if (length.isEmpty())
112 // Don't return unit-from-choice if the input(field) contains a unit
113 if (isValidGlueLength(fromqstr(length)))
114 return fromqstr(length);
116 Length::UNIT const unit = combo->currentLengthItem();
118 return Length(locstringToDouble(length.trimmed()), unit).asString();
122 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
124 QString const length = input->text();
125 if (length.isEmpty())
128 // don't return unit-from-choice if the input(field) contains a unit
129 if (isValidGlueLength(fromqstr(length)))
130 return Length(fromqstr(length));
132 Length::UNIT unit = Length::UNIT_NONE;
133 QString const item = combo->currentText();
134 for (int i = 0; i < num_units; i++) {
135 if (qt_(lyx::unit_name_gui[i]) == item) {
136 unit = unitFromString(unit_name[i]);
141 return Length(locstringToDouble(length.trimmed()), unit);
145 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
146 Length const & len, Length::UNIT /*defaultUnit*/)
149 // no length (UNIT_NONE)
150 combo->setCurrentItem(Length::defaultUnit());
153 combo->setCurrentItem(len.unit());
155 loc.setNumberOptions(QLocale::OmitGroupSeparator);
156 input->setText(formatLocFPNumber(Length(len).value()));
161 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
162 string const & len, Length::UNIT defaultUnit)
165 // no length (UNIT_NONE)
166 combo->setCurrentItem(defaultUnit);
168 } else if (!isValidLength(len) && !isStrDbl(len)) {
169 // use input field only for gluelengths
170 combo->setCurrentItem(defaultUnit);
171 input->setText(toqstr(len));
173 lengthToWidgets(input, combo, Length(len), defaultUnit);
178 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
179 docstring const & len, Length::UNIT defaultUnit)
181 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
185 double widgetToDouble(QLineEdit const * input)
187 QString const text = input->text();
191 return locstringToDouble(text.trimmed());
195 string widgetToDoubleStr(QLineEdit const * input)
197 return convert<string>(widgetToDouble(input));
201 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
204 loc.setNumberOptions(QLocale::OmitGroupSeparator);
205 input->setText(loc.toString(value, f, prec));
209 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
211 doubleToWidget(input, convert<double>(value), f, prec);
215 QString formatLocFPNumber(double d)
217 QString result = toqstr(formatFPNumber(d));
219 result.replace('.', loc.decimalPoint());
224 bool SortLocaleAware(QString const & lhs, QString const & rhs)
226 return QString::localeAwareCompare(lhs, rhs) < 0;
230 bool ColorSorter(ColorCode lhs, ColorCode rhs)
232 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
236 void setValid(QWidget * widget, bool valid)
239 widget->setPalette(QPalette());
241 QPalette pal = widget->palette();
242 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
243 widget->setPalette(pal);
248 void focusAndHighlight(QAbstractItemView * w)
251 w->setCurrentIndex(w->currentIndex());
252 w->scrollTo(w->currentIndex());
256 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
258 QPalette pal = QApplication::palette();
259 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
260 pal.color(QPalette::Active, QPalette::Highlight));
261 for (QWidget * w : highlighted)
262 w->setPalette(newpal);
263 for (QWidget * w : plain)
268 /// wrapper to hide the change of method name to setSectionResizeMode
269 void setSectionResizeMode(QHeaderView * view,
270 int logicalIndex, QHeaderView::ResizeMode mode) {
271 #if (QT_VERSION >= 0x050000)
272 view->setSectionResizeMode(logicalIndex, mode);
274 view->setResizeMode(logicalIndex, mode);
278 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
279 #if (QT_VERSION >= 0x050000)
280 view->setSectionResizeMode(mode);
282 view->setResizeMode(mode);
286 void showDirectory(FileName const & directory)
288 if (!directory.exists())
290 QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
291 // Give hints in case of bugs
292 if (!qurl.isValid()) {
293 frontend::Alert::error(_("Invalid URL"),
294 bformat(_("The URL `%1$s' could not be resolved."),
295 qstring_to_ucs4(qurl.toString())));
299 if (!QDesktopServices::openUrl(qurl))
300 frontend::Alert::error(_("URL could not be accessed"),
301 bformat(_("The URL `%1$s' could not be opened although it exists!"),
302 qstring_to_ucs4(qurl.toString())));
305 void showTarget(string const & target, string const & pdfv, string const & psv)
307 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
308 if (prefixIs(target, "EXTERNAL ")) {
309 if (!lyxrc.citation_search)
311 string tmp, tar, opts;
312 tar = split(target, tmp, ' ');
314 opts = " -v " + pdfv;
316 opts += " -w " + psv;
320 string const command = lyxrc.citation_search_view + " " + opts + tar;
321 int const result = one.startscript(Systemcall::Wait, command);
324 frontend::Alert::error(_("Could not open file"),
325 _("The lyxpaperview script failed."));
326 else if (result == 2)
327 frontend::Alert::error(_("Could not open file"),
328 bformat(_("No file was found using the pattern `%1$s'."),
332 if (!QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode)))
333 frontend::Alert::error(_("Could not open file"),
334 bformat(_("The target `%1$s' could not be resolved."),
337 } // namespace frontend
339 QString const qt_(char const * str, const char *)
341 return toqstr(_(str));
345 QString const qt_(string const & str)
347 return toqstr(_(str));
351 QString const qt_(QString const & qstr)
353 return toqstr(_(fromqstr(qstr)));
357 void rescanTexStyles(string const & arg)
359 // Run rescan in user lyx directory
360 PathChanger p(package().user_support());
361 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
363 string const command = os::python() + ' ' +
364 quoteName(prog.toFilesystemEncoding()) + ' ' +
366 int const status = one.startscript(Systemcall::Wait, command);
370 frontend::Alert::error(_("Could not update TeX information"),
371 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
375 QStringList texFileList(QString const & filename)
378 FileName const file = libFileSearch(QString(), filename);
383 vector<docstring> doclist =
384 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
386 // Normalise paths like /foo//bar ==> /foo/bar
388 for (size_t i = 0; i != doclist.size(); ++i) {
389 QString qfile = toqstr(doclist[i]);
390 qfile.replace("\r", "");
391 while (qfile.contains("//"))
392 qfile.replace("//", "/");
393 if (!qfile.isEmpty())
398 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
399 return QList<QString>(set.begin(), set.end());
401 return QList<QString>::fromSet(set);
405 QString const externalLineEnding(docstring const & str)
408 // The MAC clipboard uses \r for lineendings, and we use \n
409 return toqstr(subst(str, '\n', '\r'));
410 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
411 // Windows clipboard uses \r\n for lineendings, and we use \n
412 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
419 docstring const internalLineEnding(QString const & str)
421 docstring const s = subst(qstring_to_ucs4(str),
422 from_ascii("\r\n"), from_ascii("\n"));
423 return subst(s, '\r', '\n');
427 QString internalPath(const QString & str)
429 return toqstr(os::internal_path(fromqstr(str)));
433 QString onlyFileName(const QString & str)
435 return toqstr(support::onlyFileName(fromqstr(str)));
439 QString onlyPath(const QString & str)
441 return toqstr(support::onlyPath(fromqstr(str)));
445 QString changeExtension(QString const & oldname, QString const & ext)
447 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
450 /// Remove the extension from \p name
451 QString removeExtension(QString const & name)
453 return toqstr(support::removeExtension(fromqstr(name)));
456 /** Add the extension \p ext to \p name.
457 Use this instead of changeExtension if you know that \p name is without
458 extension, because changeExtension would wrongly interpret \p name if it
461 QString addExtension(QString const & name, QString const & ext)
463 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
466 /// Return the extension of the file (not including the .)
467 QString getExtension(QString const & name)
469 return toqstr(support::getExtension(fromqstr(name)));
473 /** Convert relative path into absolute path based on a basepath.
474 If relpath is absolute, just use that.
475 If basepath doesn't exist use CWD.
477 QString makeAbsPath(QString const & relpath, QString const & base)
479 return toqstr(support::makeAbsPath(fromqstr(relpath),
480 fromqstr(base)).absFileName());
484 /////////////////////////////////////////////////////////////////////////
488 /////////////////////////////////////////////////////////////////////////
490 /** Given a string such as
491 * "<glob> <glob> ... *.{abc,def} <glob>",
492 * convert the csh-style brace expressions:
493 * "<glob> <glob> ... *.abc *.def <glob>".
494 * Requires no system support, so should work equally on Unix, Mac, Win32.
496 static string const convert_brace_glob(string const & glob)
498 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
499 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
500 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
501 // Matches "abc" and "abc,", storing "abc" as group 1,
502 // while ignoring surrounding spaces.
503 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
507 string::const_iterator it = glob.begin();
508 string::const_iterator const end = glob.end();
510 match_results<string::const_iterator> what;
511 if (!regex_search(it, end, what, glob_re)) {
512 // Ensure that no information is lost.
513 pattern += string(it, end);
517 // Everything from the start of the input to
518 // the start of the match.
519 pattern += string(what[-1].first, what[-1].second);
521 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
522 string const head = string(what[1].first, what[1].second);
523 string const tail = string(what[2].first, what[2].second);
525 // Split the ','-separated chunks of tail so that
526 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
527 string const fmt = " " + head + "$1";
528 pattern += regex_replace(tail, block_re, fmt);
530 // Increment the iterator to the end of the match.
531 it += distance(it, what[0].second);
540 /* \param description text describing the filters.
541 * \param one or more wildcard patterns, separated by
544 Filter(docstring const & description, std::string const & globs);
546 docstring const & description() const { return desc_; }
548 QString toString() const;
551 std::vector<std::string> globs_;
555 Filter::Filter(docstring const & description, string const & globs)
558 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
559 // "<glob> <glob> ... *.abc *.def <glob>"
560 string const expanded_globs = convert_brace_glob(globs);
562 // Split into individual globs.
563 globs_ = getVectorFromString(expanded_globs, " ");
567 QString Filter::toString() const
571 bool const has_description = !desc_.empty();
573 if (has_description) {
578 s += toqstr(getStringFromVector(globs_, " "));
586 /** \c FileFilterList parses a Qt-style list of available file filters
587 * to generate the corresponding vector.
588 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
589 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
590 * will result in a vector of size 1 in which the description field is empty.
592 struct FileFilterList
594 // FIXME UNICODE: globs_ should be unicode...
595 /** \param qt_style_filter a list of available file filters.
596 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
597 * The "All files (*)" filter is always added to the list.
599 explicit FileFilterList(docstring const & qt_style_filter =
602 typedef std::vector<Filter>::size_type size_type;
604 bool empty() const { return filters_.empty(); }
605 size_type size() const { return filters_.size(); }
606 Filter & operator[](size_type i) { return filters_[i]; }
607 Filter const & operator[](size_type i) const { return filters_[i]; }
609 void parse_filter(std::string const & filter);
610 std::vector<Filter> filters_;
614 FileFilterList::FileFilterList(docstring const & qt_style_filter)
617 string const filter = to_utf8(qt_style_filter)
618 + (qt_style_filter.empty() ? string() : ";;")
619 + to_utf8(_("All Files "))
626 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
627 // into individual filters.
628 static lyx::regex const separator_re(";;");
630 string::const_iterator it = filter.begin();
631 string::const_iterator const end = filter.end();
633 match_results<string::const_iterator> what;
635 if (!lyx::regex_search(it, end, what, separator_re)) {
636 parse_filter(string(it, end));
640 // Everything from the start of the input to
641 // the start of the match.
642 parse_filter(string(it, what[0].first));
644 // Increment the iterator to the end of the match.
645 it += distance(it, what[0].second);
650 void FileFilterList::parse_filter(string const & filter)
652 // Matches "TeX documents (plain) (*.tex)",
653 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
654 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
656 match_results<string::const_iterator> what;
657 if (!lyx::regex_search(filter, what, filter_re)) {
658 // Just a glob, no description.
659 filters_.push_back(Filter(docstring(), trim(filter)));
662 docstring const desc = from_utf8(string(what[1].first, what[1].second));
663 string const globs = string(what[2].first, what[2].second);
664 filters_.push_back(Filter(trim(desc), trim(globs)));
669 /** \returns the equivalent of the string passed in
670 * although any brace expressions are expanded.
671 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
673 QStringList fileFilters(QString const & desc)
675 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
676 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
677 FileFilterList filters(qstring_to_ucs4(desc));
678 //LYXERR0("DESC: " << desc);
680 for (size_t i = 0; i != filters.filters_.size(); ++i) {
681 QString f = filters.filters_[i].toString();
682 //LYXERR0("FILTER: " << f);
689 QString formatToolTip(QString text, int em)
691 // 1. QTooltip activates word wrapping only if mightBeRichText()
692 // is true. So we convert the text to rich text.
694 // 2. The default width is way too small. Setting the width is tricky; first
695 // one has to compute the ideal width, and then force it with special
698 // do nothing if empty or already formatted
699 if (text.isEmpty() || text.startsWith(QString("<html>")))
701 // Convert to rich text if it is not already
702 if (!Qt::mightBeRichText(text))
703 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
704 // Compute desired width in pixels
705 QFont const font = QToolTip::font();
706 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
707 int const px_width = em * QFontMetrics(font).horizontalAdvance("M");
709 int const px_width = em * QFontMetrics(font).width("M");
711 // Determine the ideal width of the tooltip
712 QTextDocument td("");
714 td.setDefaultFont(QToolTip::font());
715 td.setDocumentMargin(0);
716 td.setTextWidth(px_width);
717 double best_width = td.idealWidth();
718 // Set the line wrapping with appropriate width
719 return QString("<html><body><table><tr>"
720 "<td align=justify width=%1>%2</td>"
721 "</tr></table></body></html>")
722 .arg(QString::number(int(best_width) + 1), text);
726 QString qtHtmlToPlainText(QString const & html)
728 if (!Qt::mightBeRichText(html))
732 return td.toPlainText();