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