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 LYXERR0("QUrl is invalid!");
297 if (!QDesktopServices::openUrl(qurl))
298 LYXERR0("Unable to open QUrl even though dir exists!");
301 void showTarget(string const & target, string const & pdfv, string const & psv)
303 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
304 if (prefixIs(target, "EXTERNAL ")) {
305 if (!lyxrc.citation_search)
307 string tmp, tar, opts;
308 tar = split(target, tmp, ' ');
310 opts = " -v " + pdfv;
312 opts += " -w " + psv;
316 string const command = lyxrc.citation_search_view + " " + opts + tar;
317 int const result = one.startscript(Systemcall::Wait, command);
320 frontend::Alert::error(_("Could not open file"),
321 _("The lyxpaperview script failed."));
322 else if (result == 2)
323 frontend::Alert::error(_("Could not open file"),
324 bformat(_("No file was found using the pattern `%1$s'."),
328 if (!QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode)))
329 frontend::Alert::error(_("Could not open file"),
330 bformat(_("The target `%1$s' could not be resolved."),
333 } // namespace frontend
335 QString const qt_(char const * str, const char *)
337 return toqstr(_(str));
341 QString const qt_(string const & str)
343 return toqstr(_(str));
347 QString const qt_(QString const & qstr)
349 return toqstr(_(fromqstr(qstr)));
353 void rescanTexStyles(string const & arg)
355 // Run rescan in user lyx directory
356 PathChanger p(package().user_support());
357 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
359 string const command = os::python() + ' ' +
360 quoteName(prog.toFilesystemEncoding()) + ' ' +
362 int const status = one.startscript(Systemcall::Wait, command);
366 frontend::Alert::error(_("Could not update TeX information"),
367 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
371 QStringList texFileList(QString const & filename)
374 FileName const file = libFileSearch(QString(), filename);
379 vector<docstring> doclist =
380 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
382 // Normalise paths like /foo//bar ==> /foo/bar
384 for (size_t i = 0; i != doclist.size(); ++i) {
385 QString qfile = toqstr(doclist[i]);
386 qfile.replace("\r", "");
387 while (qfile.contains("//"))
388 qfile.replace("//", "/");
389 if (!qfile.isEmpty())
394 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
395 return QList<QString>(set.begin(), set.end());
397 return QList<QString>::fromSet(set);
401 QString const externalLineEnding(docstring const & str)
404 // The MAC clipboard uses \r for lineendings, and we use \n
405 return toqstr(subst(str, '\n', '\r'));
406 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
407 // Windows clipboard uses \r\n for lineendings, and we use \n
408 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
415 docstring const internalLineEnding(QString const & str)
417 docstring const s = subst(qstring_to_ucs4(str),
418 from_ascii("\r\n"), from_ascii("\n"));
419 return subst(s, '\r', '\n');
423 QString internalPath(const QString & str)
425 return toqstr(os::internal_path(fromqstr(str)));
429 QString onlyFileName(const QString & str)
431 return toqstr(support::onlyFileName(fromqstr(str)));
435 QString onlyPath(const QString & str)
437 return toqstr(support::onlyPath(fromqstr(str)));
441 QString changeExtension(QString const & oldname, QString const & ext)
443 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
446 /// Remove the extension from \p name
447 QString removeExtension(QString const & name)
449 return toqstr(support::removeExtension(fromqstr(name)));
452 /** Add the extension \p ext to \p name.
453 Use this instead of changeExtension if you know that \p name is without
454 extension, because changeExtension would wrongly interpret \p name if it
457 QString addExtension(QString const & name, QString const & ext)
459 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
462 /// Return the extension of the file (not including the .)
463 QString getExtension(QString const & name)
465 return toqstr(support::getExtension(fromqstr(name)));
469 /** Convert relative path into absolute path based on a basepath.
470 If relpath is absolute, just use that.
471 If basepath doesn't exist use CWD.
473 QString makeAbsPath(QString const & relpath, QString const & base)
475 return toqstr(support::makeAbsPath(fromqstr(relpath),
476 fromqstr(base)).absFileName());
480 /////////////////////////////////////////////////////////////////////////
484 /////////////////////////////////////////////////////////////////////////
486 /** Given a string such as
487 * "<glob> <glob> ... *.{abc,def} <glob>",
488 * convert the csh-style brace expressions:
489 * "<glob> <glob> ... *.abc *.def <glob>".
490 * Requires no system support, so should work equally on Unix, Mac, Win32.
492 static string const convert_brace_glob(string const & glob)
494 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
495 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
496 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
497 // Matches "abc" and "abc,", storing "abc" as group 1,
498 // while ignoring surrounding spaces.
499 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
503 string::const_iterator it = glob.begin();
504 string::const_iterator const end = glob.end();
506 match_results<string::const_iterator> what;
507 if (!regex_search(it, end, what, glob_re)) {
508 // Ensure that no information is lost.
509 pattern += string(it, end);
513 // Everything from the start of the input to
514 // the start of the match.
515 pattern += string(what[-1].first, what[-1].second);
517 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
518 string const head = string(what[1].first, what[1].second);
519 string const tail = string(what[2].first, what[2].second);
521 // Split the ','-separated chunks of tail so that
522 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
523 string const fmt = " " + head + "$1";
524 pattern += regex_replace(tail, block_re, fmt);
526 // Increment the iterator to the end of the match.
527 it += distance(it, what[0].second);
536 /* \param description text describing the filters.
537 * \param one or more wildcard patterns, separated by
540 Filter(docstring const & description, std::string const & globs);
542 docstring const & description() const { return desc_; }
544 QString toString() const;
547 std::vector<std::string> globs_;
551 Filter::Filter(docstring const & description, string const & globs)
554 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
555 // "<glob> <glob> ... *.abc *.def <glob>"
556 string const expanded_globs = convert_brace_glob(globs);
558 // Split into individual globs.
559 globs_ = getVectorFromString(expanded_globs, " ");
563 QString Filter::toString() const
567 bool const has_description = !desc_.empty();
569 if (has_description) {
574 s += toqstr(getStringFromVector(globs_, " "));
582 /** \c FileFilterList parses a Qt-style list of available file filters
583 * to generate the corresponding vector.
584 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
585 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
586 * will result in a vector of size 1 in which the description field is empty.
588 struct FileFilterList
590 // FIXME UNICODE: globs_ should be unicode...
591 /** \param qt_style_filter a list of available file filters.
592 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
593 * The "All files (*)" filter is always added to the list.
595 explicit FileFilterList(docstring const & qt_style_filter =
598 typedef std::vector<Filter>::size_type size_type;
600 bool empty() const { return filters_.empty(); }
601 size_type size() const { return filters_.size(); }
602 Filter & operator[](size_type i) { return filters_[i]; }
603 Filter const & operator[](size_type i) const { return filters_[i]; }
605 void parse_filter(std::string const & filter);
606 std::vector<Filter> filters_;
610 FileFilterList::FileFilterList(docstring const & qt_style_filter)
613 string const filter = to_utf8(qt_style_filter)
614 + (qt_style_filter.empty() ? string() : ";;")
615 + to_utf8(_("All Files "))
622 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
623 // into individual filters.
624 static lyx::regex const separator_re(";;");
626 string::const_iterator it = filter.begin();
627 string::const_iterator const end = filter.end();
629 match_results<string::const_iterator> what;
631 if (!lyx::regex_search(it, end, what, separator_re)) {
632 parse_filter(string(it, end));
636 // Everything from the start of the input to
637 // the start of the match.
638 parse_filter(string(it, what[0].first));
640 // Increment the iterator to the end of the match.
641 it += distance(it, what[0].second);
646 void FileFilterList::parse_filter(string const & filter)
648 // Matches "TeX documents (plain) (*.tex)",
649 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
650 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
652 match_results<string::const_iterator> what;
653 if (!lyx::regex_search(filter, what, filter_re)) {
654 // Just a glob, no description.
655 filters_.push_back(Filter(docstring(), trim(filter)));
658 docstring const desc = from_utf8(string(what[1].first, what[1].second));
659 string const globs = string(what[2].first, what[2].second);
660 filters_.push_back(Filter(trim(desc), trim(globs)));
665 /** \returns the equivalent of the string passed in
666 * although any brace expressions are expanded.
667 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
669 QStringList fileFilters(QString const & desc)
671 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
672 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
673 FileFilterList filters(qstring_to_ucs4(desc));
674 //LYXERR0("DESC: " << desc);
676 for (size_t i = 0; i != filters.filters_.size(); ++i) {
677 QString f = filters.filters_[i].toString();
678 //LYXERR0("FILTER: " << f);
685 QString formatToolTip(QString text, int em)
687 // 1. QTooltip activates word wrapping only if mightBeRichText()
688 // is true. So we convert the text to rich text.
690 // 2. The default width is way too small. Setting the width is tricky; first
691 // one has to compute the ideal width, and then force it with special
694 // do nothing if empty or already formatted
695 if (text.isEmpty() || text.startsWith(QString("<html>")))
697 // Convert to rich text if it is not already
698 if (!Qt::mightBeRichText(text))
699 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
700 // Compute desired width in pixels
701 QFont const font = QToolTip::font();
702 int const px_width = em * QFontMetrics(font).width("M");
703 // Determine the ideal width of the tooltip
704 QTextDocument td("");
706 td.setDefaultFont(QToolTip::font());
707 td.setDocumentMargin(0);
708 td.setTextWidth(px_width);
709 double best_width = td.idealWidth();
710 // Set the line wrapping with appropriate width
711 return QString("<html><body><table><tr>"
712 "<td align=justify width=%1>%2</td>"
713 "</tr></table></body></html>")
714 .arg(QString::number(int(best_width) + 1), text);
718 QString qtHtmlToPlainText(QString const & html)
720 if (!Qt::mightBeRichText(html))
724 return td.toPlainText();