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"
44 #include <QTextLayout>
45 #include <QTextDocument>
54 #include "support/regex.h"
58 using namespace lyx::support;
62 FileName libFileSearch(QString const & dir, QString const & name,
63 QString const & ext, search_mode mode)
65 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
69 FileName imageLibFileSearch(QString & dir, QString const & name,
70 QString const & ext, search_mode mode)
72 string tmp = fromqstr(dir);
73 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
80 double locstringToDouble(QString const & str)
84 double res = loc.toDouble(str, &ok);
87 QLocale c(QLocale::C);
88 res = c.toDouble(str);
98 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
100 QString const length = input->text();
101 if (length.isEmpty())
104 // Don't return unit-from-choice if the input(field) contains a unit
105 if (isValidGlueLength(fromqstr(length)))
106 return fromqstr(length);
108 Length::UNIT const unit = combo->currentLengthItem();
110 return Length(locstringToDouble(length.trimmed()), unit).asString();
114 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
116 QString const length = input->text();
117 if (length.isEmpty())
120 // don't return unit-from-choice if the input(field) contains a unit
121 if (isValidGlueLength(fromqstr(length)))
122 return Length(fromqstr(length));
124 Length::UNIT unit = Length::UNIT_NONE;
125 QString const item = combo->currentText();
126 for (int i = 0; i < num_units; i++) {
127 if (qt_(lyx::unit_name_gui[i]) == item) {
128 unit = unitFromString(unit_name[i]);
133 return Length(locstringToDouble(length.trimmed()), unit);
137 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
138 Length const & len, Length::UNIT /*defaultUnit*/)
141 // no length (UNIT_NONE)
142 combo->setCurrentItem(Length::defaultUnit());
145 combo->setCurrentItem(len.unit());
147 loc.setNumberOptions(QLocale::OmitGroupSeparator);
148 input->setText(formatLocFPNumber(Length(len).value()));
153 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
154 string const & len, Length::UNIT defaultUnit)
157 // no length (UNIT_NONE)
158 combo->setCurrentItem(defaultUnit);
160 } else if (!isValidLength(len) && !isStrDbl(len)) {
161 // use input field only for gluelengths
162 combo->setCurrentItem(defaultUnit);
163 input->setText(toqstr(len));
165 lengthToWidgets(input, combo, Length(len), defaultUnit);
170 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
171 docstring const & len, Length::UNIT defaultUnit)
173 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
177 double widgetToDouble(QLineEdit const * input)
179 QString const text = input->text();
183 return locstringToDouble(text.trimmed());
187 string widgetToDoubleStr(QLineEdit const * input)
189 return convert<string>(widgetToDouble(input));
193 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
196 loc.setNumberOptions(QLocale::OmitGroupSeparator);
197 input->setText(loc.toString(value, f, prec));
201 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
203 doubleToWidget(input, convert<double>(value), f, prec);
207 QString formatLocFPNumber(double d)
209 QString result = toqstr(formatFPNumber(d));
211 result.replace('.', loc.decimalPoint());
216 bool ColorSorter(ColorCode lhs, ColorCode rhs)
218 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
222 void setValid(QWidget * widget, bool valid)
225 widget->setPalette(QPalette());
227 QPalette pal = widget->palette();
228 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
229 widget->setPalette(pal);
234 void focusAndHighlight(QAbstractItemView * w)
237 w->setCurrentIndex(w->currentIndex());
238 w->scrollTo(w->currentIndex());
242 /// wrapper to hide the change of method name to setSectionResizeMode
243 void setSectionResizeMode(QHeaderView * view,
244 int logicalIndex, QHeaderView::ResizeMode mode) {
245 #if (QT_VERSION >= 0x050000)
246 view->setSectionResizeMode(logicalIndex, mode);
248 view->setResizeMode(logicalIndex, mode);
252 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
253 #if (QT_VERSION >= 0x050000)
254 view->setSectionResizeMode(mode);
256 view->setResizeMode(mode);
259 } // namespace frontend
261 QString const qt_(char const * str, const char *)
263 return toqstr(_(str));
267 QString const qt_(string const & str)
269 return toqstr(_(str));
273 QString const qt_(QString const & qstr)
275 return toqstr(_(fromqstr(qstr)));
279 void rescanTexStyles(string const & arg)
281 // Run rescan in user lyx directory
282 PathChanger p(package().user_support());
283 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
285 string const command = os::python() + ' ' +
286 quoteName(prog.toFilesystemEncoding()) + ' ' +
288 int const status = one.startscript(Systemcall::Wait, command);
292 frontend::Alert::error(_("Could not update TeX information"),
293 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
297 QStringList texFileList(QString const & filename)
300 FileName const file = libFileSearch(QString(), filename);
305 vector<docstring> doclist =
306 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
308 // Normalise paths like /foo//bar ==> /foo/bar
310 for (size_t i = 0; i != doclist.size(); ++i) {
311 QString file = toqstr(doclist[i]);
312 file.replace("\r", "");
313 while (file.contains("//"))
314 file.replace("//", "/");
320 return QList<QString>::fromSet(set);
323 QString const externalLineEnding(docstring const & str)
326 // The MAC clipboard uses \r for lineendings, and we use \n
327 return toqstr(subst(str, '\n', '\r'));
328 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
329 // Windows clipboard uses \r\n for lineendings, and we use \n
330 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
337 docstring const internalLineEnding(QString const & str)
339 docstring const s = subst(qstring_to_ucs4(str),
340 from_ascii("\r\n"), from_ascii("\n"));
341 return subst(s, '\r', '\n');
345 QString internalPath(const QString & str)
347 return toqstr(os::internal_path(fromqstr(str)));
351 QString onlyFileName(const QString & str)
353 return toqstr(support::onlyFileName(fromqstr(str)));
357 QString onlyPath(const QString & str)
359 return toqstr(support::onlyPath(fromqstr(str)));
363 QString changeExtension(QString const & oldname, QString const & ext)
365 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
368 /// Remove the extension from \p name
369 QString removeExtension(QString const & name)
371 return toqstr(support::removeExtension(fromqstr(name)));
374 /** Add the extension \p ext to \p name.
375 Use this instead of changeExtension if you know that \p name is without
376 extension, because changeExtension would wrongly interpret \p name if it
379 QString addExtension(QString const & name, QString const & ext)
381 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
384 /// Return the extension of the file (not including the .)
385 QString getExtension(QString const & name)
387 return toqstr(support::getExtension(fromqstr(name)));
391 /** Convert relative path into absolute path based on a basepath.
392 If relpath is absolute, just use that.
393 If basepath doesn't exist use CWD.
395 QString makeAbsPath(QString const & relpath, QString const & base)
397 return toqstr(support::makeAbsPath(fromqstr(relpath),
398 fromqstr(base)).absFileName());
402 /////////////////////////////////////////////////////////////////////////
406 /////////////////////////////////////////////////////////////////////////
408 /** Given a string such as
409 * "<glob> <glob> ... *.{abc,def} <glob>",
410 * convert the csh-style brace expresions:
411 * "<glob> <glob> ... *.abc *.def <glob>".
412 * Requires no system support, so should work equally on Unix, Mac, Win32.
414 static string const convert_brace_glob(string const & glob)
416 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
417 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
418 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
419 // Matches "abc" and "abc,", storing "abc" as group 1,
420 // while ignoring surrounding spaces.
421 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
425 string::const_iterator it = glob.begin();
426 string::const_iterator const end = glob.end();
428 match_results<string::const_iterator> what;
429 if (!regex_search(it, end, what, glob_re)) {
430 // Ensure that no information is lost.
431 pattern += string(it, end);
435 // Everything from the start of the input to
436 // the start of the match.
437 pattern += string(what[-1].first, what[-1].second);
439 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
440 string const head = string(what[1].first, what[1].second);
441 string const tail = string(what[2].first, what[2].second);
443 // Split the ','-separated chunks of tail so that
444 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
445 string const fmt = " " + head + "$1";
446 pattern += regex_replace(tail, block_re, fmt);
448 // Increment the iterator to the end of the match.
449 it += distance(it, what[0].second);
458 /* \param description text describing the filters.
459 * \param one or more wildcard patterns, separated by
462 Filter(docstring const & description, std::string const & globs);
464 docstring const & description() const { return desc_; }
466 QString toString() const;
469 std::vector<std::string> globs_;
473 Filter::Filter(docstring const & description, string const & globs)
476 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
477 // "<glob> <glob> ... *.abc *.def <glob>"
478 string const expanded_globs = convert_brace_glob(globs);
480 // Split into individual globs.
481 globs_ = getVectorFromString(expanded_globs, " ");
485 QString Filter::toString() const
489 bool const has_description = !desc_.empty();
491 if (has_description) {
496 s += toqstr(getStringFromVector(globs_, " "));
504 /** \c FileFilterList parses a Qt-style list of available file filters
505 * to generate the corresponding vector.
506 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
507 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
508 * will result in a vector of size 1 in which the description field is empty.
510 struct FileFilterList
512 // FIXME UNICODE: globs_ should be unicode...
513 /** \param qt_style_filter a list of available file filters.
514 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
515 * The "All files (*)" filter is always added to the list.
517 explicit FileFilterList(docstring const & qt_style_filter =
520 typedef std::vector<Filter>::size_type size_type;
522 bool empty() const { return filters_.empty(); }
523 size_type size() const { return filters_.size(); }
524 Filter & operator[](size_type i) { return filters_[i]; }
525 Filter const & operator[](size_type i) const { return filters_[i]; }
527 void parse_filter(std::string const & filter);
528 std::vector<Filter> filters_;
532 FileFilterList::FileFilterList(docstring const & qt_style_filter)
535 string const filter = to_utf8(qt_style_filter)
536 + (qt_style_filter.empty() ? string() : ";;")
537 + to_utf8(_("All Files "))
544 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
545 // into individual filters.
546 static lyx::regex const separator_re(";;");
548 string::const_iterator it = filter.begin();
549 string::const_iterator const end = filter.end();
551 match_results<string::const_iterator> what;
553 if (!lyx::regex_search(it, end, what, separator_re)) {
554 parse_filter(string(it, end));
558 // Everything from the start of the input to
559 // the start of the match.
560 parse_filter(string(it, what[0].first));
562 // Increment the iterator to the end of the match.
563 it += distance(it, what[0].second);
568 void FileFilterList::parse_filter(string const & filter)
570 // Matches "TeX documents (plain) (*.tex)",
571 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
572 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
574 match_results<string::const_iterator> what;
575 if (!lyx::regex_search(filter, what, filter_re)) {
576 // Just a glob, no description.
577 filters_.push_back(Filter(docstring(), trim(filter)));
580 docstring const desc = from_utf8(string(what[1].first, what[1].second));
581 string const globs = string(what[2].first, what[2].second);
582 filters_.push_back(Filter(trim(desc), trim(globs)));
587 /** \returns the equivalent of the string passed in
588 * although any brace expressions are expanded.
589 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
591 QStringList fileFilters(QString const & desc)
593 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
594 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
595 FileFilterList filters(qstring_to_ucs4(desc));
596 //LYXERR0("DESC: " << desc);
598 for (size_t i = 0; i != filters.filters_.size(); ++i) {
599 QString f = filters.filters_[i].toString();
600 //LYXERR0("FILTER: " << f);
607 QString formatToolTip(QString text, int em)
609 // 1. QTooltip activates word wrapping only if mightBeRichText()
610 // is true. So we convert the text to rich text.
612 // 2. The default width is way too small. Setting the width is tricky; first
613 // one has to compute the ideal width, and then force it with special
616 // do nothing if empty or already formatted
617 if (text.isEmpty() || text.startsWith(QString("<html>")))
619 // Convert to rich text if it is not already
620 if (!Qt::mightBeRichText(text))
621 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
622 // Compute desired width in pixels
623 QFont const font = QToolTip::font();
624 int const px_width = em * QFontMetrics(font).width("M");
625 // Determine the ideal width of the tooltip
626 QTextDocument td("");
628 td.setDefaultFont(QToolTip::font());
629 td.setTextWidth(px_width);
630 double best_width = td.idealWidth();
631 // Set the line wrapping with appropriate width
632 return QString("<html><body><table><tr>"
633 "<td align=justify width=%1>%2</td>"
634 "</tr></table></body></html>")
635 .arg(QString::number(int(best_width) + 1), text);
639 QString qtHtmlToPlainText(QString const & html)
641 if (!Qt::mightBeRichText(html))
645 return td.toPlainText();