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"
35 #include "LaTeXFeatures.h"
37 #include "LyXAction.h"
45 #include "output_docbook.h"
46 #include "output_latex.h"
47 #include "Paragraph.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "ParIterator.h"
54 #include "TextClassList.h"
55 #include "TexStream.h"
56 #include "TocBackend.h"
59 #include "EmbeddedFiles.h"
60 #include "PDFOptions.h"
62 #include "insets/InsetBibitem.h"
63 #include "insets/InsetBibtex.h"
64 #include "insets/InsetInclude.h"
65 #include "insets/InsetText.h"
67 #include "mathed/MathMacroTemplate.h"
68 #include "mathed/MacroTable.h"
69 #include "mathed/MathSupport.h"
71 #include "frontends/alert.h"
72 #include "frontends/Delegates.h"
73 #include "frontends/WorkAreaManager.h"
74 #include "frontends/FileDialog.h"
76 #include "graphics/Previews.h"
78 #include "support/types.h"
79 #include "support/lyxalgo.h"
80 #include "support/FileFilterList.h"
81 #include "support/filetools.h"
82 #include "support/Forkedcall.h"
83 #include "support/fs_extras.h"
84 #include "support/gzstream.h"
85 #include "support/lyxlib.h"
86 #include "support/os.h"
87 #include "support/Path.h"
88 #include "support/textutils.h"
89 #include "support/convert.h"
91 #include <boost/bind.hpp>
92 #include <boost/filesystem/exception.hpp>
93 #include <boost/filesystem/operations.hpp>
94 #include <boost/shared_ptr.hpp>
104 using std::make_pair;
109 using std::ostringstream;
121 using support::addName;
122 using support::bformat;
123 using support::changeExtension;
124 using support::cmd_ret;
125 using support::createBufferTmpDir;
126 using support::destroyDir;
127 using support::FileName;
128 using support::getFormatFromContents;
129 using support::libFileSearch;
130 using support::latex_path;
131 using support::ltrim;
132 using support::makeAbsPath;
133 using support::makeDisplayPath;
134 using support::makeLatexName;
135 using support::onlyFilename;
136 using support::onlyPath;
137 using support::quoteName;
138 using support::removeAutosaveFile;
139 using support::rename;
140 using support::runCommand;
141 using support::split;
142 using support::subst;
143 using support::tempName;
146 using support::suffixIs;
148 namespace Alert = frontend::Alert;
149 namespace os = support::os;
150 namespace fs = boost::filesystem;
154 int const LYX_FORMAT = 290; //Uwe Stöhr, wrap table
159 typedef std::map<string, bool> DepClean;
164 Impl(Buffer & parent, FileName const & file, bool readonly);
166 limited_stack<Undo> undostack;
167 limited_stack<Undo> redostack;
173 /// need to regenerate .tex?
177 mutable bool lyx_clean;
179 /// is autosave needed?
180 mutable bool bak_clean;
182 /// is this a unnamed file (New...)?
188 /// name of the file the buffer is associated with.
191 /** Set to true only when the file is fully loaded.
192 * Used to prevent the premature generation of previews
193 * and by the citation inset.
195 bool file_fully_loaded;
197 /// our Text that should be wrapped in an InsetText
204 TocBackend toc_backend;
206 /// Container for all sort of Buffer dependant errors.
207 map<string, ErrorList> errorLists;
209 /// all embedded files of this buffer
210 EmbeddedFiles embedded_files;
212 /// timestamp and checksum used to test if the file has been externally
213 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
215 unsigned long checksum_;
218 frontend::WorkAreaManager * wa_;
222 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
223 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
224 filename(file), file_fully_loaded(false), inset(params),
225 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
228 inset.setAutoBreakRows(true);
229 lyxvc.buffer(&parent);
230 temppath = createBufferTmpDir();
231 params.filepath = onlyPath(file.absFilename());
232 // FIXME: And now do something if temppath == string(), because we
233 // assume from now on that temppath points to a valid temp dir.
234 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
237 wa_ = new frontend::WorkAreaManager;
241 Buffer::Buffer(string const & file, bool readonly)
242 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
244 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
250 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
251 // here the buffer should take care that it is
252 // saved properly, before it goes into the void.
254 Buffer * master = getMasterBuffer();
255 if (master != this && use_gui)
256 // We are closing buf which was a child document so we
257 // must update the labels and section numbering of its master
259 updateLabels(*master);
261 if (!temppath().empty() && !destroyDir(FileName(temppath()))) {
262 Alert::warning(_("Could not remove temporary directory"),
263 bformat(_("Could not remove the temporary directory %1$s"),
264 from_utf8(temppath())));
267 // Remove any previewed LaTeX snippets associated with this buffer.
268 graphics::Previews::get().removeLoader(*this);
271 pimpl_->wa_->closeAll();
278 void Buffer::changed() const
281 pimpl_->wa_->redrawAll();
285 frontend::WorkAreaManager & Buffer::workAreaManager() const
287 BOOST_ASSERT(pimpl_->wa_);
292 Text & Buffer::text() const
294 return const_cast<Text &>(pimpl_->inset.text_);
298 Inset & Buffer::inset() const
300 return const_cast<InsetText &>(pimpl_->inset);
304 limited_stack<Undo> & Buffer::undostack()
306 return pimpl_->undostack;
310 limited_stack<Undo> const & Buffer::undostack() const
312 return pimpl_->undostack;
316 limited_stack<Undo> & Buffer::redostack()
318 return pimpl_->redostack;
322 limited_stack<Undo> const & Buffer::redostack() const
324 return pimpl_->redostack;
328 BufferParams & Buffer::params()
330 return pimpl_->params;
334 BufferParams const & Buffer::params() const
336 return pimpl_->params;
340 ParagraphList & Buffer::paragraphs()
342 return text().paragraphs();
346 ParagraphList const & Buffer::paragraphs() const
348 return text().paragraphs();
352 LyXVC & Buffer::lyxvc()
354 return pimpl_->lyxvc;
358 LyXVC const & Buffer::lyxvc() const
360 return pimpl_->lyxvc;
364 string const & Buffer::temppath() const
366 return pimpl_->temppath;
370 TexRow & Buffer::texrow()
372 return pimpl_->texrow;
376 TexRow const & Buffer::texrow() const
378 return pimpl_->texrow;
382 TocBackend & Buffer::tocBackend()
384 return pimpl_->toc_backend;
388 TocBackend const & Buffer::tocBackend() const
390 return pimpl_->toc_backend;
394 EmbeddedFiles & Buffer::embeddedFiles()
396 return pimpl_->embedded_files;
400 EmbeddedFiles const & Buffer::embeddedFiles() const
402 return pimpl_->embedded_files;
406 string const Buffer::getLatexName(bool const no_path) const
408 string const name = changeExtension(makeLatexName(fileName()), ".tex");
409 return no_path ? onlyFilename(name) : name;
413 pair<Buffer::LogType, string> const Buffer::getLogName() const
415 string const filename = getLatexName(false);
417 if (filename.empty())
418 return make_pair(Buffer::latexlog, string());
420 string const path = temppath();
422 FileName const fname(addName(temppath(),
423 onlyFilename(changeExtension(filename,
425 FileName const bname(
426 addName(path, onlyFilename(
427 changeExtension(filename,
428 formats.extension("literate") + ".out"))));
430 // If no Latex log or Build log is newer, show Build log
432 if (fs::exists(bname.toFilesystemEncoding()) &&
433 (!fs::exists(fname.toFilesystemEncoding()) ||
434 fs::last_write_time(fname.toFilesystemEncoding()) < fs::last_write_time(bname.toFilesystemEncoding()))) {
435 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
436 return make_pair(Buffer::buildlog, bname.absFilename());
438 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
439 return make_pair(Buffer::latexlog, fname.absFilename());
443 void Buffer::setReadonly(bool const flag)
445 if (pimpl_->read_only != flag) {
446 pimpl_->read_only = flag;
452 void Buffer::setFileName(string const & newfile)
454 pimpl_->filename = makeAbsPath(newfile);
455 params().filepath = onlyPath(pimpl_->filename.absFilename());
456 setReadonly(fs::is_readonly(pimpl_->filename.toFilesystemEncoding()));
461 // We'll remove this later. (Lgb)
464 void unknownClass(string const & unknown)
466 Alert::warning(_("Unknown document class"),
467 bformat(_("Using the default document class, because the "
468 "class %1$s is unknown."), from_utf8(unknown)));
474 int Buffer::readHeader(Lexer & lex)
476 int unknown_tokens = 0;
478 int begin_header_line = -1;
480 // Initialize parameters that may be/go lacking in header:
481 params().branchlist().clear();
482 params().preamble.erase();
483 params().options.erase();
484 params().float_placement.erase();
485 params().paperwidth.erase();
486 params().paperheight.erase();
487 params().leftmargin.erase();
488 params().rightmargin.erase();
489 params().topmargin.erase();
490 params().bottommargin.erase();
491 params().headheight.erase();
492 params().headsep.erase();
493 params().footskip.erase();
494 params().listings_params.clear();
495 params().clearLayoutModules();
496 params().pdfoptions().clear();
498 for (int i = 0; i < 4; ++i) {
499 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
500 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
503 ErrorList & errorList = pimpl_->errorLists["Parse"];
507 string const token = lex.getString();
512 if (token == "\\end_header")
516 if (token == "\\begin_header") {
517 begin_header_line = line;
521 LYXERR(Debug::PARSER) << "Handling document header token: `"
522 << token << '\'' << endl;
524 string unknown = params().readToken(lex, token);
525 if (!unknown.empty()) {
526 if (unknown[0] != '\\' && token == "\\textclass") {
527 unknownClass(unknown);
530 docstring const s = bformat(_("Unknown token: "
534 errorList.push_back(ErrorItem(_("Document header error"),
539 if (begin_header_line) {
540 docstring const s = _("\\begin_header is missing");
541 errorList.push_back(ErrorItem(_("Document header error"),
545 return unknown_tokens;
550 // changed to be public and have one parameter
551 // Returns false if "\end_document" is not read (Asger)
552 bool Buffer::readDocument(Lexer & lex)
554 ErrorList & errorList = pimpl_->errorLists["Parse"];
558 string const token = lex.getString();
559 if (token != "\\begin_document") {
560 docstring const s = _("\\begin_document is missing");
561 errorList.push_back(ErrorItem(_("Document header error"),
565 // we are reading in a brand new document
566 BOOST_ASSERT(paragraphs().empty());
569 TextClass const & baseClass = textclasslist[params().getBaseClass()];
570 if (!baseClass.load(filePath())) {
571 string theclass = baseClass.name();
572 Alert::error(_("Can't load document class"), bformat(
573 _("Using the default document class, because the "
574 "class %1$s could not be loaded."), from_utf8(theclass)));
575 params().setBaseClass(defaultTextclass());
578 if (params().outputChanges) {
579 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
580 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
581 LaTeXFeatures::isAvailable("xcolor");
583 if (!dvipost && !xcolorsoul) {
584 Alert::warning(_("Changes not shown in LaTeX output"),
585 _("Changes will not be highlighted in LaTeX output, "
586 "because neither dvipost nor xcolor/soul are installed.\n"
587 "Please install these packages or redefine "
588 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
589 } else if (!xcolorsoul) {
590 Alert::warning(_("Changes not shown in LaTeX output"),
591 _("Changes will not be highlighted in LaTeX output "
592 "when using pdflatex, because xcolor and soul are not installed.\n"
593 "Please install both packages or redefine "
594 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
597 // read manifest after header
598 embeddedFiles().readManifest(lex, errorList);
601 bool const res = text().read(*this, lex, errorList);
602 for_each(text().paragraphs().begin(),
603 text().paragraphs().end(),
604 bind(&Paragraph::setInsetOwner, _1, &inset()));
610 // needed to insert the selection
611 void Buffer::insertStringAsLines(ParagraphList & pars,
612 pit_type & pit, pos_type & pos,
613 Font const & fn, docstring const & str, bool autobreakrows)
617 // insert the string, don't insert doublespace
618 bool space_inserted = true;
619 for (docstring::const_iterator cit = str.begin();
620 cit != str.end(); ++cit) {
621 Paragraph & par = pars[pit];
623 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
624 breakParagraph(params(), pars, pit, pos,
625 par.layout()->isEnvironment());
628 space_inserted = true;
632 // do not insert consecutive spaces if !free_spacing
633 } else if ((*cit == ' ' || *cit == '\t') &&
634 space_inserted && !par.isFreeSpacing()) {
636 } else if (*cit == '\t') {
637 if (!par.isFreeSpacing()) {
638 // tabs are like spaces here
639 par.insertChar(pos, ' ', font, params().trackChanges);
641 space_inserted = true;
643 const pos_type n = 8 - pos % 8;
644 for (pos_type i = 0; i < n; ++i) {
645 par.insertChar(pos, ' ', font, params().trackChanges);
648 space_inserted = true;
650 } else if (!isPrintable(*cit)) {
651 // Ignore unprintables
654 // just insert the character
655 par.insertChar(pos, *cit, font, params().trackChanges);
657 space_inserted = (*cit == ' ');
664 bool Buffer::readString(std::string const & s)
666 params().compressed = false;
668 // remove dummy empty par
669 paragraphs().clear();
671 std::istringstream is(s);
673 FileName const name(tempName());
674 switch (readFile(lex, name, true)) {
678 // We need to call lyx2lyx, so write the input to a file
679 std::ofstream os(name.toFilesystemEncoding().c_str());
682 return readFile(name);
692 bool Buffer::readFile(FileName const & filename)
694 FileName fname(filename);
695 // Check if the file is compressed.
696 string format = getFormatFromContents(filename);
697 if (format == "zip") {
698 // decompress to a temp directory
699 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
700 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
702 FileName lyxfile(addName(temppath(), "content.lyx"));
703 // if both manifest.txt and file.lyx exist, this is am embedded file
704 if (fs::exists(lyxfile.toFilesystemEncoding())) {
705 params().embedded = true;
709 // The embedded lyx file can also be compressed, for backward compatibility
710 format = getFormatFromContents(fname);
711 if (format == "gzip" || format == "zip" || format == "compress") {
712 params().compressed = true;
715 // remove dummy empty par
716 paragraphs().clear();
719 if (readFile(lex, fname) != success)
726 bool Buffer::fully_loaded() const
728 return pimpl_->file_fully_loaded;
732 void Buffer::fully_loaded(bool const value)
734 pimpl_->file_fully_loaded = value;
738 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
741 BOOST_ASSERT(!filename.empty());
744 Alert::error(_("Document could not be read"),
745 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
750 string const token(lex.getString());
753 Alert::error(_("Document could not be read"),
754 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
758 // the first token _must_ be...
759 if (token != "\\lyxformat") {
760 lyxerr << "Token: " << token << endl;
762 Alert::error(_("Document format failure"),
763 bformat(_("%1$s is not a LyX document."),
764 from_utf8(filename.absFilename())));
769 string tmp_format = lex.getString();
770 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
771 // if present remove ".," from string.
772 string::size_type dot = tmp_format.find_first_of(".,");
773 //lyxerr << " dot found at " << dot << endl;
774 if (dot != string::npos)
775 tmp_format.erase(dot, 1);
776 int const file_format = convert<int>(tmp_format);
777 //lyxerr << "format: " << file_format << endl;
779 // save timestamp and checksum of the original disk file, making sure
780 // to not overwrite them with those of the file created in the tempdir
781 // when it has to be converted to the current format.
782 if (!pimpl_->checksum_) {
783 // Save the timestamp and checksum of disk file. If filename is an
784 // emergency file, save the timestamp and sum of the original lyx file
785 // because isExternallyModified will check for this file. (BUG4193)
786 string diskfile = filename.toFilesystemEncoding();
787 if (suffixIs(diskfile, ".emergency"))
788 diskfile = diskfile.substr(0, diskfile.size() - 10);
789 saveCheckSum(diskfile);
792 if (file_format != LYX_FORMAT) {
795 // lyx2lyx would fail
798 FileName const tmpfile(tempName());
799 if (tmpfile.empty()) {
800 Alert::error(_("Conversion failed"),
801 bformat(_("%1$s is from a different"
802 " version of LyX, but a temporary"
803 " file for converting it could"
805 from_utf8(filename.absFilename())));
808 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
809 if (lyx2lyx.empty()) {
810 Alert::error(_("Conversion script not found"),
811 bformat(_("%1$s is from a different"
812 " version of LyX, but the"
813 " conversion script lyx2lyx"
814 " could not be found."),
815 from_utf8(filename.absFilename())));
818 ostringstream command;
819 command << os::python()
820 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
821 << " -t " << convert<string>(LYX_FORMAT)
822 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
823 << ' ' << quoteName(filename.toFilesystemEncoding());
824 string const command_str = command.str();
826 LYXERR(Debug::INFO) << "Running '"
827 << command_str << '\''
830 cmd_ret const ret = runCommand(command_str);
831 if (ret.first != 0) {
832 Alert::error(_("Conversion script failed"),
833 bformat(_("%1$s is from a different version"
834 " of LyX, but the lyx2lyx script"
835 " failed to convert it."),
836 from_utf8(filename.absFilename())));
839 bool const ret = readFile(tmpfile);
840 // Do stuff with tmpfile name and buffer name here.
841 return ret ? success : failure;
846 if (readDocument(lex)) {
847 Alert::error(_("Document format failure"),
848 bformat(_("%1$s ended unexpectedly, which means"
849 " that it is probably corrupted."),
850 from_utf8(filename.absFilename())));
853 //lyxerr << "removing " << MacroTable::localMacros().size()
854 // << " temporary macro entries" << endl;
855 //MacroTable::localMacros().clear();
857 pimpl_->file_fully_loaded = true;
862 // Should probably be moved to somewhere else: BufferView? LyXView?
863 bool Buffer::save() const
865 // We don't need autosaves in the immediate future. (Asger)
866 resetAutosaveTimers();
868 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
871 bool madeBackup = false;
873 // make a backup if the file already exists
874 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
875 backupName = FileName(fileName() + '~');
876 if (!lyxrc.backupdir_path.empty())
877 backupName = FileName(addName(lyxrc.backupdir_path,
878 subst(os::internal_path(backupName.absFilename()), '/', '!')));
881 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
883 } catch (fs::filesystem_error const & fe) {
884 Alert::error(_("Backup failure"),
885 bformat(_("Cannot create backup file %1$s.\n"
886 "Please check whether the directory exists and is writeable."),
887 from_utf8(backupName.absFilename())));
888 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
892 // ask if the disk file has been externally modified (use checksum method)
893 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
894 docstring const file = makeDisplayPath(fileName(), 20);
895 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
896 "you want to overwrite this file?"), file);
897 int const ret = Alert::prompt(_("Overwrite modified file?"),
898 text, 1, 1, _("&Overwrite"), _("&Cancel"));
903 if (writeFile(pimpl_->filename)) {
905 removeAutosaveFile(fileName());
906 saveCheckSum(pimpl_->filename.toFilesystemEncoding());
909 // Saving failed, so backup is not backup
911 rename(backupName, pimpl_->filename);
917 bool Buffer::writeFile(FileName const & fname) const
919 if (pimpl_->read_only && fname == pimpl_->filename)
925 if (params().embedded)
926 // first write the .lyx file to the temporary directory
927 content = FileName(addName(temppath(), "content.lyx"));
931 if (params().compressed) {
932 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
938 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
945 if (retval && params().embedded) {
946 // write file.lyx and all the embedded files to the zip file fname
947 // if embedding is enabled
948 return pimpl_->embedded_files.writeFile(fname);
954 bool Buffer::write(ostream & ofs) const
957 // Use the standard "C" locale for file output.
958 ofs.imbue(std::locale::classic());
961 // The top of the file should not be written by params().
963 // write out a comment in the top of the file
964 ofs << "#LyX " << lyx_version
965 << " created this file. For more info see http://www.lyx.org/\n"
966 << "\\lyxformat " << LYX_FORMAT << "\n"
967 << "\\begin_document\n";
970 /// For each author, set 'used' to true if there is a change
971 /// by this author in the document; otherwise set it to 'false'.
972 AuthorList::Authors::const_iterator a_it = params().authors().begin();
973 AuthorList::Authors::const_iterator a_end = params().authors().end();
974 for (; a_it != a_end; ++a_it)
975 a_it->second.used(false);
977 ParIterator const end = par_iterator_end();
978 ParIterator it = par_iterator_begin();
979 for ( ; it != end; ++it)
980 it->checkAuthors(params().authors());
982 // now write out the buffer parameters.
983 ofs << "\\begin_header\n";
984 params().writeFile(ofs);
985 ofs << "\\end_header\n";
987 // write the manifest after header
988 ofs << "\n\\begin_manifest\n";
989 pimpl_->embedded_files.update();
990 embeddedFiles().writeManifest(ofs);
991 ofs << "\\end_manifest\n";
994 ofs << "\n\\begin_body\n";
995 text().write(*this, ofs);
996 ofs << "\n\\end_body\n";
998 // Write marker that shows file is complete
999 ofs << "\\end_document" << endl;
1001 // Shouldn't really be needed....
1004 // how to check if close went ok?
1005 // Following is an attempt... (BE 20001011)
1007 // good() returns false if any error occured, including some
1008 // formatting error.
1009 // bad() returns true if something bad happened in the buffer,
1010 // which should include file system full errors.
1015 lyxerr << "File was not closed properly." << endl;
1022 bool Buffer::makeLaTeXFile(FileName const & fname,
1023 string const & original_path,
1024 OutputParams const & runparams,
1025 bool output_preamble, bool output_body)
1027 string const encoding = runparams.encoding->iconvName();
1028 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1029 << encoding << "..." << endl;
1031 odocfstream ofs(encoding);
1032 if (!openFileWrite(ofs, fname))
1035 //TexStream ts(ofs.rdbuf(), &texrow());
1037 bool failed_export = false;
1040 writeLaTeXSource(ofs, original_path,
1041 runparams, output_preamble, output_body);
1043 catch (iconv_codecvt_facet_exception & e) {
1044 lyxerr << "Caught iconv exception: " << e.what() << endl;
1045 failed_export = true;
1047 catch (std::exception const & e) {
1048 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1049 failed_export = true;
1052 lyxerr << "Caught some really weird exception..." << endl;
1053 LyX::cref().emergencyCleanup();
1059 failed_export = true;
1060 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1063 if (failed_export) {
1064 Alert::error(_("Encoding error"),
1065 _("Some characters of your document are probably not "
1066 "representable in the chosen encoding.\n"
1067 "Changing the document encoding to utf8 could help."));
1074 void Buffer::writeLaTeXSource(odocstream & os,
1075 string const & original_path,
1076 OutputParams const & runparams_in,
1077 bool const output_preamble, bool const output_body)
1079 OutputParams runparams = runparams_in;
1081 // validate the buffer.
1082 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1083 LaTeXFeatures features(*this, params(), runparams);
1085 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1087 // The starting paragraph of the coming rows is the
1088 // first paragraph of the document. (Asger)
1089 if (output_preamble && runparams.nice) {
1090 os << "%% LyX " << lyx_version << " created this file. "
1091 "For more info, see http://www.lyx.org/.\n"
1092 "%% Do not edit unless you really know what "
1097 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1098 // There are a few differences between nice LaTeX and usual files:
1099 // usual is \batchmode and has a
1100 // special input@path to allow the including of figures
1101 // with either \input or \includegraphics (what figinsets do).
1102 // input@path is set when the actual parameter
1103 // original_path is set. This is done for usual tex-file, but not
1104 // for nice-latex-file. (Matthias 250696)
1105 // Note that input@path is only needed for something the user does
1106 // in the preamble, included .tex files or ERT, files included by
1107 // LyX work without it.
1108 if (output_preamble) {
1109 if (!runparams.nice) {
1110 // code for usual, NOT nice-latex-file
1111 os << "\\batchmode\n"; // changed
1112 // from \nonstopmode
1115 if (!original_path.empty()) {
1117 // We don't know the encoding of inputpath
1118 docstring const inputpath = from_utf8(latex_path(original_path));
1119 os << "\\makeatletter\n"
1120 << "\\def\\input@path{{"
1121 << inputpath << "/}}\n"
1122 << "\\makeatother\n";
1128 // Write the preamble
1129 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1135 os << "\\begin{document}\n";
1137 } // output_preamble
1139 texrow().start(paragraphs().begin()->id(), 0);
1141 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1143 if (!lyxrc.language_auto_begin &&
1144 !params().language->babel().empty()) {
1146 os << from_utf8(subst(lyxrc.language_command_begin,
1148 params().language->babel()))
1153 Encoding const & encoding = params().encoding();
1154 if (encoding.package() == Encoding::CJK) {
1155 // Open a CJK environment, since in contrast to the encodings
1156 // handled by inputenc the document encoding is not set in
1157 // the preamble if it is handled by CJK.sty.
1158 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1163 // if we are doing a real file with body, even if this is the
1164 // child of some other buffer, let's cut the link here.
1165 // This happens for example if only a child document is printed.
1166 string save_parentname;
1167 if (output_preamble) {
1168 save_parentname = params().parentname;
1169 params().parentname.erase();
1172 loadChildDocuments(*this);
1175 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1177 // Restore the parenthood if needed
1178 if (output_preamble)
1179 params().parentname = save_parentname;
1181 // add this just in case after all the paragraphs
1185 if (encoding.package() == Encoding::CJK) {
1186 // Close the open CJK environment.
1187 // latexParagraphs will have opened one even if the last text
1189 os << "\\end{CJK}\n";
1193 if (!lyxrc.language_auto_end &&
1194 !params().language->babel().empty()) {
1195 os << from_utf8(subst(lyxrc.language_command_end,
1197 params().language->babel()))
1202 if (output_preamble) {
1203 os << "\\end{document}\n";
1206 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1208 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1211 runparams_in.encoding = runparams.encoding;
1213 // Just to be sure. (Asger)
1216 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1217 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1222 bool Buffer::isLatex() const
1224 return params().getTextClass().outputType() == LATEX;
1228 bool Buffer::isLiterate() const
1230 return params().getTextClass().outputType() == LITERATE;
1234 bool Buffer::isDocBook() const
1236 return params().getTextClass().outputType() == DOCBOOK;
1240 void Buffer::makeDocBookFile(FileName const & fname,
1241 OutputParams const & runparams,
1242 bool const body_only)
1244 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1248 if (!openFileWrite(ofs, fname))
1251 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1255 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1259 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1260 OutputParams const & runparams,
1261 bool const only_body)
1263 LaTeXFeatures features(*this, params(), runparams);
1268 TextClass const & tclass = params().getTextClass();
1269 string const top_element = tclass.latexname();
1272 if (runparams.flavor == OutputParams::XML)
1273 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1276 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1279 if (! tclass.class_header().empty())
1280 os << from_ascii(tclass.class_header());
1281 else if (runparams.flavor == OutputParams::XML)
1282 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1283 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1285 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1287 docstring preamble = from_utf8(params().preamble);
1288 if (runparams.flavor != OutputParams::XML ) {
1289 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1290 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1291 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1292 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1295 string const name = runparams.nice ? changeExtension(fileName(), ".sgml")
1297 preamble += features.getIncludedFiles(name);
1298 preamble += features.getLyXSGMLEntities();
1300 if (!preamble.empty()) {
1301 os << "\n [ " << preamble << " ]";
1306 string top = top_element;
1308 if (runparams.flavor == OutputParams::XML)
1309 top += params().language->code();
1311 top += params().language->code().substr(0,2);
1314 if (!params().options.empty()) {
1316 top += params().options;
1319 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1320 << " file was created by LyX " << lyx_version
1321 << "\n See http://www.lyx.org/ for more information -->\n";
1323 params().getTextClass().counters().reset();
1325 loadChildDocuments(*this);
1327 sgml::openTag(os, top);
1329 docbookParagraphs(paragraphs(), *this, os, runparams);
1330 sgml::closeTag(os, top_element);
1334 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1335 // Other flags: -wall -v0 -x
1336 int Buffer::runChktex()
1340 // get LaTeX-Filename
1341 FileName const path(temppath());
1342 string const name = addName(path.absFilename(), getLatexName());
1343 string const org_path = filePath();
1345 support::Path p(path); // path to LaTeX file
1346 message(_("Running chktex..."));
1348 // Generate the LaTeX file if neccessary
1349 OutputParams runparams(¶ms().encoding());
1350 runparams.flavor = OutputParams::LATEX;
1351 runparams.nice = false;
1352 makeLaTeXFile(FileName(name), org_path, runparams);
1355 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1356 int const res = chktex.run(terr); // run chktex
1359 Alert::error(_("chktex failure"),
1360 _("Could not run chktex successfully."));
1361 } else if (res > 0) {
1362 ErrorList & errorList = pimpl_->errorLists["ChkTeX"];
1363 // Clear out old errors
1365 // Fill-in the error list with the TeX errors
1366 bufferErrors(*this, terr, errorList);
1377 void Buffer::validate(LaTeXFeatures & features) const
1379 TextClass const & tclass = params().getTextClass();
1381 if (params().outputChanges) {
1382 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1383 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1384 LaTeXFeatures::isAvailable("xcolor");
1386 if (features.runparams().flavor == OutputParams::LATEX) {
1388 features.require("ct-dvipost");
1389 features.require("dvipost");
1390 } else if (xcolorsoul) {
1391 features.require("ct-xcolor-soul");
1392 features.require("soul");
1393 features.require("xcolor");
1395 features.require("ct-none");
1397 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1399 features.require("ct-xcolor-soul");
1400 features.require("soul");
1401 features.require("xcolor");
1402 features.require("pdfcolmk"); // improves color handling in PDF output
1404 features.require("ct-none");
1409 // AMS Style is at document level
1410 if (params().use_amsmath == BufferParams::package_on
1411 || tclass.provides("amsmath"))
1412 features.require("amsmath");
1413 if (params().use_esint == BufferParams::package_on)
1414 features.require("esint");
1416 loadChildDocuments(*this);
1418 for_each(paragraphs().begin(), paragraphs().end(),
1419 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1421 // the bullet shapes are buffer level not paragraph level
1422 // so they are tested here
1423 for (int i = 0; i < 4; ++i) {
1424 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1425 int const font = params().user_defined_bullet(i).getFont();
1427 int const c = params()
1428 .user_defined_bullet(i)
1435 features.require("latexsym");
1437 } else if (font == 1) {
1438 features.require("amssymb");
1439 } else if ((font >= 2 && font <= 5)) {
1440 features.require("pifont");
1445 if (lyxerr.debugging(Debug::LATEX)) {
1446 features.showStruct();
1451 void Buffer::getLabelList(vector<docstring> & list) const
1453 /// if this is a child document and the parent is already loaded
1454 /// Use the parent's list instead [ale990407]
1455 Buffer const * tmp = getMasterBuffer();
1457 lyxerr << "getMasterBuffer() failed!" << endl;
1461 tmp->getLabelList(list);
1465 loadChildDocuments(*this);
1467 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1468 it.nextInset()->getLabelList(*this, list);
1472 void Buffer::updateBibfilesCache()
1474 // if this is a child document and the parent is already loaded
1475 // update the parent's cache instead
1476 Buffer * tmp = getMasterBuffer();
1479 tmp->updateBibfilesCache();
1483 bibfilesCache_.clear();
1484 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1485 if (it->lyxCode() == Inset::BIBTEX_CODE) {
1486 InsetBibtex const & inset =
1487 static_cast<InsetBibtex const &>(*it);
1488 vector<FileName> const bibfiles = inset.getFiles(*this);
1489 bibfilesCache_.insert(bibfilesCache_.end(),
1492 } else if (it->lyxCode() == Inset::INCLUDE_CODE) {
1493 InsetInclude & inset =
1494 static_cast<InsetInclude &>(*it);
1495 inset.updateBibfilesCache(*this);
1496 vector<FileName> const & bibfiles =
1497 inset.getBibfilesCache(*this);
1498 bibfilesCache_.insert(bibfilesCache_.end(),
1506 vector<FileName> const & Buffer::getBibfilesCache() const
1508 // if this is a child document and the parent is already loaded
1509 // use the parent's cache instead
1510 Buffer const * tmp = getMasterBuffer();
1513 return tmp->getBibfilesCache();
1515 // We update the cache when first used instead of at loading time.
1516 if (bibfilesCache_.empty())
1517 const_cast<Buffer *>(this)->updateBibfilesCache();
1519 return bibfilesCache_;
1523 bool Buffer::isDepClean(string const & name) const
1525 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1526 if (it == pimpl_->dep_clean.end())
1532 void Buffer::markDepClean(string const & name)
1534 pimpl_->dep_clean[name] = true;
1538 bool Buffer::dispatch(string const & command, bool * result)
1540 return dispatch(lyxaction.lookupFunc(command), result);
1544 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1546 bool dispatched = true;
1548 switch (func.action) {
1549 case LFUN_BUFFER_EXPORT: {
1550 bool const tmp = Exporter::Export(this, to_utf8(func.argument()), false);
1563 void Buffer::changeLanguage(Language const * from, Language const * to)
1568 for_each(par_iterator_begin(),
1570 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1574 bool Buffer::isMultiLingual() const
1576 ParConstIterator end = par_iterator_end();
1577 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1578 if (it->isMultiLingual(params()))
1585 ParIterator Buffer::getParFromID(int const id) const
1587 ParConstIterator it = par_iterator_begin();
1588 ParConstIterator const end = par_iterator_end();
1591 // John says this is called with id == -1 from undo
1592 lyxerr << "getParFromID(), id: " << id << endl;
1596 for (; it != end; ++it)
1604 bool Buffer::hasParWithID(int const id) const
1606 ParConstIterator const it = getParFromID(id);
1607 return it != par_iterator_end();
1611 ParIterator Buffer::par_iterator_begin()
1613 return lyx::par_iterator_begin(inset());
1617 ParIterator Buffer::par_iterator_end()
1619 return lyx::par_iterator_end(inset());
1623 ParConstIterator Buffer::par_iterator_begin() const
1625 return lyx::par_const_iterator_begin(inset());
1629 ParConstIterator Buffer::par_iterator_end() const
1631 return lyx::par_const_iterator_end(inset());
1635 Language const * Buffer::getLanguage() const
1637 return params().language;
1641 docstring const Buffer::B_(string const & l10n) const
1643 return params().B_(l10n);
1647 bool Buffer::isClean() const
1649 return pimpl_->lyx_clean;
1653 bool Buffer::isBakClean() const
1655 return pimpl_->bak_clean;
1659 bool Buffer::isExternallyModified(CheckMethod method) const
1661 BOOST_ASSERT(fs::exists(pimpl_->filename.toFilesystemEncoding()));
1662 // if method == timestamp, check timestamp before checksum
1663 return (method == checksum_method
1664 || pimpl_->timestamp_ != fs::last_write_time(pimpl_->filename.toFilesystemEncoding()))
1665 && pimpl_->checksum_ != sum(pimpl_->filename);
1669 void Buffer::saveCheckSum(string const & file) const
1671 if (fs::exists(file)) {
1672 pimpl_->timestamp_ = fs::last_write_time(file);
1673 pimpl_->checksum_ = sum(FileName(file));
1675 // in the case of save to a new file.
1676 pimpl_->timestamp_ = 0;
1677 pimpl_->checksum_ = 0;
1682 void Buffer::markClean() const
1684 if (!pimpl_->lyx_clean) {
1685 pimpl_->lyx_clean = true;
1688 // if the .lyx file has been saved, we don't need an
1690 pimpl_->bak_clean = true;
1694 void Buffer::markBakClean() const
1696 pimpl_->bak_clean = true;
1700 void Buffer::setUnnamed(bool flag)
1702 pimpl_->unnamed = flag;
1706 bool Buffer::isUnnamed() const
1708 return pimpl_->unnamed;
1712 // FIXME: this function should be moved to buffer_pimpl.C
1713 void Buffer::markDirty()
1715 if (pimpl_->lyx_clean) {
1716 pimpl_->lyx_clean = false;
1719 pimpl_->bak_clean = false;
1721 DepClean::iterator it = pimpl_->dep_clean.begin();
1722 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1724 for (; it != end; ++it)
1729 string const Buffer::fileName() const
1731 return pimpl_->filename.absFilename();
1735 string const & Buffer::filePath() const
1737 return params().filepath;
1741 bool Buffer::isReadonly() const
1743 return pimpl_->read_only;
1747 void Buffer::setParentName(string const & name)
1749 if (name == pimpl_->filename.absFilename())
1750 // Avoids recursive include.
1751 params().parentname.clear();
1753 params().parentname = name;
1757 Buffer const * Buffer::getMasterBuffer() const
1759 if (!params().parentname.empty()
1760 && theBufferList().exists(params().parentname)) {
1761 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1762 //We need to check if the parent is us...
1763 //FIXME RECURSIVE INCLUDE
1764 //This is not sufficient, since recursive includes could be downstream.
1765 if (buf && buf != this)
1766 return buf->getMasterBuffer();
1773 Buffer * Buffer::getMasterBuffer()
1775 if (!params().parentname.empty()
1776 && theBufferList().exists(params().parentname)) {
1777 Buffer * buf = theBufferList().getBuffer(params().parentname);
1778 //We need to check if the parent is us...
1779 //FIXME RECURSIVE INCLUDE
1780 //This is not sufficient, since recursive includes could be downstream.
1781 if (buf && buf != this)
1782 return buf->getMasterBuffer();
1789 MacroData const & Buffer::getMacro(docstring const & name) const
1791 return pimpl_->macros.get(name);
1795 bool Buffer::hasMacro(docstring const & name) const
1797 return pimpl_->macros.has(name);
1801 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1803 MacroTable::globalMacros().insert(name, data);
1804 pimpl_->macros.insert(name, data);
1808 void Buffer::buildMacros()
1810 // Start with global table.
1811 pimpl_->macros = MacroTable::globalMacros();
1814 ParagraphList const & pars = text().paragraphs();
1815 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1816 //lyxerr << "searching main par " << i
1817 // << " for macro definitions" << std::endl;
1818 InsetList const & insets = pars[i].insetlist;
1819 InsetList::const_iterator it = insets.begin();
1820 InsetList::const_iterator end = insets.end();
1821 for ( ; it != end; ++it) {
1822 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1823 if (it->inset->lyxCode() == Inset::MATHMACRO_CODE) {
1824 MathMacroTemplate const & mac
1825 = static_cast<MathMacroTemplate const &>(*it->inset);
1826 insertMacro(mac.name(), mac.asMacroData());
1833 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1836 //FIXME: This does not work for child documents yet.
1837 BOOST_ASSERT(code == Inset::CITE_CODE || code == Inset::REF_CODE);
1838 // Check if the label 'from' appears more than once
1839 vector<docstring> labels;
1841 if (code == Inset::CITE_CODE) {
1843 keys.fillWithBibKeys(this);
1844 BiblioInfo::const_iterator bit = keys.begin();
1845 BiblioInfo::const_iterator bend = keys.end();
1847 for (; bit != bend; ++bit)
1849 labels.push_back(bit->first);
1851 getLabelList(labels);
1853 if (std::count(labels.begin(), labels.end(), from) > 1)
1856 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1857 if (it->lyxCode() == code) {
1858 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1859 inset.replaceContents(to_utf8(from), to_utf8(to));
1865 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1866 pit_type par_end, bool full_source)
1868 OutputParams runparams(¶ms().encoding());
1869 runparams.nice = true;
1870 runparams.flavor = OutputParams::LATEX;
1871 runparams.linelen = lyxrc.plaintext_linelen;
1872 // No side effect of file copying and image conversion
1873 runparams.dryrun = true;
1877 os << "% " << _("Preview source code") << "\n\n";
1881 writeLaTeXSource(os, filePath(), runparams, true, true);
1883 writeDocBookSource(os, fileName(), runparams, false);
1886 runparams.par_begin = par_begin;
1887 runparams.par_end = par_end;
1888 if (par_begin + 1 == par_end)
1890 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1894 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1895 convert<docstring>(par_begin),
1896 convert<docstring>(par_end - 1))
1900 // output paragraphs
1902 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1905 docbookParagraphs(paragraphs(), *this, os, runparams);
1911 ErrorList const & Buffer::errorList(string const & type) const
1913 static ErrorList const emptyErrorList;
1914 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1915 if (I == pimpl_->errorLists.end())
1916 return emptyErrorList;
1922 ErrorList & Buffer::errorList(string const & type)
1924 return pimpl_->errorLists[type];
1928 void Buffer::structureChanged() const
1931 gui_->structureChanged();
1935 void Buffer::embeddingChanged() const
1938 gui_->embeddingChanged();
1942 void Buffer::errors(std::string const & err) const
1949 void Buffer::message(docstring const & msg) const
1956 void Buffer::busy(bool on) const
1963 void Buffer::readonly(bool on) const
1970 void Buffer::updateTitles() const
1973 gui_->updateTitles();
1977 void Buffer::resetAutosaveTimers() const
1980 gui_->resetAutosaveTimers();
1984 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1993 class AutoSaveBuffer : public support::ForkedProcess {
1996 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
1997 : buffer_(buffer), fname_(fname) {}
1999 virtual boost::shared_ptr<ForkedProcess> clone() const
2001 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2006 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2007 from_utf8(fname_.absFilename())));
2008 return run(DontWait);
2012 virtual int generateChild();
2014 Buffer const & buffer_;
2019 int AutoSaveBuffer::generateChild()
2021 // tmp_ret will be located (usually) in /tmp
2022 // will that be a problem?
2023 pid_t const pid = fork(); // If you want to debug the autosave
2024 // you should set pid to -1, and comment out the
2026 if (pid == 0 || pid == -1) {
2027 // pid = -1 signifies that lyx was unable
2028 // to fork. But we will do the save
2030 bool failed = false;
2032 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2033 if (!tmp_ret.empty()) {
2034 buffer_.writeFile(tmp_ret);
2035 // assume successful write of tmp_ret
2036 if (!rename(tmp_ret, fname_)) {
2038 // most likely couldn't move between
2039 // filesystems unless write of tmp_ret
2040 // failed so remove tmp file (if it
2049 // failed to write/rename tmp_ret so try writing direct
2050 if (!buffer_.writeFile(fname_)) {
2051 // It is dangerous to do this in the child,
2052 // but safe in the parent, so...
2053 if (pid == -1) // emit message signal.
2054 buffer_.message(_("Autosave failed!"));
2057 if (pid == 0) { // we are the child so...
2067 // Perfect target for a thread...
2068 void Buffer::autoSave() const
2070 if (isBakClean() || isReadonly()) {
2071 // We don't save now, but we'll try again later
2072 resetAutosaveTimers();
2076 // emit message signal.
2077 message(_("Autosaving current document..."));
2079 // create autosave filename
2080 string fname = filePath();
2082 fname += onlyFilename(fileName());
2085 AutoSaveBuffer autosave(*this, FileName(fname));
2089 resetAutosaveTimers();
2093 /** Write a buffer to a new file name and rename the buffer
2094 according to the new file name.
2096 This function is e.g. used by menu callbacks and
2097 LFUN_BUFFER_WRITE_AS.
2099 If 'newname' is empty (the default), the user is asked via a
2100 dialog for the buffer's new name and location.
2102 If 'newname' is non-empty and has an absolute path, that is used.
2103 Otherwise the base directory of the buffer is used as the base
2104 for any relative path in 'newname'.
2107 bool Buffer::writeAs(string const & newname)
2109 string fname = fileName();
2110 string const oldname = fname;
2112 if (newname.empty()) { /// No argument? Ask user through dialog
2115 FileDialog fileDlg(_("Choose a filename to save document as"),
2116 LFUN_BUFFER_WRITE_AS,
2117 make_pair(_("Documents|#o#O"),
2118 from_utf8(lyxrc.document_path)),
2119 make_pair(_("Templates|#T#t"),
2120 from_utf8(lyxrc.template_path)));
2122 if (!support::isLyXFilename(fname))
2125 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2127 FileDialog::Result result =
2128 fileDlg.save(from_utf8(onlyPath(fname)),
2130 from_utf8(onlyFilename(fname)));
2132 if (result.first == FileDialog::Later)
2135 fname = to_utf8(result.second);
2140 // Make sure the absolute filename ends with appropriate suffix
2141 fname = makeAbsPath(fname).absFilename();
2142 if (!support::isLyXFilename(fname))
2146 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2148 if (fs::exists(FileName(fname).toFilesystemEncoding())) {
2149 docstring const file = makeDisplayPath(fname, 30);
2150 docstring text = bformat(_("The document %1$s already "
2151 "exists.\n\nDo you want to "
2152 "overwrite that document?"),
2154 int const ret = Alert::prompt(_("Overwrite document?"),
2155 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2161 // Ok, change the name of the buffer
2164 bool unnamed = isUnnamed();
2166 saveCheckSum(fname);
2169 setFileName(oldname);
2170 setUnnamed(unnamed);
2171 saveCheckSum(oldname);
2175 removeAutosaveFile(oldname);
2180 bool Buffer::menuWrite()
2183 LyX::ref().session().lastFiles().add(FileName(fileName()));
2187 // FIXME: we don't tell the user *WHY* the save failed !!
2189 docstring const file = makeDisplayPath(fileName(), 30);
2191 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2192 "Do you want to rename the document and "
2193 "try again?"), file);
2194 int const ret = Alert::prompt(_("Rename and save?"),
2195 text, 0, 1, _("&Rename"), _("&Cancel"));