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