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