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){
302 LYXERR(Debug::INSETS, "Showtarget:" << target << "\n");
303 if (prefixIs(target,"EXTERNAL ")) {
304 if (lyxrc.citation_search_view.empty())
307 tar = split(target, tmp, ' ');
308 FuncRequest cmd = FuncRequest(LFUN_VC_COMMAND,"U . \"" +
309 lyxrc.citation_search_view + " " + tar + "\"");
313 QDesktopServices::openUrl(QUrl(toqstr(target), QUrl::TolerantMode));
315 } // namespace frontend
317 QString const qt_(char const * str, const char *)
319 return toqstr(_(str));
323 QString const qt_(string const & str)
325 return toqstr(_(str));
329 QString const qt_(QString const & qstr)
331 return toqstr(_(fromqstr(qstr)));
335 void rescanTexStyles(string const & arg)
337 // Run rescan in user lyx directory
338 PathChanger p(package().user_support());
339 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
341 string const command = os::python() + ' ' +
342 quoteName(prog.toFilesystemEncoding()) + ' ' +
344 int const status = one.startscript(Systemcall::Wait, command);
348 frontend::Alert::error(_("Could not update TeX information"),
349 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
353 QStringList texFileList(QString const & filename)
356 FileName const file = libFileSearch(QString(), filename);
361 vector<docstring> doclist =
362 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
364 // Normalise paths like /foo//bar ==> /foo/bar
366 for (size_t i = 0; i != doclist.size(); ++i) {
367 QString qfile = toqstr(doclist[i]);
368 qfile.replace("\r", "");
369 while (qfile.contains("//"))
370 qfile.replace("//", "/");
371 if (!qfile.isEmpty())
376 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
377 return QList<QString>(set.begin(), set.end());
379 return QList<QString>::fromSet(set);
383 QString const externalLineEnding(docstring const & str)
386 // The MAC clipboard uses \r for lineendings, and we use \n
387 return toqstr(subst(str, '\n', '\r'));
388 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
389 // Windows clipboard uses \r\n for lineendings, and we use \n
390 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
397 docstring const internalLineEnding(QString const & str)
399 docstring const s = subst(qstring_to_ucs4(str),
400 from_ascii("\r\n"), from_ascii("\n"));
401 return subst(s, '\r', '\n');
405 QString internalPath(const QString & str)
407 return toqstr(os::internal_path(fromqstr(str)));
411 QString onlyFileName(const QString & str)
413 return toqstr(support::onlyFileName(fromqstr(str)));
417 QString onlyPath(const QString & str)
419 return toqstr(support::onlyPath(fromqstr(str)));
423 QString changeExtension(QString const & oldname, QString const & ext)
425 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
428 /// Remove the extension from \p name
429 QString removeExtension(QString const & name)
431 return toqstr(support::removeExtension(fromqstr(name)));
434 /** Add the extension \p ext to \p name.
435 Use this instead of changeExtension if you know that \p name is without
436 extension, because changeExtension would wrongly interpret \p name if it
439 QString addExtension(QString const & name, QString const & ext)
441 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
444 /// Return the extension of the file (not including the .)
445 QString getExtension(QString const & name)
447 return toqstr(support::getExtension(fromqstr(name)));
451 /** Convert relative path into absolute path based on a basepath.
452 If relpath is absolute, just use that.
453 If basepath doesn't exist use CWD.
455 QString makeAbsPath(QString const & relpath, QString const & base)
457 return toqstr(support::makeAbsPath(fromqstr(relpath),
458 fromqstr(base)).absFileName());
462 /////////////////////////////////////////////////////////////////////////
466 /////////////////////////////////////////////////////////////////////////
468 /** Given a string such as
469 * "<glob> <glob> ... *.{abc,def} <glob>",
470 * convert the csh-style brace expressions:
471 * "<glob> <glob> ... *.abc *.def <glob>".
472 * Requires no system support, so should work equally on Unix, Mac, Win32.
474 static string const convert_brace_glob(string const & glob)
476 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
477 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
478 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
479 // Matches "abc" and "abc,", storing "abc" as group 1,
480 // while ignoring surrounding spaces.
481 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
485 string::const_iterator it = glob.begin();
486 string::const_iterator const end = glob.end();
488 match_results<string::const_iterator> what;
489 if (!regex_search(it, end, what, glob_re)) {
490 // Ensure that no information is lost.
491 pattern += string(it, end);
495 // Everything from the start of the input to
496 // the start of the match.
497 pattern += string(what[-1].first, what[-1].second);
499 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
500 string const head = string(what[1].first, what[1].second);
501 string const tail = string(what[2].first, what[2].second);
503 // Split the ','-separated chunks of tail so that
504 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
505 string const fmt = " " + head + "$1";
506 pattern += regex_replace(tail, block_re, fmt);
508 // Increment the iterator to the end of the match.
509 it += distance(it, what[0].second);
518 /* \param description text describing the filters.
519 * \param one or more wildcard patterns, separated by
522 Filter(docstring const & description, std::string const & globs);
524 docstring const & description() const { return desc_; }
526 QString toString() const;
529 std::vector<std::string> globs_;
533 Filter::Filter(docstring const & description, string const & globs)
536 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
537 // "<glob> <glob> ... *.abc *.def <glob>"
538 string const expanded_globs = convert_brace_glob(globs);
540 // Split into individual globs.
541 globs_ = getVectorFromString(expanded_globs, " ");
545 QString Filter::toString() const
549 bool const has_description = !desc_.empty();
551 if (has_description) {
556 s += toqstr(getStringFromVector(globs_, " "));
564 /** \c FileFilterList parses a Qt-style list of available file filters
565 * to generate the corresponding vector.
566 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
567 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
568 * will result in a vector of size 1 in which the description field is empty.
570 struct FileFilterList
572 // FIXME UNICODE: globs_ should be unicode...
573 /** \param qt_style_filter a list of available file filters.
574 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
575 * The "All files (*)" filter is always added to the list.
577 explicit FileFilterList(docstring const & qt_style_filter =
580 typedef std::vector<Filter>::size_type size_type;
582 bool empty() const { return filters_.empty(); }
583 size_type size() const { return filters_.size(); }
584 Filter & operator[](size_type i) { return filters_[i]; }
585 Filter const & operator[](size_type i) const { return filters_[i]; }
587 void parse_filter(std::string const & filter);
588 std::vector<Filter> filters_;
592 FileFilterList::FileFilterList(docstring const & qt_style_filter)
595 string const filter = to_utf8(qt_style_filter)
596 + (qt_style_filter.empty() ? string() : ";;")
597 + to_utf8(_("All Files "))
604 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
605 // into individual filters.
606 static lyx::regex const separator_re(";;");
608 string::const_iterator it = filter.begin();
609 string::const_iterator const end = filter.end();
611 match_results<string::const_iterator> what;
613 if (!lyx::regex_search(it, end, what, separator_re)) {
614 parse_filter(string(it, end));
618 // Everything from the start of the input to
619 // the start of the match.
620 parse_filter(string(it, what[0].first));
622 // Increment the iterator to the end of the match.
623 it += distance(it, what[0].second);
628 void FileFilterList::parse_filter(string const & filter)
630 // Matches "TeX documents (plain) (*.tex)",
631 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
632 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
634 match_results<string::const_iterator> what;
635 if (!lyx::regex_search(filter, what, filter_re)) {
636 // Just a glob, no description.
637 filters_.push_back(Filter(docstring(), trim(filter)));
640 docstring const desc = from_utf8(string(what[1].first, what[1].second));
641 string const globs = string(what[2].first, what[2].second);
642 filters_.push_back(Filter(trim(desc), trim(globs)));
647 /** \returns the equivalent of the string passed in
648 * although any brace expressions are expanded.
649 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
651 QStringList fileFilters(QString const & desc)
653 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
654 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
655 FileFilterList filters(qstring_to_ucs4(desc));
656 //LYXERR0("DESC: " << desc);
658 for (size_t i = 0; i != filters.filters_.size(); ++i) {
659 QString f = filters.filters_[i].toString();
660 //LYXERR0("FILTER: " << f);
667 QString formatToolTip(QString text, int em)
669 // 1. QTooltip activates word wrapping only if mightBeRichText()
670 // is true. So we convert the text to rich text.
672 // 2. The default width is way too small. Setting the width is tricky; first
673 // one has to compute the ideal width, and then force it with special
676 // do nothing if empty or already formatted
677 if (text.isEmpty() || text.startsWith(QString("<html>")))
679 // Convert to rich text if it is not already
680 if (!Qt::mightBeRichText(text))
681 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
682 // Compute desired width in pixels
683 QFont const font = QToolTip::font();
684 int const px_width = em * QFontMetrics(font).width("M");
685 // Determine the ideal width of the tooltip
686 QTextDocument td("");
688 td.setDefaultFont(QToolTip::font());
689 td.setDocumentMargin(0);
690 td.setTextWidth(px_width);
691 double best_width = td.idealWidth();
692 // Set the line wrapping with appropriate width
693 return QString("<html><body><table><tr>"
694 "<td align=justify width=%1>%2</td>"
695 "</tr></table></body></html>")
696 .arg(QString::number(int(best_width) + 1), text);
700 QString qtHtmlToPlainText(QString const & html)
702 if (!Qt::mightBeRichText(html))
706 return td.toPlainText();