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