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){
302 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
303 if (prefixIs(target, "EXTERNAL ")) {
304 if (!lyxrc.citation_search)
306 string tmp, tar, opts;
307 tar = split(target, tmp, ' ');
309 opts = " -v " + pdfv;
311 opts += " -w " + psv;
314 FuncRequest cmd = FuncRequest(LFUN_VC_COMMAND,"U . \"" +
315 lyxrc.citation_search_view + " " + opts + tar + "\"");
319 QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode));
321 } // namespace frontend
323 QString const qt_(char const * str, const char *)
325 return toqstr(_(str));
329 QString const qt_(string const & str)
331 return toqstr(_(str));
335 QString const qt_(QString const & qstr)
337 return toqstr(_(fromqstr(qstr)));
341 void rescanTexStyles(string const & arg)
343 // Run rescan in user lyx directory
344 PathChanger p(package().user_support());
345 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
347 string const command = os::python() + ' ' +
348 quoteName(prog.toFilesystemEncoding()) + ' ' +
350 int const status = one.startscript(Systemcall::Wait, command);
354 frontend::Alert::error(_("Could not update TeX information"),
355 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
359 QStringList texFileList(QString const & filename)
362 FileName const file = libFileSearch(QString(), filename);
367 vector<docstring> doclist =
368 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
370 // Normalise paths like /foo//bar ==> /foo/bar
372 for (size_t i = 0; i != doclist.size(); ++i) {
373 QString qfile = toqstr(doclist[i]);
374 qfile.replace("\r", "");
375 while (qfile.contains("//"))
376 qfile.replace("//", "/");
377 if (!qfile.isEmpty())
382 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
383 return QList<QString>(set.begin(), set.end());
385 return QList<QString>::fromSet(set);
389 QString const externalLineEnding(docstring const & str)
392 // The MAC clipboard uses \r for lineendings, and we use \n
393 return toqstr(subst(str, '\n', '\r'));
394 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
395 // Windows clipboard uses \r\n for lineendings, and we use \n
396 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
403 docstring const internalLineEnding(QString const & str)
405 docstring const s = subst(qstring_to_ucs4(str),
406 from_ascii("\r\n"), from_ascii("\n"));
407 return subst(s, '\r', '\n');
411 QString internalPath(const QString & str)
413 return toqstr(os::internal_path(fromqstr(str)));
417 QString onlyFileName(const QString & str)
419 return toqstr(support::onlyFileName(fromqstr(str)));
423 QString onlyPath(const QString & str)
425 return toqstr(support::onlyPath(fromqstr(str)));
429 QString changeExtension(QString const & oldname, QString const & ext)
431 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
434 /// Remove the extension from \p name
435 QString removeExtension(QString const & name)
437 return toqstr(support::removeExtension(fromqstr(name)));
440 /** Add the extension \p ext to \p name.
441 Use this instead of changeExtension if you know that \p name is without
442 extension, because changeExtension would wrongly interpret \p name if it
445 QString addExtension(QString const & name, QString const & ext)
447 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
450 /// Return the extension of the file (not including the .)
451 QString getExtension(QString const & name)
453 return toqstr(support::getExtension(fromqstr(name)));
457 /** Convert relative path into absolute path based on a basepath.
458 If relpath is absolute, just use that.
459 If basepath doesn't exist use CWD.
461 QString makeAbsPath(QString const & relpath, QString const & base)
463 return toqstr(support::makeAbsPath(fromqstr(relpath),
464 fromqstr(base)).absFileName());
468 /////////////////////////////////////////////////////////////////////////
472 /////////////////////////////////////////////////////////////////////////
474 /** Given a string such as
475 * "<glob> <glob> ... *.{abc,def} <glob>",
476 * convert the csh-style brace expressions:
477 * "<glob> <glob> ... *.abc *.def <glob>".
478 * Requires no system support, so should work equally on Unix, Mac, Win32.
480 static string const convert_brace_glob(string const & glob)
482 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
483 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
484 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
485 // Matches "abc" and "abc,", storing "abc" as group 1,
486 // while ignoring surrounding spaces.
487 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
491 string::const_iterator it = glob.begin();
492 string::const_iterator const end = glob.end();
494 match_results<string::const_iterator> what;
495 if (!regex_search(it, end, what, glob_re)) {
496 // Ensure that no information is lost.
497 pattern += string(it, end);
501 // Everything from the start of the input to
502 // the start of the match.
503 pattern += string(what[-1].first, what[-1].second);
505 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
506 string const head = string(what[1].first, what[1].second);
507 string const tail = string(what[2].first, what[2].second);
509 // Split the ','-separated chunks of tail so that
510 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
511 string const fmt = " " + head + "$1";
512 pattern += regex_replace(tail, block_re, fmt);
514 // Increment the iterator to the end of the match.
515 it += distance(it, what[0].second);
524 /* \param description text describing the filters.
525 * \param one or more wildcard patterns, separated by
528 Filter(docstring const & description, std::string const & globs);
530 docstring const & description() const { return desc_; }
532 QString toString() const;
535 std::vector<std::string> globs_;
539 Filter::Filter(docstring const & description, string const & globs)
542 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
543 // "<glob> <glob> ... *.abc *.def <glob>"
544 string const expanded_globs = convert_brace_glob(globs);
546 // Split into individual globs.
547 globs_ = getVectorFromString(expanded_globs, " ");
551 QString Filter::toString() const
555 bool const has_description = !desc_.empty();
557 if (has_description) {
562 s += toqstr(getStringFromVector(globs_, " "));
570 /** \c FileFilterList parses a Qt-style list of available file filters
571 * to generate the corresponding vector.
572 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
573 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
574 * will result in a vector of size 1 in which the description field is empty.
576 struct FileFilterList
578 // FIXME UNICODE: globs_ should be unicode...
579 /** \param qt_style_filter a list of available file filters.
580 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
581 * The "All files (*)" filter is always added to the list.
583 explicit FileFilterList(docstring const & qt_style_filter =
586 typedef std::vector<Filter>::size_type size_type;
588 bool empty() const { return filters_.empty(); }
589 size_type size() const { return filters_.size(); }
590 Filter & operator[](size_type i) { return filters_[i]; }
591 Filter const & operator[](size_type i) const { return filters_[i]; }
593 void parse_filter(std::string const & filter);
594 std::vector<Filter> filters_;
598 FileFilterList::FileFilterList(docstring const & qt_style_filter)
601 string const filter = to_utf8(qt_style_filter)
602 + (qt_style_filter.empty() ? string() : ";;")
603 + to_utf8(_("All Files "))
610 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
611 // into individual filters.
612 static lyx::regex const separator_re(";;");
614 string::const_iterator it = filter.begin();
615 string::const_iterator const end = filter.end();
617 match_results<string::const_iterator> what;
619 if (!lyx::regex_search(it, end, what, separator_re)) {
620 parse_filter(string(it, end));
624 // Everything from the start of the input to
625 // the start of the match.
626 parse_filter(string(it, what[0].first));
628 // Increment the iterator to the end of the match.
629 it += distance(it, what[0].second);
634 void FileFilterList::parse_filter(string const & filter)
636 // Matches "TeX documents (plain) (*.tex)",
637 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
638 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
640 match_results<string::const_iterator> what;
641 if (!lyx::regex_search(filter, what, filter_re)) {
642 // Just a glob, no description.
643 filters_.push_back(Filter(docstring(), trim(filter)));
646 docstring const desc = from_utf8(string(what[1].first, what[1].second));
647 string const globs = string(what[2].first, what[2].second);
648 filters_.push_back(Filter(trim(desc), trim(globs)));
653 /** \returns the equivalent of the string passed in
654 * although any brace expressions are expanded.
655 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
657 QStringList fileFilters(QString const & desc)
659 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
660 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
661 FileFilterList filters(qstring_to_ucs4(desc));
662 //LYXERR0("DESC: " << desc);
664 for (size_t i = 0; i != filters.filters_.size(); ++i) {
665 QString f = filters.filters_[i].toString();
666 //LYXERR0("FILTER: " << f);
673 QString formatToolTip(QString text, int em)
675 // 1. QTooltip activates word wrapping only if mightBeRichText()
676 // is true. So we convert the text to rich text.
678 // 2. The default width is way too small. Setting the width is tricky; first
679 // one has to compute the ideal width, and then force it with special
682 // do nothing if empty or already formatted
683 if (text.isEmpty() || text.startsWith(QString("<html>")))
685 // Convert to rich text if it is not already
686 if (!Qt::mightBeRichText(text))
687 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
688 // Compute desired width in pixels
689 QFont const font = QToolTip::font();
690 int const px_width = em * QFontMetrics(font).width("M");
691 // Determine the ideal width of the tooltip
692 QTextDocument td("");
694 td.setDefaultFont(QToolTip::font());
695 td.setDocumentMargin(0);
696 td.setTextWidth(px_width);
697 double best_width = td.idealWidth();
698 // Set the line wrapping with appropriate width
699 return QString("<html><body><table><tr>"
700 "<td align=justify width=%1>%2</td>"
701 "</tr></table></body></html>")
702 .arg(QString::number(int(best_width) + 1), text);
706 QString qtHtmlToPlainText(QString const & html)
708 if (!Qt::mightBeRichText(html))
712 return td.toPlainText();