]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/qt_helpers.cpp
Remove obsolete (and false) comment.
[lyx.git] / src / frontends / qt / 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 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
340         return QList<QString>(set.begin(), set.end());
341 #else
342         return QList<QString>::fromSet(set);
343 #endif
344 }
345
346 QString const externalLineEnding(docstring const & str)
347 {
348 #ifdef Q_OS_MAC
349         // The MAC clipboard uses \r for lineendings, and we use \n
350         return toqstr(subst(str, '\n', '\r'));
351 #elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
352         // Windows clipboard uses \r\n for lineendings, and we use \n
353         return toqstr(subst(str, from_ascii("\n"), from_ascii("\r\n")));
354 #else
355         return toqstr(str);
356 #endif
357 }
358
359
360 docstring const internalLineEnding(QString const & str)
361 {
362         docstring const s = subst(qstring_to_ucs4(str),
363                                   from_ascii("\r\n"), from_ascii("\n"));
364         return subst(s, '\r', '\n');
365 }
366
367
368 QString internalPath(const QString & str)
369 {
370         return toqstr(os::internal_path(fromqstr(str)));
371 }
372
373
374 QString onlyFileName(const QString & str)
375 {
376         return toqstr(support::onlyFileName(fromqstr(str)));
377 }
378
379
380 QString onlyPath(const QString & str)
381 {
382         return toqstr(support::onlyPath(fromqstr(str)));
383 }
384
385
386 QString changeExtension(QString const & oldname, QString const & ext)
387 {
388         return toqstr(support::changeExtension(fromqstr(oldname), fromqstr(ext)));
389 }
390
391 /// Remove the extension from \p name
392 QString removeExtension(QString const & name)
393 {
394         return toqstr(support::removeExtension(fromqstr(name)));
395 }
396
397 /** Add the extension \p ext to \p name.
398  Use this instead of changeExtension if you know that \p name is without
399  extension, because changeExtension would wrongly interpret \p name if it
400  contains a dot.
401  */
402 QString addExtension(QString const & name, QString const & ext)
403 {
404         return toqstr(support::addExtension(fromqstr(name), fromqstr(ext)));
405 }
406
407 /// Return the extension of the file (not including the .)
408 QString getExtension(QString const & name)
409 {
410         return toqstr(support::getExtension(fromqstr(name)));
411 }
412
413
414 /** Convert relative path into absolute path based on a basepath.
415   If relpath is absolute, just use that.
416   If basepath doesn't exist use CWD.
417   */
418 QString makeAbsPath(QString const & relpath, QString const & base)
419 {
420         return toqstr(support::makeAbsPath(fromqstr(relpath),
421                 fromqstr(base)).absFileName());
422 }
423
424
425 /////////////////////////////////////////////////////////////////////////
426 //
427 // FileFilterList
428 //
429 /////////////////////////////////////////////////////////////////////////
430
431 /** Given a string such as
432  *      "<glob> <glob> ... *.{abc,def} <glob>",
433  *  convert the csh-style brace expresions:
434  *      "<glob> <glob> ... *.abc *.def <glob>".
435  *  Requires no system support, so should work equally on Unix, Mac, Win32.
436  */
437 static string const convert_brace_glob(string const & glob)
438 {
439         // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
440         // "abc,def,ghi" as group 2, while allowing spaces in group 2.
441         static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
442         // Matches "abc" and "abc,", storing "abc" as group 1,
443         // while ignoring surrounding spaces.
444         static lyx::regex const block_re(" *([^ ,}]+) *,? *");
445
446         string pattern;
447
448         string::const_iterator it = glob.begin();
449         string::const_iterator const end = glob.end();
450         while (true) {
451                 match_results<string::const_iterator> what;
452                 if (!regex_search(it, end, what, glob_re)) {
453                         // Ensure that no information is lost.
454                         pattern += string(it, end);
455                         break;
456                 }
457
458                 // Everything from the start of the input to
459                 // the start of the match.
460                 pattern += string(what[-1].first, what[-1].second);
461
462                 // Given " *.{abc,def}", head == "*." and tail == "abc,def".
463                 string const head = string(what[1].first, what[1].second);
464                 string const tail = string(what[2].first, what[2].second);
465
466                 // Split the ','-separated chunks of tail so that
467                 // $head{$chunk1,$chunk2} becomes "$head$chunk1 $head$chunk2".
468                 string const fmt = " " + head + "$1";
469                 pattern += regex_replace(tail, block_re, fmt);
470
471                 // Increment the iterator to the end of the match.
472                 it += distance(it, what[0].second);
473         }
474
475         return pattern;
476 }
477
478
479 struct Filter
480 {
481         /* \param description text describing the filters.
482          * \param one or more wildcard patterns, separated by
483          * whitespace.
484          */
485         Filter(docstring const & description, std::string const & globs);
486
487         docstring const & description() const { return desc_; }
488
489         QString toString() const;
490
491         docstring desc_;
492         std::vector<std::string> globs_;
493 };
494
495
496 Filter::Filter(docstring const & description, string const & globs)
497         : desc_(description)
498 {
499         // Given "<glob> <glob> ... *.{abc,def} <glob>", expand to
500         //       "<glob> <glob> ... *.abc *.def <glob>"
501         string const expanded_globs = convert_brace_glob(globs);
502
503         // Split into individual globs.
504         globs_ = getVectorFromString(expanded_globs, " ");
505 }
506
507
508 QString Filter::toString() const
509 {
510         QString s;
511
512         bool const has_description = !desc_.empty();
513
514         if (has_description) {
515                 s += toqstr(desc_);
516                 s += " (";
517         }
518
519         s += toqstr(getStringFromVector(globs_, " "));
520
521         if (has_description)
522                 s += ')';
523         return s;
524 }
525
526
527 /** \c FileFilterList parses a Qt-style list of available file filters
528  *  to generate the corresponding vector.
529  *  For example "TeX documents (*.tex);;LyX Documents (*.lyx)"
530  *  will be parsed to fill a vector of size 2, whilst "*.{p[bgp]m} *.pdf"
531  *  will result in a vector of size 1 in which the description field is empty.
532  */
533 struct FileFilterList
534 {
535         // FIXME UNICODE: globs_ should be unicode...
536         /** \param qt_style_filter a list of available file filters.
537          *  Eg. "TeX documents (*.tex);;LyX Documents (*.lyx)".
538          *  The "All files (*)" filter is always added to the list.
539          */
540         explicit FileFilterList(docstring const & qt_style_filter =
541                                 docstring());
542
543         typedef std::vector<Filter>::size_type size_type;
544
545         bool empty() const { return filters_.empty(); }
546         size_type size() const { return filters_.size(); }
547         Filter & operator[](size_type i) { return filters_[i]; }
548         Filter const & operator[](size_type i) const { return filters_[i]; }
549
550         void parse_filter(std::string const & filter);
551         std::vector<Filter> filters_;
552 };
553
554
555 FileFilterList::FileFilterList(docstring const & qt_style_filter)
556 {
557         // FIXME UNICODE
558         string const filter = to_utf8(qt_style_filter)
559                 + (qt_style_filter.empty() ? string() : ";;")
560                 + to_utf8(_("All Files "))
561 #if defined(_WIN32)
562                 + ("(*.*)");
563 #else
564                 + ("(*)");
565 #endif
566
567         // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
568         // into individual filters.
569         static lyx::regex const separator_re(";;");
570
571         string::const_iterator it = filter.begin();
572         string::const_iterator const end = filter.end();
573         while (true) {
574                 match_results<string::const_iterator> what;
575
576                 if (!lyx::regex_search(it, end, what, separator_re)) {
577                         parse_filter(string(it, end));
578                         break;
579                 }
580
581                 // Everything from the start of the input to
582                 // the start of the match.
583                 parse_filter(string(it, what[0].first));
584
585                 // Increment the iterator to the end of the match.
586                 it += distance(it, what[0].second);
587         }
588 }
589
590
591 void FileFilterList::parse_filter(string const & filter)
592 {
593         // Matches "TeX documents (plain) (*.tex)",
594         // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
595         static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
596
597         match_results<string::const_iterator> what;
598         if (!lyx::regex_search(filter, what, filter_re)) {
599                 // Just a glob, no description.
600                 filters_.push_back(Filter(docstring(), trim(filter)));
601         } else {
602                 // FIXME UNICODE
603                 docstring const desc = from_utf8(string(what[1].first, what[1].second));
604                 string const globs = string(what[2].first, what[2].second);
605                 filters_.push_back(Filter(trim(desc), trim(globs)));
606         }
607 }
608
609
610 /** \returns the equivalent of the string passed in
611  *  although any brace expressions are expanded.
612  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
613  */
614 QStringList fileFilters(QString const & desc)
615 {
616         // we have: "*.{gif,png,jpg,bmp,pbm,ppm,tga,tif,xpm,xbm}"
617         // but need:  "*.cpp;*.cc;*.C;*.cxx;*.c++"
618         FileFilterList filters(qstring_to_ucs4(desc));
619         //LYXERR0("DESC: " << desc);
620         QStringList list;
621         for (size_t i = 0; i != filters.filters_.size(); ++i) {
622                 QString f = filters.filters_[i].toString();
623                 //LYXERR0("FILTER: " << f);
624                 list.append(f);
625         }
626         return list;
627 }
628
629
630 QString formatToolTip(QString text, int em)
631 {
632         // 1. QTooltip activates word wrapping only if mightBeRichText()
633         //    is true. So we convert the text to rich text.
634         //
635         // 2. The default width is way too small. Setting the width is tricky; first
636         //    one has to compute the ideal width, and then force it with special
637         //    html markup.
638
639         // do nothing if empty or already formatted
640         if (text.isEmpty() || text.startsWith(QString("<html>")))
641                 return text;
642         // Convert to rich text if it is not already
643         if (!Qt::mightBeRichText(text))
644                 text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
645         // Compute desired width in pixels
646         QFont const font = QToolTip::font();
647         int const px_width = em * QFontMetrics(font).width("M");
648         // Determine the ideal width of the tooltip
649         QTextDocument td("");
650         td.setHtml(text);
651         td.setDefaultFont(QToolTip::font());
652         td.setDocumentMargin(0);
653         td.setTextWidth(px_width);
654         double best_width = td.idealWidth();
655         // Set the line wrapping with appropriate width
656         return QString("<html><body><table><tr>"
657                        "<td align=justify width=%1>%2</td>"
658                        "</tr></table></body></html>")
659                 .arg(QString::number(int(best_width) + 1), text);
660 }
661
662
663 QString qtHtmlToPlainText(QString const & html)
664 {
665         if (!Qt::mightBeRichText(html))
666                 return html;
667         QTextDocument td;
668         td.setHtml(html);
669         return td.toPlainText();
670 }
671
672
673 } // namespace lyx