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 SortLocaleAware(QString const & lhs, QString const & rhs)
219 return QString::localeAwareCompare(lhs, rhs) < 0;
223 bool ColorSorter(ColorCode lhs, ColorCode rhs)
225 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
229 void setValid(QWidget * widget, bool valid)
232 widget->setPalette(QPalette());
234 QPalette pal = widget->palette();
235 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
236 widget->setPalette(pal);
241 void focusAndHighlight(QAbstractItemView * w)
244 w->setCurrentIndex(w->currentIndex());
245 w->scrollTo(w->currentIndex());
249 void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
251 QPalette pal = QApplication::palette();
252 QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
253 pal.color(QPalette::Active, QPalette::Highlight));
254 for (QWidget * w : highlighted)
255 w->setPalette(newpal);
256 for (QWidget * w : plain)
261 /// wrapper to hide the change of method name to setSectionResizeMode
262 void setSectionResizeMode(QHeaderView * view,
263 int logicalIndex, QHeaderView::ResizeMode mode) {
264 #if (QT_VERSION >= 0x050000)
265 view->setSectionResizeMode(logicalIndex, mode);
267 view->setResizeMode(logicalIndex, mode);
271 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
272 #if (QT_VERSION >= 0x050000)
273 view->setSectionResizeMode(mode);
275 view->setResizeMode(mode);
278 } // namespace frontend
280 QString const qt_(char const * str, const char *)
282 return toqstr(_(str));
286 QString const qt_(string const & str)
288 return toqstr(_(str));
292 QString const qt_(QString const & qstr)
294 return toqstr(_(fromqstr(qstr)));
298 void rescanTexStyles(string const & arg)
300 // Run rescan in user lyx directory
301 PathChanger p(package().user_support());
302 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
304 string const command = os::python() + ' ' +
305 quoteName(prog.toFilesystemEncoding()) + ' ' +
307 int const status = one.startscript(Systemcall::Wait, command);
311 frontend::Alert::error(_("Could not update TeX information"),
312 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
316 QStringList texFileList(QString const & filename)
319 FileName const file = libFileSearch(QString(), filename);
324 vector<docstring> doclist =
325 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
327 // Normalise paths like /foo//bar ==> /foo/bar
329 for (size_t i = 0; i != doclist.size(); ++i) {
330 QString qfile = toqstr(doclist[i]);
331 qfile.replace("\r", "");
332 while (qfile.contains("//"))
333 qfile.replace("//", "/");
334 if (!qfile.isEmpty())
339 return QList<QString>::fromSet(set);
342 QString const externalLineEnding(docstring const & str)
345 // The MAC clipboard uses \r for lineendings, and we use \n
346 return toqstr(subst(str, '\n', '\r'));
347 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
348 // Windows clipboard uses \r\n for lineendings, and we use \n
349 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
356 docstring const internalLineEnding(QString const & str)
358 docstring const s = subst(qstring_to_ucs4(str),
359 from_ascii("\r\n"), from_ascii("\n"));
360 return subst(s, '\r', '\n');
364 QString internalPath(const QString & str)
366 return toqstr(os::internal_path(fromqstr(str)));
370 QString onlyFileName(const QString & str)
372 return toqstr(support::onlyFileName(fromqstr(str)));
376 QString onlyPath(const QString & str)
378 return toqstr(support::onlyPath(fromqstr(str)));
382 QString changeExtension(QString const & oldname, QString const & ext)
384 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
387 /// Remove the extension from \p name
388 QString removeExtension(QString const & name)
390 return toqstr(support::removeExtension(fromqstr(name)));
393 /** Add the extension \p ext to \p name.
394 Use this instead of changeExtension if you know that \p name is without
395 extension, because changeExtension would wrongly interpret \p name if it
398 QString addExtension(QString const & name, QString const & ext)
400 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
403 /// Return the extension of the file (not including the .)
404 QString getExtension(QString const & name)
406 return toqstr(support::getExtension(fromqstr(name)));
410 /** Convert relative path into absolute path based on a basepath.
411 If relpath is absolute, just use that.
412 If basepath doesn't exist use CWD.
414 QString makeAbsPath(QString const & relpath, QString const & base)
416 return toqstr(support::makeAbsPath(fromqstr(relpath),
417 fromqstr(base)).absFileName());
421 /////////////////////////////////////////////////////////////////////////
425 /////////////////////////////////////////////////////////////////////////
427 /** Given a string such as
428 * "<glob> <glob> ... *.{abc,def} <glob>",
429 * convert the csh-style brace expresions:
430 * "<glob> <glob> ... *.abc *.def <glob>".
431 * Requires no system support, so should work equally on Unix, Mac, Win32.
433 static string const convert_brace_glob(string const & glob)
435 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
436 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
437 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
438 // Matches "abc" and "abc,", storing "abc" as group 1,
439 // while ignoring surrounding spaces.
440 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
444 string::const_iterator it = glob.begin();
445 string::const_iterator const end = glob.end();
447 match_results<string::const_iterator> what;
448 if (!regex_search(it, end, what, glob_re)) {
449 // Ensure that no information is lost.
450 pattern += string(it, end);
454 // Everything from the start of the input to
455 // the start of the match.
456 pattern += string(what[-1].first, what[-1].second);
458 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
459 string const head = string(what[1].first, what[1].second);
460 string const tail = string(what[2].first, what[2].second);
462 // Split the ','-separated chunks of tail so that
463 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
464 string const fmt = " " + head + "$1";
465 pattern += regex_replace(tail, block_re, fmt);
467 // Increment the iterator to the end of the match.
468 it += distance(it, what[0].second);
477 /* \param description text describing the filters.
478 * \param one or more wildcard patterns, separated by
481 Filter(docstring const & description, std::string const & globs);
483 docstring const & description() const { return desc_; }
485 QString toString() const;
488 std::vector<std::string> globs_;
492 Filter::Filter(docstring const & description, string const & globs)
495 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
496 // "<glob> <glob> ... *.abc *.def <glob>"
497 string const expanded_globs = convert_brace_glob(globs);
499 // Split into individual globs.
500 globs_ = getVectorFromString(expanded_globs, " ");
504 QString Filter::toString() const
508 bool const has_description = !desc_.empty();
510 if (has_description) {
515 s += toqstr(getStringFromVector(globs_, " "));
523 /** \c FileFilterList parses a Qt-style list of available file filters
524 * to generate the corresponding vector.
525 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
526 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
527 * will result in a vector of size 1 in which the description field is empty.
529 struct FileFilterList
531 // FIXME UNICODE: globs_ should be unicode...
532 /** \param qt_style_filter a list of available file filters.
533 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
534 * The "All files (*)" filter is always added to the list.
536 explicit FileFilterList(docstring const & qt_style_filter =
539 typedef std::vector<Filter>::size_type size_type;
541 bool empty() const { return filters_.empty(); }
542 size_type size() const { return filters_.size(); }
543 Filter & operator[](size_type i) { return filters_[i]; }
544 Filter const & operator[](size_type i) const { return filters_[i]; }
546 void parse_filter(std::string const & filter);
547 std::vector<Filter> filters_;
551 FileFilterList::FileFilterList(docstring const & qt_style_filter)
554 string const filter = to_utf8(qt_style_filter)
555 + (qt_style_filter.empty() ? string() : ";;")
556 + to_utf8(_("All Files "))
563 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
564 // into individual filters.
565 static lyx::regex const separator_re(";;");
567 string::const_iterator it = filter.begin();
568 string::const_iterator const end = filter.end();
570 match_results<string::const_iterator> what;
572 if (!lyx::regex_search(it, end, what, separator_re)) {
573 parse_filter(string(it, end));
577 // Everything from the start of the input to
578 // the start of the match.
579 parse_filter(string(it, what[0].first));
581 // Increment the iterator to the end of the match.
582 it += distance(it, what[0].second);
587 void FileFilterList::parse_filter(string const & filter)
589 // Matches "TeX documents (plain) (*.tex)",
590 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
591 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
593 match_results<string::const_iterator> what;
594 if (!lyx::regex_search(filter, what, filter_re)) {
595 // Just a glob, no description.
596 filters_.push_back(Filter(docstring(), trim(filter)));
599 docstring const desc = from_utf8(string(what[1].first, what[1].second));
600 string const globs = string(what[2].first, what[2].second);
601 filters_.push_back(Filter(trim(desc), trim(globs)));
606 /** \returns the equivalent of the string passed in
607 * although any brace expressions are expanded.
608 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
610 QStringList fileFilters(QString const & desc)
612 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
613 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
614 FileFilterList filters(qstring_to_ucs4(desc));
615 //LYXERR0("DESC: " << desc);
617 for (size_t i = 0; i != filters.filters_.size(); ++i) {
618 QString f = filters.filters_[i].toString();
619 //LYXERR0("FILTER: " << f);
626 QString formatToolTip(QString text, int em)
628 // 1. QTooltip activates word wrapping only if mightBeRichText()
629 // is true. So we convert the text to rich text.
631 // 2. The default width is way too small. Setting the width is tricky; first
632 // one has to compute the ideal width, and then force it with special
635 // do nothing if empty or already formatted
636 if (text.isEmpty() || text.startsWith(QString("<html>")))
638 // Convert to rich text if it is not already
639 if (!Qt::mightBeRichText(text))
640 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
641 // Compute desired width in pixels
642 QFont const font = QToolTip::font();
643 int const px_width = em * QFontMetrics(font).width("M");
644 // Determine the ideal width of the tooltip
645 QTextDocument td("");
647 td.setDefaultFont(QToolTip::font());
648 td.setTextWidth(px_width);
649 double best_width = td.idealWidth();
650 // Set the line wrapping with appropriate width
651 return QString("<html><body><table><tr>"
652 "<td align=justify width=%1>%2</td>"
653 "</tr></table></body></html>")
654 .arg(QString::number(int(best_width) + 1), text);
658 QString qtHtmlToPlainText(QString const & html)
660 if (!Qt::mightBeRichText(html))
664 return td.toPlainText();