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