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