]> git.lyx.org Git - lyx.git/blob - src/Format.cpp
Correction: The inset name is citation, not cite
[lyx.git] / src / Format.cpp
1 /**
2  * \file Format.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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "Format.h"
14 #include "Buffer.h"
15 #include "BufferParams.h"
16 #include "LyXRC.h"
17 #include "ServerSocket.h"
18
19 #include "frontends/alert.h" //to be removed?
20
21 #include "support/debug.h"
22 #include "support/filetools.h"
23 #include "support/gettext.h"
24 #include "support/lstrings.h"
25 #include "support/os.h"
26 #include "support/Path.h"
27 #include "support/Systemcall.h"
28 #include "support/textutils.h"
29 #include "support/Translator.h"
30
31 #include <algorithm>
32 #include <map>
33 #include <ctime>
34
35 // FIXME: Q_WS_MACX is not available, it's in Qt
36 #ifdef USE_MACOSX_PACKAGING
37 #include "support/linkback/LinkBackProxy.h"
38 #endif
39
40 using namespace std;
41 using namespace lyx::support;
42
43 namespace lyx {
44
45 namespace Alert = frontend::Alert;
46 namespace os = support::os;
47
48 namespace {
49
50 string const token_from_format("$$i");
51 string const token_path_format("$$p");
52 string const token_socket_format("$$a");
53
54
55 class FormatNamesEqual : public unary_function<Format, bool> {
56 public:
57         FormatNamesEqual(string const & name)
58                 : name_(name) {}
59         bool operator()(Format const & f) const
60         {
61                 return f.name() == name_;
62         }
63 private:
64         string name_;
65 };
66
67
68 class FormatExtensionsEqual : public unary_function<Format, bool> {
69 public:
70         FormatExtensionsEqual(string const & extension)
71                 : extension_(extension) {}
72         bool operator()(Format const & f) const
73         {
74                 return f.hasExtension(extension_);
75         }
76 private:
77         string extension_;
78 };
79
80 } //namespace anon
81
82
83 bool operator<(Format const & a, Format const & b)
84 {
85         return _(a.prettyname()) < _(b.prettyname());
86 }
87
88
89 Format::Format(string const & n, string const & e, string const & p,
90                string const & s, string const & v, string const & ed,
91                int flags)
92         : name_(n), prettyname_(p), shortcut_(s), viewer_(v),
93           editor_(ed), flags_(flags)
94 {
95         extension_list_ = getVectorFromString(e, ",");
96         LYXERR(Debug::GRAPHICS, "New Format: n=" << n << ", flags=" << flags);
97 }
98
99
100 bool Format::dummy() const
101 {
102         return extension().empty();
103 }
104
105
106 string const Format::extensions() const
107 {
108         return getStringFromVector(extension_list_, ", ");
109 }
110
111
112 bool Format::hasExtension(string const & e) const
113 {
114         return (find(extension_list_.begin(), extension_list_.end(), e)
115                 != extension_list_.end());
116 }
117
118
119 bool Format::isChildFormat() const
120 {
121         if (name_.empty())
122                 return false;
123         return isDigitASCII(name_[name_.length() - 1]);
124 }
125
126
127 string const Format::parentFormat() const
128 {
129         return name_.substr(0, name_.length() - 1);
130 }
131
132
133 void Format::setExtensions(string const & e)
134 {
135         extension_list_ = getVectorFromString(e, ",");
136 }
137
138
139 // This method should return a reference, and throw an exception
140 // if the format named name cannot be found (Lgb)
141 Format const * Formats::getFormat(string const & name) const
142 {
143         FormatList::const_iterator cit =
144                 find_if(formatlist.begin(), formatlist.end(),
145                         FormatNamesEqual(name));
146         if (cit != formatlist.end())
147                 return &(*cit);
148         else
149                 return 0;
150 }
151
152
153 string Formats::getFormatFromFile(FileName const & filename) const
154 {
155         if (filename.empty())
156                 return string();
157
158         string const format = filename.guessFormatFromContents();
159         string const ext = getExtension(filename.absFileName());
160         if ((format == "gzip" || format == "zip" || format == "compress")
161             && !ext.empty()) {
162                 string const & fmt_name = formats.getFormatFromExtension(ext);
163                 if (!fmt_name.empty()) {
164                         Format const * p_format = formats.getFormat(fmt_name);
165                         if (p_format && p_format->zippedNative())
166                                 return p_format->name();
167                 }
168         }
169         if (!format.empty())
170                 return format;
171
172         // try to find a format from the file extension.
173         return getFormatFromExtension(ext);
174 }
175
176
177 string Formats::getFormatFromExtension(string const & ext) const
178 {
179         if (!ext.empty()) {
180                 // this is ambigous if two formats have the same extension,
181                 // but better than nothing
182                 Formats::const_iterator cit =
183                         find_if(formatlist.begin(), formatlist.end(),
184                                 FormatExtensionsEqual(ext));
185                 if (cit != formats.end()) {
186                         LYXERR(Debug::GRAPHICS, "\twill guess format from file extension: "
187                                 << ext << " -> " << cit->name());
188                         return cit->name();
189                 }
190         }
191         return string();
192 }
193
194
195 /// Used to store last timestamp of file and whether it is (was) zipped
196 struct ZippedInfo {
197         bool zipped;
198         std::time_t timestamp;
199         ZippedInfo(bool zipped, std::time_t timestamp)
200         : zipped(zipped), timestamp(timestamp) { }
201 };
202
203
204 /// Mapping absolute pathnames of files to their ZippedInfo metadata.
205 static std::map<std::string, ZippedInfo> zipped_;
206
207
208 bool Formats::isZippedFile(support::FileName const & filename) const {
209         string const & fname = filename.absFileName();
210         time_t timestamp = filename.lastModified();
211         map<string, ZippedInfo>::iterator it = zipped_.find(fname);
212         if (it != zipped_.end() && it->second.timestamp == timestamp)
213                 return it->second.zipped;
214         string const & format = getFormatFromFile(filename);
215         bool zipped = (format == "gzip" || format == "zip");
216         zipped_.insert(pair<string, ZippedInfo>(fname, ZippedInfo(zipped, timestamp)));
217         return zipped;
218 }
219
220
221 static string fixCommand(string const & cmd, string const & ext,
222                   os::auto_open_mode mode)
223 {
224         // configure.py says we do not want a viewer/editor
225         if (cmd.empty())
226                 return cmd;
227
228         // Does the OS manage this format?
229         if (os::canAutoOpenFile(ext, mode))
230                 return "auto";
231
232         // if configure.py found nothing, clear the command
233         if (token(cmd, ' ', 0) == "auto")
234                 return string();
235
236         // use the command found by configure.py
237         return cmd;
238 }
239
240
241 void Formats::setAutoOpen()
242 {
243         FormatList::iterator fit = formatlist.begin();
244         FormatList::iterator const fend = formatlist.end();
245         for ( ; fit != fend ; ++fit) {
246                 fit->setViewer(fixCommand(fit->viewer(), fit->extension(), os::VIEW));
247                 fit->setEditor(fixCommand(fit->editor(), fit->extension(), os::EDIT));
248         }
249 }
250
251
252 int Formats::getNumber(string const & name) const
253 {
254         FormatList::const_iterator cit =
255                 find_if(formatlist.begin(), formatlist.end(),
256                         FormatNamesEqual(name));
257         if (cit != formatlist.end())
258                 return distance(formatlist.begin(), cit);
259         else
260                 return -1;
261 }
262
263
264 void Formats::add(string const & name)
265 {
266         if (!getFormat(name))
267                 add(name, name, name, string(), string(), string(),
268                     Format::document);
269 }
270
271
272 void Formats::add(string const & name, string const & extensions,
273                   string const & prettyname, string const & shortcut,
274                   string const & viewer, string const & editor,
275                   int flags)
276 {
277         FormatList::iterator it =
278                 find_if(formatlist.begin(), formatlist.end(),
279                         FormatNamesEqual(name));
280         if (it == formatlist.end())
281                 formatlist.push_back(Format(name, extensions, prettyname,
282                                             shortcut, viewer, editor, flags));
283         else
284                 *it = Format(name, extensions, prettyname, shortcut, viewer,
285                              editor, flags);
286 }
287
288
289 void Formats::erase(string const & name)
290 {
291         FormatList::iterator it =
292                 find_if(formatlist.begin(), formatlist.end(),
293                         FormatNamesEqual(name));
294         if (it != formatlist.end())
295                 formatlist.erase(it);
296 }
297
298
299 void Formats::sort()
300 {
301         std::sort(formatlist.begin(), formatlist.end());
302 }
303
304
305 void Formats::setViewer(string const & name, string const & command)
306 {
307         add(name);
308         FormatList::iterator it =
309                 find_if(formatlist.begin(), formatlist.end(),
310                         FormatNamesEqual(name));
311         if (it != formatlist.end())
312                 it->setViewer(command);
313 }
314
315
316 void Formats::setEditor(string const & name, string const & command)
317 {
318         add(name);
319         FormatList::iterator it =
320                 find_if(formatlist.begin(), formatlist.end(),
321                         FormatNamesEqual(name));
322         if (it != formatlist.end())
323                 it->setEditor(command);
324 }
325
326
327 bool Formats::view(Buffer const & buffer, FileName const & filename,
328                    string const & format_name) const
329 {
330         if (filename.empty() || !filename.exists()) {
331                 Alert::error(_("Cannot view file"),
332                         bformat(_("File does not exist: %1$s"),
333                                 from_utf8(filename.absFileName())));
334                 return false;
335         }
336
337         Format const * format = getFormat(format_name);
338         if (format && format->viewer().empty() &&
339             format->isChildFormat())
340                 format = getFormat(format->parentFormat());
341         if (!format || format->viewer().empty()) {
342 // FIXME: I believe this is the wrong place to show alerts, it should be done
343 // by the caller (this should be "utility" code)
344                 Alert::error(_("Cannot view file"),
345                         bformat(_("No information for viewing %1$s"),
346                                 prettyName(format_name)));
347                 return false;
348         }
349         // viewer is 'auto'
350         if (format->viewer() == "auto") {
351                 if (os::autoOpenFile(filename.absFileName(), os::VIEW, buffer.filePath()))
352                         return true;
353                 else {
354                         Alert::error(_("Cannot view file"),
355                                 bformat(_("Auto-view file %1$s failed"),
356                                         from_utf8(filename.absFileName())));
357                         return false;
358                 }
359         }
360
361         string command = libScriptSearch(format->viewer());
362
363         if (format_name == "dvi" &&
364             !lyxrc.view_dvi_paper_option.empty()) {
365                 string paper_size = buffer.params().paperSizeName(BufferParams::XDVI);
366                 if (!paper_size.empty()) {
367                         command += ' ' + lyxrc.view_dvi_paper_option;
368                         command += ' ' + paper_size;
369                         if (buffer.params().orientation == ORIENTATION_LANDSCAPE &&
370                             buffer.params().papersize != PAPER_CUSTOM)
371                                 command += 'r';
372                 }
373         }
374
375         if (!contains(command, token_from_format))
376                 command += ' ' + token_from_format;
377
378         command = subst(command, token_from_format, quoteName(onlyFileName(filename.toFilesystemEncoding())));
379         command = subst(command, token_path_format, quoteName(onlyPath(filename.toFilesystemEncoding())));
380         command = subst(command, token_socket_format, quoteName(theServerSocket().address()));
381         LYXERR(Debug::FILES, "Executing command: " << command);
382         // FIXME UNICODE utf8 can be wrong for files
383         buffer.message(_("Executing command: ") + from_utf8(command));
384
385         PathChanger p(filename.onlyPath());
386         Systemcall one;
387         one.startscript(Systemcall::DontWait, command, buffer.filePath());
388
389         // we can't report any sort of error, since we aren't waiting
390         return true;
391 }
392
393
394 bool Formats::edit(Buffer const & buffer, FileName const & filename,
395                          string const & format_name) const
396 {
397         if (filename.empty() || !filename.exists()) {
398                 Alert::error(_("Cannot edit file"),
399                         bformat(_("File does not exist: %1$s"),
400                                 from_utf8(filename.absFileName())));
401                 return false;
402         }
403
404         // LinkBack files look like PDF, but have the .linkback extension
405         string const ext = getExtension(filename.absFileName());
406         if (format_name == "pdf" && ext == "linkback") {
407 #ifdef USE_MACOSX_PACKAGING
408                 return editLinkBackFile(filename.absFileName().c_str());
409 #else
410                 Alert::error(_("Cannot edit file"),
411                              _("LinkBack files can only be edited on Apple Mac OSX."));
412                 return false;
413 #endif // USE_MACOSX_PACKAGING
414         }
415
416         Format const * format = getFormat(format_name);
417         if (format && format->editor().empty() &&
418             format->isChildFormat())
419                 format = getFormat(format->parentFormat());
420         if (!format || format->editor().empty()) {
421 // FIXME: I believe this is the wrong place to show alerts, it should
422 // be done by the caller (this should be "utility" code)
423                 Alert::error(_("Cannot edit file"),
424                         bformat(_("No information for editing %1$s"),
425                                 prettyName(format_name)));
426                 return false;
427         }
428
429         // editor is 'auto'
430         if (format->editor() == "auto") {
431                 if (os::autoOpenFile(filename.absFileName(), os::EDIT, buffer.filePath()))
432                         return true;
433                 else {
434                         Alert::error(_("Cannot edit file"),
435                                 bformat(_("Auto-edit file %1$s failed"),
436                                         from_utf8(filename.absFileName())));
437                         return false;
438                 }
439         }
440
441         string command = format->editor();
442
443         if (!contains(command, token_from_format))
444                 command += ' ' + token_from_format;
445
446         command = subst(command, token_from_format, quoteName(filename.toFilesystemEncoding()));
447         command = subst(command, token_path_format, quoteName(onlyPath(filename.toFilesystemEncoding())));
448         command = subst(command, token_socket_format, quoteName(theServerSocket().address()));
449         LYXERR(Debug::FILES, "Executing command: " << command);
450         // FIXME UNICODE utf8 can be wrong for files
451         buffer.message(_("Executing command: ") + from_utf8(command));
452
453         Systemcall one;
454         one.startscript(Systemcall::DontWait, command, buffer.filePath());
455
456         // we can't report any sort of error, since we aren't waiting
457         return true;
458 }
459
460
461 docstring const Formats::prettyName(string const & name) const
462 {
463         Format const * format = getFormat(name);
464         if (format)
465                 return from_utf8(format->prettyname());
466         else
467                 return from_utf8(name);
468 }
469
470
471 string const Formats::extension(string const & name) const
472 {
473         Format const * format = getFormat(name);
474         if (format)
475                 return format->extension();
476         else
477                 return name;
478 }
479
480
481 string const Formats::extensions(string const & name) const
482 {
483         Format const * format = getFormat(name);
484         if (format)
485                 return format->extensions();
486         else
487                 return name;
488 }
489
490
491 namespace {
492 typedef Translator<OutputParams::FLAVOR, string> FlavorTranslator;
493
494 FlavorTranslator initFlavorTranslator()
495 {
496         FlavorTranslator f(OutputParams::LATEX, "latex");
497         f.addPair(OutputParams::DVILUATEX, "dviluatex");
498         f.addPair(OutputParams::LUATEX, "luatex");
499         f.addPair(OutputParams::PDFLATEX, "pdflatex");
500         f.addPair(OutputParams::XETEX, "xetex");
501         f.addPair(OutputParams::XML, "docbook-xml");
502         f.addPair(OutputParams::HTML, "xhtml");
503         f.addPair(OutputParams::TEXT, "text");
504         return f;
505 }
506
507
508 FlavorTranslator const & flavorTranslator()
509 {
510         static FlavorTranslator translator = initFlavorTranslator();
511         return translator;
512 }
513 }
514
515
516 std::string flavor2format(OutputParams::FLAVOR flavor)
517 {
518         return flavorTranslator().find(flavor);
519 }
520
521
522 /* Not currently needed, but I'll leave the code in case it is.
523 OutputParams::FLAVOR format2flavor(std::string fmt)
524 {
525         return flavorTranslator().find(fmt);
526 } */
527
528 Formats formats;
529
530 Formats system_formats;
531
532
533 } // namespace lyx