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 LyXErr & operator<<(LyXErr & err, QString const & str)
59 return err << fromqstr(str);
63 FileName libFileSearch(QString const & dir, QString const & name,
66 return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext));
72 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
74 QString const length = input->text();
78 // Don't return unit-from-choice if the input(field) contains a unit
79 if (isValidGlueLength(fromqstr(length)))
80 return fromqstr(length);
82 Length::UNIT const unit = combo->currentLengthItem();
84 return Length(length.toDouble(), unit).asString();
88 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
90 QString const length = input->text();
94 // don't return unit-from-choice if the input(field) contains a unit
95 if (isValidGlueLength(fromqstr(length)))
96 return Length(fromqstr(length));
98 Length::UNIT const unit = unitFromString(fromqstr(combo->currentText()));
100 return Length(length.toDouble(), unit);
104 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
105 Length const & len, Length::UNIT /*defaultUnit*/)
107 combo->setCurrentItem(len.unit());
108 input->setText(QString::number(Length(len).value()));
112 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
113 string const & len, Length::UNIT defaultUnit)
116 // no length (UNIT_NONE)
117 combo->setCurrentItem(defaultUnit);
119 } else if (!isValidLength(len) && !isStrDbl(len)) {
120 // use input field only for gluelengths
121 combo->setCurrentItem(defaultUnit);
122 input->setText(toqstr(len));
124 lengthToWidgets(input, combo, Length(len), defaultUnit);
129 void lengthAutoToWidgets(QLineEdit * input, LengthCombo * combo,
130 Length const & len, Length::UNIT defaultUnit)
132 if (len.value() == 0)
133 lengthToWidgets(input, combo, "auto", defaultUnit);
135 lengthToWidgets(input, combo, len, defaultUnit);
139 void setValid(QWidget * widget, bool valid)
142 widget->setPalette(QPalette());
144 QPalette pal = widget->palette();
145 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
146 widget->setPalette(pal);
150 } // namespace frontend
152 QString const qt_(char const * str, const char *)
154 return toqstr(_(str));
158 QString const qt_(string const & str)
160 return toqstr(_(str));
168 #if !defined(USE_WCHAR_T) && defined(__GNUC__)
169 bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
171 return lhs.first < rhs.first;
174 Sorter() : loc_ok(true)
183 bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
185 // FIXME: would that be "QString::localeAwareCompare()"?
187 return loc_(fromqstr(lhs.first), fromqstr(rhs.first));
189 return lhs.first < rhs.first;
201 QList<LanguagePair> languageData()
203 QList<LanguagePair> list;
204 Languages::const_iterator it = languages.begin();
205 for (; it != languages.end(); ++it) {
206 list << LanguagePair(
207 qt_(it->second.display()), toqstr(it->second.lang()));
213 void rescanTexStyles()
215 // Run rescan in user lyx directory
216 PathChanger p(package().user_support());
217 FileName const command = support::libFileSearch("scripts", "TeXFiles.py");
219 int const status = one.startscript(Systemcall::Wait,
221 quoteName(command.toFilesystemEncoding()));
225 frontend::Alert::error(_("Could not update TeX information"),
226 bformat(_("The script `%s' failed."), from_utf8(command.absFilename())));
230 QStringList texFileList(QString const & filename)
233 FileName const file = libFileSearch(QString(), filename);
238 vector<docstring> doclist =
239 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
241 // Normalise paths like /foo//bar ==> /foo/bar
243 for (size_t i = 0; i != doclist.size(); ++i) {
244 QString file = toqstr(doclist[i]);
245 file.replace("\r", "");
246 while (file.contains("//"))
247 file.replace("//", "/");
253 return QList<QString>::fromSet(set);
257 QString internalPath(const QString & str)
259 return toqstr(os::internal_path(fromqstr(str)));
263 QString onlyFilename(const QString & str)
265 return toqstr(support::onlyFilename(fromqstr(str)));
269 QString onlyPath(const QString & str)
271 return toqstr(support::onlyPath(fromqstr(str)));
275 QString changeExtension(QString const & oldname, QString const & ext)
277 return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
280 /// Remove the extension from \p name
281 QString removeExtension(QString const & name)
283 return toqstr(support::removeExtension(fromqstr(name)));
286 /** Add the extension \p ext to \p name.
287 Use this instead of changeExtension if you know that \p name is without
288 extension, because changeExtension would wrongly interpret \p name if it
291 QString addExtension(QString const & name, QString const & ext)
293 return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
296 /// Return the extension of the file (not including the .)
297 QString getExtension(QString const & name)
299 return toqstr(support::getExtension(fromqstr(name)));
303 /** Convert relative path into absolute path based on a basepath.
304 If relpath is absolute, just use that.
305 If basepath doesn't exist use CWD.
307 QString makeAbsPath(QString const & relpath, QString const & base)
309 return toqstr(support::makeAbsPath(fromqstr(relpath),
310 fromqstr(base)).absFilename());
314 /////////////////////////////////////////////////////////////////////////
318 /////////////////////////////////////////////////////////////////////////
320 /** Given a string such as
321 * "<glob> <glob> ... *.{abc,def} <glob>",
322 * convert the csh-style brace expresions:
323 * "<glob> <glob> ... *.abc *.def <glob>".
324 * Requires no system support, so should work equally on Unix, Mac, Win32.
326 static string const convert_brace_glob(string const & glob)
328 // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
329 // "abc,def,ghi" as group 2.
330 static boost::regex const glob_re(" *([^ {]*)\\{([^ }]+)\\}");
331 // Matches "abc" and "abc,", storing "abc" as group 1.
332 static boost::regex const block_re("([^,}]+),?");
336 string::const_iterator it = glob.begin();
337 string::const_iterator const end = glob.end();
339 boost::match_results<string::const_iterator> what;
340 if (!boost::regex_search(it, end, what, glob_re)) {
341 // Ensure that no information is lost.
342 pattern += string(it, end);
346 // Everything from the start of the input to
347 // the start of the match.
348 pattern += string(what[-1].first, what[-1].second);
350 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
351 string const head = string(what[1].first, what[1].second);
352 string const tail = string(what[2].first, what[2].second);
354 // Split the ','-separated chunks of tail so that
355 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
356 string const fmt = " " + head + "$1";
357 pattern += boost::regex_merge(tail, block_re, fmt);
359 // Increment the iterator to the end of the match.
360 it += distance(it, what[0].second);
369 /* \param description text describing the filters.
370 * \param one or more wildcard patterns, separated by
373 Filter(docstring const & description, std::string const & globs);
375 docstring const & description() const { return desc_; }
377 QString toString() const;
380 std::vector<std::string> globs_;
384 Filter::Filter(docstring const & description, string const & globs)
387 typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
388 boost::char_separator<char> const separator(" ");
390 // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
391 // "<glob> <glob> ... *.abc *.def <glob>"
392 string const expanded_globs = convert_brace_glob(globs);
394 // Split into individual globs.
395 vector<string> matches;
396 Tokenizer const tokens(expanded_globs, separator);
397 globs_ = vector<string>(tokens.begin(), tokens.end());
401 QString Filter::toString() const
405 bool const has_description = desc_.empty();
407 if (has_description) {
412 for (size_t i = 0; i != globs_.size(); ++i) {
415 s += toqstr(globs_[i]);
424 /** \c FileFilterList parses a Qt-style list of available file filters
425 * to generate the corresponding vector.
426 * For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
427 * will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
428 * will result in a vector of size 1 in which the description field is empty.
430 struct FileFilterList
432 // FIXME UNICODE: globs_ should be unicode...
433 /** \param qt_style_filter a list of available file filters.
434 * Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
435 * The "All files (*)" filter is always added to the list.
437 explicit FileFilterList(docstring const & qt_style_filter =
440 typedef std::vector<Filter>::size_type size_type;
442 bool empty() const { return filters_.empty(); }
443 size_type size() const { return filters_.size(); }
444 Filter & operator[](size_type i) { return filters_[i]; }
445 Filter const & operator[](size_type i) const { return filters_[i]; }
447 void parse_filter(std::string const & filter);
448 std::vector<Filter> filters_;
452 FileFilterList::FileFilterList(docstring const & qt_style_filter)
455 string const filter = to_utf8(qt_style_filter)
456 + (qt_style_filter.empty() ? string() : ";;")
457 + to_utf8(_("All Files "))
464 // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
465 // into individual filters.
466 static boost::regex const separator_re(";;");
468 string::const_iterator it = filter.begin();
469 string::const_iterator const end = filter.end();
471 boost::match_results<string::const_iterator> what;
473 if (!boost::regex_search(it, end, what, separator_re)) {
474 parse_filter(string(it, end));
478 // Everything from the start of the input to
479 // the start of the match.
480 parse_filter(string(what[-1].first, what[-1].second));
482 // Increment the iterator to the end of the match.
483 it += distance(it, what[0].second);
488 void FileFilterList::parse_filter(string const & filter)
490 // Matches "TeX documents (*.tex)",
491 // storing "TeX documents " as group 1 and "*.tex" as group 2.
492 static boost::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
494 boost::match_results<string::const_iterator> what;
495 if (!boost::regex_search(filter, what, filter_re)) {
496 // Just a glob, no description.
497 filters_.push_back(Filter(docstring(), trim(filter)));
500 docstring const desc = from_utf8(string(what[1].first, what[1].second));
501 string const globs = string(what[2].first, what[2].second);
502 filters_.push_back(Filter(trim(desc), trim(globs)));
507 /** \returns the equivalent of the string passed in
508 * although any brace expressions are expanded.
509 * (E.g. "*.{png,jpg}" -> "*.png *.jpg")
511 QStringList fileFilters(QString const & desc)
513 // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
514 // but need: "*.cpp;*.cc;*.C;*.cxx;*.c++"
515 FileFilterList filters(qstring_to_ucs4(desc));
516 LYXERR0("DESC: " << desc);
518 for (size_t i = 0; i != filters.filters_.size(); ++i) {
519 QString f = filters.filters_[i].toString();
520 LYXERR0("FILTER: " << f);