]> git.lyx.org Git - lyx.git/blob - src/lyx_cb.C
8bb730868f2d1a8c47bb67b376c496f414f150f4
[lyx.git] / src / lyx_cb.C
1 /* This file is part of
2  * ====================================================== 
3  * 
4  *           LyX, The Document Processor
5  *       
6  *          Copyright 1995 Matthias Ettrich,
7  *          Copyright 1995-2001 The LyX Team.
8  *
9  * ====================================================== */
10
11 #include <config.h>
12
13 #include <fstream>
14 #include <algorithm>
15 #include <utility> 
16 #include <iostream>
17
18 #include FORMS_H_LOCATION
19
20 #include "lyx_cb.h"
21 #include "lyx_gui_misc.h"
22 #include "lyx_main.h"
23 #include "bufferlist.h"
24 #include "bufferview_funcs.h"
25 #include "lastfiles.h"
26 #include "LyXView.h"
27 #include "lyxrc.h"
28 #include "lyxtext.h"
29 #include "frontends/FileDialog.h"
30 #include "insets/insetlabel.h"
31 #include "support/FileInfo.h"
32 #include "support/filetools.h"
33 #include "support/path.h"
34 #include "support/syscall.h"
35 #include "gettext.h"
36 #include "BufferView.h"
37
38 using std::vector;
39 using std::ifstream;
40 using std::copy;
41 using std::endl;
42 using std::ios;
43 using std::back_inserter;
44 using std::istream_iterator;
45 using std::pair;
46 using std::make_pair;
47
48 extern BufferList bufferlist;
49 // this should be static, but I need it in buffer.C
50 bool quitting;  // flag, that we are quitting the program
51 extern bool finished; // all cleanup done just let it run through now.
52
53 /* 
54    This is the inset locking stuff needed for mathed --------------------
55
56    an inset can simple call LockInset in it's edit call and *ONLY* in it's
57    edit call.
58    Inset::Edit() can only be called by the main lyx module.
59
60    Then the inset may modify the menu's and/or iconbars. 
61
62    Unlocking is either done by LyX or the inset itself with a UnlockInset-call
63
64    During the lock, all button and keyboard events will be modified
65    and send to the inset through the following inset-features. Note that
66    Inset::InsetUnlock will be called from inside UnlockInset. It is meant
67    to contain the code for restoring the menus and things like this.
68
69    
70    virtual void InsetButtonPress(int x, int y, int button);
71    virtual void InsetButtonRelease(int x, int y, int button);
72    virtual void InsetKeyPress(XKeyEvent *ev);
73    virtual void InsetMotionNotify(int x, int y, int state);
74    virtual void InsetUnlock();
75
76    If a inset wishes any redraw and/or update it just has to call
77    UpdateInset(this).
78    It's is completly irrelevant, where the inset is. UpdateInset will
79    find it in any paragraph in any buffer. 
80    Of course the_locking_inset and the insets in the current paragraph/buffer
81    are checked first, so no performance problem should occur.
82    
83    Hope that's ok for the beginning, Alejandro,
84    sorry that I needed so much time,
85
86                   Matthias
87    */
88
89 //void UpdateInset(BufferView * bv, Inset * inset, bool mark_dirty = true);
90
91 /* these functions return 1 if an error occured, 
92    otherwise 0 */
93 // Now they work only for updatable insets. [Alejandro 080596]
94 //int LockInset(UpdatableInset * inset);
95 void ToggleLockedInsetCursor(int x, int y, int asc, int desc);
96 //void FitLockedInsetCursor(long x, long y, int asc, int desc);
97 //int UnlockInset(UpdatableInset * inset);
98 //void LockedInsetStoreUndo(Undo::undo_kind kind);
99
100 /* this is for asyncron updating. UpdateInsetUpdateList will be called
101    automatically from LyX. Just insert the Inset into the Updatelist */
102 //void UpdateInsetUpdateList();
103 //void PutInsetIntoInsetUpdateList(Inset * inset);
104
105 //InsetUpdateStruct * InsetUpdateList = 0;
106
107
108 /*
109   -----------------------------------------------------------------------
110  */
111
112
113 void ShowMessage(Buffer const * buf,
114                  string const & msg1,
115                  string const & msg2,
116                  string const & msg3)
117 {
118         if (lyxrc.use_gui) {
119                 string const str = msg1 + ' ' + msg2 + ' ' + msg3;
120                 buf->getUser()->owner()->message(str);
121         } else
122                 lyxerr << msg1 << msg2 << msg3 << endl;
123 }
124
125
126 //
127 // Menu callbacks
128 //
129
130 //
131 // File menu
132 //
133 // should be moved to lyxfunc.C
134 bool MenuWrite(BufferView * bv, Buffer * buffer)
135 {
136         // FIXME: needed ?
137         XFlush(fl_get_display());
138  
139         if (!buffer->save()) {
140                 string const fname = buffer->fileName();
141                 string const s = MakeAbsPath(fname);
142                 if (AskQuestion(_("Save failed. Rename and try again?"),
143                                 MakeDisplayPath(s, 50),
144                                 _("(If not, document is not saved.)"))) {
145                         return WriteAs(bv, buffer);
146                 }
147                 return false;
148         } else
149                 lastfiles->newFile(buffer->fileName());
150         return true;
151 }
152
153
154
155 // should be moved to BufferView.C
156 // Half of this func should be in LyXView, the rest in BufferView.
157 bool WriteAs(BufferView * bv, Buffer * buffer, string const & filename)
158 {
159         string fname = buffer->fileName();
160         string oldname = fname;
161
162         if (filename.empty()) {
163
164                 FileDialog fileDlg(bv->owner(),
165                                    _("Choose a filename to save document as"),
166                         LFUN_WRITEAS,
167                         make_pair(string(_("Documents")),
168                                   string(lyxrc.document_path)),
169                         make_pair(string(_("Templates")),
170                                   string(lyxrc.template_path)));
171
172                 if (!IsLyXFilename(fname))
173                         fname += ".lyx";
174
175                 FileDialog::Result result =
176                         fileDlg.Select(OnlyPath(fname),
177                                        _("*.lyx|LyX Documents (*.lyx)"),
178                                        OnlyFilename(fname));
179
180                 if (result.first == FileDialog::Later)
181                         return false;
182
183                 fname = result.second;
184
185                 if (fname.empty())
186                         return false;
187
188                 // Make sure the absolute filename ends with appropriate suffix
189                 fname = MakeAbsPath(fname);
190                 if (!IsLyXFilename(fname))
191                         fname += ".lyx";
192         } else
193                 fname = filename;
194
195         // Same name as we have already?
196         if (!buffer->isUnnamed() && fname == oldname) {
197                 if (!AskQuestion(_("Same name as document already has:"),
198                                  MakeDisplayPath(fname, 50),
199                                  _("Save anyway?")))
200                         return false;
201                 // Falls through to name change and save
202         } 
203         // No, but do we have another file with this name open?
204         else if (!buffer->isUnnamed() && bufferlist.exists(fname)) {
205                 if (AskQuestion(_("Another document with same name open!"),
206                                 MakeDisplayPath(fname, 50),
207                                 _("Replace with current document?")))
208                         {
209                                 bufferlist.close(bufferlist.getBuffer(fname));
210
211                                 // Ok, change the name of the buffer, but don't save!
212                                 buffer->setFileName(fname);
213                                 buffer->markDirty();
214
215                                 ShowMessage(buffer, _("Document renamed to '"),
216                                                 MakeDisplayPath(fname), _("', but not saved..."));
217                 }
218                 return false;
219         } // Check whether the file exists
220         else {
221                 FileInfo const myfile(fname);
222                 if (myfile.isOK() && !AskQuestion(_("Document already exists:"), 
223                                                   MakeDisplayPath(fname, 50),
224                                                   _("Replace file?")))
225                         return false;
226         }
227
228         // Ok, change the name of the buffer
229         buffer->setFileName(fname);
230         buffer->markDirty();
231         bool unnamed = buffer->isUnnamed();
232         buffer->setUnnamed(false);
233
234         if (!MenuWrite(bv, buffer)) {
235             buffer->setFileName(oldname);
236             buffer->setUnnamed(unnamed);
237             ShowMessage(buffer, _("Document could not be saved!"),
238                         _("Holding the old name."), MakeDisplayPath(oldname));
239             return false;
240         }
241         // now remove the oldname autosave file if existant!
242         removeAutosaveFile(oldname);
243         return true;
244 }
245
246
247 int MenuRunChktex(Buffer * buffer)
248 {
249         int ret;
250
251         if (buffer->isSGML()) {
252                 WriteAlert(_("Chktex does not work with SGML derived documents."));
253                 return 0;
254         } else 
255                 ret = buffer->runChktex();
256    
257         if (ret >= 0) {
258                 string s;
259                 string t;
260                 if (ret == 0) {
261                         s = _("No warnings found.");
262                 } else if (ret == 1) {
263                         s = _("One warning found.");
264                         t = _("Use 'Edit->Go to Error' to find it.");
265                 } else {
266                         s += tostr(ret);
267                         s += _(" warnings found.");
268                         t = _("Use 'Edit->Go to Error' to find them.");
269                 }
270                 WriteAlert(_("Chktex run successfully"), s, t);
271         } else {
272                 WriteAlert(_("Error!"), _("It seems chktex does not work."));
273         }
274         return ret;
275 }
276
277
278 void QuitLyX()
279 {
280         lyxerr.debug() << "Running QuitLyX." << endl;
281
282         if (lyxrc.use_gui) {
283                 if (!bufferlist.QwriteAll())
284                         return;
285
286                 lastfiles->writeFile(lyxrc.lastfiles);
287         }
288
289         // Set a flag that we do quitting from the program,
290         // so no refreshes are necessary.
291         quitting = true;
292
293         // close buffers first
294         bufferlist.closeAll();
295
296         // do any other cleanup procedures now
297         lyxerr.debug() << "Deleting tmp dir " << system_tempdir << endl;
298
299         DestroyLyXTmpDir(system_tempdir);
300
301         finished = true;
302 }
303
304
305
306 void AutoSave(BufferView * bv)
307         // should probably be moved into BufferList (Lgb)
308         // Perfect target for a thread...
309 {
310         if (!bv->available())
311                 return;
312
313         if (bv->buffer()->isBakClean() || bv->buffer()->isReadonly()) {
314                 // We don't save now, but we'll try again later
315                 bv->owner()->resetAutosaveTimer();
316                 return;
317         }
318
319         bv->owner()->message(_("Autosaving current document..."));
320         
321         // create autosave filename
322         string fname =  OnlyPath(bv->buffer()->fileName());
323         fname += "#";
324         fname += OnlyFilename(bv->buffer()->fileName());
325         fname += "#";
326         
327         // tmp_ret will be located (usually) in /tmp
328         // will that be a problem?
329         pid_t const pid = fork(); // If you want to debug the autosave
330         // you should set pid to -1, and comment out the
331         // fork.
332         if (pid == 0 || pid == -1) {
333                 // pid = -1 signifies that lyx was unable
334                 // to fork. But we will do the save
335                 // anyway.
336                 bool failed = false;
337                 
338                 string const tmp_ret = lyx::tempName(string(), "lyxauto");
339                 if (!tmp_ret.empty()) {
340                         bv->buffer()->writeFile(tmp_ret, 1);
341                         // assume successful write of tmp_ret
342                         if (!lyx::rename(tmp_ret, fname)) {
343                                 failed = true;
344                                 // most likely couldn't move between filesystems
345                                 // unless write of tmp_ret failed
346                                 // so remove tmp file (if it exists)
347                                 lyx::unlink(tmp_ret);
348                         }
349                 } else {
350                         failed = true;
351                 }
352                 
353                 if (failed) {
354                         // failed to write/rename tmp_ret so try writing direct
355                         if (!bv->buffer()->writeFile(fname, 1)) {
356                                 // It is dangerous to do this in the child,
357                                 // but safe in the parent, so...
358                                 if (pid == -1)
359                                         bv->owner()->message(_("Autosave Failed!"));
360                         }
361                 }
362                 if (pid == 0) { // we are the child so...
363                         _exit(0);
364                 }
365         }
366         
367         bv->buffer()->markBakClean();
368         bv->owner()->resetAutosaveTimer();
369 }
370
371
372 //
373 // Copyright CHT Software Service GmbH
374 // Uwe C. Schroeder
375 //
376 // create new file with template
377 // SERVERCMD !
378 //
379 Buffer * NewLyxFile(string const & filename)
380 {
381         // Split argument by :
382         string name;
383         string tmpname = split(filename, name, ':');
384 #ifdef __EMX__ // Fix me! lyx_cb.C may not be low level enough to allow this.
385         if (name.length() == 1
386             && isalpha(static_cast<unsigned char>(name[0]))
387             && (prefixIs(tmpname, "/") || prefixIs(tmpname, "\\"))) {
388                 name += ':';
389                 name += token(tmpname, ':', 0);
390                 tmpname = split(tmpname, ':');
391         }
392 #endif
393         lyxerr.debug() << "Arg is " << filename
394                        << "\nName is " << name
395                        << "\nTemplate is " << tmpname << endl;
396
397         // find a free buffer 
398         Buffer * tmpbuf = bufferlist.newFile(name, tmpname);
399         if (tmpbuf)
400                 lastfiles->newFile(tmpbuf->fileName());
401         return tmpbuf;
402 }
403
404
405 // Insert ascii file (if filename is empty, prompt for one)
406 void InsertAsciiFile(BufferView * bv, string const & f, bool asParagraph)
407 {
408         string fname = f;
409
410         if (!bv->available()) 
411                 return;
412      
413         if (fname.empty()) {
414                 FileDialog fileDlg(bv->owner(), _("Select file to insert"),
415                         (asParagraph) ? LFUN_FILE_INSERT_ASCII_PARA : LFUN_FILE_INSERT_ASCII);
416  
417                 FileDialog::Result result = fileDlg.Select(bv->owner()->buffer()->filepath);
418
419                 if (result.first == FileDialog::Later)
420                         return;
421
422                 fname = result.second;
423
424                 if (fname.empty()) 
425                         return;
426         }
427
428         FileInfo fi(fname);
429
430         if (!fi.readable()) {
431                 WriteFSAlert(_("Error! Specified file is unreadable: "),
432                              MakeDisplayPath(fname, 50));
433                 return;
434         }
435
436         ifstream ifs(fname.c_str());
437         if (!ifs) {
438                 WriteFSAlert(_("Error! Cannot open specified file: "),
439                              MakeDisplayPath(fname, 50));
440                 return;
441         }
442
443         ifs.unsetf(ios::skipws);
444         istream_iterator<char> ii(ifs);
445         istream_iterator<char> end;
446 #if !defined(USE_INCLUDED_STRING) && !defined(STD_STRING_IS_GOOD)
447         // We use this until the compilers get better...
448         vector<char> tmp;
449         copy(ii, end, back_inserter(tmp));
450         string const tmpstr(tmp.begin(), tmp.end());
451 #else
452         // This is what we want to use and what we will use once the
453         // compilers get good enough. 
454         //string tmpstr(ii, end); // yet a reason for using std::string
455         // alternate approach to get the file into a string:
456         string tmpstr;
457         copy(ii, end, back_inserter(tmpstr));
458 #endif
459         // insert the string
460         bv->hideCursor();
461         
462         // clear the selection
463         bv->beforeChange(bv->text);
464         if (!asParagraph)
465                 bv->text->insertStringAsLines(bv, tmpstr);
466         else
467                 bv->text->insertStringAsParagraphs(bv, tmpstr);
468         bv->update(bv->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
469 }
470
471
472 void MenuInsertLabel(BufferView * bv, string const & arg)
473 {
474         string label(arg);
475         ProhibitInput(bv);
476         if (label.empty()) {
477                 Paragraph * par = bv->text->cursor.par();
478                 LyXLayout const * layout =
479                         &textclasslist.Style(bv->buffer()->params.textclass,
480                                              par->getLayout());
481
482                 if (layout->latextype == LATEX_PARAGRAPH && par->previous()) {
483                         Paragraph * par2 = par->previous();
484                         LyXLayout const * layout2 =
485                                 &textclasslist.Style(bv->buffer()->params.textclass,
486                                                      par2->getLayout());
487                         if (layout2->latextype != LATEX_PARAGRAPH) {
488                                 par = par2;
489                                 layout = layout2;
490                         }
491                 }
492                 string text = layout->latexname().substr(0, 3);
493                 if (layout->latexname() == "theorem")
494                         text = "thm"; // Create a correct prefix for prettyref
495
496                 text += ":";
497                 if (layout->latextype == LATEX_PARAGRAPH ||
498                     lyxrc.label_init_length < 0)
499                         text.erase();
500                 string par_text = par->asString(bv->buffer(), false);
501                 for (int i = 0; i < lyxrc.label_init_length; ++i) {
502                         if (par_text.empty())
503                                 break;
504                         string head;
505                         par_text = split(par_text, head, ' ');
506                         if (i > 0)
507                                 text += '-'; // Is it legal to use spaces in
508                                              // labels ?
509                         text += head;
510                 }
511
512                 pair<bool, string> result =
513                         askForText(_("Enter new label to insert:"), text);
514                 if (result.first) {
515                         label = frontStrip(strip(result.second));
516                 }
517         }
518         if (!label.empty()) {
519                 InsetCommandParams p( "label", label );
520                 InsetLabel * inset = new InsetLabel( p );
521                 bv->insertInset( inset );
522         }
523         AllowInput(bv);
524 }
525
526
527 void MenuLayoutSave(BufferView * bv)
528 {
529         if (!bv->available())
530                 return;
531
532         if (AskQuestion(_("Do you want to save the current settings"),
533                         _("for Character, Document, Paper and Quotes"),
534                         _("as default for new documents?")))
535                 bv->buffer()->saveParamsAsDefaults();
536 }
537
538
539 // This function runs "configure" and then rereads lyx.defaults to
540 // reconfigure the automatic settings.
541 void Reconfigure(BufferView * bv)
542 {
543         bv->owner()->message(_("Running configure..."));
544
545         // Run configure in user lyx directory
546         Path p(user_lyxdir);
547         Systemcalls one(Systemcalls::System, 
548                         AddName(system_lyxdir, "configure"));
549         p.pop();
550         bv->owner()->message(_("Reloading configuration..."));
551         lyxrc.read(LibFileSearch(string(), "lyxrc.defaults"));
552         WriteAlert(_("The system has been reconfigured."), 
553                    _("You need to restart LyX to make use of any"),
554                    _("updated document class specifications."));
555 }