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