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