]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/qt_helpers.cpp
move the filefilter stuff closer to the only place where it is used
[lyx.git] / src / frontends / qt4 / qt_helpers.cpp
1 /**
2  * \file qt_helpers.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Dekel Tsur
7  * \author Jürgen Spitzmüller
8  * \author Richard Heck
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "qt_helpers.h"
16
17 #include "FileDialog.h"
18 #include "LengthCombo.h"
19
20 #include "frontends/alert.h"
21
22 #include "Language.h"
23 #include "Length.h"
24
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"
35
36 #include <QCheckBox>
37 #include <QComboBox>
38 #include <QLineEdit>
39 #include <QPalette>
40 #include <QSet>
41
42 #include <algorithm>
43 #include <fstream>
44 #include <locale>
45
46 // for FileFilter.
47 // FIXME: Remove
48 #include <boost/regex.hpp>
49 #include <boost/tokenizer.hpp>
50
51
52 using namespace std;
53 using namespace lyx::support;
54
55 namespace lyx {
56
57 FileName libFileSearch(QString const & dir, QString const & name,
58                                 QString const & ext)
59 {
60         return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext));
61 }
62
63 namespace frontend {
64
65 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
66 {
67         QString const length = input->text();
68         if (length.isEmpty())
69                 return string();
70
71         // Don't return unit-from-choice if the input(field) contains a unit
72         if (isValidGlueLength(fromqstr(length)))
73                 return fromqstr(length);
74
75         Length::UNIT const unit = combo->currentLengthItem();
76
77         return Length(length.toDouble(), unit).asString();
78 }
79
80
81 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
82 {
83         QString const length = input->text();
84         if (length.isEmpty())
85                 return Length();
86
87         // don't return unit-from-choice if the input(field) contains a unit
88         if (isValidGlueLength(fromqstr(length)))
89                 return Length(fromqstr(length));
90
91         Length::UNIT const unit = unitFromString(fromqstr(combo->currentText()));
92
93         return Length(length.toDouble(), unit);
94 }
95
96
97 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
98                      Length const & len, Length::UNIT /*defaultUnit*/)
99 {
100         combo->setCurrentItem(len.unit());
101         input->setText(QString::number(Length(len).value()));
102 }
103
104
105 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
106         string const & len, Length::UNIT defaultUnit)
107 {
108         if (len.empty()) {
109                 // no length (UNIT_NONE)
110                 combo->setCurrentItem(defaultUnit);
111                 input->setText("");
112         } else if (!isValidLength(len) && !isStrDbl(len)) {
113                 // use input field only for gluelengths
114                 combo->setCurrentItem(defaultUnit);
115                 input->setText(toqstr(len));
116         } else {
117                 lengthToWidgets(input, combo, Length(len), defaultUnit);
118         }
119 }
120
121
122 void lengthAutoToWidgets(QLineEdit * input, LengthCombo * combo,
123         Length const & len, Length::UNIT defaultUnit)
124 {
125         if (len.value() == 0)
126                 lengthToWidgets(input, combo, "auto", defaultUnit);
127         else
128                 lengthToWidgets(input, combo, len, defaultUnit);
129 }
130
131
132 void setValid(QWidget * widget, bool valid)
133 {
134         if (valid) {
135                 widget->setPalette(QPalette());
136         } else {
137                 QPalette pal = widget->palette();
138                 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
139                 widget->setPalette(pal);
140         }
141 }
142
143 } // namespace frontend
144
145 QString const qt_(char const * str, const char *)
146 {
147         return toqstr(_(str));
148 }
149
150
151 QString const qt_(string const & str)
152 {
153         return toqstr(_(str));
154 }
155
156 namespace {
157
158 class Sorter
159 {
160 public:
161 #if !defined(USE_WCHAR_T) && defined(__GNUC__)
162         bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
163         {
164                 return lhs.first < rhs.first;
165         }
166 #else
167         Sorter() : loc_ok(true)
168         {
169                 try {
170                         loc_ = locale("");
171                 } catch (...) {
172                         loc_ok = false;
173                 }
174         }
175
176         bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
177         {
178                 //  FIXME: would that be "QString::localeAwareCompare()"?
179                 if (loc_ok)
180                         return loc_(fromqstr(lhs.first), fromqstr(rhs.first));
181                 else
182                         return lhs.first < rhs.first;
183         }
184 private:
185         locale loc_;
186         bool loc_ok;
187 #endif
188 };
189
190
191 } // namespace anon
192
193
194 QList<LanguagePair> languageData(bool character_dlg)
195 {
196         size_t const offset = character_dlg ? 2 : 0;
197         vector<LanguagePair> langs(languages.size() + offset);
198
199         if (character_dlg) {
200                 langs[0].first = qt_("No change");
201                 langs[0].second = "ignore";
202                 langs[1].first = qt_("Reset");
203                 langs[1].second = "reset";
204         }
205
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());
210         }
211
212         // Don't sort "ignore" and "reset"
213         vector<LanguagePair>::iterator begin = langs.begin() + offset;
214         sort(begin, langs.end(), Sorter());
215
216         QList<LanguagePair> list;
217         foreach (LanguagePair const & l, langs)
218                 list.append(l);
219         return list;
220 }
221
222
223 void rescanTexStyles()
224 {
225         // Run rescan in user lyx directory
226         PathChanger p(package().user_support());
227         FileName const command = libFileSearch("scripts", "TeXFiles.py");
228         Systemcall one;
229         int const status = one.startscript(Systemcall::Wait,
230                         os::python() + ' ' +
231                         quoteName(command.toFilesystemEncoding()));
232         if (status == 0)
233                 return;
234         // FIXME UNICODE
235         frontend::Alert::error(_("Could not update TeX information"),
236                 bformat(_("The script `%s' failed."), from_utf8(command.absFilename())));
237 }
238
239
240 QStringList texFileList(QString const & filename)
241 {
242         QStringList list;
243         FileName const file = libFileSearch(QString(), filename);
244         if (file.empty())
245                 return list;
246
247         // FIXME Unicode.
248         vector<docstring> doclist = 
249                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
250
251         // Normalise paths like /foo//bar ==> /foo/bar
252         QSet<QString> set;
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("//", "/");
258                 if (!file.isEmpty())
259                         set.insert(file);
260         }
261
262         // remove duplicates
263         return QList<QString>::fromSet(set);
264 }
265
266
267 QString internalPath(const QString & str)
268 {
269         return toqstr(os::internal_path(fromqstr(str)));
270 }
271
272
273 QString onlyFilename(const QString & str)
274 {
275         return toqstr(support::onlyFilename(fromqstr(str)));
276 }
277
278
279 QString onlyPath(const QString & str)
280 {
281         return toqstr(support::onlyPath(fromqstr(str)));
282 }
283
284
285 QString changeExtension(QString const & oldname, QString const & ext)
286 {
287         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
288 }
289
290 /// Remove the extension from \p name
291 QString removeExtension(QString const & name)
292 {
293         return toqstr(support::removeExtension(fromqstr(name)));
294 }
295
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
299  contains a dot.
300  */
301 QString addExtension(QString const & name, QString const & ext)
302 {
303         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
304 }
305
306 /// Return the extension of the file (not including the .)
307 QString getExtension(QString const & name)
308 {
309         return toqstr(support::getExtension(fromqstr(name)));
310 }
311
312
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.
316   */
317 QString makeAbsPath(QString const & relpath, QString const & base)
318 {
319         return toqstr(support::makeAbsPath(fromqstr(relpath),
320                 fromqstr(base)).absFilename());
321 }
322
323
324 /////////////////////////////////////////////////////////////////////////
325 //
326 // FileFilterList
327 //
328 /////////////////////////////////////////////////////////////////////////
329
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.
335  */
336 static string const convert_brace_glob(string const & glob)
337 {
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("([^,}]+),?");
343
344         string pattern;
345
346         string::const_iterator it = glob.begin();
347         string::const_iterator const end = glob.end();
348         while (true) {
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);
353                         break;
354                 }
355
356                 // Everything from the start of the input to
357                 // the start of the match.
358                 pattern += string(what[-1].first, what[-1].second);
359
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);
363
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);
368
369                 // Increment the iterator to the end of the match.
370                 it += distance(it, what[0].second);
371         }
372
373         return pattern;
374 }
375
376
377 struct Filter
378 {
379         /* \param description text describing the filters.
380          * \param one or more wildcard patterns, separated by
381          * whitespace.
382          */
383         Filter(docstring const & description, std::string const & globs);
384
385         docstring const & description() const { return desc_; }
386
387         QString toString() const;
388
389         docstring desc_;
390         std::vector<std::string> globs_;
391 };
392
393
394 Filter::Filter(docstring const & description, string const & globs)
395         : desc_(description)
396 {
397         typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
398         boost::char_separator<char> const separator(" ");
399
400         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
401         //       "<glob> <glob> ... *.abc *.def <glob>"
402         string const expanded_globs = convert_brace_glob(globs);
403
404         // Split into individual globs.
405         vector<string> matches;
406         Tokenizer const tokens(expanded_globs, separator);
407         globs_ = vector<string>(tokens.begin(), tokens.end());
408 }
409
410
411 QString Filter::toString() const
412 {
413         QString s;
414
415         bool const has_description = desc_.empty();
416
417         if (has_description) {
418                 s += toqstr(desc_);
419                 s += " (";
420         }
421
422         for (size_t i = 0; i != globs_.size(); ++i) {
423                 if (i > 0)
424                         s += ' ';
425                 s += toqstr(globs_[i]);
426         }
427
428         if (has_description)
429                 s += ')';
430         return s;
431 }
432
433
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.
439  */
440 struct FileFilterList
441 {
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.
446          */
447         explicit FileFilterList(docstring const & qt_style_filter =
448                                 docstring());
449
450         typedef std::vector<Filter>::size_type size_type;
451
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]; }
456
457         void parse_filter(std::string const & filter);
458         std::vector<Filter> filters_;
459 };
460
461
462 FileFilterList::FileFilterList(docstring const & qt_style_filter)
463 {
464         // FIXME UNICODE
465         string const filter = to_utf8(qt_style_filter)
466                 + (qt_style_filter.empty() ? string() : ";;")
467                 + to_utf8(_("All Files "))
468 #if defined(_WIN32)             
469                 + ("(*.*)");
470 #else
471                 + ("(*)");
472 #endif
473
474         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
475         // into individual filters.
476         static boost::regex const separator_re(";;");
477
478         string::const_iterator it = filter.begin();
479         string::const_iterator const end = filter.end();
480         while (true) {
481                 boost::match_results<string::const_iterator> what;
482
483                 if (!boost::regex_search(it, end, what, separator_re)) {
484                         parse_filter(string(it, end));
485                         break;
486                 }
487
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));
491
492                 // Increment the iterator to the end of the match.
493                 it += distance(it, what[0].second);
494         }
495 }
496
497
498 void FileFilterList::parse_filter(string const & filter)
499 {
500         // Matches "TeX documents (*.tex)",
501         // storing "TeX documents " as group 1 and "*.tex" as group 2.
502         static boost::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
503
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)));
508         } else {
509                 // FIXME UNICODE
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)));
513         }
514 }
515
516
517 /** \returns the equivalent of the string passed in
518  *  although any brace expressions are expanded.
519  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
520  */
521 QStringList fileFilters(QString const & desc)
522 {
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));
527         QStringList list;
528         for (size_t i = 0; i != filters.filters_.size(); ++i) {
529                 QString f = filters.filters_[i].toString();
530                 LYXERR0("FILTER: " << fromqstr(f));
531                 list.append(f);
532         }
533         return list;
534 }
535
536 } // namespace lyx