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);
233 /// wrapper to hide the change of method name to setSectionResizeMode
234 void setSectionResizeMode(QHeaderView * view,
235 int logicalIndex, QHeaderView::ResizeMode mode) {
236 #if (QT_VERSION >= 0x050000)
237 view->setSectionResizeMode(logicalIndex, mode);
239 view->setResizeMode(logicalIndex, mode);
243 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
244 #if (QT_VERSION >= 0x050000)
245 view->setSectionResizeMode(mode);
247 view->setResizeMode(mode);
250 } // namespace frontend
252 QString const qt_(char const * str, const char *)
254 return toqstr(_(str));
258 QString const qt_(string const & str)
260 return toqstr(_(str));
264 QString const qt_(QString const & qstr)
266 return toqstr(_(fromqstr(qstr)));
270 void rescanTexStyles(string const & arg)
272 // Run rescan in user lyx directory
273 PathChanger p(package().user_support());
274 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
276 string const command = os::python() + ' ' +
277 quoteName(prog.toFilesystemEncoding()) + ' ' +
279 int const status = one.startscript(Systemcall::Wait, command);
283 frontend::Alert::error(_("Could not update TeX information"),
284 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
288 QStringList texFileList(QString const & filename)
291 FileName const file = libFileSearch(QString(), filename);
296 vector<docstring> doclist =
297 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
299 // Normalise paths like /foo//bar ==> /foo/bar
301 for (size_t i = 0; i != doclist.size(); ++i) {
302 QString file = toqstr(doclist[i]);
303 file.replace("\r", "");
304 while (file.contains("//"))
305 file.replace("//", "/");
311 return QList<QString>::fromSet(set);
314 QString const externalLineEnding(docstring const & str)
317 // The MAC clipboard uses \r for lineendings, and we use \n
318 return toqstr(subst(str, '\n', '\r'));
319 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
320 // Windows clipboard uses \r\n for lineendings, and we use \n
321 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
328 docstring const internalLineEnding(QString const & str)
330 docstring const s = subst(qstring_to_ucs4(str),
331 from_ascii("\r\n"), from_ascii("\n"));
332 return subst(s, '\r', '\n');
336 QString internalPath(const QString & str)
338 return toqstr(os::internal_path(fromqstr(str)));
342 QString onlyFileName(const QString & str)
344 return toqstr(support::onlyFileName(fromqstr(str)));
348 QString onlyPath(const QString & str)
350 return toqstr(support::onlyPath(fromqstr(str)));
354 QString changeExtension(QString const & oldname, QString const & ext)
356 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
359 /// Remove the extension from \p name
360 QString removeExtension(QString const & name)
362 return toqstr(support::removeExtension(fromqstr(name)));
365 /** Add the extension \p ext to \p name.
366 Use this instead of changeExtension if you know that \p name is without
367 extension, because changeExtension would wrongly interpret \p name if it
370 QString addExtension(QString const & name, QString const & ext)
372 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
375 /// Return the extension of the file (not including the .)
376 QString getExtension(QString const & name)
378 return toqstr(support::getExtension(fromqstr(name)));
382 /** Convert relative path into absolute path based on a basepath.
383 If relpath is absolute, just use that.
384 If basepath doesn't exist use CWD.
386 QString makeAbsPath(QString const & relpath, QString const & base)
388 return toqstr(support::makeAbsPath(fromqstr(relpath),
389 fromqstr(base)).absFileName());
393 /////////////////////////////////////////////////////////////////////////
397 /////////////////////////////////////////////////////////////////////////
399 /** Given a string such as
400 * "<glob> <glob> ... *.{abc,def} <glob>",
401 * convert the csh-style brace expresions:
402 * "<glob> <glob> ... *.abc *.def <glob>".
403 * Requires no system support, so should work equally on Unix, Mac, Win32.
405 static string const convert_brace_glob(string const & glob)
407 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
408 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
409 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
410 // Matches "abc" and "abc,", storing "abc" as group 1,
411 // while ignoring surrounding spaces.
412 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
416 string::const_iterator it = glob.begin();
417 string::const_iterator const end = glob.end();
419 match_results<string::const_iterator> what;
420 if (!regex_search(it, end, what, glob_re)) {
421 // Ensure that no information is lost.
422 pattern += string(it, end);
426 // Everything from the start of the input to
427 // the start of the match.
428 pattern += string(what[-1].first, what[-1].second);
430 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
431 string const head = string(what[1].first, what[1].second);
432 string const tail = string(what[2].first, what[2].second);
434 // Split the ','-separated chunks of tail so that
435 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
436 string const fmt = " " + head + "$1";
437 pattern += regex_replace(tail, block_re, fmt);
439 // Increment the iterator to the end of the match.
440 it += distance(it, what[0].second);
449 /* \param description text describing the filters.
450 * \param one or more wildcard patterns, separated by
453 Filter(docstring const & description, std::string const & globs);
455 docstring const & description() const { return desc_; }
457 QString toString() const;
460 std::vector<std::string> globs_;
464 Filter::Filter(docstring const & description, string const & globs)
467 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
468 // "<glob> <glob> ... *.abc *.def <glob>"
469 string const expanded_globs = convert_brace_glob(globs);
471 // Split into individual globs.
472 globs_ = getVectorFromString(expanded_globs, " ");
476 QString Filter::toString() const
480 bool const has_description = !desc_.empty();
482 if (has_description) {
487 s += toqstr(getStringFromVector(globs_, " "));
495 /** \c FileFilterList parses a Qt-style list of available file filters
496 * to generate the corresponding vector.
497 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
498 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
499 * will result in a vector of size 1 in which the description field is empty.
501 struct FileFilterList
503 // FIXME UNICODE: globs_ should be unicode...
504 /** \param qt_style_filter a list of available file filters.
505 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
506 * The "All files (*)" filter is always added to the list.
508 explicit FileFilterList(docstring const & qt_style_filter =
511 typedef std::vector<Filter>::size_type size_type;
513 bool empty() const { return filters_.empty(); }
514 size_type size() const { return filters_.size(); }
515 Filter & operator[](size_type i) { return filters_[i]; }
516 Filter const & operator[](size_type i) const { return filters_[i]; }
518 void parse_filter(std::string const & filter);
519 std::vector<Filter> filters_;
523 FileFilterList::FileFilterList(docstring const & qt_style_filter)
526 string const filter = to_utf8(qt_style_filter)
527 + (qt_style_filter.empty() ? string() : ";;")
528 + to_utf8(_("All Files "))
535 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
536 // into individual filters.
537 static lyx::regex const separator_re(";;");
539 string::const_iterator it = filter.begin();
540 string::const_iterator const end = filter.end();
542 match_results<string::const_iterator> what;
544 if (!lyx::regex_search(it, end, what, separator_re)) {
545 parse_filter(string(it, end));
549 // Everything from the start of the input to
550 // the start of the match.
551 parse_filter(string(it, what[0].first));
553 // Increment the iterator to the end of the match.
554 it += distance(it, what[0].second);
559 void FileFilterList::parse_filter(string const & filter)
561 // Matches "TeX documents (plain) (*.tex)",
562 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
563 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
565 match_results<string::const_iterator> what;
566 if (!lyx::regex_search(filter, what, filter_re)) {
567 // Just a glob, no description.
568 filters_.push_back(Filter(docstring(), trim(filter)));
571 docstring const desc = from_utf8(string(what[1].first, what[1].second));
572 string const globs = string(what[2].first, what[2].second);
573 filters_.push_back(Filter(trim(desc), trim(globs)));
578 /** \returns the equivalent of the string passed in
579 * although any brace expressions are expanded.
580 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
582 QStringList fileFilters(QString const & desc)
584 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
585 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
586 FileFilterList filters(qstring_to_ucs4(desc));
587 //LYXERR0("DESC: " << desc);
589 for (size_t i = 0; i != filters.filters_.size(); ++i) {
590 QString f = filters.filters_[i].toString();
591 //LYXERR0("FILTER: " << f);
598 QString formatToolTip(QString text, int em)
600 // 1. QTooltip activates word wrapping only if mightBeRichText()
601 // is true. So we convert the text to rich text.
603 // 2. The default width is way too small. Setting the width is tricky; first
604 // one has to compute the ideal width, and then force it with special
607 // do nothing if empty or already formatted
608 if (text.isEmpty() || text.startsWith(QString("<html>")))
610 // Convert to rich text if it is not already
611 if (!Qt::mightBeRichText(text))
612 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
613 // Compute desired width in pixels
614 QFont const font = QToolTip::font();
615 int const px_width = em * QFontMetrics(font).width("M");
616 // Determine the ideal width of the tooltip
617 QTextDocument td("");
619 td.setDefaultFont(QToolTip::font());
620 td.setTextWidth(px_width);
621 double best_width = td.idealWidth();
622 // Set the line wrapping with appropriate width
623 return QString("<html><body><table><tr>"
624 "<td align=justify width=%1>%2</td>"
625 "</tr></table></body></html>")
626 .arg(QString::number(int(best_width) + 1), text);
630 QString qtHtmlToPlainText(QString const & html)
632 if (!Qt::mightBeRichText(html))
636 return td.toPlainText();