]> git.lyx.org Git - lyx.git/blob - src/BufferView2.C
finished fixing removeAutoInsets()
[lyx.git] / src / BufferView2.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 "BufferView.h"
14 #include "buffer.h"
15 #include "lyxcursor.h"
16 #include "lyxtext.h"
17 #include "LyXView.h"
18 #include "bufferlist.h"
19 #include "lyxscreen.h"
20 #include "LaTeX.h"
21 #include "BufferView_pimpl.h"
22 #include "language.h"
23 #include "gettext.h"
24 #include "undo_funcs.h"
25 #include "debug.h"
26 #include "iterators.h"
27
28 #include "frontends/Alert.h"
29
30 #include "insets/insetcommand.h" //ChangeRefs
31 #include "insets/inseterror.h"
32
33 #include "support/FileInfo.h"
34 #include "support/filetools.h"
35 #include "support/lyxfunctional.h" //equal_1st_in_pair
36 #include "support/types.h"
37
38 #include <fstream>
39 #include <algorithm>
40
41 extern BufferList bufferlist;
42
43 using lyx::pos_type;
44
45 using std::pair;
46 using std::endl;
47 using std::ifstream;
48 using std::vector;
49 using std::find;
50 using std::count;
51 using std::count_if;
52
53
54 // Inserts a file into current document
55 bool BufferView::insertLyXFile(string const & filen)
56         //
57         // Copyright CHT Software Service GmbH
58         // Uwe C. Schroeder
59         //
60         // Insert a Lyxformat - file into current buffer
61         //
62         // Moved from lyx_cb.C (Lgb)
63 {
64         if (filen.empty()) return false;
65
66         string const fname = MakeAbsPath(filen);
67
68         // check if file exist
69         FileInfo const fi(fname);
70
71         if (!fi.readable()) {
72                 Alert::alert(_("Error!"),
73                            _("Specified file is unreadable: "),
74                            MakeDisplayPath(fname, 50));
75                 return false;
76         }
77         
78         beforeChange(text);
79
80         ifstream ifs(fname.c_str());
81         if (!ifs) {
82                 Alert::alert(_("Error!"),
83                            _("Cannot open specified file: "),
84                            MakeDisplayPath(fname, 50));
85                 return false;
86         }
87         
88         int const c = ifs.peek();
89        
90         LyXLex lex(0, 0);
91         lex.setStream(ifs);
92
93         bool res = true;
94
95         if (c == '#') {
96                 lyxerr[Debug::INFO] << "Will insert file with header" << endl;
97                 res = buffer()->readFile(lex, text->cursor.par());
98         } else {
99                 lyxerr[Debug::INFO] << "Will insert file without header" 
100                                     << endl;
101                 res = buffer()->readLyXformat2(lex, text->cursor.par());
102         }
103
104         resize();
105         return res;
106 }
107
108
109 bool BufferView::removeAutoInsets()
110 {
111         LyXCursor tmpcursor = text->cursor;
112         Paragraph * cur_par = tmpcursor.par();
113         Paragraph * cur_par_prev = cur_par ? cur_par->previous() : 0;
114         Paragraph * cur_par_next = cur_par ? cur_par->next() : 0;
115         pos_type cur_pos = tmpcursor.pos();
116
117         bool found = false;
118
119         // Trap the deletion of the paragraph the cursor is in.
120         // It should be almost impossible for the new cursor par to be
121         // deleted later on in this function.
122         // This is the way to segfault this now. Although you may have to do this
123         // multiple times: Have an InsetERT with an unknown command in it.
124         // View->DVI, move cursor between Error box and InsetERT and hit <Enter>,
125         // <down-arrow>, <Enter> again, View->DVI, BANG!
126         //
127         while ((cur_par_prev || cur_par_next)
128                && text->setCursor(this,
129                                   cur_par_prev ? cur_par_prev : cur_par_next,
130                                   0)) {
131                 // we just removed cur_par so have to fix the "cursor"
132                 if (cur_par_prev) {
133                         cur_par = cur_par_prev;
134                         cur_pos = cur_par->size();
135                 } else {
136                         cur_par = cur_par_next;
137                         cur_pos = 0;
138                 }
139                 cur_par_prev = cur_par->previous();
140                 cur_par_next = cur_par->next();
141         }
142
143         ParIterator it = buffer()->par_iterator_begin();
144         ParIterator end = buffer()->par_iterator_end();
145         for (; it != end; ++it) {
146                 Paragraph * par = *it;
147                 Paragraph * par_prev = par ? par->previous() : 0;
148                 bool removed = false;
149
150                 if (text->setCursor(this, par, 0)
151                     && cur_par == par_prev) {
152                         // The previous setCursor line was deleted and that
153                         // was the cur_par line.  This can only happen if an
154                         // error box was the sole item on cur_par.
155                         if (cur_par_prev) {
156                                 // '|' = par, '.' = cur_par, 'E' = error box
157                                 // First step below may occur before while{}
158                                 //  a    |a      a     a    .a
159                                 //  E -> .E -> |.E -> .  -> |b
160                                 // .      b      b    |b
161                                 //  b
162                                 cur_par = cur_par_prev;
163                                 cur_pos = cur_par_prev->size();
164                                 cur_par_prev = cur_par->previous();
165                                 // cur_par_next remains the same 
166                         } else if (cur_par_next) {
167                                 // First step below may occur before while{}
168                                 // .
169                                 //  E -> |.E -> |.  -> . -> .|a
170                                 //  a      a      a    |a
171                                 cur_par = cur_par_next;
172                                 cur_pos = 0;
173                                 // cur_par_prev remains unset
174                                 cur_par_next = cur_par->next();
175                         } else {
176                                 // I can't find a way to trigger this
177                                 // so it should be unreachable code
178                                 // unless the buffer is corrupted.
179                                 lyxerr << "BufferView::removeAutoInsets() is bad\n";
180                         }
181                 }
182
183                 Paragraph::inset_iterator pit = par->inset_iterator_begin();
184                 Paragraph::inset_iterator pend = par->inset_iterator_end();
185                 while (pit != pend) {
186                         if (pit->autoDelete()) {
187                                 removed = true;
188                                 pos_type const pos = pit.getPos();
189                                 
190                                 par->erase(pos);
191                                 if (cur_par == par) {
192                                         if (cur_pos > pos)
193                                                 --cur_pos;
194                                 }
195                         } else {
196                                 ++pit;
197                         }
198                 }
199                 if (removed) {
200                         found = true;
201                         text->redoParagraph(this);
202                 }
203         }
204
205         text->setCursorIntern(this, cur_par, cur_pos);
206
207         return found;
208 }
209
210
211 void BufferView::insertErrors(TeXErrors & terr)
212 {
213         // Save the cursor position
214         LyXCursor cursor = text->cursor;
215
216         TeXErrors::Errors::const_iterator cit = terr.begin();
217         TeXErrors::Errors::const_iterator end = terr.end();
218         for (; cit != end; ++cit) {
219                 string const desctext(cit->error_desc);
220                 string const errortext(cit->error_text);
221                 string const msgtxt = desctext + '\n' + errortext;
222                 int const errorrow = cit->error_in_line;
223
224                 // Insert error string for row number
225                 int tmpid = -1; 
226                 int tmppos = -1;
227
228                 if (buffer()->texrow.getIdFromRow(errorrow, tmpid, tmppos)) {
229                         buffer()->texrow.increasePos(tmpid, tmppos);
230                 }
231                 
232                 Paragraph * texrowpar = 0;
233
234                 if (tmpid == -1) {
235                         texrowpar = text->ownerParagraph();
236                         tmppos = 0;
237                 } else {
238                         texrowpar = buffer()->getParFromID(tmpid);
239                 }
240
241                 if (texrowpar == 0)
242                         continue;
243
244                 InsetError * new_inset = new InsetError(msgtxt);
245                 text->setCursorIntern(this, texrowpar, tmppos);
246                 text->insertInset(this, new_inset);
247                 text->fullRebreak(this);
248         }
249         // Restore the cursor position
250         text->setCursorIntern(this, cursor.par(), cursor.pos());
251 }
252
253
254 void BufferView::setCursorFromRow(int row)
255 {
256         int tmpid = -1; 
257         int tmppos = -1;
258
259         buffer()->texrow.getIdFromRow(row, tmpid, tmppos);
260
261         Paragraph * texrowpar;
262
263         if (tmpid == -1) {
264                 texrowpar = text->ownerParagraph();
265                 tmppos = 0;
266         } else {
267                 texrowpar = buffer()->getParFromID(tmpid);
268         }
269         text->setCursor(this, texrowpar, tmppos);
270 }
271
272
273 bool BufferView::insertInset(Inset * inset, string const & lout)
274 {
275         return pimpl_->insertInset(inset, lout);
276 }
277
278
279 /* This is also a buffer property (ale) */
280 // Not so sure about that. a goto Label function can not be buffer local, just
281 // think how this will work in a multiwindo/buffer environment, all the
282 // cursors in all the views showing this buffer will move. (Lgb)
283 // OK, then no cursor action should be allowed in buffer. (ale)
284 bool BufferView::gotoLabel(string const & label)
285
286 {
287         for (Buffer::inset_iterator it = buffer()->inset_iterator_begin();
288              it != buffer()->inset_iterator_end(); ++it) {
289                 vector<string> labels = (*it)->getLabelList();
290                 if (find(labels.begin(),labels.end(),label)
291                      != labels.end()) {
292                         beforeChange(text);
293                         text->setCursor(this, it.getPar(), it.getPos());
294                         text->selection.cursor = text->cursor;
295                         update(text, BufferView::SELECT|BufferView::FITCUR);
296                         return true;
297                 }
298         }
299         return false;
300 }
301
302
303 void BufferView::menuUndo()
304 {
305         if (available()) {
306                 owner()->message(_("Undo"));
307                 hideCursor();
308                 beforeChange(text);
309                 update(text, BufferView::SELECT|BufferView::FITCUR);
310                 if (!textUndo(this))
311                         owner()->message(_("No further undo information"));
312                 else
313                         update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
314                 setState();
315         }
316 }
317
318
319 void BufferView::menuRedo()
320 {
321 #if 0 // this should not be here (Jug 20011206)
322         if (theLockingInset()) {
323                 owner()->message(_("Redo not yet supported in math mode"));
324                 return;
325         }
326 #endif
327    
328         if (available()) {
329                 owner()->message(_("Redo"));
330                 hideCursor();
331                 beforeChange(text);
332                 update(text, BufferView::SELECT|BufferView::FITCUR);
333                 if (!textRedo(this))
334                         owner()->message(_("No further redo information"));
335                 else
336                         update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
337                 setState();
338         }
339 }
340
341
342 void BufferView::copyEnvironment()
343 {
344         if (available()) {
345                 text->copyEnvironmentType();
346                 owner()->message(_("Paragraph environment type copied"));
347         }
348 }
349
350
351 void BufferView::pasteEnvironment()
352 {
353         if (available()) {
354                 text->pasteEnvironmentType(this);
355                 owner()->message(_("Paragraph environment type set"));
356                 update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
357         }
358 }
359
360
361 void BufferView::copy()
362 {
363         if (available()) {
364                 text->copySelection(this);
365                 owner()->message(_("Copy"));
366         }
367 }
368
369
370 void BufferView::cut(bool realcut)
371 {
372         if (available()) {
373                 hideCursor();
374                 update(text, BufferView::SELECT|BufferView::FITCUR);
375                 text->cutSelection(this, true, realcut);
376                 update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
377                 owner()->message(_("Cut"));
378         }
379 }
380
381
382 void BufferView::paste()
383 {
384         if (!available()) return;
385
386         owner()->message(_("Paste"));
387
388         hideCursor();
389         // clear the selection
390         toggleSelection();
391         text->clearSelection();
392         update(text, BufferView::SELECT|BufferView::FITCUR);
393         
394         // paste
395         text->pasteSelection(this);
396         update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
397         
398         // clear the selection 
399         toggleSelection();
400         text->clearSelection();
401         update(text, BufferView::SELECT|BufferView::FITCUR);
402 }
403
404
405 /* these functions are for the spellchecker */ 
406 string const BufferView::nextWord(float & value)
407 {
408         if (!available()) {
409                 value = 1;
410                 return string();
411         }
412
413         return text->selectNextWordToSpellcheck(this, value);
414 }
415
416   
417 void BufferView::selectLastWord()
418 {
419         if (!available()) return;
420    
421         LyXCursor cur = text->selection.cursor;
422         hideCursor();
423         beforeChange(text);
424         text->selection.cursor = cur;
425         text->selectSelectedWord(this);
426         toggleSelection(false);
427         update(text, BufferView::SELECT|BufferView::FITCUR);
428 }
429
430
431 void BufferView::endOfSpellCheck()
432 {
433         if (!available()) return;
434    
435         hideCursor();
436         beforeChange(text);
437         text->selectSelectedWord(this);
438         text->clearSelection();
439         update(text, BufferView::SELECT|BufferView::FITCUR);
440 }
441
442
443 void BufferView::replaceWord(string const & replacestring)
444 {
445         if (!available()) return;
446
447         LyXText * tt = getLyXText();
448         hideCursor();
449         update(tt, BufferView::SELECT|BufferView::FITCUR);
450    
451         /* clear the selection (if there is any) */ 
452         toggleSelection(false);
453         update(tt, BufferView::SELECT|BufferView::FITCUR);
454    
455         /* clear the selection (if there is any) */ 
456         toggleSelection(false);
457         tt->replaceSelectionWithString(this, replacestring);
458    
459         tt->setSelectionOverString(this, replacestring);
460
461         // Go back so that replacement string is also spellchecked
462         for (string::size_type i = 0; i < replacestring.length() + 1; ++i) {
463                 tt->cursorLeft(this);
464         }
465         update(tt, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
466 }
467 // End of spellchecker stuff
468
469
470 bool BufferView::lockInset(UpdatableInset * inset)
471 {
472         if (!inset)
473                 return false;
474         // don't relock if we're already locked
475         if (theLockingInset() == inset)
476                 return true;
477         if (!theLockingInset()) {
478                 // first check if it's the inset under the cursor we want lock
479                 // should be most of the time
480                 char const c = text->cursor.par()->getChar(text->cursor.pos());
481                 if (c == Paragraph::META_INSET) {
482                         Inset * in = text->cursor.par()->getInset(text->cursor.pos());
483                         if (inset == in) {
484                                 theLockingInset(inset);
485                                 return true;
486                         }
487                 }
488                 // Then do a deep look of the inset and lock the right one
489                 Paragraph * par = buffer()->paragraph;
490                 int const id = inset->id();
491                 while(par) {
492                         Paragraph::inset_iterator it =
493                                 par->inset_iterator_begin();
494                         Paragraph::inset_iterator const end =
495                                 par->inset_iterator_end();
496                         for (; it != end; ++it) {
497                                 if ((*it) == inset) {
498                                         text->setCursorIntern(this, par, it.getPos());
499                                         theLockingInset(inset);
500                                         return true;
501                                 }
502                                 if ((*it)->getInsetFromID(id)) {
503                                         text->setCursorIntern(this, par, it.getPos());
504                                         theLockingInset(static_cast<UpdatableInset *>(*it));
505                                         return theLockingInset()->lockInsetInInset(this, inset);
506                                 }
507                         }
508                         par = par->next();
509                 }
510                 return false;
511         }
512         return theLockingInset()->lockInsetInInset(this, inset);
513 }
514
515
516 void BufferView::showLockedInsetCursor(int x, int y, int asc, int desc)
517 {
518         if (available() && theLockingInset() && !theLockingInset()->nodraw()) {
519                 LyXCursor cursor = text->cursor;
520                 Inset * locking_inset = theLockingInset()->getLockingInset();
521
522                 if ((cursor.pos() - 1 >= 0) &&
523                     cursor.par()->isInset(cursor.pos() - 1) &&
524                     (cursor.par()->getInset(cursor.pos() - 1) ==
525                      locking_inset))
526                         text->setCursor(this, cursor,
527                                         cursor.par(), cursor.pos() - 1);
528                 LyXScreen::Cursor_Shape shape = LyXScreen::BAR_SHAPE;
529                 LyXText * txt = getLyXText();
530                 if (locking_inset->isTextInset() &&
531                     locking_inset->lyxCode() != Inset::ERT_CODE &&
532                     (txt->real_current_font.language() !=
533                      buffer()->params.language
534                      || txt->real_current_font.isVisibleRightToLeft()
535                      != buffer()->params.language->RightToLeft()))
536                         shape = (txt->real_current_font.isVisibleRightToLeft())
537                                 ? LyXScreen::REVERSED_L_SHAPE
538                                 : LyXScreen::L_SHAPE;
539                 y += cursor.y() + theLockingInset()->insetInInsetY();
540                 pimpl_->screen_->showManualCursor(text, x, y, asc, desc,
541                                                   shape);
542         }
543 }
544
545
546 void BufferView::hideLockedInsetCursor()
547 {
548         if (theLockingInset() && available()) {
549                 pimpl_->screen_->hideCursor();
550         }
551 }
552
553
554 void BufferView::fitLockedInsetCursor(int x, int y, int asc, int desc)
555 {
556         if (theLockingInset() && available()) {
557                 y += text->cursor.y() + theLockingInset()->insetInInsetY();
558                 if (pimpl_->screen_->fitManualCursor(text, this, x, y, asc, desc))
559                         updateScrollbar();
560         }
561 }
562
563
564 int BufferView::unlockInset(UpdatableInset * inset)
565 {
566         if (!inset)
567                 return 0;
568         if (inset && theLockingInset() == inset) {
569                 inset->insetUnlock(this);
570                 theLockingInset(0);
571                 // make sure we update the combo !
572                 owner()->setLayout(getLyXText()->cursor.par()->getLayout());
573                 finishUndo();
574                 return 0;
575         } else if (inset && theLockingInset() &&
576                    theLockingInset()->unlockInsetInInset(this, inset)) {
577                 // owner inset has updated the layout combo 
578                 finishUndo();
579                 return 0;
580         }
581         return bufferlist.unlockInset(inset);
582 }
583
584
585 void BufferView::lockedInsetStoreUndo(Undo::undo_kind kind)
586 {
587         if (!theLockingInset())
588                 return; // shouldn't happen
589         if (kind == Undo::EDIT) // in this case insets would not be stored!
590                 kind = Undo::FINISH;
591         setUndo(this, kind,
592                 text->cursor.par(),
593                 text->cursor.par()->next());
594 }
595
596
597 void BufferView::updateInset(Inset * inset, bool mark_dirty)
598 {
599         pimpl_->updateInset(inset, mark_dirty);
600 }
601
602
603 bool BufferView::ChangeInsets(Inset::Code code,
604                               string const & from, string const & to)
605 {
606         bool need_update = false;
607         LyXCursor cursor = text->cursor;
608         LyXCursor tmpcursor = cursor;
609         cursor.par(tmpcursor.par());
610         cursor.pos(tmpcursor.pos());
611
612         ParIterator end = buffer()->par_iterator_end();
613         for (ParIterator it = buffer()->par_iterator_begin();
614              it != end; ++it) {
615                 Paragraph * par = *it;
616                 bool changed_inset = false;
617                 for (Paragraph::inset_iterator it2 = par->inset_iterator_begin();
618                      it2 != par->inset_iterator_end(); ++it2) {
619                         if ((*it2)->lyxCode() == code) {
620                                 InsetCommand * inset = static_cast<InsetCommand *>(*it2);
621                                 if (inset->getContents() == from) {
622                                         inset->setContents(to);
623                                         changed_inset = true;
624                                 }
625                         }
626                 }
627                 if (changed_inset) {
628                         need_update = true;
629 #ifdef WITH_WARNINGS
630 #warning FIXME
631 #endif
632                         // The test it.size()==1 was needed to prevent crashes.
633                         // How to set the cursor corretly when it.size()>1 ??
634                         if (it.size() == 1) {
635                                 text->setCursorIntern(this, par, 0);
636                                 text->redoParagraphs(this, text->cursor,
637                                                      text->cursor.par()->next());
638                                 text->fullRebreak(this);
639                         }
640                 }
641         }
642         text->setCursorIntern(this, cursor.par(), cursor.pos());
643         return need_update;
644 }
645
646
647 bool BufferView::ChangeRefsIfUnique(string const & from, string const & to)
648 {
649         // Check if the label 'from' appears more than once
650         vector<string> labels = buffer()->getLabelList();
651         // count is broken on some systems, so use the HP version
652         int res;
653         count(labels.begin(), labels.end(), from, res);
654         if (res > 1)
655                 return false;
656
657         return ChangeInsets(Inset::REF_CODE, from, to);
658 }
659
660
661 bool BufferView::ChangeCitationsIfUnique(string const & from, string const & to)
662 {
663
664         vector<pair<string,string> > keys = buffer()->getBibkeyList();  
665         if (count_if(keys.begin(), keys.end(), 
666                      lyx::equal_1st_in_pair<string,string>(from)) 
667             > 1)
668                 return false;
669
670         return ChangeInsets(Inset::CITE_CODE, from, to);
671 }
672
673
674 UpdatableInset * BufferView::theLockingInset() const
675 {
676         // If NULL is not allowed we should put an Assert here. (Lgb)
677         if (text)
678                 return text->the_locking_inset;
679         return 0;
680 }
681
682
683 void BufferView::theLockingInset(UpdatableInset * inset)
684 {
685         text->the_locking_inset = inset;
686 }
687
688
689 LyXText * BufferView::getLyXText() const
690 {
691         if (theLockingInset()) {
692                 LyXText * txt = theLockingInset()->getLyXText(this, true);
693                 if (txt)
694                         return txt;
695         }
696         return text;
697 }
698
699
700 LyXText * BufferView::getParentText(Inset * inset) const
701 {
702         if (inset->owner()) {
703                 LyXText * txt = inset->getLyXText(this);
704                 inset = inset->owner();
705                 while (inset && inset->getLyXText(this) == txt)
706                         inset = inset->owner();
707                 if (inset)
708                         return inset->getLyXText(this);
709         }
710         return text;
711 }
712
713
714 Language const * BufferView::getParentLanguage(Inset * inset) const
715 {
716         LyXText * text = getParentText(inset);
717         return text->cursor.par()->getFontSettings(buffer()->params,
718                                                    text->cursor.pos()).language();
719 }