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