]> git.lyx.org Git - lyx.git/blob - src/lyx_cb.C
fixed two consecutive spaces handling
[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-2000 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 "lyxfunc.h"
36 #include "gettext.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()->getLyXFunc()->Dispatch(LFUN_MESSAGE,
121                                                                 str);
122         } else
123                 lyxerr << msg1 << msg2 << msg3 << endl;
124 }
125
126
127 //
128 // Menu callbacks
129 //
130
131 //
132 // File menu
133 //
134 // should be moved to lyxfunc.C
135 bool MenuWrite(BufferView * bv, Buffer * buffer)
136 {
137         // FIXME: needed ?
138         XFlush(fl_get_display());
139  
140         if (!buffer->save()) {
141                 string const fname = buffer->fileName();
142                 string const s = MakeAbsPath(fname);
143                 if (AskQuestion(_("Save failed. Rename and try again?"),
144                                 MakeDisplayPath(s, 50),
145                                 _("(If not, document is not saved.)"))) {
146                         return WriteAs(bv, buffer);
147                 }
148                 return false;
149         } else
150                 lastfiles->newFile(buffer->fileName());
151         return true;
152 }
153
154
155
156 // should be moved to BufferView.C
157 // Half of this func should be in LyXView, the rest in BufferView.
158 bool WriteAs(BufferView * bv, Buffer * buffer, string const & filename)
159 {
160         string fname = buffer->fileName();
161         string oldname = fname;
162
163         if (filename.empty()) {
164
165                 FileDialog fileDlg(bv->owner(),
166                                    _("Choose a filename to save document as"),
167                         LFUN_WRITEAS,
168                         make_pair(string(_("Documents")),
169                                   string(lyxrc.document_path)),
170                         make_pair(string(_("Templates")),
171                                   string(lyxrc.template_path)));
172
173                 if (!IsLyXFilename(fname))
174                         fname += ".lyx";
175
176                 FileDialog::Result result =
177                         fileDlg.Select(OnlyPath(fname),
178                                        _("*.lyx|LyX Documents (*.lyx)"),
179                                        OnlyFilename(fname));
180
181                 if (result.first == FileDialog::Later)
182                         return false;
183
184                 fname = result.second;
185
186                 if (fname.empty())
187                         return false;
188
189                 // Make sure the absolute filename ends with appropriate suffix
190                 fname = MakeAbsPath(fname);
191                 if (!IsLyXFilename(fname))
192                         fname += ".lyx";
193         } else
194                 fname = filename;
195
196         // Same name as we have already?
197         if (!buffer->isUnnamed() && fname == oldname) {
198                 if (!AskQuestion(_("Same name as document already has:"),
199                                  MakeDisplayPath(fname, 50),
200                                  _("Save anyway?")))
201                         return false;
202                 // Falls through to name change and save
203         } 
204         // No, but do we have another file with this name open?
205         else if (!buffer->isUnnamed() && bufferlist.exists(fname)) {
206                 if (AskQuestion(_("Another document with same name open!"),
207                                 MakeDisplayPath(fname, 50),
208                                 _("Replace with current document?")))
209                         {
210                                 bufferlist.close(bufferlist.getBuffer(fname));
211
212                                 // Ok, change the name of the buffer, but don't save!
213                                 buffer->setFileName(fname);
214                                 buffer->markDirty();
215
216                                 ShowMessage(buffer, _("Document renamed to '"),
217                                                 MakeDisplayPath(fname), _("', but not saved..."));
218                 }
219                 return false;
220         } // Check whether the file exists
221         else {
222                 FileInfo const myfile(fname);
223                 if (myfile.isOK() && !AskQuestion(_("Document already exists:"), 
224                                                   MakeDisplayPath(fname, 50),
225                                                   _("Replace file?")))
226                         return false;
227         }
228
229         // Ok, change the name of the buffer
230         buffer->setFileName(fname);
231         buffer->markDirty();
232         bool unnamed = buffer->isUnnamed();
233         buffer->setUnnamed(false);
234
235         if (!MenuWrite(bv, buffer)) {
236             buffer->setFileName(oldname);
237             buffer->setUnnamed(unnamed);
238             ShowMessage(buffer, _("Document could not be saved!"),
239                         _("Holding the old name."), MakeDisplayPath(oldname));
240             return false;
241         }
242         // now remove the oldname autosave file if existant!
243         removeAutosaveFile(oldname);
244         return true;
245 }
246
247
248 int MenuRunChktex(Buffer * buffer)
249 {
250         int ret;
251
252         if (buffer->isSGML()) {
253                 WriteAlert(_("Chktex does not work with SGML derived documents."));
254                 return 0;
255         } else 
256                 ret = buffer->runChktex();
257    
258         if (ret >= 0) {
259                 string s;
260                 string t;
261                 if (ret == 0) {
262                         s = _("No warnings found.");
263                 } else if (ret == 1) {
264                         s = _("One warning found.");
265                         t = _("Use 'Edit->Go to Error' to find it.");
266                 } else {
267                         s += tostr(ret);
268                         s += _(" warnings found.");
269                         t = _("Use 'Edit->Go to Error' to find them.");
270                 }
271                 WriteAlert(_("Chktex run successfully"), s, t);
272         } else {
273                 WriteAlert(_("Error!"), _("It seems chktex does not work."));
274         }
275         return ret;
276 }
277
278
279 void QuitLyX()
280 {
281         lyxerr.debug() << "Running QuitLyX." << endl;
282
283         if (lyxrc.use_gui) {
284                 if (!bufferlist.QwriteAll())
285                         return;
286
287                 lastfiles->writeFile(lyxrc.lastfiles);
288         }
289
290         // Set a flag that we do quitting from the program,
291         // so no refreshes are necessary.
292         quitting = true;
293
294         // close buffers first
295         bufferlist.closeAll();
296
297         // do any other cleanup procedures now
298         lyxerr.debug() << "Deleting tmp dir " << system_tempdir << endl;
299
300         DestroyLyXTmpDir(system_tempdir);
301
302         finished = true;
303 }
304
305
306
307 void AutoSave(BufferView * bv)
308         // should probably be moved into BufferList (Lgb)
309         // Perfect target for a thread...
310 {
311         if (!bv->available())
312                 return;
313
314         if (bv->buffer()->isBakClean() || bv->buffer()->isReadonly()) {
315                 // We don't save now, but we'll try again later
316                 bv->owner()->resetAutosaveTimer();
317                 return;
318         }
319
320         bv->owner()->getLyXFunc()
321                 ->Dispatch(LFUN_MESSAGE,
322                            _("Autosaving current document..."));
323         
324         // create autosave filename
325         string fname =  OnlyPath(bv->buffer()->fileName());
326         fname += "#";
327         fname += OnlyFilename(bv->buffer()->fileName());
328         fname += "#";
329         
330         // tmp_ret will be located (usually) in /tmp
331         // will that be a problem?
332         pid_t const pid = fork(); // If you want to debug the autosave
333         // you should set pid to -1, and comment out the
334         // fork.
335         if (pid == 0 || pid == -1) {
336                 // pid = -1 signifies that lyx was unable
337                 // to fork. But we will do the save
338                 // anyway.
339                 bool failed = false;
340                 
341                 string const tmp_ret = lyx::tempName(string(), "lyxauto");
342                 if (!tmp_ret.empty()) {
343                         bv->buffer()->writeFile(tmp_ret, 1);
344                         // assume successful write of tmp_ret
345                         if (!lyx::rename(tmp_ret, fname)) {
346                                 failed = true;
347                                 // most likely couldn't move between filesystems
348                                 // unless write of tmp_ret failed
349                                 // so remove tmp file (if it exists)
350                                 lyx::unlink(tmp_ret);
351                         }
352                 } else {
353                         failed = true;
354                 }
355                 
356                 if (failed) {
357                         // failed to write/rename tmp_ret so try writing direct
358                         if (!bv->buffer()->writeFile(fname, 1)) {
359                                 // It is dangerous to do this in the child,
360                                 // but safe in the parent, so...
361                                 if (pid == -1)
362                                         bv->owner()->getLyXFunc()
363                                                 ->Dispatch(LFUN_MESSAGE,
364                                                            _("Autosave Failed!"));
365                         }
366                 }
367                 if (pid == 0) { // we are the child so...
368                         _exit(0);
369                 }
370         }
371         
372         bv->buffer()->markBakClean();
373         bv->owner()->resetAutosaveTimer();
374 }
375
376
377 //
378 // Copyright CHT Software Service GmbH
379 // Uwe C. Schroeder
380 //
381 // create new file with template
382 // SERVERCMD !
383 //
384 Buffer * NewLyxFile(string const & filename)
385 {
386         // Split argument by :
387         string name;
388         string tmpname = split(filename, name, ':');
389 #ifdef __EMX__ // Fix me! lyx_cb.C may not be low level enough to allow this.
390         if (name.length() == 1
391             && isalpha(static_cast<unsigned char>(name[0]))
392             && (prefixIs(tmpname, "/") || prefixIs(tmpname, "\\"))) {
393                 name += ':';
394                 name += token(tmpname, ':', 0);
395                 tmpname = split(tmpname, ':');
396         }
397 #endif
398         lyxerr.debug() << "Arg is " << filename
399                        << "\nName is " << name
400                        << "\nTemplate is " << tmpname << endl;
401
402         // find a free buffer 
403         Buffer * tmpbuf = bufferlist.newFile(name, tmpname);
404         if (tmpbuf)
405                 lastfiles->newFile(tmpbuf->fileName());
406         return tmpbuf;
407 }
408
409
410 // Insert ascii file (if filename is empty, prompt for one)
411 void InsertAsciiFile(BufferView * bv, string const & f, bool asParagraph)
412 {
413         string fname = f;
414
415         if (!bv->available()) 
416                 return;
417      
418         if (fname.empty()) {
419                 FileDialog fileDlg(bv->owner(), _("Select file to insert"),
420                         (asParagraph) ? LFUN_FILE_INSERT_ASCII_PARA : LFUN_FILE_INSERT_ASCII);
421  
422                 FileDialog::Result result = fileDlg.Select(bv->owner()->buffer()->filepath);
423
424                 if (result.first == FileDialog::Later)
425                         return;
426
427                 fname = result.second;
428
429                 if (fname.empty()) 
430                         return;
431         }
432
433         FileInfo fi(fname);
434
435         if (!fi.readable()) {
436                 WriteFSAlert(_("Error! Specified file is unreadable: "),
437                              MakeDisplayPath(fname, 50));
438                 return;
439         }
440
441         ifstream ifs(fname.c_str());
442         if (!ifs) {
443                 WriteFSAlert(_("Error! Cannot open specified file: "),
444                              MakeDisplayPath(fname, 50));
445                 return;
446         }
447
448         ifs.unsetf(ios::skipws);
449         istream_iterator<char> ii(ifs);
450         istream_iterator<char> end;
451 #if !defined(USE_INCLUDED_STRING) && !defined(STD_STRING_IS_GOOD)
452         // We use this until the compilers get better...
453         vector<char> tmp;
454         copy(ii, end, back_inserter(tmp));
455         string const tmpstr(tmp.begin(), tmp.end());
456 #else
457         // This is what we want to use and what we will use once the
458         // compilers get good enough. 
459         //string tmpstr(ii, end); // yet a reason for using std::string
460         // alternate approach to get the file into a string:
461         string tmpstr;
462         copy(ii, end, back_inserter(tmpstr));
463 #endif
464         // insert the string
465         bv->hideCursor();
466         
467         // clear the selection
468         bv->beforeChange(bv->text);
469         if (!asParagraph)
470                 bv->text->InsertStringA(bv, tmpstr);
471         else
472                 bv->text->InsertStringB(bv, tmpstr);
473         bv->update(bv->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
474 }
475
476
477 void MenuInsertLabel(BufferView * bv, string const & arg)
478 {
479         string label(arg);
480         ProhibitInput(bv);
481         if (label.empty()) {
482 #ifndef NEW_INSETS
483                 LyXParagraph * par =
484                         bv->text->cursor.par()->FirstPhysicalPar();
485 #else
486                 LyXParagraph * par = bv->text->cursor.par();
487 #endif
488                 LyXLayout const * layout =
489                         &textclasslist.Style(bv->buffer()->params.textclass,
490                                              par->GetLayout());
491
492 #ifndef NEW_INSETS
493                 if (layout->latextype == LATEX_PARAGRAPH && par->previous_) {
494                         LyXParagraph * par2 = par->previous_->FirstPhysicalPar();
495 #else
496                 if (layout->latextype == LATEX_PARAGRAPH && par->previous()) {
497                         LyXParagraph * par2 = par->previous();
498 #endif
499                         LyXLayout const * layout2 =
500                                 &textclasslist.Style(bv->buffer()->params.textclass,
501                                                      par2->GetLayout());
502                         if (layout2->latextype != LATEX_PARAGRAPH) {
503                                 par = par2;
504                                 layout = layout2;
505                         }
506                 }
507                 string text = layout->latexname().substr(0, 3);
508                 if (layout->latexname() == "theorem")
509                         text = "thm"; // Create a correct prefix for prettyref
510 #ifndef NEW_INSETS
511                 if (par->footnoteflag==LyXParagraph::OPEN_FOOTNOTE)
512                         switch (par->footnotekind) {
513                         case LyXParagraph::FIG:
514                         case LyXParagraph::WIDE_FIG:
515                                 text = "fig";
516                                 break;
517                         case LyXParagraph::TAB:
518                         case LyXParagraph::WIDE_TAB:
519                                 text = "tab";
520                                 break;
521                         case LyXParagraph::ALGORITHM:
522                                 text = "alg";
523                                 break;
524                         case LyXParagraph::FOOTNOTE:    
525                         case LyXParagraph::MARGIN:
526                                 break;
527                         }
528 #endif
529                 text += ":";
530                 if (layout->latextype == LATEX_PARAGRAPH ||
531                     lyxrc.label_init_length < 0)
532                         text.erase();
533                 string par_text = par->String(bv->buffer(), false);
534                 for (int i = 0; i < lyxrc.label_init_length; ++i) {
535                         if (par_text.empty())
536                                 break;
537                         string head;
538                         par_text = split(par_text, head, ' ');
539                         if (i > 0)
540                                 text += '-'; // Is it legal to use spaces in
541                                              // labels ?
542                         text += head;
543                 }
544
545                 pair<bool, string> result =
546                         askForText(_("Enter new label to insert:"), text);
547                 if (result.first) {
548                         label = frontStrip(strip(result.second));
549                 }
550         }
551         if (!label.empty()) {
552                 InsetCommandParams p( "label", label );
553                 InsetLabel * inset = new InsetLabel( p );
554                 bv->insertInset( inset );
555         }
556         AllowInput(bv);
557 }
558
559
560 void MenuLayoutSave(BufferView * bv)
561 {
562         if (!bv->available())
563                 return;
564
565         if (AskQuestion(_("Do you want to save the current settings"),
566                         _("for Character, Document, Paper and Quotes"),
567                         _("as default for new documents?")))
568                 bv->buffer()->saveParamsAsDefaults();
569 }
570
571
572 // This function runs "configure" and then rereads lyx.defaults to
573 // reconfigure the automatic settings.
574 void Reconfigure(BufferView * bv)
575 {
576         bv->owner()->getLyXFunc()->Dispatch(LFUN_MESSAGE,
577                                             _("Running configure..."));
578
579         // Run configure in user lyx directory
580         Path p(user_lyxdir);
581         Systemcalls one(Systemcalls::System, 
582                         AddName(system_lyxdir, "configure"));
583         p.pop();
584         bv->owner()->getLyXFunc()->Dispatch(LFUN_MESSAGE,
585                                             _("Reloading configuration..."));
586         lyxrc.read(LibFileSearch(string(), "lyxrc.defaults"));
587         WriteAlert(_("The system has been reconfigured."), 
588                    _("You need to restart LyX to make use of any"),
589                    _("updated document class specifications."));
590 }