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