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