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"
25 #include "TextClass.h"
27 #include "support/convert.h"
28 #include "support/debug.h"
29 #include "support/gettext.h"
30 #include "support/Length.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>
41 #include <QDesktopServices>
47 #include <QTextLayout>
48 #include <QTextDocument>
57 #include "support/regex.h"
61 using namespace lyx::support;
65 FileName libFileSearch(QString const & dir, QString const & name,
66 QString const & ext, search_mode mode)
68 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
72 FileName imageLibFileSearch(QString & dir, QString const & name,
73 QString const & ext, search_mode mode)
75 string tmp = fromqstr(dir);
76 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
83 double locstringToDouble(QString const & str)
87 double res = loc.toDouble(str, &ok);
90 QLocale c(QLocale::C);
91 res = c.toDouble(str);
101 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
103 QString const length = input->text();
104 if (length.isEmpty())
107 // Don't return unit-from-choice if the input(field) contains a unit
108 if (isValidGlueLength(fromqstr(length)))
109 return fromqstr(length);
111 Length::UNIT const unit = combo->currentLengthItem();
113 return Length(locstringToDouble(length.trimmed()), unit).asString();
117 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
119 QString const length = input->text();
120 if (length.isEmpty())
123 // don't return unit-from-choice if the input(field) contains a unit
124 if (isValidGlueLength(fromqstr(length)))
125 return Length(fromqstr(length));
127 Length::UNIT unit = Length::UNIT_NONE;
128 QString const item = combo->currentText();
129 for (int i = 0; i < num_units; i++) {
130 if (qt_(lyx::unit_name_gui[i]) == item) {
131 unit = unitFromString(unit_name[i]);
136 return Length(locstringToDouble(length.trimmed()), unit);
140 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
141 Length const & len, Length::UNIT /*defaultUnit*/)
144 // no length (UNIT_NONE)
145 combo->setCurrentItem(Length::defaultUnit());
148 combo->setCurrentItem(len.unit());
150 loc.setNumberOptions(QLocale::OmitGroupSeparator);
151 input->setText(formatLocFPNumber(Length(len).value()));
156 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
157 string const & len, Length::UNIT defaultUnit)
160 // no length (UNIT_NONE)
161 combo->setCurrentItem(defaultUnit);
163 } else if (!isValidLength(len) && !isStrDbl(len)) {
164 // use input field only for gluelengths
165 combo->setCurrentItem(defaultUnit);
166 input->setText(toqstr(len));
168 lengthToWidgets(input, combo, Length(len), defaultUnit);
173 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
174 docstring const & len, Length::UNIT defaultUnit)
176 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
180 double widgetToDouble(QLineEdit const * input)
182 QString const text = input->text();
186 return locstringToDouble(text.trimmed());
190 string widgetToDoubleStr(QLineEdit const * input)
192 return convert<string>(widgetToDouble(input));
196 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
199 loc.setNumberOptions(QLocale::OmitGroupSeparator);
200 input->setText(loc.toString(value, f, prec));
204 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
206 doubleToWidget(input, convert<double>(value), f, prec);
210 QString formatLocFPNumber(double d)
212 QString result = toqstr(formatFPNumber(d));
214 result.replace('.', loc.decimalPoint());
219 bool SortLocaleAware(QString const & lhs, QString const & rhs)
221 return QString::localeAwareCompare(lhs, rhs) < 0;
225 bool ColorSorter(ColorCode lhs, ColorCode rhs)
227 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
231 void setValid(QWidget * widget, bool valid)
234 widget->setPalette(QPalette());
236 QPalette pal = widget->palette();
237 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
238 widget->setPalette(pal);
243 void focusAndHighlight(QAbstractItemView * w)
246 w->setCurrentIndex(w->currentIndex());
247 w->scrollTo(w->currentIndex());
251 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
253 QPalette pal = QApplication::palette();
254 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
255 pal.color(QPalette::Active, QPalette::Highlight));
256 for (QWidget * w : highlighted)
257 w->setPalette(newpal);
258 for (QWidget * w : plain)
263 /// wrapper to hide the change of method name to setSectionResizeMode
264 void setSectionResizeMode(QHeaderView * view,
265 int logicalIndex, QHeaderView::ResizeMode mode) {
266 #if (QT_VERSION >= 0x050000)
267 view->setSectionResizeMode(logicalIndex, mode);
269 view->setResizeMode(logicalIndex, mode);
273 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
274 #if (QT_VERSION >= 0x050000)
275 view->setSectionResizeMode(mode);
277 view->setResizeMode(mode);
281 void showDirectory(FileName const & directory)
283 if (!directory.exists())
285 QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
286 // Give hints in case of bugs
287 if (!qurl.isValid()) {
288 LYXERR0("QUrl is invalid!");
292 if (!QDesktopServices::openUrl(qurl))
293 LYXERR0("Unable to open QUrl even though dir exists!");
295 } // namespace frontend
297 QString const qt_(char const * str, const char *)
299 return toqstr(_(str));
303 QString const qt_(string const & str)
305 return toqstr(_(str));
309 QString const qt_(QString const & qstr)
311 return toqstr(_(fromqstr(qstr)));
315 void rescanTexStyles(string const & arg)
317 // Run rescan in user lyx directory
318 PathChanger p(package().user_support());
319 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
321 string const command = os::python() + ' ' +
322 quoteName(prog.toFilesystemEncoding()) + ' ' +
324 int const status = one.startscript(Systemcall::Wait, command);
328 frontend::Alert::error(_("Could not update TeX information"),
329 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
333 QStringList texFileList(QString const & filename)
336 FileName const file = libFileSearch(QString(), filename);
341 vector<docstring> doclist =
342 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
344 // Normalise paths like /foo//bar ==> /foo/bar
346 for (size_t i = 0; i != doclist.size(); ++i) {
347 QString qfile = toqstr(doclist[i]);
348 qfile.replace("\r", "");
349 while (qfile.contains("//"))
350 qfile.replace("//", "/");
351 if (!qfile.isEmpty())
356 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
357 return QList<QString>(set.begin(), set.end());
359 return QList<QString>::fromSet(set);
363 QString const externalLineEnding(docstring const & str)
366 // The MAC clipboard uses \r for lineendings, and we use \n
367 return toqstr(subst(str, '\n', '\r'));
368 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
369 // Windows clipboard uses \r\n for lineendings, and we use \n
370 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
377 docstring const internalLineEnding(QString const & str)
379 docstring const s = subst(qstring_to_ucs4(str),
380 from_ascii("\r\n"), from_ascii("\n"));
381 return subst(s, '\r', '\n');
385 QString internalPath(const QString & str)
387 return toqstr(os::internal_path(fromqstr(str)));
391 QString onlyFileName(const QString & str)
393 return toqstr(support::onlyFileName(fromqstr(str)));
397 QString onlyPath(const QString & str)
399 return toqstr(support::onlyPath(fromqstr(str)));
403 QString changeExtension(QString const & oldname, QString const & ext)
405 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
408 /// Remove the extension from \p name
409 QString removeExtension(QString const & name)
411 return toqstr(support::removeExtension(fromqstr(name)));
414 /** Add the extension \p ext to \p name.
415 Use this instead of changeExtension if you know that \p name is without
416 extension, because changeExtension would wrongly interpret \p name if it
419 QString addExtension(QString const & name, QString const & ext)
421 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
424 /// Return the extension of the file (not including the .)
425 QString getExtension(QString const & name)
427 return toqstr(support::getExtension(fromqstr(name)));
431 /** Convert relative path into absolute path based on a basepath.
432 If relpath is absolute, just use that.
433 If basepath doesn't exist use CWD.
435 QString makeAbsPath(QString const & relpath, QString const & base)
437 return toqstr(support::makeAbsPath(fromqstr(relpath),
438 fromqstr(base)).absFileName());
442 /////////////////////////////////////////////////////////////////////////
446 /////////////////////////////////////////////////////////////////////////
448 /** Given a string such as
449 * "<glob> <glob> ... *.{abc,def} <glob>",
450 * convert the csh-style brace expressions:
451 * "<glob> <glob> ... *.abc *.def <glob>".
452 * Requires no system support, so should work equally on Unix, Mac, Win32.
454 static string const convert_brace_glob(string const & glob)
456 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
457 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
458 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
459 // Matches "abc" and "abc,", storing "abc" as group 1,
460 // while ignoring surrounding spaces.
461 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
465 string::const_iterator it = glob.begin();
466 string::const_iterator const end = glob.end();
468 match_results<string::const_iterator> what;
469 if (!regex_search(it, end, what, glob_re)) {
470 // Ensure that no information is lost.
471 pattern += string(it, end);
475 // Everything from the start of the input to
476 // the start of the match.
477 pattern += string(what[-1].first, what[-1].second);
479 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
480 string const head = string(what[1].first, what[1].second);
481 string const tail = string(what[2].first, what[2].second);
483 // Split the ','-separated chunks of tail so that
484 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
485 string const fmt = " " + head + "$1";
486 pattern += regex_replace(tail, block_re, fmt);
488 // Increment the iterator to the end of the match.
489 it += distance(it, what[0].second);
498 /* \param description text describing the filters.
499 * \param one or more wildcard patterns, separated by
502 Filter(docstring const & description, std::string const & globs);
504 docstring const & description() const { return desc_; }
506 QString toString() const;
509 std::vector<std::string> globs_;
513 Filter::Filter(docstring const & description, string const & globs)
516 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
517 // "<glob> <glob> ... *.abc *.def <glob>"
518 string const expanded_globs = convert_brace_glob(globs);
520 // Split into individual globs.
521 globs_ = getVectorFromString(expanded_globs, " ");
525 QString Filter::toString() const
529 bool const has_description = !desc_.empty();
531 if (has_description) {
536 s += toqstr(getStringFromVector(globs_, " "));
544 /** \c FileFilterList parses a Qt-style list of available file filters
545 * to generate the corresponding vector.
546 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
547 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
548 * will result in a vector of size 1 in which the description field is empty.
550 struct FileFilterList
552 // FIXME UNICODE: globs_ should be unicode...
553 /** \param qt_style_filter a list of available file filters.
554 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
555 * The "All files (*)" filter is always added to the list.
557 explicit FileFilterList(docstring const & qt_style_filter =
560 typedef std::vector<Filter>::size_type size_type;
562 bool empty() const { return filters_.empty(); }
563 size_type size() const { return filters_.size(); }
564 Filter & operator[](size_type i) { return filters_[i]; }
565 Filter const & operator[](size_type i) const { return filters_[i]; }
567 void parse_filter(std::string const & filter);
568 std::vector<Filter> filters_;
572 FileFilterList::FileFilterList(docstring const & qt_style_filter)
575 string const filter = to_utf8(qt_style_filter)
576 + (qt_style_filter.empty() ? string() : ";;")
577 + to_utf8(_("All Files "))
584 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
585 // into individual filters.
586 static lyx::regex const separator_re(";;");
588 string::const_iterator it = filter.begin();
589 string::const_iterator const end = filter.end();
591 match_results<string::const_iterator> what;
593 if (!lyx::regex_search(it, end, what, separator_re)) {
594 parse_filter(string(it, end));
598 // Everything from the start of the input to
599 // the start of the match.
600 parse_filter(string(it, what[0].first));
602 // Increment the iterator to the end of the match.
603 it += distance(it, what[0].second);
608 void FileFilterList::parse_filter(string const & filter)
610 // Matches "TeX documents (plain) (*.tex)",
611 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
612 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
614 match_results<string::const_iterator> what;
615 if (!lyx::regex_search(filter, what, filter_re)) {
616 // Just a glob, no description.
617 filters_.push_back(Filter(docstring(), trim(filter)));
620 docstring const desc = from_utf8(string(what[1].first, what[1].second));
621 string const globs = string(what[2].first, what[2].second);
622 filters_.push_back(Filter(trim(desc), trim(globs)));
627 /** \returns the equivalent of the string passed in
628 * although any brace expressions are expanded.
629 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
631 QStringList fileFilters(QString const & desc)
633 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
634 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
635 FileFilterList filters(qstring_to_ucs4(desc));
636 //LYXERR0("DESC: " << desc);
638 for (size_t i = 0; i != filters.filters_.size(); ++i) {
639 QString f = filters.filters_[i].toString();
640 //LYXERR0("FILTER: " << f);
647 QString formatToolTip(QString text, int em)
649 // 1. QTooltip activates word wrapping only if mightBeRichText()
650 // is true. So we convert the text to rich text.
652 // 2. The default width is way too small. Setting the width is tricky; first
653 // one has to compute the ideal width, and then force it with special
656 // do nothing if empty or already formatted
657 if (text.isEmpty() || text.startsWith(QString("<html>")))
659 // Convert to rich text if it is not already
660 if (!Qt::mightBeRichText(text))
661 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
662 // Compute desired width in pixels
663 QFont const font = QToolTip::font();
664 int const px_width = em * QFontMetrics(font).width("M");
665 // Determine the ideal width of the tooltip
666 QTextDocument td("");
668 td.setDefaultFont(QToolTip::font());
669 td.setDocumentMargin(0);
670 td.setTextWidth(px_width);
671 double best_width = td.idealWidth();
672 // Set the line wrapping with appropriate width
673 return QString("<html><body><table><tr>"
674 "<td align=justify width=%1>%2</td>"
675 "</tr></table></body></html>")
676 .arg(QString::number(int(best_width) + 1), text);
680 QString qtHtmlToPlainText(QString const & html)
682 if (!Qt::mightBeRichText(html))
686 return td.toPlainText();