]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/qt_helpers.cpp
Support for "qualified citation lists"
[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/gettext.h"
31 #include "support/lstrings.h"
32 #include "support/lyxalgo.h"
33 #include "support/os.h"
34 #include "support/Package.h"
35 #include "support/PathChanger.h"
36 #include "support/Systemcall.h"
37
38 #include <QCheckBox>
39 #include <QComboBox>
40 #include <QLineEdit>
41 #include <QLocale>
42 #include <QPalette>
43 #include <QSet>
44 #include <QTextLayout>
45 #include <QTextDocument>
46 #include <QToolTip>
47
48 #include <algorithm>
49 #include <fstream>
50 #include <locale>
51
52 // for FileFilter.
53 // FIXME: Remove
54 #include "support/regex.h"
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 bool ColorSorter(ColorCode lhs, ColorCode rhs)
217 {
218         return compare_no_case(lcolor.getGUIName(lhs), lcolor.getGUIName(rhs)) < 0;
219 }
220
221
222 void setValid(QWidget * widget, bool valid)
223 {
224         if (valid) {
225                 widget->setPalette(QPalette());
226         } else {
227                 QPalette pal = widget->palette();
228                 pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
229                 widget->setPalette(pal);
230         }
231 }
232
233 /// wrapper to hide the change of method name to setSectionResizeMode
234 void setSectionResizeMode(QHeaderView * view,
235     int logicalIndex, QHeaderView::ResizeMode mode) {
236 #if (QT_VERSION >= 0x050000)
237         view->setSectionResizeMode(logicalIndex, mode);
238 #else
239         view->setResizeMode(logicalIndex, mode);
240 #endif
241 }
242
243 void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
244 #if (QT_VERSION >= 0x050000)
245         view->setSectionResizeMode(mode);
246 #else
247         view->setResizeMode(mode);
248 #endif
249 }
250 } // namespace frontend
251
252 QString const qt_(char const * str, const char *)
253 {
254         return toqstr(_(str));
255 }
256
257
258 QString const qt_(string const & str)
259 {
260         return toqstr(_(str));
261 }
262
263
264 QString const qt_(QString const & qstr)
265 {
266         return toqstr(_(fromqstr(qstr)));
267 }
268
269
270 void rescanTexStyles(string const & arg)
271 {
272         // Run rescan in user lyx directory
273         PathChanger p(package().user_support());
274         FileName const prog = support::libFileSearch("scripts", "TeXFiles.py");
275         Systemcall one;
276         string const command = os::python() + ' ' +
277             quoteName(prog.toFilesystemEncoding()) + ' ' +
278             arg;
279         int const status = one.startscript(Systemcall::Wait, command);
280         if (status == 0)
281                 return;
282         // FIXME UNICODE
283         frontend::Alert::error(_("Could not update TeX information"),
284                 bformat(_("The script `%1$s' failed."), from_utf8(prog.absFileName())));
285 }
286
287
288 QStringList texFileList(QString const & filename)
289 {
290         QStringList list;
291         FileName const file = libFileSearch(QString(), filename);
292         if (file.empty())
293                 return list;
294
295         // FIXME Unicode.
296         vector<docstring> doclist = 
297                 getVectorFromString(file.fileContents("UTF-8"), from_ascii("\n"));
298
299         // Normalise paths like /foo//bar ==> /foo/bar
300         QSet<QString> set;
301         for (size_t i = 0; i != doclist.size(); ++i) {
302                 QString file = toqstr(doclist[i]);
303                 file.replace("\r", "");
304                 while (file.contains("//"))
305                         file.replace("//", "/");
306                 if (!file.isEmpty())
307                         set.insert(file);
308         }
309
310         // remove duplicates
311         return QList<QString>::fromSet(set);
312 }
313
314 QString const externalLineEnding(docstring const & str)
315 {
316 #ifdef Q_OS_MAC
317         // The MAC clipboard uses \r for lineendings, and we use \n
318         return toqstr(subst(str, '\n', '\r'));
319 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
320         // Windows clipboard uses \r\n for lineendings, and we use \n
321         return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
322 #else
323         return toqstr(str);
324 #endif
325 }
326
327
328 docstring const internalLineEnding(QString const & str)
329 {
330         docstring const s = subst(qstring_to_ucs4(str), 
331                                   from_ascii("\r\n"), from_ascii("\n"));
332         return subst(s, '\r', '\n');
333 }
334
335
336 QString internalPath(const QString & str)
337 {
338         return toqstr(os::internal_path(fromqstr(str)));
339 }
340
341
342 QString onlyFileName(const QString & str)
343 {
344         return toqstr(support::onlyFileName(fromqstr(str)));
345 }
346
347
348 QString onlyPath(const QString & str)
349 {
350         return toqstr(support::onlyPath(fromqstr(str)));
351 }
352
353
354 QString changeExtension(QString const & oldname, QString const & ext)
355 {
356         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
357 }
358
359 /// Remove the extension from \p name
360 QString removeExtension(QString const & name)
361 {
362         return toqstr(support::removeExtension(fromqstr(name)));
363 }
364
365 /** Add the extension \p ext to \p name.
366  Use this instead of changeExtension if you know that \p name is without
367  extension, because changeExtension would wrongly interpret \p name if it
368  contains a dot.
369  */
370 QString addExtension(QString const & name, QString const & ext)
371 {
372         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
373 }
374
375 /// Return the extension of the file (not including the .)
376 QString getExtension(QString const & name)
377 {
378         return toqstr(support::getExtension(fromqstr(name)));
379 }
380
381
382 /** Convert relative path into absolute path based on a basepath.
383   If relpath is absolute, just use that.
384   If basepath doesn't exist use CWD.
385   */
386 QString makeAbsPath(QString const & relpath, QString const & base)
387 {
388         return toqstr(support::makeAbsPath(fromqstr(relpath),
389                 fromqstr(base)).absFileName());
390 }
391
392
393 /////////////////////////////////////////////////////////////////////////
394 //
395 // FileFilterList
396 //
397 /////////////////////////////////////////////////////////////////////////
398
399 /** Given a string such as
400  *      "<glob> <glob> ... *.{abc,def} <glob>",
401  *  convert the csh-style brace expresions:
402  *      "<glob> <glob> ... *.abc *.def <glob>".
403  *  Requires no system support, so should work equally on Unix, Mac, Win32.
404  */
405 static string const convert_brace_glob(string const & glob)
406 {
407         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
408         // "abc,def,ghi" as group 2, while allowing spaces in group 2.
409         static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
410         // Matches "abc" and "abc,", storing "abc" as group 1,
411         // while ignoring surrounding spaces.
412         static lyx::regex const block_re(" *([^ ,}]+) *,? *");
413
414         string pattern;
415
416         string::const_iterator it = glob.begin();
417         string::const_iterator const end = glob.end();
418         while (true) {
419                 match_results<string::const_iterator> what;
420                 if (!regex_search(it, end, what, glob_re)) {
421                         // Ensure that no information is lost.
422                         pattern += string(it, end);
423                         break;
424                 }
425
426                 // Everything from the start of the input to
427                 // the start of the match.
428                 pattern += string(what[-1].first, what[-1].second);
429
430                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
431                 string const head = string(what[1].first, what[1].second);
432                 string const tail = string(what[2].first, what[2].second);
433
434                 // Split the ','-separated chunks of tail so that
435                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
436                 string const fmt = " " + head + "$1";
437                 pattern += regex_replace(tail, block_re, fmt);
438
439                 // Increment the iterator to the end of the match.
440                 it += distance(it, what[0].second);
441         }
442
443         return pattern;
444 }
445
446
447 struct Filter
448 {
449         /* \param description text describing the filters.
450          * \param one or more wildcard patterns, separated by
451          * whitespace.
452          */
453         Filter(docstring const & description, std::string const & globs);
454
455         docstring const & description() const { return desc_; }
456
457         QString toString() const;
458
459         docstring desc_;
460         std::vector<std::string> globs_;
461 };
462
463
464 Filter::Filter(docstring const & description, string const & globs)
465         : desc_(description)
466 {
467         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
468         //       "<glob> <glob> ... *.abc *.def <glob>"
469         string const expanded_globs = convert_brace_glob(globs);
470
471         // Split into individual globs.
472         globs_ = getVectorFromString(expanded_globs, " ");
473 }
474
475
476 QString Filter::toString() const
477 {
478         QString s;
479
480         bool const has_description = !desc_.empty();
481
482         if (has_description) {
483                 s += toqstr(desc_);
484                 s += " (";
485         }
486
487         s += toqstr(getStringFromVector(globs_, " "));
488
489         if (has_description)
490                 s += ')';
491         return s;
492 }
493
494
495 /** \c FileFilterList parses a Qt-style list of available file filters
496  *  to generate the corresponding vector.
497  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
498  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
499  *  will result in a vector of size 1 in which the description field is empty.
500  */
501 struct FileFilterList
502 {
503         // FIXME UNICODE: globs_ should be unicode...
504         /** \param qt_style_filter a list of available file filters.
505          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
506          *  The "All files (*)" filter is always added to the list.
507          */
508         explicit FileFilterList(docstring const & qt_style_filter =
509                                 docstring());
510
511         typedef std::vector<Filter>::size_type size_type;
512
513         bool empty() const { return filters_.empty(); }
514         size_type size() const { return filters_.size(); }
515         Filter & operator[](size_type i) { return filters_[i]; }
516         Filter const & operator[](size_type i) const { return filters_[i]; }
517
518         void parse_filter(std::string const & filter);
519         std::vector<Filter> filters_;
520 };
521
522
523 FileFilterList::FileFilterList(docstring const & qt_style_filter)
524 {
525         // FIXME UNICODE
526         string const filter = to_utf8(qt_style_filter)
527                 + (qt_style_filter.empty() ? string() : ";;")
528                 + to_utf8(_("All Files "))
529 #if defined(_WIN32)             
530                 + ("(*.*)");
531 #else
532                 + ("(*)");
533 #endif
534
535         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
536         // into individual filters.
537         static lyx::regex const separator_re(";;");
538
539         string::const_iterator it = filter.begin();
540         string::const_iterator const end = filter.end();
541         while (true) {
542                 match_results<string::const_iterator> what;
543
544                 if (!lyx::regex_search(it, end, what, separator_re)) {
545                         parse_filter(string(it, end));
546                         break;
547                 }
548
549                 // Everything from the start of the input to
550                 // the start of the match.
551                 parse_filter(string(it, what[0].first));
552
553                 // Increment the iterator to the end of the match.
554                 it += distance(it, what[0].second);
555         }
556 }
557
558
559 void FileFilterList::parse_filter(string const & filter)
560 {
561         // Matches "TeX documents (plain) (*.tex)",
562         // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
563         static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
564
565         match_results<string::const_iterator> what;
566         if (!lyx::regex_search(filter, what, filter_re)) {
567                 // Just a glob, no description.
568                 filters_.push_back(Filter(docstring(), trim(filter)));
569         } else {
570                 // FIXME UNICODE
571                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
572                 string const globs = string(what[2].first, what[2].second);
573                 filters_.push_back(Filter(trim(desc), trim(globs)));
574         }
575 }
576
577
578 /** \returns the equivalent of the string passed in
579  *  although any brace expressions are expanded.
580  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
581  */
582 QStringList fileFilters(QString const & desc)
583 {
584         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
585         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
586         FileFilterList filters(qstring_to_ucs4(desc));
587         //LYXERR0("DESC: " << desc);
588         QStringList list;
589         for (size_t i = 0; i != filters.filters_.size(); ++i) {
590                 QString f = filters.filters_[i].toString();
591                 //LYXERR0("FILTER: " << f);
592                 list.append(f);
593         }
594         return list;
595 }
596
597
598 QString formatToolTip(QString text, int em)
599 {
600         // 1. QTooltip activates word wrapping only if mightBeRichText()
601         //    is true. So we convert the text to rich text.
602         //
603         // 2. The default width is way too small. Setting the width is tricky; first
604         //    one has to compute the ideal width, and then force it with special
605         //    html markup.
606
607         // do nothing if empty or already formatted
608         if (text.isEmpty() || text.startsWith(QString("<html>")))
609                 return text;
610         // Convert to rich text if it is not already
611         if (!Qt::mightBeRichText(text))
612                 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
613         // Compute desired width in pixels
614         QFont const font = QToolTip::font();
615         int const px_width = em * QFontMetrics(font).width("M");
616         // Determine the ideal width of the tooltip
617     QTextDocument td("");
618     td.setHtml(text);
619     td.setDefaultFont(QToolTip::font());
620     td.setTextWidth(px_width);
621     double best_width = td.idealWidth();
622         // Set the line wrapping with appropriate width
623         return QString("<html><body><table><tr>"
624                        "<td align=justify width=%1>%2</td>"
625                        "</tr></table></body></html>")
626                 .arg(QString::number(int(best_width) + 1), text);
627         return text;
628 }
629
630
631 QString qtHtmlToPlainText(QString const & html)
632 {
633         if (!Qt::mightBeRichText(html))
634                 return html;
635         QTextDocument td;
636         td.setHtml(html);
637         return td.toPlainText();
638 }
639
640
641 } // namespace lyx