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