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