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