]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/qt_helpers.cpp
cosmetics
[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(bool character_dlg)
202 {
203         size_t const offset = character_dlg ? 2 : 0;
204         vector<LanguagePair> langs(languages.size() + offset);
205
206         if (character_dlg) {
207                 langs[0].first = qt_("No change");
208                 langs[0].second = "ignore";
209                 langs[1].first = qt_("Reset");
210                 langs[1].second = "reset";
211         }
212
213         Languages::const_iterator it = languages.begin();
214         for (size_t i = 0; i != languages.size(); ++i, ++it) {
215                 langs[i + offset].first  = qt_(it->second.display());
216                 langs[i + offset].second = toqstr(it->second.lang());
217         }
218
219         // Don't sort "ignore" and "reset"
220         vector<LanguagePair>::iterator begin = langs.begin() + offset;
221         sort(begin, langs.end(), Sorter());
222
223         QList<LanguagePair> list;
224         foreach (LanguagePair const & l, langs)
225                 list.append(l);
226         return list;
227 }
228
229
230 void rescanTexStyles()
231 {
232         // Run rescan in user lyx directory
233         PathChanger p(package().user_support());
234         FileName const command = libFileSearch("scripts", "TeXFiles.py");
235         Systemcall one;
236         int const status = one.startscript(Systemcall::Wait,
237                         os::python() + ' ' +
238                         quoteName(command.toFilesystemEncoding()));
239         if (status == 0)
240                 return;
241         // FIXME UNICODE
242         frontend::Alert::error(_("Could not update TeX information"),
243                 bformat(_("The script `%s' failed."), from_utf8(command.absFilename())));
244 }
245
246
247 QStringList texFileList(QString const & filename)
248 {
249         QStringList list;
250         FileName const file = libFileSearch(QString(), filename);
251         if (file.empty())
252                 return list;
253
254         // FIXME Unicode.
255         vector<docstring> doclist = 
256                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
257
258         // Normalise paths like /foo//bar ==> /foo/bar
259         QSet<QString> set;
260         for (size_t i = 0; i != doclist.size(); ++i) {
261                 QString file = toqstr(doclist[i]);
262                 file.replace("\r", "");
263                 while (file.contains("//"))
264                         file.replace("//", "/");
265                 if (!file.isEmpty())
266                         set.insert(file);
267         }
268
269         // remove duplicates
270         return QList<QString>::fromSet(set);
271 }
272
273
274 QString internalPath(const QString & str)
275 {
276         return toqstr(os::internal_path(fromqstr(str)));
277 }
278
279
280 QString onlyFilename(const QString & str)
281 {
282         return toqstr(support::onlyFilename(fromqstr(str)));
283 }
284
285
286 QString onlyPath(const QString & str)
287 {
288         return toqstr(support::onlyPath(fromqstr(str)));
289 }
290
291
292 QString changeExtension(QString const & oldname, QString const & ext)
293 {
294         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
295 }
296
297 /// Remove the extension from \p name
298 QString removeExtension(QString const & name)
299 {
300         return toqstr(support::removeExtension(fromqstr(name)));
301 }
302
303 /** Add the extension \p ext to \p name.
304  Use this instead of changeExtension if you know that \p name is without
305  extension, because changeExtension would wrongly interpret \p name if it
306  contains a dot.
307  */
308 QString addExtension(QString const & name, QString const & ext)
309 {
310         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
311 }
312
313 /// Return the extension of the file (not including the .)
314 QString getExtension(QString const & name)
315 {
316         return toqstr(support::getExtension(fromqstr(name)));
317 }
318
319
320 /** Convert relative path into absolute path based on a basepath.
321   If relpath is absolute, just use that.
322   If basepath doesn't exist use CWD.
323   */
324 QString makeAbsPath(QString const & relpath, QString const & base)
325 {
326         return toqstr(support::makeAbsPath(fromqstr(relpath),
327                 fromqstr(base)).absFilename());
328 }
329
330
331 /////////////////////////////////////////////////////////////////////////
332 //
333 // FileFilterList
334 //
335 /////////////////////////////////////////////////////////////////////////
336
337 /** Given a string such as
338  *      "<glob> <glob> ... *.{abc,def} <glob>",
339  *  convert the csh-style brace expresions:
340  *      "<glob> <glob> ... *.abc *.def <glob>".
341  *  Requires no system support, so should work equally on Unix, Mac, Win32.
342  */
343 static string const convert_brace_glob(string const & glob)
344 {
345         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
346         // "abc,def,ghi" as group 2.
347         static boost::regex const glob_re(" *([^ {]*)\\{([^ }]+)\\}");
348         // Matches "abc" and "abc,", storing "abc" as group 1.
349         static boost::regex const block_re("([^,}]+),?");
350
351         string pattern;
352
353         string::const_iterator it = glob.begin();
354         string::const_iterator const end = glob.end();
355         while (true) {
356                 boost::match_results<string::const_iterator> what;
357                 if (!boost::regex_search(it, end, what, glob_re)) {
358                         // Ensure that no information is lost.
359                         pattern += string(it, end);
360                         break;
361                 }
362
363                 // Everything from the start of the input to
364                 // the start of the match.
365                 pattern += string(what[-1].first, what[-1].second);
366
367                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
368                 string const head = string(what[1].first, what[1].second);
369                 string const tail = string(what[2].first, what[2].second);
370
371                 // Split the ','-separated chunks of tail so that
372                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
373                 string const fmt = " " + head + "$1";
374                 pattern += boost::regex_merge(tail, block_re, fmt);
375
376                 // Increment the iterator to the end of the match.
377                 it += distance(it, what[0].second);
378         }
379
380         return pattern;
381 }
382
383
384 struct Filter
385 {
386         /* \param description text describing the filters.
387          * \param one or more wildcard patterns, separated by
388          * whitespace.
389          */
390         Filter(docstring const & description, std::string const & globs);
391
392         docstring const & description() const { return desc_; }
393
394         QString toString() const;
395
396         docstring desc_;
397         std::vector<std::string> globs_;
398 };
399
400
401 Filter::Filter(docstring const & description, string const & globs)
402         : desc_(description)
403 {
404         typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
405         boost::char_separator<char> const separator(" ");
406
407         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
408         //       "<glob> <glob> ... *.abc *.def <glob>"
409         string const expanded_globs = convert_brace_glob(globs);
410
411         // Split into individual globs.
412         vector<string> matches;
413         Tokenizer const tokens(expanded_globs, separator);
414         globs_ = vector<string>(tokens.begin(), tokens.end());
415 }
416
417
418 QString Filter::toString() const
419 {
420         QString s;
421
422         bool const has_description = desc_.empty();
423
424         if (has_description) {
425                 s += toqstr(desc_);
426                 s += " (";
427         }
428
429         for (size_t i = 0; i != globs_.size(); ++i) {
430                 if (i > 0)
431                         s += ' ';
432                 s += toqstr(globs_[i]);
433         }
434
435         if (has_description)
436                 s += ')';
437         return s;
438 }
439
440
441 /** \c FileFilterList parses a Qt-style list of available file filters
442  *  to generate the corresponding vector.
443  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
444  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
445  *  will result in a vector of size 1 in which the description field is empty.
446  */
447 struct FileFilterList
448 {
449         // FIXME UNICODE: globs_ should be unicode...
450         /** \param qt_style_filter a list of available file filters.
451          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
452          *  The "All files (*)" filter is always added to the list.
453          */
454         explicit FileFilterList(docstring const & qt_style_filter =
455                                 docstring());
456
457         typedef std::vector<Filter>::size_type size_type;
458
459         bool empty() const { return filters_.empty(); }
460         size_type size() const { return filters_.size(); }
461         Filter & operator[](size_type i) { return filters_[i]; }
462         Filter const & operator[](size_type i) const { return filters_[i]; }
463
464         void parse_filter(std::string const & filter);
465         std::vector<Filter> filters_;
466 };
467
468
469 FileFilterList::FileFilterList(docstring const & qt_style_filter)
470 {
471         // FIXME UNICODE
472         string const filter = to_utf8(qt_style_filter)
473                 + (qt_style_filter.empty() ? string() : ";;")
474                 + to_utf8(_("All Files "))
475 #if defined(_WIN32)             
476                 + ("(*.*)");
477 #else
478                 + ("(*)");
479 #endif
480
481         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
482         // into individual filters.
483         static boost::regex const separator_re(";;");
484
485         string::const_iterator it = filter.begin();
486         string::const_iterator const end = filter.end();
487         while (true) {
488                 boost::match_results<string::const_iterator> what;
489
490                 if (!boost::regex_search(it, end, what, separator_re)) {
491                         parse_filter(string(it, end));
492                         break;
493                 }
494
495                 // Everything from the start of the input to
496                 // the start of the match.
497                 parse_filter(string(what[-1].first, what[-1].second));
498
499                 // Increment the iterator to the end of the match.
500                 it += distance(it, what[0].second);
501         }
502 }
503
504
505 void FileFilterList::parse_filter(string const & filter)
506 {
507         // Matches "TeX documents (*.tex)",
508         // storing "TeX documents " as group 1 and "*.tex" as group 2.
509         static boost::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
510
511         boost::match_results<string::const_iterator> what;
512         if (!boost::regex_search(filter, what, filter_re)) {
513                 // Just a glob, no description.
514                 filters_.push_back(Filter(docstring(), trim(filter)));
515         } else {
516                 // FIXME UNICODE
517                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
518                 string const globs = string(what[2].first, what[2].second);
519                 filters_.push_back(Filter(trim(desc), trim(globs)));
520         }
521 }
522
523
524 /** \returns the equivalent of the string passed in
525  *  although any brace expressions are expanded.
526  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
527  */
528 QStringList fileFilters(QString const & desc)
529 {
530         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
531         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
532         FileFilterList filters(qstring_to_ucs4(desc));
533         LYXERR0("DESC: " << fromqstr(desc));
534         QStringList list;
535         for (size_t i = 0; i != filters.filters_.size(); ++i) {
536                 QString f = filters.filters_[i].toString();
537                 LYXERR0("FILTER: " << fromqstr(f));
538                 list.append(f);
539         }
540         return list;
541 }
542
543 } // namespace lyx