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