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