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