]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/qt_helpers.cpp
da5de87dac6d2c5d81e77997483087f08bd9a9cb
[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 LyXErr & operator<<(LyXErr & err, QString const & str)
58 {
59         return err << fromqstr(str);
60 }
61
62
63 FileName libFileSearch(QString const & dir, QString const & name,
64                                 QString const & ext)
65 {
66         return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext));
67 }
68
69
70 namespace frontend {
71
72 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
73 {
74         QString const length = input->text();
75         if (length.isEmpty())
76                 return string();
77
78         // Don't return unit-from-choice if the input(field) contains a unit
79         if (isValidGlueLength(fromqstr(length)))
80                 return fromqstr(length);
81
82         Length::UNIT const unit = combo->currentLengthItem();
83
84         return Length(length.toDouble(), unit).asString();
85 }
86
87
88 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
89 {
90         QString const length = input->text();
91         if (length.isEmpty())
92                 return Length();
93
94         // don't return unit-from-choice if the input(field) contains a unit
95         if (isValidGlueLength(fromqstr(length)))
96                 return Length(fromqstr(length));
97
98         Length::UNIT const unit = unitFromString(fromqstr(combo->currentText()));
99
100         return Length(length.toDouble(), unit);
101 }
102
103
104 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
105                      Length const & len, Length::UNIT /*defaultUnit*/)
106 {
107         combo->setCurrentItem(len.unit());
108         input->setText(QString::number(Length(len).value()));
109 }
110
111
112 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
113         string const & len, Length::UNIT defaultUnit)
114 {
115         if (len.empty()) {
116                 // no length (UNIT_NONE)
117                 combo->setCurrentItem(defaultUnit);
118                 input->setText("");
119         } else if (!isValidLength(len) && !isStrDbl(len)) {
120                 // use input field only for gluelengths
121                 combo->setCurrentItem(defaultUnit);
122                 input->setText(toqstr(len));
123         } else {
124                 lengthToWidgets(input, combo, Length(len), defaultUnit);
125         }
126 }
127
128
129 void lengthAutoToWidgets(QLineEdit * input, LengthCombo * combo,
130         Length const & len, Length::UNIT defaultUnit)
131 {
132         if (len.value() == 0)
133                 lengthToWidgets(input, combo, "auto", defaultUnit);
134         else
135                 lengthToWidgets(input, combo, len, defaultUnit);
136 }
137
138
139 void setValid(QWidget * widget, bool valid)
140 {
141         if (valid) {
142                 widget->setPalette(QPalette());
143         } else {
144                 QPalette pal = widget->palette();
145                 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
146                 widget->setPalette(pal);
147         }
148 }
149
150 } // namespace frontend
151
152 QString const qt_(char const * str, const char *)
153 {
154         return toqstr(_(str));
155 }
156
157
158 QString const qt_(string const & str)
159 {
160         return toqstr(_(str));
161 }
162
163 namespace {
164
165 class Sorter
166 {
167 public:
168 #if !defined(USE_WCHAR_T) && defined(__GNUC__)
169         bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
170         {
171                 return lhs.first < rhs.first;
172         }
173 #else
174         Sorter() : loc_ok(true)
175         {
176                 try {
177                         loc_ = locale("");
178                 } catch (...) {
179                         loc_ok = false;
180                 }
181         }
182
183         bool operator()(LanguagePair const & lhs, LanguagePair const & rhs) const
184         {
185                 //  FIXME: would that be "QString::localeAwareCompare()"?
186                 if (loc_ok)
187                         return loc_(fromqstr(lhs.first), fromqstr(rhs.first));
188                 else
189                         return lhs.first < rhs.first;
190         }
191 private:
192         locale loc_;
193         bool loc_ok;
194 #endif
195 };
196
197
198 } // namespace anon
199
200
201 QList<LanguagePair> languageData()
202 {
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()));
208         }
209         return list;
210 }
211
212
213 void rescanTexStyles()
214 {
215         // Run rescan in user lyx directory
216         PathChanger p(package().user_support());
217         FileName const command = support::libFileSearch("scripts", "TeXFiles.py");
218         Systemcall one;
219         int const status = one.startscript(Systemcall::Wait,
220                         os::python() + ' ' +
221                         quoteName(command.toFilesystemEncoding()));
222         if (status == 0)
223                 return;
224         // FIXME UNICODE
225         frontend::Alert::error(_("Could not update TeX information"),
226                 bformat(_("The script `%s' failed."), from_utf8(command.absFilename())));
227 }
228
229
230 QStringList texFileList(QString const & filename)
231 {
232         QStringList list;
233         FileName const file = libFileSearch(QString(), filename);
234         if (file.empty())
235                 return list;
236
237         // FIXME Unicode.
238         vector<docstring> doclist = 
239                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
240
241         // Normalise paths like /foo//bar ==> /foo/bar
242         QSet<QString> set;
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("//", "/");
248                 if (!file.isEmpty())
249                         set.insert(file);
250         }
251
252         // remove duplicates
253         return QList<QString>::fromSet(set);
254 }
255
256
257 QString internalPath(const QString & str)
258 {
259         return toqstr(os::internal_path(fromqstr(str)));
260 }
261
262
263 QString onlyFilename(const QString & str)
264 {
265         return toqstr(support::onlyFilename(fromqstr(str)));
266 }
267
268
269 QString onlyPath(const QString & str)
270 {
271         return toqstr(support::onlyPath(fromqstr(str)));
272 }
273
274
275 QString changeExtension(QString const & oldname, QString const & ext)
276 {
277         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
278 }
279
280 /// Remove the extension from \p name
281 QString removeExtension(QString const & name)
282 {
283         return toqstr(support::removeExtension(fromqstr(name)));
284 }
285
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
289  contains a dot.
290  */
291 QString addExtension(QString const & name, QString const & ext)
292 {
293         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
294 }
295
296 /// Return the extension of the file (not including the .)
297 QString getExtension(QString const & name)
298 {
299         return toqstr(support::getExtension(fromqstr(name)));
300 }
301
302
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.
306   */
307 QString makeAbsPath(QString const & relpath, QString const & base)
308 {
309         return toqstr(support::makeAbsPath(fromqstr(relpath),
310                 fromqstr(base)).absFilename());
311 }
312
313
314 /////////////////////////////////////////////////////////////////////////
315 //
316 // FileFilterList
317 //
318 /////////////////////////////////////////////////////////////////////////
319
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.
325  */
326 static string const convert_brace_glob(string const & glob)
327 {
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("([^,}]+),?");
333
334         string pattern;
335
336         string::const_iterator it = glob.begin();
337         string::const_iterator const end = glob.end();
338         while (true) {
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);
343                         break;
344                 }
345
346                 // Everything from the start of the input to
347                 // the start of the match.
348                 pattern += string(what[-1].first, what[-1].second);
349
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);
353
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);
358
359                 // Increment the iterator to the end of the match.
360                 it += distance(it, what[0].second);
361         }
362
363         return pattern;
364 }
365
366
367 struct Filter
368 {
369         /* \param description text describing the filters.
370          * \param one or more wildcard patterns, separated by
371          * whitespace.
372          */
373         Filter(docstring const & description, std::string const & globs);
374
375         docstring const & description() const { return desc_; }
376
377         QString toString() const;
378
379         docstring desc_;
380         std::vector<std::string> globs_;
381 };
382
383
384 Filter::Filter(docstring const & description, string const & globs)
385         : desc_(description)
386 {
387         typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
388         boost::char_separator<char> const separator(" ");
389
390         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
391         //       "<glob> <glob> ... *.abc *.def <glob>"
392         string const expanded_globs = convert_brace_glob(globs);
393
394         // Split into individual globs.
395         vector<string> matches;
396         Tokenizer const tokens(expanded_globs, separator);
397         globs_ = vector<string>(tokens.begin(), tokens.end());
398 }
399
400
401 QString Filter::toString() const
402 {
403         QString s;
404
405         bool const has_description = desc_.empty();
406
407         if (has_description) {
408                 s += toqstr(desc_);
409                 s += " (";
410         }
411
412         for (size_t i = 0; i != globs_.size(); ++i) {
413                 if (i > 0)
414                         s += ' ';
415                 s += toqstr(globs_[i]);
416         }
417
418         if (has_description)
419                 s += ')';
420         return s;
421 }
422
423
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.
429  */
430 struct FileFilterList
431 {
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.
436          */
437         explicit FileFilterList(docstring const & qt_style_filter =
438                                 docstring());
439
440         typedef std::vector<Filter>::size_type size_type;
441
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]; }
446
447         void parse_filter(std::string const & filter);
448         std::vector<Filter> filters_;
449 };
450
451
452 FileFilterList::FileFilterList(docstring const & qt_style_filter)
453 {
454         // FIXME UNICODE
455         string const filter = to_utf8(qt_style_filter)
456                 + (qt_style_filter.empty() ? string() : ";;")
457                 + to_utf8(_("All Files "))
458 #if defined(_WIN32)             
459                 + ("(*.*)");
460 #else
461                 + ("(*)");
462 #endif
463
464         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
465         // into individual filters.
466         static boost::regex const separator_re(";;");
467
468         string::const_iterator it = filter.begin();
469         string::const_iterator const end = filter.end();
470         while (true) {
471                 boost::match_results<string::const_iterator> what;
472
473                 if (!boost::regex_search(it, end, what, separator_re)) {
474                         parse_filter(string(it, end));
475                         break;
476                 }
477
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));
481
482                 // Increment the iterator to the end of the match.
483                 it += distance(it, what[0].second);
484         }
485 }
486
487
488 void FileFilterList::parse_filter(string const & filter)
489 {
490         // Matches "TeX documents (*.tex)",
491         // storing "TeX documents " as group 1 and "*.tex" as group 2.
492         static boost::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
493
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)));
498         } else {
499                 // FIXME UNICODE
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)));
503         }
504 }
505
506
507 /** \returns the equivalent of the string passed in
508  *  although any brace expressions are expanded.
509  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
510  */
511 QStringList fileFilters(QString const & desc)
512 {
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);
517         QStringList list;
518         for (size_t i = 0; i != filters.filters_.size(); ++i) {
519                 QString f = filters.filters_[i].toString();
520                 LYXERR0("FILTER: " << f);
521                 list.append(f);
522         }
523         return list;
524 }
525
526 } // namespace lyx