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 "IndicesList.h"
27 #include "TextClass.h"
29 #include "support/convert.h"
30 #include "support/debug.h"
31 #include "support/gettext.h"
32 #include "support/lstrings.h"
33 #include "support/lyxalgo.h"
34 #include "support/os.h"
35 #include "support/Package.h"
36 #include "support/PathChanger.h"
37 #include "support/Systemcall.h"
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);
234 /// wrapper to hide the change of method name to setSectionResizeMode
235 void setSectionResizeMode(QHeaderView * view,
236 int logicalIndex, QHeaderView::ResizeMode mode) {
237 #if (QT_VERSION >= 0x050000)
238 view->setSectionResizeMode(logicalIndex, mode);
240 view->setResizeMode(logicalIndex, mode);
244 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
245 #if (QT_VERSION >= 0x050000)
246 view->setSectionResizeMode(mode);
248 view->setResizeMode(mode);
251 } // namespace frontend
253 QString const qt_(char const * str, const char *)
255 return toqstr(_(str));
259 QString const qt_(string const & str)
261 return toqstr(_(str));
265 QString const qt_(QString const & qstr)
267 return toqstr(_(fromqstr(qstr)));
271 void rescanTexStyles(string const & arg)
273 // Run rescan in user lyx directory
274 PathChanger p(package().user_support());
275 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
277 string const command = os::python() + ' ' +
278 quoteName(prog.toFilesystemEncoding()) + ' ' +
280 int const status = one.startscript(Systemcall::Wait, command);
284 frontend::Alert::error(_("Could not update TeX information"),
285 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
289 QStringList texFileList(QString const & filename)
292 FileName const file = libFileSearch(QString(), filename);
297 vector<docstring> doclist =
298 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
300 // Normalise paths like /foo//bar ==> /foo/bar
302 for (size_t i = 0; i != doclist.size(); ++i) {
303 QString file = toqstr(doclist[i]);
304 file.replace("\r", "");
305 while (file.contains("//"))
306 file.replace("//", "/");
312 return QList<QString>::fromSet(set);
315 QString const externalLineEnding(docstring const & str)
318 // The MAC clipboard uses \r for lineendings, and we use \n
319 return toqstr(subst(str, '\n', '\r'));
320 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
321 // Windows clipboard uses \r\n for lineendings, and we use \n
322 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
329 docstring const internalLineEnding(QString const & str)
331 docstring const s = subst(qstring_to_ucs4(str),
332 from_ascii("\r\n"), from_ascii("\n"));
333 return subst(s, '\r', '\n');
337 QString internalPath(const QString & str)
339 return toqstr(os::internal_path(fromqstr(str)));
343 QString onlyFileName(const QString & str)
345 return toqstr(support::onlyFileName(fromqstr(str)));
349 QString onlyPath(const QString & str)
351 return toqstr(support::onlyPath(fromqstr(str)));
355 QString changeExtension(QString const & oldname, QString const & ext)
357 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
360 /// Remove the extension from \p name
361 QString removeExtension(QString const & name)
363 return toqstr(support::removeExtension(fromqstr(name)));
366 /** Add the extension \p ext to \p name.
367 Use this instead of changeExtension if you know that \p name is without
368 extension, because changeExtension would wrongly interpret \p name if it
371 QString addExtension(QString const & name, QString const & ext)
373 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
376 /// Return the extension of the file (not including the .)
377 QString getExtension(QString const & name)
379 return toqstr(support::getExtension(fromqstr(name)));
383 /** Convert relative path into absolute path based on a basepath.
384 If relpath is absolute, just use that.
385 If basepath doesn't exist use CWD.
387 QString makeAbsPath(QString const & relpath, QString const & base)
389 return toqstr(support::makeAbsPath(fromqstr(relpath),
390 fromqstr(base)).absFileName());
394 /////////////////////////////////////////////////////////////////////////
398 /////////////////////////////////////////////////////////////////////////
400 /** Given a string such as
401 * "<glob> <glob> ... *.{abc,def} <glob>",
402 * convert the csh-style brace expresions:
403 * "<glob> <glob> ... *.abc *.def <glob>".
404 * Requires no system support, so should work equally on Unix, Mac, Win32.
406 static string const convert_brace_glob(string const & glob)
408 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
409 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
410 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
411 // Matches "abc" and "abc,", storing "abc" as group 1,
412 // while ignoring surrounding spaces.
413 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
417 string::const_iterator it = glob.begin();
418 string::const_iterator const end = glob.end();
420 match_results<string::const_iterator> what;
421 if (!regex_search(it, end, what, glob_re)) {
422 // Ensure that no information is lost.
423 pattern += string(it, end);
427 // Everything from the start of the input to
428 // the start of the match.
429 pattern += string(what[-1].first, what[-1].second);
431 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
432 string const head = string(what[1].first, what[1].second);
433 string const tail = string(what[2].first, what[2].second);
435 // Split the ','-separated chunks of tail so that
436 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
437 string const fmt = " " + head + "$1";
438 pattern += regex_replace(tail, block_re, fmt);
440 // Increment the iterator to the end of the match.
441 it += distance(it, what[0].second);
450 /* \param description text describing the filters.
451 * \param one or more wildcard patterns, separated by
454 Filter(docstring const & description, std::string const & globs);
456 docstring const & description() const { return desc_; }
458 QString toString() const;
461 std::vector<std::string> globs_;
465 Filter::Filter(docstring const & description, string const & globs)
468 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
469 // "<glob> <glob> ... *.abc *.def <glob>"
470 string const expanded_globs = convert_brace_glob(globs);
472 // Split into individual globs.
473 globs_ = getVectorFromString(expanded_globs, " ");
477 QString Filter::toString() const
481 bool const has_description = !desc_.empty();
483 if (has_description) {
488 s += toqstr(getStringFromVector(globs_, " "));
496 /** \c FileFilterList parses a Qt-style list of available file filters
497 * to generate the corresponding vector.
498 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
499 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
500 * will result in a vector of size 1 in which the description field is empty.
502 struct FileFilterList
504 // FIXME UNICODE: globs_ should be unicode...
505 /** \param qt_style_filter a list of available file filters.
506 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
507 * The "All files (*)" filter is always added to the list.
509 explicit FileFilterList(docstring const & qt_style_filter =
512 typedef std::vector<Filter>::size_type size_type;
514 bool empty() const { return filters_.empty(); }
515 size_type size() const { return filters_.size(); }
516 Filter & operator[](size_type i) { return filters_[i]; }
517 Filter const & operator[](size_type i) const { return filters_[i]; }
519 void parse_filter(std::string const & filter);
520 std::vector<Filter> filters_;
524 FileFilterList::FileFilterList(docstring const & qt_style_filter)
527 string const filter = to_utf8(qt_style_filter)
528 + (qt_style_filter.empty() ? string() : ";;")
529 + to_utf8(_("All Files "))
536 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
537 // into individual filters.
538 static lyx::regex const separator_re(";;");
540 string::const_iterator it = filter.begin();
541 string::const_iterator const end = filter.end();
543 match_results<string::const_iterator> what;
545 if (!lyx::regex_search(it, end, what, separator_re)) {
546 parse_filter(string(it, end));
550 // Everything from the start of the input to
551 // the start of the match.
552 parse_filter(string(it, what[0].first));
554 // Increment the iterator to the end of the match.
555 it += distance(it, what[0].second);
560 void FileFilterList::parse_filter(string const & filter)
562 // Matches "TeX documents (plain) (*.tex)",
563 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
564 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
566 match_results<string::const_iterator> what;
567 if (!lyx::regex_search(filter, what, filter_re)) {
568 // Just a glob, no description.
569 filters_.push_back(Filter(docstring(), trim(filter)));
572 docstring const desc = from_utf8(string(what[1].first, what[1].second));
573 string const globs = string(what[2].first, what[2].second);
574 filters_.push_back(Filter(trim(desc), trim(globs)));
579 /** \returns the equivalent of the string passed in
580 * although any brace expressions are expanded.
581 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
583 QStringList fileFilters(QString const & desc)
585 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
586 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
587 FileFilterList filters(qstring_to_ucs4(desc));
588 //LYXERR0("DESC: " << desc);
590 for (size_t i = 0; i != filters.filters_.size(); ++i) {
591 QString f = filters.filters_[i].toString();
592 //LYXERR0("FILTER: " << f);
599 QString guiName(string const & type, BufferParams const & bp)
601 if (type == "tableofcontents")
602 return qt_("Table of Contents");
604 return qt_("Child Documents");
605 if (type == "graphics")
606 return qt_("Graphics");
607 if (type == "equation")
608 return qt_("Equations");
609 if (type == "external")
610 return qt_("External material");
611 if (type == "footnote")
612 return qt_("Footnotes");
613 if (type == "listing")
614 return qt_("Listings");
616 return qt_("Index Entries");
617 if (type == "marginalnote")
618 return qt_("Marginal notes");
619 if (type == "math-macro")
620 return qt_("Math macros");
621 if (type == "nomencl")
622 return qt_("Nomenclature Entries");
625 if (type == "citation")
626 return qt_("Citations");
628 return qt_("Labels and References");
629 if (type == "branch")
630 return qt_("Branches");
631 if (type == "change")
632 return qt_("Changes");
633 if (type == "senseless")
634 return qt_("Senseless");
635 if (prefixIs(type, "index:")) {
636 string const itype = split(type, ':');
637 IndicesList const & indiceslist = bp.indiceslist();
638 Index const * index = indiceslist.findShortcut(from_utf8(itype));
639 docstring indextype = _("unknown type!");
641 indextype = index->index();
642 return toqstr(bformat(_("Index Entries (%1$s)"), indextype));
645 FloatList const & floats = bp.documentClass().floats();
646 if (floats.typeExist(type))
647 return qt_(floats.getType(type).listName());
653 QString formatToolTip(QString text, int em)
655 // 1. QTooltip activates word wrapping only if mightBeRichText()
656 // is true. So we convert the text to rich text.
658 // 2. The default width is way too small. Setting the width is tricky; first
659 // one has to compute the ideal width, and then force it with special
662 // do nothing if empty or already formatted
663 if (text.isEmpty() || text.startsWith(QString("<html>")))
665 // Convert to rich text if it is not already
666 if (!Qt::mightBeRichText(text))
667 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
668 // Compute desired width in pixels
669 QFont const font = QToolTip::font();
670 int const px_width = em * QFontMetrics(font).width("M");
671 // Determine the ideal width of the tooltip
672 QTextDocument td("");
674 td.setDefaultFont(QToolTip::font());
675 td.setTextWidth(px_width);
676 double best_width = td.idealWidth();
677 // Set the line wrapping with appropriate width
678 return QString("<html><body><table><tr>"
679 "<td align=justify width=%1>%2</td>"
680 "</tr></table></body></html>")
681 .arg(QString::number(int(best_width) + 1), text);