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