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