]> git.lyx.org Git - features.git/blob - src/frontends/qt4/qt_helpers.cpp
move LyXerr QString specialisation to support/qstring_helpers
[features.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 "BufferParams.h"
23 #include "FloatList.h"
24 #include "Language.h"
25 #include "Length.h"
26 #include "TextClass.h"
27
28 #include "support/debug.h"
29 #include "support/filetools.h"
30 #include "support/foreach.h"
31 #include "support/gettext.h"
32 #include "support/lstrings.h"
33 #include "support/lyxalgo.h"
34 #include "support/os.h"
35 #include "support/Package.h"
36 #include "support/Path.h"
37 #include "support/Systemcall.h"
38
39 #include <QCheckBox>
40 #include <QComboBox>
41 #include <QLineEdit>
42 #include <QPalette>
43 #include <QSet>
44
45 #include <algorithm>
46 #include <fstream>
47 #include <locale>
48
49 // for FileFilter.
50 // FIXME: Remove
51 #include <boost/regex.hpp>
52 #include <boost/tokenizer.hpp>
53
54
55 using namespace std;
56 using namespace lyx::support;
57
58 namespace lyx {
59
60 FileName libFileSearch(QString const & dir, QString const & name,
61                                 QString const & ext)
62 {
63         return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext));
64 }
65
66
67 namespace frontend {
68
69 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
70 {
71         QString const length = input->text();
72         if (length.isEmpty())
73                 return string();
74
75         // Don't return unit-from-choice if the input(field) contains a unit
76         if (isValidGlueLength(fromqstr(length)))
77                 return fromqstr(length);
78
79         Length::UNIT const unit = combo->currentLengthItem();
80
81         return Length(length.toDouble(), unit).asString();
82 }
83
84
85 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
86 {
87         QString const length = input->text();
88         if (length.isEmpty())
89                 return Length();
90
91         // don't return unit-from-choice if the input(field) contains a unit
92         if (isValidGlueLength(fromqstr(length)))
93                 return Length(fromqstr(length));
94
95         Length::UNIT const unit = unitFromString(fromqstr(combo->currentText()));
96
97         return Length(length.toDouble(), unit);
98 }
99
100
101 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
102                      Length const & len, Length::UNIT /*defaultUnit*/)
103 {
104         combo->setCurrentItem(len.unit());
105         input->setText(QString::number(Length(len).value()));
106 }
107
108
109 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
110         string const & len, Length::UNIT defaultUnit)
111 {
112         if (len.empty()) {
113                 // no length (UNIT_NONE)
114                 combo->setCurrentItem(defaultUnit);
115                 input->setText("");
116         } else if (!isValidLength(len) && !isStrDbl(len)) {
117                 // use input field only for gluelengths
118                 combo->setCurrentItem(defaultUnit);
119                 input->setText(toqstr(len));
120         } else {
121                 lengthToWidgets(input, combo, Length(len), defaultUnit);
122         }
123 }
124
125
126 void lengthAutoToWidgets(QLineEdit * input, LengthCombo * combo,
127         Length const & len, Length::UNIT defaultUnit)
128 {
129         if (len.value() == 0)
130                 lengthToWidgets(input, combo, "auto", defaultUnit);
131         else
132                 lengthToWidgets(input, combo, len, defaultUnit);
133 }
134
135
136 void setValid(QWidget * widget, bool valid)
137 {
138         if (valid) {
139                 widget->setPalette(QPalette());
140         } else {
141                 QPalette pal = widget->palette();
142                 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
143                 widget->setPalette(pal);
144         }
145 }
146
147 } // namespace frontend
148
149 QString const qt_(char const * str, const char *)
150 {
151         return toqstr(_(str));
152 }
153
154
155 QString const qt_(string const & str)
156 {
157         return toqstr(_(str));
158 }
159
160
161 void rescanTexStyles()
162 {
163         // Run rescan in user lyx directory
164         PathChanger p(package().user_support());
165         FileName const command = support::libFileSearch("scripts", "TeXFiles.py");
166         Systemcall one;
167         int const status = one.startscript(Systemcall::Wait,
168                         os::python() + ' ' +
169                         quoteName(command.toFilesystemEncoding()));
170         if (status == 0)
171                 return;
172         // FIXME UNICODE
173         frontend::Alert::error(_("Could not update TeX information"),
174                 bformat(_("The script `%s' failed."), from_utf8(command.absFilename())));
175 }
176
177
178 QStringList texFileList(QString const & filename)
179 {
180         QStringList list;
181         FileName const file = libFileSearch(QString(), filename);
182         if (file.empty())
183                 return list;
184
185         // FIXME Unicode.
186         vector<docstring> doclist = 
187                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
188
189         // Normalise paths like /foo//bar ==> /foo/bar
190         QSet<QString> set;
191         for (size_t i = 0; i != doclist.size(); ++i) {
192                 QString file = toqstr(doclist[i]);
193                 file.replace("\r", "");
194                 while (file.contains("//"))
195                         file.replace("//", "/");
196                 if (!file.isEmpty())
197                         set.insert(file);
198         }
199
200         // remove duplicates
201         return QList<QString>::fromSet(set);
202 }
203
204
205 QString internalPath(const QString & str)
206 {
207         return toqstr(os::internal_path(fromqstr(str)));
208 }
209
210
211 QString onlyFilename(const QString & str)
212 {
213         return toqstr(support::onlyFilename(fromqstr(str)));
214 }
215
216
217 QString onlyPath(const QString & str)
218 {
219         return toqstr(support::onlyPath(fromqstr(str)));
220 }
221
222
223 QString changeExtension(QString const & oldname, QString const & ext)
224 {
225         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
226 }
227
228 /// Remove the extension from \p name
229 QString removeExtension(QString const & name)
230 {
231         return toqstr(support::removeExtension(fromqstr(name)));
232 }
233
234 /** Add the extension \p ext to \p name.
235  Use this instead of changeExtension if you know that \p name is without
236  extension, because changeExtension would wrongly interpret \p name if it
237  contains a dot.
238  */
239 QString addExtension(QString const & name, QString const & ext)
240 {
241         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
242 }
243
244 /// Return the extension of the file (not including the .)
245 QString getExtension(QString const & name)
246 {
247         return toqstr(support::getExtension(fromqstr(name)));
248 }
249
250
251 /** Convert relative path into absolute path based on a basepath.
252   If relpath is absolute, just use that.
253   If basepath doesn't exist use CWD.
254   */
255 QString makeAbsPath(QString const & relpath, QString const & base)
256 {
257         return toqstr(support::makeAbsPath(fromqstr(relpath),
258                 fromqstr(base)).absFilename());
259 }
260
261
262 /////////////////////////////////////////////////////////////////////////
263 //
264 // FileFilterList
265 //
266 /////////////////////////////////////////////////////////////////////////
267
268 /** Given a string such as
269  *      "<glob> <glob> ... *.{abc,def} <glob>",
270  *  convert the csh-style brace expresions:
271  *      "<glob> <glob> ... *.abc *.def <glob>".
272  *  Requires no system support, so should work equally on Unix, Mac, Win32.
273  */
274 static string const convert_brace_glob(string const & glob)
275 {
276         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
277         // "abc,def,ghi" as group 2.
278         static boost::regex const glob_re(" *([^ {]*)\\{([^ }]+)\\}");
279         // Matches "abc" and "abc,", storing "abc" as group 1.
280         static boost::regex const block_re("([^,}]+),?");
281
282         string pattern;
283
284         string::const_iterator it = glob.begin();
285         string::const_iterator const end = glob.end();
286         while (true) {
287                 boost::match_results<string::const_iterator> what;
288                 if (!boost::regex_search(it, end, what, glob_re)) {
289                         // Ensure that no information is lost.
290                         pattern += string(it, end);
291                         break;
292                 }
293
294                 // Everything from the start of the input to
295                 // the start of the match.
296                 pattern += string(what[-1].first, what[-1].second);
297
298                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
299                 string const head = string(what[1].first, what[1].second);
300                 string const tail = string(what[2].first, what[2].second);
301
302                 // Split the ','-separated chunks of tail so that
303                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
304                 string const fmt = " " + head + "$1";
305                 pattern += boost::regex_merge(tail, block_re, fmt);
306
307                 // Increment the iterator to the end of the match.
308                 it += distance(it, what[0].second);
309         }
310
311         return pattern;
312 }
313
314
315 struct Filter
316 {
317         /* \param description text describing the filters.
318          * \param one or more wildcard patterns, separated by
319          * whitespace.
320          */
321         Filter(docstring const & description, std::string const & globs);
322
323         docstring const & description() const { return desc_; }
324
325         QString toString() const;
326
327         docstring desc_;
328         std::vector<std::string> globs_;
329 };
330
331
332 Filter::Filter(docstring const & description, string const & globs)
333         : desc_(description)
334 {
335         typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
336         boost::char_separator<char> const separator(" ");
337
338         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
339         //       "<glob> <glob> ... *.abc *.def <glob>"
340         string const expanded_globs = convert_brace_glob(globs);
341
342         // Split into individual globs.
343         vector<string> matches;
344         Tokenizer const tokens(expanded_globs, separator);
345         globs_ = vector<string>(tokens.begin(), tokens.end());
346 }
347
348
349 QString Filter::toString() const
350 {
351         QString s;
352
353         bool const has_description = desc_.empty();
354
355         if (has_description) {
356                 s += toqstr(desc_);
357                 s += " (";
358         }
359
360         for (size_t i = 0; i != globs_.size(); ++i) {
361                 if (i > 0)
362                         s += ' ';
363                 s += toqstr(globs_[i]);
364         }
365
366         if (has_description)
367                 s += ')';
368         return s;
369 }
370
371
372 /** \c FileFilterList parses a Qt-style list of available file filters
373  *  to generate the corresponding vector.
374  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
375  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
376  *  will result in a vector of size 1 in which the description field is empty.
377  */
378 struct FileFilterList
379 {
380         // FIXME UNICODE: globs_ should be unicode...
381         /** \param qt_style_filter a list of available file filters.
382          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
383          *  The "All files (*)" filter is always added to the list.
384          */
385         explicit FileFilterList(docstring const & qt_style_filter =
386                                 docstring());
387
388         typedef std::vector<Filter>::size_type size_type;
389
390         bool empty() const { return filters_.empty(); }
391         size_type size() const { return filters_.size(); }
392         Filter & operator[](size_type i) { return filters_[i]; }
393         Filter const & operator[](size_type i) const { return filters_[i]; }
394
395         void parse_filter(std::string const & filter);
396         std::vector<Filter> filters_;
397 };
398
399
400 FileFilterList::FileFilterList(docstring const & qt_style_filter)
401 {
402         // FIXME UNICODE
403         string const filter = to_utf8(qt_style_filter)
404                 + (qt_style_filter.empty() ? string() : ";;")
405                 + to_utf8(_("All Files "))
406 #if defined(_WIN32)             
407                 + ("(*.*)");
408 #else
409                 + ("(*)");
410 #endif
411
412         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
413         // into individual filters.
414         static boost::regex const separator_re(";;");
415
416         string::const_iterator it = filter.begin();
417         string::const_iterator const end = filter.end();
418         while (true) {
419                 boost::match_results<string::const_iterator> what;
420
421                 if (!boost::regex_search(it, end, what, separator_re)) {
422                         parse_filter(string(it, end));
423                         break;
424                 }
425
426                 // Everything from the start of the input to
427                 // the start of the match.
428                 parse_filter(string(what[-1].first, what[-1].second));
429
430                 // Increment the iterator to the end of the match.
431                 it += distance(it, what[0].second);
432         }
433 }
434
435
436 void FileFilterList::parse_filter(string const & filter)
437 {
438         // Matches "TeX documents (*.tex)",
439         // storing "TeX documents " as group 1 and "*.tex" as group 2.
440         static boost::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
441
442         boost::match_results<string::const_iterator> what;
443         if (!boost::regex_search(filter, what, filter_re)) {
444                 // Just a glob, no description.
445                 filters_.push_back(Filter(docstring(), trim(filter)));
446         } else {
447                 // FIXME UNICODE
448                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
449                 string const globs = string(what[2].first, what[2].second);
450                 filters_.push_back(Filter(trim(desc), trim(globs)));
451         }
452 }
453
454
455 /** \returns the equivalent of the string passed in
456  *  although any brace expressions are expanded.
457  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
458  */
459 QStringList fileFilters(QString const & desc)
460 {
461         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
462         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
463         FileFilterList filters(qstring_to_ucs4(desc));
464         //LYXERR0("DESC: " << desc);
465         QStringList list;
466         for (size_t i = 0; i != filters.filters_.size(); ++i) {
467                 QString f = filters.filters_[i].toString();
468                 //LYXERR0("FILTER: " << f);
469                 list.append(f);
470         }
471         return list;
472 }
473
474
475 QString guiName(string const & type, BufferParams const & bp)
476 {
477         if (type == "tableofcontents")
478                 return qt_("Table of Contents");
479         if (type == "child")
480                 return qt_("Child Documents");
481         if (type == "graphics")
482                 return qt_("List of Graphics");
483         if (type == "equation")
484                 return qt_("List of Equations");
485         if (type == "footnote")
486                 return qt_("List of Footnotes");
487         if (type == "listing")
488                 return qt_("List of Listings");
489         if (type == "index")
490                 return qt_("List of Indexes");
491         if (type == "marginalnote")
492                 return qt_("List of Marginal notes");
493         if (type == "note")
494                 return qt_("List of Notes");
495         if (type == "citation")
496                 return qt_("List of Citations");
497         if (type == "label")
498                 return qt_("Labels and References");
499         if (type == "branch")
500                 return qt_("List of Branches");
501
502         FloatList const & floats = bp.documentClass().floats();
503         if (floats.typeExist(type))
504                 return qt_(floats.getType(type).listName());
505
506         return qt_(type);
507 }
508
509
510 } // namespace lyx