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/foreach.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/lyxalgo.h"
35 #include "support/os.h"
36 #include "support/Package.h"
37 #include "support/PathChanger.h"
38 #include "support/Systemcall.h"
53 #include "support/regex.h"
57 using namespace lyx::support;
61 FileName libFileSearch(QString const & dir, QString const & name,
62 QString const & ext, search_mode mode)
64 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext), mode);
68 FileName imageLibFileSearch(QString & dir, QString const & name,
69 QString const & ext, search_mode mode)
71 string tmp = fromqstr(dir);
72 FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext), mode);
79 double locstringToDouble(QString const & str)
83 double res = loc.toDouble(str, &ok);
86 QLocale c(QLocale::C);
87 res = c.toDouble(str);
97 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
99 QString const length = input->text();
100 if (length.isEmpty())
103 // Don't return unit-from-choice if the input(field) contains a unit
104 if (isValidGlueLength(fromqstr(length)))
105 return fromqstr(length);
107 Length::UNIT const unit = combo->currentLengthItem();
109 return Length(locstringToDouble(length.trimmed()), unit).asString();
113 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
115 QString const length = input->text();
116 if (length.isEmpty())
119 // don't return unit-from-choice if the input(field) contains a unit
120 if (isValidGlueLength(fromqstr(length)))
121 return Length(fromqstr(length));
123 Length::UNIT unit = Length::UNIT_NONE;
124 QString const item = combo->currentText();
125 for (int i = 0; i < num_units; i++) {
126 if (qt_(lyx::unit_name_gui[i]) == item) {
127 unit = unitFromString(unit_name[i]);
132 return Length(locstringToDouble(length.trimmed()), unit);
136 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
137 Length const & len, Length::UNIT /*defaultUnit*/)
140 // no length (UNIT_NONE)
141 combo->setCurrentItem(Length::defaultUnit());
144 combo->setCurrentItem(len.unit());
146 loc.setNumberOptions(QLocale::OmitGroupSeparator);
147 input->setText(formatLocFPNumber(Length(len).value()));
152 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
153 string const & len, Length::UNIT defaultUnit)
156 // no length (UNIT_NONE)
157 combo->setCurrentItem(defaultUnit);
159 } else if (!isValidLength(len) && !isStrDbl(len)) {
160 // use input field only for gluelengths
161 combo->setCurrentItem(defaultUnit);
162 input->setText(toqstr(len));
164 lengthToWidgets(input, combo, Length(len), defaultUnit);
169 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
170 docstring const & len, Length::UNIT defaultUnit)
172 lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
176 double widgetToDouble(QLineEdit const * input)
178 QString const text = input->text();
182 return locstringToDouble(text.trimmed());
186 string widgetToDoubleStr(QLineEdit const * input)
188 return convert<string>(widgetToDouble(input));
192 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
195 loc.setNumberOptions(QLocale::OmitGroupSeparator);
196 input->setText(loc.toString(value, f, prec));
200 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
202 doubleToWidget(input, convert<double>(value), f, prec);
206 QString formatLocFPNumber(double d)
208 QString result = toqstr(formatFPNumber(d));
210 result.replace('.', loc.decimalPoint());
215 bool ColorSorter(ColorCode lhs, ColorCode rhs)
217 return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
221 void setValid(QWidget * widget, bool valid)
224 widget->setPalette(QPalette());
226 QPalette pal = widget->palette();
227 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
228 widget->setPalette(pal);
232 /// wrapper to hide the change of method name to setSectionResizeMode
233 void setSectionResizeMode(QHeaderView * view,
234 int logicalIndex, QHeaderView::ResizeMode mode) {
235 #if (QT_VERSION >= 0x050000)
236 view->setSectionResizeMode(logicalIndex, mode);
238 view->setResizeMode(logicalIndex, mode);
242 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
243 #if (QT_VERSION >= 0x050000)
244 view->setSectionResizeMode(mode);
246 view->setResizeMode(mode);
249 } // namespace frontend
251 QString const qt_(char const * str, const char *)
253 return toqstr(_(str));
257 QString const qt_(string const & str)
259 return toqstr(_(str));
263 QString const qt_(QString const & qstr)
265 return toqstr(_(fromqstr(qstr)));
269 void rescanTexStyles(string const & arg)
271 // Run rescan in user lyx directory
272 PathChanger p(package().user_support());
273 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
275 string const command = os::python() + ' ' +
276 quoteName(prog.toFilesystemEncoding()) + ' ' +
278 int const status = one.startscript(Systemcall::Wait, command);
282 frontend::Alert::error(_("Could not update TeX information"),
283 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
287 QStringList texFileList(QString const & filename)
290 FileName const file = libFileSearch(QString(), filename);
295 vector<docstring> doclist =
296 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
298 // Normalise paths like /foo//bar ==> /foo/bar
300 for (size_t i = 0; i != doclist.size(); ++i) {
301 QString file = toqstr(doclist[i]);
302 file.replace("\r", "");
303 while (file.contains("//"))
304 file.replace("//", "/");
310 return QList<QString>::fromSet(set);
313 QString const externalLineEnding(docstring const & str)
316 // The MAC clipboard uses \r for lineendings, and we use \n
317 return toqstr(subst(str, '\n', '\r'));
318 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
319 // Windows clipboard uses \r\n for lineendings, and we use \n
320 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
327 docstring const internalLineEnding(QString const & str)
329 docstring const s = subst(qstring_to_ucs4(str),
330 from_ascii("\r\n"), from_ascii("\n"));
331 return subst(s, '\r', '\n');
335 QString internalPath(const QString & str)
337 return toqstr(os::internal_path(fromqstr(str)));
341 QString onlyFileName(const QString & str)
343 return toqstr(support::onlyFileName(fromqstr(str)));
347 QString onlyPath(const QString & str)
349 return toqstr(support::onlyPath(fromqstr(str)));
353 QString changeExtension(QString const & oldname, QString const & ext)
355 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
358 /// Remove the extension from \p name
359 QString removeExtension(QString const & name)
361 return toqstr(support::removeExtension(fromqstr(name)));
364 /** Add the extension \p ext to \p name.
365 Use this instead of changeExtension if you know that \p name is without
366 extension, because changeExtension would wrongly interpret \p name if it
369 QString addExtension(QString const & name, QString const & ext)
371 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
374 /// Return the extension of the file (not including the .)
375 QString getExtension(QString const & name)
377 return toqstr(support::getExtension(fromqstr(name)));
381 /** Convert relative path into absolute path based on a basepath.
382 If relpath is absolute, just use that.
383 If basepath doesn't exist use CWD.
385 QString makeAbsPath(QString const & relpath, QString const & base)
387 return toqstr(support::makeAbsPath(fromqstr(relpath),
388 fromqstr(base)).absFileName());
392 /////////////////////////////////////////////////////////////////////////
396 /////////////////////////////////////////////////////////////////////////
398 /** Given a string such as
399 * "<glob> <glob> ... *.{abc,def} <glob>",
400 * convert the csh-style brace expresions:
401 * "<glob> <glob> ... *.abc *.def <glob>".
402 * Requires no system support, so should work equally on Unix, Mac, Win32.
404 static string const convert_brace_glob(string const & glob)
406 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
407 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
408 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
409 // Matches "abc" and "abc,", storing "abc" as group 1,
410 // while ignoring surrounding spaces.
411 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
415 string::const_iterator it = glob.begin();
416 string::const_iterator const end = glob.end();
418 match_results<string::const_iterator> what;
419 if (!regex_search(it, end, what, glob_re)) {
420 // Ensure that no information is lost.
421 pattern += string(it, end);
425 // Everything from the start of the input to
426 // the start of the match.
427 pattern += string(what[-1].first, what[-1].second);
429 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
430 string const head = string(what[1].first, what[1].second);
431 string const tail = string(what[2].first, what[2].second);
433 // Split the ','-separated chunks of tail so that
434 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
435 string const fmt = " " + head + "$1";
436 pattern += regex_replace(tail, block_re, fmt);
438 // Increment the iterator to the end of the match.
439 it += distance(it, what[0].second);
448 /* \param description text describing the filters.
449 * \param one or more wildcard patterns, separated by
452 Filter(docstring const & description, std::string const & globs);
454 docstring const & description() const { return desc_; }
456 QString toString() const;
459 std::vector<std::string> globs_;
463 Filter::Filter(docstring const & description, string const & globs)
466 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
467 // "<glob> <glob> ... *.abc *.def <glob>"
468 string const expanded_globs = convert_brace_glob(globs);
470 // Split into individual globs.
471 globs_ = getVectorFromString(expanded_globs, " ");
475 QString Filter::toString() const
479 bool const has_description = !desc_.empty();
481 if (has_description) {
486 s += toqstr(getStringFromVector(globs_, " "));
494 /** \c FileFilterList parses a Qt-style list of available file filters
495 * to generate the corresponding vector.
496 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
497 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
498 * will result in a vector of size 1 in which the description field is empty.
500 struct FileFilterList
502 // FIXME UNICODE: globs_ should be unicode...
503 /** \param qt_style_filter a list of available file filters.
504 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
505 * The "All files (*)" filter is always added to the list.
507 explicit FileFilterList(docstring const & qt_style_filter =
510 typedef std::vector<Filter>::size_type size_type;
512 bool empty() const { return filters_.empty(); }
513 size_type size() const { return filters_.size(); }
514 Filter & operator[](size_type i) { return filters_[i]; }
515 Filter const & operator[](size_type i) const { return filters_[i]; }
517 void parse_filter(std::string const & filter);
518 std::vector<Filter> filters_;
522 FileFilterList::FileFilterList(docstring const & qt_style_filter)
525 string const filter = to_utf8(qt_style_filter)
526 + (qt_style_filter.empty() ? string() : ";;")
527 + to_utf8(_("All Files "))
534 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
535 // into individual filters.
536 static lyx::regex const separator_re(";;");
538 string::const_iterator it = filter.begin();
539 string::const_iterator const end = filter.end();
541 match_results<string::const_iterator> what;
543 if (!lyx::regex_search(it, end, what, separator_re)) {
544 parse_filter(string(it, end));
548 // Everything from the start of the input to
549 // the start of the match.
550 parse_filter(string(what[-1].first, what[-1].second));
552 // Increment the iterator to the end of the match.
553 it += distance(it, what[0].second);
558 void FileFilterList::parse_filter(string const & filter)
560 // Matches "TeX documents (plain) (*.tex)",
561 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
562 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
564 match_results<string::const_iterator> what;
565 if (!lyx::regex_search(filter, what, filter_re)) {
566 // Just a glob, no description.
567 filters_.push_back(Filter(docstring(), trim(filter)));
570 docstring const desc = from_utf8(string(what[1].first, what[1].second));
571 string const globs = string(what[2].first, what[2].second);
572 filters_.push_back(Filter(trim(desc), trim(globs)));
577 /** \returns the equivalent of the string passed in
578 * although any brace expressions are expanded.
579 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
581 QStringList fileFilters(QString const & desc)
583 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
584 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
585 FileFilterList filters(qstring_to_ucs4(desc));
586 //LYXERR0("DESC: " << desc);
588 for (size_t i = 0; i != filters.filters_.size(); ++i) {
589 QString f = filters.filters_[i].toString();
590 //LYXERR0("FILTER: " << f);
597 QString guiName(string const & type, BufferParams const & bp)
599 if (type == "tableofcontents")
600 return qt_("Table of Contents");
602 return qt_("Child Documents");
603 if (type == "graphics")
604 return qt_("Graphics");
605 if (type == "equation")
606 return qt_("Equations");
607 if (type == "external")
608 return qt_("External material");
609 if (type == "footnote")
610 return qt_("Footnotes");
611 if (type == "listing")
612 return qt_("Listings");
614 return qt_("Index Entries");
615 if (type == "marginalnote")
616 return qt_("Marginal notes");
617 if (type == "math-macro")
618 return qt_("Math macros");
619 if (type == "nomencl")
620 return qt_("Nomenclature Entries");
623 if (type == "citation")
624 return qt_("Citations");
626 return qt_("Labels and References");
627 if (type == "branch")
628 return qt_("Branches");
629 if (type == "change")
630 return qt_("Changes");
631 if (type == "senseless")
632 return qt_("Senseless");
633 if (prefixIs(type, "index:")) {
634 string const itype = split(type, ':');
635 IndicesList const & indiceslist = bp.indiceslist();
636 Index const * index = indiceslist.findShortcut(from_utf8(itype));
637 docstring indextype = _("unknown type!");
639 indextype = index->index();
640 return toqstr(bformat(_("Index Entries (%1$s)"), indextype));
643 FloatList const & floats = bp.documentClass().floats();
644 if (floats.typeExist(type))
645 return qt_(floats.getType(type).listName());