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"
25 #include "support/debug.h"
26 #include "support/filetools.h"
27 #include "support/foreach.h"
28 #include "support/gettext.h"
29 #include "support/lstrings.h"
30 #include "support/lyxalgo.h"
31 #include "support/os.h"
32 #include "support/Package.h"
33 #include "support/Path.h"
34 #include "support/Systemcall.h"
48 #include <boost/regex.hpp>
49 #include <boost/tokenizer.hpp>
53 using namespace lyx::support;
57 FileName libFileSearch(QString const & dir, QString const & name,
60 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext));
65 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
67 QString const length = input->text();
71 // Don't return unit-from-choice if the input(field) contains a unit
72 if (isValidGlueLength(fromqstr(length)))
73 return fromqstr(length);
75 Length::UNIT const unit = combo->currentLengthItem();
77 return Length(length.toDouble(), unit).asString();
81 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
83 QString const length = input->text();
87 // don't return unit-from-choice if the input(field) contains a unit
88 if (isValidGlueLength(fromqstr(length)))
89 return Length(fromqstr(length));
91 Length::UNIT const unit = unitFromString(fromqstr(combo->currentText()));
93 return Length(length.toDouble(), unit);
97 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
98 Length const & len, Length::UNIT /*defaultUnit*/)
100 combo->setCurrentItem(len.unit());
101 input->setText(QString::number(Length(len).value()));
105 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
106 string const & len, Length::UNIT defaultUnit)
109 // no length (UNIT_NONE)
110 combo->setCurrentItem(defaultUnit);
112 } else if (!isValidLength(len) && !isStrDbl(len)) {
113 // use input field only for gluelengths
114 combo->setCurrentItem(defaultUnit);
115 input->setText(toqstr(len));
117 lengthToWidgets(input, combo, Length(len), defaultUnit);
122 void lengthAutoToWidgets(QLineEdit * input, LengthCombo * combo,
123 Length const & len, Length::UNIT defaultUnit)
125 if (len.value() == 0)
126 lengthToWidgets(input, combo, "auto", defaultUnit);
128 lengthToWidgets(input, combo, len, defaultUnit);
132 void setValid(QWidget * widget, bool valid)
135 widget->setPalette(QPalette());
137 QPalette pal = widget->palette();
138 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
139 widget->setPalette(pal);
143 } // namespace frontend
145 QString const qt_(char const * str, const char *)
147 return toqstr(_(str));
151 QString const qt_(string const & str)
153 return toqstr(_(str));
161 #if !defined(USE_WCHAR_T) && defined(__GNUC__)
162 bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
164 return lhs.first < rhs.first;
167 Sorter() : loc_ok(true)
176 bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
178 // FIXME: would that be "QString::localeAwareCompare()"?
180 return loc_(fromqstr(lhs.first), fromqstr(rhs.first));
182 return lhs.first < rhs.first;
194 QList<LanguagePair> languageData(bool character_dlg)
196 size_t const offset = character_dlg ? 2 : 0;
197 vector<LanguagePair> langs(languages.size() + offset);
200 langs[0].first = qt_("No change");
201 langs[0].second = "ignore";
202 langs[1].first = qt_("Reset");
203 langs[1].second = "reset";
206 Languages::const_iterator it = languages.begin();
207 for (size_t i = 0; i != languages.size(); ++i, ++it) {
208 langs[i + offset].first = qt_(it->second.display());
209 langs[i + offset].second = toqstr(it->second.lang());
212 // Don't sort "ignore" and "reset"
213 vector<LanguagePair>::iterator begin = langs.begin() + offset;
214 sort(begin, langs.end(), Sorter());
216 QList<LanguagePair> list;
217 foreach (LanguagePair const & l, langs)
223 void rescanTexStyles()
225 // Run rescan in user lyx directory
226 PathChanger p(package().user_support());
227 FileName const command = libFileSearch("scripts", "TeXFiles.py");
229 int const status = one.startscript(Systemcall::Wait,
231 quoteName(command.toFilesystemEncoding()));
235 frontend::Alert::error(_("Could not update TeX information"),
236 bformat(_("The script `%s' failed."), from_utf8(command.absFilename())));
240 QStringList texFileList(QString const & filename)
243 FileName const file = libFileSearch(QString(), filename);
248 vector<docstring> doclist =
249 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
251 // Normalise paths like /foo//bar ==> /foo/bar
253 for (size_t i = 0; i != doclist.size(); ++i) {
254 QString file = toqstr(doclist[i]);
255 file.replace("\r", "");
256 while (file.contains("//"))
257 file.replace("//", "/");
263 return QList<QString>::fromSet(set);
267 QString internalPath(const QString & str)
269 return toqstr(os::internal_path(fromqstr(str)));
273 QString onlyFilename(const QString & str)
275 return toqstr(support::onlyFilename(fromqstr(str)));
279 QString onlyPath(const QString & str)
281 return toqstr(support::onlyPath(fromqstr(str)));
285 QString changeExtension(QString const & oldname, QString const & ext)
287 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
290 /// Remove the extension from \p name
291 QString removeExtension(QString const & name)
293 return toqstr(support::removeExtension(fromqstr(name)));
296 /** Add the extension \p ext to \p name.
297 Use this instead of changeExtension if you know that \p name is without
298 extension, because changeExtension would wrongly interpret \p name if it
301 QString addExtension(QString const & name, QString const & ext)
303 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
306 /// Return the extension of the file (not including the .)
307 QString getExtension(QString const & name)
309 return toqstr(support::getExtension(fromqstr(name)));
313 /** Convert relative path into absolute path based on a basepath.
314 If relpath is absolute, just use that.
315 If basepath doesn't exist use CWD.
317 QString makeAbsPath(QString const & relpath, QString const & base)
319 return toqstr(support::makeAbsPath(fromqstr(relpath),
320 fromqstr(base)).absFilename());
324 /////////////////////////////////////////////////////////////////////////
328 /////////////////////////////////////////////////////////////////////////
330 /** Given a string such as
331 * "<glob> <glob> ... *.{abc,def} <glob>",
332 * convert the csh-style brace expresions:
333 * "<glob> <glob> ... *.abc *.def <glob>".
334 * Requires no system support, so should work equally on Unix, Mac, Win32.
336 static string const convert_brace_glob(string const & glob)
338 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
339 // "abc,def,ghi" as group 2.
340 static boost::regex const glob_re(" *([^ {]*)\\{([^ }]+)\\}");
341 // Matches "abc" and "abc,", storing "abc" as group 1.
342 static boost::regex const block_re("([^,}]+),?");
346 string::const_iterator it = glob.begin();
347 string::const_iterator const end = glob.end();
349 boost::match_results<string::const_iterator> what;
350 if (!boost::regex_search(it, end, what, glob_re)) {
351 // Ensure that no information is lost.
352 pattern += string(it, end);
356 // Everything from the start of the input to
357 // the start of the match.
358 pattern += string(what[-1].first, what[-1].second);
360 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
361 string const head = string(what[1].first, what[1].second);
362 string const tail = string(what[2].first, what[2].second);
364 // Split the ','-separated chunks of tail so that
365 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
366 string const fmt = " " + head + "$1";
367 pattern += boost::regex_merge(tail, block_re, fmt);
369 // Increment the iterator to the end of the match.
370 it += distance(it, what[0].second);
379 /* \param description text describing the filters.
380 * \param one or more wildcard patterns, separated by
383 Filter(docstring const & description, std::string const & globs);
385 docstring const & description() const { return desc_; }
387 QString toString() const;
390 std::vector<std::string> globs_;
394 Filter::Filter(docstring const & description, string const & globs)
397 typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
398 boost::char_separator<char> const separator(" ");
400 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
401 // "<glob> <glob> ... *.abc *.def <glob>"
402 string const expanded_globs = convert_brace_glob(globs);
404 // Split into individual globs.
405 vector<string> matches;
406 Tokenizer const tokens(expanded_globs, separator);
407 globs_ = vector<string>(tokens.begin(), tokens.end());
411 QString Filter::toString() const
415 bool const has_description = desc_.empty();
417 if (has_description) {
422 for (size_t i = 0; i != globs_.size(); ++i) {
425 s += toqstr(globs_[i]);
434 /** \c FileFilterList parses a Qt-style list of available file filters
435 * to generate the corresponding vector.
436 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
437 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
438 * will result in a vector of size 1 in which the description field is empty.
440 struct FileFilterList
442 // FIXME UNICODE: globs_ should be unicode...
443 /** \param qt_style_filter a list of available file filters.
444 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
445 * The "All files (*)" filter is always added to the list.
447 explicit FileFilterList(docstring const & qt_style_filter =
450 typedef std::vector<Filter>::size_type size_type;
452 bool empty() const { return filters_.empty(); }
453 size_type size() const { return filters_.size(); }
454 Filter & operator[](size_type i) { return filters_[i]; }
455 Filter const & operator[](size_type i) const { return filters_[i]; }
457 void parse_filter(std::string const & filter);
458 std::vector<Filter> filters_;
462 FileFilterList::FileFilterList(docstring const & qt_style_filter)
465 string const filter = to_utf8(qt_style_filter)
466 + (qt_style_filter.empty() ? string() : ";;")
467 + to_utf8(_("All Files "))
474 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
475 // into individual filters.
476 static boost::regex const separator_re(";;");
478 string::const_iterator it = filter.begin();
479 string::const_iterator const end = filter.end();
481 boost::match_results<string::const_iterator> what;
483 if (!boost::regex_search(it, end, what, separator_re)) {
484 parse_filter(string(it, end));
488 // Everything from the start of the input to
489 // the start of the match.
490 parse_filter(string(what[-1].first, what[-1].second));
492 // Increment the iterator to the end of the match.
493 it += distance(it, what[0].second);
498 void FileFilterList::parse_filter(string const & filter)
500 // Matches "TeX documents (*.tex)",
501 // storing "TeX documents " as group 1 and "*.tex" as group 2.
502 static boost::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
504 boost::match_results<string::const_iterator> what;
505 if (!boost::regex_search(filter, what, filter_re)) {
506 // Just a glob, no description.
507 filters_.push_back(Filter(docstring(), trim(filter)));
510 docstring const desc = from_utf8(string(what[1].first, what[1].second));
511 string const globs = string(what[2].first, what[2].second);
512 filters_.push_back(Filter(trim(desc), trim(globs)));
517 /** \returns the equivalent of the string passed in
518 * although any brace expressions are expanded.
519 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
521 QStringList fileFilters(QString const & desc)
523 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
524 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
525 FileFilterList filters(qstring_to_ucs4(desc));
526 LYXERR0("DESC: " << fromqstr(desc));
528 for (size_t i = 0; i != filters.filters_.size(); ++i) {
529 QString f = filters.filters_[i].toString();
530 LYXERR0("FILTER: " << fromqstr(f));