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