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