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"
54 #include <boost/tokenizer.hpp>
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 void setValid(QWidget * widget, bool valid)
219 widget->setPalette(QPalette());
221 QPalette pal = widget->palette();
222 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
223 widget->setPalette(pal);
227 /// wrapper to hide the change of method name to setSectionResizeMode
228 void setSectionResizeMode(QHeaderView * view,
229 int logicalIndex, QHeaderView::ResizeMode mode) {
230 #if (QT_VERSION >= 0x050000)
231 view->setSectionResizeMode(logicalIndex, mode);
233 view->setResizeMode(logicalIndex, mode);
237 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
238 #if (QT_VERSION >= 0x050000)
239 view->setSectionResizeMode(mode);
241 view->setResizeMode(mode);
244 } // namespace frontend
246 QString const qt_(char const * str, const char *)
248 return toqstr(_(str));
252 QString const qt_(string const & str)
254 return toqstr(_(str));
258 QString const qt_(QString const & qstr)
260 return toqstr(_(fromqstr(qstr)));
264 void rescanTexStyles(string const & arg)
266 // Run rescan in user lyx directory
267 PathChanger p(package().user_support());
268 FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
270 string const command = os::python() + ' ' +
271 quoteName(prog.toFilesystemEncoding()) + ' ' +
273 int const status = one.startscript(Systemcall::Wait, command);
277 frontend::Alert::error(_("Could not update TeX information"),
278 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
282 QStringList texFileList(QString const & filename)
285 FileName const file = libFileSearch(QString(), filename);
290 vector<docstring> doclist =
291 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
293 // Normalise paths like /foo//bar ==> /foo/bar
295 for (size_t i = 0; i != doclist.size(); ++i) {
296 QString file = toqstr(doclist[i]);
297 file.replace("\r", "");
298 while (file.contains("//"))
299 file.replace("//", "/");
305 return QList<QString>::fromSet(set);
308 QString const externalLineEnding(docstring const & str)
311 // The MAC clipboard uses \r for lineendings, and we use \n
312 return toqstr(subst(str, '\n', '\r'));
313 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
314 // Windows clipboard uses \r\n for lineendings, and we use \n
315 return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
322 docstring const internalLineEnding(QString const & str)
324 docstring const s = subst(qstring_to_ucs4(str),
325 from_ascii("\r\n"), from_ascii("\n"));
326 return subst(s, '\r', '\n');
330 QString internalPath(const QString & str)
332 return toqstr(os::internal_path(fromqstr(str)));
336 QString onlyFileName(const QString & str)
338 return toqstr(support::onlyFileName(fromqstr(str)));
342 QString onlyPath(const QString & str)
344 return toqstr(support::onlyPath(fromqstr(str)));
348 QString changeExtension(QString const & oldname, QString const & ext)
350 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
353 /// Remove the extension from \p name
354 QString removeExtension(QString const & name)
356 return toqstr(support::removeExtension(fromqstr(name)));
359 /** Add the extension \p ext to \p name.
360 Use this instead of changeExtension if you know that \p name is without
361 extension, because changeExtension would wrongly interpret \p name if it
364 QString addExtension(QString const & name, QString const & ext)
366 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
369 /// Return the extension of the file (not including the .)
370 QString getExtension(QString const & name)
372 return toqstr(support::getExtension(fromqstr(name)));
376 /** Convert relative path into absolute path based on a basepath.
377 If relpath is absolute, just use that.
378 If basepath doesn't exist use CWD.
380 QString makeAbsPath(QString const & relpath, QString const & base)
382 return toqstr(support::makeAbsPath(fromqstr(relpath),
383 fromqstr(base)).absFileName());
387 /////////////////////////////////////////////////////////////////////////
391 /////////////////////////////////////////////////////////////////////////
393 /** Given a string such as
394 * "<glob> <glob> ... *.{abc,def} <glob>",
395 * convert the csh-style brace expresions:
396 * "<glob> <glob> ... *.abc *.def <glob>".
397 * Requires no system support, so should work equally on Unix, Mac, Win32.
399 static string const convert_brace_glob(string const & glob)
401 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
402 // "abc,def,ghi" as group 2, while allowing spaces in group 2.
403 static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
404 // Matches "abc" and "abc,", storing "abc" as group 1,
405 // while ignoring surrounding spaces.
406 static lyx::regex const block_re(" *([^ ,}]+) *,? *");
410 string::const_iterator it = glob.begin();
411 string::const_iterator const end = glob.end();
413 match_results<string::const_iterator> what;
414 if (!regex_search(it, end, what, glob_re)) {
415 // Ensure that no information is lost.
416 pattern += string(it, end);
420 // Everything from the start of the input to
421 // the start of the match.
422 pattern += string(what[-1].first, what[-1].second);
424 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
425 string const head = string(what[1].first, what[1].second);
426 string const tail = string(what[2].first, what[2].second);
428 // Split the ','-separated chunks of tail so that
429 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
430 string const fmt = " " + head + "$1";
431 pattern += regex_replace(tail, block_re, fmt);
433 // Increment the iterator to the end of the match.
434 it += distance(it, what[0].second);
443 /* \param description text describing the filters.
444 * \param one or more wildcard patterns, separated by
447 Filter(docstring const & description, std::string const & globs);
449 docstring const & description() const { return desc_; }
451 QString toString() const;
454 std::vector<std::string> globs_;
458 Filter::Filter(docstring const & description, string const & globs)
461 typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
462 boost::char_separator<char> const separator(" ");
464 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
465 // "<glob> <glob> ... *.abc *.def <glob>"
466 string const expanded_globs = convert_brace_glob(globs);
468 // Split into individual globs.
469 vector<string> matches;
470 Tokenizer const tokens(expanded_globs, separator);
471 globs_ = vector<string>(tokens.begin(), tokens.end());
475 QString Filter::toString() const
479 bool const has_description = !desc_.empty();
481 if (has_description) {
486 for (size_t i = 0; i != globs_.size(); ++i) {
489 s += toqstr(globs_[i]);
498 /** \c FileFilterList parses a Qt-style list of available file filters
499 * to generate the corresponding vector.
500 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
501 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
502 * will result in a vector of size 1 in which the description field is empty.
504 struct FileFilterList
506 // FIXME UNICODE: globs_ should be unicode...
507 /** \param qt_style_filter a list of available file filters.
508 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
509 * The "All files (*)" filter is always added to the list.
511 explicit FileFilterList(docstring const & qt_style_filter =
514 typedef std::vector<Filter>::size_type size_type;
516 bool empty() const { return filters_.empty(); }
517 size_type size() const { return filters_.size(); }
518 Filter & operator[](size_type i) { return filters_[i]; }
519 Filter const & operator[](size_type i) const { return filters_[i]; }
521 void parse_filter(std::string const & filter);
522 std::vector<Filter> filters_;
526 FileFilterList::FileFilterList(docstring const & qt_style_filter)
529 string const filter = to_utf8(qt_style_filter)
530 + (qt_style_filter.empty() ? string() : ";;")
531 + to_utf8(_("All Files "))
538 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
539 // into individual filters.
540 static lyx::regex const separator_re(";;");
542 string::const_iterator it = filter.begin();
543 string::const_iterator const end = filter.end();
545 match_results<string::const_iterator> what;
547 if (!lyx::regex_search(it, end, what, separator_re)) {
548 parse_filter(string(it, end));
552 // Everything from the start of the input to
553 // the start of the match.
554 parse_filter(string(what[-1].first, what[-1].second));
556 // Increment the iterator to the end of the match.
557 it += distance(it, what[0].second);
562 void FileFilterList::parse_filter(string const & filter)
564 // Matches "TeX documents (plain) (*.tex)",
565 // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
566 static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
568 match_results<string::const_iterator> what;
569 if (!lyx::regex_search(filter, what, filter_re)) {
570 // Just a glob, no description.
571 filters_.push_back(Filter(docstring(), trim(filter)));
574 docstring const desc = from_utf8(string(what[1].first, what[1].second));
575 string const globs = string(what[2].first, what[2].second);
576 filters_.push_back(Filter(trim(desc), trim(globs)));
581 /** \returns the equivalent of the string passed in
582 * although any brace expressions are expanded.
583 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
585 QStringList fileFilters(QString const & desc)
587 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
588 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
589 FileFilterList filters(qstring_to_ucs4(desc));
590 //LYXERR0("DESC: " << desc);
592 for (size_t i = 0; i != filters.filters_.size(); ++i) {
593 QString f = filters.filters_[i].toString();
594 //LYXERR0("FILTER: " << f);
601 QString guiName(string const & type, BufferParams const & bp)
603 if (type == "tableofcontents")
604 return qt_("Table of Contents");
606 return qt_("Child Documents");
607 if (type == "graphics")
608 return qt_("Graphics");
609 if (type == "equation")
610 return qt_("Equations");
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 == "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 (prefixIs(type, "index:")) {
632 string const itype = split(type, ':');
633 IndicesList const & indiceslist = bp.indiceslist();
634 Index const * index = indiceslist.findShortcut(from_utf8(itype));
635 docstring indextype = _("unknown type!");
637 indextype = index->index();
638 return toqstr(bformat(_("Index Entries (%1$s)"), indextype));
641 FloatList const & floats = bp.documentClass().floats();
642 if (floats.typeExist(type))
643 return qt_(floats.getType(type).listName());