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"
26 #include "TextClass.h"
28 #include "support/convert.h"
29 #include "support/debug.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
32 #include "support/lyxalgo.h"
33 #include "support/os.h"
34 #include "support/Package.h"
35 #include "support/PathChanger.h"
36 #include "support/Systemcall.h"
38 #include <QApplication>
45 #include <QTextLayout>
46 #include <QTextDocument>
55 #include "support/regex.h"
59 using namespace lyx::support;
63 FileName libFileSearch(QString const & dir, QString const & name,
64 QString const & ext, search_mode mode)
66 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
70 FileName imageLibFileSearch(QString & dir, QString const & name,
71 QString const & ext, search_mode mode)
73 string tmp = fromqstr(dir);
74 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
81 double locstringToDouble(QString const & str)
85 double res = loc.toDouble(str, &ok);
88 QLocale c(QLocale::C);
89 res = c.toDouble(str);
99 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
101 QString const length = input->text();
102 if (length.isEmpty())
105 // Don't return unit-from-choice if the input(field) contains a unit
106 if (isValidGlueLength(fromqstr(length)))
107 return fromqstr(length);
109 Length::UNIT const unit = combo->currentLengthItem();
111 return Length(locstringToDouble(length.trimmed()), unit).asString();
115 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
117 QString const length = input->text();
118 if (length.isEmpty())
121 // don't return unit-from-choice if the input(field) contains a unit
122 if (isValidGlueLength(fromqstr(length)))
123 return Length(fromqstr(length));
125 Length::UNIT unit = Length::UNIT_NONE;
126 QString const item = combo->currentText();
127 for (int i = 0; i < num_units; i++) {
128 if (qt_(lyx::unit_name_gui[i]) == item) {
129 unit = unitFromString(unit_name[i]);
134 return Length(locstringToDouble(length.trimmed()), unit);
138 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
139 Length const & len, Length::UNIT /*defaultUnit*/)
142 // no length (UNIT_NONE)
143 combo->setCurrentItem(Length::defaultUnit());
146 combo->setCurrentItem(len.unit());
148 loc.setNumberOptions(QLocale::OmitGroupSeparator);
149 input->setText(formatLocFPNumber(Length(len).value()));
154 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
155 string const & len, Length::UNIT defaultUnit)
158 // no length (UNIT_NONE)
159 combo->setCurrentItem(defaultUnit);
161 } else if (!isValidLength(len) && !isStrDbl(len)) {
162 // use input field only for gluelengths
163 combo->setCurrentItem(defaultUnit);
164 input->setText(toqstr(len));
166 lengthToWidgets(input, combo, Length(len), defaultUnit);
171 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
172 docstring const & len, Length::UNIT defaultUnit)
174 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
178 double widgetToDouble(QLineEdit const * input)
180 QString const text = input->text();
184 return locstringToDouble(text.trimmed());
188 string widgetToDoubleStr(QLineEdit const * input)
190 return convert<string>(widgetToDouble(input));
194 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
197 loc.setNumberOptions(QLocale::OmitGroupSeparator);
198 input->setText(loc.toString(value, f, prec));
202 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
204 doubleToWidget(input, convert<double>(value), f, prec);
208 QString formatLocFPNumber(double d)
210 QString result = toqstr(formatFPNumber(d));
212 result.replace('.', loc.decimalPoint());
217 bool ColorSorter(ColorCode lhs, ColorCode rhs)
219 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
223 void setValid(QWidget * widget, bool valid)
226 widget->setPalette(QPalette());
228 QPalette pal = widget->palette();
229 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
230 widget->setPalette(pal);
235 void focusAndHighlight(QAbstractItemView * w)
238 w->setCurrentIndex(w->currentIndex());
239 w->scrollTo(w->currentIndex());
243 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
245 QPalette pal = QApplication::palette();
246 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
247 pal.color(QPalette::Active, QPalette::Highlight));
248 for (QWidget * w : highlighted)
249 w->setPalette(newpal);
250 for (QWidget * w : plain)
255 /// wrapper to hide the change of method name to setSectionResizeMode
256 void setSectionResizeMode(QHeaderView * view,
257 int logicalIndex, QHeaderView::ResizeMode mode) {
258 #if (QT_VERSION >= 0x050000)
259 view->setSectionResizeMode(logicalIndex, mode);
261 view->setResizeMode(logicalIndex, mode);
265 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
266 #if (QT_VERSION >= 0x050000)
267 view->setSectionResizeMode(mode);
269 view->setResizeMode(mode);
272 } // namespace frontend
274 QString const qt_(char const * str, const char *)
276 return toqstr(_(str));
280 QString const qt_(string const & str)
282 return toqstr(_(str));
286 QString const qt_(QString const & qstr)
288 return toqstr(_(fromqstr(qstr)));
292 void rescanTexStyles(string const & arg)
294 // Run rescan in user lyx directory
295 PathChanger p(package().user_support());
296 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
298 string const command = os::python() + ' ' +
299 quoteName(prog.toFilesystemEncoding()) + ' ' +
301 int const status = one.startscript(Systemcall::Wait, command);
305 frontend::Alert::error(_("Could not update TeX information"),
306 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
310 QStringList texFileList(QString const & filename)
313 FileName const file = libFileSearch(QString(), filename);
318 vector<docstring> doclist =
319 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
321 // Normalise paths like /foo//bar ==> /foo/bar
323 for (size_t i = 0; i != doclist.size(); ++i) {
324 QString file = toqstr(doclist[i]);
325 file.replace("\r", "");
326 while (file.contains("//"))
327 file.replace("//", "/");
333 return QList<QString>::fromSet(set);
336 QString const externalLineEnding(docstring const & str)
339 // The MAC clipboard uses \r for lineendings, and we use \n
340 return toqstr(subst(str, '\n', '\r'));
341 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
342 // Windows clipboard uses \r\n for lineendings, and we use \n
343 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
350 docstring const internalLineEnding(QString const & str)
352 docstring const s = subst(qstring_to_ucs4(str),
353 from_ascii("\r\n"), from_ascii("\n"));
354 return subst(s, '\r', '\n');
358 QString internalPath(const QString & str)
360 return toqstr(os::internal_path(fromqstr(str)));
364 QString onlyFileName(const QString & str)
366 return toqstr(support::onlyFileName(fromqstr(str)));
370 QString onlyPath(const QString & str)
372 return toqstr(support::onlyPath(fromqstr(str)));
376 QString changeExtension(QString const & oldname, QString const & ext)
378 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
381 /// Remove the extension from \p name
382 QString removeExtension(QString const & name)
384 return toqstr(support::removeExtension(fromqstr(name)));
387 /** Add the extension \p ext to \p name.
388 Use this instead of changeExtension if you know that \p name is without
389 extension, because changeExtension would wrongly interpret \p name if it
392 QString addExtension(QString const & name, QString const & ext)
394 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
397 /// Return the extension of the file (not including the .)
398 QString getExtension(QString const & name)
400 return toqstr(support::getExtension(fromqstr(name)));
404 /** Convert relative path into absolute path based on a basepath.
405 If relpath is absolute, just use that.
406 If basepath doesn't exist use CWD.
408 QString makeAbsPath(QString const & relpath, QString const & base)
410 return toqstr(support::makeAbsPath(fromqstr(relpath),
411 fromqstr(base)).absFileName());
415 /////////////////////////////////////////////////////////////////////////
419 /////////////////////////////////////////////////////////////////////////
421 /** Given a string such as
422 * "<glob> <glob> ... *.{abc,def} <glob>",
423 * convert the csh-style brace expresions:
424 * "<glob> <glob> ... *.abc *.def <glob>".
425 * Requires no system support, so should work equally on Unix, Mac, Win32.
427 static string const convert_brace_glob(string const & glob)
429 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
430 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
431 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
432 // Matches "abc" and "abc,", storing "abc" as group 1,
433 // while ignoring surrounding spaces.
434 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
438 string::const_iterator it = glob.begin();
439 string::const_iterator const end = glob.end();
441 match_results<string::const_iterator> what;
442 if (!regex_search(it, end, what, glob_re)) {
443 // Ensure that no information is lost.
444 pattern += string(it, end);
448 // Everything from the start of the input to
449 // the start of the match.
450 pattern += string(what[-1].first, what[-1].second);
452 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
453 string const head = string(what[1].first, what[1].second);
454 string const tail = string(what[2].first, what[2].second);
456 // Split the ','-separated chunks of tail so that
457 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
458 string const fmt = " " + head + "$1";
459 pattern += regex_replace(tail, block_re, fmt);
461 // Increment the iterator to the end of the match.
462 it += distance(it, what[0].second);
471 /* \param description text describing the filters.
472 * \param one or more wildcard patterns, separated by
475 Filter(docstring const & description, std::string const & globs);
477 docstring const & description() const { return desc_; }
479 QString toString() const;
482 std::vector<std::string> globs_;
486 Filter::Filter(docstring const & description, string const & globs)
489 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
490 // "<glob> <glob> ... *.abc *.def <glob>"
491 string const expanded_globs = convert_brace_glob(globs);
493 // Split into individual globs.
494 globs_ = getVectorFromString(expanded_globs, " ");
498 QString Filter::toString() const
502 bool const has_description = !desc_.empty();
504 if (has_description) {
509 s += toqstr(getStringFromVector(globs_, " "));
517 /** \c FileFilterList parses a Qt-style list of available file filters
518 * to generate the corresponding vector.
519 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
520 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
521 * will result in a vector of size 1 in which the description field is empty.
523 struct FileFilterList
525 // FIXME UNICODE: globs_ should be unicode...
526 /** \param qt_style_filter a list of available file filters.
527 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
528 * The "All files (*)" filter is always added to the list.
530 explicit FileFilterList(docstring const & qt_style_filter =
533 typedef std::vector<Filter>::size_type size_type;
535 bool empty() const { return filters_.empty(); }
536 size_type size() const { return filters_.size(); }
537 Filter & operator[](size_type i) { return filters_[i]; }
538 Filter const & operator[](size_type i) const { return filters_[i]; }
540 void parse_filter(std::string const & filter);
541 std::vector<Filter> filters_;
545 FileFilterList::FileFilterList(docstring const & qt_style_filter)
548 string const filter = to_utf8(qt_style_filter)
549 + (qt_style_filter.empty() ? string() : ";;")
550 + to_utf8(_("All Files "))
557 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
558 // into individual filters.
559 static lyx::regex const separator_re(";;");
561 string::const_iterator it = filter.begin();
562 string::const_iterator const end = filter.end();
564 match_results<string::const_iterator> what;
566 if (!lyx::regex_search(it, end, what, separator_re)) {
567 parse_filter(string(it, end));
571 // Everything from the start of the input to
572 // the start of the match.
573 parse_filter(string(it, what[0].first));
575 // Increment the iterator to the end of the match.
576 it += distance(it, what[0].second);
581 void FileFilterList::parse_filter(string const & filter)
583 // Matches "TeX documents (plain) (*.tex)",
584 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
585 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
587 match_results<string::const_iterator> what;
588 if (!lyx::regex_search(filter, what, filter_re)) {
589 // Just a glob, no description.
590 filters_.push_back(Filter(docstring(), trim(filter)));
593 docstring const desc = from_utf8(string(what[1].first, what[1].second));
594 string const globs = string(what[2].first, what[2].second);
595 filters_.push_back(Filter(trim(desc), trim(globs)));
600 /** \returns the equivalent of the string passed in
601 * although any brace expressions are expanded.
602 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
604 QStringList fileFilters(QString const & desc)
606 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
607 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
608 FileFilterList filters(qstring_to_ucs4(desc));
609 //LYXERR0("DESC: " << desc);
611 for (size_t i = 0; i != filters.filters_.size(); ++i) {
612 QString f = filters.filters_[i].toString();
613 //LYXERR0("FILTER: " << f);
620 QString formatToolTip(QString text, int em)
622 // 1. QTooltip activates word wrapping only if mightBeRichText()
623 // is true. So we convert the text to rich text.
625 // 2. The default width is way too small. Setting the width is tricky; first
626 // one has to compute the ideal width, and then force it with special
629 // do nothing if empty or already formatted
630 if (text.isEmpty() || text.startsWith(QString("<html>")))
632 // Convert to rich text if it is not already
633 if (!Qt::mightBeRichText(text))
634 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
635 // Compute desired width in pixels
636 QFont const font = QToolTip::font();
637 int const px_width = em * QFontMetrics(font).width("M");
638 // Determine the ideal width of the tooltip
639 QTextDocument td("");
641 td.setDefaultFont(QToolTip::font());
642 td.setTextWidth(px_width);
643 double best_width = td.idealWidth();
644 // Set the line wrapping with appropriate width
645 return QString("<html><body><table><tr>"
646 "<td align=justify width=%1>%2</td>"
647 "</tr></table></body></html>")
648 .arg(QString::number(int(best_width) + 1), text);
652 QString qtHtmlToPlainText(QString const & html)
654 if (!Qt::mightBeRichText(html))
658 return td.toPlainText();