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/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>
61 #include "support/regex.h"
65 using namespace lyx::support;
69 FileName libFileSearch(QString const & dir, QString const & name,
70 QString const & ext, search_mode mode)
72 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
76 FileName imageLibFileSearch(QString & dir, QString const & name,
77 QString const & ext, search_mode mode)
79 string tmp = fromqstr(dir);
80 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
87 double locstringToDouble(QString const & str)
91 double res = loc.toDouble(str, &ok);
94 QLocale c(QLocale::C);
95 res = c.toDouble(str);
105 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
107 QString const length = input->text();
108 if (length.isEmpty())
111 // Don't return unit-from-choice if the input(field) contains a unit
112 if (isValidGlueLength(fromqstr(length)))
113 return fromqstr(length);
115 Length::UNIT const unit = combo->currentLengthItem();
117 return Length(locstringToDouble(length.trimmed()), unit).asString();
121 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
123 QString const length = input->text();
124 if (length.isEmpty())
127 // don't return unit-from-choice if the input(field) contains a unit
128 if (isValidGlueLength(fromqstr(length)))
129 return Length(fromqstr(length));
131 Length::UNIT unit = Length::UNIT_NONE;
132 QString const item = combo->currentText();
133 for (int i = 0; i < num_units; i++) {
134 if (qt_(lyx::unit_name_gui[i]) == item) {
135 unit = unitFromString(unit_name[i]);
140 return Length(locstringToDouble(length.trimmed()), unit);
144 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
145 Length const & len, Length::UNIT /*defaultUnit*/)
148 // no length (UNIT_NONE)
149 combo->setCurrentItem(Length::defaultUnit());
152 combo->setCurrentItem(len.unit());
154 loc.setNumberOptions(QLocale::OmitGroupSeparator);
155 input->setText(formatLocFPNumber(Length(len).value()));
160 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
161 string const & len, Length::UNIT defaultUnit)
164 // no length (UNIT_NONE)
165 combo->setCurrentItem(defaultUnit);
167 } else if (!isValidLength(len) && !isStrDbl(len)) {
168 // use input field only for gluelengths
169 combo->setCurrentItem(defaultUnit);
170 input->setText(toqstr(len));
172 lengthToWidgets(input, combo, Length(len), defaultUnit);
177 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
178 docstring const & len, Length::UNIT defaultUnit)
180 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
184 double widgetToDouble(QLineEdit const * input)
186 QString const text = input->text();
190 return locstringToDouble(text.trimmed());
194 string widgetToDoubleStr(QLineEdit const * input)
196 return convert<string>(widgetToDouble(input));
200 void doubleToWidget(QLineEdit * input, double value, char f, int prec)
203 loc.setNumberOptions(QLocale::OmitGroupSeparator);
204 input->setText(loc.toString(value, f, prec));
208 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
210 doubleToWidget(input, convert<double>(value), f, prec);
214 QString formatLocFPNumber(double d)
216 QString result = toqstr(formatFPNumber(d));
218 result.replace('.', loc.decimalPoint());
223 bool SortLocaleAware(QString const & lhs, QString const & rhs)
225 return QString::localeAwareCompare(lhs, rhs) < 0;
229 bool ColorSorter(ColorCode lhs, ColorCode rhs)
231 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
235 void setValid(QWidget * widget, bool valid)
238 widget->setPalette(QPalette());
240 QPalette pal = widget->palette();
241 pal.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0));
242 widget->setPalette(pal);
247 void focusAndHighlight(QAbstractItemView * w)
250 w->setCurrentIndex(w->currentIndex());
251 w->scrollTo(w->currentIndex());
255 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
257 QPalette pal = QApplication::palette();
258 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
259 pal.color(QPalette::Active, QPalette::Highlight));
260 for (QWidget * w : highlighted)
261 w->setPalette(newpal);
262 for (QWidget * w : plain)
267 /// wrapper to hide the change of method name to setSectionResizeMode
268 void setSectionResizeMode(QHeaderView * view,
269 int logicalIndex, QHeaderView::ResizeMode mode) {
270 #if (QT_VERSION >= 0x050000)
271 view->setSectionResizeMode(logicalIndex, mode);
273 view->setResizeMode(logicalIndex, mode);
277 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
278 #if (QT_VERSION >= 0x050000)
279 view->setSectionResizeMode(mode);
281 view->setResizeMode(mode);
285 void showDirectory(FileName const & directory)
287 if (!directory.exists())
289 QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
290 // Give hints in case of bugs
291 if (!qurl.isValid()) {
292 frontend::Alert::error(_("Invalid URL"),
293 bformat(_("The URL `%1$s' could not be resolved."),
294 qstring_to_ucs4(qurl.toString())));
298 if (!QDesktopServices::openUrl(qurl))
299 frontend::Alert::error(_("URL could not be accessed"),
300 bformat(_("The URL `%1$s' could not be opened although it exists!"),
301 qstring_to_ucs4(qurl.toString())));
304 void showTarget(string const & target, string const & pdfv, string const & psv)
306 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
307 if (prefixIs(target, "EXTERNAL ")) {
308 if (!lyxrc.citation_search)
310 string tmp, tar, opts;
311 tar = split(target, tmp, ' ');
313 opts = " -v " + pdfv;
315 opts += " -w " + psv;
319 string const command = lyxrc.citation_search_view + " " + opts + tar;
320 int const result = one.startscript(Systemcall::Wait, command);
323 frontend::Alert::error(_("Could not open file"),
324 _("The lyxpaperview script failed."));
325 else if (result == 2)
326 frontend::Alert::error(_("Could not open file"),
327 bformat(_("No file was found using the pattern `%1$s'."),
331 if (!QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode)))
332 frontend::Alert::error(_("Could not open file"),
333 bformat(_("The target `%1$s' could not be resolved."),
336 } // namespace frontend
338 QString const qt_(char const * str, const char *)
340 return toqstr(_(str));
344 QString const qt_(string const & str)
346 return toqstr(_(str));
350 QString const qt_(QString const & qstr)
352 return toqstr(_(fromqstr(qstr)));
356 void rescanTexStyles(string const & arg)
358 // Run rescan in user lyx directory
359 PathChanger p(package().user_support());
360 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
362 string const command = os::python() + ' ' +
363 quoteName(prog.toFilesystemEncoding()) + ' ' +
365 int const status = one.startscript(Systemcall::Wait, command);
369 frontend::Alert::error(_("Could not update TeX information"),
370 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
374 QStringList texFileList(QString const & filename)
377 FileName const file = libFileSearch(QString(), filename);
382 vector<docstring> doclist =
383 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
385 // Normalise paths like /foo//bar ==> /foo/bar
387 for (size_t i = 0; i != doclist.size(); ++i) {
388 QString qfile = toqstr(doclist[i]);
389 qfile.replace("\r", "");
390 while (qfile.contains("//"))
391 qfile.replace("//", "/");
392 if (!qfile.isEmpty())
397 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
398 return QList<QString>(set.begin(), set.end());
400 return QList<QString>::fromSet(set);
404 QString const externalLineEnding(docstring const & str)
407 // The MAC clipboard uses \r for lineendings, and we use \n
408 return toqstr(subst(str, '\n', '\r'));
409 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
410 // Windows clipboard uses \r\n for lineendings, and we use \n
411 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
418 docstring const internalLineEnding(QString const & str)
420 docstring const s = subst(qstring_to_ucs4(str),
421 from_ascii("\r\n"), from_ascii("\n"));
422 return subst(s, '\r', '\n');
426 QString internalPath(const QString & str)
428 return toqstr(os::internal_path(fromqstr(str)));
432 QString onlyFileName(const QString & str)
434 return toqstr(support::onlyFileName(fromqstr(str)));
438 QString onlyPath(const QString & str)
440 return toqstr(support::onlyPath(fromqstr(str)));
444 QString changeExtension(QString const & oldname, QString const & ext)
446 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
449 /// Remove the extension from \p name
450 QString removeExtension(QString const & name)
452 return toqstr(support::removeExtension(fromqstr(name)));
455 /** Add the extension \p ext to \p name.
456 Use this instead of changeExtension if you know that \p name is without
457 extension, because changeExtension would wrongly interpret \p name if it
460 QString addExtension(QString const & name, QString const & ext)
462 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
465 /// Return the extension of the file (not including the .)
466 QString getExtension(QString const & name)
468 return toqstr(support::getExtension(fromqstr(name)));
472 /** Convert relative path into absolute path based on a basepath.
473 If relpath is absolute, just use that.
474 If basepath doesn't exist use CWD.
476 QString makeAbsPath(QString const & relpath, QString const & base)
478 return toqstr(support::makeAbsPath(fromqstr(relpath),
479 fromqstr(base)).absFileName());
483 /////////////////////////////////////////////////////////////////////////
487 /////////////////////////////////////////////////////////////////////////
489 /** Given a string such as
490 * "<glob> <glob> ... *.{abc,def} <glob>",
491 * convert the csh-style brace expressions:
492 * "<glob> <glob> ... *.abc *.def <glob>".
493 * Requires no system support, so should work equally on Unix, Mac, Win32.
495 static string const convert_brace_glob(string const & glob)
497 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
498 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
499 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
500 // Matches "abc" and "abc,", storing "abc" as group 1,
501 // while ignoring surrounding spaces.
502 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
506 string::const_iterator it = glob.begin();
507 string::const_iterator const end = glob.end();
509 match_results<string::const_iterator> what;
510 if (!regex_search(it, end, what, glob_re)) {
511 // Ensure that no information is lost.
512 pattern += string(it, end);
516 // Everything from the start of the input to
517 // the start of the match.
518 pattern += string(what[-1].first, what[-1].second);
520 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
521 string const head = string(what[1].first, what[1].second);
522 string const tail = string(what[2].first, what[2].second);
524 // Split the ','-separated chunks of tail so that
525 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
526 string const fmt = " " + head + "$1";
527 pattern += regex_replace(tail, block_re, fmt);
529 // Increment the iterator to the end of the match.
530 it += distance(it, what[0].second);
539 /* \param description text describing the filters.
540 * \param one or more wildcard patterns, separated by
543 Filter(docstring const & description, std::string const & globs);
545 docstring const & description() const { return desc_; }
547 QString toString() const;
550 std::vector<std::string> globs_;
554 Filter::Filter(docstring const & description, string const & globs)
557 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
558 // "<glob> <glob> ... *.abc *.def <glob>"
559 string const expanded_globs = convert_brace_glob(globs);
561 // Split into individual globs.
562 globs_ = getVectorFromString(expanded_globs, " ");
566 QString Filter::toString() const
570 bool const has_description = !desc_.empty();
572 if (has_description) {
577 s += toqstr(getStringFromVector(globs_, " "));
585 /** \c FileFilterList parses a Qt-style list of available file filters
586 * to generate the corresponding vector.
587 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
588 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
589 * will result in a vector of size 1 in which the description field is empty.
591 struct FileFilterList
593 // FIXME UNICODE: globs_ should be unicode...
594 /** \param qt_style_filter a list of available file filters.
595 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
596 * The "All files (*)" filter is always added to the list.
598 explicit FileFilterList(docstring const & qt_style_filter =
601 typedef std::vector<Filter>::size_type size_type;
603 bool empty() const { return filters_.empty(); }
604 size_type size() const { return filters_.size(); }
605 Filter & operator[](size_type i) { return filters_[i]; }
606 Filter const & operator[](size_type i) const { return filters_[i]; }
608 void parse_filter(std::string const & filter);
609 std::vector<Filter> filters_;
613 FileFilterList::FileFilterList(docstring const & qt_style_filter)
616 string const filter = to_utf8(qt_style_filter)
617 + (qt_style_filter.empty() ? string() : ";;")
618 + to_utf8(_("All Files "))
625 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
626 // into individual filters.
627 static lyx::regex const separator_re(";;");
629 string::const_iterator it = filter.begin();
630 string::const_iterator const end = filter.end();
632 match_results<string::const_iterator> what;
634 if (!lyx::regex_search(it, end, what, separator_re)) {
635 parse_filter(string(it, end));
639 // Everything from the start of the input to
640 // the start of the match.
641 parse_filter(string(it, what[0].first));
643 // Increment the iterator to the end of the match.
644 it += distance(it, what[0].second);
649 void FileFilterList::parse_filter(string const & filter)
651 // Matches "TeX documents (plain) (*.tex)",
652 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
653 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
655 match_results<string::const_iterator> what;
656 if (!lyx::regex_search(filter, what, filter_re)) {
657 // Just a glob, no description.
658 filters_.push_back(Filter(docstring(), trim(filter)));
661 docstring const desc = from_utf8(string(what[1].first, what[1].second));
662 string const globs = string(what[2].first, what[2].second);
663 filters_.push_back(Filter(trim(desc), trim(globs)));
668 /** \returns the equivalent of the string passed in
669 * although any brace expressions are expanded.
670 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
672 QStringList fileFilters(QString const & desc)
674 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
675 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
676 FileFilterList filters(qstring_to_ucs4(desc));
677 //LYXERR0("DESC: " << desc);
679 for (size_t i = 0; i != filters.filters_.size(); ++i) {
680 QString f = filters.filters_[i].toString();
681 //LYXERR0("FILTER: " << f);
688 QString formatToolTip(QString text, int width)
690 // 1. QTooltip activates word wrapping only if mightBeRichText()
691 // is true. So we convert the text to rich text.
693 // 2. The default width is way too small. Setting the width is tricky; first
694 // one has to compute the ideal width, and then force it with special
697 // do nothing if empty or already formatted
698 if (text.isEmpty() || text.startsWith(QString("<html>")))
700 // Convert to rich text if it is not already
701 if (!Qt::mightBeRichText(text))
702 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
703 // Compute desired width in pixels
704 QFont const font = QToolTip::font();
705 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
706 int const px_width = width * QFontMetrics(font).horizontalAdvance("M");
708 int const px_width = width * QFontMetrics(font).width("M");
710 // Determine the ideal width of the tooltip
711 QTextDocument td("");
713 td.setDefaultFont(QToolTip::font());
714 td.setDocumentMargin(0);
715 td.setTextWidth(px_width);
716 double best_width = td.idealWidth();
717 // Set the line wrapping with appropriate width
718 return QString("<html><body><table><tr>"
719 "<td align=justify width=%1>%2</td>"
720 "</tr></table></body></html>")
721 .arg(QString::number(int(best_width) + 1), text);
725 QString qtHtmlToPlainText(QString const & text)
727 if (!Qt::mightBeRichText(text))
731 return td.toPlainText();