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