]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/qt_helpers.cpp
Don't allow newline characters in preference (#5840).
[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 "BufferParams.h"
23 #include "FloatList.h"
24 #include "Language.h"
25 #include "Length.h"
26 #include "TextClass.h"
27
28 #include "support/convert.h"
29 #include "support/debug.h"
30 #include "support/filetools.h"
31 #include "support/foreach.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/lyxalgo.h"
35 #include "support/os.h"
36 #include "support/Package.h"
37 #include "support/Path.h"
38 #include "support/Systemcall.h"
39
40 #include <QCheckBox>
41 #include <QComboBox>
42 #include <QLineEdit>
43 #include <QLocale>
44 #include <QPalette>
45 #include <QSet>
46
47 #include <algorithm>
48 #include <fstream>
49 #include <locale>
50
51 // for FileFilter.
52 // FIXME: Remove
53 #include "support/regex.h"
54 #include <boost/tokenizer.hpp>
55
56
57 using namespace std;
58 using namespace lyx::support;
59
60 namespace lyx {
61
62 FileName libFileSearch(QString const & dir, QString const & name,
63                                 QString const & ext)
64 {
65         return support::libFileSearch(fromqstr(dir), fromqstr(name), fromqstr(ext));
66 }
67
68
69 FileName imageLibFileSearch(QString & dir, QString const & name,
70                                 QString const & ext)
71 {
72         string tmp = fromqstr(dir);
73         FileName fn = support::imageLibFileSearch(tmp, fromqstr(name), fromqstr(ext));
74         dir = toqstr(tmp);
75         return fn;
76 }
77
78
79 namespace frontend {
80
81 string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
82 {
83         QString const length = input->text();
84         if (length.isEmpty())
85                 return string();
86
87         // Don't return unit-from-choice if the input(field) contains a unit
88         if (isValidGlueLength(fromqstr(length)))
89                 return fromqstr(length);
90
91         Length::UNIT const unit = combo->currentLengthItem();
92
93         return Length(length.trimmed().toDouble(), unit).asString();
94 }
95
96
97 Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
98 {
99         QString const length = input->text();
100         if (length.isEmpty())
101                 return Length();
102
103         // don't return unit-from-choice if the input(field) contains a unit
104         if (isValidGlueLength(fromqstr(length)))
105                 return Length(fromqstr(length));
106
107         Length::UNIT unit = Length::UNIT_NONE;
108         QString const item = combo->currentText();
109         for (int i = 0; i < num_units; i++) {
110                 if (qt_(lyx::unit_name_gui[i]) == item) {
111                         unit = unitFromString(unit_name[i]);
112                         break;
113                 }
114         }
115
116         return Length(length.trimmed().toDouble(), unit);
117 }
118
119
120 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
121                      Length const & len, Length::UNIT /*defaultUnit*/)
122 {
123         combo->setCurrentItem(len.unit());
124         QLocale loc;
125         loc.setNumberOptions(QLocale::OmitGroupSeparator);
126         input->setText(loc.toString(Length(len).value()));
127 }
128
129
130 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
131         string const & len, Length::UNIT defaultUnit)
132 {
133         if (len.empty()) {
134                 // no length (UNIT_NONE)
135                 combo->setCurrentItem(defaultUnit);
136                 input->setText("");
137         } else if (!isValidLength(len) && !isStrDbl(len)) {
138                 // use input field only for gluelengths
139                 combo->setCurrentItem(defaultUnit);
140                 input->setText(toqstr(len));
141         } else {
142                 lengthToWidgets(input, combo, Length(len), defaultUnit);
143         }
144 }
145
146
147 void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
148         docstring const & len, Length::UNIT defaultUnit)
149 {
150         lengthToWidgets(input, combo, to_utf8(len), defaultUnit);
151 }
152
153
154 double widgetToDouble(QLineEdit const * input)
155 {
156         QString const text = input->text();
157         if (text.isEmpty())
158                 return 0.0;
159         
160         return text.trimmed().toDouble();
161 }
162
163
164 string widgetToDoubleStr(QLineEdit const * input)
165 {
166         QString const text = input->text();
167         if (text.isEmpty())
168                 return string();
169         
170         return convert<string>(text.trimmed().toDouble());
171 }
172
173
174 void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
175 {
176         QLocale loc;
177         loc.setNumberOptions(QLocale::OmitGroupSeparator);
178         input->setText(loc.toString(value, f, prec));
179 }
180
181
182 void doubleToWidget(QLineEdit * input, string const & value, char f, int prec)
183 {
184         doubleToWidget(input, convert<double>(value), f, prec);
185 }
186
187
188 void setValid(QWidget * widget, bool valid)
189 {
190         if (valid) {
191                 widget->setPalette(QPalette());
192         } else {
193                 QPalette pal = widget->palette();
194                 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
195                 widget->setPalette(pal);
196         }
197 }
198
199 } // namespace frontend
200
201 QString const qt_(char const * str, const char *)
202 {
203         return toqstr(_(str));
204 }
205
206
207 QString const qt_(string const & str)
208 {
209         return toqstr(_(str));
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 `%1$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 QString const externalLineEnding(docstring const & str)
257 {
258 #ifdef Q_WS_MACX
259         // The MAC clipboard uses \r for lineendings, and we use \n
260         return toqstr(subst(str, '\n', '\r'));
261 #elif defined(Q_WS_WIN)
262         // Windows clipboard uses \r\n for lineendings, and we use \n
263         return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
264 #else
265         return toqstr(str);
266 #endif
267 }
268
269
270 docstring const internalLineEnding(QString const & str)
271 {
272         docstring const s = subst(qstring_to_ucs4(str), 
273                                   from_ascii("\r\n"), from_ascii("\n"));
274         return subst(s, '\r', '\n');
275 }
276
277
278 QString internalPath(const QString & str)
279 {
280         return toqstr(os::internal_path(fromqstr(str)));
281 }
282
283
284 QString onlyFileName(const QString & str)
285 {
286         return toqstr(support::onlyFileName(fromqstr(str)));
287 }
288
289
290 QString onlyPath(const QString & str)
291 {
292         return toqstr(support::onlyPath(fromqstr(str)));
293 }
294
295
296 QString changeExtension(QString const & oldname, QString const & ext)
297 {
298         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
299 }
300
301 /// Remove the extension from \p name
302 QString removeExtension(QString const & name)
303 {
304         return toqstr(support::removeExtension(fromqstr(name)));
305 }
306
307 /** Add the extension \p ext to \p name.
308  Use this instead of changeExtension if you know that \p name is without
309  extension, because changeExtension would wrongly interpret \p name if it
310  contains a dot.
311  */
312 QString addExtension(QString const & name, QString const & ext)
313 {
314         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
315 }
316
317 /// Return the extension of the file (not including the .)
318 QString getExtension(QString const & name)
319 {
320         return toqstr(support::getExtension(fromqstr(name)));
321 }
322
323
324 /** Convert relative path into absolute path based on a basepath.
325   If relpath is absolute, just use that.
326   If basepath doesn't exist use CWD.
327   */
328 QString makeAbsPath(QString const & relpath, QString const & base)
329 {
330         return toqstr(support::makeAbsPath(fromqstr(relpath),
331                 fromqstr(base)).absFileName());
332 }
333
334
335 /////////////////////////////////////////////////////////////////////////
336 //
337 // FileFilterList
338 //
339 /////////////////////////////////////////////////////////////////////////
340
341 /** Given a string such as
342  *      "<glob> <glob> ... *.{abc,def} <glob>",
343  *  convert the csh-style brace expresions:
344  *      "<glob> <glob> ... *.abc *.def <glob>".
345  *  Requires no system support, so should work equally on Unix, Mac, Win32.
346  */
347 static string const convert_brace_glob(string const & glob)
348 {
349         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
350         // "abc,def,ghi" as group 2.
351         static lyx::regex const glob_re(" *([^ {]*)\\{([^ }]+)\\}");
352         // Matches "abc" and "abc,", storing "abc" as group 1.
353         static lyx::regex const block_re("([^,}]+),?");
354
355         string pattern;
356
357         string::const_iterator it = glob.begin();
358         string::const_iterator const end = glob.end();
359         while (true) {
360                 match_results<string::const_iterator> what;
361                 if (!regex_search(it, end, what, glob_re)) {
362                         // Ensure that no information is lost.
363                         pattern += string(it, end);
364                         break;
365                 }
366
367                 // Everything from the start of the input to
368                 // the start of the match.
369                 pattern += string(what[-1].first, what[-1].second);
370
371                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
372                 string const head = string(what[1].first, what[1].second);
373                 string const tail = string(what[2].first, what[2].second);
374
375                 // Split the ','-separated chunks of tail so that
376                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
377                 string const fmt = " " + head + "$1";
378                 pattern += regex_replace(tail, block_re, fmt);
379
380                 // Increment the iterator to the end of the match.
381                 it += distance(it, what[0].second);
382         }
383
384         return pattern;
385 }
386
387
388 struct Filter
389 {
390         /* \param description text describing the filters.
391          * \param one or more wildcard patterns, separated by
392          * whitespace.
393          */
394         Filter(docstring const & description, std::string const & globs);
395
396         docstring const & description() const { return desc_; }
397
398         QString toString() const;
399
400         docstring desc_;
401         std::vector<std::string> globs_;
402 };
403
404
405 Filter::Filter(docstring const & description, string const & globs)
406         : desc_(description)
407 {
408         typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
409         boost::char_separator<char> const separator(" ");
410
411         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
412         //       "<glob> <glob> ... *.abc *.def <glob>"
413         string const expanded_globs = convert_brace_glob(globs);
414
415         // Split into individual globs.
416         vector<string> matches;
417         Tokenizer const tokens(expanded_globs, separator);
418         globs_ = vector<string>(tokens.begin(), tokens.end());
419 }
420
421
422 QString Filter::toString() const
423 {
424         QString s;
425
426         bool const has_description = desc_.empty();
427
428         if (has_description) {
429                 s += toqstr(desc_);
430                 s += " (";
431         }
432
433         for (size_t i = 0; i != globs_.size(); ++i) {
434                 if (i > 0)
435                         s += ' ';
436                 s += toqstr(globs_[i]);
437         }
438
439         if (has_description)
440                 s += ')';
441         return s;
442 }
443
444
445 /** \c FileFilterList parses a Qt-style list of available file filters
446  *  to generate the corresponding vector.
447  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
448  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
449  *  will result in a vector of size 1 in which the description field is empty.
450  */
451 struct FileFilterList
452 {
453         // FIXME UNICODE: globs_ should be unicode...
454         /** \param qt_style_filter a list of available file filters.
455          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
456          *  The "All files (*)" filter is always added to the list.
457          */
458         explicit FileFilterList(docstring const & qt_style_filter =
459                                 docstring());
460
461         typedef std::vector<Filter>::size_type size_type;
462
463         bool empty() const { return filters_.empty(); }
464         size_type size() const { return filters_.size(); }
465         Filter & operator[](size_type i) { return filters_[i]; }
466         Filter const & operator[](size_type i) const { return filters_[i]; }
467
468         void parse_filter(std::string const & filter);
469         std::vector<Filter> filters_;
470 };
471
472
473 FileFilterList::FileFilterList(docstring const & qt_style_filter)
474 {
475         // FIXME UNICODE
476         string const filter = to_utf8(qt_style_filter)
477                 + (qt_style_filter.empty() ? string() : ";;")
478                 + to_utf8(_("All Files "))
479 #if defined(_WIN32)             
480                 + ("(*.*)");
481 #else
482                 + ("(*)");
483 #endif
484
485         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
486         // into individual filters.
487         static lyx::regex const separator_re(";;");
488
489         string::const_iterator it = filter.begin();
490         string::const_iterator const end = filter.end();
491         while (true) {
492                 match_results<string::const_iterator> what;
493
494                 if (!lyx::regex_search(it, end, what, separator_re)) {
495                         parse_filter(string(it, end));
496                         break;
497                 }
498
499                 // Everything from the start of the input to
500                 // the start of the match.
501                 parse_filter(string(what[-1].first, what[-1].second));
502
503                 // Increment the iterator to the end of the match.
504                 it += distance(it, what[0].second);
505         }
506 }
507
508
509 void FileFilterList::parse_filter(string const & filter)
510 {
511         // Matches "TeX documents (*.tex)",
512         // storing "TeX documents " as group 1 and "*.tex" as group 2.
513         static lyx::regex const filter_re("([^(]*)\\(([^)]+)\\) *$");
514
515         match_results<string::const_iterator> what;
516         if (!lyx::regex_search(filter, what, filter_re)) {
517                 // Just a glob, no description.
518                 filters_.push_back(Filter(docstring(), trim(filter)));
519         } else {
520                 // FIXME UNICODE
521                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
522                 string const globs = string(what[2].first, what[2].second);
523                 filters_.push_back(Filter(trim(desc), trim(globs)));
524         }
525 }
526
527
528 /** \returns the equivalent of the string passed in
529  *  although any brace expressions are expanded.
530  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
531  */
532 QStringList fileFilters(QString const & desc)
533 {
534         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
535         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
536         FileFilterList filters(qstring_to_ucs4(desc));
537         //LYXERR0("DESC: " << desc);
538         QStringList list;
539         for (size_t i = 0; i != filters.filters_.size(); ++i) {
540                 QString f = filters.filters_[i].toString();
541                 //LYXERR0("FILTER: " << f);
542                 list.append(f);
543         }
544         return list;
545 }
546
547
548 QString guiName(string const & type, BufferParams const & bp)
549 {
550         if (type == "tableofcontents")
551                 return qt_("Table of Contents");
552         if (type == "child")
553                 return qt_("Child Documents");
554         if (type == "graphics")
555                 return qt_("List of Graphics");
556         if (type == "equation")
557                 return qt_("List of Equations");
558         if (type == "footnote")
559                 return qt_("List of Footnotes");
560         if (type == "listing")
561                 return qt_("List of Listings");
562         if (type == "index")
563                 return qt_("List of Indexes");
564         if (type == "marginalnote")
565                 return qt_("List of Marginal notes");
566         if (type == "note")
567                 return qt_("List of Notes");
568         if (type == "citation")
569                 return qt_("List of Citations");
570         if (type == "label")
571                 return qt_("Labels and References");
572         if (type == "branch")
573                 return qt_("List of Branches");
574         if (type == "change")
575                 return qt_("List of Changes");
576
577         FloatList const & floats = bp.documentClass().floats();
578         if (floats.typeExist(type))
579                 return qt_(floats.getType(type).listName());
580
581         return qt_(type);
582 }
583
584
585 } // namespace lyx