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>
58 #include "support/regex.h"
62 using namespace lyx::support;
66 FileName libFileSearch(QString const & dir, QString const & name,
67 QString const & ext, search_mode mode)
69 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
73 FileName imageLibFileSearch(QString & dir, QString const & name,
74 QString const & ext, search_mode mode)
76 string tmp = fromqstr(dir);
77 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
84 double locstringToDouble(QString const & str)
88 double res = loc.toDouble(str, &ok);
91 QLocale c(QLocale::C);
92 res = c.toDouble(str);
102 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
104 QString const length = input->text();
105 if (length.isEmpty())
108 // Don't return unit-from-choice if the input(field) contains a unit
109 if (isValidGlueLength(fromqstr(length)))
110 return fromqstr(length);
112 Length::UNIT const unit = combo->currentLengthItem();
114 return Length(locstringToDouble(length.trimmed()), unit).asString();
118 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
120 QString const length = input->text();
121 if (length.isEmpty())
124 // don't return unit-from-choice if the input(field) contains a unit
125 if (isValidGlueLength(fromqstr(length)))
126 return Length(fromqstr(length));
128 Length::UNIT unit = Length::UNIT_NONE;
129 QString const item = combo->currentText();
130 for (int i = 0; i < num_units; i++) {
131 if (qt_(lyx::unit_name_gui[i]) == item) {
132 unit = unitFromString(unit_name[i]);
137 return Length(locstringToDouble(length.trimmed()), unit);
141 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
142 Length const & len, Length::UNIT /*defaultUnit*/)
145 // no length (UNIT_NONE)
146 combo->setCurrentItem(Length::defaultUnit());
149 combo->setCurrentItem(len.unit());
151 loc.setNumberOptions(QLocale::OmitGroupSeparator);
152 input->setText(formatLocFPNumber(Length(len).value()));
157 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
158 string const & len, Length::UNIT defaultUnit)
161 // no length (UNIT_NONE)
162 combo->setCurrentItem(defaultUnit);
164 } else if (!isValidLength(len) && !isStrDbl(len)) {
165 // use input field only for gluelengths
166 combo->setCurrentItem(defaultUnit);
167 input->setText(toqstr(len));
169 lengthToWidgets(input, combo, Length(len), defaultUnit);
174 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
175 docstring const & len, Length::UNIT defaultUnit)
177 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
181 double widgetToDouble(QLineEdit const * input)
183 QString const text = input->text();
187 return locstringToDouble(text.trimmed());
191 string widgetToDoubleStr(QLineEdit const * input)
193 return convert<string>(widgetToDouble(input));
197 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
200 loc.setNumberOptions(QLocale::OmitGroupSeparator);
201 input->setText(loc.toString(value, f, prec));
205 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
207 doubleToWidget(input, convert<double>(value), f, prec);
211 QString formatLocFPNumber(double d)
213 QString result = toqstr(formatFPNumber(d));
215 result.replace('.', loc.decimalPoint());
220 bool SortLocaleAware(QString const & lhs, QString const & rhs)
222 return QString::localeAwareCompare(lhs, rhs) < 0;
226 bool ColorSorter(ColorCode lhs, ColorCode rhs)
228 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
232 void setValid(QWidget * widget, bool valid)
235 widget->setPalette(QPalette());
237 QPalette pal = widget->palette();
238 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
239 widget->setPalette(pal);
244 void focusAndHighlight(QAbstractItemView * w)
247 w->setCurrentIndex(w->currentIndex());
248 w->scrollTo(w->currentIndex());
252 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
254 QPalette pal = QApplication::palette();
255 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
256 pal.color(QPalette::Active, QPalette::Highlight));
257 for (QWidget * w : highlighted)
258 w->setPalette(newpal);
259 for (QWidget * w : plain)
264 /// wrapper to hide the change of method name to setSectionResizeMode
265 void setSectionResizeMode(QHeaderView * view,
266 int logicalIndex, QHeaderView::ResizeMode mode) {
267 #if (QT_VERSION >= 0x050000)
268 view->setSectionResizeMode(logicalIndex, mode);
270 view->setResizeMode(logicalIndex, mode);
274 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
275 #if (QT_VERSION >= 0x050000)
276 view->setSectionResizeMode(mode);
278 view->setResizeMode(mode);
282 void showDirectory(FileName const & directory)
284 if (!directory.exists())
286 QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
287 // Give hints in case of bugs
288 if (!qurl.isValid()) {
289 LYXERR0("QUrl is invalid!");
293 if (!QDesktopServices::openUrl(qurl))
294 LYXERR0("Unable to open QUrl even though dir exists!");
296 } // namespace frontend
298 QString const qt_(char const * str, const char *)
300 return toqstr(_(str));
304 QString const qt_(string const & str)
306 return toqstr(_(str));
310 QString const qt_(QString const & qstr)
312 return toqstr(_(fromqstr(qstr)));
316 void rescanTexStyles(string const & arg)
318 // Run rescan in user lyx directory
319 PathChanger p(package().user_support());
320 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
322 string const command = os::python() + ' ' +
323 quoteName(prog.toFilesystemEncoding()) + ' ' +
325 int const status = one.startscript(Systemcall::Wait, command);
329 frontend::Alert::error(_("Could not update TeX information"),
330 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
334 QStringList texFileList(QString const & filename)
337 FileName const file = libFileSearch(QString(), filename);
342 vector<docstring> doclist =
343 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
345 // Normalise paths like /foo//bar ==> /foo/bar
347 for (size_t i = 0; i != doclist.size(); ++i) {
348 QString qfile = toqstr(doclist[i]);
349 qfile.replace("\r", "");
350 while (qfile.contains("//"))
351 qfile.replace("//", "/");
352 if (!qfile.isEmpty())
357 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
358 return QList<QString>(set.begin(), set.end());
360 return QList<QString>::fromSet(set);
364 QString const externalLineEnding(docstring const & str)
367 // The MAC clipboard uses \r for lineendings, and we use \n
368 return toqstr(subst(str, '\n', '\r'));
369 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
370 // Windows clipboard uses \r\n for lineendings, and we use \n
371 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
378 docstring const internalLineEnding(QString const & str)
380 docstring const s = subst(qstring_to_ucs4(str),
381 from_ascii("\r\n"), from_ascii("\n"));
382 return subst(s, '\r', '\n');
386 QString internalPath(const QString & str)
388 return toqstr(os::internal_path(fromqstr(str)));
392 QString onlyFileName(const QString & str)
394 return toqstr(support::onlyFileName(fromqstr(str)));
398 QString onlyPath(const QString & str)
400 return toqstr(support::onlyPath(fromqstr(str)));
404 QString changeExtension(QString const & oldname, QString const & ext)
406 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
409 /// Remove the extension from \p name
410 QString removeExtension(QString const & name)
412 return toqstr(support::removeExtension(fromqstr(name)));
415 /** Add the extension \p ext to \p name.
416 Use this instead of changeExtension if you know that \p name is without
417 extension, because changeExtension would wrongly interpret \p name if it
420 QString addExtension(QString const & name, QString const & ext)
422 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
425 /// Return the extension of the file (not including the .)
426 QString getExtension(QString const & name)
428 return toqstr(support::getExtension(fromqstr(name)));
432 /** Convert relative path into absolute path based on a basepath.
433 If relpath is absolute, just use that.
434 If basepath doesn't exist use CWD.
436 QString makeAbsPath(QString const & relpath, QString const & base)
438 return toqstr(support::makeAbsPath(fromqstr(relpath),
439 fromqstr(base)).absFileName());
443 /////////////////////////////////////////////////////////////////////////
447 /////////////////////////////////////////////////////////////////////////
449 /** Given a string such as
450 * "<glob> <glob> ... *.{abc,def} <glob>",
451 * convert the csh-style brace expressions:
452 * "<glob> <glob> ... *.abc *.def <glob>".
453 * Requires no system support, so should work equally on Unix, Mac, Win32.
455 static string const convert_brace_glob(string const & glob)
457 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
458 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
459 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
460 // Matches "abc" and "abc,", storing "abc" as group 1,
461 // while ignoring surrounding spaces.
462 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
466 string::const_iterator it = glob.begin();
467 string::const_iterator const end = glob.end();
469 match_results<string::const_iterator> what;
470 if (!regex_search(it, end, what, glob_re)) {
471 // Ensure that no information is lost.
472 pattern += string(it, end);
476 // Everything from the start of the input to
477 // the start of the match.
478 pattern += string(what[-1].first, what[-1].second);
480 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
481 string const head = string(what[1].first, what[1].second);
482 string const tail = string(what[2].first, what[2].second);
484 // Split the ','-separated chunks of tail so that
485 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
486 string const fmt = " " + head + "$1";
487 pattern += regex_replace(tail, block_re, fmt);
489 // Increment the iterator to the end of the match.
490 it += distance(it, what[0].second);
499 /* \param description text describing the filters.
500 * \param one or more wildcard patterns, separated by
503 Filter(docstring const & description, std::string const & globs);
505 docstring const & description() const { return desc_; }
507 QString toString() const;
510 std::vector<std::string> globs_;
514 Filter::Filter(docstring const & description, string const & globs)
517 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
518 // "<glob> <glob> ... *.abc *.def <glob>"
519 string const expanded_globs = convert_brace_glob(globs);
521 // Split into individual globs.
522 globs_ = getVectorFromString(expanded_globs, " ");
526 QString Filter::toString() const
530 bool const has_description = !desc_.empty();
532 if (has_description) {
537 s += toqstr(getStringFromVector(globs_, " "));
545 /** \c FileFilterList parses a Qt-style list of available file filters
546 * to generate the corresponding vector.
547 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
548 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
549 * will result in a vector of size 1 in which the description field is empty.
551 struct FileFilterList
553 // FIXME UNICODE: globs_ should be unicode...
554 /** \param qt_style_filter a list of available file filters.
555 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
556 * The "All files (*)" filter is always added to the list.
558 explicit FileFilterList(docstring const & qt_style_filter =
561 typedef std::vector<Filter>::size_type size_type;
563 bool empty() const { return filters_.empty(); }
564 size_type size() const { return filters_.size(); }
565 Filter & operator[](size_type i) { return filters_[i]; }
566 Filter const & operator[](size_type i) const { return filters_[i]; }
568 void parse_filter(std::string const & filter);
569 std::vector<Filter> filters_;
573 FileFilterList::FileFilterList(docstring const & qt_style_filter)
576 string const filter = to_utf8(qt_style_filter)
577 + (qt_style_filter.empty() ? string() : ";;")
578 + to_utf8(_("All Files "))
585 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
586 // into individual filters.
587 static lyx::regex const separator_re(";;");
589 string::const_iterator it = filter.begin();
590 string::const_iterator const end = filter.end();
592 match_results<string::const_iterator> what;
594 if (!lyx::regex_search(it, end, what, separator_re)) {
595 parse_filter(string(it, end));
599 // Everything from the start of the input to
600 // the start of the match.
601 parse_filter(string(it, what[0].first));
603 // Increment the iterator to the end of the match.
604 it += distance(it, what[0].second);
609 void FileFilterList::parse_filter(string const & filter)
611 // Matches "TeX documents (plain) (*.tex)",
612 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
613 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
615 match_results<string::const_iterator> what;
616 if (!lyx::regex_search(filter, what, filter_re)) {
617 // Just a glob, no description.
618 filters_.push_back(Filter(docstring(), trim(filter)));
621 docstring const desc = from_utf8(string(what[1].first, what[1].second));
622 string const globs = string(what[2].first, what[2].second);
623 filters_.push_back(Filter(trim(desc), trim(globs)));
628 /** \returns the equivalent of the string passed in
629 * although any brace expressions are expanded.
630 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
632 QStringList fileFilters(QString const & desc)
634 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
635 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
636 FileFilterList filters(qstring_to_ucs4(desc));
637 //LYXERR0("DESC: " << desc);
639 for (size_t i = 0; i != filters.filters_.size(); ++i) {
640 QString f = filters.filters_[i].toString();
641 //LYXERR0("FILTER: " << f);
648 QString formatToolTip(QString text, int em)
650 // 1. QTooltip activates word wrapping only if mightBeRichText()
651 // is true. So we convert the text to rich text.
653 // 2. The default width is way too small. Setting the width is tricky; first
654 // one has to compute the ideal width, and then force it with special
657 // do nothing if empty or already formatted
658 if (text.isEmpty() || text.startsWith(QString("<html>")))
660 // Convert to rich text if it is not already
661 if (!Qt::mightBeRichText(text))
662 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
663 // Compute desired width in pixels
664 QFont const font = QToolTip::font();
665 int const px_width = em * QFontMetrics(font).width("M");
666 // Determine the ideal width of the tooltip
667 QTextDocument td("");
669 td.setDefaultFont(QToolTip::font());
670 td.setDocumentMargin(0);
671 td.setTextWidth(px_width);
672 double best_width = td.idealWidth();
673 // Set the line wrapping with appropriate width
674 return QString("<html><body><table><tr>"
675 "<td align=justify width=%1>%2</td>"
676 "</tr></table></body></html>")
677 .arg(QString::number(int(best_width) + 1), text);
681 QString qtHtmlToPlainText(QString const & html)
683 if (!Qt::mightBeRichText(html))
687 return td.toPlainText();