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"
26 #include "TextClass.h"
28 #include "support/convert.h"
29 #include "support/debug.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
32 #include "support/lyxalgo.h"
33 #include "support/os.h"
34 #include "support/Package.h"
35 #include "support/PathChanger.h"
36 #include "support/Systemcall.h"
38 #include <QApplication>
45 #include <QTextLayout>
46 #include <QTextDocument>
55 #include "support/regex.h"
59 using namespace lyx::support;
63 FileName libFileSearch(QString const & dir, QString const & name,
64 QString const & ext, search_mode mode)
66 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
70 FileName imageLibFileSearch(QString & dir, QString const & name,
71 QString const & ext, search_mode mode)
73 string tmp = fromqstr(dir);
74 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
81 double locstringToDouble(QString const & str)
85 double res = loc.toDouble(str, &ok);
88 QLocale c(QLocale::C);
89 res = c.toDouble(str);
99 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
101 QString const length = input->text();
102 if (length.isEmpty())
105 // Don't return unit-from-choice if the input(field) contains a unit
106 if (isValidGlueLength(fromqstr(length)))
107 return fromqstr(length);
109 Length::UNIT const unit = combo->currentLengthItem();
111 return Length(locstringToDouble(length.trimmed()), unit).asString();
115 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
117 QString const length = input->text();
118 if (length.isEmpty())
121 // don't return unit-from-choice if the input(field) contains a unit
122 if (isValidGlueLength(fromqstr(length)))
123 return Length(fromqstr(length));
125 Length::UNIT unit = Length::UNIT_NONE;
126 QString const item = combo->currentText();
127 for (int i = 0; i < num_units; i++) {
128 if (qt_(lyx::unit_name_gui[i]) == item) {
129 unit = unitFromString(unit_name[i]);
134 return Length(locstringToDouble(length.trimmed()), unit);
138 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
139 Length const & len, Length::UNIT /*defaultUnit*/)
142 // no length (UNIT_NONE)
143 combo->setCurrentItem(Length::defaultUnit());
146 combo->setCurrentItem(len.unit());
148 loc.setNumberOptions(QLocale::OmitGroupSeparator);
149 input->setText(formatLocFPNumber(Length(len).value()));
154 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
155 string const & len, Length::UNIT defaultUnit)
158 // no length (UNIT_NONE)
159 combo->setCurrentItem(defaultUnit);
161 } else if (!isValidLength(len) && !isStrDbl(len)) {
162 // use input field only for gluelengths
163 combo->setCurrentItem(defaultUnit);
164 input->setText(toqstr(len));
166 lengthToWidgets(input, combo, Length(len), defaultUnit);
171 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
172 docstring const & len, Length::UNIT defaultUnit)
174 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
178 double widgetToDouble(QLineEdit const * input)
180 QString const text = input->text();
184 return locstringToDouble(text.trimmed());
188 string widgetToDoubleStr(QLineEdit const * input)
190 return convert<string>(widgetToDouble(input));
194 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
197 loc.setNumberOptions(QLocale::OmitGroupSeparator);
198 input->setText(loc.toString(value, f, prec));
202 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
204 doubleToWidget(input, convert<double>(value), f, prec);
208 QString formatLocFPNumber(double d)
210 QString result = toqstr(formatFPNumber(d));
212 result.replace('.', loc.decimalPoint());
217 bool SortLocaleAware(QString const & lhs, QString const & rhs)
219 return QString::localeAwareCompare(lhs, rhs) < 0;
223 bool ColorSorter(ColorCode lhs, ColorCode rhs)
225 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
229 void setValid(QWidget * widget, bool valid)
232 widget->setPalette(QPalette());
234 QPalette pal = widget->palette();
235 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
236 widget->setPalette(pal);
241 void focusAndHighlight(QAbstractItemView * w)
244 w->setCurrentIndex(w->currentIndex());
245 w->scrollTo(w->currentIndex());
249 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
251 QPalette pal = QApplication::palette();
252 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
253 pal.color(QPalette::Active, QPalette::Highlight));
254 for (QWidget * w : highlighted)
255 w->setPalette(newpal);
256 for (QWidget * w : plain)
261 /// wrapper to hide the change of method name to setSectionResizeMode
262 void setSectionResizeMode(QHeaderView * view,
263 int logicalIndex, QHeaderView::ResizeMode mode) {
264 #if (QT_VERSION >= 0x050000)
265 view->setSectionResizeMode(logicalIndex, mode);
267 view->setResizeMode(logicalIndex, mode);
271 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
272 #if (QT_VERSION >= 0x050000)
273 view->setSectionResizeMode(mode);
275 view->setResizeMode(mode);
278 } // namespace frontend
280 QString const qt_(char const * str, const char *)
282 return toqstr(_(str));
286 QString const qt_(string const & str)
288 return toqstr(_(str));
292 QString const qt_(QString const & qstr)
294 return toqstr(_(fromqstr(qstr)));
298 void rescanTexStyles(string const & arg)
300 // Run rescan in user lyx directory
301 PathChanger p(package().user_support());
302 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
304 string const command = os::python() + ' ' +
305 quoteName(prog.toFilesystemEncoding()) + ' ' +
307 int const status = one.startscript(Systemcall::Wait, command);
311 frontend::Alert::error(_("Could not update TeX information"),
312 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
316 QStringList texFileList(QString const & filename)
319 FileName const file = libFileSearch(QString(), filename);
324 vector<docstring> doclist =
325 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
327 // Normalise paths like /foo//bar ==> /foo/bar
329 for (size_t i = 0; i != doclist.size(); ++i) {
330 QString qfile = toqstr(doclist[i]);
331 qfile.replace("\r", "");
332 while (qfile.contains("//"))
333 qfile.replace("//", "/");
334 if (!qfile.isEmpty())
339 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
340 return QList<QString>(set.begin(), set.end());
342 return QList<QString>::fromSet(set);
346 QString const externalLineEnding(docstring const & str)
349 // The MAC clipboard uses \r for lineendings, and we use \n
350 return toqstr(subst(str, '\n', '\r'));
351 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
352 // Windows clipboard uses \r\n for lineendings, and we use \n
353 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
360 docstring const internalLineEnding(QString const & str)
362 docstring const s = subst(qstring_to_ucs4(str),
363 from_ascii("\r\n"), from_ascii("\n"));
364 return subst(s, '\r', '\n');
368 QString internalPath(const QString & str)
370 return toqstr(os::internal_path(fromqstr(str)));
374 QString onlyFileName(const QString & str)
376 return toqstr(support::onlyFileName(fromqstr(str)));
380 QString onlyPath(const QString & str)
382 return toqstr(support::onlyPath(fromqstr(str)));
386 QString changeExtension(QString const & oldname, QString const & ext)
388 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
391 /// Remove the extension from \p name
392 QString removeExtension(QString const & name)
394 return toqstr(support::removeExtension(fromqstr(name)));
397 /** Add the extension \p ext to \p name.
398 Use this instead of changeExtension if you know that \p name is without
399 extension, because changeExtension would wrongly interpret \p name if it
402 QString addExtension(QString const & name, QString const & ext)
404 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
407 /// Return the extension of the file (not including the .)
408 QString getExtension(QString const & name)
410 return toqstr(support::getExtension(fromqstr(name)));
414 /** Convert relative path into absolute path based on a basepath.
415 If relpath is absolute, just use that.
416 If basepath doesn't exist use CWD.
418 QString makeAbsPath(QString const & relpath, QString const & base)
420 return toqstr(support::makeAbsPath(fromqstr(relpath),
421 fromqstr(base)).absFileName());
425 /////////////////////////////////////////////////////////////////////////
429 /////////////////////////////////////////////////////////////////////////
431 /** Given a string such as
432 * "<glob> <glob> ... *.{abc,def} <glob>",
433 * convert the csh-style brace expressions:
434 * "<glob> <glob> ... *.abc *.def <glob>".
435 * Requires no system support, so should work equally on Unix, Mac, Win32.
437 static string const convert_brace_glob(string const & glob)
439 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
440 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
441 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
442 // Matches "abc" and "abc,", storing "abc" as group 1,
443 // while ignoring surrounding spaces.
444 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
448 string::const_iterator it = glob.begin();
449 string::const_iterator const end = glob.end();
451 match_results<string::const_iterator> what;
452 if (!regex_search(it, end, what, glob_re)) {
453 // Ensure that no information is lost.
454 pattern += string(it, end);
458 // Everything from the start of the input to
459 // the start of the match.
460 pattern += string(what[-1].first, what[-1].second);
462 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
463 string const head = string(what[1].first, what[1].second);
464 string const tail = string(what[2].first, what[2].second);
466 // Split the ','-separated chunks of tail so that
467 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
468 string const fmt = " " + head + "$1";
469 pattern += regex_replace(tail, block_re, fmt);
471 // Increment the iterator to the end of the match.
472 it += distance(it, what[0].second);
481 /* \param description text describing the filters.
482 * \param one or more wildcard patterns, separated by
485 Filter(docstring const & description, std::string const & globs);
487 docstring const & description() const { return desc_; }
489 QString toString() const;
492 std::vector<std::string> globs_;
496 Filter::Filter(docstring const & description, string const & globs)
499 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
500 // "<glob> <glob> ... *.abc *.def <glob>"
501 string const expanded_globs = convert_brace_glob(globs);
503 // Split into individual globs.
504 globs_ = getVectorFromString(expanded_globs, " ");
508 QString Filter::toString() const
512 bool const has_description = !desc_.empty();
514 if (has_description) {
519 s += toqstr(getStringFromVector(globs_, " "));
527 /** \c FileFilterList parses a Qt-style list of available file filters
528 * to generate the corresponding vector.
529 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
530 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
531 * will result in a vector of size 1 in which the description field is empty.
533 struct FileFilterList
535 // FIXME UNICODE: globs_ should be unicode...
536 /** \param qt_style_filter a list of available file filters.
537 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
538 * The "All files (*)" filter is always added to the list.
540 explicit FileFilterList(docstring const & qt_style_filter =
543 typedef std::vector<Filter>::size_type size_type;
545 bool empty() const { return filters_.empty(); }
546 size_type size() const { return filters_.size(); }
547 Filter & operator[](size_type i) { return filters_[i]; }
548 Filter const & operator[](size_type i) const { return filters_[i]; }
550 void parse_filter(std::string const & filter);
551 std::vector<Filter> filters_;
555 FileFilterList::FileFilterList(docstring const & qt_style_filter)
558 string const filter = to_utf8(qt_style_filter)
559 + (qt_style_filter.empty() ? string() : ";;")
560 + to_utf8(_("All Files "))
567 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
568 // into individual filters.
569 static lyx::regex const separator_re(";;");
571 string::const_iterator it = filter.begin();
572 string::const_iterator const end = filter.end();
574 match_results<string::const_iterator> what;
576 if (!lyx::regex_search(it, end, what, separator_re)) {
577 parse_filter(string(it, end));
581 // Everything from the start of the input to
582 // the start of the match.
583 parse_filter(string(it, what[0].first));
585 // Increment the iterator to the end of the match.
586 it += distance(it, what[0].second);
591 void FileFilterList::parse_filter(string const & filter)
593 // Matches "TeX documents (plain) (*.tex)",
594 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
595 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
597 match_results<string::const_iterator> what;
598 if (!lyx::regex_search(filter, what, filter_re)) {
599 // Just a glob, no description.
600 filters_.push_back(Filter(docstring(), trim(filter)));
603 docstring const desc = from_utf8(string(what[1].first, what[1].second));
604 string const globs = string(what[2].first, what[2].second);
605 filters_.push_back(Filter(trim(desc), trim(globs)));
610 /** \returns the equivalent of the string passed in
611 * although any brace expressions are expanded.
612 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
614 QStringList fileFilters(QString const & desc)
616 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
617 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
618 FileFilterList filters(qstring_to_ucs4(desc));
619 //LYXERR0("DESC: " << desc);
621 for (size_t i = 0; i != filters.filters_.size(); ++i) {
622 QString f = filters.filters_[i].toString();
623 //LYXERR0("FILTER: " << f);
630 QString formatToolTip(QString text, int em)
632 // 1. QTooltip activates word wrapping only if mightBeRichText()
633 // is true. So we convert the text to rich text.
635 // 2. The default width is way too small. Setting the width is tricky; first
636 // one has to compute the ideal width, and then force it with special
639 // do nothing if empty or already formatted
640 if (text.isEmpty() || text.startsWith(QString("<html>")))
642 // Convert to rich text if it is not already
643 if (!Qt::mightBeRichText(text))
644 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
645 // Compute desired width in pixels
646 QFont const font = QToolTip::font();
647 int const px_width = em * QFontMetrics(font).width("M");
648 // Determine the ideal width of the tooltip
649 QTextDocument td("");
651 td.setDefaultFont(QToolTip::font());
652 td.setDocumentMargin(0);
653 td.setTextWidth(px_width);
654 double best_width = td.idealWidth();
655 // Set the line wrapping with appropriate width
656 return QString("<html><body><table><tr>"
657 "<td align=justify width=%1>%2</td>"
658 "</tr></table></body></html>")
659 .arg(QString::number(int(best_width) + 1), text);
663 QString qtHtmlToPlainText(QString const & html)
665 if (!Qt::mightBeRichText(html))
669 return td.toPlainText();