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