3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * Full author contact details are available in file CREDITS.
16 #include "BiblioInfo.h"
17 #include "BranchList.h"
18 #include "buffer_funcs.h"
19 #include "BufferList.h"
20 #include "BufferParams.h"
25 #include "DocIterator.h"
27 #include "ErrorList.h"
30 #include "FuncRequest.h"
32 #include "InsetIterator.h"
33 #include "InsetList.h"
36 #include "LaTeXFeatures.h"
38 #include "LyXAction.h"
46 #include "output_docbook.h"
47 #include "output_latex.h"
48 #include "Paragraph.h"
49 #include "paragraph_funcs.h"
50 #include "ParagraphParameters.h"
51 #include "ParIterator.h"
55 #include "TextClassList.h"
56 #include "TexStream.h"
57 #include "TocBackend.h"
60 #include "EmbeddedFiles.h"
61 #include "PDFOptions.h"
63 #include "insets/InsetBibitem.h"
64 #include "insets/InsetBibtex.h"
65 #include "insets/InsetInclude.h"
66 #include "insets/InsetText.h"
68 #include "mathed/MathMacroTemplate.h"
69 #include "mathed/MacroTable.h"
70 #include "mathed/MathSupport.h"
72 #include "frontends/alert.h"
73 #include "frontends/Delegates.h"
74 #include "frontends/WorkAreaManager.h"
75 #include "frontends/FileDialog.h"
77 #include "graphics/Previews.h"
79 #include "support/types.h"
80 #include "support/lyxalgo.h"
81 #include "support/FileFilterList.h"
82 #include "support/filetools.h"
83 #include "support/Forkedcall.h"
84 #include "support/fs_extras.h"
85 #include "support/gzstream.h"
86 #include "support/lyxlib.h"
87 #include "support/os.h"
88 #include "support/Path.h"
89 #include "support/textutils.h"
90 #include "support/convert.h"
92 #if !defined (HAVE_FORK)
96 #include <boost/bind.hpp>
97 #include <boost/filesystem/exception.hpp>
98 #include <boost/filesystem/operations.hpp>
99 #include <boost/shared_ptr.hpp>
109 using std::make_pair;
114 using std::ostringstream;
125 using support::addName;
126 using support::bformat;
127 using support::changeExtension;
128 using support::cmd_ret;
129 using support::createBufferTmpDir;
130 using support::destroyDir;
131 using support::FileName;
132 using support::getFormatFromContents;
133 using support::libFileSearch;
134 using support::latex_path;
135 using support::ltrim;
136 using support::makeAbsPath;
137 using support::makeDisplayPath;
138 using support::makeLatexName;
139 using support::onlyFilename;
140 using support::onlyPath;
141 using support::quoteName;
142 using support::removeAutosaveFile;
143 using support::rename;
144 using support::runCommand;
145 using support::split;
146 using support::subst;
147 using support::tempName;
150 using support::suffixIs;
152 namespace Alert = frontend::Alert;
153 namespace os = support::os;
154 namespace fs = boost::filesystem;
158 int const LYX_FORMAT = 295; //Uwe: htmlurl, href
163 typedef std::map<string, bool> DepClean;
168 Impl(Buffer & parent, FileName const & file, bool readonly);
175 /// need to regenerate .tex?
179 mutable bool lyx_clean;
181 /// is autosave needed?
182 mutable bool bak_clean;
184 /// is this a unnamed file (New...)?
190 /// name of the file the buffer is associated with.
193 /** Set to true only when the file is fully loaded.
194 * Used to prevent the premature generation of previews
195 * and by the citation inset.
197 bool file_fully_loaded;
199 /// our Text that should be wrapped in an InsetText
206 TocBackend toc_backend;
208 /// Container for all sort of Buffer dependant errors.
209 map<string, ErrorList> errorLists;
211 /// all embedded files of this buffer
212 EmbeddedFiles embedded_files;
214 /// timestamp and checksum used to test if the file has been externally
215 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
217 unsigned long checksum_;
220 frontend::WorkAreaManager * wa_;
227 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
228 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
229 filename(file), file_fully_loaded(false), inset(params),
230 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
231 checksum_(0), wa_(0), undo_(parent)
233 inset.setAutoBreakRows(true);
234 lyxvc.buffer(&parent);
235 temppath = createBufferTmpDir();
236 params.filepath = onlyPath(file.absFilename());
237 // FIXME: And now do something if temppath == string(), because we
238 // assume from now on that temppath points to a valid temp dir.
239 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
242 wa_ = new frontend::WorkAreaManager;
246 Buffer::Buffer(string const & file, bool readonly)
247 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
249 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
255 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
256 // here the buffer should take care that it is
257 // saved properly, before it goes into the void.
259 Buffer * master = getMasterBuffer();
260 if (master != this && use_gui)
261 // We are closing buf which was a child document so we
262 // must update the labels and section numbering of its master
264 updateLabels(*master);
266 if (!temppath().empty() && !destroyDir(FileName(temppath()))) {
267 Alert::warning(_("Could not remove temporary directory"),
268 bformat(_("Could not remove the temporary directory %1$s"),
269 from_utf8(temppath())));
272 // Remove any previewed LaTeX snippets associated with this buffer.
273 graphics::Previews::get().removeLoader(*this);
276 pimpl_->wa_->closeAll();
283 void Buffer::changed() const
286 pimpl_->wa_->redrawAll();
290 frontend::WorkAreaManager & Buffer::workAreaManager() const
292 BOOST_ASSERT(pimpl_->wa_);
297 Text & Buffer::text() const
299 return const_cast<Text &>(pimpl_->inset.text_);
303 Inset & Buffer::inset() const
305 return const_cast<InsetText &>(pimpl_->inset);
309 BufferParams & Buffer::params()
311 return pimpl_->params;
315 BufferParams const & Buffer::params() const
317 return pimpl_->params;
321 ParagraphList & Buffer::paragraphs()
323 return text().paragraphs();
327 ParagraphList const & Buffer::paragraphs() const
329 return text().paragraphs();
333 LyXVC & Buffer::lyxvc()
335 return pimpl_->lyxvc;
339 LyXVC const & Buffer::lyxvc() const
341 return pimpl_->lyxvc;
345 string const & Buffer::temppath() const
347 return pimpl_->temppath;
351 TexRow & Buffer::texrow()
353 return pimpl_->texrow;
357 TexRow const & Buffer::texrow() const
359 return pimpl_->texrow;
363 TocBackend & Buffer::tocBackend()
365 return pimpl_->toc_backend;
369 TocBackend const & Buffer::tocBackend() const
371 return pimpl_->toc_backend;
375 EmbeddedFiles & Buffer::embeddedFiles()
377 return pimpl_->embedded_files;
381 EmbeddedFiles const & Buffer::embeddedFiles() const
383 return pimpl_->embedded_files;
386 Undo & Buffer::undo()
388 return pimpl_->undo_;
393 string const Buffer::getLatexName(bool const no_path) const
395 string const name = changeExtension(makeLatexName(fileName()), ".tex");
396 return no_path ? onlyFilename(name) : name;
400 pair<Buffer::LogType, string> const Buffer::getLogName() const
402 string const filename = getLatexName(false);
404 if (filename.empty())
405 return make_pair(Buffer::latexlog, string());
407 string const path = temppath();
409 FileName const fname(addName(temppath(),
410 onlyFilename(changeExtension(filename,
412 FileName const bname(
413 addName(path, onlyFilename(
414 changeExtension(filename,
415 formats.extension("literate") + ".out"))));
417 // If no Latex log or Build log is newer, show Build log
419 if (bname.exists() &&
420 (!fname.exists() || fname.lastModified() < bname.lastModified())) {
421 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
422 return make_pair(Buffer::buildlog, bname.absFilename());
424 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
425 return make_pair(Buffer::latexlog, fname.absFilename());
429 void Buffer::setReadonly(bool const flag)
431 if (pimpl_->read_only != flag) {
432 pimpl_->read_only = flag;
438 void Buffer::setFileName(string const & newfile)
440 pimpl_->filename = makeAbsPath(newfile);
441 params().filepath = onlyPath(pimpl_->filename.absFilename());
442 setReadonly(fs::is_readonly(pimpl_->filename.toFilesystemEncoding()));
447 // We'll remove this later. (Lgb)
450 void unknownClass(string const & unknown)
452 Alert::warning(_("Unknown document class"),
453 bformat(_("Using the default document class, because the "
454 "class %1$s is unknown."), from_utf8(unknown)));
460 int Buffer::readHeader(Lexer & lex)
462 int unknown_tokens = 0;
464 int begin_header_line = -1;
466 // Initialize parameters that may be/go lacking in header:
467 params().branchlist().clear();
468 params().preamble.erase();
469 params().options.erase();
470 params().float_placement.erase();
471 params().paperwidth.erase();
472 params().paperheight.erase();
473 params().leftmargin.erase();
474 params().rightmargin.erase();
475 params().topmargin.erase();
476 params().bottommargin.erase();
477 params().headheight.erase();
478 params().headsep.erase();
479 params().footskip.erase();
480 params().listings_params.clear();
481 params().clearLayoutModules();
482 params().pdfoptions().clear();
484 for (int i = 0; i < 4; ++i) {
485 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
486 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
489 ErrorList & errorList = pimpl_->errorLists["Parse"];
493 string const token = lex.getString();
498 if (token == "\\end_header")
502 if (token == "\\begin_header") {
503 begin_header_line = line;
507 LYXERR(Debug::PARSER) << "Handling document header token: `"
508 << token << '\'' << endl;
510 string unknown = params().readToken(lex, token);
511 if (!unknown.empty()) {
512 if (unknown[0] != '\\' && token == "\\textclass") {
513 unknownClass(unknown);
516 docstring const s = bformat(_("Unknown token: "
520 errorList.push_back(ErrorItem(_("Document header error"),
525 if (begin_header_line) {
526 docstring const s = _("\\begin_header is missing");
527 errorList.push_back(ErrorItem(_("Document header error"),
531 return unknown_tokens;
536 // changed to be public and have one parameter
537 // Returns false if "\end_document" is not read (Asger)
538 bool Buffer::readDocument(Lexer & lex)
540 ErrorList & errorList = pimpl_->errorLists["Parse"];
544 string const token = lex.getString();
545 if (token != "\\begin_document") {
546 docstring const s = _("\\begin_document is missing");
547 errorList.push_back(ErrorItem(_("Document header error"),
551 // we are reading in a brand new document
552 BOOST_ASSERT(paragraphs().empty());
555 TextClass const & baseClass = textclasslist[params().getBaseClass()];
556 if (!baseClass.load(filePath())) {
557 string theclass = baseClass.name();
558 Alert::error(_("Can't load document class"), bformat(
559 _("Using the default document class, because the "
560 "class %1$s could not be loaded."), from_utf8(theclass)));
561 params().setBaseClass(defaultTextclass());
564 if (params().outputChanges) {
565 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
566 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
567 LaTeXFeatures::isAvailable("xcolor");
569 if (!dvipost && !xcolorsoul) {
570 Alert::warning(_("Changes not shown in LaTeX output"),
571 _("Changes will not be highlighted in LaTeX output, "
572 "because neither dvipost nor xcolor/soul are installed.\n"
573 "Please install these packages or redefine "
574 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
575 } else if (!xcolorsoul) {
576 Alert::warning(_("Changes not shown in LaTeX output"),
577 _("Changes will not be highlighted in LaTeX output "
578 "when using pdflatex, because xcolor and soul are not installed.\n"
579 "Please install both packages or redefine "
580 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
583 // read manifest after header
584 embeddedFiles().readManifest(lex, errorList);
587 bool const res = text().read(*this, lex, errorList);
588 for_each(text().paragraphs().begin(),
589 text().paragraphs().end(),
590 bind(&Paragraph::setInsetOwner, _1, &inset()));
596 // needed to insert the selection
597 void Buffer::insertStringAsLines(ParagraphList & pars,
598 pit_type & pit, pos_type & pos,
599 Font const & fn, docstring const & str, bool autobreakrows)
603 // insert the string, don't insert doublespace
604 bool space_inserted = true;
605 for (docstring::const_iterator cit = str.begin();
606 cit != str.end(); ++cit) {
607 Paragraph & par = pars[pit];
609 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
610 breakParagraph(params(), pars, pit, pos,
611 par.layout()->isEnvironment());
614 space_inserted = true;
618 // do not insert consecutive spaces if !free_spacing
619 } else if ((*cit == ' ' || *cit == '\t') &&
620 space_inserted && !par.isFreeSpacing()) {
622 } else if (*cit == '\t') {
623 if (!par.isFreeSpacing()) {
624 // tabs are like spaces here
625 par.insertChar(pos, ' ', font, params().trackChanges);
627 space_inserted = true;
629 const pos_type n = 8 - pos % 8;
630 for (pos_type i = 0; i < n; ++i) {
631 par.insertChar(pos, ' ', font, params().trackChanges);
634 space_inserted = true;
636 } else if (!isPrintable(*cit)) {
637 // Ignore unprintables
640 // just insert the character
641 par.insertChar(pos, *cit, font, params().trackChanges);
643 space_inserted = (*cit == ' ');
650 bool Buffer::readString(std::string const & s)
652 params().compressed = false;
654 // remove dummy empty par
655 paragraphs().clear();
657 std::istringstream is(s);
659 FileName const name(tempName());
660 switch (readFile(lex, name, true)) {
664 // We need to call lyx2lyx, so write the input to a file
665 std::ofstream os(name.toFilesystemEncoding().c_str());
668 return readFile(name);
678 bool Buffer::readFile(FileName const & filename)
680 FileName fname(filename);
681 // Check if the file is compressed.
682 string format = getFormatFromContents(filename);
683 if (format == "zip") {
684 // decompress to a temp directory
685 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
686 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
688 FileName lyxfile(addName(temppath(), "content.lyx"));
689 // if both manifest.txt and file.lyx exist, this is am embedded file
690 if (lyxfile.exists()) {
691 params().embedded = true;
695 // The embedded lyx file can also be compressed, for backward compatibility
696 format = getFormatFromContents(fname);
697 if (format == "gzip" || format == "zip" || format == "compress") {
698 params().compressed = true;
701 // remove dummy empty par
702 paragraphs().clear();
705 if (readFile(lex, fname) != success)
712 bool Buffer::fully_loaded() const
714 return pimpl_->file_fully_loaded;
718 void Buffer::fully_loaded(bool const value)
720 pimpl_->file_fully_loaded = value;
724 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
727 BOOST_ASSERT(!filename.empty());
730 Alert::error(_("Document could not be read"),
731 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
736 string const token = lex.getString();
739 Alert::error(_("Document could not be read"),
740 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
744 // the first token _must_ be...
745 if (token != "\\lyxformat") {
746 lyxerr << "Token: " << token << endl;
748 Alert::error(_("Document format failure"),
749 bformat(_("%1$s is not a LyX document."),
750 from_utf8(filename.absFilename())));
755 string tmp_format = lex.getString();
756 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
757 // if present remove ".," from string.
758 string::size_type dot = tmp_format.find_first_of(".,");
759 //lyxerr << " dot found at " << dot << endl;
760 if (dot != string::npos)
761 tmp_format.erase(dot, 1);
762 int const file_format = convert<int>(tmp_format);
763 //lyxerr << "format: " << file_format << endl;
765 // save timestamp and checksum of the original disk file, making sure
766 // to not overwrite them with those of the file created in the tempdir
767 // when it has to be converted to the current format.
768 if (!pimpl_->checksum_) {
769 // Save the timestamp and checksum of disk file. If filename is an
770 // emergency file, save the timestamp and sum of the original lyx file
771 // because isExternallyModified will check for this file. (BUG4193)
772 string diskfile = filename.toFilesystemEncoding();
773 if (suffixIs(diskfile, ".emergency"))
774 diskfile = diskfile.substr(0, diskfile.size() - 10);
775 saveCheckSum(diskfile);
778 if (file_format != LYX_FORMAT) {
781 // lyx2lyx would fail
784 FileName const tmpfile(tempName());
785 if (tmpfile.empty()) {
786 Alert::error(_("Conversion failed"),
787 bformat(_("%1$s is from a different"
788 " version of LyX, but a temporary"
789 " file for converting it could"
791 from_utf8(filename.absFilename())));
794 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
795 if (lyx2lyx.empty()) {
796 Alert::error(_("Conversion script not found"),
797 bformat(_("%1$s is from a different"
798 " version of LyX, but the"
799 " conversion script lyx2lyx"
800 " could not be found."),
801 from_utf8(filename.absFilename())));
804 ostringstream command;
805 command << os::python()
806 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
807 << " -t " << convert<string>(LYX_FORMAT)
808 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
809 << ' ' << quoteName(filename.toFilesystemEncoding());
810 string const command_str = command.str();
812 LYXERR(Debug::INFO) << "Running '"
813 << command_str << '\''
816 cmd_ret const ret = runCommand(command_str);
817 if (ret.first != 0) {
818 Alert::error(_("Conversion script failed"),
819 bformat(_("%1$s is from a different version"
820 " of LyX, but the lyx2lyx script"
821 " failed to convert it."),
822 from_utf8(filename.absFilename())));
825 bool const ret = readFile(tmpfile);
826 // Do stuff with tmpfile name and buffer name here.
827 return ret ? success : failure;
832 if (readDocument(lex)) {
833 Alert::error(_("Document format failure"),
834 bformat(_("%1$s ended unexpectedly, which means"
835 " that it is probably corrupted."),
836 from_utf8(filename.absFilename())));
839 //lyxerr << "removing " << MacroTable::localMacros().size()
840 // << " temporary macro entries" << endl;
841 //MacroTable::localMacros().clear();
843 pimpl_->file_fully_loaded = true;
848 // Should probably be moved to somewhere else: BufferView? LyXView?
849 bool Buffer::save() const
851 // We don't need autosaves in the immediate future. (Asger)
852 resetAutosaveTimers();
854 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
857 bool madeBackup = false;
859 // make a backup if the file already exists
860 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
861 backupName = FileName(fileName() + '~');
862 if (!lyxrc.backupdir_path.empty())
863 backupName = FileName(addName(lyxrc.backupdir_path,
864 subst(os::internal_path(backupName.absFilename()), '/', '!')));
867 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
869 } catch (fs::filesystem_error const & fe) {
870 Alert::error(_("Backup failure"),
871 bformat(_("Cannot create backup file %1$s.\n"
872 "Please check whether the directory exists and is writeable."),
873 from_utf8(backupName.absFilename())));
874 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
878 // ask if the disk file has been externally modified (use checksum method)
879 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
880 docstring const file = makeDisplayPath(fileName(), 20);
881 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
882 "you want to overwrite this file?"), file);
883 int const ret = Alert::prompt(_("Overwrite modified file?"),
884 text, 1, 1, _("&Overwrite"), _("&Cancel"));
889 if (writeFile(pimpl_->filename)) {
891 removeAutosaveFile(fileName());
892 saveCheckSum(pimpl_->filename.toFilesystemEncoding());
895 // Saving failed, so backup is not backup
897 rename(backupName, pimpl_->filename);
903 bool Buffer::writeFile(FileName const & fname) const
905 if (pimpl_->read_only && fname == pimpl_->filename)
911 if (params().embedded)
912 // first write the .lyx file to the temporary directory
913 content = FileName(addName(temppath(), "content.lyx"));
917 if (params().compressed) {
918 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
924 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
931 if (retval && params().embedded) {
932 // write file.lyx and all the embedded files to the zip file fname
933 // if embedding is enabled
934 return pimpl_->embedded_files.writeFile(fname);
940 bool Buffer::write(ostream & ofs) const
943 // Use the standard "C" locale for file output.
944 ofs.imbue(std::locale::classic());
947 // The top of the file should not be written by params().
949 // write out a comment in the top of the file
950 ofs << "#LyX " << lyx_version
951 << " created this file. For more info see http://www.lyx.org/\n"
952 << "\\lyxformat " << LYX_FORMAT << "\n"
953 << "\\begin_document\n";
956 /// For each author, set 'used' to true if there is a change
957 /// by this author in the document; otherwise set it to 'false'.
958 AuthorList::Authors::const_iterator a_it = params().authors().begin();
959 AuthorList::Authors::const_iterator a_end = params().authors().end();
960 for (; a_it != a_end; ++a_it)
961 a_it->second.used(false);
963 ParIterator const end = par_iterator_end();
964 ParIterator it = par_iterator_begin();
965 for ( ; it != end; ++it)
966 it->checkAuthors(params().authors());
968 // now write out the buffer parameters.
969 ofs << "\\begin_header\n";
970 params().writeFile(ofs);
971 ofs << "\\end_header\n";
973 // write the manifest after header
974 ofs << "\n\\begin_manifest\n";
975 pimpl_->embedded_files.update();
976 embeddedFiles().writeManifest(ofs);
977 ofs << "\\end_manifest\n";
980 ofs << "\n\\begin_body\n";
981 text().write(*this, ofs);
982 ofs << "\n\\end_body\n";
984 // Write marker that shows file is complete
985 ofs << "\\end_document" << endl;
987 // Shouldn't really be needed....
990 // how to check if close went ok?
991 // Following is an attempt... (BE 20001011)
993 // good() returns false if any error occured, including some
995 // bad() returns true if something bad happened in the buffer,
996 // which should include file system full errors.
1001 lyxerr << "File was not closed properly." << endl;
1008 bool Buffer::makeLaTeXFile(FileName const & fname,
1009 string const & original_path,
1010 OutputParams const & runparams,
1011 bool output_preamble, bool output_body)
1013 string const encoding = runparams.encoding->iconvName();
1014 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1015 << encoding << "..." << endl;
1017 odocfstream ofs(encoding);
1018 if (!openFileWrite(ofs, fname))
1021 //TexStream ts(ofs.rdbuf(), &texrow());
1023 bool failed_export = false;
1026 writeLaTeXSource(ofs, original_path,
1027 runparams, output_preamble, output_body);
1029 catch (iconv_codecvt_facet_exception & e) {
1030 lyxerr << "Caught iconv exception: " << e.what() << endl;
1031 failed_export = true;
1033 catch (std::exception const & e) {
1034 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1035 failed_export = true;
1038 lyxerr << "Caught some really weird exception..." << endl;
1039 LyX::cref().emergencyCleanup();
1045 failed_export = true;
1046 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1049 if (failed_export) {
1050 Alert::error(_("Encoding error"),
1051 _("Some characters of your document are probably not "
1052 "representable in the chosen encoding.\n"
1053 "Changing the document encoding to utf8 could help."));
1060 void Buffer::writeLaTeXSource(odocstream & os,
1061 string const & original_path,
1062 OutputParams const & runparams_in,
1063 bool const output_preamble, bool const output_body)
1065 OutputParams runparams = runparams_in;
1067 // validate the buffer.
1068 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1069 LaTeXFeatures features(*this, params(), runparams);
1071 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1073 // The starting paragraph of the coming rows is the
1074 // first paragraph of the document. (Asger)
1075 if (output_preamble && runparams.nice) {
1076 os << "%% LyX " << lyx_version << " created this file. "
1077 "For more info, see http://www.lyx.org/.\n"
1078 "%% Do not edit unless you really know what "
1083 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1084 // There are a few differences between nice LaTeX and usual files:
1085 // usual is \batchmode and has a
1086 // special input@path to allow the including of figures
1087 // with either \input or \includegraphics (what figinsets do).
1088 // input@path is set when the actual parameter
1089 // original_path is set. This is done for usual tex-file, but not
1090 // for nice-latex-file. (Matthias 250696)
1091 // Note that input@path is only needed for something the user does
1092 // in the preamble, included .tex files or ERT, files included by
1093 // LyX work without it.
1094 if (output_preamble) {
1095 if (!runparams.nice) {
1096 // code for usual, NOT nice-latex-file
1097 os << "\\batchmode\n"; // changed
1098 // from \nonstopmode
1101 if (!original_path.empty()) {
1103 // We don't know the encoding of inputpath
1104 docstring const inputpath = from_utf8(latex_path(original_path));
1105 os << "\\makeatletter\n"
1106 << "\\def\\input@path{{"
1107 << inputpath << "/}}\n"
1108 << "\\makeatother\n";
1114 // Write the preamble
1115 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1121 os << "\\begin{document}\n";
1123 } // output_preamble
1125 texrow().start(paragraphs().begin()->id(), 0);
1127 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1129 if (!lyxrc.language_auto_begin &&
1130 !params().language->babel().empty()) {
1132 os << from_utf8(subst(lyxrc.language_command_begin,
1134 params().language->babel()))
1139 Encoding const & encoding = params().encoding();
1140 if (encoding.package() == Encoding::CJK) {
1141 // Open a CJK environment, since in contrast to the encodings
1142 // handled by inputenc the document encoding is not set in
1143 // the preamble if it is handled by CJK.sty.
1144 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1149 // if we are doing a real file with body, even if this is the
1150 // child of some other buffer, let's cut the link here.
1151 // This happens for example if only a child document is printed.
1152 string save_parentname;
1153 if (output_preamble) {
1154 save_parentname = params().parentname;
1155 params().parentname.erase();
1158 loadChildDocuments(*this);
1161 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1163 // Restore the parenthood if needed
1164 if (output_preamble)
1165 params().parentname = save_parentname;
1167 // add this just in case after all the paragraphs
1171 if (encoding.package() == Encoding::CJK) {
1172 // Close the open CJK environment.
1173 // latexParagraphs will have opened one even if the last text
1175 os << "\\end{CJK}\n";
1179 if (!lyxrc.language_auto_end &&
1180 !params().language->babel().empty()) {
1181 os << from_utf8(subst(lyxrc.language_command_end,
1183 params().language->babel()))
1188 if (output_preamble) {
1189 os << "\\end{document}\n";
1192 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1194 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1197 runparams_in.encoding = runparams.encoding;
1199 // Just to be sure. (Asger)
1202 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1203 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1208 bool Buffer::isLatex() const
1210 return params().getTextClass().outputType() == LATEX;
1214 bool Buffer::isLiterate() const
1216 return params().getTextClass().outputType() == LITERATE;
1220 bool Buffer::isDocBook() const
1222 return params().getTextClass().outputType() == DOCBOOK;
1226 void Buffer::makeDocBookFile(FileName const & fname,
1227 OutputParams const & runparams,
1228 bool const body_only)
1230 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1234 if (!openFileWrite(ofs, fname))
1237 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1241 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1245 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1246 OutputParams const & runparams,
1247 bool const only_body)
1249 LaTeXFeatures features(*this, params(), runparams);
1254 TextClass const & tclass = params().getTextClass();
1255 string const top_element = tclass.latexname();
1258 if (runparams.flavor == OutputParams::XML)
1259 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1262 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1265 if (! tclass.class_header().empty())
1266 os << from_ascii(tclass.class_header());
1267 else if (runparams.flavor == OutputParams::XML)
1268 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1269 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1271 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1273 docstring preamble = from_utf8(params().preamble);
1274 if (runparams.flavor != OutputParams::XML ) {
1275 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1276 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1277 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1278 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1281 string const name = runparams.nice ? changeExtension(fileName(), ".sgml")
1283 preamble += features.getIncludedFiles(name);
1284 preamble += features.getLyXSGMLEntities();
1286 if (!preamble.empty()) {
1287 os << "\n [ " << preamble << " ]";
1292 string top = top_element;
1294 if (runparams.flavor == OutputParams::XML)
1295 top += params().language->code();
1297 top += params().language->code().substr(0,2);
1300 if (!params().options.empty()) {
1302 top += params().options;
1305 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1306 << " file was created by LyX " << lyx_version
1307 << "\n See http://www.lyx.org/ for more information -->\n";
1309 params().getTextClass().counters().reset();
1311 loadChildDocuments(*this);
1313 sgml::openTag(os, top);
1315 docbookParagraphs(paragraphs(), *this, os, runparams);
1316 sgml::closeTag(os, top_element);
1320 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1321 // Other flags: -wall -v0 -x
1322 int Buffer::runChktex()
1326 // get LaTeX-Filename
1327 FileName const path(temppath());
1328 string const name = addName(path.absFilename(), getLatexName());
1329 string const org_path = filePath();
1331 support::Path p(path); // path to LaTeX file
1332 message(_("Running chktex..."));
1334 // Generate the LaTeX file if neccessary
1335 OutputParams runparams(¶ms().encoding());
1336 runparams.flavor = OutputParams::LATEX;
1337 runparams.nice = false;
1338 makeLaTeXFile(FileName(name), org_path, runparams);
1341 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1342 int const res = chktex.run(terr); // run chktex
1345 Alert::error(_("chktex failure"),
1346 _("Could not run chktex successfully."));
1347 } else if (res > 0) {
1348 ErrorList & errorList = pimpl_->errorLists["ChkTeX"];
1349 // Clear out old errors
1351 // Fill-in the error list with the TeX errors
1352 bufferErrors(*this, terr, errorList);
1363 void Buffer::validate(LaTeXFeatures & features) const
1365 TextClass const & tclass = params().getTextClass();
1367 if (params().outputChanges) {
1368 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1369 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1370 LaTeXFeatures::isAvailable("xcolor");
1372 if (features.runparams().flavor == OutputParams::LATEX) {
1374 features.require("ct-dvipost");
1375 features.require("dvipost");
1376 } else if (xcolorsoul) {
1377 features.require("ct-xcolor-soul");
1378 features.require("soul");
1379 features.require("xcolor");
1381 features.require("ct-none");
1383 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1385 features.require("ct-xcolor-soul");
1386 features.require("soul");
1387 features.require("xcolor");
1388 features.require("pdfcolmk"); // improves color handling in PDF output
1390 features.require("ct-none");
1395 // AMS Style is at document level
1396 if (params().use_amsmath == BufferParams::package_on
1397 || tclass.provides("amsmath"))
1398 features.require("amsmath");
1399 if (params().use_esint == BufferParams::package_on)
1400 features.require("esint");
1402 loadChildDocuments(*this);
1404 for_each(paragraphs().begin(), paragraphs().end(),
1405 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1407 // the bullet shapes are buffer level not paragraph level
1408 // so they are tested here
1409 for (int i = 0; i < 4; ++i) {
1410 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1411 int const font = params().user_defined_bullet(i).getFont();
1413 int const c = params()
1414 .user_defined_bullet(i)
1421 features.require("latexsym");
1423 } else if (font == 1) {
1424 features.require("amssymb");
1425 } else if ((font >= 2 && font <= 5)) {
1426 features.require("pifont");
1431 if (lyxerr.debugging(Debug::LATEX)) {
1432 features.showStruct();
1437 void Buffer::getLabelList(vector<docstring> & list) const
1439 /// if this is a child document and the parent is already loaded
1440 /// Use the parent's list instead [ale990407]
1441 Buffer const * tmp = getMasterBuffer();
1443 lyxerr << "getMasterBuffer() failed!" << endl;
1447 tmp->getLabelList(list);
1451 loadChildDocuments(*this);
1453 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1454 it.nextInset()->getLabelList(*this, list);
1458 void Buffer::updateBibfilesCache()
1460 // if this is a child document and the parent is already loaded
1461 // update the parent's cache instead
1462 Buffer * tmp = getMasterBuffer();
1465 tmp->updateBibfilesCache();
1469 bibfilesCache_.clear();
1470 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1471 if (it->lyxCode() == BIBTEX_CODE) {
1472 InsetBibtex const & inset =
1473 static_cast<InsetBibtex const &>(*it);
1474 vector<FileName> const bibfiles = inset.getFiles(*this);
1475 bibfilesCache_.insert(bibfilesCache_.end(),
1478 } else if (it->lyxCode() == INCLUDE_CODE) {
1479 InsetInclude & inset =
1480 static_cast<InsetInclude &>(*it);
1481 inset.updateBibfilesCache(*this);
1482 vector<FileName> const & bibfiles =
1483 inset.getBibfilesCache(*this);
1484 bibfilesCache_.insert(bibfilesCache_.end(),
1492 vector<FileName> const & Buffer::getBibfilesCache() const
1494 // if this is a child document and the parent is already loaded
1495 // use the parent's cache instead
1496 Buffer const * tmp = getMasterBuffer();
1499 return tmp->getBibfilesCache();
1501 // We update the cache when first used instead of at loading time.
1502 if (bibfilesCache_.empty())
1503 const_cast<Buffer *>(this)->updateBibfilesCache();
1505 return bibfilesCache_;
1509 bool Buffer::isDepClean(string const & name) const
1511 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1512 if (it == pimpl_->dep_clean.end())
1518 void Buffer::markDepClean(string const & name)
1520 pimpl_->dep_clean[name] = true;
1524 bool Buffer::dispatch(string const & command, bool * result)
1526 return dispatch(lyxaction.lookupFunc(command), result);
1530 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1532 bool dispatched = true;
1534 switch (func.action) {
1535 case LFUN_BUFFER_EXPORT: {
1536 bool const tmp = Exporter::Export(this, to_utf8(func.argument()), false);
1549 void Buffer::changeLanguage(Language const * from, Language const * to)
1554 for_each(par_iterator_begin(),
1556 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1560 bool Buffer::isMultiLingual() const
1562 ParConstIterator end = par_iterator_end();
1563 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1564 if (it->isMultiLingual(params()))
1571 ParIterator Buffer::getParFromID(int const id) const
1573 ParConstIterator it = par_iterator_begin();
1574 ParConstIterator const end = par_iterator_end();
1577 // John says this is called with id == -1 from undo
1578 lyxerr << "getParFromID(), id: " << id << endl;
1582 for (; it != end; ++it)
1590 bool Buffer::hasParWithID(int const id) const
1592 ParConstIterator const it = getParFromID(id);
1593 return it != par_iterator_end();
1597 ParIterator Buffer::par_iterator_begin()
1599 return lyx::par_iterator_begin(inset());
1603 ParIterator Buffer::par_iterator_end()
1605 return lyx::par_iterator_end(inset());
1609 ParConstIterator Buffer::par_iterator_begin() const
1611 return lyx::par_const_iterator_begin(inset());
1615 ParConstIterator Buffer::par_iterator_end() const
1617 return lyx::par_const_iterator_end(inset());
1621 Language const * Buffer::getLanguage() const
1623 return params().language;
1627 docstring const Buffer::B_(string const & l10n) const
1629 return params().B_(l10n);
1633 bool Buffer::isClean() const
1635 return pimpl_->lyx_clean;
1639 bool Buffer::isBakClean() const
1641 return pimpl_->bak_clean;
1645 bool Buffer::isExternallyModified(CheckMethod method) const
1647 BOOST_ASSERT(pimpl_->filename.exists());
1648 // if method == timestamp, check timestamp before checksum
1649 return (method == checksum_method
1650 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1651 && pimpl_->checksum_ != sum(pimpl_->filename);
1655 void Buffer::saveCheckSum(string const & file) const
1657 if (fs::exists(file)) {
1658 pimpl_->timestamp_ = fs::last_write_time(file);
1659 pimpl_->checksum_ = sum(FileName(file));
1661 // in the case of save to a new file.
1662 pimpl_->timestamp_ = 0;
1663 pimpl_->checksum_ = 0;
1668 void Buffer::markClean() const
1670 if (!pimpl_->lyx_clean) {
1671 pimpl_->lyx_clean = true;
1674 // if the .lyx file has been saved, we don't need an
1676 pimpl_->bak_clean = true;
1680 void Buffer::markBakClean() const
1682 pimpl_->bak_clean = true;
1686 void Buffer::setUnnamed(bool flag)
1688 pimpl_->unnamed = flag;
1692 bool Buffer::isUnnamed() const
1694 return pimpl_->unnamed;
1698 // FIXME: this function should be moved to buffer_pimpl.C
1699 void Buffer::markDirty()
1701 if (pimpl_->lyx_clean) {
1702 pimpl_->lyx_clean = false;
1705 pimpl_->bak_clean = false;
1707 DepClean::iterator it = pimpl_->dep_clean.begin();
1708 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1710 for (; it != end; ++it)
1715 string const Buffer::fileName() const
1717 return pimpl_->filename.absFilename();
1721 string const & Buffer::filePath() const
1723 return params().filepath;
1727 bool Buffer::isReadonly() const
1729 return pimpl_->read_only;
1733 void Buffer::setParentName(string const & name)
1735 if (name == pimpl_->filename.absFilename())
1736 // Avoids recursive include.
1737 params().parentname.clear();
1739 params().parentname = name;
1743 Buffer const * Buffer::getMasterBuffer() const
1745 if (!params().parentname.empty()
1746 && theBufferList().exists(params().parentname)) {
1747 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1748 //We need to check if the parent is us...
1749 //FIXME RECURSIVE INCLUDE
1750 //This is not sufficient, since recursive includes could be downstream.
1751 if (buf && buf != this)
1752 return buf->getMasterBuffer();
1759 Buffer * Buffer::getMasterBuffer()
1761 if (!params().parentname.empty()
1762 && theBufferList().exists(params().parentname)) {
1763 Buffer * buf = theBufferList().getBuffer(params().parentname);
1764 //We need to check if the parent is us...
1765 //FIXME RECURSIVE INCLUDE
1766 //This is not sufficient, since recursive includes could be downstream.
1767 if (buf && buf != this)
1768 return buf->getMasterBuffer();
1775 MacroData const & Buffer::getMacro(docstring const & name) const
1777 return pimpl_->macros.get(name);
1781 bool Buffer::hasMacro(docstring const & name) const
1783 return pimpl_->macros.has(name);
1787 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1789 MacroTable::globalMacros().insert(name, data);
1790 pimpl_->macros.insert(name, data);
1794 void Buffer::buildMacros()
1796 // Start with global table.
1797 pimpl_->macros = MacroTable::globalMacros();
1800 ParagraphList const & pars = text().paragraphs();
1801 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1802 //lyxerr << "searching main par " << i
1803 // << " for macro definitions" << std::endl;
1804 InsetList const & insets = pars[i].insetList();
1805 InsetList::const_iterator it = insets.begin();
1806 InsetList::const_iterator end = insets.end();
1807 for ( ; it != end; ++it) {
1808 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1809 if (it->inset->lyxCode() == MATHMACRO_CODE) {
1810 MathMacroTemplate const & mac
1811 = static_cast<MathMacroTemplate const &>(*it->inset);
1812 insertMacro(mac.name(), mac.asMacroData());
1819 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1822 //FIXME: This does not work for child documents yet.
1823 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1824 // Check if the label 'from' appears more than once
1825 vector<docstring> labels;
1827 if (code == CITE_CODE) {
1829 keys.fillWithBibKeys(this);
1830 BiblioInfo::const_iterator bit = keys.begin();
1831 BiblioInfo::const_iterator bend = keys.end();
1833 for (; bit != bend; ++bit)
1835 labels.push_back(bit->first);
1837 getLabelList(labels);
1839 if (std::count(labels.begin(), labels.end(), from) > 1)
1842 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1843 if (it->lyxCode() == code) {
1844 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1845 inset.replaceContents(to_utf8(from), to_utf8(to));
1851 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1852 pit_type par_end, bool full_source)
1854 OutputParams runparams(¶ms().encoding());
1855 runparams.nice = true;
1856 runparams.flavor = OutputParams::LATEX;
1857 runparams.linelen = lyxrc.plaintext_linelen;
1858 // No side effect of file copying and image conversion
1859 runparams.dryrun = true;
1863 os << "% " << _("Preview source code") << "\n\n";
1867 writeLaTeXSource(os, filePath(), runparams, true, true);
1869 writeDocBookSource(os, fileName(), runparams, false);
1872 runparams.par_begin = par_begin;
1873 runparams.par_end = par_end;
1874 if (par_begin + 1 == par_end)
1876 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1880 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1881 convert<docstring>(par_begin),
1882 convert<docstring>(par_end - 1))
1886 // output paragraphs
1888 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1891 docbookParagraphs(paragraphs(), *this, os, runparams);
1897 ErrorList const & Buffer::errorList(string const & type) const
1899 static ErrorList const emptyErrorList;
1900 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1901 if (I == pimpl_->errorLists.end())
1902 return emptyErrorList;
1908 ErrorList & Buffer::errorList(string const & type)
1910 return pimpl_->errorLists[type];
1914 void Buffer::structureChanged() const
1917 gui_->structureChanged();
1921 void Buffer::embeddingChanged() const
1924 gui_->embeddingChanged();
1928 void Buffer::errors(std::string const & err) const
1935 void Buffer::message(docstring const & msg) const
1942 void Buffer::busy(bool on) const
1949 void Buffer::readonly(bool on) const
1956 void Buffer::updateTitles() const
1959 gui_->updateTitles();
1963 void Buffer::resetAutosaveTimers() const
1966 gui_->resetAutosaveTimers();
1970 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1979 class AutoSaveBuffer : public support::ForkedProcess {
1982 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
1983 : buffer_(buffer), fname_(fname) {}
1985 virtual boost::shared_ptr<ForkedProcess> clone() const
1987 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
1992 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
1993 from_utf8(fname_.absFilename())));
1994 return run(DontWait);
1998 virtual int generateChild();
2000 Buffer const & buffer_;
2005 #if !defined (HAVE_FORK)
2009 int AutoSaveBuffer::generateChild()
2011 // tmp_ret will be located (usually) in /tmp
2012 // will that be a problem?
2013 pid_t const pid = fork();
2014 // If you want to debug the autosave
2015 // you should set pid to -1, and comment out the fork.
2016 if (pid == 0 || pid == -1) {
2017 // pid = -1 signifies that lyx was unable
2018 // to fork. But we will do the save
2020 bool failed = false;
2022 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2023 if (!tmp_ret.empty()) {
2024 buffer_.writeFile(tmp_ret);
2025 // assume successful write of tmp_ret
2026 if (!rename(tmp_ret, fname_)) {
2028 // most likely couldn't move between
2029 // filesystems unless write of tmp_ret
2030 // failed so remove tmp file (if it
2039 // failed to write/rename tmp_ret so try writing direct
2040 if (!buffer_.writeFile(fname_)) {
2041 // It is dangerous to do this in the child,
2042 // but safe in the parent, so...
2043 if (pid == -1) // emit message signal.
2044 buffer_.message(_("Autosave failed!"));
2047 if (pid == 0) { // we are the child so...
2057 // Perfect target for a thread...
2058 void Buffer::autoSave() const
2060 if (isBakClean() || isReadonly()) {
2061 // We don't save now, but we'll try again later
2062 resetAutosaveTimers();
2066 // emit message signal.
2067 message(_("Autosaving current document..."));
2069 // create autosave filename
2070 string fname = filePath();
2072 fname += onlyFilename(fileName());
2075 AutoSaveBuffer autosave(*this, FileName(fname));
2079 resetAutosaveTimers();
2083 /** Write a buffer to a new file name and rename the buffer
2084 according to the new file name.
2086 This function is e.g. used by menu callbacks and
2087 LFUN_BUFFER_WRITE_AS.
2089 If 'newname' is empty (the default), the user is asked via a
2090 dialog for the buffer's new name and location.
2092 If 'newname' is non-empty and has an absolute path, that is used.
2093 Otherwise the base directory of the buffer is used as the base
2094 for any relative path in 'newname'.
2097 bool Buffer::writeAs(string const & newname)
2099 string fname = fileName();
2100 string const oldname = fname;
2102 if (newname.empty()) { /// No argument? Ask user through dialog
2105 FileDialog fileDlg(_("Choose a filename to save document as"),
2106 LFUN_BUFFER_WRITE_AS,
2107 make_pair(_("Documents|#o#O"),
2108 from_utf8(lyxrc.document_path)),
2109 make_pair(_("Templates|#T#t"),
2110 from_utf8(lyxrc.template_path)));
2112 if (!support::isLyXFilename(fname))
2115 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2117 FileDialog::Result result =
2118 fileDlg.save(from_utf8(onlyPath(fname)),
2120 from_utf8(onlyFilename(fname)));
2122 if (result.first == FileDialog::Later)
2125 fname = to_utf8(result.second);
2130 // Make sure the absolute filename ends with appropriate suffix
2131 fname = makeAbsPath(fname).absFilename();
2132 if (!support::isLyXFilename(fname))
2136 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2138 if (FileName(fname).exists()) {
2139 docstring const file = makeDisplayPath(fname, 30);
2140 docstring text = bformat(_("The document %1$s already "
2141 "exists.\n\nDo you want to "
2142 "overwrite that document?"),
2144 int const ret = Alert::prompt(_("Overwrite document?"),
2145 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2151 // Ok, change the name of the buffer
2154 bool unnamed = isUnnamed();
2156 saveCheckSum(fname);
2159 setFileName(oldname);
2160 setUnnamed(unnamed);
2161 saveCheckSum(oldname);
2165 removeAutosaveFile(oldname);
2170 bool Buffer::menuWrite()
2173 LyX::ref().session().lastFiles().add(FileName(fileName()));
2177 // FIXME: we don't tell the user *WHY* the save failed !!
2179 docstring const file = makeDisplayPath(fileName(), 30);
2181 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2182 "Do you want to rename the document and "
2183 "try again?"), file);
2184 int const ret = Alert::prompt(_("Rename and save?"),
2185 text, 0, 1, _("&Rename"), _("&Cancel"));