]> git.lyx.org Git - lyx.git/blob - src/frontends/controllers/frontend_helpers.cpp
fix scrolling bug: 3320 and 3652, maybe not perfect
[lyx.git] / src / frontends / controllers / frontend_helpers.cpp
1 /**
2  * \file frontend_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 Angus Leeming
7  * \author Herbert Voß
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "frontend_helpers.h"
15
16 #include "Buffer.h"
17 #include "BufferParams.h"
18 #include "debug.h"
19 #include "gettext.h"
20 #include "Language.h"
21 #include "Length.h"
22
23 #include <boost/regex.hpp>
24
25 #include <algorithm>
26 #include <config.h>
27
28 #include "Color.h"
29
30 #include "frontends/FileDialog.h"
31 #include "frontends/alert.h"
32 #include "support/filetools.h"
33 #include "support/lstrings.h"
34 #include "support/Package.h"
35 #include "support/filetools.h"
36 #include "support/lstrings.h"
37 #include "support/lyxalgo.h"
38 #include "support/os.h"
39 #include "support/Package.h"
40 #include "support/Path.h"
41 #include "support/Systemcall.h"
42
43 #include <boost/cregex.hpp>
44 #include <fstream>
45 using std::string;
46 using std::vector;
47 using std::pair;
48 using std::endl;
49
50 namespace lyx {
51
52 using support::ascii_lowercase;
53 using support::bformat;
54 using support::compare_ascii_no_case;
55 using support::contains;
56 using support::getVectorFromString;
57 using support::ltrim;
58 using support::prefixIs;
59 using support::rtrim;
60 using support::split;
61 using support::subst;
62 using support::token;
63 using support::trim;
64
65 namespace biblio {
66
67 namespace {
68
69 vector<string> const init_possible_cite_commands()
70 {
71         char const * const pos[] = {
72                 "cite",
73                 "citet", "citep", "citealt", "citealp",
74                 "citeauthor", "citeyear", "citeyearpar",
75                 "citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
76                 "Citet",  "Citep",  "Citealt",  "Citealp",  "Citeauthor",
77                 "Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
78                 "fullcite",
79                 "footcite", "footcitet", "footcitep", "footcitealt",
80                 "footcitealp", "footciteauthor", "footciteyear",
81                 "footciteyearpar",
82                 "citefield",
83                 "citetitle",
84                 "cite*"
85         };
86         size_t const size_pos = sizeof(pos) / sizeof(pos[0]);
87
88         return vector<string>(pos, pos + size_pos);
89 }
90
91
92 vector<string> const & possible_cite_commands()
93 {
94         static vector<string> const pos = init_possible_cite_commands();
95         return pos;
96 }
97
98
99 bool is_possible_cite_command(string const & input)
100 {
101         vector<string> const & possibles = possible_cite_commands();
102         vector<string>::const_iterator const end = possibles.end();
103         return std::find(possibles.begin(), end, input) != end;
104 }
105
106
107 string const default_cite_command(CiteEngine engine)
108 {
109         string str;
110         switch (engine) {
111         case ENGINE_BASIC:
112                 str = "cite";
113                 break;
114         case ENGINE_NATBIB_AUTHORYEAR:
115                 str = "citet";
116                 break;
117         case ENGINE_NATBIB_NUMERICAL:
118                 str = "citep";
119                 break;
120         case ENGINE_JURABIB:
121                 str = "cite";
122                 break;
123         }
124         return str;
125 }
126
127
128 static const docstring TheBibliographyRef(from_ascii("TheBibliographyRef"));
129
130 } // namespace anon
131
132
133 string const asValidLatexCommand(string const & input,
134                                  CiteEngine const engine)
135 {
136         string const default_str = default_cite_command(engine);
137         if (!is_possible_cite_command(input))
138                 return default_str;
139
140         string output;
141         switch (engine) {
142         case ENGINE_BASIC:
143                 output = default_str;
144                 break;
145
146         case ENGINE_NATBIB_AUTHORYEAR:
147         case ENGINE_NATBIB_NUMERICAL:
148                 if (input == "cite" || input == "citefield" ||
149                     input == "citetitle" || input == "cite*")
150                         output = default_str;
151                 else if (prefixIs(input, "foot"))
152                         output = input.substr(4);
153                 else
154                         output = input;
155                 break;
156
157         case ENGINE_JURABIB: {
158                 // Jurabib does not support the 'uppercase' natbib style.
159                 if (input[0] == 'C')
160                         output = string(1, 'c') + input.substr(1);
161                 else
162                         output = input;
163
164                 // Jurabib does not support the 'full' natbib style.
165                 string::size_type const n = output.size() - 1;
166                 if (output != "cite*" && output[n] == '*')
167                         output = output.substr(0, n);
168
169                 break;
170         }
171         }
172
173         return output;
174 }
175
176
177 docstring const familyName(docstring const & name)
178 {
179         // Very simple parser
180         docstring fname = name;
181
182         // possible authorname combinations are:
183         // "Surname, FirstName"
184         // "Surname, F."
185         // "FirstName Surname"
186         // "F. Surname"
187         docstring::size_type idx = fname.find(',');
188         if (idx != docstring::npos)
189                 return ltrim(fname.substr(0, idx));
190         idx = fname.rfind('.');
191         if (idx != docstring::npos)
192                 fname = ltrim(fname.substr(idx + 1));
193         // test if we have a LaTeX Space in front
194         if (fname[0] == '\\')
195                 return fname.substr(2);
196
197         return rtrim(fname);
198 }
199
200
201 docstring const getAbbreviatedAuthor(InfoMap const & map, string const & key)
202 {
203         BOOST_ASSERT(!map.empty());
204
205         InfoMap::const_iterator it = map.find(key);
206         if (it == map.end())
207                 return docstring();
208         docstring const & data = it->second;
209
210         // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
211         docstring::size_type const pos = data.find(TheBibliographyRef);
212         if (pos != docstring::npos) {
213                 if (pos <= 2) {
214                         return docstring();
215                 }
216
217                 docstring const opt = trim(data.substr(0, pos - 1));
218                 if (opt.empty())
219                         return docstring();
220
221                 docstring authors;
222                 split(opt, authors, '(');
223                 return authors;
224         }
225
226         docstring author = parseBibTeX(data, "author");
227
228         if (author.empty())
229                 author = parseBibTeX(data, "editor");
230
231         if (author.empty()) {
232                 author = parseBibTeX(data, "key");
233                 if (author.empty())
234                         // FIXME UNICODE
235                         return from_utf8(key);
236                 return author;
237         }
238
239         vector<docstring> const authors = getVectorFromString(author, from_ascii(" and "));
240         if (authors.empty())
241                 return author;
242
243         if (authors.size() == 2)
244                 return bformat(_("%1$s and %2$s"),
245                         familyName(authors[0]), familyName(authors[1]));
246
247         if (authors.size() > 2)
248                 return bformat(_("%1$s et al."), familyName(authors[0]));
249
250         return familyName(authors[0]);
251 }
252
253
254 docstring const getYear(InfoMap const & map, string const & key)
255 {
256         BOOST_ASSERT(!map.empty());
257
258         InfoMap::const_iterator it = map.find(key);
259         if (it == map.end())
260                 return docstring();
261         docstring const & data = it->second;
262
263         // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
264         docstring::size_type const pos = data.find(TheBibliographyRef);
265         if (pos != docstring::npos) {
266                 if (pos <= 2) {
267                         return docstring();
268                 }
269
270                 docstring const opt =
271                         trim(data.substr(0, pos - 1));
272                 if (opt.empty())
273                         return docstring();
274
275                 docstring authors;
276                 docstring const tmp = split(opt, authors, '(');
277                 docstring year;
278                 split(tmp, year, ')');
279                 return year;
280
281         }
282
283         docstring year = parseBibTeX(data, "year");
284         if (year.empty())
285                 year = _("No year");
286
287         return year;
288 }
289
290
291 namespace {
292
293 // A functor for use with std::sort, leading to case insensitive sorting
294 class compareNoCase: public std::binary_function<string, string, bool>
295 {
296 public:
297         bool operator()(string const & s1, string const & s2) const {
298                 return compare_ascii_no_case(s1, s2) < 0;
299         }
300 };
301
302 } // namespace anon
303
304
305 vector<string> const getKeys(InfoMap const & map)
306 {
307         vector<string> bibkeys;
308         InfoMap::const_iterator it  = map.begin();
309         InfoMap::const_iterator end = map.end();
310         for (; it != end; ++it) {
311                 bibkeys.push_back(it->first);
312         }
313
314         std::sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
315         return bibkeys;
316 }
317
318
319 docstring const getInfo(InfoMap const & map, string const & key)
320 {
321         BOOST_ASSERT(!map.empty());
322
323         InfoMap::const_iterator it = map.find(key);
324         if (it == map.end())
325                 return docstring();
326         docstring const & data = it->second;
327
328         // is the entry a BibTeX one or one from lyx-layout "bibliography"?
329         docstring::size_type const pos = data.find(TheBibliographyRef);
330         if (pos != docstring::npos) {
331                 docstring::size_type const pos2 = pos + TheBibliographyRef.size();
332                 docstring const info = trim(data.substr(pos2));
333                 return info;
334         }
335
336         // Search for all possible "required" keys
337         docstring author = parseBibTeX(data, "author");
338         if (author.empty())
339                 author = parseBibTeX(data, "editor");
340
341         docstring year      = parseBibTeX(data, "year");
342         docstring title     = parseBibTeX(data, "title");
343         docstring booktitle = parseBibTeX(data, "booktitle");
344         docstring chapter   = parseBibTeX(data, "chapter");
345         docstring number    = parseBibTeX(data, "number");
346         docstring volume    = parseBibTeX(data, "volume");
347         docstring pages     = parseBibTeX(data, "pages");
348         docstring annote    = parseBibTeX(data, "annote");
349         docstring media     = parseBibTeX(data, "journal");
350         if (media.empty())
351                 media = parseBibTeX(data, "publisher");
352         if (media.empty())
353                 media = parseBibTeX(data, "school");
354         if (media.empty())
355                 media = parseBibTeX(data, "institution");
356
357         odocstringstream result;
358         if (!author.empty())
359                 result << author << ", ";
360         if (!title.empty())
361                 result << title;
362         if (!booktitle.empty())
363                 result << ", in " << booktitle;
364         if (!chapter.empty())
365                 result << ", Ch. " << chapter;
366         if (!media.empty())
367                 result << ", " << media;
368         if (!volume.empty())
369                 result << ", vol. " << volume;
370         if (!number.empty())
371                 result << ", no. " << number;
372         if (!pages.empty())
373                 result << ", pp. " << pages;
374         if (!year.empty())
375                 result << ", " << year;
376         if (!annote.empty())
377                 result << "\n\n" << annote;
378
379         docstring const result_str = rtrim(result.str());
380         if (!result_str.empty())
381                 return result_str;
382
383         // This should never happen (or at least be very unusual!)
384         return data;
385 }
386
387
388 namespace {
389
390 // Escape special chars.
391 // All characters are literals except: '.|*?+(){}[]^$\'
392 // These characters are literals when preceded by a "\", which is done here
393 // @todo: This function should be moved to support, and then the test in tests
394 //        should be moved there as well.
395 string const escape_special_chars(string const & expr)
396 {
397         // Search for all chars '.|*?+(){}[^$]\'
398         // Note that '[' and '\' must be escaped.
399         // This is a limitation of boost::regex, but all other chars in BREs
400         // are assumed literal.
401         boost::regex reg("[].|*?+(){}^$\\[\\\\]");
402
403         // $& is a perl-like expression that expands to all
404         // of the current match
405         // The '$' must be prefixed with the escape character '\' for
406         // boost to treat it as a literal.
407         // Thus, to prefix a matched expression with '\', we use:
408         return boost::regex_replace(expr, reg, "\\\\$&");
409 }
410
411
412 // A functor for use with std::find_if, used to ascertain whether a
413 // data entry matches the required regex_
414 // @throws: boost::regex_error if the supplied regex pattern is not valid
415 // @todo: This function should be moved to support.
416 class RegexMatch : public std::unary_function<string, bool>
417 {
418 public:
419         // re and icase are used to construct an instance of boost::RegEx.
420         // if icase is true, then matching is insensitive to case
421         RegexMatch(InfoMap const & m, string const & re, bool icase)
422                 : map_(m), regex_(re, icase) {}
423
424         bool operator()(string const & key) const {
425                 // the data searched is the key + its associated BibTeX/biblio
426                 // fields
427                 string data = key;
428                 InfoMap::const_iterator info = map_.find(key);
429                 if (info != map_.end())
430                         // FIXME UNICODE
431                         data += ' ' + to_utf8(info->second);
432
433                 // Attempts to find a match for the current RE
434                 // somewhere in data.
435                 return boost::regex_search(data, regex_);
436         }
437 private:
438         InfoMap const map_;
439         mutable boost::regex regex_;
440 };
441
442 } // namespace anon
443
444
445 vector<string>::const_iterator
446 searchKeys(InfoMap const & theMap,
447            vector<string> const & keys,
448            string const & search_expr,
449            vector<string>::const_iterator start,
450            Search type,
451            Direction dir,
452            bool caseSensitive)
453 {
454         // Preliminary checks
455         if (start < keys.begin() || start >= keys.end())
456                 return keys.end();
457
458         string expr = trim(search_expr);
459         if (expr.empty())
460                 return keys.end();
461
462         if (type == SIMPLE)
463                 // We must escape special chars in the search_expr so that
464                 // it is treated as a simple string by boost::regex.
465                 expr = escape_special_chars(expr);
466
467         try {
468                 // Build the functor that will be passed to find_if.
469                 RegexMatch const match(theMap, expr, !caseSensitive);
470
471                 // Search the vector of 'keys' from 'start' for one
472                 // that matches the predicate 'match'. Searching can
473                 // be forward or backward from start.
474                 if (dir == FORWARD)
475                         return std::find_if(start, keys.end(), match);
476
477                 vector<string>::const_reverse_iterator rit(start);
478                 vector<string>::const_reverse_iterator rend = keys.rend();
479                 rit = std::find_if(rit, rend, match);
480
481                 if (rit == rend)
482                         return keys.end();
483                 // This is correct and always safe.
484                 // (See Meyer's Effective STL, Item 28.)
485                 return (++rit).base();
486         }
487         catch (boost::regex_error &) {
488                 return keys.end();
489         }
490 }
491
492
493 docstring const parseBibTeX(docstring data, string const & findkey)
494 {
495         // at first we delete all characters right of '%' and
496         // replace tabs through a space and remove leading spaces
497         // we read the data line by line so that the \n are
498         // ignored, too.
499         docstring data_;
500         int Entries = 0;
501         docstring dummy = token(data,'\n', Entries);
502         while (!dummy.empty()) {
503                 // no tabs
504                 dummy = subst(dummy, '\t', ' ');
505                 // no leading spaces
506                 dummy = ltrim(dummy);
507                 // ignore lines with a beginning '%' or ignore all right of %
508                 docstring::size_type const idx =
509                         dummy.empty() ? docstring::npos : dummy.find('%');
510                 if (idx != docstring::npos)
511                         // Check if this is really a comment or just "\%"
512                         if (idx == 0 || dummy[idx - 1] != '\\')
513                                 dummy.erase(idx, docstring::npos);
514                         else
515                                 //  This is "\%", so just erase the '\'
516                                 dummy.erase(idx - 1, 1);
517                 // do we have a new token or a new line of
518                 // the same one? In the first case we ignore
519                 // the \n and in the second we replace it
520                 // with a space
521                 if (!dummy.empty()) {
522                         if (!contains(dummy, '='))
523                                 data_ += ' ' + dummy;
524                         else
525                                 data_ += dummy;
526                 }
527                 dummy = token(data, '\n', ++Entries);
528         }
529
530         // replace double commas with "" for easy scanning
531         data = subst(data_, from_ascii(",,"), from_ascii("\"\""));
532
533         // unlikely!
534         if (data.empty())
535                 return docstring();
536
537         // now get only the important line of the bibtex entry.
538         // all entries are devided by ',' except the last one.
539         data += ',';
540         // now we have same behaviour for all entries because the last one
541         // is "blah ... }"
542         Entries = 0;
543         bool found = false;
544         // parsing of title and booktitle is different from the
545         // others, because booktitle contains title
546         do {
547                 dummy = token(data, ',', Entries++);
548                 if (!dummy.empty()) {
549                         found = contains(ascii_lowercase(dummy), from_ascii(findkey));
550                         if (findkey == "title" &&
551                             contains(ascii_lowercase(dummy), from_ascii("booktitle")))
552                                 found = false;
553                 }
554         } while (!found && !dummy.empty());
555         if (dummy.empty())
556                 // no such keyword
557                 return docstring();
558
559         // we are not sure, if we get all, because "key= "blah, blah" is
560         // allowed.
561         // Therefore we read all until the next "=" character, which follows a
562         // new keyword
563         docstring keyvalue = dummy;
564         dummy = token(data, ',', Entries++);
565         while (!contains(dummy, '=') && !dummy.empty()) {
566                 keyvalue += ',' + dummy;
567                 dummy = token(data, ',', Entries++);
568         }
569
570         // replace double "" with originals ,, (two commas)
571         // leaving us with the all-important line
572         data = subst(keyvalue, from_ascii("\"\""), from_ascii(",,"));
573
574         // Clean-up.
575         // 1. Spaces
576         data = rtrim(data);
577         // 2. if there is no opening '{' then a closing '{' is probably cruft.
578         if (!contains(data, '{'))
579                 data = rtrim(data, "}");
580         // happens, when last keyword
581         docstring::size_type const idx =
582                 !data.empty() ? data.find('=') : docstring::npos;
583
584         if (idx == docstring::npos)
585                 return docstring();
586
587         data = trim(data.substr(idx));
588
589         // a valid entry?
590         if (data.length() < 2 || data[0] != '=')
591                 return docstring();
592         else {
593                 // delete '=' and the following spaces
594                 data = ltrim(data, " =");
595                 if (data.length() < 2) {
596                         // not long enough to find delimiters
597                         return data;
598                 } else {
599                         docstring::size_type keypos = 1;
600                         char_type enclosing;
601                         if (data[0] == '{') {
602                                 enclosing = '}';
603                         } else if (data[0] == '"') {
604                                 enclosing = '"';
605                         } else {
606                                 // no {} and no "", pure data but with a
607                                 // possible ',' at the end
608                                 return rtrim(data, ",");
609                         }
610                         docstring tmp = data.substr(keypos);
611                         while (tmp.find('{') != docstring::npos &&
612                                tmp.find('}') != docstring::npos &&
613                                tmp.find('{') < tmp.find('}') &&
614                                tmp.find('{') < tmp.find(enclosing)) {
615
616                                 keypos += tmp.find('{') + 1;
617                                 tmp = data.substr(keypos);
618                                 keypos += tmp.find('}') + 1;
619                                 tmp = data.substr(keypos);
620                         }
621                         if (tmp.find(enclosing) == docstring::npos)
622                                 return data;
623                         else {
624                                 keypos += tmp.find(enclosing);
625                                 return data.substr(1, keypos - 1);
626                         }
627                 }
628         }
629 }
630
631
632 namespace {
633
634
635 char const * const citeCommands[] = {
636         "cite", "citet", "citep", "citealt", "citealp", "citeauthor",
637         "citeyear", "citeyearpar" };
638
639 unsigned int const nCiteCommands =
640         sizeof(citeCommands) / sizeof(char *);
641
642 CiteStyle const citeStyles[] = {
643         CITE, CITET, CITEP, CITEALT, CITEALP,
644         CITEAUTHOR, CITEYEAR, CITEYEARPAR };
645
646 unsigned int const nCiteStyles =
647         sizeof(citeStyles) / sizeof(CiteStyle);
648
649 CiteStyle const citeStylesFull[] = {
650         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
651
652 unsigned int const nCiteStylesFull =
653         sizeof(citeStylesFull) / sizeof(CiteStyle);
654
655 CiteStyle const citeStylesUCase[] = {
656         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
657
658 unsigned int const nCiteStylesUCase =
659         sizeof(citeStylesUCase) / sizeof(CiteStyle);
660
661 } // namespace anon
662
663
664 CitationStyle::CitationStyle(string const & command)
665         : style(CITE), full(false), forceUCase(false)
666 {
667         if (command.empty())
668                 return;
669
670         string cmd = command;
671         if (cmd[0] == 'C') {
672                 forceUCase = true;
673                 cmd[0] = 'c';
674         }
675
676         string::size_type const n = cmd.size() - 1;
677         if (cmd != "cite" && cmd[n] == '*') {
678                 full = true;
679                 cmd = cmd.substr(0,n);
680         }
681
682         char const * const * const last = citeCommands + nCiteCommands;
683         char const * const * const ptr = std::find(citeCommands, last, cmd);
684
685         if (ptr != last) {
686                 size_t idx = ptr - citeCommands;
687                 style = citeStyles[idx];
688         }
689 }
690
691
692 string const CitationStyle::asLatexStr() const
693 {
694         string cite = citeCommands[style];
695         if (full) {
696                 CiteStyle const * last = citeStylesFull + nCiteStylesFull;
697                 if (std::find(citeStylesFull, last, style) != last)
698                         cite += '*';
699         }
700
701         if (forceUCase) {
702                 CiteStyle const * last = citeStylesUCase + nCiteStylesUCase;
703                 if (std::find(citeStylesUCase, last, style) != last)
704                         cite[0] = 'C';
705         }
706
707         return cite;
708 }
709
710
711 vector<CiteStyle> const getCiteStyles(CiteEngine const engine)
712 {
713         unsigned int nStyles = 0;
714         unsigned int start = 0;
715
716         switch (engine) {
717         case ENGINE_BASIC:
718                 nStyles = 1;
719                 start = 0;
720                 break;
721         case ENGINE_NATBIB_AUTHORYEAR:
722         case ENGINE_NATBIB_NUMERICAL:
723                 nStyles = nCiteStyles - 1;
724                 start = 1;
725                 break;
726         case ENGINE_JURABIB:
727                 nStyles = nCiteStyles;
728                 start = 0;
729                 break;
730         }
731
732         typedef vector<CiteStyle> cite_vec;
733
734         cite_vec styles(nStyles);
735         cite_vec::size_type i = 0;
736         int j = start;
737         for (; i != styles.size(); ++i, ++j)
738                 styles[i] = citeStyles[j];
739
740         return styles;
741 }
742
743
744 vector<docstring> const
745 getNumericalStrings(string const & key,
746                     InfoMap const & map, vector<CiteStyle> const & styles)
747 {
748         if (map.empty())
749                 return vector<docstring>();
750
751         docstring const author = getAbbreviatedAuthor(map, key);
752         docstring const year   = getYear(map, key);
753         if (author.empty() || year.empty())
754                 return vector<docstring>();
755
756         vector<docstring> vec(styles.size());
757         for (vector<docstring>::size_type i = 0; i != vec.size(); ++i) {
758                 docstring str;
759
760                 switch (styles[i]) {
761                 case CITE:
762                 case CITEP:
763                         str = from_ascii("[#ID]");
764                         break;
765
766                 case CITET:
767                         str = author + " [#ID]";
768                         break;
769
770                 case CITEALT:
771                         str = author + " #ID";
772                         break;
773
774                 case CITEALP:
775                         str = from_ascii("#ID");
776                         break;
777
778                 case CITEAUTHOR:
779                         str = author;
780                         break;
781
782                 case CITEYEAR:
783                         str = year;
784                         break;
785
786                 case CITEYEARPAR:
787                         str = '(' + year + ')';
788                         break;
789                 }
790
791                 vec[i] = str;
792         }
793
794         return vec;
795 }
796
797
798 vector<docstring> const
799 getAuthorYearStrings(string const & key,
800                     InfoMap const & map, vector<CiteStyle> const & styles)
801 {
802         if (map.empty())
803                 return vector<docstring>();
804
805         docstring const author = getAbbreviatedAuthor(map, key);
806         docstring const year   = getYear(map, key);
807         if (author.empty() || year.empty())
808                 return vector<docstring>();
809
810         vector<docstring> vec(styles.size());
811         for (vector<docstring>::size_type i = 0; i != vec.size(); ++i) {
812                 docstring str;
813
814                 switch (styles[i]) {
815                 case CITE:
816                         // jurabib only: Author/Annotator
817                         // (i.e. the "before" field, 2nd opt arg)
818                         str = author + "/<" + _("before") + '>';
819                         break;
820
821                 case CITET:
822                         str = author + " (" + year + ')';
823                         break;
824
825                 case CITEP:
826                         str = '(' + author + ", " + year + ')';
827                         break;
828
829                 case CITEALT:
830                         str = author + ' ' + year ;
831                         break;
832
833                 case CITEALP:
834                         str = author + ", " + year ;
835                         break;
836
837                 case CITEAUTHOR:
838                         str = author;
839                         break;
840
841                 case CITEYEAR:
842                         str = year;
843                         break;
844
845                 case CITEYEARPAR:
846                         str = '(' + year + ')';
847                         break;
848                 }
849
850                 vec[i] = str;
851         }
852
853         return vec;
854 }
855
856 } // namespace biblio
857
858 namespace frontend {
859
860 vector<FamilyPair> const getFamilyData()
861 {
862         vector<FamilyPair> family(5);
863
864         FamilyPair pr;
865
866         pr.first = _("No change");
867         pr.second = Font::IGNORE_FAMILY;
868         family[0] = pr;
869
870         pr.first = _("Roman");
871         pr.second = Font::ROMAN_FAMILY;
872         family[1] = pr;
873
874         pr.first = _("Sans Serif");
875         pr.second = Font::SANS_FAMILY;
876         family[2] = pr;
877
878         pr.first = _("Typewriter");
879         pr.second = Font::TYPEWRITER_FAMILY;
880         family[3] = pr;
881
882         pr.first = _("Reset");
883         pr.second = Font::INHERIT_FAMILY;
884         family[4] = pr;
885
886         return family;
887 }
888
889
890 vector<SeriesPair> const getSeriesData()
891 {
892         vector<SeriesPair> series(4);
893
894         SeriesPair pr;
895
896         pr.first = _("No change");
897         pr.second = Font::IGNORE_SERIES;
898         series[0] = pr;
899
900         pr.first = _("Medium");
901         pr.second = Font::MEDIUM_SERIES;
902         series[1] = pr;
903
904         pr.first = _("Bold");
905         pr.second = Font::BOLD_SERIES;
906         series[2] = pr;
907
908         pr.first = _("Reset");
909         pr.second = Font::INHERIT_SERIES;
910         series[3] = pr;
911
912         return series;
913 }
914
915
916 vector<ShapePair> const getShapeData()
917 {
918         vector<ShapePair> shape(6);
919
920         ShapePair pr;
921
922         pr.first = _("No change");
923         pr.second = Font::IGNORE_SHAPE;
924         shape[0] = pr;
925
926         pr.first = _("Upright");
927         pr.second = Font::UP_SHAPE;
928         shape[1] = pr;
929
930         pr.first = _("Italic");
931         pr.second = Font::ITALIC_SHAPE;
932         shape[2] = pr;
933
934         pr.first = _("Slanted");
935         pr.second = Font::SLANTED_SHAPE;
936         shape[3] = pr;
937
938         pr.first = _("Small Caps");
939         pr.second = Font::SMALLCAPS_SHAPE;
940         shape[4] = pr;
941
942         pr.first = _("Reset");
943         pr.second = Font::INHERIT_SHAPE;
944         shape[5] = pr;
945
946         return shape;
947 }
948
949
950 vector<SizePair> const getSizeData()
951 {
952         vector<SizePair> size(14);
953
954         SizePair pr;
955
956         pr.first = _("No change");
957         pr.second = Font::IGNORE_SIZE;
958         size[0] = pr;
959
960         pr.first = _("Tiny");
961         pr.second = Font::SIZE_TINY;
962         size[1] = pr;
963
964         pr.first = _("Smallest");
965         pr.second = Font::SIZE_SCRIPT;
966         size[2] = pr;
967
968         pr.first = _("Smaller");
969         pr.second = Font::SIZE_FOOTNOTE;
970         size[3] = pr;
971
972         pr.first = _("Small");
973         pr.second = Font::SIZE_SMALL;
974         size[4] = pr;
975
976         pr.first = _("Normal");
977         pr.second = Font::SIZE_NORMAL;
978         size[5] = pr;
979
980         pr.first = _("Large");
981         pr.second = Font::SIZE_LARGE;
982         size[6] = pr;
983
984         pr.first = _("Larger");
985         pr.second = Font::SIZE_LARGER;
986         size[7] = pr;
987
988         pr.first = _("Largest");
989         pr.second = Font::SIZE_LARGEST;
990         size[8] = pr;
991
992         pr.first = _("Huge");
993         pr.second = Font::SIZE_HUGE;
994         size[9] = pr;
995
996         pr.first = _("Huger");
997         pr.second = Font::SIZE_HUGER;
998         size[10] = pr;
999
1000         pr.first = _("Increase");
1001         pr.second = Font::INCREASE_SIZE;
1002         size[11] = pr;
1003
1004         pr.first = _("Decrease");
1005         pr.second = Font::DECREASE_SIZE;
1006         size[12] = pr;
1007
1008         pr.first = _("Reset");
1009         pr.second = Font::INHERIT_SIZE;
1010         size[13] = pr;
1011
1012         return size;
1013 }
1014
1015
1016 vector<BarPair> const getBarData()
1017 {
1018         vector<BarPair> bar(5);
1019
1020         BarPair pr;
1021
1022         pr.first = _("No change");
1023         pr.second = IGNORE;
1024         bar[0] = pr;
1025
1026         pr.first = _("Emph");
1027         pr.second = EMPH_TOGGLE;
1028         bar[1] = pr;
1029
1030         pr.first = _("Underbar");
1031         pr.second = UNDERBAR_TOGGLE;
1032         bar[2] = pr;
1033
1034         pr.first = _("Noun");
1035         pr.second = NOUN_TOGGLE;
1036         bar[3] = pr;
1037
1038         pr.first = _("Reset");
1039         pr.second = INHERIT;
1040         bar[4] = pr;
1041
1042         return bar;
1043 }
1044
1045
1046 vector<ColorPair> const getColorData()
1047 {
1048         vector<ColorPair> color(11);
1049
1050         ColorPair pr;
1051
1052         pr.first = _("No change");
1053         pr.second = Color::ignore;
1054         color[0] = pr;
1055
1056         pr.first = _("No color");
1057         pr.second = Color::none;
1058         color[1] = pr;
1059
1060         pr.first = _("Black");
1061         pr.second = Color::black;
1062         color[2] = pr;
1063
1064         pr.first = _("White");
1065         pr.second = Color::white;
1066         color[3] = pr;
1067
1068         pr.first = _("Red");
1069         pr.second = Color::red;
1070         color[4] = pr;
1071
1072         pr.first = _("Green");
1073         pr.second = Color::green;
1074         color[5] = pr;
1075
1076         pr.first = _("Blue");
1077         pr.second = Color::blue;
1078         color[6] = pr;
1079
1080         pr.first = _("Cyan");
1081         pr.second = Color::cyan;
1082         color[7] = pr;
1083
1084         pr.first = _("Magenta");
1085         pr.second = Color::magenta;
1086         color[8] = pr;
1087
1088         pr.first = _("Yellow");
1089         pr.second = Color::yellow;
1090         color[9] = pr;
1091
1092         pr.first = _("Reset");
1093         pr.second = Color::inherit;
1094         color[10] = pr;
1095
1096         return color;
1097 }
1098
1099
1100
1101 namespace {
1102
1103 class Sorter
1104         : public std::binary_function<LanguagePair,
1105                                       LanguagePair, bool>
1106 {
1107 public:
1108         bool operator()(LanguagePair const & lhs,
1109                         LanguagePair const & rhs) const {
1110                 return lhs.first < rhs.first;
1111         }
1112 };
1113
1114 } // namespace anon
1115
1116
1117 vector<LanguagePair> const getLanguageData(bool character_dlg)
1118 {
1119         vector<LanguagePair>::size_type const size = character_dlg ?
1120                 languages.size() + 2 : languages.size();
1121
1122         vector<LanguagePair> langs(size);
1123
1124         if (character_dlg) {
1125                 langs[0].first = _("No change");
1126                 langs[0].second = "ignore";
1127                 langs[1].first = _("Reset");
1128                 langs[1].second = "reset";
1129         }
1130
1131         vector<string>::size_type i = character_dlg ? 2 : 0;
1132         for (Languages::const_iterator cit = languages.begin();
1133              cit != languages.end(); ++cit) {
1134                 langs[i].first  = _(cit->second.display());
1135                 langs[i].second = cit->second.lang();
1136                 ++i;
1137         }
1138
1139         // Don't sort "ignore" and "reset"
1140         vector<LanguagePair>::iterator begin = character_dlg ?
1141                 langs.begin() + 2 : langs.begin();
1142
1143         std::sort(begin, langs.end(), Sorter());
1144
1145         return langs;
1146 }
1147
1148 } // namespace frontend
1149
1150 using support::addName;
1151 using support::FileFilterList;
1152 using support::getExtension;
1153 using support::libFileSearch;
1154 using support::makeAbsPath;
1155 using support::makeRelPath;
1156 using support::onlyFilename;
1157 using support::onlyPath;
1158 using support::package;
1159 using support::prefixIs;
1160 using support::removeExtension;
1161
1162 namespace frontend {
1163
1164
1165 docstring const browseFile(docstring const & filename,
1166                         docstring const & title,
1167                         FileFilterList const & filters,
1168                         bool save,
1169                         pair<docstring,docstring> const & dir1,
1170                         pair<docstring,docstring> const & dir2)
1171 {
1172         docstring lastPath = from_ascii(".");
1173         if (!filename.empty())
1174                 lastPath = from_utf8(onlyPath(to_utf8(filename)));
1175
1176         FileDialog fileDlg(title, LFUN_SELECT_FILE_SYNC, dir1, dir2);
1177
1178         FileDialog::Result result;
1179
1180         if (save)
1181                 result = fileDlg.save(lastPath, filters,
1182                                       from_utf8(onlyFilename(to_utf8(filename))));
1183         else
1184                 result = fileDlg.open(lastPath, filters,
1185                                       from_utf8(onlyFilename(to_utf8(filename))));
1186
1187         return result.second;
1188 }
1189
1190
1191 docstring const browseRelFile(docstring const & filename,
1192                            docstring const & refpath,
1193                            docstring const & title,
1194                            FileFilterList const & filters,
1195                            bool save,
1196                            pair<docstring,docstring> const & dir1,
1197                            pair<docstring,docstring> const & dir2)
1198 {
1199         docstring const fname = from_utf8(makeAbsPath(
1200                 to_utf8(filename), to_utf8(refpath)).absFilename());
1201
1202         docstring const outname = browseFile(fname, title, filters, save,
1203                                           dir1, dir2);
1204         docstring const reloutname = makeRelPath(outname, refpath);
1205         if (prefixIs(reloutname, from_ascii("../")))
1206                 return outname;
1207         else
1208                 return reloutname;
1209 }
1210
1211
1212
1213 docstring const browseLibFile(docstring const & dir,
1214                            docstring const & name,
1215                            docstring const & ext,
1216                            docstring const & title,
1217                            FileFilterList const & filters)
1218 {
1219         // FIXME UNICODE
1220         pair<docstring, docstring> const dir1(_("System files|#S#s"),
1221                                        from_utf8(addName(package().system_support().absFilename(), to_utf8(dir))));
1222
1223         pair<docstring, docstring> const dir2(_("User files|#U#u"),
1224                                        from_utf8(addName(package().user_support().absFilename(), to_utf8(dir))));
1225
1226         docstring const result = browseFile(from_utf8(
1227                 libFileSearch(to_utf8(dir), to_utf8(name), to_utf8(ext)).absFilename()),
1228                 title, filters, false, dir1, dir2);
1229
1230         // remove the extension if it is the default one
1231         docstring noextresult;
1232         if (from_utf8(getExtension(to_utf8(result))) == ext)
1233                 noextresult = from_utf8(removeExtension(to_utf8(result)));
1234         else
1235                 noextresult = result;
1236
1237         // remove the directory, if it is the default one
1238         docstring const file = from_utf8(onlyFilename(to_utf8(noextresult)));
1239         if (from_utf8(libFileSearch(to_utf8(dir), to_utf8(file), to_utf8(ext)).absFilename()) == result)
1240                 return file;
1241         else
1242                 return noextresult;
1243 }
1244
1245
1246 docstring const browseDir(docstring const & pathname,
1247                        docstring const & title,
1248                        pair<docstring,docstring> const & dir1,
1249                        pair<docstring,docstring> const & dir2)
1250 {
1251         docstring lastPath = from_ascii(".");
1252         if (!pathname.empty())
1253                 lastPath = from_utf8(onlyPath(to_utf8(pathname)));
1254
1255         FileDialog fileDlg(title, LFUN_SELECT_FILE_SYNC, dir1, dir2);
1256
1257         FileDialog::Result const result =
1258                 fileDlg.opendir(lastPath, from_utf8(onlyFilename(to_utf8(pathname))));
1259
1260         return result.second;
1261 }
1262
1263
1264 vector<docstring> const getLatexUnits()
1265 {
1266         vector<docstring> units;
1267         int i = 0;
1268         char const * str = stringFromUnit(i);
1269         for (; str != 0; ++i, str = stringFromUnit(i))
1270                 units.push_back(from_ascii(str));
1271
1272         return units;
1273 }
1274
1275 } // namespace frontend
1276
1277
1278 using support::bformat;
1279 using support::contains;
1280 using support::FileName;
1281 using support::getExtension;
1282 using support::getFileContents;
1283 using support::getVectorFromString;
1284 using support::libFileSearch;
1285 using support::onlyFilename;
1286 using support::package;
1287 using support::quoteName;
1288 using support::split;
1289 using support::Systemcall;
1290 using support::token;
1291
1292 namespace frontend {
1293
1294 void rescanTexStyles()
1295 {
1296         // Run rescan in user lyx directory
1297         support::Path p(package().user_support());
1298         FileName const command = libFileSearch("scripts", "TeXFiles.py");
1299         Systemcall one;
1300         int const status = one.startscript(Systemcall::Wait,
1301                         lyx::support::os::python() + ' ' +
1302                         quoteName(command.toFilesystemEncoding()));
1303         if (status == 0)
1304                 return;
1305         // FIXME UNICODE
1306         Alert::error(_("Could not update TeX information"),
1307                      bformat(_("The script `%s' failed."), lyx::from_utf8(command.absFilename())));
1308 }
1309
1310
1311 void texhash()
1312 {
1313         // Run texhash in user lyx directory
1314         support::Path p(package().user_support());
1315
1316         //path to texhash through system
1317         Systemcall one;
1318         one.startscript(Systemcall::Wait,"texhash");
1319 }
1320
1321
1322 void getTexFileList(string const & filename, std::vector<string> & list)
1323 {
1324         list.clear();
1325         FileName const file = libFileSearch("", filename);
1326         if (file.empty())
1327                 return;
1328
1329         list = getVectorFromString(getFileContents(file), "\n");
1330
1331         // Normalise paths like /foo//bar ==> /foo/bar
1332         boost::RegEx regex("/{2,}");
1333         std::vector<string>::iterator it  = list.begin();
1334         std::vector<string>::iterator end = list.end();
1335         for (; it != end; ++it) {
1336                 *it = regex.Merge((*it), "/");
1337         }
1338
1339         // remove empty items and duplicates
1340         list.erase(std::remove(list.begin(), list.end(), ""), list.end());
1341         eliminate_duplicates(list);
1342 }
1343
1344
1345 string const getListOfOptions(string const & classname, string const & type)
1346 {
1347         FileName const filename(getTexFileFromList(classname, type));
1348         if (filename.empty())
1349                 return string();
1350         string optionList = string();
1351         std::ifstream is(filename.toFilesystemEncoding().c_str());
1352         while (is) {
1353                 string s;
1354                 is >> s;
1355                 if (contains(s,"DeclareOption")) {
1356                         s = s.substr(s.find("DeclareOption"));
1357                         s = split(s,'{');               // cut front
1358                         s = token(s,'}',0);             // cut end
1359                         optionList += (s + '\n');
1360                 }
1361         }
1362         return optionList;
1363 }
1364
1365
1366 string const getTexFileFromList(string const & file,
1367                             string const & type)
1368 {
1369         string file_ = file;
1370         // do we need to add the suffix?
1371         if (!(getExtension(file) == type))
1372                 file_ += '.' + type;
1373
1374         lyxerr << "Searching for file " << file_ << endl;
1375
1376         string lstfile;
1377         if (type == "cls")
1378                 lstfile = "clsFiles.lst";
1379         else if (type == "sty")
1380                 lstfile = "styFiles.lst";
1381         else if (type == "bst")
1382                 lstfile = "bstFiles.lst";
1383         else if (type == "bib")
1384                 lstfile = "bibFiles.lst";
1385         FileName const abslstfile = libFileSearch(string(), lstfile);
1386         if (abslstfile.empty()) {
1387                 lyxerr << "File `'" << lstfile << "' not found." << endl;
1388                 return string();
1389         }
1390         string const allClasses = getFileContents(abslstfile);
1391         int entries = 0;
1392         string classfile = token(allClasses, '\n', entries);
1393         int count = 0;
1394         while ((!contains(classfile, file) ||
1395                 (onlyFilename(classfile) != file)) &&
1396                 (++count < 1000)) {
1397                 classfile = token(allClasses, '\n', ++entries);
1398         }
1399
1400         // now we have filename with full path
1401         lyxerr << "with full path: " << classfile << endl;
1402
1403         return classfile;
1404 }
1405
1406 } // namespace frontend
1407 } // namespace lyx