]> git.lyx.org Git - lyx.git/blob - src/lyx_cb.C
ab444b148b8615ab95edfc89db92f22f618c8d7c
[lyx.git] / src / lyx_cb.C
1 /**
2  * \file lyx_cb.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Angus Leeming
8  * \author John Levon
9  * \author André Pönitz
10  * \author Jürgen Vigna
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "lyx_cb.h"
18
19 #include "buffer.h"
20 #include "BufferView.h"
21 #include "buffer_funcs.h"
22 #include "cursor.h"
23 #include "debug.h"
24 #include "gettext.h"
25 #include "session.h"
26 #include "LaTeXFeatures.h"
27 #include "lyx_main.h"
28 #include "lyxlayout.h"
29 #include "lyxrc.h"
30 #include "lyxtext.h"
31 #include "paragraph.h"
32
33 #include "frontends/Alert.h"
34 #include "frontends/Application.h"
35 #include "frontends/FileDialog.h"
36 #include "frontends/lyx_gui.h"
37
38 #include "support/filefilterlist.h"
39 #include "support/filetools.h"
40 #include "support/forkedcall.h"
41 #include "support/fs_extras.h"
42 #include "support/lyxlib.h"
43 #include "support/package.h"
44 #include "support/path.h"
45 #include "support/systemcall.h"
46
47 #if !defined (HAVE_FORK)
48 # define fork() -1
49 #endif
50
51 #include <boost/shared_ptr.hpp>
52 #include <boost/filesystem/operations.hpp>
53
54 #include <cerrno>
55 #include <fstream>
56
57 using lyx::docstring;
58 using lyx::support::addName;
59 using lyx::support::bformat;
60 using lyx::support::destroyDir;
61 using lyx::support::FileFilterList;
62 using lyx::support::ForkedProcess;
63 using lyx::support::isLyXFilename;
64 using lyx::support::libFileSearch;
65 using lyx::support::makeAbsPath;
66 using lyx::support::makeDisplayPath;
67 using lyx::support::onlyFilename;
68 using lyx::support::onlyPath;
69 using lyx::support::package;
70 using lyx::support::removeAutosaveFile;
71 using lyx::support::rename;
72 using lyx::support::split;
73 using lyx::support::Systemcall;
74 using lyx::support::tempName;
75 using lyx::support::unlink;
76
77 using boost::shared_ptr;
78
79 namespace fs = boost::filesystem;
80
81 using std::back_inserter;
82 using std::copy;
83 using std::endl;
84 using std::make_pair;
85 using std::string;
86 using std::ifstream;
87 using std::ios;
88 using std::istream_iterator;
89
90
91 // this should be static, but I need it in buffer.C
92 bool quitting;  // flag, that we are quitting the program
93
94
95 //
96 // Menu callbacks
97 //
98
99 bool menuWrite(Buffer * buffer)
100 {
101         if (buffer->save()) {
102                 LyX::ref().session().addLastFile(buffer->fileName());
103                 return true;
104         }
105
106         // FIXME: we don't tell the user *WHY* the save failed !!
107
108         docstring const file = makeDisplayPath(buffer->fileName(), 30);
109
110         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
111                                              "Do you want to rename the document and try again?"), file);
112         int const ret = Alert::prompt(_("Rename and save?"),
113                 text, 0, 1, _("&Rename"), _("&Cancel"));
114
115         if (ret == 0)
116                 return writeAs(buffer);
117         return false;
118 }
119
120
121
122 bool writeAs(Buffer * buffer, string const & filename)
123 {
124         string fname = buffer->fileName();
125         string const oldname = fname;
126
127         if (filename.empty()) {
128
129                 FileDialog fileDlg(lyx::to_utf8(_("Choose a filename to save document as")),
130                         LFUN_BUFFER_WRITE_AS,
131                         make_pair(string(lyx::to_utf8(_("Documents|#o#O"))),
132                                   string(lyxrc.document_path)),
133                         make_pair(string(lyx::to_utf8(_("Templates|#T#t"))),
134                                   string(lyxrc.template_path)));
135
136                 if (!isLyXFilename(fname))
137                         fname += ".lyx";
138
139                 FileFilterList const filter (lyx::to_utf8(_("LyX Documents (*.lyx)")));
140
141                 FileDialog::Result result =
142                         fileDlg.save(onlyPath(fname),
143                                      filter,
144                                      onlyFilename(fname));
145
146                 if (result.first == FileDialog::Later)
147                         return false;
148
149                 fname = result.second;
150
151                 if (fname.empty())
152                         return false;
153
154                 // Make sure the absolute filename ends with appropriate suffix
155                 fname = makeAbsPath(fname);
156                 if (!isLyXFilename(fname))
157                         fname += ".lyx";
158         } else
159                 fname = filename;
160
161         if (fs::exists(fname)) {
162                 docstring const file = makeDisplayPath(fname, 30);
163                 docstring text = bformat(_("The document %1$s already exists.\n\n"
164                                                      "Do you want to over-write that document?"), file);
165                 int const ret = Alert::prompt(_("Over-write document?"),
166                         text, 0, 1, _("&Over-write"), _("&Cancel"));
167
168                 if (ret == 1)
169                         return false;
170         }
171
172         // Ok, change the name of the buffer
173         buffer->setFileName(fname);
174         buffer->markDirty();
175         bool unnamed = buffer->isUnnamed();
176         buffer->setUnnamed(false);
177
178         if (!menuWrite(buffer)) {
179                 buffer->setFileName(oldname);
180                 buffer->setUnnamed(unnamed);
181                 return false;
182         }
183
184         removeAutosaveFile(oldname);
185         return true;
186 }
187
188
189 void quitLyX(bool noask)
190 {
191         lyxerr[Debug::INFO] << "Running QuitLyX." << endl;
192
193         if (lyx_gui::use_gui) {
194                 if (!noask && !theApp->bufferList().quitWriteAll())
195                         return;
196
197                 LyX::cref().session().writeFile();
198         }
199
200         // Set a flag that we do quitting from the program,
201         // so no refreshes are necessary.
202         quitting = true;
203
204         // close buffers first
205         theApp->bufferList().closeAll();
206
207         // do any other cleanup procedures now
208         lyxerr[Debug::INFO] << "Deleting tmp dir " << package().temp_dir() << endl;
209
210         if (!destroyDir(package().temp_dir())) {
211                 docstring const msg =
212                         bformat(_("Unable to remove the temporary directory %1$s"),
213                         lyx::from_utf8(package().temp_dir()));
214                 Alert::warning(_("Unable to remove temporary directory"), msg);
215         }
216
217         lyx_gui::exit(0);
218 }
219
220
221 namespace {
222
223 class AutoSaveBuffer : public ForkedProcess {
224 public:
225         ///
226         AutoSaveBuffer(BufferView & bv, string const & fname)
227                 : bv_(bv), fname_(fname) {}
228         ///
229         virtual shared_ptr<ForkedProcess> clone() const
230         {
231                 return shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
232         }
233         ///
234         int start();
235 private:
236         ///
237         virtual int generateChild();
238         ///
239         BufferView & bv_;
240         string fname_;
241 };
242
243
244 int AutoSaveBuffer::start()
245 {
246         command_ = lyx::to_utf8(bformat(_("Auto-saving %1$s"), lyx::from_utf8(fname_)));
247         return run(DontWait);
248 }
249
250
251 int AutoSaveBuffer::generateChild()
252 {
253         // tmp_ret will be located (usually) in /tmp
254         // will that be a problem?
255         pid_t const pid = fork(); // If you want to debug the autosave
256         // you should set pid to -1, and comment out the
257         // fork.
258         if (pid == 0 || pid == -1) {
259                 // pid = -1 signifies that lyx was unable
260                 // to fork. But we will do the save
261                 // anyway.
262                 bool failed = false;
263
264                 string const tmp_ret = tempName(string(), "lyxauto");
265                 if (!tmp_ret.empty()) {
266                         bv_.buffer()->writeFile(tmp_ret);
267                         // assume successful write of tmp_ret
268                         if (!rename(tmp_ret, fname_)) {
269                                 failed = true;
270                                 // most likely couldn't move between filesystems
271                                 // unless write of tmp_ret failed
272                                 // so remove tmp file (if it exists)
273                                 unlink(tmp_ret);
274                         }
275                 } else {
276                         failed = true;
277                 }
278
279                 if (failed) {
280                         // failed to write/rename tmp_ret so try writing direct
281                         if (!bv_.buffer()->writeFile(fname_)) {
282                                 // It is dangerous to do this in the child,
283                                 // but safe in the parent, so...
284                                 if (pid == -1)
285                                         // emit message signal.
286                                         bv_.buffer()->message(_("Autosave failed!"));
287                         }
288                 }
289                 if (pid == 0) { // we are the child so...
290                         _exit(0);
291                 }
292         }
293         return pid;
294 }
295
296 } // namespace anon
297
298
299 void autoSave(BufferView * bv)
300         // should probably be moved into BufferList (Lgb)
301         // Perfect target for a thread...
302 {
303         if (!bv->buffer())
304                 return;
305
306         if (bv->buffer()->isBakClean() || bv->buffer()->isReadonly()) {
307                 // We don't save now, but we'll try again later
308                 bv->buffer()->resetAutosaveTimers();
309                 return;
310         }
311
312         // emit message signal.
313         bv->buffer()->message(_("Autosaving current document..."));
314
315         // create autosave filename
316         string fname = bv->buffer()->filePath();
317         fname += '#';
318         fname += onlyFilename(bv->buffer()->fileName());
319         fname += '#';
320
321         AutoSaveBuffer autosave(*bv, fname);
322         autosave.start();
323
324         bv->buffer()->markBakClean();
325         bv->buffer()->resetAutosaveTimers();
326 }
327
328
329 //
330 // Copyright CHT Software Service GmbH
331 // Uwe C. Schroeder
332 //
333 // create new file with template
334 // SERVERCMD !
335 //
336 void newFile(BufferView * bv, string const & filename)
337 {
338         // Split argument by :
339         string name;
340         string tmpname = split(filename, name, ':');
341         lyxerr[Debug::INFO] << "Arg is " << filename
342                             << "\nName is " << name
343                             << "\nTemplate is " << tmpname << endl;
344
345         Buffer * const b = newFile(name, tmpname);
346         if (b)
347                 bv->setBuffer(b);
348 }
349
350
351 // Insert ascii file (if filename is empty, prompt for one)
352 void insertAsciiFile(BufferView * bv, string const & f, bool asParagraph)
353 {
354         if (!bv->buffer())
355                 return;
356
357         // FIXME: We don't know the encoding of the file
358         docstring const tmpstr = lyx::from_utf8(getContentsOfAsciiFile(bv, f, asParagraph));
359         if (tmpstr.empty())
360                 return;
361
362         // clear the selection
363         LyXText const & text = bv->buffer()->text();
364         if (&text == bv->getLyXText())
365                 bv->cursor().clearSelection();
366         if (asParagraph)
367                 bv->getLyXText()->insertStringAsParagraphs(bv->cursor(), tmpstr);
368         else
369                 bv->getLyXText()->insertStringAsLines(bv->cursor(), tmpstr);
370         bv->update();
371 }
372
373
374 // Insert ascii file (if filename is empty, prompt for one)
375 string getContentsOfAsciiFile(BufferView * bv, string const & f, bool asParagraph)
376 {
377         string fname = f;
378
379         if (fname.empty()) {
380                 FileDialog fileDlg(lyx::to_utf8(_("Select file to insert")),
381                         (asParagraph) ? LFUN_FILE_INSERT_ASCII_PARA : LFUN_FILE_INSERT_ASCII);
382
383                 FileDialog::Result result =
384                         fileDlg.open(bv->buffer()->filePath(),
385                                      FileFilterList(), string());
386
387                 if (result.first == FileDialog::Later)
388                         return string();
389
390                 fname = result.second;
391
392                 if (fname.empty())
393                         return string();
394         }
395
396         if (!fs::is_readable(fname)) {
397                 docstring const error = lyx::from_ascii(strerror(errno));
398                 docstring const file = makeDisplayPath(fname, 50);
399                 docstring const text = bformat(_("Could not read the specified document\n"
400                                                            "%1$s\ndue to the error: %2$s"), file, error);
401                 Alert::error(_("Could not read file"), text);
402                 return string();
403         }
404
405         ifstream ifs(fname.c_str());
406         if (!ifs) {
407                 docstring const error = lyx::from_ascii(strerror(errno));
408                 docstring const file = makeDisplayPath(fname, 50);
409                 docstring const text = bformat(_("Could not open the specified document\n"
410                                                            "%1$s\ndue to the error: %2$s"), file, error);
411                 Alert::error(_("Could not open file"), text);
412                 return string();
413         }
414
415         ifs.unsetf(ios::skipws);
416         istream_iterator<char> ii(ifs);
417         istream_iterator<char> end;
418 #if !defined(USE_INCLUDED_STRING) && !defined(STD_STRING_IS_GOOD)
419         // We use this until the compilers get better...
420         std::vector<char> tmp;
421         copy(ii, end, back_inserter(tmp));
422         string const tmpstr(tmp.begin(), tmp.end());
423 #else
424         // This is what we want to use and what we will use once the
425         // compilers get good enough.
426         //string tmpstr(ii, end); // yet a reason for using std::string
427         // alternate approach to get the file into a string:
428         string tmpstr;
429         copy(ii, end, back_inserter(tmpstr));
430 #endif
431
432         return tmpstr;
433 }
434
435
436 // This function runs "configure" and then rereads lyx.defaults to
437 // reconfigure the automatic settings.
438 void reconfigure(BufferView * bv)
439 {
440         // emit message signal.
441         bv->buffer()->message(_("Running configure..."));
442
443         // Run configure in user lyx directory
444         lyx::support::Path p(package().user_support());
445         string const configure_command = package().configure_command();
446         Systemcall one;
447         one.startscript(Systemcall::Wait, configure_command);
448         p.pop();
449         // emit message signal.
450         bv->buffer()->message(_("Reloading configuration..."));
451         lyxrc.read(libFileSearch(string(), "lyxrc.defaults"));
452         // Re-read packages.lst
453         LaTeXFeatures::getAvailable();
454
455         Alert::information(_("System reconfigured"),
456                            _("The system has been reconfigured.\n"
457                                           "You need to restart LyX to make use of any\n"
458                                           "updated document class specifications."));
459 }