]> git.lyx.org Git - lyx.git/blob - src/lyx_cb.C
ffd6289c8c58cd8bc560e75a1b9cecc6d11790bc
[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 #include "lyx.h"
20 #include "lyx_main.h"
21 #include "lyx_cb.h"
22 #include "insets/insetlabel.h"
23 #include "insets/figinset.h"
24 #include "minibuffer.h"
25 #include "bufferlist.h"
26 #include "frontends/FileDialog.h"
27 #include "lyx_gui_misc.h"
28 #include "LyXView.h"
29 #include "lastfiles.h"
30 #include "bufferview_funcs.h"
31 #include "support/FileInfo.h"
32 #include "support/syscall.h"
33 #include "support/filetools.h"
34 #include "support/path.h"
35 #include "lyxrc.h"
36 #include "lyxtext.h"
37
38 using std::vector;
39 using std::ifstream;
40 using std::copy;
41 using std::endl;
42 using std::back_inserter;
43 using std::istream_iterator;
44 using std::pair;
45 using std::make_pair;
46
47 extern BufferList bufferlist;
48 extern FD_form_figure * fd_form_figure;
49
50 extern BufferView * current_view; // called too many times in this file...
51
52 // this should be static, but I need it in buffer.C
53 bool quitting;  // flag, that we are quitting the program
54 extern bool finished; // all cleanup done just let it run through now.
55
56 /* 
57    This is the inset locking stuff needed for mathed --------------------
58
59    an inset can simple call LockInset in it's edit call and *ONLY* in it's
60    edit call.
61    Inset::Edit() can only be called by the main lyx module.
62
63    Then the inset may modify the menu's and/or iconbars. 
64
65    Unlocking is either done by LyX or the inset itself with a UnlockInset-call
66
67    During the lock, all button and keyboard events will be modified
68    and send to the inset through the following inset-features. Note that
69    Inset::InsetUnlock will be called from inside UnlockInset. It is meant
70    to contain the code for restoring the menus and things like this.
71
72    
73    virtual void InsetButtonPress(int x, int y, int button);
74    virtual void InsetButtonRelease(int x, int y, int button);
75    virtual void InsetKeyPress(XKeyEvent *ev);
76    virtual void InsetMotionNotify(int x, int y, int state);
77    virtual void InsetUnlock();
78
79    If a inset wishes any redraw and/or update it just has to call
80    UpdateInset(this).
81    It's is completly irrelevant, where the inset is. UpdateInset will
82    find it in any paragraph in any buffer. 
83    Of course the_locking_inset and the insets in the current paragraph/buffer
84    are checked first, so no performance problem should occur.
85    
86    Hope that's ok for the beginning, Alejandro,
87    sorry that I needed so much time,
88
89                   Matthias
90    */
91
92 //void UpdateInset(BufferView * bv, Inset * inset, bool mark_dirty = true);
93
94 /* these functions return 1 if an error occured, 
95    otherwise 0 */
96 // Now they work only for updatable insets. [Alejandro 080596]
97 //int LockInset(UpdatableInset * inset);
98 void ToggleLockedInsetCursor(int x, int y, int asc, int desc);
99 //void FitLockedInsetCursor(long x, long y, int asc, int desc);
100 //int UnlockInset(UpdatableInset * inset);
101 //void LockedInsetStoreUndo(Undo::undo_kind kind);
102
103 /* this is for asyncron updating. UpdateInsetUpdateList will be called
104    automatically from LyX. Just insert the Inset into the Updatelist */
105 //void UpdateInsetUpdateList();
106 //void PutInsetIntoInsetUpdateList(Inset * inset);
107
108 //InsetUpdateStruct * InsetUpdateList = 0;
109
110
111 /*
112   -----------------------------------------------------------------------
113  */
114
115
116 void ShowMessage(Buffer const * buf,
117                  string const & msg1,
118                  string const & msg2,
119                  string const & msg3, int delay)
120 {
121         if (lyxrc.use_gui)
122                 buf->getUser()->owner()->getMiniBuffer()->Set(msg1, msg2,
123                                                               msg3, delay);
124         else
125                 lyxerr << msg1 << msg2 << msg3 << endl;
126 }
127
128
129 //
130 // Menu callbacks
131 //
132
133 //
134 // File menu
135 //
136 // should be moved to lyxfunc.C
137 bool MenuWrite(BufferView * bv, Buffer * buffer)
138 {
139         // FIXME: needed ?
140         XFlush(fl_get_display());
141  
142         if (!buffer->save()) {
143                 string const fname = buffer->fileName();
144                 string const s = MakeAbsPath(fname);
145                 if (AskQuestion(_("Save failed. Rename and try again?"),
146                                 MakeDisplayPath(s, 50),
147                                 _("(If not, document is not saved.)"))) {
148                         return WriteAs(bv, buffer);
149                 }
150                 return false;
151         } else
152                 lastfiles->newFile(buffer->fileName());
153         return true;
154 }
155
156
157
158 // should be moved to BufferView.C
159 // Half of this func should be in LyXView, the rest in BufferView.
160 bool WriteAs(BufferView * bv, Buffer * buffer, string const & filename)
161 {
162         string fname = buffer->fileName();
163         string oldname = fname;
164
165         if (filename.empty()) {
166
167                 FileDialog fileDlg(bv->owner(),
168                                    _("Choose a filename to save document as"),
169                         LFUN_WRITEAS,
170                         make_pair(string(_("Documents")),
171                                   string(lyxrc.document_path)),
172                         make_pair(string(_("Templates")),
173                                   string(lyxrc.template_path)));
174
175                 if (!IsLyXFilename(fname))
176                         fname += ".lyx";
177
178                 FileDialog::Result result =
179                         fileDlg.Select(OnlyPath(fname),
180                                        _("*.lyx|LyX Documents (*.lyx)"),
181                                        OnlyFilename(fname));
182
183                 if (result.first == FileDialog::Later)
184                         return false;
185
186                 fname = result.second;
187
188                 if (fname.empty())
189                         return false;
190
191                 // Make sure the absolute filename ends with appropriate suffix
192                 fname = MakeAbsPath(fname);
193                 if (!IsLyXFilename(fname))
194                         fname += ".lyx";
195         } else
196                 fname = filename;
197
198         // Same name as we have already?
199         if (!buffer->isUnnamed() && fname == oldname) {
200                 if (!AskQuestion(_("Same name as document already has:"),
201                                  MakeDisplayPath(fname, 50),
202                                  _("Save anyway?")))
203                         return false;
204                 // Falls through to name change and save
205         } 
206         // No, but do we have another file with this name open?
207         else if (!buffer->isUnnamed() && bufferlist.exists(fname)) {
208                 if (AskQuestion(_("Another document with same name open!"),
209                                 MakeDisplayPath(fname, 50),
210                                 _("Replace with current document?")))
211                         {
212                                 bufferlist.close(bufferlist.getBuffer(fname));
213
214                                 // Ok, change the name of the buffer, but don't save!
215                                 buffer->setFileName(fname);
216                                 buffer->markDirty();
217
218                                 ShowMessage(buffer, _("Document renamed to '"),
219                                                 MakeDisplayPath(fname), _("', but not saved..."));
220                 }
221                 return false;
222         } // Check whether the file exists
223         else {
224                 FileInfo const myfile(fname);
225                 if (myfile.isOK() && !AskQuestion(_("Document already exists:"), 
226                                                   MakeDisplayPath(fname, 50),
227                                                   _("Replace file?")))
228                         return false;
229         }
230
231         // Ok, change the name of the buffer
232         buffer->setFileName(fname);
233         buffer->markDirty();
234         bool unnamed = buffer->isUnnamed();
235         buffer->setUnnamed(false);
236
237         if (!MenuWrite(bv, buffer)) {
238             buffer->setFileName(oldname);
239             buffer->setUnnamed(unnamed);
240             ShowMessage(buffer, _("Document could not be saved!"),
241                         _("Holding the old name."), MakeDisplayPath(oldname));
242             return false;
243         }
244         // now remove the oldname autosave file if existant!
245         removeAutosaveFile(oldname);
246         return true;
247 }
248
249
250 int MenuRunChktex(Buffer * buffer)
251 {
252         int ret;
253
254         if (buffer->isSGML()) {
255                 WriteAlert(_("Chktex does not work with SGML derived documents."));
256                 return 0;
257         } else 
258                 ret = buffer->runChktex();
259    
260         if (ret >= 0) {
261                 string s;
262                 string t;
263                 if (ret == 0) {
264                         s = _("No warnings found.");
265                 } else if (ret == 1) {
266                         s = _("One warning found.");
267                         t = _("Use 'Edit->Go to Error' to find it.");
268                 } else {
269                         s += tostr(ret);
270                         s += _(" warnings found.");
271                         t = _("Use 'Edit->Go to Error' to find them.");
272                 }
273                 WriteAlert(_("Chktex run successfully"), s, t);
274         } else {
275                 WriteAlert(_("Error!"), _("It seems chktex does not work."));
276         }
277         return ret;
278 }
279
280
281 void QuitLyX()
282 {
283         lyxerr.debug() << "Running QuitLyX." << endl;
284
285         if (lyxrc.use_gui) {
286                 if (!bufferlist.QwriteAll())
287                         return;
288
289                 lastfiles->writeFile(lyxrc.lastfiles);
290         }
291
292         // Set a flag that we do quitting from the program,
293         // so no refreshes are necessary.
294         quitting = true;
295
296         // close buffers first
297         bufferlist.closeAll();
298
299         // do any other cleanup procedures now
300         lyxerr.debug() << "Deleting tmp dir " << system_tempdir << endl;
301
302         DestroyLyXTmpDir(system_tempdir);
303
304         finished = true;
305 }
306
307
308
309 void AutoSave(BufferView * bv)
310         // should probably be moved into BufferList (Lgb)
311         // Perfect target for a thread...
312 {
313         if (!bv->available())
314                 return;
315
316         if (bv->buffer()->isBakClean() || bv->buffer()->isReadonly()) {
317                 // We don't save now, but we'll try again later
318                 bv->owner()->resetAutosaveTimer();
319                 return;
320         }
321
322         bv->owner()->getMiniBuffer()->Set(_("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()->getMiniBuffer()->Set(_("Autosave Failed!"));
363                         }
364                 }
365                 if (pid == 0) { // we are the child so...
366                         _exit(0);
367                 }
368         }
369         
370         bv->buffer()->markBakClean();
371         bv->owner()->resetAutosaveTimer();
372 }
373
374
375 //
376 // Copyright CHT Software Service GmbH
377 // Uwe C. Schroeder
378 //
379 // create new file with template
380 // SERVERCMD !
381 //
382 Buffer * NewLyxFile(string const & filename)
383 {
384         // Split argument by :
385         string name;
386         string tmpname = split(filename, name, ':');
387 #ifdef __EMX__ // Fix me! lyx_cb.C may not be low level enough to allow this.
388         if (name.length() == 1
389             && isalpha(static_cast<unsigned char>(name[0]))
390             && (prefixIs(tmpname, "/") || prefixIs(tmpname, "\\"))) {
391                 name += ':';
392                 name += token(tmpname, ':', 0);
393                 tmpname = split(tmpname, ':');
394         }
395 #endif
396         lyxerr.debug() << "Arg is " << filename
397                        << "\nName is " << name
398                        << "\nTemplate is " << tmpname << endl;
399
400         // find a free buffer 
401         Buffer * tmpbuf = bufferlist.newFile(name, tmpname);
402         if (tmpbuf)
403                 lastfiles->newFile(tmpbuf->fileName());
404         return tmpbuf;
405 }
406
407
408 // Insert ascii file (if filename is empty, prompt for one)
409 void InsertAsciiFile(BufferView * bv, string const & f, bool asParagraph)
410 {
411         string fname = f;
412
413         if (!bv->available()) 
414                 return;
415      
416         if (fname.empty()) {
417                 FileDialog fileDlg(bv->owner(), _("Select file to insert"),
418                         (asParagraph) ? LFUN_FILE_INSERT_ASCII_PARA : LFUN_FILE_INSERT_ASCII);
419  
420                 FileDialog::Result result = fileDlg.Select(bv->owner()->buffer()->filepath);
421
422                 if (result.first == FileDialog::Later)
423                         return;
424
425                 fname = result.second;
426
427                 if (fname.empty()) 
428                         return;
429         }
430
431         FileInfo fi(fname);
432
433         if (!fi.readable()) {
434                 WriteFSAlert(_("Error! Specified file is unreadable: "),
435                              MakeDisplayPath(fname, 50));
436                 return;
437         }
438
439         ifstream ifs(fname.c_str());
440         if (!ifs) {
441                 WriteFSAlert(_("Error! Cannot open specified file: "),
442                              MakeDisplayPath(fname, 50));
443                 return;
444         }
445
446         ifs.unsetf(ios::skipws);
447         istream_iterator<char> ii(ifs);
448         istream_iterator<char> end;
449 #if !defined(USE_INCLUDED_STRING) && !defined(STD_STRING_IS_GOOD)
450         // We use this until the compilers get better...
451         vector<char> tmp;
452         copy(ii, end, back_inserter(tmp));
453         string const tmpstr(tmp.begin(), tmp.end());
454 #else
455         // This is what we want to use and what we will use once the
456         // compilers get good enough. 
457         //string tmpstr(ii, end); // yet a reason for using std::string
458         // alternate approach to get the file into a string:
459         string tmpstr;
460         copy(ii, end, back_inserter(tmpstr));
461 #endif
462         // insert the string
463         bv->hideCursor();
464         
465         // clear the selection
466         bv->beforeChange(bv->text);
467         if (!asParagraph)
468                 bv->text->InsertStringA(bv, tmpstr);
469         else
470                 bv->text->InsertStringB(bv, tmpstr);
471         bv->update(bv->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
472 }
473
474
475 void MenuInsertLabel(BufferView * bv, string const & arg)
476 {
477         string label(arg);
478         ProhibitInput(bv);
479         if (label.empty()) {
480 #ifndef NEW_INSETS
481                 LyXParagraph * par =
482                         bv->text->cursor.par()->FirstPhysicalPar();
483 #else
484                 LyXParagraph * par = bv->text->cursor.par();
485 #endif
486                 LyXLayout const * layout =
487                         &textclasslist.Style(bv->buffer()->params.textclass,
488                                              par->GetLayout());
489
490 #ifndef NEW_INSETS
491                 if (layout->latextype == LATEX_PARAGRAPH && par->previous_) {
492                         LyXParagraph * par2 = par->previous_->FirstPhysicalPar();
493 #else
494                 if (layout->latextype == LATEX_PARAGRAPH && par->previous()) {
495                         LyXParagraph * par2 = par->previous();
496 #endif
497                         LyXLayout const * layout2 =
498                                 &textclasslist.Style(bv->buffer()->params.textclass,
499                                                      par2->GetLayout());
500                         if (layout2->latextype != LATEX_PARAGRAPH) {
501                                 par = par2;
502                                 layout = layout2;
503                         }
504                 }
505                 string text = layout->latexname().substr(0, 3);
506                 if (layout->latexname() == "theorem")
507                         text = "thm"; // Create a correct prefix for prettyref
508 #ifndef NEW_INSETS
509                 if (par->footnoteflag==LyXParagraph::OPEN_FOOTNOTE)
510                         switch (par->footnotekind) {
511                         case LyXParagraph::FIG:
512                         case LyXParagraph::WIDE_FIG:
513                                 text = "fig";
514                                 break;
515                         case LyXParagraph::TAB:
516                         case LyXParagraph::WIDE_TAB:
517                                 text = "tab";
518                                 break;
519                         case LyXParagraph::ALGORITHM:
520                                 text = "alg";
521                                 break;
522                         case LyXParagraph::FOOTNOTE:    
523                         case LyXParagraph::MARGIN:
524                                 break;
525                         }
526 #endif
527                 text += ":";
528                 if (layout->latextype == LATEX_PARAGRAPH ||
529                     lyxrc.label_init_length < 0)
530                         text.erase();
531                 string par_text = par->String(bv->buffer(), false);
532                 for (int i = 0; i < lyxrc.label_init_length; ++i) {
533                         if (par_text.empty())
534                                 break;
535                         string head;
536                         par_text = split(par_text, head, ' ');
537                         if (i > 0)
538                                 text += '-'; // Is it legal to use spaces in
539                                              // labels ?
540                         text += head;
541                 }
542
543                 pair<bool, string> result =
544                         askForText(_("Enter new label to insert:"), text);
545                 if (result.first) {
546                         label = frontStrip(strip(result.second));
547                 }
548         }
549         if (!label.empty()) {
550                 InsetCommandParams p( "label", label );
551                 InsetLabel * inset = new InsetLabel( p );
552                 bv->insertInset( inset );
553         }
554         AllowInput(bv);
555 }
556
557
558 void MenuLayoutSave(BufferView * bv)
559 {
560         if (!bv->available())
561                 return;
562
563         if (AskQuestion(_("Do you want to save the current settings"),
564                         _("for Character, Document, Paper and Quotes"),
565                         _("as default for new documents?")))
566                 bv->buffer()->saveParamsAsDefaults();
567 }
568
569
570 void Figure()
571 {
572         if (fd_form_figure->form_figure->visible) {
573                 fl_raise_form(fd_form_figure->form_figure);
574         } else {
575                 fl_show_form(fd_form_figure->form_figure,
576                              FL_PLACE_MOUSE | FL_FREE_SIZE, FL_TRANSIENT,
577                              _("Insert Figure"));
578         }
579 }
580
581
582 /* callbacks for form form_figure */
583 extern "C"
584 void FigureApplyCB(FL_OBJECT *, long)
585 {
586         if (!current_view->available())
587                 return;
588
589         Buffer * buffer = current_view->buffer();
590         if (buffer->isReadonly()) // paranoia
591                 return;
592         
593         current_view->owner()->getMiniBuffer()->Set(_("Inserting figure..."));
594         if (fl_get_button(fd_form_figure->radio_inline)) {
595                 InsetFig * new_inset = new InsetFig(100, 20, *buffer);
596                 current_view->insertInset(new_inset);
597                 current_view->owner()->getMiniBuffer()->Set(_("Figure inserted"));
598                 new_inset->Edit(current_view, 0, 0, 0);
599                 return;
600         }
601         
602         current_view->hideCursor();
603         current_view->update(current_view->text, BufferView::SELECT|BufferView::FITCUR);
604         current_view->beforeChange(current_view->text);
605       
606         current_view->text->SetCursorParUndo(current_view->buffer()); 
607         current_view->text->FreezeUndo();
608
609         current_view->text->BreakParagraph(current_view);
610         current_view->update(current_view->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
611
612 #ifndef NEW_INSETS
613         if (current_view->text->cursor.par()->Last()) {
614 #else
615         if (current_view->text->cursor.par()->size()) {
616 #endif
617                 current_view->text->CursorLeft(current_view);
618          
619                 current_view->text->BreakParagraph(current_view);
620                 current_view->update(current_view->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
621         }
622
623         // The standard layout should always be numer 0;
624         current_view->text->SetLayout(current_view, 0);
625
626 #ifndef NEW_INSETS
627         if (current_view->text->cursor.par()->footnoteflag == 
628             LyXParagraph::NO_FOOTNOTE) {
629 #endif
630                 current_view->text->
631                         SetParagraph(current_view, 0, 0,
632                                      0, 0,
633                                      VSpace (0.3 * buffer->params.spacing.getValue(),
634                                              LyXLength::CM),
635                                      VSpace (0.3 *
636                                              buffer->params.spacing.getValue(),
637                                              LyXLength::CM),
638                                      LYX_ALIGN_CENTER, string(), 0);
639 #ifndef NEW_INSETS
640         } else {
641                 current_view->text->SetParagraph(current_view, 0, 0,
642                                                  0, 0,
643                                                  VSpace(VSpace::NONE),
644                                                  VSpace(VSpace::NONE),
645                                                  LYX_ALIGN_CENTER, 
646                                                  string(),
647                                                  0);
648         }
649 #endif
650         
651         current_view->update(current_view->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
652       
653         Inset * new_inset = new InsetFig(100, 100, *buffer);
654         current_view->insertInset(new_inset);
655         new_inset->Edit(current_view, 0, 0, 0);
656         current_view->update(current_view->text, BufferView::SELECT|BufferView::FITCUR);
657         current_view->owner()->getMiniBuffer()->Set(_("Figure inserted"));
658         current_view->text->UnFreezeUndo();
659         current_view->setState();
660 }
661
662
663 extern "C"
664 void FigureCancelCB(FL_OBJECT *, long)
665 {
666         fl_hide_form(fd_form_figure->form_figure);
667 }
668
669
670 extern "C"
671 void FigureOKCB(FL_OBJECT * ob, long data)
672 {
673         FigureApplyCB(ob, data);
674         FigureCancelCB(ob, data);
675 }
676
677
678 // This function runs "configure" and then rereads lyx.defaults to
679 // reconfigure the automatic settings.
680 void Reconfigure(BufferView * bv)
681 {
682         bv->owner()->getMiniBuffer()->Set(_("Running configure..."));
683
684         // Run configure in user lyx directory
685         Path p(user_lyxdir);
686         Systemcalls one(Systemcalls::System, 
687                         AddName(system_lyxdir, "configure"));
688         p.pop();
689         bv->owner()->getMiniBuffer()->Set(_("Reloading configuration..."));
690         lyxrc.read(LibFileSearch(string(), "lyxrc.defaults"));
691         WriteAlert(_("The system has been reconfigured."), 
692                    _("You need to restart LyX to make use of any"),
693                    _("updated document class specifications."));
694 }