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"
27 #include "LyXAction.h"
28 #include "TextClass.h"
30 #include "support/convert.h"
31 #include "support/debug.h"
32 #include "support/gettext.h"
33 #include "support/Length.h"
34 #include "support/lstrings.h"
35 #include "support/lyxalgo.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 const & 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::Foreground, 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 LYXERR0("QUrl is invalid!");
296 if (!QDesktopServices::openUrl(qurl))
297 LYXERR0("Unable to open QUrl even though dir exists!");
300 void showTarget(string const & target){
301 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
302 if (prefixIs(target,"EXTERNAL ")) {
304 tar = split(target, tmp, ' ');
305 FuncRequest cmd = FuncRequest(LFUN_VC_COMMAND,"U . \"lyxpaperview " + tar + "\"");
309 QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode));
311 } // namespace frontend
313 QString const qt_(char const * str, const char *)
315 return toqstr(_(str));
319 QString const qt_(string const & str)
321 return toqstr(_(str));
325 QString const qt_(QString const & qstr)
327 return toqstr(_(fromqstr(qstr)));
331 void rescanTexStyles(string const & arg)
333 // Run rescan in user lyx directory
334 PathChanger p(package().user_support());
335 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
337 string const command = os::python() + ' ' +
338 quoteName(prog.toFilesystemEncoding()) + ' ' +
340 int const status = one.startscript(Systemcall::Wait, command);
344 frontend::Alert::error(_("Could not update TeX information"),
345 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
349 QStringList texFileList(QString const & filename)
352 FileName const file = libFileSearch(QString(), filename);
357 vector<docstring> doclist =
358 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
360 // Normalise paths like /foo//bar ==> /foo/bar
362 for (size_t i = 0; i != doclist.size(); ++i) {
363 QString qfile = toqstr(doclist[i]);
364 qfile.replace("\r", "");
365 while (qfile.contains("//"))
366 qfile.replace("//", "/");
367 if (!qfile.isEmpty())
372 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
373 return QList<QString>(set.begin(), set.end());
375 return QList<QString>::fromSet(set);
379 QString const externalLineEnding(docstring const & str)
382 // The MAC clipboard uses \r for lineendings, and we use \n
383 return toqstr(subst(str, '\n', '\r'));
384 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
385 // Windows clipboard uses \r\n for lineendings, and we use \n
386 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
393 docstring const internalLineEnding(QString const & str)
395 docstring const s = subst(qstring_to_ucs4(str),
396 from_ascii("\r\n"), from_ascii("\n"));
397 return subst(s, '\r', '\n');
401 QString internalPath(const QString & str)
403 return toqstr(os::internal_path(fromqstr(str)));
407 QString onlyFileName(const QString & str)
409 return toqstr(support::onlyFileName(fromqstr(str)));
413 QString onlyPath(const QString & str)
415 return toqstr(support::onlyPath(fromqstr(str)));
419 QString changeExtension(QString const & oldname, QString const & ext)
421 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
424 /// Remove the extension from \p name
425 QString removeExtension(QString const & name)
427 return toqstr(support::removeExtension(fromqstr(name)));
430 /** Add the extension \p ext to \p name.
431 Use this instead of changeExtension if you know that \p name is without
432 extension, because changeExtension would wrongly interpret \p name if it
435 QString addExtension(QString const & name, QString const & ext)
437 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
440 /// Return the extension of the file (not including the .)
441 QString getExtension(QString const & name)
443 return toqstr(support::getExtension(fromqstr(name)));
447 /** Convert relative path into absolute path based on a basepath.
448 If relpath is absolute, just use that.
449 If basepath doesn't exist use CWD.
451 QString makeAbsPath(QString const & relpath, QString const & base)
453 return toqstr(support::makeAbsPath(fromqstr(relpath),
454 fromqstr(base)).absFileName());
458 /////////////////////////////////////////////////////////////////////////
462 /////////////////////////////////////////////////////////////////////////
464 /** Given a string such as
465 * "<glob> <glob> ... *.{abc,def} <glob>",
466 * convert the csh-style brace expressions:
467 * "<glob> <glob> ... *.abc *.def <glob>".
468 * Requires no system support, so should work equally on Unix, Mac, Win32.
470 static string const convert_brace_glob(string const & glob)
472 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
473 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
474 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
475 // Matches "abc" and "abc,", storing "abc" as group 1,
476 // while ignoring surrounding spaces.
477 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
481 string::const_iterator it = glob.begin();
482 string::const_iterator const end = glob.end();
484 match_results<string::const_iterator> what;
485 if (!regex_search(it, end, what, glob_re)) {
486 // Ensure that no information is lost.
487 pattern += string(it, end);
491 // Everything from the start of the input to
492 // the start of the match.
493 pattern += string(what[-1].first, what[-1].second);
495 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
496 string const head = string(what[1].first, what[1].second);
497 string const tail = string(what[2].first, what[2].second);
499 // Split the ','-separated chunks of tail so that
500 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
501 string const fmt = " " + head + "$1";
502 pattern += regex_replace(tail, block_re, fmt);
504 // Increment the iterator to the end of the match.
505 it += distance(it, what[0].second);
514 /* \param description text describing the filters.
515 * \param one or more wildcard patterns, separated by
518 Filter(docstring const & description, std::string const & globs);
520 docstring const & description() const { return desc_; }
522 QString toString() const;
525 std::vector<std::string> globs_;
529 Filter::Filter(docstring const & description, string const & globs)
532 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
533 // "<glob> <glob> ... *.abc *.def <glob>"
534 string const expanded_globs = convert_brace_glob(globs);
536 // Split into individual globs.
537 globs_ = getVectorFromString(expanded_globs, " ");
541 QString Filter::toString() const
545 bool const has_description = !desc_.empty();
547 if (has_description) {
552 s += toqstr(getStringFromVector(globs_, " "));
560 /** \c FileFilterList parses a Qt-style list of available file filters
561 * to generate the corresponding vector.
562 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
563 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
564 * will result in a vector of size 1 in which the description field is empty.
566 struct FileFilterList
568 // FIXME UNICODE: globs_ should be unicode...
569 /** \param qt_style_filter a list of available file filters.
570 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
571 * The "All files (*)" filter is always added to the list.
573 explicit FileFilterList(docstring const & qt_style_filter =
576 typedef std::vector<Filter>::size_type size_type;
578 bool empty() const { return filters_.empty(); }
579 size_type size() const { return filters_.size(); }
580 Filter & operator[](size_type i) { return filters_[i]; }
581 Filter const & operator[](size_type i) const { return filters_[i]; }
583 void parse_filter(std::string const & filter);
584 std::vector<Filter> filters_;
588 FileFilterList::FileFilterList(docstring const & qt_style_filter)
591 string const filter = to_utf8(qt_style_filter)
592 + (qt_style_filter.empty() ? string() : ";;")
593 + to_utf8(_("All Files "))
600 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
601 // into individual filters.
602 static lyx::regex const separator_re(";;");
604 string::const_iterator it = filter.begin();
605 string::const_iterator const end = filter.end();
607 match_results<string::const_iterator> what;
609 if (!lyx::regex_search(it, end, what, separator_re)) {
610 parse_filter(string(it, end));
614 // Everything from the start of the input to
615 // the start of the match.
616 parse_filter(string(it, what[0].first));
618 // Increment the iterator to the end of the match.
619 it += distance(it, what[0].second);
624 void FileFilterList::parse_filter(string const & filter)
626 // Matches "TeX documents (plain) (*.tex)",
627 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
628 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
630 match_results<string::const_iterator> what;
631 if (!lyx::regex_search(filter, what, filter_re)) {
632 // Just a glob, no description.
633 filters_.push_back(Filter(docstring(), trim(filter)));
636 docstring const desc = from_utf8(string(what[1].first, what[1].second));
637 string const globs = string(what[2].first, what[2].second);
638 filters_.push_back(Filter(trim(desc), trim(globs)));
643 /** \returns the equivalent of the string passed in
644 * although any brace expressions are expanded.
645 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
647 QStringList fileFilters(QString const & desc)
649 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
650 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
651 FileFilterList filters(qstring_to_ucs4(desc));
652 //LYXERR0("DESC: " << desc);
654 for (size_t i = 0; i != filters.filters_.size(); ++i) {
655 QString f = filters.filters_[i].toString();
656 //LYXERR0("FILTER: " << f);
663 QString formatToolTip(QString text, int em)
665 // 1. QTooltip activates word wrapping only if mightBeRichText()
666 // is true. So we convert the text to rich text.
668 // 2. The default width is way too small. Setting the width is tricky; first
669 // one has to compute the ideal width, and then force it with special
672 // do nothing if empty or already formatted
673 if (text.isEmpty() || text.startsWith(QString("<html>")))
675 // Convert to rich text if it is not already
676 if (!Qt::mightBeRichText(text))
677 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
678 // Compute desired width in pixels
679 QFont const font = QToolTip::font();
680 int const px_width = em * QFontMetrics(font).width("M");
681 // Determine the ideal width of the tooltip
682 QTextDocument td("");
684 td.setDefaultFont(QToolTip::font());
685 td.setDocumentMargin(0);
686 td.setTextWidth(px_width);
687 double best_width = td.idealWidth();
688 // Set the line wrapping with appropriate width
689 return QString("<html><body><table><tr>"
690 "<td align=justify width=%1>%2</td>"
691 "</tr></table></body></html>")
692 .arg(QString::number(int(best_width) + 1), text);
696 QString qtHtmlToPlainText(QString const & html)
698 if (!Qt::mightBeRichText(html))
702 return td.toPlainText();